Record and store invoked instructions in transaction meta (#12311)

* Record invoked instructions and store in transaction meta

* Enable cpi recording if transaction sender is some

* Rename invoked to innerInstructions
This commit is contained in:
Justin Starry
2020-09-24 22:36:22 +08:00
committed by GitHub
parent 860ecdd376
commit 6601ec8f26
19 changed files with 429 additions and 98 deletions

View File

@ -531,8 +531,19 @@ impl BankingStage {
} else { } else {
vec![] vec![]
}; };
let (mut loaded_accounts, results, mut retryable_txs, tx_count, signature_count) = let (
bank.load_and_execute_transactions(batch, MAX_PROCESSING_AGE, None); mut loaded_accounts,
results,
inner_instructions,
mut retryable_txs,
tx_count,
signature_count,
) = bank.load_and_execute_transactions(
batch,
MAX_PROCESSING_AGE,
None,
transaction_status_sender.is_some(),
);
load_execute_time.stop(); load_execute_time.stop();
let freeze_lock = bank.freeze_lock(); let freeze_lock = bank.freeze_lock();
@ -569,6 +580,7 @@ impl BankingStage {
batch.iteration_order_vec(), batch.iteration_order_vec(),
tx_results.processing_results, tx_results.processing_results,
TransactionBalancesSet::new(pre_balances, post_balances), TransactionBalancesSet::new(pre_balances, post_balances),
inner_instructions,
sender, sender,
); );
} }

View File

