From b6292918485ef784d75866363dbca849908a4ae9 Mon Sep 17 00:00:00 2001 From: Michael Vines Date: Sat, 10 Jul 2021 23:18:42 -0700 Subject: [PATCH] Record vote account commission with voting/staking rewards and surface in RPC (cherry picked from commit 4098af3b5bd69d51729f338b7aab0799bd2f699c) # Conflicts: # docs/src/developing/clients/jsonrpc-api.md # runtime/src/bank.rs --- cli-output/src/cli_output.rs | 37 +- cli/src/stake.rs | 1 + client/src/rpc_response.rs | 5 +- core/src/rewards_recorder_service.rs | 1 + core/src/rpc.rs | 1 + core/src/transaction_status_service.rs | 1 + docs/src/developing/clients/jsonrpc-api.md | 529 +++++++++++++++++++++ ledger-tool/src/main.rs | 12 +- ledger/benches/protobuf.rs | 1 + ledger/src/blockstore.rs | 2 + programs/stake/src/stake_state.rs | 5 +- runtime/src/bank.rs | 95 +++- storage-bigtable/src/lib.rs | 1 + storage-proto/proto/confirmed_block.proto | 1 + storage-proto/src/convert.rs | 3 + storage-proto/src/lib.rs | 7 +- transaction-status/src/lib.rs | 2 + 17 files changed, 679 insertions(+), 25 deletions(-) diff --git a/cli-output/src/cli_output.rs b/cli-output/src/cli_output.rs index acff76e43a..08ebee06c6 100644 --- a/cli-output/src/cli_output.rs +++ b/cli-output/src/cli_output.rs @@ -771,6 +771,7 @@ pub struct CliEpochReward { pub post_balance: u64, // lamports pub percent_change: f64, pub apr: Option, + pub commission: Option, } #[derive(Serialize, Deserialize)] @@ -815,23 +816,27 @@ impl fmt::Display for CliKeyedEpochRewards { writeln!(f, "Epoch Rewards:")?; writeln!( f, - " {:<44} {:<18} {:<18} {:>14} {:>14}", - "Address", "Amount", "New Balance", "Percent Change", "APR" + " {:<44} {:<18} {:<18} {:>14} {:>14} {:>10}", + "Address", "Amount", "New Balance", "Percent Change", "APR", "Commission" )?; for keyed_reward in &self.rewards { match &keyed_reward.reward { Some(reward) => { writeln!( f, - " {:<44} ◎{:<17.9} ◎{:<17.9} {:>13.2}% {}", + " {:<44} ◎{:<17.9} ◎{:<17.9} {:>13.9}% {:>14} {:>10}", keyed_reward.address, lamports_to_sol(reward.amount), lamports_to_sol(reward.post_balance), reward.percent_change, reward .apr - .map(|apr| format!("{:>13.2}%", apr)) + .map(|apr| format!("{:.2}%", apr)) .unwrap_or_default(), + reward + .commission + .map(|commission| format!("{}%", commission)) + .unwrap_or_else(|| "-".to_string()) )?; } None => { @@ -948,13 +953,13 @@ fn show_epoch_rewards( writeln!(f, "Epoch Rewards:")?; writeln!( f, - " {:<6} {:<11} {:<18} {:<18} {:>14} {:>14}", - "Epoch", "Reward Slot", "Amount", "New Balance", "Percent Change", "APR" + " {:<6} {:<11} {:<18} {:<18} {:>14} {:>14} {:>10}", + "Epoch", "Reward Slot", "Amount", "New Balance", "Percent Change", "APR", "Commission" )?; for reward in epoch_rewards { writeln!( f, - " {:<6} {:<11} ◎{:<17.9} ◎{:<17.9} {:>13.2}% {}", + " {:<6} {:<11} ◎{:<17.9} ◎{:<17.9} {:>13.9}% {:>14} {:>10}", reward.epoch, reward.effective_slot, lamports_to_sol(reward.amount), @@ -962,8 +967,12 @@ fn show_epoch_rewards( reward.percent_change, reward .apr - .map(|apr| format!("{:>13.2}%", apr)) + .map(|apr| format!("{:.2}%", apr)) .unwrap_or_default(), + reward + .commission + .map(|commission| format!("{}%", commission)) + .unwrap_or_else(|| "-".to_string()) )?; } } @@ -2174,8 +2183,8 @@ impl fmt::Display for CliBlock { writeln!(f, "Rewards:")?; writeln!( f, - " {:<44} {:^15} {:<15} {:<20} {:>14}", - "Address", "Type", "Amount", "New Balance", "Percent Change" + " {:<44} {:^15} {:<15} {:<20} {:>14} {:>10}", + "Address", "Type", "Amount", "New Balance", "Percent Change", "Commission" )?; for reward in rewards { let sign = if reward.lamports < 0 { "-" } else { "" }; @@ -2183,7 +2192,7 @@ impl fmt::Display for CliBlock { total_rewards += reward.lamports; writeln!( f, - " {:<44} {:^15} {:>15} {}", + " {:<44} {:^15} {:>15} {} {}", reward.pubkey, if let Some(reward_type) = reward.reward_type { format!("{}", reward_type) @@ -2205,7 +2214,11 @@ impl fmt::Display for CliBlock { / (reward.post_balance as f64 - reward.lamports as f64)) * 100.0 ) - } + }, + reward + .commission + .map(|commission| format!("{:>9}%", commission)) + .unwrap_or_else(|| " -".to_string()) )?; } diff --git a/cli/src/stake.rs b/cli/src/stake.rs index 325f7acaca..b4b28dcac3 100644 --- a/cli/src/stake.rs +++ b/cli/src/stake.rs @@ -1822,6 +1822,7 @@ pub fn make_cli_reward( post_balance: reward.post_balance, percent_change: rate_change * 100.0, apr: Some(apr * 100.0), + commission: reward.commission, }) } else { None diff --git a/client/src/rpc_response.rs b/client/src/rpc_response.rs index a712739e60..ba18118fd8 100644 --- a/client/src/rpc_response.rs +++ b/client/src/rpc_response.rs @@ -394,8 +394,9 @@ pub struct RpcPerfSample { pub struct RpcInflationReward { pub epoch: Epoch, pub effective_slot: Slot, - pub amount: u64, // lamports - pub post_balance: u64, // lamports + pub amount: u64, // lamports + pub post_balance: u64, // lamports + pub commission: Option, // Vote account commission when the reward was credited } impl From for RpcConfirmedTransactionStatusWithSignature { diff --git a/core/src/rewards_recorder_service.rs b/core/src/rewards_recorder_service.rs index ef0ad17958..65c66fe49a 100644 --- a/core/src/rewards_recorder_service.rs +++ b/core/src/rewards_recorder_service.rs @@ -55,6 +55,7 @@ impl RewardsRecorderService { lamports: reward_info.lamports, post_balance: reward_info.post_balance, reward_type: Some(reward_info.reward_type), + commission: reward_info.commission, }) .collect(); diff --git a/core/src/rpc.rs b/core/src/rpc.rs index 668acff029..c85f71b576 100644 --- a/core/src/rpc.rs +++ b/core/src/rpc.rs @@ -479,6 +479,7 @@ impl JsonRpcRequestProcessor { effective_slot: first_confirmed_block_in_epoch, amount: reward.lamports.abs() as u64, post_balance: reward.post_balance, + commission: reward.commission, }); } None diff --git a/core/src/transaction_status_service.rs b/core/src/transaction_status_service.rs index 148648b39f..fbdac8721f 100644 --- a/core/src/transaction_status_service.rs +++ b/core/src/transaction_status_service.rs @@ -135,6 +135,7 @@ impl TransactionStatusService { lamports: reward_info.lamports, post_balance: reward_info.post_balance, reward_type: Some(reward_info.reward_type), + commission: reward_info.commission, }) .collect(), ); diff --git a/docs/src/developing/clients/jsonrpc-api.md b/docs/src/developing/clients/jsonrpc-api.md index 4ace466485..fea5f5f924 100644 --- a/docs/src/developing/clients/jsonrpc-api.md +++ b/docs/src/developing/clients/jsonrpc-api.md @@ -630,6 +630,7 @@ The result field will be an object with the following fields: - `lamports: `- number of reward lamports credited or debited by the account, as a i64 - `postBalance: ` - account balance in lamports after the reward was applied - `rewardType: ` - type of reward: "fee", "rent", "voting", "staking" + - `commission: ` - vote account commission when the reward was credited, only present for voting and staking rewards - `blockTime: ` - estimated production time, as Unix timestamp (seconds since the Unix epoch). null if not available - `blockHeight: ` - the number of blocks beneath this block @@ -1633,6 +1634,7 @@ The result field will be a JSON array with the following fields: - `effectiveSlot: `, the slot in which the rewards are effective - `amount: `, reward amount in lamports - `postBalance: `, post balance of the account in lamports +- `commission: ` - vote account commission when the reward was credited #### Example @@ -3015,7 +3017,33 @@ None #### Results: +<<<<<<< HEAD The result field will be a JSON object with the following fields: +======= +- `` - if transaction is not found or not confirmed +- `` - if transaction is confirmed, an object with the following fields: + - `slot: ` - the slot this transaction was processed in + - `transaction: ` - [Transaction](#transaction-structure) object, either in JSON format or encoded binary data, depending on encoding parameter + - `blockTime: ` - estimated production time, as Unix timestamp (seconds since the Unix epoch) of when the transaction was processed. null if not available + - `meta: ` - transaction status metadata object: + - `err: ` - Error if transaction failed, null if transaction succeeded. [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L24) + - `fee: ` - fee this transaction was charged, as u64 integer + - `preBalances: ` - array of u64 account balances from before the transaction was processed + - `postBalances: ` - array of u64 account balances after the transaction was processed + - `innerInstructions: ` - List of [inner instructions](#inner-instructions-structure) or omitted if inner instruction recording was not yet enabled during this transaction + - `preTokenBalances: ` - List of [token balances](#token-balances-structure) from before the transaction was processed or omitted if token balance recording was not yet enabled during this transaction + - `postTokenBalances: ` - List of [token balances](#token-balances-structure) from after the transaction was processed or omitted if token balance recording was not yet enabled during this transaction + - `logMessages: ` - array of string log messages or omitted if log message recording was not yet enabled during this transaction + - DEPRECATED: `status: ` - Transaction status + - `"Ok": ` - Transaction was successful + - `"Err": ` - Transaction failed with TransactionError + - `rewards: ` - present if rewards are requested; an array of JSON objects containing: + - `pubkey: ` - The public key, as base-58 encoded string, of the account that received the reward + - `lamports: `- number of reward lamports credited or debited by the account, as a i64 + - `postBalance: ` - account balance in lamports after the reward was applied + - `rewardType: ` - type of reward: currently only "rent", other types may be added in the future + - `commission: ` - vote account commission when the reward was credited, only present for voting and staking rewards +>>>>>>> 4098af3b5 (Record vote account commission with voting/staking rewards and surface in RPC) - `solana-core`, software version of solana-core - `feature-set`, unique identifier of the current software's feature set @@ -4022,3 +4050,504 @@ Response: ```json {"jsonrpc": "2.0","result": true,"id": 1} ``` +<<<<<<< HEAD +======= + +## JSON RPC API Deprecated Methods + +### getConfirmedBlock + +**DEPRECATED: Please use [getBlock](jsonrpc-api.md#getblock) instead** +This method is expected to be removed in solana-core v1.8 + +Returns identity and transaction information about a confirmed block in the ledger + +#### Parameters: + +- `` - slot, as u64 integer +- `` - (optional) Configuration object containing the following optional fields: + - (optional) `encoding: ` - encoding for each returned Transaction, either "json", "jsonParsed", "base58" (*slow*), "base64". If parameter not provided, the default encoding is "json". + "jsonParsed" encoding attempts to use program-specific instruction parsers to return more human-readable and explicit data in the `transaction.message.instructions` list. If "jsonParsed" is requested but a parser cannot be found, the instruction falls back to regular JSON encoding (`accounts`, `data`, and `programIdIndex` fields). + - (optional) `transactionDetails: ` - level of transaction detail to return, either "full", "signatures", or "none". If parameter not provided, the default detail level is "full". + - (optional) `rewards: bool` - whether to populate the `rewards` array. If parameter not provided, the default includes rewards. + - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment); "processed" is not supported. If parameter not provided, the default is "finalized". + +#### Results: + +The result field will be an object with the following fields: + +- `` - if specified block is not confirmed +- `` - if block is confirmed, an object with the following fields: + - `blockhash: ` - the blockhash of this block, as base-58 encoded string + - `previousBlockhash: ` - the blockhash of this block's parent, as base-58 encoded string; if the parent block is not available due to ledger cleanup, this field will return "11111111111111111111111111111111" + - `parentSlot: ` - the slot index of this block's parent + - `transactions: ` - present if "full" transaction details are requested; an array of JSON objects containing: + - `transaction: ` - [Transaction](#transaction-structure) object, either in JSON format or encoded binary data, depending on encoding parameter + - `meta: ` - transaction status metadata object, containing `null` or: + - `err: ` - Error if transaction failed, null if transaction succeeded. [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L24) + - `fee: ` - fee this transaction was charged, as u64 integer + - `preBalances: ` - array of u64 account balances from before the transaction was processed + - `postBalances: ` - array of u64 account balances after the transaction was processed + - `innerInstructions: ` - List of [inner instructions](#inner-instructions-structure) or omitted if inner instruction recording was not yet enabled during this transaction + - `preTokenBalances: ` - List of [token balances](#token-balances-structure) from before the transaction was processed or omitted if token balance recording was not yet enabled during this transaction + - `postTokenBalances: ` - List of [token balances](#token-balances-structure) from after the transaction was processed or omitted if token balance recording was not yet enabled during this transaction + - `logMessages: ` - array of string log messages or omitted if log message recording was not yet enabled during this transaction + - DEPRECATED: `status: ` - Transaction status + - `"Ok": ` - Transaction was successful + - `"Err": ` - Transaction failed with TransactionError + - `signatures: ` - present if "signatures" are requested for transaction details; an array of signatures strings, corresponding to the transaction order in the block + - `rewards: ` - present if rewards are requested; an array of JSON objects containing: + - `pubkey: ` - The public key, as base-58 encoded string, of the account that received the reward + - `lamports: `- number of reward lamports credited or debited by the account, as a i64 + - `postBalance: ` - account balance in lamports after the reward was applied + - `rewardType: ` - type of reward: "fee", "rent", "voting", "staking" + - `commission: ` - vote account commission when the reward was credited, only present for voting and staking rewards + - `blockTime: ` - estimated production time, as Unix timestamp (seconds since the Unix epoch). null if not available + +#### Example: + +Request: +```bash +curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d ' + {"jsonrpc": "2.0","id":1,"method":"getConfirmedBlock","params":[430, {"encoding": "json","transactionDetails":"full","rewards":false}]} +' +``` + +Result: +```json +{ + "jsonrpc": "2.0", + "result": { + "blockTime": null, + "blockhash": "3Eq21vXNB5s86c62bVuUfTeaMif1N2kUqRPBmGRJhyTA", + "parentSlot": 429, + "previousBlockhash": "mfcyqEXB3DnHXki6KjjmZck6YjmZLvpAByy2fj4nh6B", + "transactions": [ + { + "meta": { + "err": null, + "fee": 5000, + "innerInstructions": [], + "logMessages": [], + "postBalances": [ + 499998932500, + 26858640, + 1, + 1, + 1 + ], + "postTokenBalances": [], + "preBalances": [ + 499998937500, + 26858640, + 1, + 1, + 1 + ], + "preTokenBalances": [], + "status": { + "Ok": null + } + }, + "transaction": { + "message": { + "accountKeys": [ + "3UVYmECPPMZSCqWKfENfuoTv51fTDTWicX9xmBD2euKe", + "AjozzgE83A3x1sHNUR64hfH7zaEBWeMaFuAN9kQgujrc", + "SysvarS1otHashes111111111111111111111111111", + "SysvarC1ock11111111111111111111111111111111", + "Vote111111111111111111111111111111111111111" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 3, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 1, + 2, + 3, + 0 + ], + "data": "37u9WtQpcm6ULa3WRQHmj49EPs4if7o9f1jSRVZpm2dvihR9C8jY4NqEwXUbLwx15HBSNcP1", + "programIdIndex": 4 + } + ], + "recentBlockhash": "mfcyqEXB3DnHXki6KjjmZck6YjmZLvpAByy2fj4nh6B" + }, + "signatures": [ + "2nBhEBYYvfaAe16UMNqRHre4YNSskvuYgx3M6E4JP1oDYvZEJHvoPzyUidNgNX5r9sTyN1J9UxtbCXy2rqYcuyuv" + ] + } + } + ] + }, + "id": 1 +} +``` + +#### Example: +Request: +```bash +curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d ' + {"jsonrpc": "2.0","id":1,"method":"getConfirmedBlock","params":[430, "base64"]} +' +``` + +Result: +```json +{ + "jsonrpc": "2.0", + "result": { + "blockTime": null, + "blockhash": "3Eq21vXNB5s86c62bVuUfTeaMif1N2kUqRPBmGRJhyTA", + "parentSlot": 429, + "previousBlockhash": "mfcyqEXB3DnHXki6KjjmZck6YjmZLvpAByy2fj4nh6B", + "rewards": [], + "transactions": [ + { + "meta": { + "err": null, + "fee": 5000, + "innerInstructions": [], + "logMessages": [], + "postBalances": [ + 499998932500, + 26858640, + 1, + 1, + 1 + ], + "postTokenBalances": [], + "preBalances": [ + 499998937500, + 26858640, + 1, + 1, + 1 + ], + "preTokenBalances": [], + "status": { + "Ok": null + } + }, + "transaction": [ + "AVj7dxHlQ9IrvdYVIjuiRFs1jLaDMHixgrv+qtHBwz51L4/ImLZhszwiyEJDIp7xeBSpm/TX5B7mYzxa+fPOMw0BAAMFJMJVqLw+hJYheizSoYlLm53KzgT82cDVmazarqQKG2GQsLgiqktA+a+FDR4/7xnDX7rsusMwryYVUdixfz1B1Qan1RcZLwqvxvJl4/t3zHragsUp0L47E24tAFUgAAAABqfVFxjHdMkoVmOYaR1etoteuKObS21cc1VbIQAAAAAHYUgdNXR0u3xNdiTr072z2DVec9EQQ/wNo1OAAAAAAAtxOUhPBp2WSjUNJEgfvy70BbxI00fZyEPvFHNfxrtEAQQEAQIDADUCAAAAAQAAAAAAAACtAQAAAAAAAAdUE18R96XTJCe+YfRfUp6WP+YKCy/72ucOL8AoBFSpAA==", + "base64" + ] + } + ] + }, + "id": 1 +} +``` + +For more details on returned data: +[Transaction Structure](jsonrpc-api.md#transactionstructure) +[Inner Instructions Structure](jsonrpc-api.md#innerinstructionsstructure) +[Token Balances Structure](jsonrpc-api.md#tokenbalancesstructure) + +### getConfirmedBlocks + +**DEPRECATED: Please use [getBlocks](jsonrpc-api.md#getblocks) instead** +This method is expected to be removed in solana-core v1.8 + +Returns a list of confirmed blocks between two slots + +#### Parameters: + +- `` - start_slot, as u64 integer +- `` - (optional) end_slot, as u64 integer +- (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment); "processed" is not supported. If parameter not provided, the default is "finalized". + +#### Results: + +The result field will be an array of u64 integers listing confirmed blocks +between `start_slot` and either `end_slot`, if provided, or latest confirmed block, +inclusive. Max range allowed is 500,000 slots. + + +#### Example: + +Request: +```bash +curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d ' + {"jsonrpc": "2.0","id":1,"method":"getConfirmedBlocks","params":[5, 10]} +' +``` + +Result: +```json +{"jsonrpc":"2.0","result":[5,6,7,8,9,10],"id":1} +``` + +### getConfirmedBlocksWithLimit + +**DEPRECATED: Please use [getBlocksWithLimit](jsonrpc-api.md#getblockswithlimit) instead** +This method is expected to be removed in solana-core v1.8 + +Returns a list of confirmed blocks starting at the given slot + +#### Parameters: + +- `` - start_slot, as u64 integer +- `` - limit, as u64 integer +- (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment); "processed" is not supported. If parameter not provided, the default is "finalized". + +#### Results: + +The result field will be an array of u64 integers listing confirmed blocks +starting at `start_slot` for up to `limit` blocks, inclusive. + +#### Example: + +Request: +```bash +curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d ' + {"jsonrpc": "2.0","id":1,"method":"getConfirmedBlocksWithLimit","params":[5, 3]} +' +``` + +Result: +```json +{"jsonrpc":"2.0","result":[5,6,7],"id":1} +``` + +### getConfirmedSignaturesForAddress2 + +**DEPRECATED: Please use [getSignaturesForAddress](jsonrpc-api.md#getsignaturesforaddress) instead** +This method is expected to be removed in solana-core v1.8 + +Returns confirmed signatures for transactions involving an +address backwards in time from the provided signature or most recent confirmed block + +#### Parameters: +* `` - account address as base-58 encoded string +* `` - (optional) Configuration object containing the following fields: + * `limit: ` - (optional) maximum transaction signatures to return (between 1 and 1,000, default: 1,000). + * `before: ` - (optional) start searching backwards from this transaction signature. + If not provided the search starts from the top of the highest max confirmed block. + * `until: ` - (optional) search until this transaction signature, if found before limit reached. + * (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment); "processed" is not supported. If parameter not provided, the default is "finalized". + +#### Results: +The result field will be an array of transaction signature information, ordered +from newest to oldest transaction: +* `` + * `signature: ` - transaction signature as base-58 encoded string + * `slot: ` - The slot that contains the block with the transaction + * `err: ` - Error if transaction failed, null if transaction succeeded. [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L24) + * `memo: ` - Memo associated with the transaction, null if no memo is present + * `blockTime: ` - estimated production time, as Unix timestamp (seconds since the Unix epoch) of when transaction was processed. null if not available. + +#### Example: +Request: +```bash +curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d ' + { + "jsonrpc": "2.0", + "id": 1, + "method": "getConfirmedSignaturesForAddress2", + "params": [ + "Vote111111111111111111111111111111111111111", + { + "limit": 1 + } + ] + } +' +``` + +Result: +```json +{ + "jsonrpc": "2.0", + "result": [ + { + "err": null, + "memo": null, + "signature": "5h6xBEauJ3PK6SWCZ1PGjBvj8vDdWG3KpwATGy1ARAXFSDwt8GFXM7W5Ncn16wmqokgpiKRLuS83KUxyZyv2sUYv", + "slot": 114, + "blockTime": null + } + ], + "id": 1 +} +``` + +### getConfirmedTransaction + +**DEPRECATED: Please use [getTransaction](jsonrpc-api.md#gettransaction) instead** +This method is expected to be removed in solana-core v1.8 + +Returns transaction details for a confirmed transaction + +#### Parameters: + +- `` - transaction signature as base-58 encoded string +- `` - (optional) Configuration object containing the following optional fields: + - (optional) `encoding: ` - encoding for each returned Transaction, either "json", "jsonParsed", "base58" (*slow*), "base64". If parameter not provided, the default encoding is "json". + "jsonParsed" encoding attempts to use program-specific instruction parsers to return more human-readable and explicit data in the `transaction.message.instructions` list. If "jsonParsed" is requested but a parser cannot be found, the instruction falls back to regular JSON encoding (`accounts`, `data`, and `programIdIndex` fields). + - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment); "processed" is not supported. If parameter not provided, the default is "finalized". + +#### Results: + +- `` - if transaction is not found or not confirmed +- `` - if transaction is confirmed, an object with the following fields: + - `slot: ` - the slot this transaction was processed in + - `transaction: ` - [Transaction](#transaction-structure) object, either in JSON format or encoded binary data, depending on encoding parameter + - `blockTime: ` - estimated production time, as Unix timestamp (seconds since the Unix epoch) of when the transaction was processed. null if not available + - `meta: ` - transaction status metadata object: + - `err: ` - Error if transaction failed, null if transaction succeeded. [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L24) + - `fee: ` - fee this transaction was charged, as u64 integer + - `preBalances: ` - array of u64 account balances from before the transaction was processed + - `postBalances: ` - array of u64 account balances after the transaction was processed + - `innerInstructions: ` - List of [inner instructions](#inner-instructions-structure) or omitted if inner instruction recording was not yet enabled during this transaction + - `preTokenBalances: ` - List of [token balances](#token-balances-structure) from before the transaction was processed or omitted if token balance recording was not yet enabled during this transaction + - `postTokenBalances: ` - List of [token balances](#token-balances-structure) from after the transaction was processed or omitted if token balance recording was not yet enabled during this transaction + - `logMessages: ` - array of string log messages or omitted if log message recording was not yet enabled during this transaction + - DEPRECATED: `status: ` - Transaction status + - `"Ok": ` - Transaction was successful + - `"Err": ` - Transaction failed with TransactionError + +#### Example: +Request: +```bash +curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d ' + { + "jsonrpc": "2.0", + "id": 1, + "method": "getConfirmedTransaction", + "params": [ + "2nBhEBYYvfaAe16UMNqRHre4YNSskvuYgx3M6E4JP1oDYvZEJHvoPzyUidNgNX5r9sTyN1J9UxtbCXy2rqYcuyuv", + "json" + ] + } +' +``` + +Result: +```json +{ + "jsonrpc": "2.0", + "result": { + "meta": { + "err": null, + "fee": 5000, + "innerInstructions": [], + "postBalances": [ + 499998932500, + 26858640, + 1, + 1, + 1 + ], + "postTokenBalances": [], + "preBalances": [ + 499998937500, + 26858640, + 1, + 1, + 1 + ], + "preTokenBalances": [], + "status": { + "Ok": null + } + }, + "slot": 430, + "transaction": { + "message": { + "accountKeys": [ + "3UVYmECPPMZSCqWKfENfuoTv51fTDTWicX9xmBD2euKe", + "AjozzgE83A3x1sHNUR64hfH7zaEBWeMaFuAN9kQgujrc", + "SysvarS1otHashes111111111111111111111111111", + "SysvarC1ock11111111111111111111111111111111", + "Vote111111111111111111111111111111111111111" + ], + "header": { + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 3, + "numRequiredSignatures": 1 + }, + "instructions": [ + { + "accounts": [ + 1, + 2, + 3, + 0 + ], + "data": "37u9WtQpcm6ULa3WRQHmj49EPs4if7o9f1jSRVZpm2dvihR9C8jY4NqEwXUbLwx15HBSNcP1", + "programIdIndex": 4 + } + ], + "recentBlockhash": "mfcyqEXB3DnHXki6KjjmZck6YjmZLvpAByy2fj4nh6B" + }, + "signatures": [ + "2nBhEBYYvfaAe16UMNqRHre4YNSskvuYgx3M6E4JP1oDYvZEJHvoPzyUidNgNX5r9sTyN1J9UxtbCXy2rqYcuyuv" + ] + } + }, + "blockTime": null, + "id": 1 +} +``` + +#### Example: +Request: +```bash +curl http://localhost:8899 -X POST -H "Content-Type: application/json" -d ' + { + "jsonrpc": "2.0", + "id": 1, + "method": "getConfirmedTransaction", + "params": [ + "2nBhEBYYvfaAe16UMNqRHre4YNSskvuYgx3M6E4JP1oDYvZEJHvoPzyUidNgNX5r9sTyN1J9UxtbCXy2rqYcuyuv", + "base64" + ] + } +' +``` + +Result: +```json +{ + "jsonrpc": "2.0", + "result": { + "meta": { + "err": null, + "fee": 5000, + "innerInstructions": [], + "postBalances": [ + 499998932500, + 26858640, + 1, + 1, + 1 + ], + "postTokenBalances": [], + "preBalances": [ + 499998937500, + 26858640, + 1, + 1, + 1 + ], + "preTokenBalances": [], + "status": { + "Ok": null + } + }, + "slot": 430, + "transaction": [ + "AVj7dxHlQ9IrvdYVIjuiRFs1jLaDMHixgrv+qtHBwz51L4/ImLZhszwiyEJDIp7xeBSpm/TX5B7mYzxa+fPOMw0BAAMFJMJVqLw+hJYheizSoYlLm53KzgT82cDVmazarqQKG2GQsLgiqktA+a+FDR4/7xnDX7rsusMwryYVUdixfz1B1Qan1RcZLwqvxvJl4/t3zHragsUp0L47E24tAFUgAAAABqfVFxjHdMkoVmOYaR1etoteuKObS21cc1VbIQAAAAAHYUgdNXR0u3xNdiTr072z2DVec9EQQ/wNo1OAAAAAAAtxOUhPBp2WSjUNJEgfvy70BbxI00fZyEPvFHNfxrtEAQQEAQIDADUCAAAAAQAAAAAAAACtAQAAAAAAAAdUE18R96XTJCe+YfRfUp6WP+YKCy/72ucOL8AoBFSpAA==", + "base64" + ] + }, + "id": 1 +} +``` +>>>>>>> 4098af3b5 (Record vote account commission with voting/staking rewards and surface in RPC) diff --git a/ledger-tool/src/main.rs b/ledger-tool/src/main.rs index cb431b6463..7497862a34 100644 --- a/ledger-tool/src/main.rs +++ b/ledger-tool/src/main.rs @@ -76,14 +76,14 @@ fn output_slot_rewards(blockstore: &Blockstore, slot: Slot, method: &LedgerOutpu if !rewards.is_empty() { println!(" Rewards:"); println!( - " {:<44} {:^15} {:<15} {:<20}", - "Address", "Type", "Amount", "New Balance" + " {:<44} {:^15} {:<15} {:<20} {:>10}", + "Address", "Type", "Amount", "New Balance", "Commission", ); for reward in rewards { let sign = if reward.lamports < 0 { "-" } else { "" }; println!( - " {:<44} {:^15} {:<15} {}", + " {:<44} {:^15} {:<15} {} {}", reward.pubkey, if let Some(reward_type) = reward.reward_type { format!("{}", reward_type) @@ -95,7 +95,11 @@ fn output_slot_rewards(blockstore: &Blockstore, slot: Slot, method: &LedgerOutpu sign, lamports_to_sol(reward.lamports.abs() as u64) ), - format!("◎{:<18.9}", lamports_to_sol(reward.post_balance)) + format!("◎{:<18.9}", lamports_to_sol(reward.post_balance)), + reward + .commission + .map(|commission| format!("{:>9}%", commission)) + .unwrap_or_else(|| " -".to_string()) ); } } diff --git a/ledger/benches/protobuf.rs b/ledger/benches/protobuf.rs index 6b511b2925..e0244efa07 100644 --- a/ledger/benches/protobuf.rs +++ b/ledger/benches/protobuf.rs @@ -21,6 +21,7 @@ fn create_rewards() -> Rewards { lamports: 42 + i, post_balance: std::u64::MAX, reward_type: Some(RewardType::Fee), + commission: None, }) .collect() } diff --git a/ledger/src/blockstore.rs b/ledger/src/blockstore.rs index 3ad3af30ff..8c560541b7 100644 --- a/ledger/src/blockstore.rs +++ b/ledger/src/blockstore.rs @@ -8183,6 +8183,7 @@ pub mod tests { lamports: 42 + i, post_balance: std::u64::MAX, reward_type: Some(RewardType::Fee), + commission: None, }) .collect(); let protobuf_rewards: generated::Rewards = rewards.into(); @@ -8249,6 +8250,7 @@ pub mod tests { lamports: -42, post_balance: 42, reward_type: Some(RewardType::Rent), + commission: None, }]), }; let deprecated_status: StoredTransactionStatusMeta = status.clone().into(); diff --git a/programs/stake/src/stake_state.rs b/programs/stake/src/stake_state.rs index 28fa34a9ac..67b844b045 100644 --- a/programs/stake/src/stake_state.rs +++ b/programs/stake/src/stake_state.rs @@ -1523,14 +1523,13 @@ pub fn redeem_rewards( rewarded_epoch: Epoch, stake_account: &mut AccountSharedData, vote_account: &mut AccountSharedData, + vote_state: &VoteState, point_value: &PointValue, stake_history: Option<&StakeHistory>, inflation_point_calc_tracer: &mut Option, fix_stake_deactivate: bool, ) -> Result<(u64, u64), InstructionError> { if let StakeState::Stake(meta, mut stake) = stake_account.state()? { - let vote_state: VoteState = - StateMut::::state(vote_account)?.convert_to_current(); if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer { inflation_point_calc_tracer( &InflationPointCalculationEvent::EffectiveStakeAtRewardedEpoch(stake.stake( @@ -1549,7 +1548,7 @@ pub fn redeem_rewards( if let Some((stakers_reward, voters_reward)) = stake.redeem_rewards( point_value, - &vote_state, + vote_state, stake_history, inflation_point_calc_tracer, fix_stake_deactivate, diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index ad5303a3a3..febd204071 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -39,6 +39,7 @@ use solana_sdk::{ create_account_shared_data_with_fields as create_account, from_account, Account, AccountSharedData, InheritableAccountFields, ReadableAccount, }, + account_utils::StateMut, clock::{ Epoch, Slot, SlotCount, SlotIndex, UnixTimestamp, DEFAULT_TICKS_PER_SECOND, INITIAL_RENT_EPOCH, MAX_PROCESSING_AGE, MAX_RECENT_BLOCKHASHES, @@ -72,10 +73,18 @@ use solana_sdk::{ timing::years_as_slots, transaction::{self, Result, Transaction, TransactionError}, }; +<<<<<<< HEAD use solana_stake_program::stake_state::{ self, Delegation, InflationPointCalculationEvent, PointValue, }; use solana_vote_program::vote_instruction::VoteInstruction; +======= +use solana_stake_program::stake_state::{self, InflationPointCalculationEvent, PointValue}; +use solana_vote_program::{ + vote_instruction::VoteInstruction, + vote_state::{VoteState, VoteStateVersions}, +}; +>>>>>>> 4098af3b5 (Record vote account commission with voting/staking rewards and surface in RPC) use std::{ borrow::Cow, cell::RefCell, @@ -110,6 +119,7 @@ impl RentDebits { reward_type: RewardType::Rent, lamports: rent_debit, post_balance, + commission: None, // Not applicable }; self.0.push((*account, reward_info)); } else { @@ -735,8 +745,9 @@ pub trait DropCallback: fmt::Debug { #[derive(Debug, PartialEq, Serialize, Deserialize, AbiExample, Clone, Copy)] pub struct RewardInfo { pub reward_type: RewardType, - pub lamports: i64, // Reward amount - pub post_balance: u64, // Account balance in lamports after `lamports` was applied + pub lamports: i64, // Reward amount + pub post_balance: u64, // Account balance in lamports after `lamports` was applied + pub commission: Option, // Vote account commission when the reward was credited, only present for voting and staking rewards } #[derive(Debug, Default)] @@ -1877,7 +1888,22 @@ impl Bank { // pay according to point value for (vote_pubkey, (stake_group, vote_account)) in stake_delegation_accounts.iter_mut() { let mut vote_account_changed = false; +<<<<<<< HEAD let voters_account_pre_balance = vote_account.lamports; +======= + let voters_account_pre_balance = vote_account.lamports(); + let vote_state: VoteState = match StateMut::::state(vote_account) { + Ok(vote_state) => vote_state.convert_to_current(), + Err(err) => { + debug!( + "failed to deserialize vote account {}: {}", + vote_pubkey, err + ); + continue; + } + }; + let commission = Some(vote_state.commission); +>>>>>>> 4098af3b5 (Record vote account commission with voting/staking rewards and surface in RPC) for (stake_pubkey, stake_account) in stake_group.iter_mut() { // curry closure to add the contextual stake_pubkey @@ -1892,6 +1918,7 @@ impl Bank { rewarded_epoch, stake_account, vote_account, + &vote_state, &point_value, Some(&stake_history), &mut reward_calc_tracer.as_mut(), @@ -1907,7 +1934,12 @@ impl Bank { RewardInfo { reward_type: RewardType::Staking, lamports: stakers_reward as i64, +<<<<<<< HEAD post_balance: stake_account.lamports, +======= + post_balance: stake_account.lamports(), + commission, +>>>>>>> 4098af3b5 (Record vote account commission with voting/staking rewards and surface in RPC) }, )); } @@ -1929,6 +1961,7 @@ impl Bank { reward_type: RewardType::Voting, lamports, post_balance, + commission, }, )); } @@ -2031,6 +2064,7 @@ impl Bank { unburned, collector_fees, burned ); +<<<<<<< HEAD let post_balance = self.deposit(&self.collector_id, unburned); if unburned != 0 { self.rewards.write().unwrap().push(( @@ -2041,6 +2075,30 @@ impl Bank { post_balance, }, )); +======= + match self.deposit(&self.collector_id, deposit) { + Ok(post_balance) => { + if deposit != 0 { + self.rewards.write().unwrap().push(( + self.collector_id, + RewardInfo { + reward_type: RewardType::Fee, + lamports: deposit as i64, + post_balance, + commission: None, + }, + )); + } + } + Err(_) => { + error!( + "Burning {} fee instead of crediting {}", + deposit, self.collector_id + ); + inc_new_counter_error!("bank-burned_fee_lamports", deposit as usize); + burn += deposit; + } +>>>>>>> 4098af3b5 (Record vote account commission with voting/staking rewards and surface in RPC) } self.capitalization.fetch_sub(burned, Relaxed); } @@ -3410,6 +3468,7 @@ impl Bank { rent_share }; if !enforce_fix || rent_to_be_paid > 0 { +<<<<<<< HEAD let mut account = self.get_account(&pubkey).unwrap_or_default(); account.lamports += rent_to_be_paid; self.store_account(&pubkey, &account); @@ -3421,6 +3480,34 @@ impl Bank { post_balance: account.lamports, }, )); +======= + let mut account = self + .get_account_with_fixed_root(&pubkey) + .unwrap_or_default(); + if account.checked_add_lamports(rent_to_be_paid).is_err() { + // overflow adding lamports + self.capitalization.fetch_sub(rent_to_be_paid, Relaxed); + error!( + "Burned {} rent lamports instead of sending to {}", + rent_to_be_paid, pubkey + ); + inc_new_counter_error!( + "bank-burned_rent_lamports", + rent_to_be_paid as usize + ); + } else { + self.store_account(&pubkey, &account); + rewards.push(( + pubkey, + RewardInfo { + reward_type: RewardType::Rent, + lamports: rent_to_be_paid as i64, + post_balance: account.lamports(), + commission: None, + }, + )); + } +>>>>>>> 4098af3b5 (Record vote account commission with voting/staking rewards and surface in RPC) } }); self.rewards.write().unwrap().append(&mut rewards); @@ -5135,7 +5222,6 @@ pub(crate) mod tests { use crossbeam_channel::bounded; use solana_sdk::{ account::Account, - account_utils::StateMut, clock::{DEFAULT_SLOTS_PER_EPOCH, DEFAULT_TICKS_PER_SLOT}, epoch_schedule::MINIMUM_SLOTS_PER_EPOCH, feature::Feature, @@ -7131,6 +7217,7 @@ pub(crate) mod tests { reward_type: RewardType::Staking, lamports: (rewards.validator_point_value * validator_points as f64) as i64, post_balance: bank1.get_balance(&stake_id), + commission: Some(0), } )] ); @@ -7631,6 +7718,7 @@ pub(crate) mod tests { reward_type: RewardType::Fee, lamports: expected_fee_collected as i64, post_balance: initial_balance + expected_fee_collected, + commission: None, } )] ); @@ -7666,6 +7754,7 @@ pub(crate) mod tests { reward_type: RewardType::Fee, lamports: expected_fee_collected as i64, post_balance: initial_balance + 2 * expected_fee_collected, + commission: None, } )] ); diff --git a/storage-bigtable/src/lib.rs b/storage-bigtable/src/lib.rs index a06cd48335..6b6877e678 100644 --- a/storage-bigtable/src/lib.rs +++ b/storage-bigtable/src/lib.rs @@ -238,6 +238,7 @@ impl From for Reward { lamports, post_balance: 0, reward_type: None, + commission: None, } } } diff --git a/storage-proto/proto/confirmed_block.proto b/storage-proto/proto/confirmed_block.proto index 6e0d88f3cf..57f119febe 100644 --- a/storage-proto/proto/confirmed_block.proto +++ b/storage-proto/proto/confirmed_block.proto @@ -88,6 +88,7 @@ message Reward { int64 lamports = 2; uint64 post_balance = 3; RewardType reward_type = 4; + string commission = 5; } message Rewards { diff --git a/storage-proto/src/convert.rs b/storage-proto/src/convert.rs index 7a7e3d1b6e..a2304bec7b 100644 --- a/storage-proto/src/convert.rs +++ b/storage-proto/src/convert.rs @@ -89,6 +89,7 @@ impl From for generated::Reward { Some(RewardType::Staking) => generated::RewardType::Staking, Some(RewardType::Voting) => generated::RewardType::Voting, } as i32, + commission: reward.commission.map(|c| c.to_string()).unwrap_or_default(), } } } @@ -107,6 +108,7 @@ impl From for Reward { 4 => Some(RewardType::Voting), _ => None, }, + commission: reward.commission.parse::().ok(), } } } @@ -841,6 +843,7 @@ mod test { lamports: 123, post_balance: 321, reward_type: None, + commission: None, }; let gen_reward: generated::Reward = reward.clone().into(); assert_eq!(reward, gen_reward.into()); diff --git a/storage-proto/src/lib.rs b/storage-proto/src/lib.rs index 2a21b6b937..bcb876e65a 100644 --- a/storage-proto/src/lib.rs +++ b/storage-proto/src/lib.rs @@ -23,6 +23,8 @@ pub struct StoredExtendedReward { post_balance: u64, #[serde(deserialize_with = "default_on_eof")] reward_type: Option, + #[serde(deserialize_with = "default_on_eof")] + commission: Option, } impl From for Reward { @@ -32,12 +34,14 @@ impl From for Reward { lamports, post_balance, reward_type, + commission, } = value; Self { pubkey, lamports, post_balance, reward_type, + commission, } } } @@ -49,13 +53,14 @@ impl From for StoredExtendedReward { lamports, post_balance, reward_type, - .. + commission, } = value; Self { pubkey, lamports, post_balance, reward_type, + commission, } } } diff --git a/transaction-status/src/lib.rs b/transaction-status/src/lib.rs index 84a90d5317..c0126311af 100644 --- a/transaction-status/src/lib.rs +++ b/transaction-status/src/lib.rs @@ -339,6 +339,8 @@ pub struct Reward { pub lamports: i64, pub post_balance: u64, // Account balance in lamports after `lamports` was applied pub reward_type: Option, + #[serde(deserialize_with = "default_on_eof")] + pub commission: Option, // Vote account commission when the reward was credited, only present for voting and staking rewards } pub type Rewards = Vec;