Skip to content

Commit f158f86

Browse files
committed
add get_chain handler
1 parent 6e8fbf0 commit f158f86

File tree

5 files changed

+205
-38
lines changed

5 files changed

+205
-38
lines changed

binaries/cuprated/src/p2p/request_handler.rs

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ use tower::{Service, ServiceExt};
1010
use cuprate_blockchain::service::BlockchainReadHandle;
1111
use cuprate_consensus::BlockChainContextService;
1212
use cuprate_fixed_bytes::ByteArrayVec;
13+
use cuprate_helper::cast::usize_to_u64;
14+
use cuprate_helper::map::{combine_low_high_bits_to_u128, split_u128_into_low_high_bits};
1315
use cuprate_p2p::constants::MAX_BLOCK_BATCH_LEN;
1416
use cuprate_p2p_core::{client::PeerInformation, NetworkZone, ProtocolRequest, ProtocolResponse};
1517
use cuprate_types::blockchain::{BlockchainReadRequest, BlockchainResponse};
16-
use cuprate_wire::protocol::{GetObjectsRequest, GetObjectsResponse};
18+
use cuprate_wire::protocol::{ChainRequest, ChainResponse, GetObjectsRequest, GetObjectsResponse};
1719

1820
#[derive(Clone)]
1921
pub struct P2pProtocolRequestHandlerMaker {
@@ -82,7 +84,7 @@ async fn get_objects(
8284
anyhow::bail!("Peer requested more blocks than allowed.")
8385
}
8486

85-
let block_ids: Vec<[u8; 32]> = (&request.blocks).into();
87+
let block_hashes: Vec<[u8; 32]> = (&request.blocks).into();
8688
// de-allocate the backing `Bytes`.
8789
drop(request);
8890

@@ -93,7 +95,7 @@ async fn get_objects(
9395
} = blockchain_read_handle
9496
.ready()
9597
.await?
96-
.call(BlockchainReadRequest::BlockCompleteEntries(block_ids))
98+
.call(BlockchainReadRequest::BlockCompleteEntries(block_hashes))
9799
.await?
98100
else {
99101
panic!("blockchain returned wrong response!");
@@ -102,6 +104,55 @@ async fn get_objects(
102104
Ok(ProtocolResponse::GetObjects(GetObjectsResponse {
103105
blocks,
104106
missed_ids: ByteArrayVec::from(missing_hashes),
105-
current_blockchain_height,
107+
current_blockchain_height: usize_to_u64(blockchain_height),
108+
}))
109+
}
110+
111+
/// [`ProtocolRequest::GetChain`]
112+
async fn get_chain(
113+
request: ChainRequest,
114+
mut blockchain_read_handle: BlockchainReadHandle,
115+
) -> anyhow::Result<ProtocolResponse> {
116+
if request.block_ids.len() > 25_000 {
117+
anyhow::bail!("Peer sent too many block hashes in chain request.")
118+
}
119+
120+
let block_hashes: Vec<[u8; 32]> = (&request.block_ids).into();
121+
let want_pruned_data = request.prune;
122+
// de-allocate the backing `Bytes`.
123+
drop(request);
124+
125+
let BlockchainResponse::NextChainEntry {
126+
start_height,
127+
chain_height,
128+
block_ids,
129+
block_weights,
130+
cumulative_difficulty,
131+
first_block_blob,
132+
} = blockchain_read_handle
133+
.ready()
134+
.await?
135+
.call(BlockchainReadRequest::NextChainEntry(block_hashes, 10_000))
136+
.await?
137+
else {
138+
panic!("blockchain returned wrong response!");
139+
};
140+
141+
let (cumulative_difficulty_low64, cumulative_difficulty_top64) =
142+
split_u128_into_low_high_bits(cumulative_difficulty);
143+
144+
Ok(ProtocolResponse::GetChain(ChainResponse {
145+
start_height: usize_to_u64(start_height),
146+
total_height: usize_to_u64(chain_height),
147+
cumulative_difficulty_low64,
148+
cumulative_difficulty_top64,
149+
m_block_ids: ByteArrayVec::from(block_ids),
150+
first_block: Default::default(),
151+
// only needed when
152+
m_block_weights: if want_pruned_data {
153+
block_weights.into_iter().map(usize_to_u64).collect()
154+
} else {
155+
vec![]
156+
},
106157
}))
107158
}

storage/blockchain/src/ops/block.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
//! Block functions.
22
3-
use std::slice;
43
//---------------------------------------------------------------------------------------------------- Import
54
use bytemuck::TransparentWrapper;
6-
use bytes::{Bytes, BytesMut};
5+
use bytes::Bytes;
76
use monero_serai::{
87
block::{Block, BlockHeader},
98
transaction::Transaction,
109
};
1110

1211
use cuprate_database::{
13-
RuntimeError, StorableVec, {DatabaseRo, DatabaseRw},
12+
RuntimeError, StorableVec, {DatabaseIter, DatabaseRo, DatabaseRw},
1413
};
1514
use cuprate_helper::cast::usize_to_u64;
1615
use cuprate_helper::{
@@ -22,7 +21,6 @@ use cuprate_types::{
2221
TransactionBlobs, VerifiedBlockInformation, VerifiedTransactionInformation,
2322
};
2423

25-
use crate::tables::TablesIter;
2624
use crate::{
2725
ops::{
2826
alt_block,
@@ -31,7 +29,7 @@ use crate::{
3129
output::get_rct_num_outputs,
3230
tx::{add_tx, remove_tx},
3331
},
34-
tables::{BlockHeights, BlockInfos, Tables, TablesMut},
32+
tables::{BlockHeights, BlockInfos, Tables, TablesIter, TablesMut},
3533
types::{BlockHash, BlockHeight, BlockInfo},
3634
};
3735

@@ -235,14 +233,14 @@ pub fn get_block_blob_with_tx_indexes(
235233
) -> Result<(Vec<u8>, u64, usize), RuntimeError> {
236234
use monero_serai::io::write_varint;
237235

238-
let block_info = tables.block_infos().get(&block_height)?;
236+
let block_info = tables.block_infos().get(block_height)?;
239237

240238
let miner_tx_idx = block_info.mining_tx_index;
241-
let mut block_txs = tables.block_txs_hashes().get(&block_height)?.0;
239+
let block_txs = tables.block_txs_hashes().get(block_height)?.0;
242240
let numb_txs = block_txs.len();
243241

244242
// Get the block header
245-
let mut block = tables.block_header_blobs().get(&block_height)?.0;
243+
let mut block = tables.block_header_blobs().get(block_height)?.0;
246244

247245
// Add the miner tx to the blob.
248246
let mut miner_tx_blob = tables.tx_blobs().get(&miner_tx_idx)?.0;
@@ -273,7 +271,7 @@ pub fn get_block_complete_entry(
273271
.tx_blobs_iter()
274272
.get_range(first_tx_idx..=usize_to_u64(numb_non_miner_txs))?
275273
.map(|tx_blob| Ok(Bytes::from(tx_blob?.0)))
276-
.collect::<Result<_, _>>()?;
274+
.collect::<Result<_, RuntimeError>>()?;
277275

278276
Ok(BlockCompleteEntry {
279277
block: Bytes::from(block_blob),

storage/blockchain/src/ops/blockchain.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
//---------------------------------------------------------------------------------------------------- Import
44
use cuprate_database::{DatabaseRo, RuntimeError};
55

6+
use crate::ops::block::block_exists;
7+
use crate::types::BlockHash;
68
use crate::{
79
ops::macros::doc_error,
810
tables::{BlockHeights, BlockInfos},
@@ -78,6 +80,45 @@ pub fn cumulative_generated_coins(
7880
}
7981
}
8082

83+
/// Find the split point between our chain and a list of [`BlockHash`]s from another chain.
84+
///
85+
/// This function can be used accepts chains in chronological and reverse chronological order, however
86+
/// if the wrong order is specified the return value is meaningless.
87+
///
88+
/// For chronologically ordered chains this will return the index of the first unknown, for reverse
89+
/// chronologically ordered chains this will return the index of the fist known.
90+
///
91+
/// If all blocks are known for chronologically ordered chains or unknown for reverse chronologically
92+
/// ordered chains then the length of the chain will be returned.
93+
#[doc = doc_error!()]
94+
#[inline]
95+
pub fn find_split_point(
96+
block_ids: &[BlockHash],
97+
chronological_order: bool,
98+
table_block_heights: &impl DatabaseRo<BlockHeights>,
99+
) -> Result<usize, RuntimeError> {
100+
let mut err = None;
101+
102+
// Do a binary search to find the first unknown block in the batch.
103+
let idx =
104+
block_ids.partition_point(
105+
|block_id| match block_exists(block_id, table_block_heights) {
106+
Ok(exists) => exists & chronological_order,
107+
Err(e) => {
108+
err.get_or_insert(e);
109+
// if this happens the search is scrapped, just return `false` back.
110+
false
111+
}
112+
},
113+
);
114+
115+
if let Some(e) = err {
116+
return Err(e);
117+
}
118+
119+
Ok(idx)
120+
}
121+
81122
//---------------------------------------------------------------------------------------------------- Tests
82123
#[cfg(test)]
83124
mod test {

storage/blockchain/src/service/read.rs

Lines changed: 80 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,45 @@
22
33
//---------------------------------------------------------------------------------------------------- Import
44
use std::{
5+
cmp::min,
56
collections::{HashMap, HashSet},
67
sync::Arc,
78
};
89

910
use rayon::{
10-
iter::{IntoParallelIterator, ParallelIterator, Either},
11+
iter::{Either, IntoParallelIterator, ParallelIterator},
1112
prelude::*,
1213
ThreadPool,
1314
};
1415
use thread_local::ThreadLocal;
1516

16-
use cuprate_database::{ConcreteEnv, DatabaseRo, Env, EnvInner, RuntimeError};
17+
use cuprate_database::{ConcreteEnv, DatabaseIter, DatabaseRo, Env, EnvInner, RuntimeError};
1718
use cuprate_database_service::{init_thread_pool, DatabaseReadService, ReaderThreads};
1819
use cuprate_helper::map::combine_low_high_bits_to_u128;
1920
use cuprate_types::{
2021
blockchain::{BlockchainReadRequest, BlockchainResponse},
21-
BlockCompleteEntry, Chain, ChainId, ExtendedBlockHeader, OutputOnChain,
22+
Chain, ChainId, ExtendedBlockHeader, OutputOnChain,
2223
};
2324

24-
use crate::ops::block::get_block_complete_entry;
2525
use crate::{
2626
ops::{
2727
alt_block::{
2828
get_alt_block, get_alt_block_extended_header_from_height, get_alt_block_hash,
2929
get_alt_chain_history_ranges,
3030
},
3131
block::{
32-
block_exists, get_block_extended_header_from_height, get_block_height, get_block_info,
32+
block_exists, get_block_blob_with_tx_indexes, get_block_complete_entry,
33+
get_block_extended_header_from_height, get_block_height, get_block_info,
3334
},
34-
blockchain::{cumulative_generated_coins, top_block_height},
35+
blockchain::{cumulative_generated_coins, find_split_point, top_block_height},
3536
key_image::key_image_exists,
3637
output::id_to_output_on_chain,
3738
},
3839
service::{
3940
free::{compact_history_genesis_not_included, compact_history_index_to_height_offset},
4041
types::{BlockchainReadHandle, ResponseResult},
4142
},
42-
tables::{AltBlockHeights, BlockHeights, BlockInfos, OpenTables, Tables},
43+
tables::{AltBlockHeights, BlockHeights, BlockInfos, OpenTables, Tables, TablesIter},
4344
types::{
4445
AltBlockHeight, Amount, AmountIndex, BlockHash, BlockHeight, KeyImage, PreRctOutputId,
4546
},
@@ -107,6 +108,7 @@ fn map_request(
107108
R::NumberOutputsWithAmount(vec) => number_outputs_with_amount(env, vec),
108109
R::KeyImagesSpent(set) => key_images_spent(env, set),
109110
R::CompactChainHistory => compact_chain_history(env),
111+
R::NextChainEntry(block_hashes, amount) => next_chain_entry(env, &block_hashes, amount),
110112
R::FindFirstUnknown(block_ids) => find_first_unknown(env, &block_ids),
111113
R::AltBlocksInChain(chain_id) => alt_blocks_in_chain(env, chain_id),
112114
}
@@ -552,6 +554,76 @@ fn compact_chain_history(env: &ConcreteEnv) -> ResponseResult {
552554
})
553555
}
554556

557+
/// [`BlockchainReadRequest::NextChainEntry`]
558+
///
559+
/// # Invariant
560+
/// `block_ids` must be sorted in reverse chronological block order, or else
561+
/// the returned result is unspecified and meaningless, as this function
562+
/// performs a binary search.
563+
fn next_chain_entry(
564+
env: &ConcreteEnv,
565+
block_ids: &[BlockHash],
566+
next_entry_size: usize,
567+
) -> ResponseResult {
568+
// Single-threaded, no `ThreadLocal` required.
569+
let env_inner = env.env_inner();
570+
let tx_ro = env_inner.tx_ro()?;
571+
572+
let tables = env_inner.open_tables(&tx_ro)?;
573+
let table_block_heights = tables.block_heights();
574+
let table_block_infos = tables.block_infos_iter();
575+
576+
let idx = find_split_point(block_ids, false, table_block_heights)?;
577+
578+
// This will happen if we have a different genesis block.
579+
if idx == block_ids.len() {
580+
return Ok(BlockchainResponse::NextChainEntry {
581+
start_height: 0,
582+
chain_height: 0,
583+
block_ids: vec![],
584+
block_weights: vec![],
585+
cumulative_difficulty: 0,
586+
first_block_blob: None,
587+
});
588+
}
589+
590+
// The returned chain entry must overlap with one of the blocks we were told about.
591+
let first_known_block_hash = block_ids[idx];
592+
let first_known_height = table_block_heights.get(&first_known_block_hash)?;
593+
594+
let chain_height = crate::ops::blockchain::chain_height(table_block_heights)?;
595+
let last_height_in_chain_entry = min(first_known_height + next_entry_size, chain_height);
596+
597+
let (block_ids, block_weights) = table_block_infos
598+
.get_range(first_known_height..last_height_in_chain_entry)?
599+
.map(|block_info| {
600+
let block_info = block_info?;
601+
602+
Ok((block_info.block_hash, block_info.weight))
603+
})
604+
.collect::<Result<(Vec<_>, Vec<_>), RuntimeError>>()?;
605+
606+
let top_block_info = table_block_infos.get(&(chain_height - 1))?;
607+
608+
let first_block_blob = if block_ids.len() >= 2 {
609+
Some(get_block_blob_with_tx_indexes(&(first_known_height + 1), &tables)?.0)
610+
} else {
611+
None
612+
};
613+
614+
Ok(BlockchainResponse::NextChainEntry {
615+
start_height: first_known_height,
616+
chain_height,
617+
block_ids,
618+
block_weights,
619+
cumulative_difficulty: combine_low_high_bits_to_u128(
620+
top_block_info.cumulative_difficulty_low,
621+
top_block_info.cumulative_difficulty_high,
622+
),
623+
first_block_blob,
624+
})
625+
}
626+
555627
/// [`BlockchainReadRequest::FindFirstUnknown`]
556628
///
557629
/// # Invariant
@@ -564,24 +636,7 @@ fn find_first_unknown(env: &ConcreteEnv, block_ids: &[BlockHash]) -> ResponseRes
564636

565637
let table_block_heights = env_inner.open_db_ro::<BlockHeights>(&tx_ro)?;
566638

567-
let mut err = None;
568-
569-
// Do a binary search to find the first unknown block in the batch.
570-
let idx =
571-
block_ids.partition_point(
572-
|block_id| match block_exists(block_id, &table_block_heights) {
573-
Ok(exists) => exists,
574-
Err(e) => {
575-
err.get_or_insert(e);
576-
// if this happens the search is scrapped, just return `false` back.
577-
false
578-
}
579-
},
580-
);
581-
582-
if let Some(e) = err {
583-
return Err(e);
584-
}
639+
let idx = find_split_point(block_ids, true, &table_block_heights)?;
585640

586641
Ok(if idx == block_ids.len() {
587642
BlockchainResponse::FindFirstUnknown(None)

0 commit comments

Comments
 (0)