Indeed you are right, a length of 1
works for me (at least locally). I also spotted this length in the crate example but since it is not particularly documented I though that 1
was just there as a dummy value.
Thanks for the feedback, help and context about the new block_pb
. I’ll check what happens there, until I launch the new project I’m working on there are still things to do but if I hear anything, will change the implementation.
Meanwhile, if it can help anyone here’s what I implemented today related to this question - note that I’m a Rust newbie so my code is probably not the best and the it only checks that the source, from and amount of the transaction are matching - i.e. it doesn’t not check if the payment has already been proceeded or not. In my canister I’ll keep track of the block index that has been proceeded to do so.
use crate::env::LEDGER;
use crate::utils::account_identifier_equal;
use candid::{Func, Principal};
use ic_cdk::api::call::CallResult;
use ic_cdk::call;
use ic_ledger_types::{query_blocks, AccountIdentifier, ArchivedBlockRange, Block, BlockIndex, GetBlocksArgs, GetBlocksResult, Operation, Transaction, DEFAULT_SUBACCOUNT, TransferResult, TransferArgs, Memo, Tokens, transfer, Subaccount};
use futures::future::join_all;
// We do not use subaccount, yet.
static SUB_ACCOUNT: Subaccount = DEFAULT_SUBACCOUNT;
pub fn principal_to_account_identifier(principal: &Principal) -> AccountIdentifier {
// We do not use subaccount, yet.
AccountIdentifier::new(principal, &SUB_ACCOUNT)
}
pub async fn transfer_payment(to: &Principal, amount: Tokens, fee: Tokens) -> CallResult<TransferResult> {
let args = TransferArgs {
memo: Memo(0),
amount,
fee,
from_subaccount: Some(SUB_ACCOUNT),
to: principal_to_account_identifier(&to),
created_at_time: None,
};
let ledger = Principal::from_text(&LEDGER).unwrap();
transfer(ledger, args).await
}
pub async fn verify_payment(
from: AccountIdentifier,
to: AccountIdentifier,
amount: Tokens,
block_index: BlockIndex,
) -> bool {
let ledger = Principal::from_text(&LEDGER).unwrap();
// We can use a length of block of 1 to find the block we are interested in
// https://forum.dfinity.org/t/ledger-query-blocks-how-to/16996/4
let response = blocks_since(ledger, block_index, 1).await.unwrap();
fn payment_check(
transaction: &Transaction,
expected_from: AccountIdentifier,
expected_to: AccountIdentifier,
expected_amount: Tokens,
) -> bool {
match &transaction.operation {
None => (),
Some(operation) => match operation {
Operation::Transfer {
from, to, amount, ..
} => {
return account_identifier_equal(expected_from, from.clone())
&& account_identifier_equal(expected_to, to.clone())
&& expected_amount.e8s() == amount.e8s();
}
Operation::Mint { .. } => (),
Operation::Burn { .. } => (),
},
}
false
}
response
.iter()
.any(|block| payment_check(&block.transaction, from, to, amount))
}
// Source: OpenChat
// https://github.com/open-ic/transaction-notifier/blob/cf8c2deaaa2e90aac9dc1e39ecc3e67e94451c08/canister/impl/src/lifecycle/heartbeat.rs
async fn blocks_since(
ledger_canister_id: Principal,
start: BlockIndex,
length: u64,
) -> CallResult<Vec<Block>> {
let response = query_blocks(ledger_canister_id, GetBlocksArgs { start, length }).await?;
if response.archived_blocks.is_empty() {
Ok(response.blocks)
} else {
async fn get_blocks_from_archive(range: ArchivedBlockRange) -> CallResult<GetBlocksResult> {
let args = GetBlocksArgs {
start: range.start,
length: range.length,
};
let func: Func = range.callback.into();
let (response,) = call(func.principal, &func.method, (args,)).await?;
Ok(response)
}
// Adapt original code .archived_blocks.into_iter().sorted_by_key(|a| a.start)
let mut order_archived_blocks = response.archived_blocks;
order_archived_blocks.sort_by(|a, b| a.start.cmp(&b.start));
// Get the transactions from the archive canisters
let futures: Vec<_> = order_archived_blocks
.into_iter()
.map(get_blocks_from_archive)
.collect();
let archive_responses = join_all(futures).await;
let results = archive_responses
.into_iter()
.collect::<CallResult<Vec<_>>>()?;
Ok(results
.into_iter()
.flat_map(|r| r.unwrap().blocks)
.chain(response.blocks)
.collect())
}
}