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:
parent
860ecdd376
commit
6601ec8f26
|
@ -531,8 +531,19 @@ impl BankingStage {
|
|||
} else {
|
||||
vec![]
|
||||
};
|
||||
let (mut loaded_accounts, results, mut retryable_txs, tx_count, signature_count) =
|
||||
bank.load_and_execute_transactions(batch, MAX_PROCESSING_AGE, None);
|
||||
let (
|
||||
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();
|
||||
|
||||
let freeze_lock = bank.freeze_lock();
|
||||
|
@ -569,6 +580,7 @@ impl BankingStage {
|
|||
batch.iteration_order_vec(),
|
||||
tx_results.processing_results,
|
||||
TransactionBalancesSet::new(pre_balances, post_balances),
|
||||
inner_instructions,
|
||||
sender,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
use crossbeam_channel::{Receiver, RecvTimeoutError};
|
||||
use itertools::izip;
|
||||
use solana_ledger::{blockstore::Blockstore, blockstore_processor::TransactionStatusBatch};
|
||||
use solana_runtime::{
|
||||
bank::{Bank, HashAgeKind},
|
||||
nonce_utils,
|
||||
transaction_utils::OrderedIterator,
|
||||
};
|
||||
use solana_transaction_status::TransactionStatusMeta;
|
||||
use solana_transaction_status::{InnerInstructions, TransactionStatusMeta};
|
||||
use std::{
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
|
@ -54,15 +55,23 @@ impl TransactionStatusService {
|
|||
iteration_order,
|
||||
statuses,
|
||||
balances,
|
||||
inner_instructions,
|
||||
} = write_transaction_status_receiver.recv_timeout(Duration::from_secs(1))?;
|
||||
|
||||
let slot = bank.slot();
|
||||
for ((((_, transaction), (status, hash_age_kind)), pre_balances), post_balances) in
|
||||
OrderedIterator::new(&transactions, iteration_order.as_deref())
|
||||
.zip(statuses)
|
||||
.zip(balances.pre_balances)
|
||||
.zip(balances.post_balances)
|
||||
{
|
||||
for (
|
||||
(_, transaction),
|
||||
(status, hash_age_kind),
|
||||
pre_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() {
|
||||
let fee_calculator = match hash_age_kind {
|
||||
Some(HashAgeKind::DurableNonce(_, account)) => {
|
||||
|
@ -77,6 +86,19 @@ impl TransactionStatusService {
|
|||
);
|
||||
let (writable_keys, readonly_keys) =
|
||||
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
|
||||
.write_transaction_status(
|
||||
slot,
|
||||
|
@ -88,6 +110,7 @@ impl TransactionStatusService {
|
|||
fee,
|
||||
pre_balances,
|
||||
post_balances,
|
||||
inner_instructions,
|
||||
},
|
||||
)
|
||||
.expect("Expect database write to succeed");
|
||||
|
|
|
@ -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
|
||||
- `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
|
||||
- `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
|
||||
- `"Ok": <null>` - Transaction was successful
|
||||
- `"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
|
||||
|
||||
// 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
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedBlock","params":[430, "base64"]}' localhost:8899
|
||||
|
||||
// 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
|
||||
|
@ -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.
|
||||
- `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
|
||||
|
||||
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
|
||||
- `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
|
||||
- `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
|
||||
- `"Ok": <null>` - Transaction was successful
|
||||
- `"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
|
||||
|
||||
// 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
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedTransaction","params":["2nBhEBYYvfaAe16UMNqRHre4YNSskvuYgx3M6E4JP1oDYvZEJHvoPzyUidNgNX5r9sTyN1J9UxtbCXy2rqYcuyuv", "base64"]}' localhost:8899
|
||||
|
||||
// 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
|
||||
|
|
|
@ -3474,6 +3474,7 @@ pub mod tests {
|
|||
signature::Signature,
|
||||
transaction::TransactionError,
|
||||
};
|
||||
use solana_transaction_status::InnerInstructions;
|
||||
use solana_vote_program::{vote_instruction, vote_state::Vote};
|
||||
use std::{iter::FromIterator, time::Duration};
|
||||
|
||||
|
@ -5671,6 +5672,7 @@ pub mod tests {
|
|||
fee: 42,
|
||||
pre_balances: pre_balances.clone(),
|
||||
post_balances: post_balances.clone(),
|
||||
inner_instructions: Some(vec![]),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -5683,6 +5685,7 @@ pub mod tests {
|
|||
fee: 42,
|
||||
pre_balances: pre_balances.clone(),
|
||||
post_balances: post_balances.clone(),
|
||||
inner_instructions: Some(vec![]),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -5693,6 +5696,7 @@ pub mod tests {
|
|||
fee: 42,
|
||||
pre_balances,
|
||||
post_balances,
|
||||
inner_instructions: Some(vec![]),
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
@ -5985,6 +5989,10 @@ pub mod tests {
|
|||
|
||||
let pre_balances_vec = vec![1, 2, 3];
|
||||
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
|
||||
assert!(transaction_status_cf
|
||||
|
@ -6003,6 +6011,7 @@ pub mod tests {
|
|||
fee: 5u64,
|
||||
pre_balances: pre_balances_vec.clone(),
|
||||
post_balances: post_balances_vec.clone(),
|
||||
inner_instructions: Some(inner_instructions_vec.clone()),
|
||||
},
|
||||
)
|
||||
.is_ok());
|
||||
|
@ -6013,6 +6022,7 @@ pub mod tests {
|
|||
fee,
|
||||
pre_balances,
|
||||
post_balances,
|
||||
inner_instructions,
|
||||
} = transaction_status_cf
|
||||
.get((0, Signature::default(), 0))
|
||||
.unwrap()
|
||||
|
@ -6021,6 +6031,7 @@ pub mod tests {
|
|||
assert_eq!(fee, 5u64);
|
||||
assert_eq!(pre_balances, pre_balances_vec);
|
||||
assert_eq!(post_balances, post_balances_vec);
|
||||
assert_eq!(inner_instructions.unwrap(), inner_instructions_vec);
|
||||
|
||||
// insert value
|
||||
assert!(transaction_status_cf
|
||||
|
@ -6031,6 +6042,7 @@ pub mod tests {
|
|||
fee: 9u64,
|
||||
pre_balances: pre_balances_vec.clone(),
|
||||
post_balances: post_balances_vec.clone(),
|
||||
inner_instructions: Some(inner_instructions_vec.clone()),
|
||||
},
|
||||
)
|
||||
.is_ok());
|
||||
|
@ -6041,6 +6053,7 @@ pub mod tests {
|
|||
fee,
|
||||
pre_balances,
|
||||
post_balances,
|
||||
inner_instructions,
|
||||
} = transaction_status_cf
|
||||
.get((0, Signature::new(&[2u8; 64]), 9))
|
||||
.unwrap()
|
||||
|
@ -6051,6 +6064,7 @@ pub mod tests {
|
|||
assert_eq!(fee, 9u64);
|
||||
assert_eq!(pre_balances, pre_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");
|
||||
}
|
||||
|
@ -6277,6 +6291,7 @@ pub mod tests {
|
|||
fee: 42u64,
|
||||
pre_balances: pre_balances_vec,
|
||||
post_balances: post_balances_vec,
|
||||
inner_instructions: Some(vec![]),
|
||||
};
|
||||
|
||||
let signature1 = Signature::new(&[1u8; 64]);
|
||||
|
@ -6406,6 +6421,10 @@ pub mod tests {
|
|||
pre_balances.push(i as u64 * 10);
|
||||
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];
|
||||
blockstore
|
||||
.transaction_status_cf
|
||||
|
@ -6416,6 +6435,7 @@ pub mod tests {
|
|||
fee: 42,
|
||||
pre_balances: pre_balances.clone(),
|
||||
post_balances: post_balances.clone(),
|
||||
inner_instructions: inner_instructions.clone(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -6426,6 +6446,7 @@ pub mod tests {
|
|||
fee: 42,
|
||||
pre_balances,
|
||||
post_balances,
|
||||
inner_instructions,
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
@ -6865,6 +6886,7 @@ pub mod tests {
|
|||
fee: x,
|
||||
pre_balances: vec![],
|
||||
post_balances: vec![],
|
||||
inner_instructions: Some(vec![]),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
|
|
@ -15,7 +15,10 @@ use solana_measure::{measure::Measure, thread_mem_usage};
|
|||
use solana_metrics::{datapoint_error, inc_new_counter_debug};
|
||||
use solana_rayon_threadlimit::get_thread_count;
|
||||
use solana_runtime::{
|
||||
bank::{Bank, TransactionBalancesSet, TransactionProcessResult, TransactionResults},
|
||||
bank::{
|
||||
Bank, InnerInstructionsList, TransactionBalancesSet, TransactionProcessResult,
|
||||
TransactionResults,
|
||||
},
|
||||
bank_forks::BankForks,
|
||||
bank_utils,
|
||||
commitment::VOTE_THRESHOLD_SIZE,
|
||||
|
@ -100,11 +103,13 @@ fn execute_batch(
|
|||
transaction_status_sender: Option<TransactionStatusSender>,
|
||||
replay_vote_sender: Option<&ReplayVoteSender>,
|
||||
) -> Result<()> {
|
||||
let (tx_results, balances) = batch.bank().load_execute_and_commit_transactions(
|
||||
batch,
|
||||
MAX_PROCESSING_AGE,
|
||||
transaction_status_sender.is_some(),
|
||||
);
|
||||
let (tx_results, balances, inner_instructions) =
|
||||
batch.bank().load_execute_and_commit_transactions(
|
||||
batch,
|
||||
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);
|
||||
|
||||
|
@ -121,6 +126,7 @@ fn execute_batch(
|
|||
batch.iteration_order_vec(),
|
||||
processing_results,
|
||||
balances,
|
||||
inner_instructions,
|
||||
sender,
|
||||
);
|
||||
}
|
||||
|
@ -1048,7 +1054,9 @@ pub struct TransactionStatusBatch {
|
|||
pub iteration_order: Option<Vec<usize>>,
|
||||
pub statuses: Vec<TransactionProcessResult>,
|
||||
pub balances: TransactionBalancesSet,
|
||||
pub inner_instructions: Vec<Option<InnerInstructionsList>>,
|
||||
}
|
||||
|
||||
pub type TransactionStatusSender = Sender<TransactionStatusBatch>;
|
||||
|
||||
pub fn send_transaction_status_batch(
|
||||
|
@ -1057,6 +1065,7 @@ pub fn send_transaction_status_batch(
|
|||
iteration_order: Option<Vec<usize>>,
|
||||
statuses: Vec<TransactionProcessResult>,
|
||||
balances: TransactionBalancesSet,
|
||||
inner_instructions: Vec<Option<InnerInstructionsList>>,
|
||||
transaction_status_sender: TransactionStatusSender,
|
||||
) {
|
||||
let slot = bank.slot();
|
||||
|
@ -1066,6 +1075,7 @@ pub fn send_transaction_status_batch(
|
|||
iteration_order,
|
||||
statuses,
|
||||
balances,
|
||||
inner_instructions,
|
||||
}) {
|
||||
trace!(
|
||||
"Slot {} transaction_status send batch failed: {:?}",
|
||||
|
@ -2913,9 +2923,13 @@ pub mod tests {
|
|||
..
|
||||
},
|
||||
_balances,
|
||||
) = batch
|
||||
.bank()
|
||||
.load_execute_and_commit_transactions(&batch, MAX_PROCESSING_AGE, false);
|
||||
_inner_instructions,
|
||||
) = batch.bank().load_execute_and_commit_transactions(
|
||||
&batch,
|
||||
MAX_PROCESSING_AGE,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
let (err, signature) = get_first_error(&batch, fee_collection_results).unwrap();
|
||||
// First error found should be for the 2nd transaction, due to iteration_order
|
||||
assert_eq!(err.unwrap_err(), TransactionError::AccountNotFound);
|
||||
|
|
|
@ -227,6 +227,7 @@ impl InvokeContext for MockInvokeContext {
|
|||
fn get_executor(&mut self, _pubkey: &Pubkey) -> Option<Arc<dyn Executor>> {
|
||||
None
|
||||
}
|
||||
fn record_instruction(&self, _instruction: &Instruction) {}
|
||||
}
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct MockLogger {
|
||||
|
|
|
@ -251,9 +251,9 @@ extern uint64_t entrypoint(const uint8_t *input) {
|
|||
sol_assert(SUCCESS ==
|
||||
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
|
||||
|
||||
// Signer privilege escalation will always fail the whole transaction
|
||||
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;
|
||||
}
|
||||
case TEST_PRIVILEGE_ESCALATION_WRITABLE: {
|
||||
|
@ -267,9 +267,9 @@ extern uint64_t entrypoint(const uint8_t *input) {
|
|||
sol_assert(SUCCESS ==
|
||||
sol_invoke(&instruction, accounts, SOL_ARRAY_SIZE(accounts)));
|
||||
|
||||
// Writable privilege escalation will always fail the whole transaction
|
||||
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;
|
||||
}
|
||||
default:
|
||||
|
|
|
@ -239,6 +239,7 @@ fn process_instruction(
|
|||
);
|
||||
invoke(&invoked_instruction, accounts)?;
|
||||
|
||||
// Signer privilege escalation will always fail the whole transaction
|
||||
invoked_instruction.accounts[0].is_signer = true;
|
||||
assert_eq!(
|
||||
invoke(&invoked_instruction, accounts),
|
||||
|
@ -254,6 +255,7 @@ fn process_instruction(
|
|||
);
|
||||
invoke(&invoked_instruction, accounts)?;
|
||||
|
||||
// Writable privilege escalation will always fail the whole transaction
|
||||
invoked_instruction.accounts[0].is_writable = true;
|
||||
assert_eq!(
|
||||
invoke(&invoked_instruction, accounts),
|
||||
|
|
|
@ -18,7 +18,7 @@ use solana_sdk::{
|
|||
account::{Account, KeyedAccount},
|
||||
bpf_loader, bpf_loader_deprecated,
|
||||
client::SyncClient,
|
||||
clock::DEFAULT_SLOTS_PER_EPOCH,
|
||||
clock::{DEFAULT_SLOTS_PER_EPOCH, MAX_PROCESSING_AGE},
|
||||
entrypoint::{MAX_PERMITTED_DATA_INCREASE, SUCCESS},
|
||||
entrypoint_native::{
|
||||
ComputeBudget, ComputeMeter, Executor, InvokeContext, Logger, ProcessInstruction,
|
||||
|
@ -28,7 +28,7 @@ use solana_sdk::{
|
|||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signer},
|
||||
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};
|
||||
|
||||
|
@ -98,6 +98,26 @@ fn run_program(
|
|||
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]
|
||||
#[cfg(any(feature = "bpf_c", feature = "bpf_rust"))]
|
||||
fn test_program_bpf_sanity() {
|
||||
|
@ -482,61 +502,91 @@ fn test_program_bpf_invoke() {
|
|||
account_metas.clone(),
|
||||
);
|
||||
let message = Message::new(&[instruction], Some(&mint_pubkey));
|
||||
assert!(bank_client
|
||||
.send_and_confirm_message(
|
||||
&[
|
||||
&mint_keypair,
|
||||
&argument_keypair,
|
||||
&invoked_argument_keypair,
|
||||
&from_keypair
|
||||
],
|
||||
message,
|
||||
)
|
||||
.is_ok());
|
||||
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);
|
||||
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
|
||||
|
||||
let instruction = Instruction::new(
|
||||
invoke_program_id,
|
||||
&TEST_PRIVILEGE_ESCALATION_SIGNER,
|
||||
&[TEST_PRIVILEGE_ESCALATION_SIGNER, nonce1, nonce2, nonce3],
|
||||
account_metas.clone(),
|
||||
);
|
||||
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!(
|
||||
bank_client
|
||||
.send_and_confirm_message(
|
||||
&[
|
||||
&mint_keypair,
|
||||
&argument_keypair,
|
||||
&invoked_argument_keypair,
|
||||
&from_keypair
|
||||
],
|
||||
message,
|
||||
)
|
||||
.unwrap_err()
|
||||
.unwrap(),
|
||||
result.unwrap_err(),
|
||||
TransactionError::InstructionError(0, InstructionError::Custom(194969602))
|
||||
);
|
||||
|
||||
let instruction = Instruction::new(
|
||||
invoke_program_id,
|
||||
&TEST_PRIVILEGE_ESCALATION_WRITABLE,
|
||||
&[TEST_PRIVILEGE_ESCALATION_WRITABLE, nonce1, nonce2, nonce3],
|
||||
account_metas.clone(),
|
||||
);
|
||||
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!(
|
||||
bank_client
|
||||
.send_and_confirm_message(
|
||||
&[
|
||||
&mint_keypair,
|
||||
&argument_keypair,
|
||||
&invoked_argument_keypair,
|
||||
&from_keypair
|
||||
],
|
||||
message,
|
||||
)
|
||||
.unwrap_err()
|
||||
.unwrap(),
|
||||
result.unwrap_err(),
|
||||
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>> {
|
||||
None
|
||||
}
|
||||
fn record_instruction(&self, _instruction: &Instruction) {}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
|
|
|
@ -275,6 +275,7 @@ mod tests {
|
|||
account::Account,
|
||||
entrypoint_native::{ComputeBudget, Logger, ProcessInstruction},
|
||||
instruction::CompiledInstruction,
|
||||
instruction::Instruction,
|
||||
message::Message,
|
||||
rent::Rent,
|
||||
};
|
||||
|
@ -360,6 +361,7 @@ mod tests {
|
|||
fn get_executor(&mut self, _pubkey: &Pubkey) -> Option<Arc<dyn Executor>> {
|
||||
None
|
||||
}
|
||||
fn record_instruction(&self, _instruction: &Instruction) {}
|
||||
}
|
||||
|
||||
struct TestInstructionMeter {
|
||||
|
@ -587,6 +589,7 @@ mod tests {
|
|||
max_invoke_depth: 2,
|
||||
},
|
||||
Rc::new(RefCell::new(Executors::default())),
|
||||
None,
|
||||
);
|
||||
assert_eq!(
|
||||
Err(InstructionError::Custom(194969602)),
|
||||
|
|
|
@ -1020,6 +1020,7 @@ fn call<'a>(
|
|||
ro_regions,
|
||||
)?;
|
||||
verify_instruction(syscall, &instruction, &signers)?;
|
||||
invoke_context.record_instruction(&instruction);
|
||||
let message = Message::new(&[instruction], None);
|
||||
let callee_program_id_index = message.instructions[0].program_id_index as usize;
|
||||
let callee_program_id = message.account_keys[callee_program_id_index];
|
||||
|
|
|
@ -12,6 +12,7 @@ use crate::{
|
|||
blockhash_queue::BlockhashQueue,
|
||||
builtins::get_builtins,
|
||||
epoch_stakes::{EpochStakes, NodeVoteAccounts},
|
||||
instruction_recorder::InstructionRecorder,
|
||||
log_collector::LogCollector,
|
||||
message_processor::{Executors, MessageProcessor},
|
||||
nonce_utils,
|
||||
|
@ -46,6 +47,7 @@ use solana_sdk::{
|
|||
hash::{extend_and_hash, hashv, Hash},
|
||||
incinerator,
|
||||
inflation::Inflation,
|
||||
instruction::CompiledInstruction,
|
||||
message::Message,
|
||||
native_loader,
|
||||
native_token::sol_to_lamports,
|
||||
|
@ -299,6 +301,12 @@ impl TransactionBalancesSet {
|
|||
}
|
||||
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)]
|
||||
pub enum HashAgeKind {
|
||||
Extant,
|
||||
|
@ -1535,6 +1543,7 @@ impl Bank {
|
|||
let (
|
||||
_loaded_accounts,
|
||||
executed,
|
||||
_inner_instructions,
|
||||
_retryable_transactions,
|
||||
_transaction_count,
|
||||
_signature_count,
|
||||
|
@ -1542,6 +1551,7 @@ impl Bank {
|
|||
&batch,
|
||||
MAX_PROCESSING_AGE,
|
||||
Some(log_collector.clone()),
|
||||
false,
|
||||
);
|
||||
let transaction_result = executed[0].0.clone().map(|_| ());
|
||||
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
|
||||
fn get_executors(
|
||||
&self,
|
||||
|
@ -1926,9 +1949,11 @@ impl Bank {
|
|||
batch: &TransactionBatch,
|
||||
max_age: usize,
|
||||
log_collector: Option<Rc<LogCollector>>,
|
||||
enable_cpi_recording: bool,
|
||||
) -> (
|
||||
Vec<(Result<TransactionLoadResult>, Option<HashAgeKind>)>,
|
||||
Vec<TransactionProcessResult>,
|
||||
Vec<Option<InnerInstructionsList>>,
|
||||
Vec<usize>,
|
||||
u64,
|
||||
u64,
|
||||
|
@ -1969,6 +1994,8 @@ impl Bank {
|
|||
|
||||
let mut execution_time = Measure::start("execution_time");
|
||||
let mut signature_count: u64 = 0;
|
||||
let mut inner_instructions: Vec<Option<InnerInstructionsList>> =
|
||||
Vec::with_capacity(txs.len());
|
||||
let executed: Vec<TransactionProcessResult> = loaded_accounts
|
||||
.iter_mut()
|
||||
.zip(OrderedIterator::new(txs, batch.iteration_order()))
|
||||
|
@ -1982,6 +2009,11 @@ impl Bank {
|
|||
let (account_refcells, loader_refcells) =
|
||||
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(
|
||||
tx.message(),
|
||||
&loader_refcells,
|
||||
|
@ -1989,10 +2021,17 @@ impl Bank {
|
|||
&self.rent_collector,
|
||||
log_collector.clone(),
|
||||
executors.clone(),
|
||||
instruction_recorders.as_mut(),
|
||||
self.cluster_type(),
|
||||
self.epoch(),
|
||||
);
|
||||
|
||||
Self::compile_recorded_instructions(
|
||||
&mut inner_instructions,
|
||||
instruction_recorders,
|
||||
&tx.message,
|
||||
);
|
||||
|
||||
Self::refcells_to_accounts(
|
||||
accounts,
|
||||
loaders,
|
||||
|
@ -2050,6 +2089,7 @@ impl Bank {
|
|||
(
|
||||
loaded_accounts,
|
||||
executed,
|
||||
inner_instructions,
|
||||
retryable_txs,
|
||||
tx_count,
|
||||
signature_count,
|
||||
|
@ -2676,14 +2716,19 @@ impl Bank {
|
|||
batch: &TransactionBatch,
|
||||
max_age: usize,
|
||||
collect_balances: bool,
|
||||
) -> (TransactionResults, TransactionBalancesSet) {
|
||||
enable_cpi_recording: bool,
|
||||
) -> (
|
||||
TransactionResults,
|
||||
TransactionBalancesSet,
|
||||
Vec<Option<InnerInstructionsList>>,
|
||||
) {
|
||||
let pre_balances = if collect_balances {
|
||||
self.collect_balances(batch)
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
let (mut loaded_accounts, executed, _, tx_count, signature_count) =
|
||||
self.load_and_execute_transactions(batch, max_age, None);
|
||||
let (mut loaded_accounts, executed, inner_instructions, _, tx_count, signature_count) =
|
||||
self.load_and_execute_transactions(batch, max_age, None, enable_cpi_recording);
|
||||
|
||||
let results = self.commit_transactions(
|
||||
batch.transactions(),
|
||||
|
@ -2701,13 +2746,14 @@ impl Bank {
|
|||
(
|
||||
results,
|
||||
TransactionBalancesSet::new(pre_balances, post_balances),
|
||||
inner_instructions,
|
||||
)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn process_transactions(&self, txs: &[Transaction]) -> Vec<Result<()>> {
|
||||
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
|
||||
.fee_collection_results
|
||||
}
|
||||
|
@ -6010,7 +6056,7 @@ mod tests {
|
|||
|
||||
let lock_result = bank.prepare_batch(&pay_alice, None);
|
||||
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
|
||||
.fee_collection_results;
|
||||
assert_eq!(results_alice[0], Ok(()));
|
||||
|
@ -7821,9 +7867,10 @@ mod tests {
|
|||
let txs = vec![tx0, tx1, tx2];
|
||||
|
||||
let lock_result = bank0.prepare_batch(&txs, None);
|
||||
let (transaction_results, transaction_balances_set) =
|
||||
bank0.load_execute_and_commit_transactions(&lock_result, MAX_PROCESSING_AGE, true);
|
||||
let (transaction_results, transaction_balances_set, inner_instructions) = bank0
|
||||
.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.post_balances.len(), 3);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ pub mod commitment;
|
|||
pub mod epoch_stakes;
|
||||
pub mod genesis_utils;
|
||||
pub mod hardened_unpack;
|
||||
pub mod instruction_recorder;
|
||||
pub mod loader_utils;
|
||||
pub mod log_collector;
|
||||
pub mod message_processor;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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 serde::{Deserialize, Serialize};
|
||||
|
@ -11,7 +12,7 @@ use solana_sdk::{
|
|||
Executor, InvokeContext, Logger, ProcessInstruction, ProcessInstructionWithContext,
|
||||
},
|
||||
genesis_config::ClusterType,
|
||||
instruction::{CompiledInstruction, InstructionError},
|
||||
instruction::{CompiledInstruction, Instruction, InstructionError},
|
||||
message::Message,
|
||||
native_loader,
|
||||
pubkey::Pubkey,
|
||||
|
@ -206,6 +207,7 @@ pub struct ThisInvokeContext {
|
|||
compute_budget: ComputeBudget,
|
||||
compute_meter: Rc<RefCell<dyn ComputeMeter>>,
|
||||
executors: Rc<RefCell<Executors>>,
|
||||
instruction_recorder: Option<InstructionRecorder>,
|
||||
}
|
||||
impl ThisInvokeContext {
|
||||
pub fn new(
|
||||
|
@ -217,6 +219,7 @@ impl ThisInvokeContext {
|
|||
is_cross_program_supported: bool,
|
||||
compute_budget: ComputeBudget,
|
||||
executors: Rc<RefCell<Executors>>,
|
||||
instruction_recorder: Option<InstructionRecorder>,
|
||||
) -> Self {
|
||||
let mut program_ids = Vec::with_capacity(compute_budget.max_invoke_depth);
|
||||
program_ids.push(*program_id);
|
||||
|
@ -232,6 +235,7 @@ impl ThisInvokeContext {
|
|||
remaining: compute_budget.max_units,
|
||||
})),
|
||||
executors,
|
||||
instruction_recorder,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -294,6 +298,11 @@ impl InvokeContext for ThisInvokeContext {
|
|||
fn get_executor(&mut self, pubkey: &Pubkey) -> Option<Arc<dyn Executor>> {
|
||||
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 {
|
||||
log_collector: Option<Rc<LogCollector>>,
|
||||
|
@ -667,6 +676,7 @@ impl MessageProcessor {
|
|||
rent_collector: &RentCollector,
|
||||
log_collector: Option<Rc<LogCollector>>,
|
||||
executors: Rc<RefCell<Executors>>,
|
||||
instruction_recorder: Option<InstructionRecorder>,
|
||||
instruction_index: usize,
|
||||
cluster_type: ClusterType,
|
||||
epoch: Epoch,
|
||||
|
@ -696,6 +706,7 @@ impl MessageProcessor {
|
|||
self.is_cross_program_supported,
|
||||
self.compute_budget,
|
||||
executors,
|
||||
instruction_recorder,
|
||||
);
|
||||
let keyed_accounts =
|
||||
Self::create_keyed_accounts(message, instruction, executable_accounts, accounts)?;
|
||||
|
@ -714,6 +725,7 @@ impl MessageProcessor {
|
|||
/// Process a message.
|
||||
/// 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
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn process_message(
|
||||
&self,
|
||||
message: &Message,
|
||||
|
@ -722,10 +734,16 @@ impl MessageProcessor {
|
|||
rent_collector: &RentCollector,
|
||||
log_collector: Option<Rc<LogCollector>>,
|
||||
executors: Rc<RefCell<Executors>>,
|
||||
mut instruction_recorders: Option<&mut Vec<InstructionRecorder>>,
|
||||
cluster_type: ClusterType,
|
||||
epoch: Epoch,
|
||||
) -> Result<(), TransactionError> {
|
||||
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(
|
||||
message,
|
||||
instruction,
|
||||
|
@ -734,6 +752,7 @@ impl MessageProcessor {
|
|||
rent_collector,
|
||||
log_collector.clone(),
|
||||
executors.clone(),
|
||||
instruction_recorder,
|
||||
instruction_index,
|
||||
cluster_type,
|
||||
epoch,
|
||||
|
@ -794,6 +813,7 @@ mod tests {
|
|||
true,
|
||||
ComputeBudget::default(),
|
||||
Rc::new(RefCell::new(Executors::default())),
|
||||
None,
|
||||
);
|
||||
|
||||
// Check call depth increases and has a limit
|
||||
|
@ -1329,6 +1349,7 @@ mod tests {
|
|||
&rent_collector,
|
||||
None,
|
||||
executors.clone(),
|
||||
None,
|
||||
ClusterType::Development,
|
||||
0,
|
||||
);
|
||||
|
@ -1352,6 +1373,7 @@ mod tests {
|
|||
&rent_collector,
|
||||
None,
|
||||
executors.clone(),
|
||||
None,
|
||||
ClusterType::Development,
|
||||
0,
|
||||
);
|
||||
|
@ -1379,6 +1401,7 @@ mod tests {
|
|||
&rent_collector,
|
||||
None,
|
||||
executors,
|
||||
None,
|
||||
ClusterType::Development,
|
||||
0,
|
||||
);
|
||||
|
@ -1489,6 +1512,7 @@ mod tests {
|
|||
&rent_collector,
|
||||
None,
|
||||
executors.clone(),
|
||||
None,
|
||||
ClusterType::Development,
|
||||
0,
|
||||
);
|
||||
|
@ -1516,6 +1540,7 @@ mod tests {
|
|||
&rent_collector,
|
||||
None,
|
||||
executors.clone(),
|
||||
None,
|
||||
ClusterType::Development,
|
||||
0,
|
||||
);
|
||||
|
@ -1540,6 +1565,7 @@ mod tests {
|
|||
&rent_collector,
|
||||
None,
|
||||
executors,
|
||||
None,
|
||||
ClusterType::Development,
|
||||
0,
|
||||
);
|
||||
|
@ -1618,6 +1644,7 @@ mod tests {
|
|||
true,
|
||||
ComputeBudget::default(),
|
||||
Rc::new(RefCell::new(Executors::default())),
|
||||
None,
|
||||
);
|
||||
let metas = vec![
|
||||
AccountMeta::new(owned_key, false),
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
#[cfg(RUSTC_WITH_SPECIALIZATION)]
|
||||
use crate::abi_example::AbiExample;
|
||||
use crate::{
|
||||
account::Account, account::KeyedAccount, instruction::CompiledInstruction,
|
||||
instruction::InstructionError, message::Message, pubkey::Pubkey,
|
||||
account::{Account, KeyedAccount},
|
||||
instruction::{CompiledInstruction, Instruction, InstructionError},
|
||||
message::Message,
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
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>);
|
||||
/// Get the completed loader work that can be re-used across executions
|
||||
fn get_executor(&mut self, pubkey: &Pubkey) -> Option<Arc<dyn Executor>>;
|
||||
/// Record invoked instruction
|
||||
fn record_instruction(&self, instruction: &Instruction);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
|
|
@ -272,6 +272,10 @@ impl Message {
|
|||
Self::new(&instructions, payer)
|
||||
}
|
||||
|
||||
pub fn compile_instruction(&self, ix: &Instruction) -> CompiledInstruction {
|
||||
compile_instruction(ix, &self.account_keys)
|
||||
}
|
||||
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
bincode::serialize(self).unwrap()
|
||||
}
|
||||
|
|
|
@ -8,8 +8,9 @@ use solana_sdk::{
|
|||
transaction::{Transaction, TransactionError},
|
||||
};
|
||||
use solana_transaction_status::{
|
||||
ConfirmedBlock, ConfirmedTransaction, ConfirmedTransactionStatusWithSignature, Rewards,
|
||||
TransactionStatus, TransactionStatusMeta, TransactionWithStatusMeta,
|
||||
ConfirmedBlock, ConfirmedTransaction, ConfirmedTransactionStatusWithSignature,
|
||||
InnerInstructions, Rewards, TransactionStatus, TransactionStatusMeta,
|
||||
TransactionWithStatusMeta,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use thiserror::Error;
|
||||
|
@ -161,6 +162,7 @@ struct StoredConfirmedBlockTransactionStatusMeta {
|
|||
fee: u64,
|
||||
pre_balances: Vec<u64>,
|
||||
post_balances: Vec<u64>,
|
||||
inner_instructions: Option<Vec<InnerInstructions>>,
|
||||
}
|
||||
|
||||
impl From<StoredConfirmedBlockTransactionStatusMeta> for TransactionStatusMeta {
|
||||
|
@ -170,6 +172,7 @@ impl From<StoredConfirmedBlockTransactionStatusMeta> for TransactionStatusMeta {
|
|||
fee,
|
||||
pre_balances,
|
||||
post_balances,
|
||||
inner_instructions,
|
||||
} = value;
|
||||
let status = match &err {
|
||||
None => Ok(()),
|
||||
|
@ -180,6 +183,7 @@ impl From<StoredConfirmedBlockTransactionStatusMeta> for TransactionStatusMeta {
|
|||
fee,
|
||||
pre_balances,
|
||||
post_balances,
|
||||
inner_instructions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -191,6 +195,7 @@ impl From<TransactionStatusMeta> for StoredConfirmedBlockTransactionStatusMeta {
|
|||
fee,
|
||||
pre_balances,
|
||||
post_balances,
|
||||
inner_instructions,
|
||||
..
|
||||
} = value;
|
||||
Self {
|
||||
|
@ -198,6 +203,7 @@ impl From<TransactionStatusMeta> for StoredConfirmedBlockTransactionStatusMeta {
|
|||
fee,
|
||||
pre_balances,
|
||||
post_balances,
|
||||
inner_instructions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ use solana_sdk::{
|
|||
clock::{Slot, UnixTimestamp},
|
||||
commitment_config::CommitmentConfig,
|
||||
instruction::CompiledInstruction,
|
||||
message::MessageHeader,
|
||||
message::{Message, MessageHeader},
|
||||
pubkey::Pubkey,
|
||||
signature::Signature,
|
||||
transaction::{Result, Transaction, TransactionError},
|
||||
|
@ -36,6 +36,19 @@ pub enum UiParsedInstruction {
|
|||
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
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[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)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TransactionStatusMeta {
|
||||
|
@ -85,6 +141,7 @@ pub struct TransactionStatusMeta {
|
|||
pub fee: u64,
|
||||
pub pre_balances: Vec<u64>,
|
||||
pub post_balances: Vec<u64>,
|
||||
pub inner_instructions: Option<Vec<InnerInstructions>>,
|
||||
}
|
||||
|
||||
impl Default for TransactionStatusMeta {
|
||||
|
@ -94,6 +151,7 @@ impl Default for TransactionStatusMeta {
|
|||
fee: 0,
|
||||
pre_balances: vec![],
|
||||
post_balances: vec![],
|
||||
inner_instructions: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -107,6 +165,24 @@ pub struct UiTransactionStatusMeta {
|
|||
pub fee: u64,
|
||||
pub pre_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 {
|
||||
|
@ -117,6 +193,9 @@ impl From<TransactionStatusMeta> for UiTransactionStatusMeta {
|
|||
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| ix.into()).collect()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -261,9 +340,11 @@ pub struct TransactionWithStatusMeta {
|
|||
|
||||
impl TransactionWithStatusMeta {
|
||||
fn encode(self, encoding: UiTransactionEncoding) -> EncodedTransactionWithStatusMeta {
|
||||
let message = self.transaction.message();
|
||||
let meta = self.meta.map(|meta| meta.encode(encoding, message));
|
||||
EncodedTransactionWithStatusMeta {
|
||||
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>,
|
||||
}
|
||||
|
||||
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)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum UiTransactionEncoding {
|
||||
|
@ -334,24 +424,7 @@ impl EncodedTransaction {
|
|||
.instructions
|
||||
.iter()
|
||||
.map(|instruction| {
|
||||
let program_id =
|
||||
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,
|
||||
),
|
||||
))
|
||||
}
|
||||
UiInstruction::parse(instruction, &transaction.message)
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue