Skip to content

Commit 704ad6a

Browse files
committed
feat: support getting next and previous blocks
1 parent 62cba55 commit 704ad6a

File tree

4 files changed

+207
-72
lines changed

4 files changed

+207
-72
lines changed

common/src/queries/blocks.rs

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,35 @@ pub enum BlocksStateQuery {
88
GetLatestBlock,
99
GetLatestBlockTransactions,
1010
GetLatestBlockTransactionsCBOR,
11-
GetBlockInfo { block_key: Vec<u8> },
12-
GetNextBlocks { block_key: Vec<u8> },
13-
GetPreviousBlocks { block_key: Vec<u8> },
14-
GetBlockBySlot { slot: u64 },
15-
GetBlockByEpochSlot { epoch: u64, slot: u64 },
16-
GetBlockTransactions { block_key: Vec<u8> },
17-
GetBlockTransactionsCBOR { block_key: Vec<u8> },
18-
GetBlockInvolvedAddresses { block_key: Vec<u8> },
11+
GetBlockInfo {
12+
block_key: Vec<u8>,
13+
},
14+
GetNextBlocks {
15+
block_key: Vec<u8>,
16+
limit: u64,
17+
skip: u64,
18+
},
19+
GetPreviousBlocks {
20+
block_key: Vec<u8>,
21+
limit: u64,
22+
skip: u64,
23+
},
24+
GetBlockBySlot {
25+
slot: u64,
26+
},
27+
GetBlockByEpochSlot {
28+
epoch: u64,
29+
slot: u64,
30+
},
31+
GetBlockTransactions {
32+
block_key: Vec<u8>,
33+
},
34+
GetBlockTransactionsCBOR {
35+
block_key: Vec<u8>,
36+
},
37+
GetBlockInvolvedAddresses {
38+
block_key: Vec<u8>,
39+
},
1940
}
2041

2142
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
@@ -57,10 +78,14 @@ pub struct BlockInfo {
5778
}
5879

5980
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
60-
pub struct NextBlocks {}
81+
pub struct NextBlocks {
82+
pub blocks: Vec<BlockInfo>,
83+
}
6184

6285
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
63-
pub struct PreviousBlocks {}
86+
pub struct PreviousBlocks {
87+
pub blocks: Vec<BlockInfo>,
88+
}
6489

6590
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
6691
pub struct BlockTransactions {}

modules/chain_store/src/chain_store.rs

Lines changed: 149 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ use acropolis_common::{
44
crypto::keyhash,
55
messages::{CardanoMessage, Message, StateQuery, StateQueryResponse},
66
queries::blocks::{
7-
BlockInfo, BlocksStateQuery, BlocksStateQueryResponse, DEFAULT_BLOCKS_QUERY_TOPIC,
7+
BlockInfo, BlocksStateQuery, BlocksStateQueryResponse, NextBlocks, PreviousBlocks,
8+
DEFAULT_BLOCKS_QUERY_TOPIC,
89
},
910
};
1011
use anyhow::{bail, Result};
@@ -115,81 +116,167 @@ impl ChainStore {
115116
None => Ok(BlocksStateQueryResponse::NotFound),
116117
}
117118
}
119+
BlocksStateQuery::GetNextBlocks {
120+
block_key,
121+
limit,
122+
skip,
123+
} => {
124+
if *limit == 0 {
125+
return Ok(BlocksStateQueryResponse::NextBlocks(NextBlocks {
126+
blocks: vec![],
127+
}));
128+
}
129+
match store.get_block_by_hash(&block_key)? {
130+
Some(block) => {
131+
let number = Self::get_block_number(&block)?;
132+
let min_number = number + 1 + skip;
133+
let max_number = min_number + limit - 1;
134+
let blocks = store.get_blocks_by_number_range(min_number, max_number)?;
135+
let info = Self::to_block_info_bulk(blocks, store, false)?;
136+
Ok(BlocksStateQueryResponse::NextBlocks(NextBlocks {
137+
blocks: info,
138+
}))
139+
}
140+
None => Ok(BlocksStateQueryResponse::NotFound),
141+
}
142+
}
143+
BlocksStateQuery::GetPreviousBlocks {
144+
block_key,
145+
limit,
146+
skip,
147+
} => {
148+
if *limit == 0 {
149+
return Ok(BlocksStateQueryResponse::PreviousBlocks(PreviousBlocks {
150+
blocks: vec![],
151+
}));
152+
}
153+
match store.get_block_by_hash(&block_key)? {
154+
Some(block) => {
155+
let number = Self::get_block_number(&block)?;
156+
let Some(max_number) = number.checked_sub(1 + skip) else {
157+
return Ok(BlocksStateQueryResponse::PreviousBlocks(PreviousBlocks {
158+
blocks: vec![],
159+
}));
160+
};
161+
let min_number = max_number.saturating_sub(limit - 1);
162+
let blocks = store.get_blocks_by_number_range(min_number, max_number)?;
163+
let info = Self::to_block_info_bulk(blocks, store, false)?;
164+
Ok(BlocksStateQueryResponse::PreviousBlocks(PreviousBlocks {
165+
blocks: info,
166+
}))
167+
}
168+
None => Ok(BlocksStateQueryResponse::NotFound),
169+
}
170+
}
171+
118172
other => bail!("{other:?} not yet supported"),
119173
}
120174
}
121175

176+
fn get_block_number(block: &Block) -> Result<u64> {
177+
Ok(pallas_traverse::MultiEraBlock::decode(&block.bytes)?.number())
178+
}
179+
122180
fn to_block_info(block: Block, store: &Arc<dyn Store>, is_latest: bool) -> Result<BlockInfo> {
123-
let decoded = pallas_traverse::MultiEraBlock::decode(&block.bytes)?;
124-
let header = decoded.header();
125-
let mut output = None;
126-
let mut fees = None;
127-
for tx in decoded.txs() {
128-
if let Some(new_fee) = tx.fee() {
129-
fees = Some(fees.unwrap_or_default() + new_fee);
130-
}
131-
for o in tx.outputs() {
132-
output = Some(output.unwrap_or_default() + o.value().coin())
133-
}
181+
let blocks = vec![block];
182+
let mut info = Self::to_block_info_bulk(blocks, store, is_latest)?;
183+
Ok(info.remove(0))
184+
}
185+
186+
fn to_block_info_bulk(
187+
blocks: Vec<Block>,
188+
store: &Arc<dyn Store>,
189+
final_block_is_latest: bool,
190+
) -> Result<Vec<BlockInfo>> {
191+
if blocks.is_empty() {
192+
return Ok(vec![]);
134193
}
135-
let (op_cert_hot_vkey, op_cert_counter) = match &header {
136-
pallas_traverse::MultiEraHeader::BabbageCompatible(h) => {
137-
let cert = &h.header_body.operational_cert;
138-
(
139-
Some(&cert.operational_cert_hot_vkey),
140-
Some(cert.operational_cert_sequence_number),
141-
)
142-
}
143-
pallas_traverse::MultiEraHeader::ShelleyCompatible(h) => (
144-
Some(&h.header_body.operational_cert_hot_vkey),
145-
Some(h.header_body.operational_cert_sequence_number),
146-
),
147-
_ => (None, None),
194+
let mut decoded_blocks = vec![];
195+
for block in &blocks {
196+
decoded_blocks.push(pallas_traverse::MultiEraBlock::decode(&block.bytes)?);
197+
}
198+
199+
let (latest_number, latest_hash) = if final_block_is_latest {
200+
let latest = decoded_blocks.last().unwrap();
201+
(latest.number(), latest.hash())
202+
} else {
203+
let raw_latest = store.get_latest_block()?.unwrap();
204+
let latest = pallas_traverse::MultiEraBlock::decode(&raw_latest.bytes)?;
205+
(latest.number(), latest.hash())
148206
};
149-
let op_cert = op_cert_hot_vkey.map(|vkey| keyhash(vkey));
150207

151-
let (next_block, confirmations) = if is_latest {
152-
(None, 0)
208+
let mut next_hash = if final_block_is_latest {
209+
None
153210
} else {
154-
let number = header.number();
155-
let raw_latest_block = store.get_latest_block()?.unwrap();
156-
let latest_block = pallas_traverse::MultiEraBlock::decode(&raw_latest_block.bytes)?;
157-
let latest_block_number = latest_block.number();
158-
let confirmations = latest_block_number - number;
159-
160-
let next_block_number = number + 1;
161-
let next_block_hash = if next_block_number == latest_block_number {
162-
Some(latest_block.hash().to_vec())
211+
let next_number = decoded_blocks.last().unwrap().number() + 1;
212+
if next_number > latest_number {
213+
None
214+
} else if next_number == latest_number {
215+
Some(latest_hash)
163216
} else {
164-
let raw_next_block = store.get_block_by_number(next_block_number)?;
165-
if let Some(raw_block) = raw_next_block {
166-
let block = pallas_traverse::MultiEraBlock::decode(&raw_block.bytes)?;
167-
Some(block.hash().to_vec())
217+
let raw_next = store.get_block_by_number(next_number)?;
218+
if let Some(raw_next) = raw_next {
219+
let next = pallas_traverse::MultiEraBlock::decode(&raw_next.bytes)?;
220+
Some(next.hash())
168221
} else {
169222
None
170223
}
171-
};
172-
(next_block_hash, confirmations)
224+
}
173225
};
174226

175-
Ok(BlockInfo {
176-
timestamp: block.extra.timestamp,
177-
number: header.number(),
178-
hash: header.hash().to_vec(),
179-
slot: header.slot(),
180-
epoch: block.extra.epoch,
181-
epoch_slot: block.extra.epoch_slot,
182-
issuer_vkey: header.issuer_vkey().map(|key| key.to_vec()),
183-
size: block.bytes.len() as u64,
184-
tx_count: decoded.tx_count() as u64,
185-
output,
186-
fees,
187-
block_vrf: header.vrf_vkey().map(|key| key.to_vec()),
188-
op_cert,
189-
op_cert_counter,
190-
previous_block: header.previous_hash().map(|x| x.to_vec()),
191-
next_block,
192-
confirmations,
193-
})
227+
let mut block_info = vec![];
228+
for (block, decoded) in blocks.iter().zip(decoded_blocks).rev() {
229+
let header = decoded.header();
230+
let mut output = None;
231+
let mut fees = None;
232+
for tx in decoded.txs() {
233+
if let Some(new_fee) = tx.fee() {
234+
fees = Some(fees.unwrap_or_default() + new_fee);
235+
}
236+
for o in tx.outputs() {
237+
output = Some(output.unwrap_or_default() + o.value().coin())
238+
}
239+
}
240+
let (op_cert_hot_vkey, op_cert_counter) = match &header {
241+
pallas_traverse::MultiEraHeader::BabbageCompatible(h) => {
242+
let cert = &h.header_body.operational_cert;
243+
(
244+
Some(&cert.operational_cert_hot_vkey),
245+
Some(cert.operational_cert_sequence_number),
246+
)
247+
}
248+
pallas_traverse::MultiEraHeader::ShelleyCompatible(h) => (
249+
Some(&h.header_body.operational_cert_hot_vkey),
250+
Some(h.header_body.operational_cert_sequence_number),
251+
),
252+
_ => (None, None),
253+
};
254+
let op_cert = op_cert_hot_vkey.map(|vkey| keyhash(vkey));
255+
256+
block_info.push(BlockInfo {
257+
timestamp: block.extra.timestamp,
258+
number: header.number(),
259+
hash: header.hash().to_vec(),
260+
slot: header.slot(),
261+
epoch: block.extra.epoch,
262+
epoch_slot: block.extra.epoch_slot,
263+
issuer_vkey: header.issuer_vkey().map(|key| key.to_vec()),
264+
size: block.bytes.len() as u64,
265+
tx_count: decoded.tx_count() as u64,
266+
output,
267+
fees,
268+
block_vrf: header.vrf_vkey().map(|key| key.to_vec()),
269+
op_cert,
270+
op_cert_counter,
271+
previous_block: header.previous_hash().map(|h| h.to_vec()),
272+
next_block: next_hash.map(|h| h.to_vec()),
273+
confirmations: latest_number - header.number(),
274+
});
275+
276+
next_hash = Some(header.hash());
277+
}
278+
279+
block_info.reverse();
280+
Ok(block_info)
194281
}
195282
}

modules/chain_store/src/stores/fjall.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ impl super::Store for FjallStore {
7575
self.blocks.get_by_number(number)
7676
}
7777

78+
fn get_blocks_by_number_range(&self, min_number: u64, max_number: u64) -> Result<Vec<Block>> {
79+
self.blocks.get_by_number_range(min_number, max_number)
80+
}
81+
7882
fn get_block_by_epoch_slot(&self, epoch: u64, epoch_slot: u64) -> Result<Option<Block>> {
7983
self.blocks.get_by_epoch_slot(epoch, epoch_slot)
8084
}
@@ -160,6 +164,24 @@ impl FjallBlockStore {
160164
self.get_by_hash(&hash)
161165
}
162166

167+
fn get_by_number_range(&self, min_number: u64, max_number: u64) -> Result<Vec<Block>> {
168+
let min_number_bytes = min_number.to_be_bytes();
169+
let max_number_bytes = max_number.to_be_bytes();
170+
let mut hashes = vec![];
171+
for res in self.block_hashes_by_number.range(min_number_bytes..=max_number_bytes) {
172+
let (_, hash) = res?;
173+
hashes.push(hash);
174+
}
175+
176+
let mut blocks = vec![];
177+
for hash in hashes {
178+
if let Some(block) = self.get_by_hash(&hash)? {
179+
blocks.push(block);
180+
}
181+
}
182+
Ok(blocks)
183+
}
184+
163185
fn get_by_epoch_slot(&self, epoch: u64, epoch_slot: u64) -> Result<Option<Block>> {
164186
let Some(hash) = self.block_hashes_by_epoch_slot.get(epoch_slot_key(epoch, epoch_slot))?
165187
else {

modules/chain_store/src/stores/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub trait Store: Send + Sync {
99
fn get_block_by_hash(&self, hash: &[u8]) -> Result<Option<Block>>;
1010
fn get_block_by_slot(&self, slot: u64) -> Result<Option<Block>>;
1111
fn get_block_by_number(&self, number: u64) -> Result<Option<Block>>;
12+
fn get_blocks_by_number_range(&self, min_number: u64, max_number: u64) -> Result<Vec<Block>>;
1213
fn get_block_by_epoch_slot(&self, epoch: u64, epoch_slot: u64) -> Result<Option<Block>>;
1314
fn get_latest_block(&self) -> Result<Option<Block>>;
1415
}

0 commit comments

Comments
 (0)