Repository navigation
Expand file tree
/
Copy pathlib.rs
More file actions
305 lines (283 loc) · 11.6 KB
/
Copy pathlib.rs
File metadata and controls
305 lines (283 loc) · 11.6 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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
#![doc = include_str!("../README.md")]
use std::{
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
time::Instant,
};
use derive_more::Deref;
use keeper::{Keeper, builder::KeeperBuilder, error::KeeperError};
use ledger::schema::OwnedBlockstoreEntry;
use magic_root_program::entrypoint::MagicRootEntrypoint;
use nucleus::{
runtime::{self, BarrierGuard, BarrierHandle, BlockInput, SequencerHandle},
shutdown::{Service, ShutdownManager, ShutdownReason},
};
use processor::{SequencerMessage, sequencer::Sequencer};
use solana_compute_budget_program::Entrypoint as ComputeBudgetEntrypoint;
use solana_program_runtime::{
loaded_programs::{ProgramCache, ProgramCacheEntry},
solana_sbpf::program::BuiltinFunctionDefinition,
};
use solana_pubkey::Pubkey;
use solana_system_program::system_processor::Entrypoint as SystemProgramEntrypoint;
use tracing::{error, info};
mod accessor;
mod error;
pub mod pacemaker;
mod transaction;
#[cfg(feature = "testkit")]
pub mod testkit;
pub use accessor::{AccountAccessor, TransactionAccessor};
pub use error::{EngineError, ReplayError, Result};
pub use magic_root_interface::PostFinalize;
pub use transaction::{IntoTransactionView, TransactionVerifier, VerifiedTransaction};
use crate::pacemaker::{ExternalPacer, PaceMaker};
/// Top-level engine handle: owns the durable state and the sequencer submission
/// channels.
#[derive(Deref, Clone)]
pub struct Engine {
/// Durable engine state (accountsdb + ledger), shared across components.
#[deref]
state: Arc<Keeper>,
/// Submission handle into the sequencer's execution and simulation channels.
sequencer: SequencerHandle,
/// Rejects new transactions once coordinated shutdown begins.
terminating: Arc<AtomicBool>,
}
impl Engine {
/// Builds and starts the engine.
///
/// Opens and validates storage through Keeper. If recovered account state
/// trails the ledger, replays retained entries to restore account state and
/// cached results before starting live execution and block pacing.
pub async fn new(
mut builder: KeeperBuilder,
pacer: Option<ExternalPacer>,
shutdown: &mut ShutdownManager,
) -> Result<Self> {
let cache = Arc::new(ProgramCache::default());
let cpus = (num_cpus::get().saturating_sub(2)).max(2);
builder.builtins.insert(
magic_root_interface::ID,
(MagicRootEntrypoint::vm, MagicRootEntrypoint::codegen),
);
builder.builtins.insert(
solana_system_program::id(),
(
SystemProgramEntrypoint::vm,
SystemProgramEntrypoint::codegen,
),
);
builder.builtins.insert(
solana_sdk_ids::compute_budget::id(),
(
ComputeBudgetEntrypoint::vm,
ComputeBudgetEntrypoint::codegen,
),
);
for (&id, builtin) in &builder.builtins {
let entry = ProgramCacheEntry::new_builtin(*builtin);
cache.assign_program(id, entry.into());
}
let blockstore = builder.blockstore;
let rent = builder.rent.clone();
let state = Arc::new(builder.build(shutdown).await?);
Self::try_replay(&state, &cache, cpus).await?;
if pacer.is_none() {
state.reset(rent)?;
}
let (service, sequencer) = Sequencer::new(cpus / 2, state.clone(), cache, shutdown, false)?;
service.spawn()?;
let terminating = Arc::new(AtomicBool::new(false));
let engine = Self { state, sequencer, terminating };
PaceMaker::spawn(engine.clone(), pacer, blockstore, shutdown)?;
info!(authority = %engine.authority(), cpus, "engine started");
Ok(engine)
}
/// Quiesces execution and closes durable state.
///
/// Work ahead of the terminal marker drains before storage closes. Submissions
/// queued behind it are dropped when the sequencer exits, even if queueing was
/// acknowledged; their execution waiters receive channel errors.
///
/// `dump` serializes chain-mirrored state for an externally paced replica to
/// restore on its next open. Internally paced leaders clear that state once
/// during startup instead and only flush durable state here.
pub async fn shutdown(&self, dump: bool) -> Result<()> {
info!(dump, "shutting down the engine");
self.terminating.store(true, Ordering::Release);
let _guard = self.pause(SequencerMessage::Shutdown).await?;
if dump {
self.accounts().dump(None).map_err(KeeperError::from)?;
}
self.sync(true).map_err(Into::into)
}
/// Waits for exclusive account-mutation ownership. An idle accessor releases
/// it on drop; a submitted mutation owns it independently of its waiter.
/// The accessor reports presence observed after acquisition.
pub async fn account(&self, pubkey: Pubkey) -> Result<AccountAccessor<'_>> {
let lease = self.accounts().lock(pubkey).await?;
Ok(AccountAccessor {
engine: self,
lease,
expected: None,
})
}
/// Returns exclusive accessors for accounts that remain absent after locking.
///
/// A single loader scans the requested keys before any lock wait. Accounts
/// found present are promoted in the recency cache. Missing keys are
/// deduplicated and locked in pubkey order so overlapping scans cannot
/// deadlock each other. Presence is checked again under each lease;
/// callers must still avoid acquiring these keys recursively.
pub async fn missing_accounts(&self, pubkeys: &[Pubkey]) -> Result<Vec<AccountAccessor<'_>>> {
let mut missing = {
let accounts = self.accounts();
let loader = accounts.loader();
let mut missing = Vec::new();
for &pubkey in pubkeys {
if loader.contains(&pubkey).map_err(KeeperError::from)? {
accounts.touch(&pubkey);
} else {
missing.push(pubkey);
}
}
missing
};
missing.sort_unstable();
missing.dedup();
let mut accessors = Vec::with_capacity(missing.len());
for pubkey in missing {
let accessor = self.account(pubkey).await?;
if accessor.exists() {
self.accounts().touch(&pubkey);
} else {
accessors.push(accessor);
}
}
Ok(accessors)
}
/// Returns an accessor for signing and submitting transactions.
///
/// Signature verification is deferred to execution submission; simulation
/// does not verify transaction signatures.
pub fn transaction<T>(&self, transaction: T) -> Result<TransactionAccessor<'_>>
where
T: IntoTransactionView,
{
Ok(TransactionAccessor {
engine: self,
transaction: transaction.compose(self)?,
verify: true,
})
}
/// Returns an authority-bound verifier for replicated transaction batches.
pub fn verifier(&self) -> TransactionVerifier {
TransactionVerifier::new(self.authority())
}
/// Drains execution and simulation, keeping both paused until the handle is dropped.
pub async fn barrier(&self) -> Result<BarrierHandle> {
self.pause(SequencerMessage::Barrier).await
}
/// Submits a barrier-bearing message and retains its release handle after acknowledgment.
async fn pause(
&self,
message: impl FnOnce(BarrierGuard) -> SequencerMessage,
) -> Result<BarrierHandle> {
let (controller, guard) = runtime::barrier();
self.sequencer.send(message(guard)).await?;
controller.acknowledged.await?;
Ok(controller.released)
}
/// Applies one retained ledger entry through the engine's ordered paths.
///
/// Seals, checkpoints, and resets quiesce execution before touching shared state;
/// a reconstructed state whose checksum differs returns
/// [`ReplayError::StateMismatch`].
/// Entries come from trusted local storage and are never appended again.
async fn replay(&self, entry: OwnedBlockstoreEntry) -> Result<()> {
match entry {
OwnedBlockstoreEntry::Transaction(txn) => {
TransactionAccessor::replay(self, txn)?.schedule().await?;
}
OwnedBlockstoreEntry::Block(block) => {
let block = BlockInput::Replay(block);
let msg = SequencerMessage::Block { block, tx: None };
self.sequencer.send(msg).await?;
}
OwnedBlockstoreEntry::Superblock(expected) => {
let _guard = self.barrier().await?;
let previous = self.superblocks().sealed().id;
self.accounts().set_superblock(expected.id);
self.sync(false)?;
let observed = self.superblocks().sealed();
if observed != *expected {
error!(?observed, ?expected, "state mismatch; aborting replay");
self.accounts().set_superblock(previous);
self.sync(false)?;
Err(ReplayError::StateMismatch)?;
}
}
OwnedBlockstoreEntry::Reset(reset) => {
let _guard = self.barrier().await?;
self.apply_reset(&reset.payload)?;
}
OwnedBlockstoreEntry::Checkpoint(expected) => {
let _guard = self.barrier().await?;
// SAFETY: the barrier excludes account writes and metadata updates.
let observed =
unsafe { self.accounts().compute_checksum() }.map_err(KeeperError::from)?;
if observed != expected.payload.0 {
error!(observed, ?expected, "checkpoint mismatch; aborting replay");
return Err(ReplayError::StateMismatch.into());
}
}
};
Ok(())
}
/// Rebuilds state through a temporary replay sequencer when accountsdb trails
/// the retained ledger, then stops every temporary service before returning.
async fn try_replay(state: &Arc<Keeper>, cache: &Arc<ProgramCache>, cpus: usize) -> Result<()> {
let timer = Instant::now();
let Some(mut replayer) = state.replay().await? else {
return Ok(());
};
let mut shutdown = ShutdownManager::default();
let mut sh = shutdown.handle(Service::LedgerReplayer);
let (service, sequencer) =
Sequencer::new(cpus, state.clone(), cache.clone(), &mut shutdown, true)?;
service.spawn()?;
let engine = Self {
state: state.clone(),
sequencer,
terminating: Default::default(),
};
while let Some(entry) = replayer.rx.recv().await {
engine.replay(entry).await?;
}
replayer
.response
.recv_timeout()
.await
.map_err(ReplayError::from)?
.map_err(ReplayError::from)?;
drop(engine.barrier().await?);
engine.sync(false)?;
let accountsdb = state.accounts().transactions();
let ledger = state.ledger().transactions();
if accountsdb != ledger {
error!(
accountsdb,
ledger, "transaction count mismatch; aborting replay"
);
Err(ReplayError::StateMismatch)?;
}
let slot = state.blocks().latest().slot;
info!(slot, duration = ?timer.elapsed(), "ledger replay complete");
sh.terminate(ShutdownReason::Signalled);
shutdown.terminate().await;
Ok(())
}
}