@ -1,11 +1,12 @@
use crossbeam_channel::{Receiver, RecvTimeoutError}; use crossbeam_channel::{Receiver, RecvTimeoutError};
use itertools::izip;
use solana_ledger::{blockstore::Blockstore, blockstore_processor::TransactionStatusBatch}; use solana_ledger::{blockstore::Blockstore, blockstore_processor::TransactionStatusBatch};
use solana_runtime::{ use solana_runtime::{
bank::{Bank, HashAgeKind}, bank::{Bank, HashAgeKind},
nonce_utils, nonce_utils,
transaction_utils::OrderedIterator, transaction_utils::OrderedIterator,
}; };
use solana_transaction_status::TransactionStatusMeta; use solana_transaction_status::{InnerInstructions, TransactionStatusMeta};
use std::{ use std::{
sync::{ sync::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
@ -54,15 +55,23 @@ impl TransactionStatusService {
iteration_order, iteration_order,
statuses, statuses,
balances, balances,
inner_instructions,
} = write_transaction_status_receiver.recv_timeout(Duration::from_secs(1))?; } = write_transaction_status_receiver.recv_timeout(Duration::from_secs(1))?;
let slot = bank.slot(); let slot = bank.slot();
for ((((_, transaction), (status, hash_age_kind)), pre_balances), post_balances) in for (
OrderedIterator::new(&transactions, iteration_order.as_deref()) (_, transaction),
.zip(statuses) (status, hash_age_kind),
.zip(balances.pre_balances) pre_balances,
.zip(balances.post_balances) post_balances,
{ inner_instructions,
) in izip!(
OrderedIterator::new(&transactions, iteration_order.as_deref()),
statuses,
balances.pre_balances,
balances.post_balances,
inner_instructions
) {
if Bank::can_commit(&status) && !transaction.signatures.is_empty() { if Bank::can_commit(&status) && !transaction.signatures.is_empty() {
let fee_calculator = match hash_age_kind { let fee_calculator = match hash_age_kind {
Some(HashAgeKind::DurableNonce(_, account)) => { Some(HashAgeKind::DurableNonce(_, account)) => {
@ -77,6 +86,19 @@ impl TransactionStatusService {
); );
let (writable_keys, readonly_keys) = let (writable_keys, readonly_keys) =
transaction.message.get_account_keys_by_lock_type(); transaction.message.get_account_keys_by_lock_type();
let inner_instructions = inner_instructions.map(|inner_instructions| {
inner_instructions
.into_iter()
.enumerate()
.map(|(index, instructions)| InnerInstructions {
index: index as u8,
instructions,
})
.filter(|i| !i.instructions.is_empty())
.collect()
});
blockstore blockstore
.write_transaction_status( .write_transaction_status(
slot, slot,
@ -88,6 +110,7 @@ impl TransactionStatusService {
fee, fee,
pre_balances, pre_balances,
post_balances, post_balances,
inner_instructions,
}, },
) )
.expect("Expect database write to succeed"); .expect("Expect database write to succeed");

View File

@ -332,6 +332,7 @@ The result field will be an object with the following fields:
- `fee: <u64>` - fee this transaction was charged, as u64 integer - `fee: <u64>` - fee this transaction was charged, as u64 integer
- `preBalances: <array>` - array of u64 account balances from before the transaction was processed - `preBalances: <array>` - array of u64 account balances from before the transaction was processed
- `postBalances: <array>` - array of u64 account balances after the transaction was processed - `postBalances: <array>` - array of u64 account balances after the transaction was processed
- `innerInstructions: <array|undefined>` - List of [inner instructions](#inner-instructions-structure) or omitted if inner instruction recording was not yet enabled during this transaction
- DEPRECATED: `status: <object>` - Transaction status - DEPRECATED: `status: <object>` - Transaction status
- `"Ok": <null>` - Transaction was successful - `"Ok": <null>` - Transaction was successful
- `"Err": <ERR>` - Transaction failed with TransactionError - `"Err": <ERR>` - Transaction failed with TransactionError
@ -347,13 +348,13 @@ The result field will be an object with the following fields:
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedBlock","params":[430, "json"]}' localhost:8899 curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedBlock","params":[430, "json"]}' localhost:8899
// Result // Result
{"jsonrpc":"2.0","result":{"blockTime":null,"blockhash":"3Eq21vXNB5s86c62bVuUfTeaMif1N2kUqRPBmGRJhyTA","parentSlot":429,"previousBlockhash":"mfcyqEXB3DnHXki6KjjmZck6YjmZLvpAByy2fj4nh6B","rewards":[],"transactions":[{"meta":{"err":null,"fee":5000,"postBalances":[499998932500,26858640,1,1,1],"preBalances":[499998937500,26858640,1,1,1],"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} {"jsonrpc":"2.0","result":{"blockTime":null,"blockhash":"3Eq21vXNB5s86c62bVuUfTeaMif1N2kUqRPBmGRJhyTA","parentSlot":429,"previousBlockhash":"mfcyqEXB3DnHXki6KjjmZck6YjmZLvpAByy2fj4nh6B","rewards":[],"transactions":[{"meta":{"err":null,"fee":5000,"innerInstructions":[],"postBalances":[499998932500,26858640,1,1,1],"preBalances":[499998937500,26858640,1,1,1],"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}
// Request // Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedBlock","params":[430, "base64"]}' localhost:8899 curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedBlock","params":[430, "base64"]}' localhost:8899
// Result // Result
{"jsonrpc":"2.0","result":{"blockTime":null,"blockhash":"3Eq21vXNB5s86c62bVuUfTeaMif1N2kUqRPBmGRJhyTA","parentSlot":429,"previousBlockhash":"mfcyqEXB3DnHXki6KjjmZck6YjmZLvpAByy2fj4nh6B","rewards":[],"transactions":[{"meta":{"err":null,"fee":5000,"postBalances":[499998932500,26858640,1,1,1],"preBalances":[499998937500,26858640,1,1,1],"status":{"Ok":null}},"transaction":["AVj7dxHlQ9IrvdYVIjuiRFs1jLaDMHixgrv+qtHBwz51L4/ImLZhszwiyEJDIp7xeBSpm/TX5B7mYzxa+fPOMw0BAAMFJMJVqLw+hJYheizSoYlLm53KzgT82cDVmazarqQKG2GQsLgiqktA+a+FDR4/7xnDX7rsusMwryYVUdixfz1B1Qan1RcZLwqvxvJl4/t3zHragsUp0L47E24tAFUgAAAABqfVFxjHdMkoVmOYaR1etoteuKObS21cc1VbIQAAAAAHYUgdNXR0u3xNdiTr072z2DVec9EQQ/wNo1OAAAAAAAtxOUhPBp2WSjUNJEgfvy70BbxI00fZyEPvFHNfxrtEAQQEAQIDADUCAAAAAQAAAAAAAACtAQAAAAAAAAdUE18R96XTJCe+YfRfUp6WP+YKCy/72ucOL8AoBFSpAA==","base64"]}]},"id":1} {"jsonrpc":"2.0","result":{"blockTime":null,"blockhash":"3Eq21vXNB5s86c62bVuUfTeaMif1N2kUqRPBmGRJhyTA","parentSlot":429,"previousBlockhash":"mfcyqEXB3DnHXki6KjjmZck6YjmZLvpAByy2fj4nh6B","rewards":[],"transactions":[{"meta":{"err":null,"fee":5000,"innerInstructions":[],"postBalances":[499998932500,26858640,1,1,1],"preBalances":[499998937500,26858640,1,1,1],"status":{"Ok":null}},"transaction":["AVj7dxHlQ9IrvdYVIjuiRFs1jLaDMHixgrv+qtHBwz51L4/ImLZhszwiyEJDIp7xeBSpm/TX5B7mYzxa+fPOMw0BAAMFJMJVqLw+hJYheizSoYlLm53KzgT82cDVmazarqQKG2GQsLgiqktA+a+FDR4/7xnDX7rsusMwryYVUdixfz1B1Qan1RcZLwqvxvJl4/t3zHragsUp0L47E24tAFUgAAAABqfVFxjHdMkoVmOYaR1etoteuKObS21cc1VbIQAAAAAHYUgdNXR0u3xNdiTr072z2DVec9EQQ/wNo1OAAAAAAAtxOUhPBp2WSjUNJEgfvy70BbxI00fZyEPvFHNfxrtEAQQEAQIDADUCAAAAAQAAAAAAAACtAQAAAAAAAAdUE18R96XTJCe+YfRfUp6WP+YKCy/72ucOL8AoBFSpAA==","base64"]}]},"id":1}
``` ```
#### Transaction Structure #### Transaction Structure
@ -375,6 +376,18 @@ The JSON structure of a transaction is defined as follows:
- `accounts: <array[number]>` - List of ordered indices into the `message.accountKeys` array indicating which accounts to pass to the program. - `accounts: <array[number]>` - List of ordered indices into the `message.accountKeys` array indicating which accounts to pass to the program.
- `data: <string>` - The program input data encoded in a base-58 string. - `data: <string>` - The program input data encoded in a base-58 string.
#### Inner Instructions Structure
The Solana runtime records the cross-program instructions that are invoked during transaction processing and makes these available for greater transparency of what was executed on-chain per transaction instruction. Invoked instructions are grouped by the originating transaction instruction and are listed in order of processing.
The JSON structure of inner instructions is defined as a list of objects in the following structure:
- `index: number` - Index of the transaction instruction from which the inner instruction(s) originated
- `instructions: <array[object]>` - Ordered list of inner program instructions that were invoked during a single transaction instruction.
- `programIdIndex: <number>` - Index into the `message.accountKeys` array indicating the program account that executes this instruction.
- `accounts: <array[number]>` - List of ordered indices into the `message.accountKeys` array indicating which accounts to pass to the program.
- `data: <string>` - The program input data encoded in a base-58 string.
### getConfirmedBlocks ### getConfirmedBlocks
Returns a list of confirmed blocks Returns a list of confirmed blocks
@ -485,6 +498,7 @@ N encoding attempts to use program-specific instruction parsers to return more h
- `fee: <u64>` - fee this transaction was charged, as u64 integer - `fee: <u64>` - fee this transaction was charged, as u64 integer
- `preBalances: <array>` - array of u64 account balances from before the transaction was processed - `preBalances: <array>` - array of u64 account balances from before the transaction was processed
- `postBalances: <array>` - array of u64 account balances after the transaction was processed - `postBalances: <array>` - array of u64 account balances after the transaction was processed
- `innerInstructions: <array|undefined>` - List of [inner instructions](#inner-instructions-structure) or omitted if inner instruction recording was not yet enabled during this transaction
- DEPRECATED: `status: <object>` - Transaction status - DEPRECATED: `status: <object>` - Transaction status
- `"Ok": <null>` - Transaction was successful - `"Ok": <null>` - Transaction was successful
- `"Err": <ERR>` - Transaction failed with TransactionError - `"Err": <ERR>` - Transaction failed with TransactionError
@ -496,13 +510,13 @@ N encoding attempts to use program-specific instruction parsers to return more h
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedTransaction","params":["2nBhEBYYvfaAe16UMNqRHre4YNSskvuYgx3M6E4JP1oDYvZEJHvoPzyUidNgNX5r9sTyN1J9UxtbCXy2rqYcuyuv", "json"]}' localhost:8899 curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedTransaction","params":["2nBhEBYYvfaAe16UMNqRHre4YNSskvuYgx3M6E4JP1oDYvZEJHvoPzyUidNgNX5r9sTyN1J9UxtbCXy2rqYcuyuv", "json"]}' localhost:8899
// Result // Result
{"jsonrpc":"2.0","result":{"meta":{"err":null,"fee":5000,"postBalances":[499998932500,26858640,1,1,1],"preBalances":[499998937500,26858640,1,1,1],"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"]}},"id":1} {"jsonrpc":"2.0","result":{"meta":{"err":null,"fee":5000,"innerInstructions":[],"postBalances":[499998932500,26858640,1,1,1],"preBalances":[499998937500,26858640,1,1,1],"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"]}},"id":1}
// Request // Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedTransaction","params":["2nBhEBYYvfaAe16UMNqRHre4YNSskvuYgx3M6E4JP1oDYvZEJHvoPzyUidNgNX5r9sTyN1J9UxtbCXy2rqYcuyuv", "base64"]}' localhost:8899 curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedTransaction","params":["2nBhEBYYvfaAe16UMNqRHre4YNSskvuYgx3M6E4JP1oDYvZEJHvoPzyUidNgNX5r9sTyN1J9UxtbCXy2rqYcuyuv", "base64"]}' localhost:8899
// Result // Result
{"jsonrpc":"2.0","result":{"meta":{"err":null,"fee":5000,"postBalances":[499998932500,26858640,1,1,1],"preBalances":[499998937500,26858640,1,1,1],"status":{"Ok":null}},"slot":430,"transaction":["AVj7dxHlQ9IrvdYVIjuiRFs1jLaDMHixgrv+qtHBwz51L4/ImLZhszwiyEJDIp7xeBSpm/TX5B7mYzxa+fPOMw0BAAMFJMJVqLw+hJYheizSoYlLm53KzgT82cDVmazarqQKG2GQsLgiqktA+a+FDR4/7xnDX7rsusMwryYVUdixfz1B1Qan1RcZLwqvxvJl4/t3zHragsUp0L47E24tAFUgAAAABqfVFxjHdMkoVmOYaR1etoteuKObS21cc1VbIQAAAAAHYUgdNXR0u3xNdiTr072z2DVec9EQQ/wNo1OAAAAAAAtxOUhPBp2WSjUNJEgfvy70BbxI00fZyEPvFHNfxrtEAQQEAQIDADUCAAAAAQAAAAAAAACtAQAAAAAAAAdUE18R96XTJCe+YfRfUp6WP+YKCy/72ucOL8AoBFSpAA==","base64"]},"id":1} {"jsonrpc":"2.0","result":{"meta":{"err":null,"fee":5000,"innerInstructions":[],"postBalances":[499998932500,26858640,1,1,1],"preBalances":[499998937500,26858640,1,1,1],"status":{"Ok":null}},"slot":430,"transaction":["AVj7dxHlQ9IrvdYVIjuiRFs1jLaDMHixgrv+qtHBwz51L4/ImLZhszwiyEJDIp7xeBSpm/TX5B7mYzxa+fPOMw0BAAMFJMJVqLw+hJYheizSoYlLm53KzgT82cDVmazarqQKG2GQsLgiqktA+a+FDR4/7xnDX7rsusMwryYVUdixfz1B1Qan1RcZLwqvxvJl4/t3zHragsUp0L47E24tAFUgAAAABqfVFxjHdMkoVmOYaR1etoteuKObS21cc1VbIQAAAAAHYUgdNXR0u3xNdiTr072z2DVec9EQQ/wNo1OAAAAAAAtxOUhPBp2WSjUNJEgfvy70BbxI00fZyEPvFHNfxrtEAQQEAQIDADUCAAAAAQAAAAAAAACtAQAAAAAAAAdUE18R96XTJCe+YfRfUp6WP+YKCy/72ucOL8AoBFSpAA==","base64"]},"id":1}
``` ```
### getEpochInfo ### getEpochInfo

View File

@ -3474,6 +3474,7 @@ pub mod tests {
signature::Signature, signature::Signature,
transaction::TransactionError, transaction::TransactionError,
}; };
use solana_transaction_status::InnerInstructions;
use solana_vote_program::{vote_instruction, vote_state::Vote}; use solana_vote_program::{vote_instruction, vote_state::Vote};
use std::{iter::FromIterator, time::Duration}; use std::{iter::FromIterator, time::Duration};
@ -5671,6 +5672,7 @@ pub mod tests {
fee: 42, fee: 42,
pre_balances: pre_balances.clone(), pre_balances: pre_balances.clone(),
post_balances: post_balances.clone(), post_balances: post_balances.clone(),
inner_instructions: Some(vec![]),
}, },
) )
.unwrap(); .unwrap();
@ -5683,6 +5685,7 @@ pub mod tests {
fee: 42, fee: 42,
pre_balances: pre_balances.clone(), pre_balances: pre_balances.clone(),
post_balances: post_balances.clone(), post_balances: post_balances.clone(),
inner_instructions: Some(vec![]),
}, },
) )
.unwrap(); .unwrap();
@ -5693,6 +5696,7 @@ pub mod tests {
fee: 42, fee: 42,
pre_balances, pre_balances,
post_balances, post_balances,
inner_instructions: Some(vec![]),
}), }),
} }
}) })
@ -5985,6 +5989,10 @@ pub mod tests {
let pre_balances_vec = vec![1, 2, 3]; let pre_balances_vec = vec![1, 2, 3];
let post_balances_vec = vec![3, 2, 1]; let post_balances_vec = vec![3, 2, 1];
let inner_instructions_vec = vec![InnerInstructions {
index: 0,
instructions: vec![CompiledInstruction::new(1, &(), vec![0])],
}];
// result not found // result not found
assert!(transaction_status_cf assert!(transaction_status_cf
@ -6003,6 +6011,7 @@ pub mod tests {
fee: 5u64, fee: 5u64,
pre_balances: pre_balances_vec.clone(), pre_balances: pre_balances_vec.clone(),
post_balances: post_balances_vec.clone(), post_balances: post_balances_vec.clone(),
inner_instructions: Some(inner_instructions_vec.clone()),
}, },
) )
.is_ok()); .is_ok());
@ -6013,6 +6022,7 @@ pub mod tests {
fee, fee,
pre_balances, pre_balances,
post_balances, post_balances,
inner_instructions,
} = transaction_status_cf } = transaction_status_cf
.get((0, Signature::default(), 0)) .get((0, Signature::default(), 0))
.unwrap() .unwrap()
@ -6021,6 +6031,7 @@ pub mod tests {
assert_eq!(fee, 5u64); assert_eq!(fee, 5u64);
assert_eq!(pre_balances, pre_balances_vec); assert_eq!(pre_balances, pre_balances_vec);
assert_eq!(post_balances, post_balances_vec); assert_eq!(post_balances, post_balances_vec);
assert_eq!(inner_instructions.unwrap(), inner_instructions_vec);
// insert value // insert value
assert!(transaction_status_cf assert!(transaction_status_cf
@ -6031,6 +6042,7 @@ pub mod tests {
fee: 9u64, fee: 9u64,
pre_balances: pre_balances_vec.clone(), pre_balances: pre_balances_vec.clone(),
post_balances: post_balances_vec.clone(), post_balances: post_balances_vec.clone(),
inner_instructions: Some(inner_instructions_vec.clone()),
}, },
) )
.is_ok()); .is_ok());
@ -6041,6 +6053,7 @@ pub mod tests {
fee, fee,
pre_balances, pre_balances,
post_balances, post_balances,
inner_instructions,
} = transaction_status_cf } = transaction_status_cf
.get((0, Signature::new(&[2u8; 64]), 9)) .get((0, Signature::new(&[2u8; 64]), 9))
.unwrap() .unwrap()
@ -6051,6 +6064,7 @@ pub mod tests {
assert_eq!(fee, 9u64); assert_eq!(fee, 9u64);
assert_eq!(pre_balances, pre_balances_vec); assert_eq!(pre_balances, pre_balances_vec);
assert_eq!(post_balances, post_balances_vec); assert_eq!(post_balances, post_balances_vec);
assert_eq!(inner_instructions.unwrap(), inner_instructions_vec);
} }
Blockstore::destroy(&blockstore_path).expect("Expected successful database destruction"); Blockstore::destroy(&blockstore_path).expect("Expected successful database destruction");
} }
@ -6277,6 +6291,7 @@ pub mod tests {
fee: 42u64, fee: 42u64,
pre_balances: pre_balances_vec, pre_balances: pre_balances_vec,
post_balances: post_balances_vec, post_balances: post_balances_vec,
inner_instructions: Some(vec![]),
}; };
let signature1 = Signature::new(&[1u8; 64]); let signature1 = Signature::new(&[1u8; 64]);
@ -6406,6 +6421,10 @@ pub mod tests {
pre_balances.push(i as u64 * 10); pre_balances.push(i as u64 * 10);
post_balances.push(i as u64 * 11); post_balances.push(i as u64 * 11);
} }
let inner_instructions = Some(vec![InnerInstructions {
index: 0,
instructions: vec![CompiledInstruction::new(1, &(), vec![0])],
}]);
let signature = transaction.signatures[0]; let signature = transaction.signatures[0];
blockstore blockstore
.transaction_status_cf .transaction_status_cf
@ -6416,6 +6435,7 @@ pub mod tests {
fee: 42, fee: 42,
pre_balances: pre_balances.clone(), pre_balances: pre_balances.clone(),
post_balances: post_balances.clone(), post_balances: post_balances.clone(),
inner_instructions: inner_instructions.clone(),
}, },
) )
.unwrap(); .unwrap();
@ -6426,6 +6446,7 @@ pub mod tests {
fee: 42, fee: 42,
pre_balances, pre_balances,
post_balances, post_balances,
inner_instructions,
}), }),
} }
}) })
@ -6865,6 +6886,7 @@ pub mod tests {
fee: x, fee: x,
pre_balances: vec![], pre_balances: vec![],
post_balances: vec![], post_balances: vec![],
inner_instructions: Some(vec![]),
}, },
) )
.unwrap(); .unwrap();

