-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathprocessor.rs
More file actions
218 lines (197 loc) · 8.12 KB
/
processor.rs
File metadata and controls
218 lines (197 loc) · 8.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
//! A helper to initialize Solana SVM API's `TransactionBatchProcessor`.
use {
anyhow::Result,
solana_bpf_loader_program::syscalls::create_program_runtime_environment_v1,
solana_compute_budget::compute_budget::SVMTransactionExecutionBudget,
solana_program_runtime::{
execution_budget::SVMTransactionExecutionAndFeeBudgetLimits,
loaded_programs::{BlockRelation, ForkGraph, LoadProgramMetrics, ProgramCacheEntry},
},
solana_sdk::{account::ReadableAccount, clock::Slot, fee::FeeDetails, transaction},
solana_svm::{
account_loader::CheckedTransactionDetails,
transaction_processing_callback::TransactionProcessingCallback,
transaction_processor::TransactionBatchProcessor,
},
solana_svm_feature_set::SVMFeatureSet,
solana_system_program::system_processor,
std::{
num::NonZeroU32,
sync::{Arc, RwLock},
},
};
/// In order to use the `TransactionBatchProcessor`, another trait - Solana
/// Program Runtime's `ForkGraph` - must be implemented, to tell the batch
/// processor how to work across forks.
///
/// Since Contra doesn't use slots or forks, this implementation is mocked.
pub struct ContraForkGraph {}
impl ForkGraph for ContraForkGraph {
fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation {
BlockRelation::Unknown
}
}
/// This function encapsulates some initial setup required to tweak the
/// `TransactionBatchProcessor` for use within Contra.
///
/// We're simply configuring the mocked fork graph on the SVM API's program
/// cache, then adding the System program to the processor's builtins.
pub fn create_transaction_batch_processor<AccountsDB: TransactionProcessingCallback>(
accounts_db: &AccountsDB,
feature_set: &SVMFeatureSet,
compute_budget: &SVMTransactionExecutionBudget,
) -> Result<(
TransactionBatchProcessor<ContraForkGraph>,
Arc<RwLock<ContraForkGraph>>,
)> {
let processor = TransactionBatchProcessor::<ContraForkGraph>::default();
// Create and keep the fork graph alive
let fork_graph = Arc::new(RwLock::new(ContraForkGraph {}));
{
let mut cache = processor.program_cache.write().unwrap();
// Initialize the mocked fork graph with a weak reference
cache.fork_graph = Some(Arc::downgrade(&fork_graph));
// Initialize a proper cache environment.
// (Use Loader v4 program to initialize runtime v2 if desired)
cache.environments.program_runtime_v1 = Arc::new(
create_program_runtime_environment_v1(feature_set, compute_budget, false, false)
.unwrap(),
);
// List of BPF programs to load into the cache
// These should match the precompiles loaded in BOB
let bpf_programs = [
spl_token::id(),
spl_associated_token_account::id(),
spl_memo::id(),
contra_withdraw_program_client::CONTRA_WITHDRAW_PROGRAM_ID,
];
// Loop over all BPF programs and add them to the cache
for program_id in bpf_programs {
if let Some(program_account) = accounts_db.get_account_shared_data(&program_id) {
let elf_bytes = program_account.data();
let program_runtime_environment = cache.environments.program_runtime_v1.clone();
cache.assign_program(
program_id,
Arc::new(
ProgramCacheEntry::new(
&solana_sdk::bpf_loader::id(),
program_runtime_environment,
0,
0,
elf_bytes,
elf_bytes.len(),
&mut LoadProgramMetrics::default(),
)
.unwrap(),
),
);
} else {
return Err(anyhow::anyhow!("BPF program {} not found", program_id));
}
}
}
// Add the system program builtin.
processor.add_builtin(
accounts_db,
solana_system_program::id(),
"system_program",
ProgramCacheEntry::new_builtin(
0,
b"system_program".len(),
system_processor::Entrypoint::vm,
),
);
// Add the BPF Loader v2 builtin, for the SPL Token program.
processor.add_builtin(
accounts_db,
solana_sdk::bpf_loader::id(),
"solana_bpf_loader_program",
ProgramCacheEntry::new_builtin(
0,
b"solana_bpf_loader_program".len(),
solana_bpf_loader_program::Entrypoint::vm,
),
);
// Fill the sysvar cache with the accounts from the accounts DB
processor.fill_missing_sysvar_cache_entries(accounts_db);
Ok((processor, fork_graph))
}
/// This functions is also a mock. In the Agave validator, the bank pre-checks
/// transactions before providing them to the SVM API. We mock this step in
/// Contra, since we don't need to perform such pre-checks.
pub fn get_transaction_check_results(
len: usize,
) -> Vec<transaction::Result<CheckedTransactionDetails>> {
vec![
transaction::Result::Ok(CheckedTransactionDetails::new(
None,
Ok(SVMTransactionExecutionAndFeeBudgetLimits {
budget: SVMTransactionExecutionBudget::default(),
loaded_accounts_data_size_limit: NonZeroU32::new(64 * 1024 * 1024)
.expect("Failed to set loaded_accounts_bytes"),
fee_details: FeeDetails::default(),
}),
));
len
]
}
#[cfg(test)]
mod tests {
use super::*;
use solana_sdk::{account::AccountSharedData, pubkey::Pubkey};
use solana_svm::transaction_processing_callback::TransactionProcessingCallback;
use solana_svm_callback::InvokeContextCallback;
#[test]
fn test_get_transaction_check_results_constructed_with_expected_values() {
// Verify the hardcoded configuration values by constructing
// the expected details and comparing with Debug output.
// Fields are pub(crate) so we can't access them directly,
// but we can verify the construction matches our expectation.
let expected = CheckedTransactionDetails::new(
None, // no nonce
Ok(SVMTransactionExecutionAndFeeBudgetLimits {
budget: SVMTransactionExecutionBudget::default(),
loaded_accounts_data_size_limit: NonZeroU32::new(64 * 1024 * 1024).expect("64 MiB"),
fee_details: FeeDetails::default(),
}),
);
let results = get_transaction_check_results(1);
let actual = results[0].as_ref().unwrap();
assert_eq!(
actual, &expected,
"check result should use None nonce, default budget, 64 MiB limit, default fees"
);
}
/// Minimal mock that returns None for all accounts — triggers the
/// "BPF program not found" error path.
struct EmptyAccountsDB;
impl InvokeContextCallback for EmptyAccountsDB {}
impl TransactionProcessingCallback for EmptyAccountsDB {
fn get_account_shared_data(&self, _pubkey: &Pubkey) -> Option<AccountSharedData> {
None
}
fn account_matches_owners(&self, _account: &Pubkey, _owners: &[Pubkey]) -> Option<usize> {
None
}
}
#[test]
fn test_create_processor_fails_when_bpf_program_missing() {
let db = EmptyAccountsDB;
let feature_set = SVMFeatureSet::default();
let budget = SVMTransactionExecutionBudget::default();
let result = create_transaction_batch_processor(&db, &feature_set, &budget);
let err = match result {
Err(e) => e.to_string(),
Ok(_) => panic!("expected error when BPF programs are missing"),
};
// The first BPF program looked up is spl_token
assert!(
err.contains("BPF program") && err.contains("not found"),
"expected 'BPF program ... not found' error, got: {err}"
);
assert!(
err.contains(&spl_token::id().to_string()),
"error should mention the first missing program (spl_token), got: {err}"
);
}
}