Hi there,
Thank you for your question. Yes, the main objective is to enable linking transactions from the ledger to the official dashboard, such as this example link. The reference in these links is indeed the transaction hash.
Currently, we’re utilizing Rosetta in Docker to read ledger transactions, but we’re planning to switch to the canister ledger. However, a challenge arises as the canister ledger doesn’t provide transaction hashes directly. Therefore, we need to construct a hash from the transaction data in the canister to create links to the dashboard from our application.
To address this, I’ve written a code snippet in Rust aimed at recalculating the hash for debugging purposes, and I plan to replicate this process in Java. However, I’m encountering an issue: the hash generated by my implementation doesn’t match the one on the dashboard. It seems the problem might be related to the AccountIdentifier
. Since I’m not very familiar with Rust, this has been a challenging aspect for me.
I would greatly appreciate your assistance in this matter. Here is the code:
use sha2::{Sha256, Digest};
use serde::{Serialize, Deserialize};
use serde_cbor;
#[derive(Debug, Serialize, Deserialize)]
pub struct TimeStamp {
timestamp_nanos: u64,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Tokens {
/// Number of 10^-8 Tokens.
/// Named because the equivalent part of a Bitcoin is called a Satoshi
e8s: u64,
}
#[derive(Debug,Serialize,Deserialize)]
pub struct AccountIdentifier {
pub hash: [u8; 28],
}
#[derive(Debug,Serialize,Deserialize)]
pub struct Memo(pub u64);
#[derive(Debug,Serialize,Deserialize)]
pub struct ByteBuf {
bytes: Vec<u8>,
}
#[derive(Debug,Serialize,Deserialize)]
pub enum Operation {
Burn {
from: AccountIdentifier,
amount: Tokens,
#[serde(skip_serializing_if = "Option::is_none")]
spender: Option<AccountIdentifier>,
},
Mint {
to: AccountIdentifier,
amount: Tokens,
},
Transfer {
from: AccountIdentifier,
to: AccountIdentifier,
amount: Tokens,
fee: Tokens,
#[serde(skip_serializing_if = "Option::is_none")]
spender: Option<AccountIdentifier>,
},
Approve {
from: AccountIdentifier,
spender: AccountIdentifier,
allowance: Tokens,
expected_allowance: Option<Tokens>,
expires_at: Option<TimeStamp>,
fee: Tokens,
},
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Transaction {
pub operation: Operation,
pub memo: Memo,
/// The time this transaction was created.
pub created_at_time: Option<TimeStamp>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icrc1_memo: Option<ByteBuf>,
}
fn hash_transaction(tx: &Transaction) -> String {
// Print the serialized CBOR data in hexadecimal format
println!("Serialized Transaction (CBOR): {:?}", hex::encode(&serde_cbor::ser::to_vec_packed(&tx).unwrap()));
let mut state = Sha256::new();
state.update(&serde_cbor::ser::to_vec_packed(&tx).unwrap());
let result = state.finalize();
format!("{:x}", result)
}
fn main() {
// transaction: https://dashboard.internetcomputer.org/transaction/2e6a1a86ceadae6c36e2f4be16f05bcdb686d73f7c8c5b007eab351f78ac82f1
// expected hash: 2e6a1a86ceadae6c36e2f4be16f05bcdb686d73f7c8c5b007eab351f78ac82f1
//let from_account_bytes: [u8; 32] = [34, 12, 58, 51, 249, 6, 1, 137, 110, 38, 247, 111, 166, 25, 254, 40, 135, 66, 223, 31, 167, 84, 38, 237, 250, 247, 89, 211, 159, 36, 85, 165];
let from_account_bytes: [u8; 28] = [249, 6, 1, 137, 110, 38, 247, 111, 166, 25, 254, 40, 135, 66, 223, 31, 167, 84, 38, 237, 250, 247, 89, 211, 159, 36, 85, 165];
// Truncate the array to the first 28 bytes
let from_truncated_bytes = <[u8; 28]>::try_from(&from_account_bytes[..28]).unwrap();
//let to_account_bytes: [u8; 32] = [6, 151, 172, 14, 101, 6, 130, 101, 214, 212, 21, 79, 226, 17, 192, 244, 205, 133, 97, 115, 232, 71, 168, 69, 20, 120, 119, 0, 181, 227, 116, 195];
let to_account_bytes: [u8; 28] = [101, 6, 130, 101, 214, 212, 21, 79, 226, 17, 192, 244, 205, 133, 97, 115, 232, 71, 168, 69, 20, 120, 119, 0, 181, 227, 116, 195];
let to_truncated_bytes = <[u8; 28]>::try_from(&to_account_bytes[..28]).unwrap();
let tx = Transaction {
operation: Operation::Transfer {
from: AccountIdentifier {
hash: from_truncated_bytes,
},
to: AccountIdentifier {
hash: to_truncated_bytes,
},
amount: Tokens { e8s: 155814000 },
fee: Tokens { e8s: 10000 },
spender: None,
},
memo: Memo(1704894717240),
created_at_time: Some(TimeStamp {
timestamp_nanos: 1704894717239479196,
}),
//icrc1_memo: None,
icrc1_memo: Some(ByteBuf {
bytes: vec![],
}),
};
let hash = hash_transaction(&tx);
println!("The SHA-256 hash of transaction {:?} is {}", tx, hash);
}
Any guidance or suggestions to resolve this discrepancy would be immensely helpful.
Thank you in advance for your help!