View File

@ -15,7 +15,10 @@ use solana_measure::{measure::Measure, thread_mem_usage};
use solana_metrics::{datapoint_error, inc_new_counter_debug}; use solana_metrics::{datapoint_error, inc_new_counter_debug};
use solana_rayon_threadlimit::get_thread_count; use solana_rayon_threadlimit::get_thread_count;
use solana_runtime::{ use solana_runtime::{
bank::{Bank, TransactionBalancesSet, TransactionProcessResult, TransactionResults}, bank::{
Bank, InnerInstructionsList, TransactionBalancesSet, TransactionProcessResult,
TransactionResults,
},
bank_forks::BankForks, bank_forks::BankForks,
bank_utils, bank_utils,
commitment::VOTE_THRESHOLD_SIZE, commitment::VOTE_THRESHOLD_SIZE,
@ -100,11 +103,13 @@ fn execute_batch(
transaction_status_sender: Option<TransactionStatusSender>, transaction_status_sender: Option<TransactionStatusSender>,
replay_vote_sender: Option<&ReplayVoteSender>, replay_vote_sender: Option<&ReplayVoteSender>,
) -> Result<()> { ) -> Result<()> {
let (tx_results, balances) = batch.bank().load_execute_and_commit_transactions( let (tx_results, balances, inner_instructions) =
batch, batch.bank().load_execute_and_commit_transactions(
MAX_PROCESSING_AGE, batch,
transaction_status_sender.is_some(), MAX_PROCESSING_AGE,
); transaction_status_sender.is_some(),
transaction_status_sender.is_some(),
);
bank_utils::find_and_send_votes(batch.transactions(), &tx_results, replay_vote_sender); bank_utils::find_and_send_votes(batch.transactions(), &tx_results, replay_vote_sender);
@ -121,6 +126,7 @@ fn execute_batch(
batch.iteration_order_vec(), batch.iteration_order_vec(),
processing_results, processing_results,
balances, balances,
inner_instructions,
sender, sender,
); );
} }
@ -1048,7 +1054,9 @@ pub struct TransactionStatusBatch {
pub iteration_order: Option<Vec<usize>>, pub iteration_order: Option<Vec<usize>>,
pub statuses: Vec<TransactionProcessResult>, pub statuses: Vec<TransactionProcessResult>,
pub balances: TransactionBalancesSet, pub balances: TransactionBalancesSet,
pub inner_instructions: Vec<Option<InnerInstructionsList>>,
} }
pub type TransactionStatusSender = Sender<TransactionStatusBatch>; pub type TransactionStatusSender = Sender<TransactionStatusBatch>;
pub fn send_transaction_status_batch( pub fn send_transaction_status_batch(
@ -1057,6 +1065,7 @@ pub fn send_transaction_status_batch(
iteration_order: Option<Vec<usize>>, iteration_order: Option<Vec<usize>>,
statuses: Vec<TransactionProcessResult>, statuses: Vec<TransactionProcessResult>,
balances: TransactionBalancesSet, balances: TransactionBalancesSet,
inner_instructions: Vec<Option<InnerInstructionsList>>,
transaction_status_sender: TransactionStatusSender, transaction_status_sender: TransactionStatusSender,
) { ) {
let slot = bank.slot(); let slot = bank.slot();
@ -1066,6 +1075,7 @@ pub fn send_transaction_status_batch(
iteration_order, iteration_order,
statuses, statuses,
balances, balances,
inner_instructions,
}) { }) {
trace!( trace!(
"Slot {} transaction_status send batch failed: {:?}", "Slot {} transaction_status send batch failed: {:?}",
@ -2913,9 +2923,13 @@ pub mod tests {
.. ..
}, },
_balances, _balances,
) = batch _inner_instructions,
.bank() ) = batch.bank().load_execute_and_commit_transactions(
.load_execute_and_commit_transactions(&batch, MAX_PROCESSING_AGE, false); &batch,
MAX_PROCESSING_AGE,
false,
false,
);
let (err, signature) = get_first_error(&batch, fee_collection_results).unwrap(); let (err, signature) = get_first_error(&batch, fee_collection_results).unwrap();
// First error found should be for the 2nd transaction, due to iteration_order // First error found should be for the 2nd transaction, due to iteration_order
assert_eq!(err.unwrap_err(), TransactionError::AccountNotFound); assert_eq!(err.unwrap_err(), TransactionError::AccountNotFound);

View File

@ -227,6 +227,7 @@ impl InvokeContext for MockInvokeContext {
fn get_executor(&mut self, _pubkey: &Pubkey) -> Option<Arc<dyn Executor>> { fn get_executor(&mut self, _pubkey: &Pubkey) -> Option<Arc<dyn Executor>> {
None None
} }
fn record_instruction(&self, _instruction: &Instruction) {}
} }
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct MockLogger { pub struct MockLogger {

View File

@ -251,9 +251,9 @@ extern uint64_t entrypoint(const uint8_t *input) {
sol_assert(SUCCESS == sol_assert(SUCCESS ==
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts))); sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
// Signer privilege escalation will always fail the whole transaction
instruction.accounts[0].is_signer = true; instruction.accounts[0].is_signer = true;
sol_assert(SUCCESS != sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts));
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
break; break;
} }
case TEST_PRIVILEGE_ESCALATION_WRITABLE: { case TEST_PRIVILEGE_ESCALATION_WRITABLE: {
@ -267,9 +267,9 @@ extern uint64_t entrypoint(const uint8_t *input) {
sol_assert(SUCCESS == sol_assert(SUCCESS ==
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts))); sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
// Writable privilege escalation will always fail the whole transaction
instruction.accounts[0].is_writable = true; instruction.accounts[0].is_writable = true;
sol_assert(SUCCESS != sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts));
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
break; break;
} }
default: default:

