Skip to content

Commit b97bbab

Browse files
authored
Cuprated: Fix reorgs (#408)
* fix reorgs * fix off by 1 and add a test * commit file * docs * remove unneeded fn * fix tests
1 parent c5cbe51 commit b97bbab

File tree

12 files changed

+263
-18
lines changed

12 files changed

+263
-18
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

binaries/cuprated/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,5 +78,8 @@ tracing-appender = { workspace = true }
7878
tracing-subscriber = { workspace = true, features = ["std", "fmt", "default"] }
7979
tracing = { workspace = true, features = ["default"] }
8080

81+
[dev-dependencies]
82+
tempfile = { workspace = true }
83+
8184
[lints]
8285
workspace = true

binaries/cuprated/src/blockchain/manager.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ use crate::{
3333
mod commands;
3434
mod handler;
3535

36+
#[cfg(test)]
37+
mod tests;
38+
3639
pub use commands::{BlockchainManagerCommand, IncomingBlockOk};
3740

3841
/// Initialize the blockchain manager.

binaries/cuprated/src/blockchain/manager/handler.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ impl super::BlockchainManager {
396396
.await
397397
.expect(PANIC_CRITICAL_SERVICE_ERROR)
398398
.call(BlockchainWriteRequest::PopBlocks(
399-
current_main_chain_height - split_height + 1,
399+
current_main_chain_height - split_height,
400400
))
401401
.await
402402
.expect(PANIC_CRITICAL_SERVICE_ERROR)
@@ -409,7 +409,7 @@ impl super::BlockchainManager {
409409
.await
410410
.expect(PANIC_CRITICAL_SERVICE_ERROR)
411411
.call(BlockChainContextRequest::PopBlocks {
412-
numb_blocks: current_main_chain_height - split_height + 1,
412+
numb_blocks: current_main_chain_height - split_height,
413413
})
414414
.await
415415
.expect(PANIC_CRITICAL_SERVICE_ERROR);
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
use std::{collections::HashMap, env::temp_dir, path::PathBuf, sync::Arc};
2+
3+
use monero_serai::{
4+
block::{Block, BlockHeader},
5+
transaction::{Input, Output, Timelock, Transaction, TransactionPrefix},
6+
};
7+
use tokio::sync::{oneshot, watch};
8+
use tower::BoxError;
9+
10+
use cuprate_consensus_context::{BlockchainContext, ContextConfig};
11+
use cuprate_consensus_rules::{hard_forks::HFInfo, miner_tx::calculate_block_reward, HFsInfo};
12+
use cuprate_helper::network::Network;
13+
use cuprate_p2p::BroadcastSvc;
14+
15+
use crate::blockchain::{
16+
check_add_genesis, manager::BlockchainManager, manager::BlockchainManagerCommand,
17+
ConsensusBlockchainReadHandle,
18+
};
19+
20+
async fn mock_manager(data_dir: PathBuf) -> BlockchainManager {
21+
let blockchain_config = cuprate_blockchain::config::ConfigBuilder::new()
22+
.data_directory(data_dir.clone())
23+
.build();
24+
let txpool_config = cuprate_txpool::config::ConfigBuilder::new()
25+
.data_directory(data_dir)
26+
.build();
27+
28+
let (mut blockchain_read_handle, mut blockchain_write_handle, _) =
29+
cuprate_blockchain::service::init(blockchain_config).unwrap();
30+
let (txpool_read_handle, txpool_write_handle, _) =
31+
cuprate_txpool::service::init(txpool_config).unwrap();
32+
33+
check_add_genesis(
34+
&mut blockchain_read_handle,
35+
&mut blockchain_write_handle,
36+
Network::Mainnet,
37+
)
38+
.await;
39+
40+
let mut context_config = ContextConfig::main_net();
41+
context_config.difficulty_cfg.fixed_difficulty = Some(1);
42+
context_config.hard_fork_cfg.info = HFsInfo::new([HFInfo::new(0, 0); 16]);
43+
44+
let blockchain_read_handle =
45+
ConsensusBlockchainReadHandle::new(blockchain_read_handle, BoxError::from);
46+
47+
let blockchain_context_service = cuprate_consensus_context::initialize_blockchain_context(
48+
context_config,
49+
blockchain_read_handle.clone(),
50+
)
51+
.await
52+
.unwrap();
53+
54+
BlockchainManager {
55+
blockchain_write_handle,
56+
blockchain_read_handle,
57+
txpool_write_handle,
58+
blockchain_context_service,
59+
stop_current_block_downloader: Arc::new(Default::default()),
60+
broadcast_svc: BroadcastSvc::mock(),
61+
}
62+
}
63+
64+
fn generate_block(context: &BlockchainContext) -> Block {
65+
Block {
66+
header: BlockHeader {
67+
hardfork_version: 16,
68+
hardfork_signal: 16,
69+
timestamp: 1000,
70+
previous: context.top_hash,
71+
nonce: 0,
72+
},
73+
miner_transaction: Transaction::V2 {
74+
prefix: TransactionPrefix {
75+
additional_timelock: Timelock::Block(context.chain_height + 60),
76+
inputs: vec![Input::Gen(context.chain_height)],
77+
outputs: vec![Output {
78+
// we can set the block weight to 1 as the true value won't get us into the penalty zone.
79+
amount: Some(calculate_block_reward(
80+
1,
81+
context.median_weight_for_block_reward,
82+
context.already_generated_coins,
83+
context.current_hf,
84+
)),
85+
key: Default::default(),
86+
view_tag: Some(1),
87+
}],
88+
extra: rand::random::<[u8; 32]>().to_vec(),
89+
},
90+
proofs: None,
91+
},
92+
transactions: vec![],
93+
}
94+
}
95+
96+
#[tokio::test]
97+
async fn simple_reorg() {
98+
// create 2 managers
99+
let data_dir_1 = tempfile::tempdir().unwrap();
100+
let mut manager_1 = mock_manager(data_dir_1.path().to_path_buf()).await;
101+
102+
let data_dir_2 = tempfile::tempdir().unwrap();
103+
let mut manager_2 = mock_manager(data_dir_2.path().to_path_buf()).await;
104+
105+
// give both managers the same first non-genesis block
106+
let block_1 = generate_block(manager_1.blockchain_context_service.blockchain_context());
107+
108+
manager_1
109+
.handle_command(BlockchainManagerCommand::AddBlock {
110+
block: block_1.clone(),
111+
prepped_txs: HashMap::new(),
112+
response_tx: oneshot::channel().0,
113+
})
114+
.await;
115+
116+
manager_2
117+
.handle_command(BlockchainManagerCommand::AddBlock {
118+
block: block_1,
119+
prepped_txs: HashMap::new(),
120+
response_tx: oneshot::channel().0,
121+
})
122+
.await;
123+
124+
assert_eq!(
125+
manager_1.blockchain_context_service.blockchain_context(),
126+
manager_2.blockchain_context_service.blockchain_context()
127+
);
128+
129+
// give managers different 2nd block
130+
let block_2a = generate_block(manager_1.blockchain_context_service.blockchain_context());
131+
let block_2b = generate_block(manager_2.blockchain_context_service.blockchain_context());
132+
133+
manager_1
134+
.handle_command(BlockchainManagerCommand::AddBlock {
135+
block: block_2a,
136+
prepped_txs: HashMap::new(),
137+
response_tx: oneshot::channel().0,
138+
})
139+
.await;
140+
141+
manager_2
142+
.handle_command(BlockchainManagerCommand::AddBlock {
143+
block: block_2b.clone(),
144+
prepped_txs: HashMap::new(),
145+
response_tx: oneshot::channel().0,
146+
})
147+
.await;
148+
149+
let manager_1_context = manager_1
150+
.blockchain_context_service
151+
.blockchain_context()
152+
.clone();
153+
assert_ne!(
154+
&manager_1_context,
155+
manager_2.blockchain_context_service.blockchain_context()
156+
);
157+
158+
// give manager 1 missing block
159+
160+
manager_1
161+
.handle_command(BlockchainManagerCommand::AddBlock {
162+
block: block_2b,
163+
prepped_txs: HashMap::new(),
164+
response_tx: oneshot::channel().0,
165+
})
166+
.await;
167+
// make sure this didn't change the context
168+
assert_eq!(
169+
&manager_1_context,
170+
manager_1.blockchain_context_service.blockchain_context()
171+
);
172+
173+
// give both managers new block (built of manager 2's chain)
174+
let block_3 = generate_block(manager_2.blockchain_context_service.blockchain_context());
175+
176+
manager_1
177+
.handle_command(BlockchainManagerCommand::AddBlock {
178+
block: block_3.clone(),
179+
prepped_txs: HashMap::new(),
180+
response_tx: oneshot::channel().0,
181+
})
182+
.await;
183+
184+
manager_2
185+
.handle_command(BlockchainManagerCommand::AddBlock {
186+
block: block_3,
187+
prepped_txs: HashMap::new(),
188+
response_tx: oneshot::channel().0,
189+
})
190+
.await;
191+
192+
// make sure manager 1 reorged.
193+
assert_eq!(
194+
manager_1.blockchain_context_service.blockchain_context(),
195+
manager_2.blockchain_context_service.blockchain_context()
196+
);
197+
assert_eq!(
198+
manager_1
199+
.blockchain_context_service
200+
.blockchain_context()
201+
.chain_height,
202+
4
203+
);
204+
}

consensus/context/src/alt_chains.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,10 @@ impl AltChainContextCache {
5252
block_weight: usize,
5353
long_term_block_weight: usize,
5454
timestamp: u64,
55+
cumulative_difficulty: u128,
5556
) {
5657
if let Some(difficulty_cache) = &mut self.difficulty_cache {
57-
difficulty_cache.new_block(height, timestamp, difficulty_cache.cumulative_difficulty());
58+
difficulty_cache.new_block(height, timestamp, cumulative_difficulty);
5859
}
5960

6061
if let Some(weight_cache) = &mut self.weight_cache {

consensus/context/src/difficulty.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,11 @@ pub struct DifficultyCacheConfig {
3636
pub window: usize,
3737
pub cut: usize,
3838
pub lag: usize,
39+
/// If [`Some`] the difficulty cache will always return this value as the current difficulty.
40+
pub fixed_difficulty: Option<u128>,
3941
}
4042

4143
impl DifficultyCacheConfig {
42-
/// Create a new difficulty cache config.
43-
///
44-
/// # Notes
45-
/// You probably do not need this, use [`DifficultyCacheConfig::main_net`] instead.
46-
pub const fn new(window: usize, cut: usize, lag: usize) -> Self {
47-
Self { window, cut, lag }
48-
}
49-
5044
/// Returns the total amount of blocks we need to track to calculate difficulty
5145
pub const fn total_block_count(&self) -> usize {
5246
self.window + self.lag
@@ -64,6 +58,7 @@ impl DifficultyCacheConfig {
6458
window: DIFFICULTY_WINDOW,
6559
cut: DIFFICULTY_CUT,
6660
lag: DIFFICULTY_LAG,
61+
fixed_difficulty: None,
6762
}
6863
}
6964
}
@@ -297,6 +292,10 @@ fn next_difficulty(
297292
cumulative_difficulties: &VecDeque<u128>,
298293
hf: HardFork,
299294
) -> u128 {
295+
if let Some(fixed_difficulty) = config.fixed_difficulty {
296+
return fixed_difficulty;
297+
}
298+
300299
if timestamps.len() <= 1 {
301300
return 1;
302301
}

consensus/src/block/alt_block.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ where
173173
block_info.weight,
174174
block_info.long_term_weight,
175175
block_info.block.header.timestamp,
176+
cumulative_difficulty,
176177
);
177178

178179
// Add this alt cache back to the context service.

consensus/src/tests/context/difficulty.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@ const TEST_LAG: usize = 2;
1717

1818
const TEST_TOTAL_ACCOUNTED_BLOCKS: usize = TEST_WINDOW + TEST_LAG;
1919

20-
pub(crate) const TEST_DIFFICULTY_CONFIG: DifficultyCacheConfig =
21-
DifficultyCacheConfig::new(TEST_WINDOW, TEST_CUT, TEST_LAG);
20+
pub(crate) const TEST_DIFFICULTY_CONFIG: DifficultyCacheConfig = DifficultyCacheConfig {
21+
window: TEST_WINDOW,
22+
cut: TEST_CUT,
23+
lag: TEST_LAG,
24+
fixed_difficulty: None,
25+
};
2226

2327
#[tokio::test]
2428
async fn first_3_blocks_fixed_difficulty() -> Result<(), tower::BoxError> {

p2p/p2p/src/broadcast.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,13 @@ pub struct BroadcastSvc<N: NetworkZone> {
159159
tx_broadcast_channel_inbound: broadcast::Sender<BroadcastTxInfo<N>>,
160160
}
161161

162+
impl<N: NetworkZone> BroadcastSvc<N> {
163+
/// Create a mock [`BroadcastSvc`] that does nothing, useful for testing.
164+
pub fn mock() -> Self {
165+
init_broadcast_channels(BroadcastConfig::default()).0
166+
}
167+
}
168+
162169
impl<N: NetworkZone> Service<BroadcastRequest<N>> for BroadcastSvc<N> {
163170
type Response = ();
164171
type Error = std::convert::Infallible;

0 commit comments

Comments
 (0)