Penumbra Guide
Penumbra is a fully shielded zone for the Cosmos ecosystem, allowing anyone to securely transact, stake, swap, or marketmake without broadcasting their personal information to the world.
This site contains documentation on how to use, deploy, and develop the Penumbra software. The description of the protocol itself can be found in the protocol specification, and the API documentation can be found here.
Test networks
Penumbra is a decentralized protocol, so Penumbra Labs is building in public, launching (and crashing) lots of work-in-progress testnets to allow community participation, engagement, and feedback.
Currently, Penumbra only has a command line client, pcli
(pronounced
“pickle-y”), which bundles all of the client components in one binary, and a
chain-scanning daemon, pclientd
, which runs just the view service, without spend
capability. To get started with the Penumbra test network, all that’s required
is to download and build pcli
, as described in
Installation.
The Penumbra node software is the Penumbra daemon, pd
. This is an ABCI
application, which must be driven by Tendermint, so a Penumbra full node
consists of both a pd
instance and a tendermint
instance.
The basic architecture of Penumbra is as follows:
╭ ┌───────┐
spending│ │custody│
capability│ │service│
╰ └───────┘
▲ │
│tx │auth
│plan │data
│ ▼
╭ ┌───────┐
viewing│ │wallet │ tx submission
capability│ │logic │────────┐
│ └───────┘ │
│ ▲ │
│ │view private state
│ │ │
│ │ │
│ ┌───────┐ │
│ │view │ │
│ │service│ │
╰ └───────┘ │
▲ │
│sync private state
│ │
╭ ┌──┼───────────────┼──────┐
public│ │ │ Penumbra Fullnode│
chain│ │ │ │ │
data│ │ │ ▼ │
│ │ ┌──┐ app ┌──────────┐ │
│ │ │pd│◀─────▶│tendermint│ │
│ │ └──┘ sync └──────────┘ │
│ │ ▲ │
╰ └───────────────┼─────────┘
.──│.
,' │ `.
.───; │consensus
; │sync
.─┤ │ ├──.
,' │ `.
; Penumbra │ :
: Network ◀──┘ ;
╲ ╱
`. `. `. ,'
`───' `───' `───'
The custody service holds signing keys and is responsible for authorizing transaction plans. The view service holds viewing keys and scans the chain state. Wallet logic can query the view service to get information about what funds are available, submit a transaction plan to the custody service for signing, and then use the returned signatures to build the transaction and submit it.
As a shielded chain, Penumbra’s architecture is slightly different than a
transparent chain, because user data such as account balances, transaction
activity, etc., is not part of the public chain state. This means that clients
need to synchronize with the chain to build a copy of the private user data they
have access to. This logic is provided by the view service, which is bundled
into pcli
, but can also be run as a standalone pclientd
daemon.
Modeling authorization as an (asynchronous) RPC to a custody service means that the client software is compatible with many different custody flows by default – an in-process “SoftHSM”, a hardware wallet with user intervention, a cluster of online threshold signers, an offline threshold signing process, etc.
Using pcli
This section describes how to use pcli
, the command line client for Penumbra:
- Installation describes how to compile and run
pcli
; - Generating a Wallet describes how to generate a wallet and use the testnet faucet;
- Updating pcli describes how to update to a newly released testnet from a previous testnet;
- Viewing Balances describes how to view balances;
- Sending Transactions describes how to send funds.
Penumbra is a private blockchain, so the public chain state does not reveal any
private user data. By default, pcli
includes a view service that
synchronizes with the chain and scans with a viewing key. However, it’s also
possible to run the view service as a standalone pclientd
daemon:
- Using
pcli
withpclientd
describes how to usepcli
withpclientd
.
Please submit any feedback and bug reports
Thank you for helping us test the Penumbra network! If you have any feedback, please let us know in
the #testnet-feedback
channel on our Discord. We would love to know about bugs, crashes,
confusing error messages, or any of the many other things that inevitably won’t quite work yet. Have
fun! :)
Diagnostics and Warnings
By default, pcli
prints a warning message to the terminal, to be sure that people understand that this is unstable, unfinished, pre-release software.
To disable this warning, export the PCLI_UNLEASH_DANGER
environment variable.
Installing pcli
Installing the Rust toolchain
This requires that you install a recent stable version
of the Rust compiler, installation instructions for which you can find
here. Don’t forget to reload your shell so that
cargo
is available in your \$PATH
!
pcli
requires rustfmt
as part of the build process — depending on your
OS/install method for Rust, you may have to install that separately.
Installing build prerequisites
Linux
You may need to install some additional packages in order to build pcli
,
depending on your distribution. For a bare-bones Ubuntu installation, you can
run:
sudo apt-get install build-essential pkg-config libssl-dev clang git-lfs
For a minimal Fedora/CentOS/RHEL image, you can run:
sudo dnf install openssl-devel clang git cargo rustfmt git-lfs
macOS
You may need to install the command-line developer tools if you have never done so:
xcode-select --install
You’ll also need to install Git LFS, which you can do via Homebrew:
brew install git-lfs
Cloning the repository
Once you have installed the above tools, you can clone the repository:
git clone https://github.com/penumbra-zone/penumbra
To build the version of pcli
compatible with the current testnet, navigate to
the penumbra folder, fetch the latest from the repository, and check out the
latest tag for the current
testnet:
cd penumbra && git fetch && git checkout 048-carme.1
Building the pcli
client software
Then, build the pcli
tool using cargo
:
cargo build --release --bin pcli
Because you are building a work-in-progress version of the client, you may see compilation warnings, which you can safely ignore.
Generating a Wallet
On first installation of pcli
, you will need to generate a fresh wallet to use with Penumbra. You
should see something like this:
$ cargo run --quiet --release --bin pcli keys generate
YOUR PRIVATE SEED PHRASE: [...]
DO NOT SHARE WITH ANYONE!
Saving backup wallet to /home/$USER/.local/share/penumbra-testnet-archive/.../custody.json
Penumbra’s design automatically creates many (u64::MAX
) publicly unlinkable addresses which all
correspond to your own wallet. When you first created your wallet above, pcli
initialized all
of your wallet addresses, which you can view like this:
$ cargo run --quiet --release --bin pcli view address 0
penumbrav2t1...
Getting testnet tokens on the [Discord] in the #testnet-faucet
channel
In order to use the testnet, it’s first necessary for you to get some testnet tokens. The current
way to do this is to join our Discord and post your address in the #testnet-faucet
channel.
We’ll send your address some tokens on the testnet for you to send to your friends! :)
Just keep in mind: testnet tokens do not have monetary value, and in order to keep the signal-to-noise ratio high on the server, requests for tokens in other channels will be deleted without response. Please do not DM Penumbra Labs employees asking for testnet tokens; the correct venue is the dedicated channel.
Updating pcli
Make sure you’ve followed the installation steps. Then, to update to the latest testnet release:
cd penumbra && git fetch && git checkout 048-carme.1
Once again, build pcli
with cargo:
cargo build --release --bin pcli
No wallet needs to be generated. Instead, keep one’s existing wallet and reset view data.
cargo run --quiet --release --bin pcli view reset
Viewing Balances
Once you’ve received your first tokens, you can scan the chain to import them into your local wallet (this may take a few minutes the first time you run it):
cargo run --quiet --release --bin pcli view sync
Syncing is performed automatically, but running the sync
subcommand will
ensure that the client state is synced to a recent state, so that future
invocations of pcli
commands don’t need to wait.
If someone sent you testnet assets, you should be able to see them now by running:
cargo run --quiet --release --bin pcli view balance
This will print a table of assets by balance in each. The balance
view just
shows asset amounts. To see more information about delegation tokens and the stake they represent, use
cargo run --quiet --release --bin pcli view staked
Sending Transactions
Now, for the fun part: sending transactions. If you have someone else’s testnet address, you can send them any amount of any asset you have.
First, use balance to find the amount of assets you have:
cargo run --release --bin pcli view balance
Second, if I wanted to send 10 penumbra tokens to my friend, I could do that like this (filling in their full address at the end):
cargo run --quiet --release --bin pcli tx send 10penumbra --to penumbrav2t...
Notice that asset amounts are typed amounts, specified without a space between the amount (10
)
and the asset name (penumbra
). If you have the asset in your wallet to send, then so it shall be done!
Staking
In addition, to sending an asset, one may also stake penumbra tokens to validators.
Find a validator to stake to:
cargo run --release --bin pcli query validator list
Copy and paste the identity key of one of the validators to stake to, then construct the staking tx:
cargo run --release --bin pcli tx delegate 10penumbra --to penumbravalid...
To undelegate from a validator, use the pcli tx undelegate
command, passing it the typed amount of
delegation tokens you wish to undelegate. Wait a moment for the network to process the undelegation,
then reclaim your funds:
cargo run --release --bin pcli tx undelegate-claim
Inspect the output; a message may instruct you to wait longer, for a new epoch. Check back and rerun the command later to add the previously delegated funds to your wallet.
Governance
Penumbra features on-chain governance similar to Cosmos Hub where anyone can submit proposals and both validators and delegators to vote on them. Penumbra’s governance model incorporates a single DAO account, into which anyone can freely deposit, but from which only a successful governance vote can spend. For details on using governance, see the governance section.
Swapping Assets
One of the most exciting features of Penumbra is that by using IBC (inter-blockchain communication) and our shielded pool design, any tokens can be exchanged in a private way.
NOTE: Since there’s not yet a way to open liquidity positions, we have implemented a stub
Uniswap-V2-style constant-product market maker with some
hardcoded liquidity for a few trading pairs: gm:gn
, penumbra:gm
, and penumbra:gn
.
This allows testing the shielded pool integration, and will cause a floating exchange rate based on the volume of swaps occurring in each pair.
You can check the current reserves for a trading pair using the cpmm-reserves
dex query:
cargo run --release --bin pcli -- q dex cpmm-reserves gm:penumbra
If you wanted to exchange 1 penumbra
tokens for gm
tokens, you could do so like so:
cargo run --release --bin pcli -- tx swap --into gm 1penumbra
This will handle generating the swap transaction and you’d soon have the market-rate equivalent of 1 penumbra
in gm
tokens returned to you, or the original investment of 1 penumbra
tokens returned if there wasn’t
enough liquidity available to perform the swap.
Governance
Penumbra features on-chain governance similar to Cosmos Hub, with the simplification that there are only 3 kinds of vote: yes, no, and abstain.
Quick Start
There’s a lot you can do with the governance system in Penumbra. If you have a particular intention in mind, here are some quick links:
- I am a delegator and I want to vote on a proposal.
- I am a validator and I want to vote on a proposal.
- I want to learn about the different kinds of proposal.
- I want to submit a new proposal.
- I submitted a proposal and I want to withdraw it before voting concludes.
- Voting has concluded on a proposal I submitted and I want to claim my deposit.
- I want to contribute funds to the DAO.
Getting Proposal Information
To see information about the currently active proposals, including your own, use the pcli query proposal
subcommand.
To list all the active proposals by their ID, use:
cargo run --release --bin pcli query governance list-proposals
Other proposal query commands all follow the form:
cargo run --release --bin pcli query governance proposal [PROPOSAL_ID] [QUERY]
These are the queries currently defined:
definition
gets the details of a proposal, as the submitted JSON;state
gets information about the current state of a proposal (voting, withdrawn, or finished, along with the reason for withdrawal if any, and the outcome of finished proposals);period
gets the voting start and end block heights of a proposal;tally
gets the current tally of a proposal’s votes, as a total across all validators, and broken down by each validator’s votes and the total votes of their delegators.
Voting On A Proposal
Validators and delegators may both vote on proposals. Validator votes are public and attributable to that validator; delegator votes are anonymous, revealing only the voting power used in the vote, and the validator which the voting delegator had delegated to. Neither validators nor delegators can change their votes after they have voted.
Voting As A Delegator
If you had staked delegation tokens to one or more active validators when a proposal started, you
can vote on it using the tx vote
subcommand of pcli
. For example, if you wanted to vote “yes” on
proposal 1, you would do:
cargo run --release --bin pcli tx vote yes --on 1
When you vote as a delegator (but not when you vote as a validator), you will receive
commemorative voted_on_N
tokens, where N
is the proposal ID, proportionate to the weight of your
vote. Think of these as the cryptocurrency equivalent of the “I voted!” stickers you may have
received when voting in real life at your polling place.
Voting As A Validator
If you are a validator who was active when the proposal started, you can vote on it using the
validator vote
subcommand of pcli
. For example, if you wanted to vote “yes” on proposal 1, you
would do:
cargo run --release --bin pcli validator vote yes --on 1
Eligibility And Voting Power
Only validators who were active at the time the proposal started voting may vote on proposals. Only delegators who had staked delegation tokens to active validators at the time the proposal started voting may vote on proposals.
A validator’s voting power is equal to their voting power at the time a proposal started voting, and
a delegator’s voting power is equal to the unbonded staking token value (i.e. the value in
penumbra
) of the delegation tokens they had staked to an active validator at the time the proposal
started voting. When a delegator votes, their voting power is subtracted from the voting power of
the validator(s) to whom they had staked delegation notes at the time of the proposal start, and
their stake-weighted vote is added to the total of the votes: in other words, validators vote on
behalf of their delegators, but delegators may override their portion of their validator’s vote.
Authoring A Proposal
Anyone can submit a new governance proposal for voting by escrowing a proposal deposit, which will be held until the end of the proposal’s voting period. Penumbra’s governance system discourages proposal spam with a slashing mechanism: proposals which receive more than a high threshold of no votes have their deposit burned. At present, the slashing threshold is 80%. If the proposal is not slashed (but regardless of whether it passes or fails), the deposit will then be returned to the proposer at the end of voting.
From the proposer’s point of view, the lifecycle of a proposal begins when it is submitted and ends when it the deposit is claimed. During the voting period, the proposer may also optionally withdraw the proposal, which prevents it from passing, but does not prevent it from being slashed. This is usually used when a proposal has been superceded by a revised alternative.

In the above, rounded grey boxes are actions submitted by the proposal author, rectangular colored boxes are the state of the proposal on chain, and colored circles are outcomes of voting.
Kinds Of Proposal
There are 4 kinds of governance proposal on Penumbra: signaling, emergency, parameter change, and DAO spend.
Signaling Proposals
Signaling proposals are meant to signal community consensus about something. They do not have a mechanized effect on the chain when passed; they merely indicate that the community agrees about something.
This kind of proposal is often used to agree on code changes; as such, an optional commit
field
may be included to specify these changes.
Emergency Proposals
Emergency proposals are meant for when immediate action is required to address a crisis, and conclude early as soon as a 2/3 majority of all active voting power votes yes.
Emergency proposals have the power to optionally halt the chain when passed. If this occurs, off-chain coordination between validators will be required to restart the chain.
Parameter Change Proposals
Parameter change proposals alter the chain parameters when they are passed. Chain parameters specify things like the base staking reward rate, the amount of penalty applied when slashing, and other properties that determine how the chain behaves. Many of these can be changed by parameter change proposals, but some cannot, and instead would require a chain halt and upgrade.
A parameter change proposal specifies both the old and the new parameters. If the current set of parameters at the time the proposal passes are an exact match for the old parameters specified in the proposal, the entire set of parameters is immediately set to the new parameters; otherwise, nothing happens. This is to prevent two simultaneous parameter change proposals from overwriting each others’ changes or merging with one another into an undesired state. Almost always, the set of old parameters should be the current parameters at the time the proposal is submitted.
DAO Spend Proposals
DAO spend proposals submit a transaction plan which may spend funds from the DAO if passed.
DAO spend transactions have exclusive capability to use two special actions which are not allowed in
directly submitted user transactions: DaoSpend
and DaoOutput
. These actions, respectively, spend
funds from the DAO, and mint funds transparently to an output address (unlike regular output
actions, which are shielded). DAO spend transactions are unable to use regular shielded outputs,
spend funds from any source other than the DAO itself, perform swaps, or submit, withdraw, or claim
governance proposals.
Submitting A Proposal
To submit a proposal, first generate a proposal template for the kind of proposal you want to submit. For example, suppose we want to create a signaling proposal:
cargo run --release --bin pcli tx proposal template signaling --file proposal.toml
This outputs a TOML template for the proposal to the file proposal.toml
, where you can edit the
details to match what you’d like to submit. The template will contain relevant default fields for
you to fill in, as well as a proposal ID, automatically set to the next proposal ID at the time you
generated the template. If someone else submits a proposal before you’re ready to upload yours, you
may need to increment this ID, because it must be the sequentially next proposal ID at the time the
proposal is submitted to the chain.
Once you’re ready to submit the proposal, you can submit it. Note that you do not have to explicitly specify the proposal deposit in this action; it is determined automatically based on the chain parameters.
cargo run --release --bin pcli tx proposal submit --file proposal.toml
The proposal deposit will be immediately escrowed and the proposal voting period will start in the
very next block. As the proposer, you will receive a proposal deposit NFT which can be redeemed
for the proposal deposit after voting concludes, provided the proposal is not slashed. This NFT has
denomination proposal_N_deposit
, where N
is the ID of your proposal. Note that whoever holds
this NFT has exclusive control of the proposal: they can withdraw it or claim the deposit.
Making A DAO Spend Transaction Plan
In order to submit a DAO spend proposal, it is necessary to create a transaction plan. At present, the only way to specify this is to provide a rather human-unfriendly JSON-formatted transaction plan, because there is no stable human-readable representation for a transaction plan at present. This will change in the future as better tooling is developed.
For now, here is a template for a transaction plan that withdraws 100 penumbra from the DAO and sends it to a specified address (in this case, the address of the author of this document):
{
"fee": { "amount": { "lo": 0, "hi": 0 } },
"actions": [
{ "daoSpend": { "value": {
"amount": { "lo": 100000000, "hi": 0 },
"assetId": { "inner": "KeqcLzNx9qSH5+lcJHBB9KNW+YPrBk5dKzvPMiypahA=" }
} } },
{ "daoOutput": {
"value": {
"amount": { "lo": 100000000, "hi": 0 },
"assetId": { "inner": "KeqcLzNx9qSH5+lcJHBB9KNW+YPrBk5dKzvPMiypahA=" }
},
"address": {
"inner": "vzZ60xfMPPwewTiSb08jk5OdUjc0BhQ7IXLgHAayJoi5mvmlnTpqFuaPU2hCBhwaEwO2c03tBbN/GVh0+CajAjYBmBq3yHAbzNJCnZS8jUs="
}
} }
]
}
Note that the asset ID and address are specified not in the usual bech32 formats you are used to
seeing, but in base64. To get your address in this format, use pcli view address 0 --base64
.
To template a DAO spend proposal using a JSON transaction plan, use the pcli tx proposal template dao-spend --transaction-plan <FILENAME>.json
, which will take the transaction plan and include it
in the generated proposal template. If no plan is specified, the transaction plan will be the empty
transaction which does nothing when executed.
Withdrawing A Proposal
If you want to withdraw a proposal that you have made (perhaps because a better proposal has come to community consensus), you can do so before voting concludes. Note that this does not make you immune to losing your deposit by slashing, as withdrawn proposals can still be voted on and slashed.
cargo run --release --bin pcli \
tx proposal withdraw 0 \
--reason "some human-readable reason for withdrawal"
When you withdraw a proposal, you consume your proposal deposit NFT, and produce a new proposal
unbonding deposit NFT, which has the denomination proposal_N_unbonding_deposit
, where N
is the
proposal ID. This, like the proposal deposit NFT, can be used to redeem the deposit at the end of
voting, provided the proposal is not slashed.
Claiming A Proposal Deposit
Regardless of whether you have or have not withdrawn your proposal, once voting on the proposal
concludes, you can claim your proposal deposit using the tx proposal deposit-claim
subcommand of
pcli
. For example, if you wanted to claim the deposit for a concluded proposal number 1, you could
say:
cargo run --release --bin pcli tx proposal deposit-claim 1
This will consume your proposal deposit NFT (either the original or the one you received after
withdrawing the proposal) and send you back one of three different proposal result NFTs, depending
on the result of the vote: proposal_N_passed
, proposal_N_failed
or proposal_N_slashed
. If the
proposal was not slashed (that is, it passed or failed), this action will also produce the
original proposal deposit. Note that you can claim a slashed proposal: you will receive the
slashed proposal result NFT, but you will not receive the original proposal deposit.
Contributing To The DAO
Anyone can contribute any amount of any denomination to the Penumbra DAO. To do this, use the
command pcli tx dao-deposit
, like so:
cargo run --release --bin pcli tx dao-deposit 100penumbra
Funds contributed to the DAO cannot be withdrawn except by a successful DAO spend governance proposal.
To query the current DAO balance, use pcli query dao balance
with the base denomination of an
asset or its asset ID (display denominations are not currently accepted). For example:
cargo run --release --bin pcli query dao balance upenumbra
DAO spend proposals are only accepted for voting if they would not overdraw the current funds in the DAO at the time the proposal is submitted, so it’s worth checking this information before submitting such a proposal.
Sending Validator Funding Streams To The DAO
A validator may non-custodially send funds to the DAO, similarly to any other funding stream. To do
this, add a [[funding_stream]]
section to your validator definition TOML file that declares the
DAO as a recipient for a funding stream. For example, your definition might look like this:
sequence_number = 0
enabled = true
name = "My Validator"
website = "https://example.com"
description = "An example validator"
identity_key = "penumbravalid1s6kgjgnphs99udwvyplwceh7phwt95dyn849je0jl0nptw78lcqqvcd65j"
governance_key = "penumbragovern1s6kgjgnphs99udwvyplwceh7phwt95dyn849je0jl0nptw78lcqqhknap5"
[consensus_key]
type = "tendermint/PubKeyEd25519"
value = "tDk3/k8zjEyDQjQC1jUyv8nJ1cC1B/MgrDzeWvBTGDM="
# Send a 1% commission to this address:
[[funding_stream]]
recipient = "penumbrav2t1hum845ches70c8kp8zfx7nerjwfe653hxsrpgwepwtspcp4jy6ytnxhe5kwn56sku684x6zzqcwp5ycrkee5mmg9kdl3jkr5lqn2xq3kqxvp4d7gwqdue5jznk2ter2t66mk4n"
rate_bps = 100
# Send another 1% commission to the DAO:
[[funding_stream]]
recipient = "DAO"
rate_bps = 100
Using pcli
with pclientd
First, export a viewing key from pcli
:
pcli keys export full-viewing-key
Next, use the FVK it prints to initialize the pclientd
state:
pclientd init FVK_STRING
The location of the pclientd
state can be changed with the -s
parameter.
Finally, run
pclientd start
to start the view server, and invoke pcli
with
pcli -v 127.0.0.1:8081
to use it instead of an in-process view service.
WARNING: the view service does not currently use transport encryption, so it should not be used over a public network.
Using pd
This section describes how to build and run pd
, the node implementation for
Penumbra:
- Building
pd
describes how to buildpd
; - Joining a Testnet describes how to join the current testnet;
- Creating a Testnet describes how to create a custom testnet, for instance for local development.
Building pd
The node software pd
is part of the same repository as pcli
, so follow
those instructions to clone the repo and install dependencies.
To build pd
, run
cargo build --release --bin pd
Because you are building a work-in-progress version of the node software, you may see compilation warnings, which you can safely ignore.
Installing Tendermint
You’ll need to have Tendermint installed on your system to join your node to the testnet.
NOTE: Previous versions of Penumbra used Tendermint 0.35, which
has now been officially
deprecated
by the Tendermint Council. We have now rolled back to
v0.34.
Do not use Tendermint 0.35
, which will no longer work with pd
.
that can prevent nodes from staying online.
Follow Tendermint’s installation instructions,
but before you start compiling, make sure you are compiling version v0.34.23
.
git checkout v0.34.23
Joining a Testnet
We provide instructions for running both fullnode deployments and validator deployments. A fullnode will sync with the network but will not have any voting power, and will not be eligible for staking or funding stream rewards. For more information on what a fullnode is, see the Tendermint documentation.
A regular validator will participate in voting and rewards, if it becomes part of the consensus set. Of course, these rewards, like all other testnet tokens, have no value.
Joining as a fullnode
To join a testnet as a fullnode, check out the tag for the current testnet, run
pd testnet join
to generate configs, then use those configs to run pd
and
tendermint
. In more detail:
Resetting state
First, reset the testnet data from any prior testnet you may have joined:
cargo run --bin pd --release -- testnet unsafe-reset-all
This will delete the entire testnet data directory.
Generating configs
Next, generate a set of configs for the current testnet:
cargo run --bin pd --release -- testnet join --external-address IP_ADDRESS --moniker MY_NODE_NAME
where IP_ADDRESS
(like 1.2.3.4
) is the public IP address of the node you’re running,
and MY_NODE_NAME
is a moniker identifying your node. Other peers will try to connect
to your node over port 26656/TCP.
If your node is behind a firewall or not publicly routable for some other reason,
skip the --external-address
flag, so that other peers won’t try to connect to it.
You can also skip the --moniker
flag to use a randomized moniker instead of selecting one.
This command fetches the genesis file for the current testnet, and writes
configs to a testnet data directory (by default, ~/.penumbra/testnet_data
).
If any data exists in the testnet data directory, this command will fail. See
the section above on resetting node state.
Running pd
and tendermint
Next, run pd
with the --home
parameter pointed at the correct part of the
testnet data directory. It’s useful to set the RUST_LOG
environment variable
to get information about what it’s doing:
export RUST_LOG="warn,pd=debug,penumbra=debug" # or some other logging level
cargo run --bin pd --release -- start --home ~/.penumbra/testnet_data/node0/pd
Then (perhaps in another terminal), run Tendermint, also specifying --home
:
tendermint start --home ~/.penumbra/testnet_data/node0/tendermint
Alternatively, pd
and tendermint
can be orchestrated with docker-compose
:
cd deployments/compose/
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
or via systemd:
cd deployments/systemd/
sudo cp *.service /etc/systemd/system/
# edit service files to customize for your system
sudo systemctl daemon-reload
sudo systemctl restart penumbra tendermint
Joining as a validator
After starting your node, as above, you should now be participating in the network as a fullnode. However your validator won’t be visible to the chain yet, as the definition hasn’t been uploaded.
Validator Definitions (Penumbra)
A validator definition contains fields defining metadata regarding your validator as well as funding streams, which are Penumbra’s analogue to validator commissions.
The root of a validator’s identity is their identity key. Currently, pcli
reuses the spend authorization key in whatever wallet is active as the
validator’s identity key. This key is used to sign validator definitions that
update the configuration for a validator.
Creating a template definition
To create a template configuration, use pcli validator definition template
:
$ cargo run --release --bin pcli -- validator definition template \
--tendermint-validator-keyfile ~/.penumbra/testnet_data/node0/tendermint/config/priv_validator_key.json \
--file validator.toml
$ cat validator.toml
# This is a template for a validator definition.
#
# The identity_key and governance_key fields are auto-filled with values derived
# from this wallet's account.
#
# You should fill in the name, website, and description fields.
#
# By default, validators are disabled, and cannot be delegated to. To change
# this, set `enabled = true`.
#
# Every time you upload a new validator config, you'll need to increment the
# `sequence_number`.
sequence_number = 0
enabled = false
name = ''
website = ''
description = ''
identity_key = 'penumbravalid1kqrecmvwcc75rvg9arhl0apsggtuannqphxhlzl34vfamp4ukg9q87ejej'
governance_key = 'penumbragovern1kqrecmvwcc75rvg9arhl0apsggtuannqphxhlzl34vfamp4ukg9qus84v5'
[consensus_key]
type = 'tendermint/PubKeyEd25519'
value = 'HDmm2FmJhLHxaKPnP5Fw3tC1DtlBx8ETgTL35UF+p6w='
[[funding_stream]]
recipient = 'penumbrav2t1cntf73e36y3um4zmqm4j0zar3jyxvyfqxywwg5q6fjxzhe28qttppmcww2kunetdp3q2zywcakwv6tzxdnaa3sqymll2gzq6zqhr5p0v7fnfdaghrr2ru2uw78nkeyt49uf49q'
rate_bps = 100
[[funding_stream]]
recipient = "DAO"
rate_bps = 100
and adjust the data like the name, website, description, etc as desired.
The enabled
field can be used to enable/disable your validator without facing slashing
penalties. Disabled validators can not appear in the active validator set and are ineligible for
rewards.
This is useful if, for example, you know your validator will not be online for a period of time,
and you want to avoid an uptime violation penalty. If you are uploading your validator for the
first time, you will likely want to start with it disabled until your Tendermint & pd
instances have caught up to the consensus block height.
Note that by default the enabled
field is set to false and will need to be
enabled in order to activate one’s validator.
In the default template, there is a funding stream declared to contribute funds to the DAO. This is not required, and may be altered or removed if you wish.
Setting the consensus key
In the command above, the --tendermint-validator-keyfile
flag was used to instruct
pcli
to import the consensus key for the Tendermint identity. This works well
if pcli
and pd
are used on the same machine. If you are running them in separate
environments, you can omit the flag, and pd
will generate a random key in the template.
You must then manually update the consensus_key
. You can get the correct value
for consensus_key
from your tendermint
configs:
$ grep -A3 pub_key ~/.penumbra/testnet_data/node0/tendermint/config/priv_validator_key.json
"pub_key": {
"type": "tendermint/PubKeyEd25519",
"value": "Fodjg0m1kF/6uzcAZpRcLJswGf3EeNShLP2A+UCz8lw="
},
Copy the string in the value
field and paste that into your validator.toml
,
as the value
field under the [consensus_key]
heading.
Configuring funding streams
Unlike the Cosmos SDK, which has validators specify a commission percentage that goes to the validator, Penumbra uses funding streams, a list of pairs of commission amounts and addresses. This design allows validators to dedicate portions of their commission non-custodially – for instance, a validator could declare some amount of commission to cover their operating costs, and another that would be sent to an address controlled by a DAO.
Uploading a definition
After setting up metadata, funding streams, and the correct consensus key in
your validator.toml
, you can upload it to the chain:
cargo run --release --bin pcli -- validator definition upload --file validator.toml
And verify that it’s known to the chain:
cargo run --release --bin pcli -- query validator list -i
However your validator doesn’t have anything delegated to it and will remain in
an Inactive
state until it receives enough delegations to place it in the
active set of validators.
Delegating to your validator
First find your validator’s identity key:
cargo run --release --bin pcli -- validator identity
And then delegate some amount of penumbra
to it:
cargo run --release --bin pcli -- tx delegate 1penumbra --to penumbravalid1g2huds8klwypzczfgx67j7zp6ntq2m5fxmctkf7ja96zn49d6s9qz72hu3
You should then see your balance of penumbra
decreased and that you have received some amount of delegation tokens for your validator:
cargo run --release --bin pcli view balance
Voting power will be calculated on the next epoch transition after your
delegation takes place. Assuming that your delegation was enough to place your
validator in the top N validators by voting power, it should appear in the
validator list as Active
after the next epoch transition. The epoch duration
and the active validator limit are chain parameters, and will vary by
deployment. You can find the values in use for the current chain in its
genesis.json
file.
Updating your validator
First fetch your existing validator definition from the chain:
cargo run --release --bin pcli -- validator definition fetch --file validator.toml
Then make any changes desired and make sure to increase by sequence_number
by at least 1!
The sequence_number
is a unique, increasing identifier for the version of the validator definition.
After updating the validator definition you can upload it again to update your validator metadata on-chain:
cargo run --release --bin pcli -- validator definition upload --file validator.toml
Development
This section is for developers working on pd
itself.
Devnet Quickstart
This page describes a quickstart method for running pd
+tendermint
to test
changes during development.
To start, you’ll need to install Tendermint v0.34
.
Generating configs
To generate a clean set of configs, run
cargo run --release --bin pd -- testnet generate
This will write configs to ~/.penumbra/testnet_data/
.
Running pd
You’ll probably want to set RUST_LOG
. Here’s one suggestion:
export RUST_LOG="warn,pd=debug,penumbra=debug,jmt=info"
To run pd
, run
cargo run --release --bin pd -- start --home ~/.penumbra/testnet_data/node0/pd
This will start but won’t do anything yet, because Tendermint isn’t running.
Running tendermint
To run Tendermint, run
tendermint --home ~/.penumbra/testnet_data/node0/tendermint/ start
in another terminal window.
Running pcli
To interact with the chain, first do
cargo run --release --bin pcli -- view reset
and then pass the -n
flag to any commands you run to point pcli
at your local node, e.g.,
cargo run --bin pcli -- -n 127.0.0.1 view balance
By default, pd testnet generate
uses the latest snapshot of the Discord’s
faucet channel, so if you posted your address there more than a week or two ago,
you should already have an allocation in your local devnet.
If not, reset the state as below, and edit the genesis.json
to add your address.
Resetting and restarting
After making changes, you may want to reset and restart the devnet:
cargo run --release --bin pd -- testnet unsafe-reset-all
You’ll probably also want to reset your wallet state:
cargo run --release --bin pcli -- view reset
At this point you’re ready to generate new configs, and restart both pd
and
tendermint
. The order they’re started in doesn’t particularly matter for
correctness, because tendermint
will retry connecting to the ABCI server until
it succeeds.
Optional: running smoke-tests
Once you have a working devnet running, you should be able to run the smoke tests successfully. This can be useful if you are looking to contribute to Penumbra, or if you need to check that your setup is correct.
To run the smoke tests:
- Make sure you have a devnet running (see previous steps)
- Run integration tests:
PENUMBRA_NODE_HOSTNAME=127.0.0.1 PCLI_UNLEASH_DANGER=yes cargo test --package pcli -- --ignored --test-threads 1
SQLite compilation setup
The view server uses SQLite via sqlx
as its backing store. The type-safe
query macros require compile-time information about the database schemas.
Normally, this information is cached in the crate’s sqlx-data.json
, and
nothing extra is required to build.
However, when editing the view server’s database code, it’s necessary to work with a development database:
-
You’ll need
sqlx-cli
installed with the correct features:cargo install sqlx-cli --features sqlite
-
The database structure is defined in the
migrations/
directory of theview
crate. -
Set the
DATABASE_URL
environment variable to point to the SQLite location. For instance,export DATABASE_URL="sqlite:///tmp/pclientd-dev-db.sqlite"
will set the shell environment variable to the same one set in the project’s
.vscode/settings.json
. -
From the
view
directory, runcargo sqlx database setup
to create the database and run migrations. -
From the
view
directory, runcargo sqlx prepare -- --lib
to regenerate thesqlx-data.json
file that allows offline compilation.
Building documentation
The protocol docs and the guide (this document) are built using
mdBook and auto-deployed on pushes to main
. To build locally:
- Install the requirements:
cargo install mdbook mdbook-katex mdbook-mermaid
- Run
mdbook serve
fromdocs/protocol
(for the protocol spec) or fromdocs/guide
(for this document).
The Rust API docs can be built with ./deployments/scripts/rust-docs
.
The landing page, the top-level index.html
, is handled as a special case.
If you added new crates by appending a -p <crate_name>
to the rust-docs
script,
then you must rebuild the index page via:
REGENERATE_INDEX=1 ./deployments/scripts/rust-docs
CI will automatically rebuild all our docs on merges into main, except for the Rust doc index.html, which must be updated manually.
Maintaining protobuf specs
The Penumbra project dynamically generates code for interfacing with gRPC. The following locations within the repository are relevant:
proto/proto/penumbra/**/*.proto
, the developer-authored spec filesproto/src/gen/*.rs
, the generated Rust code filestools/proto-compiler/
, the build logic for generated the Rust code files
We use buf to auto-publish the protobuf schemas at
buf.build/penumbra-zone/penumbra, and to generate Go and Typescript packages.
The Rust code files are generated with our own tooling, located at tools/proto-compiler
.
Our custom tooling for generating the Rust files will also shape the Serde implementations of the derived Rust types to have more favorable JSON output (such as rendering addresses as Bech32-encoded strings).
Installing protobuf
Obtain the most recent pre-compiled binary from the protoc
website.
After installing, run protoc --version
and confirm you’re running
at least 3.21.8
(or newer). Don’t install protoc
from package managers
such as apt
, as those versions are often outdated, and will not work
with Penumbra.
Building protobuf
Switch to the proto-compiler directory and run the tool:
cd tools/proto-compiler
cargo run
Then run git status
to determine whether any changes were made.
The build process is deterministic, so regenerating multiple times
from the same source files should not change the output.
A possible exception to this rule is if prost
makes a superficial
change to the output that isn’t substantive.
If the generated output would change in any way, CI will fail, prompting the developer to commit the changes.
Metrics
Metrics are an important part of observability, allowing us to understand what the Penumbra software is doing.
Viewing Metrics
TODO: add details on how to use Grafana:
- link to https://grafana.testnet.penumbra.zone for dashboard on current testnet;
-
instructions on how to run Grafana + Prometheus for local dev setup (ideally this could work without requiring that
pd
itself is Dockerized, since local development is often more convenient outside of docker); - instructions on how to commit dashboards back to the repo.
Adding Metrics
We use a common structure for organizing metrics code throughout the penumbra
workspace. Each crate that uses metrics has a top-level metrics
module, which
is private to the crate. That module contains:
- a re-export of the entire metrics crate:
pub use metrics::*;
&'static str
constants for every metrics key used by the crate;- a
pub fn register_metrics()
that registers and describes all of the metrics used by the crate;
Finally, the register_metrics
function is publicly re-exported from the crate root.
The only part of this structure visible outside the crate is the
register_metrics
function in the crate root, allowing users of the library to
register and describe the metrics it uses on startup.
Internally to the crate, all metrics keys are in one place, rather than being
scattered across the codebase, so it’s easy to see what metrics there are.
Because the metrics
module re-exports the contents of the metrics
crate,
doing use crate::metrics;
is effectively a way to monkey-patch the
crate-specific constants into the metrics
crate, allowing code like:
#![allow(unused)] fn main() { metrics::increment_counter!( metrics::MEMPOOL_CHECKTX_TOTAL, "kind" => "new", "code" => "1" ); }
The metrics keys themselves should:
- follow the Prometheus metrics naming guidelines
- have an initial prefix of the form
penumbra_CRATE
, e.g.,penumbra_stake
,penumbra_pd
, etc; - have some following module prefix that makes sense relative to the other metrics in the crate.
For instance:
#![allow(unused)] fn main() { pub const MEMPOOL_CHECKTX_TOTAL: &str = "penumbra_pd_mempool_checktx_total"; }
Backing up Grafana
After being changed, Grafana dashboards should be backed up to the repository for posterity and redeployment.
Grafana has an import/export feature that we use for maintaining our dashboards.
- Export the dashboard as JSON with the default settings
- Rename the JSON file and copy into the repo (
config/grafana/dashboards/
) - Use the import function in the UI to update all deployments
Resources
This page links to various resources that are helpful for working with and understanding Penumbra:
Discord
The primary communication hub is our Discord; click the link to join the discussion there.
Guide
Documentation on how to use pcli
, how to run pd
, and how to do development can be found at guide.penumbra.zone.
Protocol Specification
The protocol specification is rendered at protocol.penumbra.zone.
API documentation
The API documentation is rendered at rustdoc.penumbra.zone.
Protobuf documentation
The protobuf documentation is rendered at buf.build/penumbra-zone/penumbra.
Talks and presentations
These talks were given at various conferences and events, describing different aspects of the Penumbra ecosystem.