View File

@ -239,6 +239,7 @@ fn process_instruction(
); );
invoke(&invoked_instruction, accounts)?; invoke(&invoked_instruction, accounts)?;
// Signer privilege escalation will always fail the whole transaction
invoked_instruction.accounts[0].is_signer = true; invoked_instruction.accounts[0].is_signer = true;
assert_eq!( assert_eq!(
invoke(&invoked_instruction, accounts), invoke(&invoked_instruction, accounts),
@ -254,6 +255,7 @@ fn process_instruction(
); );
invoke(&invoked_instruction, accounts)?; invoke(&invoked_instruction, accounts)?;
// Writable privilege escalation will always fail the whole transaction
invoked_instruction.accounts[0].is_writable = true; invoked_instruction.accounts[0].is_writable = true;
assert_eq!( assert_eq!(
invoke(&invoked_instruction, accounts), invoke(&invoked_instruction, accounts),

View File

@ -18,7 +18,7 @@ use solana_sdk::{
account::{Account, KeyedAccount}, account::{Account, KeyedAccount},
bpf_loader, bpf_loader_deprecated, bpf_loader, bpf_loader_deprecated,
client::SyncClient, client::SyncClient,
clock::DEFAULT_SLOTS_PER_EPOCH, clock::{DEFAULT_SLOTS_PER_EPOCH, MAX_PROCESSING_AGE},
entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS}, entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS},
entrypoint_native::{ entrypoint_native::{
ComputeBudget, ComputeMeter, Executor, InvokeContext, Logger, ProcessInstruction, ComputeBudget, ComputeMeter, Executor, InvokeContext, Logger, ProcessInstruction,
@ -28,7 +28,7 @@ use solana_sdk::{
pubkey::Pubkey, pubkey::Pubkey,
signature::{Keypair, Signer}, signature::{Keypair, Signer},
sysvar::{clock, fees, rent, rewards, slot_hashes, stake_history}, sysvar::{clock, fees, rent, rewards, slot_hashes, stake_history},
transaction::TransactionError, transaction::{Transaction, TransactionError},
}; };
use std::{cell::RefCell, env, fs::File, io::Read, path::PathBuf, rc::Rc, sync::Arc}; use std::{cell::RefCell, env, fs::File, io::Read, path::PathBuf, rc::Rc, sync::Arc};
@ -98,6 +98,26 @@ fn run_program(
Ok(vm.get_total_instruction_count()) Ok(vm.get_total_instruction_count())
} }
fn process_transaction_and_record_inner(
bank: &Bank,
tx: Transaction,
) -> (Result<(), TransactionError>, Vec<Vec<CompiledInstruction>>) {
let signature = tx.signatures.get(0).unwrap().clone();
let txs = vec![tx];
let tx_batch = bank.prepare_batch(&txs, None);
let (mut results, _, mut inner) =
bank.load_execute_and_commit_transactions(&tx_batch, MAX_PROCESSING_AGE, false, true);
let inner_instructions = inner.swap_remove(0);
let result = results
.fee_collection_results
.swap_remove(0)
.and_then(|_| bank.get_signature_status(&signature).unwrap());
(
result,
inner_instructions.expect("cpi recording should be enabled"),
)
}
#[test] #[test]
#[cfg(any(feature = "bpf_c", feature = "bpf_rust"))] #[cfg(any(feature = "bpf_c", feature = "bpf_rust"))]
fn test_program_bpf_sanity() { fn test_program_bpf_sanity() {
@ -482,61 +502,91 @@ fn test_program_bpf_invoke() {
account_metas.clone(), account_metas.clone(),
); );
let message = Message::new(&[instruction], Some(&mint_pubkey)); let message = Message::new(&[instruction], Some(&mint_pubkey));
assert!(bank_client let tx = Transaction::new(
.send_and_confirm_message( &[
&[ &mint_keypair,
&mint_keypair, &argument_keypair,
&argument_keypair, &invoked_argument_keypair,
&invoked_argument_keypair, &from_keypair,
&from_keypair ],
], message.clone(),
message, bank.last_blockhash(),
) );
.is_ok()); let (result, inner_instructions) = process_transaction_and_record_inner(&bank, tx);
assert!(result.is_ok());
let invoked_programs: Vec<Pubkey> = inner_instructions[0]
.iter()
.map(|ix| message.account_keys[ix.program_id_index as usize].clone())
.collect();
assert_eq!(
invoked_programs,
vec![
solana_sdk::system_program::id(),
solana_sdk::system_program::id(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
invoked_program_id.clone(),
]
);
// failure cases // failure cases
let instruction = Instruction::new( let instruction = Instruction::new(
invoke_program_id, invoke_program_id,
&TEST_PRIVILEGE_ESCALATION_SIGNER, &[TEST_PRIVILEGE_ESCALATION_SIGNER, nonce1, nonce2, nonce3],
account_metas.clone(), account_metas.clone(),
); );
let message = Message::new(&[instruction], Some(&mint_pubkey)); let message = Message::new(&[instruction], Some(&mint_pubkey));
let tx = Transaction::new(
&[
&mint_keypair,
&argument_keypair,
&invoked_argument_keypair,
&from_keypair,
],
message.clone(),
bank.last_blockhash(),
);
let (result, inner_instructions) = process_transaction_and_record_inner(&bank, tx);
let invoked_programs: Vec<Pubkey> = inner_instructions[0]
.iter()
.map(|ix| message.account_keys[ix.program_id_index as usize].clone())
.collect();
assert_eq!(invoked_programs, vec![invoked_program_id.clone()]);
assert_eq!( assert_eq!(
bank_client result.unwrap_err(),
.send_and_confirm_message(
&[
&mint_keypair,
&argument_keypair,
&invoked_argument_keypair,
&from_keypair
],
message,
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::Custom(194969602)) TransactionError::InstructionError(0, InstructionError::Custom(194969602))
); );
let instruction = Instruction::new( let instruction = Instruction::new(
invoke_program_id, invoke_program_id,
&TEST_PRIVILEGE_ESCALATION_WRITABLE, &[TEST_PRIVILEGE_ESCALATION_WRITABLE, nonce1, nonce2, nonce3],
account_metas.clone(), account_metas.clone(),
); );
let message = Message::new(&[instruction], Some(&mint_pubkey)); let message = Message::new(&[instruction], Some(&mint_pubkey));
let tx = Transaction::new(
&[
&mint_keypair,
&argument_keypair,
&invoked_argument_keypair,
&from_keypair,
],
message.clone(),
bank.last_blockhash(),
);
let (result, inner_instructions) = process_transaction_and_record_inner(&bank, tx);
let invoked_programs: Vec<Pubkey> = inner_instructions[0]
.iter()
.map(|ix| message.account_keys[ix.program_id_index as usize].clone())
.collect();
assert_eq!(invoked_programs, vec![invoked_program_id.clone()]);
assert_eq!( assert_eq!(
bank_client result.unwrap_err(),
.send_and_confirm_message(
&[
&mint_keypair,
&argument_keypair,
&invoked_argument_keypair,
&from_keypair
],
message,
)
.unwrap_err()
.unwrap(),
TransactionError::InstructionError(0, InstructionError::Custom(194969602)) TransactionError::InstructionError(0, InstructionError::Custom(194969602))
); );
@ -639,6 +689,7 @@ impl InvokeContext for MockInvokeContext {
fn get_executor(&mut self, _pubkey: &Pubkey) -> Option<Arc<dyn Executor>> { fn get_executor(&mut self, _pubkey: &Pubkey) -> Option<Arc<dyn Executor>> {
None None
} }
fn record_instruction(&self, _instruction: &Instruction) {}
} }
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]

View File

@ -275,6 +275,7 @@ mod tests {
account::Account, account::Account,
entrypoint_native::{ComputeBudget, Logger, ProcessInstruction}, entrypoint_native::{ComputeBudget, Logger, ProcessInstruction},
instruction::CompiledInstruction, instruction::CompiledInstruction,
instruction::Instruction,
message::Message, message::Message,
rent::Rent, rent::Rent,
}; };
@ -360,6 +361,7 @@ mod tests {
fn get_executor(&mut self, _pubkey: &Pubkey) -> Option<Arc<dyn Executor>> { fn get_executor(&mut self, _pubkey: &Pubkey) -> Option<Arc<dyn Executor>> {
None None
} }
fn record_instruction(&self, _instruction: &Instruction) {}
} }
struct TestInstructionMeter { struct TestInstructionMeter {
@ -587,6 +589,7 @@ mod tests {
max_invoke_depth: 2, max_invoke_depth: 2,
}, },
Rc::new(RefCell::new(Executors::default())), Rc::new(RefCell::new(Executors::default())),
None,
); );
assert_eq!( assert_eq!(
Err(InstructionError::Custom(194969602)), Err(InstructionError::Custom(194969602)),

View File

@ -1020,6 +1020,7 @@ fn call<'a>(
ro_regions, ro_regions,
)?; )?;
verify_instruction(syscall, &instruction, &signers)?; verify_instruction(syscall, &instruction, &signers)?;
invoke_context.record_instruction(&instruction);
let message = Message::new(&[instruction], None); let message = Message::new(&[instruction], None);
let callee_program_id_index = message.instructions[0].program_id_index as usize; let callee_program_id_index = message.instructions[0].program_id_index as usize;
let callee_program_id = message.account_keys[callee_program_id_index]; let callee_program_id = message.account_keys[callee_program_id_index];

