Skip to content

Commit ec218cf

Browse files
feat: interface to query ledger blocks (#269)
* feat: interface to query ledger blocks This change introduces new interface in `ic-ledger-types` to query blocks (the [`query_blocks` endpoint](https://github.com/dfinity/ic/blob/7456121c506acaaa79d2251b803709abf0bdcbb3/rs/rosetta-api/ledger_canister/ledger.did#L236) of the ICP ledger). * make block fields pub
1 parent a080645 commit ec218cf

File tree

3 files changed

+189
-1
lines changed

3 files changed

+189
-1
lines changed

src/ic-ledger-types/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [Unreleased]
8+
### Added
9+
* Methods to query ledger blocks.
10+
711
### Changed
812
* Support conversion from `[u8; 32]` to `AccountIdentifier` via `TryFrom` with CRC-32 check.
13+
* Upgrade `ic-cdk` to 0.5.0
914

1015
## [0.1.1] - 2022-02-04
1116
### Changed

src/ic-ledger-types/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,6 @@ candid = "0.7.4"
2222
crc32fast = "1.2.0"
2323
hex = "0.4"
2424
serde = "1"
25+
serde_bytes = "0.11"
2526
sha2 = "0.9"
2627

src/ic-ledger-types/src/lib.rs

+183-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
use candid::{types::reference::Func, CandidType, Principal};
12
use ic_cdk::api::call::CallResult;
2-
use ic_cdk::export::candid::{CandidType, Principal};
33
use serde::{Deserialize, Serialize};
4+
use serde_bytes::ByteBuf;
45
use sha2::Digest;
56
use std::convert::TryFrom;
67
use std::fmt;
@@ -246,6 +247,143 @@ impl fmt::Display for TransferError {
246247
}
247248
}
248249

250+
#[derive(CandidType, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
251+
pub enum Operation {
252+
Mint {
253+
to: AccountIdentifier,
254+
amount: Tokens,
255+
},
256+
Burn {
257+
from: AccountIdentifier,
258+
amount: Tokens,
259+
},
260+
Transfer {
261+
from: AccountIdentifier,
262+
to: AccountIdentifier,
263+
amount: Tokens,
264+
fee: Tokens,
265+
},
266+
}
267+
268+
#[derive(CandidType, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
269+
pub struct Transaction {
270+
pub memo: Memo,
271+
pub operation: Option<Operation>,
272+
/// The time at which the client of the ledger constructed the transaction.
273+
pub created_at_time: Timestamp,
274+
}
275+
276+
#[derive(CandidType, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
277+
pub struct Block {
278+
/// The hash of the parent block.
279+
pub parent_hash: Option<[u8; 32]>,
280+
pub transaction: Transaction,
281+
/// The time at which the ledger constructed the block.
282+
pub timestamp: Timestamp,
283+
}
284+
285+
#[derive(CandidType, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
286+
pub struct GetBlocksArgs {
287+
/// The index of the first block to fetch.
288+
pub start: BlockIndex,
289+
/// Max number of blocks to fetch.
290+
pub length: u64,
291+
}
292+
293+
#[derive(CandidType, Deserialize, Clone, Debug)]
294+
pub struct QueryBlocksResponse {
295+
pub chain_length: u64,
296+
/// The replica certificate for the last block hash (see https://internetcomputer.org/docs/current/references/ic-interface-spec#certification-encoding).
297+
/// Not available when querying blocks from a canister.
298+
pub certificate: Option<ByteBuf>,
299+
pub blocks: Vec<Block>,
300+
/// The index of the first block in [QueryBlocksResponse::blocks].
301+
pub first_block_index: BlockIndex,
302+
pub archived_blocks: Vec<ArchivedBlockRange>,
303+
}
304+
305+
#[derive(CandidType, Deserialize, Clone, Debug)]
306+
pub struct ArchivedBlockRange {
307+
pub start: BlockIndex,
308+
pub length: u64,
309+
pub callback: QueryArchiveFn,
310+
}
311+
312+
#[derive(CandidType, Deserialize, Clone, Debug, PartialEq, Eq)]
313+
pub struct BlockRange {
314+
pub blocks: Vec<Block>,
315+
}
316+
317+
pub type GetBlocksResult = Result<BlockRange, GetBlocksError>;
318+
319+
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, CandidType)]
320+
pub enum GetBlocksError {
321+
BadFirstBlockIndex {
322+
requested_index: BlockIndex,
323+
first_valid_index: BlockIndex,
324+
},
325+
Other {
326+
error_code: u64,
327+
error_message: String,
328+
},
329+
}
330+
331+
impl fmt::Display for GetBlocksError {
332+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333+
match self {
334+
Self::BadFirstBlockIndex {
335+
requested_index,
336+
first_valid_index,
337+
} => write!(
338+
f,
339+
"invalid first block index: requested block = {}, first valid block = {}",
340+
requested_index, first_valid_index
341+
),
342+
Self::Other {
343+
error_code,
344+
error_message,
345+
} => write!(
346+
f,
347+
"failed to query blocks (error code {}): {}",
348+
error_code, error_message
349+
),
350+
}
351+
}
352+
}
353+
354+
#[derive(Debug, Clone, Deserialize)]
355+
#[serde(transparent)]
356+
pub struct QueryArchiveFn(Func);
357+
358+
impl From<Func> for QueryArchiveFn {
359+
fn from(func: Func) -> Self {
360+
Self(func)
361+
}
362+
}
363+
364+
impl From<QueryArchiveFn> for Func {
365+
fn from(query_func: QueryArchiveFn) -> Self {
366+
query_func.0
367+
}
368+
}
369+
370+
impl CandidType for QueryArchiveFn {
371+
fn _ty() -> candid::types::Type {
372+
candid::types::Type::Func(candid::types::Function {
373+
modes: vec![candid::parser::types::FuncMode::Query],
374+
args: vec![GetBlocksArgs::_ty()],
375+
rets: vec![GetBlocksResult::_ty()],
376+
})
377+
}
378+
379+
fn idl_serialize<S>(&self, serializer: S) -> Result<(), S::Error>
380+
where
381+
S: candid::types::Serializer,
382+
{
383+
Func::from(self.clone()).idl_serialize(serializer)
384+
}
385+
}
386+
249387
/// Calls the "account_balance" method on the specified canister.
250388
///
251389
/// # Example
@@ -298,6 +436,50 @@ pub async fn transfer(
298436
Ok(result)
299437
}
300438

