Staking Mechanism
Last updated
Last updated
As a proof of stake protocol, DaVinci depends on stakers locking up capital within the protocol (deposits), and, eventually, receiving that capital back along with the rewards they have earned (withdrawals).
The form of capital that is staked is DaVin (DCOIN), DaVinci native currency. DaVin on the consensus layer exists separately, and is accounted for separately, from DaVin in normal DaVinci accounts and contracts. DaVin on the consensus layer is in the form of balances of validator accounts. Validator accounts are extremely limited: they have a balance that increases due to deposits and rewards, and decreases due to withdrawals and penalties. You cannot make transfers between validator accounts or run any kind of transaction on them. Validator account balances are tracked as part of the beacon state, and do not form part of the normal DaVinci execution state. Note that execution balances are denominated in Wei ( DCOIN), whereas validator balances are denominated in Gwei ( DCOIN).
For Your Information
Deposits are transfers of DCOIN from the execution layer to the consensus layer.
Withdrawals are transfers of DCOIN from the consensus layer5 to the execution layer.
Accounting on each layer is completely separate.
Stakers send transactions to the deposit contract in order to stake.
Staking is permissionless.
Withdrawals are periodic and automatic.
Withdrawals are either partial or full.
The deposit contract is the means by which stakers commit their DCOIN to the protocol in order to gain the right to run a validator.
The deposit contract is a normal DaVinci smart contract running on the execution layer. Anyone wishing to place a stake in order to run a validator may send 32 DCOIN to the deposit contract via a normal DaVinci transaction.
In addition to the DCOIN transferred, the deposit transaction must contain further data as follows.
First, the public key of the validator. A validator's public key is derived from its secret signing key, and is its primary identity on the consensus layer. The staker will provide the secret signing key separately to the consensus client for normal operational use.
Second, withdrawal credentials specifying which DaVinci account rewards earned will be sent to. This will also be the address that receives the validator's full balance when it eventually exits.
Third, a signature over the public key, the withdrawal credentials, and the deposit amount, using the normal signing key. This signature's main role is to serve as a "proof of possession" of the secret key of the validator, which side-steps a nasty rogue public key attack.
Fourth, the deposit data root, which is an SSZ Merkleization of all of the above data that serves as a kind of checksum that the contract can verify.
The deposit contract does some verification on these parameters. In particular, the deposit amount is subject to checks, and the deposit data root is verified. If either of these fails then the deposit will be rejected - that is, the deposit transaction will be reverted.
However, the deposit contract does not validate the signature - the EVM does not yet have the elliptic curve apparatus to do this, and it would be prohibitively expensive to do in normal bytecode. The signature will be validated later by the consensus layer, and if found to be incorrect (for new validators) the deposit will fail, and the DCOIN will be lost.
Once the deposit contract is as satisfied as it can be that the deposit is valid, it issues a receipt (an EVM log event) containing the deposit data. This receipt will later be picked up by the consensus layer for processing.
The underlying data structure of the deposit contract is an incremental Merkle tree. This is a Merkle tree that supports only two operations, (1) appending a leaf, and (2) calculating the root. Constraining the data like this allows us to avoid storing the entire Merkle tree, which would be huge. Instead the contract stores only the last branch
– a mere 32 nodes – which is all the information that's needed to calculate the Merkle root.
To gain this efficiency, we need an array of zero_hashes
. At any given level of the tree, the zero hash is the value the node would have if all of the leaves under it were zero. Since we assign leaves sequentially, huge parts of the tree can be represented by the zero hashes.
The constructor()
(which takes no arguments) only initialises the zero_hashes
structure, taking advantage of the DVM's default that the uninitialised zero_hashes[0]
storage value will be zero.
This is the business part of the contract - where stakers' deposits are made.
A deposit comprises the following items.
The public key of the validator: pubkey
is the 48 byte (compressed) BLS public key derived from the staker's secret signing key.
The withdrawal credentials: withdrawal_credentials
is 32 bytes of either 0x00
BLS credentials or 0x01
credentials. Apart from their length, the withdrawal credentials are not validated anywhere in the contract, or even on the consensus layer.
The signature
is a 96 Byte BLS signature. It is generated by signing the hash tree root of a DepositMessage
object (public_key
, withdrawal_credentials
, and deposit_amount
), with the validator's signing key.
The deposit_data_root
is basically a form of checksum. See below for how it is verified.
at least one DCOIN,
a whole number of DCOIN, and
The very last condition is formally to avoid overflowing a consensus layer uint64
, but seems kind of redundant in practice.
For every deposit accepted by the deposit contract it issues a receipt (also called a log or event6), which is generated via an EVM LOG1
opcode.
The receipt has a single topic, which is the DepositEvent
signature: 0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5
, equal to keccak256("DepositEvent(bytes,bytes,bytes,bytes,bytes)")
.
The receipt's data is the 576 byte ABI encoding of pubkey
, withdrawal_credentials
, amount
, signature
, and deposit_count
, converted to little-endian where required.
The first column is the hexadecimal byte position of the start of the data in the second column.
A consensus client can request these receipts from its attached execution client via the standard eth_getLogs
RPC method, filtering by the deposit contract address, block numbers, and event topic. This is how the consensus layer becomes aware of the details of new deposits.
The use of event logs here is an optimisation. The deposit contract could instead store all the Merkle tree's leaves and make them available via an eth_call
method. However, since logs are not stored in the chain's state, only in block history, it is much cheaper to use them than it would be to store the leaves in the contract's state. However, this places a constraint on the amount of history we must keep around - we cannot now discard block history from before the deployment of the deposit contract. A newly activated consensus client needs access to the full receipt history in order to rebuild its internal view of the Merkle tree, even if it is able to checkpoint sync its beacon state. For convenience, some clients now support starting from a deposit snapshot of the Merkle tree that can be shared with other clients in much the same way as checkpoint states. This allows aggressive pruning of block history for those who want to do that.
The DEPOSIT_CONTRACT_TREE_DEPTH
specifies the number of levels in the internal Merkle tree. With a depth of 32, it can have leaves, allowing for up to 4.3 billion deposits (MAX_DEPOSIT_COUNT
). A deposit is a minimum of DCOIN, so there's sufficient space for every DCOIN in existence to be deposited 35 times over.
Finally, a msg.value
. The message value is the amount of DaVinci (denominated in Wei, which are DCOIN) that was sent with the transaction. This will normally be 32 DCOIN for a new validator, but can be more or less. It must be,
less than Gwei , which is 18.4 Billion DCOIN.