|
| 1 | +use candid::{types::reference::Func, CandidType, Principal}; |
1 | 2 | use ic_cdk::api::call::CallResult;
|
2 |
| -use ic_cdk::export::candid::{CandidType, Principal}; |
3 | 3 | use serde::{Deserialize, Serialize};
|
| 4 | +use serde_bytes::ByteBuf; |
4 | 5 | use sha2::Digest;
|
5 | 6 | use std::convert::TryFrom;
|
6 | 7 | use std::fmt;
|
@@ -246,6 +247,143 @@ impl fmt::Display for TransferError {
|
246 | 247 | }
|
247 | 248 | }
|
248 | 249 |
|
| 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 | + |
249 | 387 | /// Calls the "account_balance" method on the specified canister.
|
250 | 388 | ///
|
251 | 389 | /// # Example
|
@@ -298,6 +436,50 @@ pub async fn transfer(
|
298 | 436 | Ok(result)
|
299 | 437 | }
|
300 | 438 |
|
| 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 | + |
301 | 483 | #[cfg(test)]
|
302 | 484 | mod tests {
|
303 | 485 | use super::*;
|
|
0 commit comments