439+
/// Calls the "query_block" method on the specified canister.
440+
/// # Example
441+
/// ```no_run
442+
/// use candid::Principal;
443+
/// use ic_cdk::api::call::CallResult;
444+
/// use ic_ledger_types::{BlockIndex, Block, GetBlocksArgs, query_blocks, query_archived_blocks};
445+
///
446+
/// async fn query_one_block(ledger: Principal, block_index: BlockIndex) -> CallResult<Option<Block>> {
447+
/// let args = GetBlocksArgs { start: block_index, length: 1 };
448+
///
449+
/// let blocks_result = query_blocks(ledger, args.clone()).await?;
450+
///
451+
/// if blocks_result.blocks.len() >= 1 {
452+
/// debug_assert_eq!(blocks_result.first_block_index, block_index);
453+
/// return Ok(blocks_result.blocks.into_iter().next());
454+
/// }
455+
///
456+
/// if let Some(func) = blocks_result
457+
/// .archived_blocks
458+
/// .into_iter()
459+
/// .find_map(|b| (b.start <= block_index && (block_index - b.start) < b.length).then(|| b.callback)) {
460+
/// match query_archived_blocks(&func, args).await? {
461+
/// Ok(range) => return Ok(range.blocks.into_iter().next()),
462+
/// _ => (),
463+
/// }
464+
/// }
465+
/// Ok(None)
466+
/// }
467+
pub async fn query_blocks(
468+
ledger_canister_id: Principal,
469+
args: GetBlocksArgs,
470+
) -> CallResult<QueryBlocksResponse> {
471+
let (result,) = ic_cdk::call(ledger_canister_id, "query_blocks", (args,)).await?;
472+
Ok(result)
473+
}
474+
475+
pub async fn query_archived_blocks(
476+
func: &QueryArchiveFn,
477+
args: GetBlocksArgs,
478+
) -> CallResult<GetBlocksResult> {
479+
let (result,) = ic_cdk::api::call::call(func.0.principal, &func.0.method, (args,)).await?;
480+
Ok(result)
481+
}
482+
301483
#[cfg(test)]
302484
mod tests {
303485
use super::*;

0 commit comments

Comments
 (0)