1use super::{SnapBackfillSync, SnapHandoff};
4use reth_engine_tree::backfill::{BackfillAction, BackfillEvent, BackfillSync, PipelineSync};
5use reth_network_p2p::{headers::client::HeadersClient, snap::client::SnapClient};
6use reth_node_builder::{
7 sync::{BackfillContext, BackfillSyncBuilder, PipelineBackfill},
8 NodeConfig,
9};
10use reth_provider::{providers::ProviderNodeTypes, DatabaseProviderFactory, ProviderFactory};
11use std::task::{Context, Poll};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum EthereumBackfill {
16 Pipeline,
18 Snap,
20}
21
22impl EthereumBackfill {
23 pub const fn new<ChainSpec>(config: &NodeConfig<ChainSpec>) -> Self {
25 if config.network.snap_v2 {
26 Self::Snap
27 } else {
28 Self::Pipeline
29 }
30 }
31}
32
33impl<N, C> BackfillSyncBuilder<N, C> for EthereumBackfill
34where
35 N: ProviderNodeTypes,
36 C: SnapClient + HeadersClient + Clone + Unpin + 'static,
37{
38 type Backfill = EthereumBackfillSync<N, C>;
39
40 fn build(self, ctx: BackfillContext<N, C>) -> eyre::Result<Self::Backfill> {
41 ctx.provider_factory().database_provider_ro()?.ensure_sync_mode(self == Self::Snap)?;
44 match self {
45 Self::Pipeline => PipelineBackfill.build(ctx).map(EthereumBackfillSync::Pipeline),
46 Self::Snap => {
47 let client = ctx.client().clone();
48 let provider_factory = ctx.provider_factory().clone();
49 let runtime = ctx.runtime().clone();
50 Ok(EthereumBackfillSync::Snap(Box::new(SnapBackfillSync::new(
51 ctx.into_pipeline(),
52 client,
53 provider_factory,
54 runtime,
55 ))))
56 }
57 }
58 }
59
60 fn recover(&mut self, provider_factory: &ProviderFactory<N>) -> eyre::Result<()> {
61 SnapHandoff::new(provider_factory.clone()).resume_interrupted_publish()?;
62 if *self == Self::Pipeline {
64 provider_factory.database_provider_ro()?.ensure_sync_mode(false)?;
65 }
66 Ok(())
67 }
68}
69
70#[derive(Debug)]
72pub enum EthereumBackfillSync<N: ProviderNodeTypes, C> {
73 Pipeline(PipelineSync<N>),
75 Snap(Box<SnapBackfillSync<N, C>>),
77}
78
79impl<N, C> BackfillSync for EthereumBackfillSync<N, C>
80where
81 N: ProviderNodeTypes,
82 C: SnapClient + HeadersClient + Clone + Unpin + 'static,
83{
84 fn on_action(&mut self, action: BackfillAction) {
85 match self {
86 Self::Pipeline(sync) => sync.on_action(action),
87 Self::Snap(sync) => sync.on_action(action),
88 }
89 }
90
91 fn poll(&mut self, cx: &mut Context<'_>) -> Poll<BackfillEvent> {
92 match self {
93 Self::Pipeline(sync) => sync.poll(cx),
94 Self::Snap(sync) => sync.poll(cx),
95 }
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102 use crate::snap::tests::pipeline_on;
103 use alloy_primitives::B256;
104 use reth_chainspec::{ChainSpec, ChainSpecBuilder};
105 use reth_network_p2p::NoopFullBlockClient;
106 use reth_node_core::args::NetworkArgs;
107 use reth_primitives_traits::{Header, SealedHeader};
108 use reth_provider::{
109 test_utils::{
110 create_test_provider_factory_with_chain_spec, insert_headers, MockNodeTypesWithDB,
111 },
112 ChainSpecProvider, DBProvider, MetadataWriter, ProviderError, StorageSettings,
113 StorageSettingsCache,
114 };
115 use reth_snap_sync::{SnapAttemptStore, SnapGeneration};
116 use reth_stages::StageSetBuilder;
117 use reth_tasks::Runtime;
118 use std::sync::Arc;
119
120 type TestFactory = ProviderFactory<MockNodeTypesWithDB>;
121
122 fn factory(chain_spec: ChainSpec) -> TestFactory {
123 create_test_provider_factory_with_chain_spec(Arc::new(chain_spec))
124 }
125
126 fn amsterdam() -> ChainSpec {
127 ChainSpecBuilder::mainnet().amsterdam_activated().build()
128 }
129
130 fn backfill(snap_v2: bool, factory: &TestFactory) -> EthereumBackfill {
132 let config = NodeConfig::new(factory.chain_spec())
133 .with_network(NetworkArgs { snap_v2, ..Default::default() });
134 EthereumBackfill::new(&config)
135 }
136
137 fn build(
139 snap_v2: bool,
140 factory: TestFactory,
141 ) -> eyre::Result<EthereumBackfillSync<MockNodeTypesWithDB, NoopFullBlockClient>> {
142 let pipeline = pipeline_on(
143 &factory,
144 StageSetBuilder::default(),
145 tokio::sync::watch::channel(B256::ZERO).0,
146 );
147 backfill(snap_v2, &factory).build(BackfillContext::new(
148 pipeline,
149 NoopFullBlockClient::default(),
150 factory,
151 Runtime::test(),
152 ))
153 }
154
155 fn recover(snap_v2: bool, factory: &TestFactory) -> eyre::Result<()> {
157 BackfillSyncBuilder::<MockNodeTypesWithDB, NoopFullBlockClient>::recover(
158 &mut backfill(snap_v2, factory),
159 factory,
160 )
161 }
162
163 #[test]
164 fn the_staged_pipeline_runs_without_the_flag() {
165 assert!(matches!(
166 build(false, factory(amsterdam())).unwrap(),
167 EthereumBackfillSync::Pipeline(_)
168 ));
169 }
170
171 #[test]
172 fn the_flag_selects_snap_sync() {
173 let factory = factory(amsterdam());
174 recover(true, &factory).unwrap();
175
176 factory.set_storage_settings_cache(StorageSettings::v2());
178 assert!(matches!(build(true, factory).unwrap(), EthereumBackfillSync::Snap(_)));
179 }
180
181 #[test]
182 fn building_refuses_snap_into_a_legacy_layout() {
183 let factory = factory(amsterdam());
184 factory.set_storage_settings_cache(StorageSettings::v1());
186
187 let error = build(true, factory).unwrap_err();
189 assert!(matches!(
190 error.downcast_ref::<ProviderError>(),
191 Some(ProviderError::SnapStorageLayoutUnsupported)
192 ));
193 }
194
195 #[test]
196 fn recovery_refuses_unverified_snap_state_without_the_flag() {
197 let factory = factory(amsterdam());
198 factory.set_storage_settings_cache(StorageSettings::v2());
199 let provider = factory.database_provider_rw().unwrap();
200 provider.write_storage_settings(StorageSettings::v2()).unwrap();
201 provider.commit().unwrap();
202 let genesis = SealedHeader::seal_slow(Header::default());
203 insert_headers(&factory, std::slice::from_ref(&genesis));
204 let provider = factory.database_provider_rw().unwrap();
205 provider
206 .start_snap_attempt(SnapGeneration::new(genesis.num_hash(), genesis.state_root))
207 .unwrap();
208 provider.commit().unwrap();
209
210 let error = recover(false, &factory).unwrap_err();
211 assert!(matches!(
212 error.downcast_ref::<ProviderError>(),
213 Some(ProviderError::SnapStateRequiresSnapSync { .. })
214 ));
215 recover(true, &factory).unwrap();
216 }
217}