diff --git a/rationale/fees.md b/rationale/fees.md new file mode 100644 index 0000000..7f2238c --- /dev/null +++ b/rationale/fees.md @@ -0,0 +1,40 @@ +Fees +=== + +- [Preamble](#preamble) +- [Background: EIP-1559](#background-eip-1559) +- [Fee Burning](#fee-burning) + +## Preamble + +Two desiderata are elastic block size limits—which can smooth out transaction fees and prevent fee spikes—and variable burning of fees—which can respond to market forces and provides intrinsic demand for the burned coin that cannot be replicated by issuance of new supply. Interestingly, these two properties cannot be accomplished independently. Without a negative feedback mechanism that cannot be bypassed, block producers will always produce maximum-size blocks. This negative feedback mechanism is in-protocol variable fee burning. In a similar vein, variable (rather than fixed) fee burning requires a measure of block usage, which happens to exactly be an elastic block size limit. + +This document describes the rationale and structure of an elastic block size limit through variable in-protocol fee burning. + +## Background: EIP-1559 + +[EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) introduces a concrete proposal for in-protocol variable fee burning for Ethereum, with only a superficial analysis of properties. [This paper](http://timroughgarden.org/papers/eip1559.pdf) provides a rigorous formal analysis of the same. + +The essence of EIP-1559 is that, rather than the [first-price auction fee rate](https://arxiv.org/abs/1901.06830) of traditional cryptocurrencies, transactions under EIP-1559 define two fee rate parameters: a maximum base fee rate and a tip rate. The base fee is burned as a mechanism for enabling an elastic block size, and the tip is given to the miner of the block that includes the transaction (and is itself subject to a first-price auction). All transactions in a block burn the same base fee. + +The base fee grows if the previous block was more than half-full, and shrinks if the previous block was less than half full, by a parameter. We note, as above, that fee burning is simply a mechanism for making an elastic block size limit viable. This parameter should be chosen with the following in mind: +1. The target (or, expected) block size should be bounded by the cost to run a full node assuming all blocks are at the target block size (also known as "decentralization"). +1. The maximum block size should bounded by the size of a "poison" block, i.e. one that would disrupt the consensus protocol. + +From this, we see that there is nothing inherent in half-fullness, and indeed this parameter should be chosen more carefully through empirical results. + +## Fee Burning + +The scheme described here is fundamentally identical to EIP-1559, save for the choice of parameters: +1. Target block size: since blocks grow in size by a power of 4 due to the arrangement of data in a square, a reasonable initial choice would be 1:4 target:maximum block size limits. +1. Rate of change: there is nothing inherently wrong (or especially right) with the $\frac{1}{8}$ rate of change prescribed by EIP-1559, so this parameter remains unchanged. + +We can now define the base fee as + +$$ +b_{cur} = b_{prev} \cdot \left( 1 + R \cdot \frac{s_{prev} - S_{target}}{S_{target}} \right) +$$ + +, where $b_{cur}$ is the base fee of the current block, $b_{prev}$ is the base fee of the previous block, $R$ is the rate of change in base fee, $s_{prev}$ is the size of the previous block, and $S_{target}$ is the target block size. In other words, the base fee is only dependent on the current and previous blocks. + +With $R = \frac{1}{8}$ (the rate defined by EIP-1559 and used here), the base fee can reduce by up to $12.5\%$ if the current block is completely empty and increase by up to $37.5\%$ if the current block is full. diff --git a/specs/architecture.md b/specs/architecture.md index 3956487..ee4f99f 100644 --- a/specs/architecture.md +++ b/specs/architecture.md @@ -9,8 +9,8 @@ Architecture | name | description | | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | App (application) | Alternate name for "[virtual sidechain](https://arxiv.org/abs/1905.09274)." LazyLedger apps are sidechains that post all their data onto the LazyLedger chain to share security. | -| Transaction | Requests that modify the consensus-critical state (validator balances and statuses). | -| Message | Requests that are executed by non-consensus-critical apps. | +| Transaction | Request that modifies the consensus-critical state (validator balances and statuses). | +| Message | Request that is executed by a non-consensus-critical app. | ## System Architecture @@ -18,7 +18,7 @@ LazyLedger has a minimal state: the validator set (account balances, validator s Transactions pay fees similarly to how they would in a normal blockchain (e.g. Bitcoin), and their side effects are restricted to modifying the validator set and their balances. Transactions can additionally pay fees for the inclusion of a message (identified by a hash) in the same block. The validator set is committed to in the block header, and since the entire system state _is_ the validator set, this is the only state commitment needed in the header. -One desideratum that will most likely be included is burning a non-proportional amount of coins for each transaction as a network fee. This provides baseline demand for the native coin: as the chain is used more, more coins must be bought then burned to pay for fees. +One desideratum that will most likely be included is [burning a non-proportional amount of coins for each transaction as a network fee](../rationale/fees.md). This provides baseline demand for the native coin: as the chain is used more, more coins must be bought then burned to pay for fees. This architecture has the benefit of allowing a spectrum of clients. Since different components are made available through commitments, client that are only interested in a portion of the block data do not need to download and process the whole block. diff --git a/specs/consensus.md b/specs/consensus.md index 4b755db..a471b97 100644 --- a/specs/consensus.md +++ b/specs/consensus.md @@ -43,20 +43,22 @@ Consensus Rules ### Constants -| name | type | value | unit | description | -| ------------------------------------ | -------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `AVAILABLE_DATA_ORIGINAL_SQUARE_MAX` | `uint64` | | `share` | Maximum number of rows/columns of the original data [shares](data_structures.md#share) in [square layout](data_structures.md#arranging-available-data-into-shares). | -| `CHAIN_ID` | `uint64` | `1` | | Chain ID. Each chain assigns itself a (unique) ID. | -| `GENESIS_COIN_COUNT` | `uint64` | `10**8` | `4u` | `(= 100000000)` Number of coins at genesis. | -| `MAX_GRAFFITI_BYTES` | `uint64` | `32` | `byte` | Maximum size of transaction graffiti, in bytes. | -| `MAX_VALIDATORS` | `uint16` | `64` | | Maximum number of active validators. | -| `NAMESPACE_ID_BYTES` | `uint64` | `8` | `byte` | Size of namespace ID, in bytes. | -| `NAMESPACE_ID_MAX_RESERVED` | `uint64` | `255` | | Value of maximum reserved namespace ID (inclusive). 1 byte worth of IDs. | -| `SHARE_RESERVED_BYTES` | `uint64` | `1` | `byte` | Bytes reserved at the beginning of each [share](data_structures.md#share). Must be sufficient to represent `SHARE_SIZE`. | -| `SHARE_SIZE` | `uint64` | `256` | `byte` | Size of transaction and message [shares](data_structures.md#share), in bytes. | -| `STATE_SUBTREE_RESERVED_BYTES` | `uint64` | `1` | `byte` | Number of bytes reserved to identify state subtrees. | -| `UNBONDING_DURATION` | `uint32` | | `block` | Duration, in blocks, for unbonding a validator or delegation. | -| `VERSION` | `uint64` | `1` | | Version of the LazyLedger chain. Breaking changes (hard forks) must update this parameter. | +| name | type | value | unit | description | +| --------------------------------------- | -------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `AVAILABLE_DATA_ORIGINAL_SQUARE_MAX` | `uint64` | | `share` | Maximum number of rows/columns of the original data [shares](data_structures.md#share) in [square layout](data_structures.md#arranging-available-data-into-shares). | +| `AVAILABLE_DATA_ORIGINAL_SQUARE_TARGET` | `uint64` | | `share` | Target number of rows/columns of the original data [shares](data_structures.md#share) in [square layout](data_structures.md#arranging-available-data-into-shares). | +| `BASE_FEE_CHANGE_RATE` | `uint64` | `8` | | Inverse of rate at which the [base fee](../rationale/fees.md) changes. | +| `CHAIN_ID` | `uint64` | `1` | | Chain ID. Each chain assigns itself a (unique) ID. | +| `GENESIS_COIN_COUNT` | `uint64` | `10**8` | `4u` | `(= 100000000)` Number of coins at genesis. | +| `MAX_GRAFFITI_BYTES` | `uint64` | `32` | `byte` | Maximum size of transaction graffiti, in bytes. | +| `MAX_VALIDATORS` | `uint16` | `64` | | Maximum number of active validators. | +| `NAMESPACE_ID_BYTES` | `uint64` | `8` | `byte` | Size of namespace ID, in bytes. | +| `NAMESPACE_ID_MAX_RESERVED` | `uint64` | `255` | | Value of maximum reserved namespace ID (inclusive). 1 byte worth of IDs. | +| `SHARE_RESERVED_BYTES` | `uint64` | `1` | `byte` | Bytes reserved at the beginning of each [share](data_structures.md#share). Must be sufficient to represent `SHARE_SIZE`. | +| `SHARE_SIZE` | `uint64` | `256` | `byte` | Size of transaction and message [shares](data_structures.md#share), in bytes. | +| `STATE_SUBTREE_RESERVED_BYTES` | `uint64` | `1` | `byte` | Number of bytes reserved to identify state subtrees. | +| `UNBONDING_DURATION` | `uint32` | | `block` | Duration, in blocks, for unbonding a validator or delegation. | +| `VERSION` | `uint64` | `1` | | Version of the LazyLedger chain. Breaking changes (hard forks) must update this parameter. | ### Reserved Namespace IDs @@ -120,6 +122,7 @@ The [block header](./data_structures.md#header) `block.header` (`header` for sho 1. `header.lastBlockID` == the [block ID](./data_structures.md#blockid) of `prev`. 1. `header.lastCommitHash` == the [hash](./data_structures.md#hashing) of `lastCommit`. 1. `header.consensusRoot` == the value computed [here](./data_structures.md#consensus-parameters). +1. `header.feeHeader.baseRate` == `prev.header.feeHeader.baseRate * floor(1 + (prev.header.availableDataOriginalSharesUsed - AVAILABLE_DATA_ORIGINAL_SQUARE_TARGET) / (BASE_FEE_CHANGE_RATE * AVAILABLE_DATA_ORIGINAL_SQUARE_TARGET))`. 1. `header.stateCommitment` == the root of the state, computed [with the application of all state transitions in this block](#state-transitions). 1. `availableDataOriginalSquareSize` <= [`AVAILABLE_DATA_ORIGINAL_SQUARE_MAX`](#constants). 1. `header.availableDataRoot` == the [Merkle root](./data_structures.md#binary-merkle-tree) of the tree with the row and column roots of `block.availableDataHeader` as leaves. @@ -176,14 +179,28 @@ For `wrappedTransaction`'s [transaction](./data_structures.md#transaction) `tran 1. `transaction.signature` must be a [valid signature](./data_structures.md#public-key-cryptography) over `transaction.signedTransactionData`. -Finally, each `wrappedTransaction` is processed depending on [its transaction type](./data_structures.md#signedtransactiondata). These are specified in the next subsections, where `tx` is short for `transaction.signedTransactionData`, and `sender` is the recovered signing [address](./data_structures.md#address). After applying a transaction, the new state state root is computed. +Finally, each `wrappedTransaction` is processed depending on [its transaction type](./data_structures.md#signedtransactiondata). These are specified in the next subsections, where `tx` is short for `transaction.signedTransactionData`, and `sender` is the recovered signing [address](./data_structures.md#address). We will define a few helper functions: +``` +baseCost(y) = block.header.feeHeader.baseRate * y +tipCost(y) = block.header.feeHeader.tipRate * y +totalCost(x, y) = x + baseCost(y) + tipCost(y) +``` +, where `x` above represents the amount of coins sent by the transaction authorizer and `y` above represents the a measure of the block space used by the transaction (i.e. size in bytes). + +After applying a transaction, the new state state root is computed. #### SignedTransactionDataTransfer +``` +bytesPaid = len(tx) +``` + The following checks must be `true`: 1. `tx.type` == [`TransactionType.Transfer`](./data_structures.md#signedtransactiondata). -1. `tx.amount` <= `state.accounts[sender].balance`. +1. `tx.fee.baseRateMax` >= `block.header.feeHeader.baseRate`. +1. `tx.fee.tipRateMax` >= `block.header.feeHeader.tipRate`. +1. `totalCost(tx.amount, bytesPaid)` <= `state.accounts[sender].balance`. 1. `tx.nonce` == `state.accounts[sender].nonce + 1`. Apply the following to the state: @@ -191,15 +208,23 @@ Apply the following to the state: ``` state.accounts[sender].nonce += 1 -state.accounts[sender].balance -= tx.amount +state.accounts[sender].balance -= totalCost(tx.amount, bytesPaid) state.accounts[tx.to].balance += tx.amount +state.activeValidatorSet[block.header.proposerAddress].pendingRewards += tipCost(bytesPaid) ``` #### SignedTransactionDataPayForMessage +``` +bytesPaid = len(tx) + tx.messageSize +``` + The following checks must be `true`: 1. `tx.type` == [`TransactionType.PayForMessage`](./data_structures.md#signedtransactiondata). +1. `tx.fee.baseRateMax` >= `block.header.feeHeader.baseRate`. +1. `tx.fee.tipRateMax` >= `block.header.feeHeader.tipRate`. +1. `totalCost(0, bytesPaid)` <= `state.accounts[sender].balance`. 1. `tx.nonce` == `state.accounts[sender].nonce + 1`. 1. The `ceil(tx.messageSize / SHARE_SIZE)` shares starting at index `wrappedTransactions.messageStartIndex` must: 1. Have namespace ID `tx.messageNamespaceID`. @@ -209,6 +234,9 @@ Apply the following to the state: ``` state.accounts[sender].nonce += 1 + +state.accounts[sender].balance -= totalCost(tx.amount, bytesPaid) +state.activeValidatorSet[block.header.proposerAddress].pendingRewards += tipCost(bytesPaid) ``` #### SignedTransactionDataPayForPadding @@ -224,10 +252,16 @@ Apply the following to the state: #### SignedTransactionDataCreateValidator +``` +bytesPaid = len(tx) +``` + The following checks must be `true`: 1. `tx.type` == [`TransactionType.CreateValidator`](./data_structures.md#signedtransactiondata). -1. `tx.amount` <= `state.accounts[sender].balance`. +1. `tx.fee.baseRateMax` >= `block.header.feeHeader.baseRate`. +1. `tx.fee.tipRateMax` >= `block.header.feeHeader.tipRate`. +1. `totalCost(tx.amount, bytesPaid)` <= `state.accounts[sender].balance`. 1. `tx.nonce` == `state.accounts[sender].nonce + 1`. 1. `tx.commissionRate` 1. `state.accounts[sender].isValidator` == `false` and `state.accounts[sender].isDelegating` == `false`. @@ -250,16 +284,25 @@ validator.latestEntry = PeriodEntry(0) validator.unbondingHeight = 0 validator.isSlashed = false -state.accounts[sender].balance -= tx.amount +state.accounts[sender].balance -= totalCost(tx.amount, bytesPaid) state.inactiveValidatorSet[sender] = validator + +state.activeValidatorSet[block.header.proposerAddress].pendingRewards += tipCost(bytesPaid) ``` #### SignedTransactionDataBeginUnbondingValidator +``` +bytesPaid = len(tx) +``` + The following checks must be `true`: 1. `tx.type` == [`TransactionType.BeginUnbondingValidator`](./data_structures.md#signedtransactiondata). +1. `tx.fee.baseRateMax` >= `block.header.feeHeader.baseRate`. +1. `tx.fee.tipRateMax` >= `block.header.feeHeader.tipRate`. +1. `totalCost(0, bytesPaid)` <= `state.accounts[sender].balance`. 1. `tx.nonce` == `state.accounts[sender].nonce + 1`. 1. `state.inactiveValidatorSet[sender]` == `ValidatorStatus.Queued` or `state.activeValidatorSet[sender]` == `ValidatorStatus.Bonded`. @@ -280,13 +323,22 @@ validator.latestEntry += validator.pendingRewards // validator.votingPower validator.pendingRewards = 0 state.inactiveValidatorSet[sender] = validator + +state.activeValidatorSet[block.header.proposerAddress].pendingRewards += tipCost(bytesPaid) ``` #### SignedTransactionDataUnbondValidator +``` +bytesPaid = len(tx) +``` + The following checks must be `true`: 1. `tx.type` == [`TransactionType.UnbondValidator`](./data_structures.md#signedtransactiondata). +1. `tx.fee.baseRateMax` >= `block.header.feeHeader.baseRate`. +1. `tx.fee.tipRateMax` >= `block.header.feeHeader.tipRate`. +1. `totalCost(0, bytesPaid)` <= `state.accounts[sender].balance`. 1. `tx.nonce` == `state.accounts[sender].nonce + 1`. 1. `state.inactiveValidatorSet[sender]` == `ValidatorStatus.Unbonding`. 1. `state.inactiveValidatorSet[sender].unbondingHeight + UNBONDING_DURATION < block.height`. @@ -309,14 +361,22 @@ state.inactiveValidatorSet[sender] = validator if validator.delegatedCount == 0 state.accounts[sender].isValidator = false delete state.inactiveValidatorSet[sender] + +state.activeValidatorSet[block.header.proposerAddress].pendingRewards += tipCost(bytesPaid) ``` #### SignedTransactionDataCreateDelegation +``` +bytesPaid = len(tx) +``` + The following checks must be `true`: 1. `tx.type` == [`TransactionType.CreateDelegation`](./data_structures.md#signedtransactiondata). -1. `tx.amount` <= `state.accounts[sender].balance`. +1. `tx.fee.baseRateMax` >= `block.header.feeHeader.baseRate`. +1. `tx.fee.tipRateMax` >= `block.header.feeHeader.tipRate`. +1. `totalCost(tx.amount, bytesPaid)` <= `state.accounts[sender].balance`. 1. `state.accounts[tx.to].isValidator == true`. 1. `state.inactiveValidatorSet[tx.to].status` == `ValidatorStatus.Queued` or `state.activeValidatorSet[tx.to].status` == `ValidatorStatus.Bonded` 1. `tx.nonce` == `state.accounts[sender].nonce + 1`. @@ -328,7 +388,7 @@ Apply the following to the state: state.accounts[sender].nonce += 1 state.accounts[sender].isDelegating = true -state.accounts[sender].balance -= tx.amount +state.accounts[sender].balance -= totalCost(tx.amount, bytesPaid) delegation = new Delegation @@ -355,13 +415,22 @@ if state.inactiveValidatorSet[tx.to].status == ValidatorStatus.Queued state.inactiveValidatorSet[tx.to] = validator else if state.activeValidatorSet[tx.to].status == ValidatorStatus.Bonded state.activeValidatorSet[tx.to] = validator + +state.activeValidatorSet[block.header.proposerAddress].pendingRewards += tipCost(bytesPaid) ``` #### SignedTransactionDataBeginUnbondingDelegation +``` +bytesPaid = len(tx) +``` + The following checks must be `true`: 1. `tx.type` == [`TransactionType.BeginUnbondingDelegation`](./data_structures.md#signedtransactiondata). +1. `tx.fee.baseRateMax` >= `block.header.feeHeader.baseRate`. +1. `tx.fee.tipRateMax` >= `block.header.feeHeader.tipRate`. +1. `totalCost(0, bytesPaid)` <= `state.accounts[sender].balance`. 1. `tx.nonce` == `state.accounts[sender].nonce + 1`. 1. `state.accounts[sender].isDelegating == true`. 1. `state.accounts[sender].delegationInfo.status` == `DelegationStatus.Bonded`. @@ -397,13 +466,22 @@ if state.inactiveValidatorSet[delegation.validator].status == ValidatorStatus.Qu state.inactiveValidatorSet[delegation.validator] = validator else if state.activeValidatorSet[delegation.validator].status == ValidatorStatus.Bonded state.activeValidatorSet[delegation.validator] = validator + +state.activeValidatorSet[block.header.proposerAddress].pendingRewards += tipCost(bytesPaid) ``` #### SignedTransactionDataUnbondDelegation +``` +bytesPaid = len(tx) +``` + The following checks must be `true`: 1. `tx.type` == [`TransactionType.UnbondDelegation`](./data_structures.md#signedtransactiondata). +1. `tx.fee.baseRateMax` >= `block.header.feeHeader.baseRate`. +1. `tx.fee.tipRateMax` >= `block.header.feeHeader.tipRate`. +1. `totalCost(0, bytesPaid)` <= `state.accounts[sender].balance`. 1. `tx.nonce` == `state.accounts[sender].nonce + 1`. 1. `state.accounts[sender].isDelegating` == `true`. 1. `state.accounts[sender].delegationInfo.status` == `DelegationStatus.Unbonding`. @@ -432,14 +510,22 @@ if validator.delegatedCount == 0 delete state.inactiveValidatorSet[delegation.validator] delete state.accounts[sender].delegationInfo + +state.activeValidatorSet[block.header.proposerAddress].pendingRewards += tipCost(bytesPaid) ``` #### SignedTransactionDataBurn +``` +bytesPaid = len(tx) +``` + The following checks must be `true`: 1. `tx.type` == [`TransactionType.Burn`](./data_structures.md#signedtransactiondata). -1. `tx.amount` <= `state.accounts[sender].balance`. +1. `tx.fee.baseRateMax` >= `block.header.feeHeader.baseRate`. +1. `tx.fee.tipRateMax` >= `block.header.feeHeader.tipRate`. +1. `totalCost(tx.amount, bytesPaid)` <= `state.accounts[sender].balance`. 1. `tx.nonce` == `state.accounts[sender].nonce + 1`. Apply the following to the state: @@ -447,7 +533,8 @@ Apply the following to the state: ``` state.accounts[sender].nonce += 1 -state.accounts[sender].balance -= tx.amount +state.accounts[sender].balance -= totalCost(tx.amount, bytesPaid) +state.activeValidatorSet[block.header.proposerAddress].pendingRewards += tipCost(bytesPaid) ``` #### End Block diff --git a/specs/data_structures.md b/specs/data_structures.md index d26da38..1ebc760 100644 --- a/specs/data_structures.md +++ b/specs/data_structures.md @@ -12,6 +12,8 @@ Data Structures - [Timestamp](#timestamp) - [BlockID](#blockid) - [HashDigest](#hashdigest) + - [FeeHeader](#feeheader) + - [TransactionFee](#transactionfee) - [Address](#address) - [CommitSig](#commitsig) - [Signature](#signature) @@ -77,7 +79,6 @@ Data Structures | [`Address`](#address) | `byte[32]` | | `Amount` | `uint64` | | [`BlockID`](#blockid) | [HashDigest](#hashdigest) | -| `FeeRate` | `uint64` | | `Graffiti` | `byte[MAX_GRAFFITI_BYTES]` | | [`HashDigest`](#hashdigest) | `byte[32]` | | `Height` | `uint64` | @@ -112,6 +113,7 @@ Block header, which is fully downloaded by both full clients and light clients. | `lastBlockID` | [BlockID](#blockid) | Previous block's ID. | | `lastCommitHash` | [HashDigest](#hashdigest) | Previous block's Tendermint commit hash. | | `consensusRoot` | [HashDigest](#hashdigest) | Merkle root of [consensus parameters](#consensus-parameters) for this block. | +| `feeHeader` | [FeeHeader](#feeheader) | Header data pertaining to fees. | | `stateCommitment` | [HashDigest](#hashdigest) | The [state root](#state) after this block's transactions are applied. | | `availableDataOriginalSharesUsed` | `uint64` | The number of shares used in the [original data square](#arranging-available-data-into-shares) that are not [tail padding](./consensus.md#reserved-namespace-ids). | | `availableDataRoot` | [HashDigest](#hashdigest) | Root of [commitments to erasure-coded data](#availabledataheader). | @@ -170,6 +172,24 @@ HashDigest is a [type alias](#type-aliases). Output of the [hashing](#hashing) function. Exactly 256 bits (32 bytes) long. +### FeeHeader + +| name | type | description | +| ---------- | -------- | ------------------------------------------------ | +| `baseRate` | `uint64` | The base fee rate for this block. | +| `tipRate` | `uint64` | The tip rate for all transactions in this block. | + +See the [rationale document](../rationale/fees.md) for more information on base fees. + +### TransactionFee + +| name | type | description | +| ------------- | -------- | ----------------------------------------------- | +| `baseRateMax` | `uint64` | The maximum base fee rate for this transaction. | +| `tipRateMax` | `uint64` | The maximum tip rate for this transaction. | + +See the [rationale document](../rationale/fees.md) for more information on base fees. + ### Address Address is a [type alias](#type-aliases). @@ -565,36 +585,36 @@ Signed transaction data comes in a number of types: Common fields are denoted here to avoid repeating descriptions: -| name | type | description | -| ------------ | ------------------------ | -------------------------------------------------------------------------- | -| `type` | `TransactionType` | Type of the transaction. Each type indicates a different state transition. | -| `amount` | [Amount](#type-aliases) | Amount of coins to send, in `1u`. | -| `to` | [Address](#address) | Recipient's address. | -| `maxFeeRate` | [FeeRate](#type-aliases) | The maximum fee rate the sender is willing to pay. | -| `nonce` | [Nonce](#type-aliases) | Nonce of sender. | +| name | type | description | +| -------- | --------------------------------- | -------------------------------------------------------------------------- | +| `type` | `TransactionType` | Type of the transaction. Each type indicates a different state transition. | +| `amount` | [Amount](#type-aliases) | Amount of coins to send, in `1u`. | +| `to` | [Address](#address) | Recipient's address. | +| `fee` | [TransactionFee](#transactionfee) | The fee information for this transaction. | +| `nonce` | [Nonce](#type-aliases) | Nonce of sender. | ##### SignedTransactionDataTransfer -| name | type | description | -| ------------ | ------------------------ | ----------------------------------- | -| `type` | `TransactionType` | Must be `TransactionType.Transfer`. | -| `amount` | [Amount](#type-aliases) | | -| `to` | [Address](#address) | | -| `maxFeeRate` | [FeeRate](#type-aliases) | | -| `nonce` | [Nonce](#type-aliases) | | +| name | type | description | +| -------- | --------------------------------- | ----------------------------------- | +| `type` | `TransactionType` | Must be `TransactionType.Transfer`. | +| `amount` | [Amount](#type-aliases) | | +| `to` | [Address](#address) | | +| `fee` | [TransactionFee](#transactionfee) | | +| `nonce` | [Nonce](#type-aliases) | | Transfers `amount` coins to `to`. ##### SignedTransactionDataPayForMessage -| name | type | description | -| ------------------------ | ------------------------------ | ------------------------------------------------------------ | -| `type` | `TransactionType` | Must be `TransactionType.PayForMessage`. | -| `maxFeeRate` | [FeeRate](#type-aliases) | | -| `nonce` | [Nonce](#type-aliases) | | -| `messageNamespaceID` | [`NamespaceID`](#type-aliases) | Namespace ID of message this transaction pays a fee for. | -| `messageSize` | `uint64` | Size of message this transaction pays a fee for, in `byte`s. | -| `messageShareCommitment` | [HashDigest](#hashdigest) | Commitment to message shares (details below). | +| name | type | description | +| ------------------------ | --------------------------------- | ------------------------------------------------------------ | +| `type` | `TransactionType` | Must be `TransactionType.PayForMessage`. | +| `fee` | [TransactionFee](#transactionfee) | | +| `nonce` | [Nonce](#type-aliases) | | +| `messageNamespaceID` | [`NamespaceID`](#type-aliases) | Namespace ID of message this transaction pays a fee for. | +| `messageSize` | `uint64` | Size of message this transaction pays a fee for, in `byte`s. | +| `messageShareCommitment` | [HashDigest](#hashdigest) | Commitment to message shares (details below). | Pays for the inclusion of a [message](#message) in the same block. @@ -612,77 +632,77 @@ Pays for the inclusion of a padding shares in the same block. Padding shares are ##### SignedTransactionDataCreateValidator -| name | type | description | -| ---------------- | ------------------------ | ------------------------------------------ | -| `type` | `TransactionType` | Must be `TransactionType.CreateValidator`. | -| `amount` | [Amount](#type-aliases) | | -| `maxFeeRate` | [FeeRate](#type-aliases) | | -| `nonce` | [Nonce](#type-aliases) | | -| `commissionRate` | [Decimal](#decimal) | | +| name | type | description | +| ---------------- | --------------------------------- | ------------------------------------------ | +| `type` | `TransactionType` | Must be `TransactionType.CreateValidator`. | +| `amount` | [Amount](#type-aliases) | | +| `fee` | [TransactionFee](#transactionfee) | | +| `nonce` | [Nonce](#type-aliases) | | +| `commissionRate` | [Decimal](#decimal) | | Create a new [Validator](#validator) at this address for `amount` coins worth of voting power. ##### SignedTransactionDataBeginUnbondingValidator -| name | type | description | -| ------------ | ------------------------ | -------------------------------------------------- | -| `type` | `TransactionType` | Must be `TransactionType.BeginUnbondingValidator`. | -| `maxFeeRate` | [FeeRate](#type-aliases) | | -| `nonce` | [Nonce](#type-aliases) | | +| name | type | description | +| ------- | --------------------------------- | -------------------------------------------------- | +| `type` | `TransactionType` | Must be `TransactionType.BeginUnbondingValidator`. | +| `fee` | [TransactionFee](#transactionfee) | | +| `nonce` | [Nonce](#type-aliases) | | Begin unbonding the [Validator](#validator) at this address. ##### SignedTransactionDataUnbondValidator -| name | type | description | -| ------------ | ------------------------ | ------------------------------------------ | -| `type` | `TransactionType` | Must be `TransactionType.UnbondValidator`. | -| `maxFeeRate` | [FeeRate](#type-aliases) | | -| `nonce` | [Nonce](#type-aliases) | | +| name | type | description | +| ------- | --------------------------------- | ------------------------------------------ | +| `type` | `TransactionType` | Must be `TransactionType.UnbondValidator`. | +| `fee` | [TransactionFee](#transactionfee) | | +| `nonce` | [Nonce](#type-aliases) | | Finish unbonding the [Validator](#validator) at this address. ##### SignedTransactionDataCreateDelegation -| name | type | description | -| ------------ | ------------------------ | ------------------------------------------- | -| `type` | `TransactionType` | Must be `TransactionType.CreateDelegation`. | -| `amount` | [Amount](#type-aliases) | | -| `to` | [Address](#address) | | -| `maxFeeRate` | [FeeRate](#type-aliases) | | -| `nonce` | [Nonce](#type-aliases) | | +| name | type | description | +| -------- | --------------------------------- | ------------------------------------------- | +| `type` | `TransactionType` | Must be `TransactionType.CreateDelegation`. | +| `amount` | [Amount](#type-aliases) | | +| `to` | [Address](#address) | | +| `fee` | [TransactionFee](#transactionfee) | | +| `nonce` | [Nonce](#type-aliases) | | Create a new [Delegation](#delegation) of `amount` coins worth of voting power for validator with address `to`. ##### SignedTransactionDataBeginUnbondingDelegation -| name | type | description | -| ------------ | ------------------------ | --------------------------------------------------- | -| `type` | `TransactionType` | Must be `TransactionType.BeginUnbondingDelegation`. | -| `maxFeeRate` | [FeeRate](#type-aliases) | | -| `nonce` | [Nonce](#type-aliases) | | +| name | type | description | +| ------- | --------------------------------- | --------------------------------------------------- | +| `type` | `TransactionType` | Must be `TransactionType.BeginUnbondingDelegation`. | +| `fee` | [TransactionFee](#transactionfee) | | +| `nonce` | [Nonce](#type-aliases) | | Begin unbonding the [Delegation](#delegation) at this address. ##### SignedTransactionDataUnbondDelegation -| name | type | description | -| ------------ | ------------------------ | ------------------------------------------- | -| `type` | `TransactionType` | Must be `TransactionType.UnbondDelegation`. | -| `maxFeeRate` | [FeeRate](#type-aliases) | | -| `nonce` | [Nonce](#type-aliases) | | +| name | type | description | +| ------- | --------------------------------- | ------------------------------------------- | +| `type` | `TransactionType` | Must be `TransactionType.UnbondDelegation`. | +| `fee` | [TransactionFee](#transactionfee) | | +| `nonce` | [Nonce](#type-aliases) | | Finish unbonding the [Delegation](#delegation) at this address. ##### SignedTransactionDataBurn -| name | type | description | -| ------------ | ------------------------- | -------------------------------------------- | -| `type` | `TransactionType` | Must be `TransactionType.Burn`. | -| `amount` | [Amount](#type-aliases) | | -| `maxFeeRate` | [FeeRate](#type-aliases) | | -| `nonce` | [Nonce](#type-aliases) | | -| `graffiti` | [Graffiti](#type-aliases) | Graffiti to indicate the reason for burning. | +| name | type | description | +| ---------- | --------------------------------- | -------------------------------------------- | +| `type` | `TransactionType` | Must be `TransactionType.Burn`. | +| `amount` | [Amount](#type-aliases) | | +| `fee` | [TransactionFee](#transactionfee) | | +| `nonce` | [Nonce](#type-aliases) | | +| `graffiti` | [Graffiti](#type-aliases) | Graffiti to indicate the reason for burning. | ### IntermediateStateRootData diff --git a/specs/figures/block_data_structures.dot b/specs/figures/block_data_structures.dot index c94d945..2065c92 100644 --- a/specs/figures/block_data_structures.dot +++ b/specs/figures/block_data_structures.dot @@ -7,12 +7,17 @@ digraph G { subgraph cluster_availableheader { label = "availableDataHeader"; - struct4 [label = "{ | { rowRoots | colRoots } }"]; + struct5 [label = "{ | { rowRoots | colRoots } }"]; } subgraph cluster_body { label = "availableData"; - struct3 [label = "{ | { transactionData | intermediateStateRoots | evidenceData | messageData } }"]; + struct4 [label = "{ | { transactionData | intermediateStateRoots | evidenceData | messageData } }"]; + } + + subgraph cluster_feeheader { + label = "feeHeader"; + struct3 [label = "baseRate | tipRate"]; } subgraph cluster_lastcommit { @@ -22,15 +27,16 @@ digraph G { subgraph cluster_header { label = "header"; - struct1 [label = "height | timestamp | lastBlockID | lastCommitHash | consensusRoot | stateCommitment | availableDataOriginalSharesUsed | availableDataRoot | proposerAddress"]; + struct1 [label = "height | timestamp | lastBlockID | lastCommitHash | consensusRoot | feeHeader | stateCommitment | availableDataOriginalSharesUsed | availableDataRoot | proposerAddress"]; } } - struct1:f3 -> struct2; - struct1:f7 -> struct4 [label = "Merkle root of"]; - struct4:f0 -> struct3 [label = "NMT roots to\nerasure-coded data"]; + struct1:f4 -> struct2; + struct1:f5 -> struct3; + struct1:f8 -> struct5 [label = "Merkle root of"]; + struct5:f0 -> struct4 [label = "NMT roots to\nerasure-coded data"]; edge [style = invis]; - struct1 -> struct3; struct1 -> struct4; + struct1 -> struct5; } \ No newline at end of file diff --git a/specs/figures/block_data_structures.svg b/specs/figures/block_data_structures.svg index 3e110df..3840714 100644 --- a/specs/figures/block_data_structures.svg +++ b/specs/figures/block_data_structures.svg @@ -4,110 +4,131 @@ - - + + G - + cluster_block - -block + +block cluster_availableheader - -availableDataHeader + +availableDataHeader cluster_body - -availableData + +availableData -cluster_lastcommit - -lastCommit +cluster_feeheader + +feeHeader +cluster_lastcommit + +lastCommit + + cluster_header - -header + +header - + +struct5 + + + +rowRoots + +colRoots + + + struct4 - - - -rowRoots - -colRoots + + + +transactionData + +intermediateStateRoots + +evidenceData + +messageData + + + +struct5:f0->struct4 + + +NMT roots to +erasure-coded data - + struct3 - - - -transactionData - -intermediateStateRoots - -evidenceData - -messageData - - - -struct4:f0->struct3 - - -NMT roots to -erasure-coded data + +baseRate + +tipRate - + struct2 - -lastCommit + +lastCommit - + struct1 - -height - -timestamp - -lastBlockID - -lastCommitHash - -consensusRoot - -stateCommitment - -availableDataOriginalSharesUsed - -availableDataRoot - -proposerAddress + +height + +timestamp + +lastBlockID + +lastCommitHash + +consensusRoot + +feeHeader + +stateCommitment + +availableDataOriginalSharesUsed + +availableDataRoot + +proposerAddress - - -struct1:f7->struct4 - - -Merkle root of + + +struct1:f8->struct5 + + +Merkle root of + + +struct1:f5->struct3 + + + -struct1:f3->struct2 - - +struct1:f4->struct2 + +