View File

@ -12,6 +12,7 @@ use crate::{
blockhash_queue::BlockhashQueue, blockhash_queue::BlockhashQueue,
builtins::get_builtins, builtins::get_builtins,
epoch_stakes::{EpochStakes, NodeVoteAccounts}, epoch_stakes::{EpochStakes, NodeVoteAccounts},
instruction_recorder::InstructionRecorder,
log_collector::LogCollector, log_collector::LogCollector,
message_processor::{Executors, MessageProcessor}, message_processor::{Executors, MessageProcessor},
nonce_utils, nonce_utils,
@ -46,6 +47,7 @@ use solana_sdk::{
hash::{extend_and_hash, hashv, Hash}, hash::{extend_and_hash, hashv, Hash},
incinerator, incinerator,
inflation::Inflation, inflation::Inflation,
instruction::CompiledInstruction,
message::Message, message::Message,
native_loader, native_loader,
native_token::sol_to_lamports, native_token::sol_to_lamports,
@ -299,6 +301,12 @@ impl TransactionBalancesSet {
} }
pub type TransactionBalances = Vec<Vec<u64>>; pub type TransactionBalances = Vec<Vec<u64>>;
/// An ordered list of instructions that were invoked during a transaction instruction
pub type InnerInstructions = Vec<CompiledInstruction>;
/// A list of instructions that were invoked during each instruction of a transaction
pub type InnerInstructionsList = Vec<InnerInstructions>;
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum HashAgeKind { pub enum HashAgeKind {
Extant, Extant,
@ -1535,6 +1543,7 @@ impl Bank {
let ( let (
_loaded_accounts, _loaded_accounts,
executed, executed,
_inner_instructions,
_retryable_transactions, _retryable_transactions,
_transaction_count, _transaction_count,
_signature_count, _signature_count,
@ -1542,6 +1551,7 @@ impl Bank {
&batch, &batch,
MAX_PROCESSING_AGE, MAX_PROCESSING_AGE,
Some(log_collector.clone()), Some(log_collector.clone()),
false,
); );
let transaction_result = executed[0].0.clone().map(|_| ()); let transaction_result = executed[0].0.clone().map(|_| ());
let log_messages = Rc::try_unwrap(log_collector).unwrap_or_default().into(); let log_messages = Rc::try_unwrap(log_collector).unwrap_or_default().into();
@ -1877,6 +1887,19 @@ impl Bank {
}); });
} }
fn compile_recorded_instructions(
inner_instructions: &mut Vec<Option<InnerInstructionsList>>,
instruction_recorders: Option<Vec<InstructionRecorder>>,
message: &Message,
) {
inner_instructions.push(instruction_recorders.map(|instruction_recorders| {
instruction_recorders
.into_iter()
.map(|r| r.compile_instructions(message))
.collect()
}));
}
/// Get any cached executors needed by the transaction /// Get any cached executors needed by the transaction
fn get_executors( fn get_executors(
&self, &self,
@ -1926,9 +1949,11 @@ impl Bank {
batch: &TransactionBatch, batch: &TransactionBatch,
max_age: usize, max_age: usize,
log_collector: Option<Rc<LogCollector>>, log_collector: Option<Rc<LogCollector>>,
enable_cpi_recording: bool,
) -> ( ) -> (
Vec<(Result<TransactionLoadResult>, Option<HashAgeKind>)>, Vec<(Result<TransactionLoadResult>, Option<HashAgeKind>)>,
Vec<TransactionProcessResult>, Vec<TransactionProcessResult>,
Vec<Option<InnerInstructionsList>>,
Vec<usize>, Vec<usize>,
u64, u64,
u64, u64,
@ -1969,6 +1994,8 @@ impl Bank {
let mut execution_time = Measure::start("execution_time"); let mut execution_time = Measure::start("execution_time");
let mut signature_count: u64 = 0; let mut signature_count: u64 = 0;
let mut inner_instructions: Vec<Option<InnerInstructionsList>> =
Vec::with_capacity(txs.len());
let executed: Vec<TransactionProcessResult> = loaded_accounts let executed: Vec<TransactionProcessResult> = loaded_accounts
.iter_mut() .iter_mut()
.zip(OrderedIterator::new(txs, batch.iteration_order())) .zip(OrderedIterator::new(txs, batch.iteration_order()))
@ -1982,6 +2009,11 @@ impl Bank {
let (account_refcells, loader_refcells) = let (account_refcells, loader_refcells) =
Self::accounts_to_refcells(accounts, loaders); Self::accounts_to_refcells(accounts, loaders);
let mut instruction_recorders = if enable_cpi_recording {
Some(Vec::new())
} else {
None
};
let process_result = self.message_processor.process_message( let process_result = self.message_processor.process_message(
tx.message(), tx.message(),
&loader_refcells, &loader_refcells,
@ -1989,10 +2021,17 @@ impl Bank {
&self.rent_collector, &self.rent_collector,
log_collector.clone(), log_collector.clone(),
executors.clone(), executors.clone(),
instruction_recorders.as_mut(),
self.cluster_type(), self.cluster_type(),
self.epoch(), self.epoch(),
); );
Self::compile_recorded_instructions(
&mut inner_instructions,
instruction_recorders,
&tx.message,
);
Self::refcells_to_accounts( Self::refcells_to_accounts(
accounts, accounts,
loaders, loaders,
@ -2050,6 +2089,7 @@ impl Bank {
( (
loaded_accounts, loaded_accounts,
executed, executed,
inner_instructions,
retryable_txs, retryable_txs,
tx_count, tx_count,
signature_count, signature_count,
@ -2676,14 +2716,19 @@ impl Bank {
batch: &TransactionBatch, batch: &TransactionBatch,
max_age: usize, max_age: usize,
collect_balances: bool, collect_balances: bool,
) -> (TransactionResults, TransactionBalancesSet) { enable_cpi_recording: bool,
) -> (
TransactionResults,
TransactionBalancesSet,
Vec<Option<InnerInstructionsList>>,
) {
let pre_balances = if collect_balances { let pre_balances = if collect_balances {
self.collect_balances(batch) self.collect_balances(batch)
} else { } else {
vec![] vec![]
}; };
let (mut loaded_accounts, executed, _, tx_count, signature_count) = let (mut loaded_accounts, executed, inner_instructions, _, tx_count, signature_count) =
self.load_and_execute_transactions(batch, max_age, None); self.load_and_execute_transactions(batch, max_age, None, enable_cpi_recording);
let results = self.commit_transactions( let results = self.commit_transactions(
batch.transactions(), batch.transactions(),
@ -2701,13 +2746,14 @@ impl Bank {
( (
results, results,
TransactionBalancesSet::new(pre_balances, post_balances), TransactionBalancesSet::new(pre_balances, post_balances),
inner_instructions,
) )
} }
#[must_use] #[must_use]
pub fn process_transactions(&self, txs: &[Transaction]) -> Vec<Result<()>> { pub fn process_transactions(&self, txs: &[Transaction]) -> Vec<Result<()>> {
let batch = self.prepare_batch(txs, None); let batch = self.prepare_batch(txs, None);
self.load_execute_and_commit_transactions(&batch, MAX_PROCESSING_AGE, false) self.load_execute_and_commit_transactions(&batch, MAX_PROCESSING_AGE, false, false)
.0 .0
.fee_collection_results .fee_collection_results
} }
@ -6010,7 +6056,7 @@ mod tests {
let lock_result = bank.prepare_batch(&pay_alice, None); let lock_result = bank.prepare_batch(&pay_alice, None);
let results_alice = bank let results_alice = bank
.load_execute_and_commit_transactions(&lock_result, MAX_PROCESSING_AGE, false) .load_execute_and_commit_transactions(&lock_result, MAX_PROCESSING_AGE, false, false)
.0 .0
.fee_collection_results; .fee_collection_results;
assert_eq!(results_alice[0], Ok(())); assert_eq!(results_alice[0], Ok(()));
@ -7821,9 +7867,10 @@ mod tests {
let txs = vec![tx0, tx1, tx2]; let txs = vec![tx0, tx1, tx2];
let lock_result = bank0.prepare_batch(&txs, None); let lock_result = bank0.prepare_batch(&txs, None);
let (transaction_results, transaction_balances_set) = let (transaction_results, transaction_balances_set, inner_instructions) = bank0
bank0.load_execute_and_commit_transactions(&lock_result, MAX_PROCESSING_AGE, true); .load_execute_and_commit_transactions(&lock_result, MAX_PROCESSING_AGE, true, false);
assert!(inner_instructions[0].iter().all(|ix| ix.is_empty()));
assert_eq!(transaction_balances_set.pre_balances.len(), 3); assert_eq!(transaction_balances_set.pre_balances.len(), 3);
assert_eq!(transaction_balances_set.post_balances.len(), 3); assert_eq!(transaction_balances_set.post_balances.len(), 3);

View File

@ -0,0 +1,26 @@
use std::{cell::RefCell, rc::Rc};
use solana_sdk::{
instruction::{CompiledInstruction, Instruction},
message::Message,
};
/// Records and compiles cross-program invoked instructions
#[derive(Clone, Default)]
pub struct InstructionRecorder {
inner: Rc<RefCell<Vec<Instruction>>>,
}
impl InstructionRecorder {
pub fn compile_instructions(&self, message: &Message) -> Vec<CompiledInstruction> {
self.inner
.borrow()
.iter()
.map(|ix| message.compile_instruction(ix))
.collect()
}
pub fn record_instruction(&self, instruction: Instruction) {
self.inner.borrow_mut().push(instruction);
}
}

View File

@ -14,6 +14,7 @@ pub mod commitment;
pub mod epoch_stakes; pub mod epoch_stakes;
pub mod genesis_utils; pub mod genesis_utils;
pub mod hardened_unpack; pub mod hardened_unpack;
pub mod instruction_recorder;
pub mod loader_utils; pub mod loader_utils;
pub mod log_collector; pub mod log_collector;
pub mod message_processor; pub mod message_processor;

View File

@ -1,5 +1,6 @@
use crate::{ use crate::{
log_collector::LogCollector, native_loader::NativeLoader, rent_collector::RentCollector, instruction_recorder::InstructionRecorder, log_collector::LogCollector,
native_loader::NativeLoader, rent_collector::RentCollector,
}; };
use log::*; use log::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -11,7 +12,7 @@ use solana_sdk::{
Executor, InvokeContext, Logger, ProcessInstruction, ProcessInstructionWithContext, Executor, InvokeContext, Logger, ProcessInstruction, ProcessInstructionWithContext,
}, },
genesis_config::ClusterType, genesis_config::ClusterType,
instruction::{CompiledInstruction, InstructionError}, instruction::{CompiledInstruction, Instruction, InstructionError},
message::Message, message::Message,
native_loader, native_loader,
pubkey::Pubkey, pubkey::Pubkey,
@ -206,6 +207,7 @@ pub struct ThisInvokeContext {
compute_budget: ComputeBudget, compute_budget: ComputeBudget,
compute_meter: Rc<RefCell<dyn ComputeMeter>>, compute_meter: Rc<RefCell<dyn ComputeMeter>>,
executors: Rc<RefCell<Executors>>, executors: Rc<RefCell<Executors>>,
instruction_recorder: Option<InstructionRecorder>,
} }
impl ThisInvokeContext { impl ThisInvokeContext {
pub fn new( pub fn new(
@ -217,6 +219,7 @@ impl ThisInvokeContext {
is_cross_program_supported: bool, is_cross_program_supported: bool,
compute_budget: ComputeBudget, compute_budget: ComputeBudget,
executors: Rc<RefCell<Executors>>, executors: Rc<RefCell<Executors>>,
instruction_recorder: Option<InstructionRecorder>,
) -> Self { ) -> Self {
let mut program_ids = Vec::with_capacity(compute_budget.max_invoke_depth); let mut program_ids = Vec::with_capacity(compute_budget.max_invoke_depth);
program_ids.push(*program_id); program_ids.push(*program_id);
@ -232,6 +235,7 @@ impl ThisInvokeContext {
remaining: compute_budget.max_units, remaining: compute_budget.max_units,
})), })),
executors, executors,
instruction_recorder,
} }
} }
} }
@ -294,6 +298,11 @@ impl InvokeContext for ThisInvokeContext {
fn get_executor(&mut self, pubkey: &Pubkey) -> Option<Arc<dyn Executor>> { fn get_executor(&mut self, pubkey: &Pubkey) -> Option<Arc<dyn Executor>> {
self.executors.borrow().get(&pubkey) self.executors.borrow().get(&pubkey)
} }
fn record_instruction(&self, instruction: &Instruction) {
if let Some(recorder) = &self.instruction_recorder {
recorder.record_instruction(instruction.clone());
}
}
} }
pub struct ThisLogger { pub struct ThisLogger {
log_collector: Option<Rc<LogCollector>>, log_collector: Option<Rc<LogCollector>>,
@ -667,6 +676,7 @@ impl MessageProcessor {
rent_collector: &RentCollector, rent_collector: &RentCollector,
log_collector: Option<Rc<LogCollector>>, log_collector: Option<Rc<LogCollector>>,
executors: Rc<RefCell<Executors>>, executors: Rc<RefCell<Executors>>,
instruction_recorder: Option<InstructionRecorder>,
instruction_index: usize, instruction_index: usize,
cluster_type: ClusterType, cluster_type: ClusterType,
epoch: Epoch, epoch: Epoch,
@ -696,6 +706,7 @@ impl MessageProcessor {
self.is_cross_program_supported, self.is_cross_program_supported,
self.compute_budget, self.compute_budget,
executors, executors,
instruction_recorder,
); );
let keyed_accounts = let keyed_accounts =
Self::create_keyed_accounts(message, instruction, executable_accounts, accounts)?; Self::create_keyed_accounts(message, instruction, executable_accounts, accounts)?;
@ -714,6 +725,7 @@ impl MessageProcessor {
/// Process a message. /// Process a message.
/// This method calls each instruction in the message over the set of loaded Accounts /// This method calls each instruction in the message over the set of loaded Accounts
/// The accounts are committed back to the bank only if every instruction succeeds /// The accounts are committed back to the bank only if every instruction succeeds
#[allow(clippy::too_many_arguments)]
pub fn process_message( pub fn process_message(
&self, &self,
message: &Message, message: &Message,
@ -722,10 +734,16 @@ impl MessageProcessor {
rent_collector: &RentCollector, rent_collector: &RentCollector,
log_collector: Option<Rc<LogCollector>>, log_collector: Option<Rc<LogCollector>>,
executors: Rc<RefCell<Executors>>, executors: Rc<RefCell<Executors>>,
mut instruction_recorders: Option<&mut Vec<InstructionRecorder>>,
cluster_type: ClusterType, cluster_type: ClusterType,
epoch: Epoch, epoch: Epoch,
) -> Result<(), TransactionError> { ) -> Result<(), TransactionError> {
for (instruction_index, instruction) in message.instructions.iter().enumerate() { for (instruction_index, instruction) in message.instructions.iter().enumerate() {
let instruction_recorder = instruction_recorders.as_mut().map(|recorders| {
let instruction_recorder = InstructionRecorder::default();
recorders.push(instruction_recorder.clone());
instruction_recorder
});
self.execute_instruction( self.execute_instruction(
message, message,
instruction, instruction,
@ -734,6 +752,7 @@ impl MessageProcessor {
rent_collector, rent_collector,
log_collector.clone(), log_collector.clone(),
executors.clone(), executors.clone(),
instruction_recorder,
instruction_index, instruction_index,
cluster_type, cluster_type,
epoch, epoch,
@ -794,6 +813,7 @@ mod tests {
true, true,
ComputeBudget::default(), ComputeBudget::default(),
Rc::new(RefCell::new(Executors::default())), Rc::new(RefCell::new(Executors::default())),
None,
); );
// Check call depth increases and has a limit // Check call depth increases and has a limit
@ -1329,6 +1349,7 @@ mod tests {
&rent_collector, &rent_collector,
None, None,
executors.clone(), executors.clone(),
None,
ClusterType::Development, ClusterType::Development,
0, 0,
); );
@ -1352,6 +1373,7 @@ mod tests {
&rent_collector, &rent_collector,
None, None,
executors.clone(), executors.clone(),
None,
ClusterType::Development, ClusterType::Development,
0, 0,
); );
@ -1379,6 +1401,7 @@ mod tests {
&rent_collector, &rent_collector,
None, None,
executors, executors,
None,
ClusterType::Development, ClusterType::Development,
0, 0,
); );
@ -1489,6 +1512,7 @@ mod tests {
&rent_collector, &rent_collector,
None, None,
executors.clone(), executors.clone(),
None,
ClusterType::Development, ClusterType::Development,
0, 0,
); );
@ -1516,6 +1540,7 @@ mod tests {
&rent_collector, &rent_collector,
None, None,
executors.clone(), executors.clone(),
None,
ClusterType::Development, ClusterType::Development,
0, 0,
); );
@ -1540,6 +1565,7 @@ mod tests {
&rent_collector, &rent_collector,
None, None,
executors, executors,
None,
ClusterType::Development, ClusterType::Development,
0, 0,
); );
@ -1618,6 +1644,7 @@ mod tests {
true, true,
ComputeBudget::default(), ComputeBudget::default(),
Rc::new(RefCell::new(Executors::default())), Rc::new(RefCell::new(Executors::default())),
None,
); );
let metas = vec![ let metas = vec![
AccountMeta::new(owned_key, false), AccountMeta::new(owned_key, false),

View File

@ -3,8 +3,10 @@
#[cfg(RUSTC_WITH_SPECIALIZATION)] #[cfg(RUSTC_WITH_SPECIALIZATION)]
use crate::abi_example::AbiExample; use crate::abi_example::AbiExample;
use crate::{ use crate::{
account::Account, account::KeyedAccount, instruction::CompiledInstruction, account::{Account, KeyedAccount},
instruction::InstructionError, message::Message, pubkey::Pubkey, instruction::{CompiledInstruction, Instruction, InstructionError},
message::Message,
pubkey::Pubkey,
}; };
use std::{cell::RefCell, rc::Rc, sync::Arc}; use std::{cell::RefCell, rc::Rc, sync::Arc};
@ -225,6 +227,8 @@ pub trait InvokeContext {
fn add_executor(&mut self, pubkey: &Pubkey, executor: Arc<dyn Executor>); fn add_executor(&mut self, pubkey: &Pubkey, executor: Arc<dyn Executor>);
/// Get the completed loader work that can be re-used across executions /// Get the completed loader work that can be re-used across executions
fn get_executor(&mut self, pubkey: &Pubkey) -> Option<Arc<dyn Executor>>; fn get_executor(&mut self, pubkey: &Pubkey) -> Option<Arc<dyn Executor>>;
/// Record invoked instruction
fn record_instruction(&self, instruction: &Instruction);
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]

View File

@ -272,6 +272,10 @@ impl Message {
Self::new(&instructions, payer) Self::new(&instructions, payer)
} }
pub fn compile_instruction(&self, ix: &Instruction) -> CompiledInstruction {
compile_instruction(ix, &self.account_keys)
}
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
bincode::serialize(self).unwrap() bincode::serialize(self).unwrap()
} }

View File

@ -8,8 +8,9 @@ use solana_sdk::{
transaction::{Transaction, TransactionError}, transaction::{Transaction, TransactionError},
}; };
use solana_transaction_status::{ use solana_transaction_status::{
ConfirmedBlock, ConfirmedTransaction, ConfirmedTransactionStatusWithSignature, Rewards, ConfirmedBlock, ConfirmedTransaction, ConfirmedTransactionStatusWithSignature,
TransactionStatus, TransactionStatusMeta, TransactionWithStatusMeta, InnerInstructions, Rewards, TransactionStatus, TransactionStatusMeta,
TransactionWithStatusMeta,
}; };
use std::collections::HashMap; use std::collections::HashMap;
use thiserror::Error; use thiserror::Error;
@ -161,6 +162,7 @@ struct StoredConfirmedBlockTransactionStatusMeta {
fee: u64, fee: u64,
pre_balances: Vec<u64>, pre_balances: Vec<u64>,
post_balances: Vec<u64>, post_balances: Vec<u64>,
inner_instructions: Option<Vec<InnerInstructions>>,
} }
impl From<StoredConfirmedBlockTransactionStatusMeta> for TransactionStatusMeta { impl From<StoredConfirmedBlockTransactionStatusMeta> for TransactionStatusMeta {
@ -170,6 +172,7 @@ impl From<StoredConfirmedBlockTransactionStatusMeta> for TransactionStatusMeta {
fee, fee,
pre_balances, pre_balances,
post_balances, post_balances,
inner_instructions,
} = value; } = value;
let status = match &err { let status = match &err {
None => Ok(()), None => Ok(()),
@ -180,6 +183,7 @@ impl From<StoredConfirmedBlockTransactionStatusMeta> for TransactionStatusMeta {
fee, fee,
pre_balances, pre_balances,
post_balances, post_balances,
inner_instructions,
} }
} }
} }
@ -191,6 +195,7 @@ impl From<TransactionStatusMeta> for StoredConfirmedBlockTransactionStatusMeta {
fee, fee,
pre_balances, pre_balances,
post_balances, post_balances,
inner_instructions,
.. ..
} = value; } = value;
Self { Self {
@ -198,6 +203,7 @@ impl From<TransactionStatusMeta> for StoredConfirmedBlockTransactionStatusMeta {
fee, fee,
pre_balances, pre_balances,
post_balances, post_balances,
inner_instructions,
} }
} }
} }

