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

* Record invoked instructions and store in transaction meta

* Enable cpi recording if transaction sender is some

* Rename invoked to innerInstructions
This commit is contained in:
Justin Starry 2020-09-24 22:36:22 +08:00 committed by GitHub
parent 860ecdd376
commit 6601ec8f26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 429 additions and 98 deletions

View File

@ -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,
);
}

View File

@ -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");

View File

@ -332,6 +332,7 @@ The result field will be an object with the following fields:
- `fee: <u64>` - fee this transaction was charged, as u64 integer
- `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

View File

@ -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();

View File

@ -15,7 +15,10 @@ use solana_measure::{measure::Measure, thread_mem_usage};
use solana_metrics::{datapoint_error, inc_new_counter_debug};
use solana_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);

View File

@ -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 {

View File

@ -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:

View File

@ -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),

View File

@ -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)]

View File

@ -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)),

View File

@ -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];

View File

@ -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);

View File

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

View File

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

View File

@ -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),

View File

@ -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)]

View File

@ -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()
}

View File

@ -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,
}
}
}

View File

@ -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(),
})