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:
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
|
@ -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);
|
||||||
|
@ -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 {
|
||||||
|
@ -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:
|
||||||
|
@ -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),
|
||||||
|
@ -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)]
|
||||||
|
@ -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)),
|
||||||
|
@ -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];
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
26
runtime/src/instruction_recorder.rs
Normal file
26
runtime/src/instruction_recorder.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
@ -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),
|
||||||
|
@ -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)]
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user