View File

@ -15,7 +15,7 @@ use solana_sdk::{
clock::{Slot, UnixTimestamp}, clock::{Slot, UnixTimestamp},
commitment_config::CommitmentConfig, commitment_config::CommitmentConfig,
instruction::CompiledInstruction, instruction::CompiledInstruction,
message::MessageHeader, message::{Message, MessageHeader},
pubkey::Pubkey, pubkey::Pubkey,
signature::Signature, signature::Signature,
transaction::{Result, Transaction, TransactionError}, transaction::{Result, Transaction, TransactionError},
@ -36,6 +36,19 @@ pub enum UiParsedInstruction {
PartiallyDecoded(UiPartiallyDecodedInstruction), PartiallyDecoded(UiPartiallyDecodedInstruction),
} }
impl UiInstruction {
fn parse(instruction: &CompiledInstruction, message: &Message) -> Self {
let program_id = instruction.program_id(&message.account_keys);
if let Ok(parsed_instruction) = parse(program_id, instruction, &message.account_keys) {
UiInstruction::Parsed(UiParsedInstruction::Parsed(parsed_instruction))
} else {
UiInstruction::Parsed(UiParsedInstruction::PartiallyDecoded(
UiPartiallyDecodedInstruction::from(instruction, &message.account_keys),
))
}
}
}
/// A duplicate representation of a CompiledInstruction for pretty JSON serialization /// A duplicate representation of a CompiledInstruction for pretty JSON serialization
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -78,6 +91,49 @@ impl UiPartiallyDecodedInstruction {
} }
} }
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct InnerInstructions {
/// Transaction instruction index
pub index: u8,
/// List of inner instructions
pub instructions: Vec<CompiledInstruction>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UiInnerInstructions {
/// Transaction instruction index
pub index: u8,
/// List of inner instructions
pub instructions: Vec<UiInstruction>,
}
impl UiInnerInstructions {
fn parse(inner_instructions: InnerInstructions, message: &Message) -> Self {
Self {
index: inner_instructions.index,
instructions: inner_instructions
.instructions
.iter()
.map(|ix| UiInstruction::parse(ix, message))
.collect(),
}
}
}
impl From<InnerInstructions> for UiInnerInstructions {
fn from(inner_instructions: InnerInstructions) -> Self {
Self {
index: inner_instructions.index,
instructions: inner_instructions
.instructions
.iter()
.map(|ix| UiInstruction::Compiled(ix.into()))
.collect(),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct TransactionStatusMeta { pub struct TransactionStatusMeta {
@ -85,6 +141,7 @@ pub struct TransactionStatusMeta {
pub fee: u64, pub fee: u64,
pub pre_balances: Vec<u64>, pub pre_balances: Vec<u64>,
pub post_balances: Vec<u64>, pub post_balances: Vec<u64>,
pub inner_instructions: Option<Vec<InnerInstructions>>,
} }
impl Default for TransactionStatusMeta { impl Default for TransactionStatusMeta {
@ -94,6 +151,7 @@ impl Default for TransactionStatusMeta {
fee: 0, fee: 0,
pre_balances: vec![], pre_balances: vec![],
post_balances: vec![], post_balances: vec![],
inner_instructions: None,
} }
} }
} }
@ -107,6 +165,24 @@ pub struct UiTransactionStatusMeta {
pub fee: u64, pub fee: u64,
pub pre_balances: Vec<u64>, pub pre_balances: Vec<u64>,
pub post_balances: Vec<u64>, pub post_balances: Vec<u64>,
pub inner_instructions: Option<Vec<UiInnerInstructions>>,
}
impl UiTransactionStatusMeta {
fn parse(meta: TransactionStatusMeta, message: &Message) -> Self {
Self {
err: meta.status.clone().err(),
status: meta.status,
fee: meta.fee,
pre_balances: meta.pre_balances,
post_balances: meta.post_balances,
inner_instructions: meta.inner_instructions.map(|ixs| {
ixs.into_iter()
.map(|ix| UiInnerInstructions::parse(ix, message))
.collect()
}),
}
}
} }
impl From<TransactionStatusMeta> for UiTransactionStatusMeta { impl From<TransactionStatusMeta> for UiTransactionStatusMeta {
@ -117,6 +193,9 @@ impl From<TransactionStatusMeta> for UiTransactionStatusMeta {
fee: meta.fee, fee: meta.fee,
pre_balances: meta.pre_balances, pre_balances: meta.pre_balances,
post_balances: meta.post_balances, post_balances: meta.post_balances,
inner_instructions: meta
.inner_instructions
.map(|ixs| ixs.into_iter().map(|ix| ix.into()).collect()),
} }
} }
} }
@ -261,9 +340,11 @@ pub struct TransactionWithStatusMeta {
impl TransactionWithStatusMeta { impl TransactionWithStatusMeta {
fn encode(self, encoding: UiTransactionEncoding) -> EncodedTransactionWithStatusMeta { fn encode(self, encoding: UiTransactionEncoding) -> EncodedTransactionWithStatusMeta {
let message = self.transaction.message();
let meta = self.meta.map(|meta| meta.encode(encoding, message));
EncodedTransactionWithStatusMeta { EncodedTransactionWithStatusMeta {
transaction: EncodedTransaction::encode(self.transaction, encoding), transaction: EncodedTransaction::encode(self.transaction, encoding),
meta: self.meta.map(|meta| meta.into()), meta,
} }
} }
} }
@ -275,6 +356,15 @@ pub struct EncodedTransactionWithStatusMeta {
pub meta: Option<UiTransactionStatusMeta>, pub meta: Option<UiTransactionStatusMeta>,
} }
impl TransactionStatusMeta {
fn encode(self, encoding: UiTransactionEncoding, message: &Message) -> UiTransactionStatusMeta {
match encoding {
UiTransactionEncoding::JsonParsed => UiTransactionStatusMeta::parse(self, message),
_ => self.into(),
}
}
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] #[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub enum UiTransactionEncoding { pub enum UiTransactionEncoding {
@ -334,24 +424,7 @@ impl EncodedTransaction {
.instructions .instructions
.iter() .iter()
.map(|instruction| { .map(|instruction| {
let program_id = UiInstruction::parse(instruction, &transaction.message)
instruction.program_id(&transaction.message.account_keys);
if let Ok(parsed_instruction) = parse(
program_id,
instruction,
&transaction.message.account_keys,
) {
UiInstruction::Parsed(UiParsedInstruction::Parsed(
parsed_instruction,
))
} else {
UiInstruction::Parsed(UiParsedInstruction::PartiallyDecoded(
UiPartiallyDecodedInstruction::from(
instruction,
&transaction.message.account_keys,
),
))
}
}) })
.collect(), .collect(),
}) })