Skip to content

Commit

Permalink
feat: extracting transaction witness (#327)
Browse files Browse the repository at this point in the history
* feat: extract witness from tx

* fix: format

* fix: add word to dic

* fix: add witness to block data and add utils file

* fix: add babbage block test

* fix: new babbage block + fix name

* fix: update babbage block data

* fix: babbage block data slot 67749703

* fix: move utils blake

* fix: cargo toml

* fix: update test data
  • Loading branch information
bkioshn authored Aug 13, 2024
1 parent 7b10997 commit 6d8d714
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 3 deletions.
1 change: 1 addition & 0 deletions .config/dictionaries/project.dic
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ upnp
utimensat
vitss
vkey
vkeywitness
VKEYS
voteplan
voteplans
Expand Down
1 change: 1 addition & 0 deletions hermes/crates/cardano-chain-follower/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ mod point;
mod snapshot_id;
mod stats;
mod utils;
mod witness;

pub use chain_sync_config::ChainSyncConfig;
pub use chain_update::{ChainUpdate, Kind};
Expand Down
25 changes: 22 additions & 3 deletions hermes/crates/cardano-chain-follower/src/multi_era_block_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::{
metadata,
point::{ORIGIN_POINT, UNKNOWN_POINT},
stats::stats_invalid_block,
witness::TxWitness,
Network, Point,
};

Expand Down Expand Up @@ -52,6 +53,9 @@ pub struct MultiEraBlockInner {
data: SelfReferencedMultiEraBlock,
/// Decoded Metadata in the transactions in the block.
metadata: metadata::DecodedTransaction,
/// A map of public key hashes to the public key and transaction numbers they are in.
#[allow(dead_code)]
witness_map: Option<TxWitness>,
}

/// Multi-era block.
Expand Down Expand Up @@ -97,6 +101,8 @@ impl MultiEraBlock {
let self_ref_block = builder.try_build()?;
let decoded_block = self_ref_block.borrow_block();

let witness_map = TxWitness::new(&decoded_block.txs()).ok();

let slot = decoded_block.slot();

let point = Point::new(slot, decoded_block.hash().to_vec());
Expand Down Expand Up @@ -146,6 +152,7 @@ impl MultiEraBlock {
previous: previous.clone(),
data: self_ref_block,
metadata,
witness_map,
}),
})
}
Expand Down Expand Up @@ -228,6 +235,12 @@ impl MultiEraBlock {
pub fn txn_raw_metadata(&self, txn_idx: usize, label: u64) -> Option<Arc<Vec<u8>>> {
self.inner.metadata.get_raw_metadata(txn_idx, label)
}

/// Returns the witness map for the block.
#[allow(dead_code)]
pub(crate) fn witness_map(&self) -> Option<&TxWitness> {
self.inner.witness_map.as_ref()
}
}

impl Display for MultiEraBlock {
Expand Down Expand Up @@ -308,7 +321,7 @@ impl PartialOrd<Point> for MultiEraBlock {
}

#[cfg(test)]
mod tests {
pub(crate) mod tests {
use std::ops::Add;

use anyhow::Ok;
Expand Down Expand Up @@ -343,12 +356,18 @@ mod tests {
.expect("Failed to decode hex block.")
}

/// Allegra Test Block data
fn alonzo_block() -> Vec<u8> {
/// Alonzo Test Block data
pub(crate) fn alonzo_block() -> Vec<u8> {
hex::decode(include_str!("./../test_data/allegra.block"))
.expect("Failed to decode hex block.")
}

/// Babbage Test Block data
pub(crate) fn babbage_block() -> Vec<u8> {
hex::decode(include_str!("./../test_data/babbage.block"))
.expect("Failed to decode hex block.")
}

/// An array of test blocks
fn test_blocks() -> Vec<TestRecord> {
vec![
Expand Down
19 changes: 19 additions & 0 deletions hermes/crates/cardano-chain-follower/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Simple general purpose utility functions.
use blake2b_simd::{self, Params};

/// Convert T to an i16. (saturate if out of range.)
#[allow(dead_code)]
pub(crate) fn i16_from_saturating<T: TryInto<i16>>(value: T) -> i16 {
Expand Down Expand Up @@ -28,3 +30,20 @@ pub(crate) fn usize_from_saturating<
}
}
}

/// Convert the given value to `blake2b_244` array.
pub(crate) fn blake2b_244(value: &[u8]) -> anyhow::Result<[u8; 28]> {
let h = Params::new().hash_length(28).hash(value);
let b = h.as_bytes();
b.try_into()
.map_err(|_| anyhow::anyhow!("Invalid length of blake2b_244, expected 28 got {}", b.len()))
}

#[allow(dead_code)]
/// Convert the given value to `blake2b_256` array.
pub(crate) fn blake2b_256(value: &[u8]) -> anyhow::Result<[u8; 32]> {
let h = Params::new().hash_length(32).hash(value);
let b = h.as_bytes();
b.try_into()
.map_err(|_| anyhow::anyhow!("Invalid length of blake2b_256, expected 32 got {}", b.len()))
}
136 changes: 136 additions & 0 deletions hermes/crates/cardano-chain-follower/src/witness.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
//! Transaction Witness
use std::fmt::{Display, Formatter};

use dashmap::DashMap;
use pallas::{codec::utils::Bytes, ledger::traverse::MultiEraTx};

use crate::utils::blake2b_244;

/// `WitnessMap` type of `DashMap` with
/// key as [u8; 28] = (`blake2b_244` hash of the public key)
/// value as (Bytes, Vec<u8>) = (public key, tx index within the block)
#[allow(dead_code)]
pub(crate) type WitnessMap = DashMap<[u8; 28], (Bytes, Vec<u8>)>;

#[derive(Debug)]
#[allow(dead_code)]
/// `TxWitness` struct to store the witness data.
pub(crate) struct TxWitness(WitnessMap);

#[allow(dead_code)]
impl TxWitness {
/// Create a new `TxWitness` from a list of `MultiEraTx`.
pub(crate) fn new(txs: &[MultiEraTx]) -> anyhow::Result<Self> {
let map: WitnessMap = DashMap::new();
for (i, tx) in txs.iter().enumerate() {
match tx {
MultiEraTx::AlonzoCompatible(tx, _) => {
let witness_set = &tx.transaction_witness_set;
if let Some(vkey_witness_set) = witness_set.vkeywitness.clone() {
for vkey_witness in vkey_witness_set {
let vkey_hash = blake2b_244(&vkey_witness.vkey)?;
let tx_num = u8::try_from(i)?;
map.entry(vkey_hash)
.and_modify(|entry: &mut (_, Vec<u8>)| entry.1.push(tx_num))
.or_insert((vkey_witness.vkey.clone(), vec![tx_num]));
}
};
},
MultiEraTx::Babbage(tx) => {
let witness_set = &tx.transaction_witness_set;
if let Some(vkey_witness_set) = witness_set.vkeywitness.clone() {
for vkey_witness in vkey_witness_set {
let vkey_hash = blake2b_244(&vkey_witness.vkey)?;
let tx_num = u8::try_from(i)?;
map.entry(vkey_hash)
.and_modify(|entry: &mut (_, Vec<u8>)| entry.1.push(tx_num))
.or_insert((vkey_witness.vkey.clone(), vec![tx_num]));
}
}
},
MultiEraTx::Conway(tx) => {
let witness_set = &tx.transaction_witness_set;
if let Some(vkey_witness_set) = &witness_set.vkeywitness.clone() {
for vkey_witness in vkey_witness_set {
let vkey_hash = blake2b_244(&vkey_witness.vkey)?;
let tx_num = u8::try_from(i)?;
map.entry(vkey_hash)
.and_modify(|entry: &mut (_, Vec<u8>)| entry.1.push(tx_num))
.or_insert((vkey_witness.vkey.clone(), vec![tx_num]));
}
}
},
_ => {},
};
}
Ok(Self(map))
}

/// Check whether the public key hash is in the given transaction number.
pub(crate) fn check_witness_in_tx(&self, vkey_hash: &[u8; 28], tx_num: u8) -> bool {
self.0
.get(vkey_hash)
.map_or(false, |entry| entry.1.contains(&tx_num))
}

/// Get the actual address from the given public key hash.
pub(crate) fn get_witness_pk_addr(&self, vkey_hash: &[u8; 28]) -> Option<Bytes> {
self.0.get(vkey_hash).map(|entry| entry.0.clone())
}
}

impl Display for TxWitness {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for data in &self.0 {
let vkey_hash = hex::encode(data.key());
let vkey: Vec<u8> = data.0.clone().into();
let vkey_encoded = hex::encode(&vkey);
writeln!(
f,
"Key Hash: {}, PublicKey: {}, Tx: {:?}",
vkey_hash, vkey_encoded, data.1
)?;
}
Ok(())
}
}

#[cfg(test)]
mod tests {

use super::*;
use crate::multi_era_block_data::tests::{alonzo_block, babbage_block};

#[test]
fn tx_witness() {
let alonzo = alonzo_block();
let alonzo_block = pallas::ledger::traverse::MultiEraBlock::decode(&alonzo)
.expect("Failed to decode MultiEraBlock");
let txs_alonzo = alonzo_block.txs();
let tx_witness_alonzo = TxWitness::new(&txs_alonzo).expect("Failed to create TxWitness");
let vkey1_hash: [u8; 28] =
hex::decode("6082eb618d161a704207a0b3a9609e820111570d94d1e711b005386c")
.expect("Failed to decode vkey1_hash")
.try_into()
.expect("Invalid length of vkey1_hash");
println!("{tx_witness_alonzo}");
assert!(tx_witness_alonzo.get_witness_pk_addr(&vkey1_hash).is_some());
assert!(tx_witness_alonzo.check_witness_in_tx(&vkey1_hash, 0));

let babbage = babbage_block();
let babbage_block = pallas::ledger::traverse::MultiEraBlock::decode(&babbage)
.expect("Failed to decode MultiEraBlock");
let txs_babbage = babbage_block.txs();
let tx_witness_babbage = TxWitness::new(&txs_babbage).expect("Failed to create TxWitness");
let vkey2_hash: [u8; 28] =
hex::decode("ba4ab50bdecca85162f3b8114739bc5ba3aaa6490e2b1d15ad0f9c66")
.expect("Failed to decode vkey2_hash")
.try_into()
.expect("Invalid length of vkey2_hash");
println!("{tx_witness_babbage}");
assert!(tx_witness_babbage
.get_witness_pk_addr(&vkey2_hash)
.is_some());
assert!(tx_witness_babbage.check_witness_in_tx(&vkey2_hash, 0));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
820685828a1a002751831a0409c74758208f359d2ca9a3e513b574cc88c93346506e2520f9939eadfc2e02e6c251a94769582069b5785508a9898605833a7c346c9fa294eb9e6793e0887a0230a1e026ebd1fa5820e9fff58586a4a588f15f371b4d7bfc04344b6a281bce39a7ab81fe5b6751cbac825840df4ca9e1e98212e3bfa0356274e63890304710237e92973de89d8dfba015f3eb8e925557cadcb70031cd644fe84b043f260e81342bf865d811409ae2aefb528458509aab92e40a5c75252e190d6860fbe0274ee6f7de16d12256df1dbd1f08b0f835fc0f333395d8d94464e5181e72c3fd935573005f03656191e9e95855f8fbcfd3456a6c268df905c805c9e510dff0df0f190c505820e6aebff5e8b2049914014d88821ef94b873b219ea32b44a50161b7bd2442deb5845820356e1f77552adeab6d94c2383cb4f7a5fb1b644858a6639cf9f71da339ad1e17081901ff5840fc25623ba50cf9f2858463dca7a3b1c703b97bd49d54e965d2df9602424986bf26e29ab4dac9b82f185cb825cd4a0b6dbc90374defd9ca20206cd25f47dc01058209015901c07d47377a4d51a9ed20b203308e73c500db49947189f6c92e951ab0d24b131d20d0f157fdf10a90aad4c8c6173dcd8d16b834ff47f1914556ae14b8015d7edc0ea0c33c70c7867e06a2952efe9066ceae6e1b9b23c59c76148f01917099657153af53c0cb5bb0a601d6e7e504033c2522fe069dda3a488d189032fd4ff59273526b263cabf7966326054aa26bb3d3e24010aa1edec3a545c80a1e1e357b0bea164c6663240e865cff3e29d30862b99f1ce52ca042f5d7d065bc29cc7d4ccce45acce31cc0da588c4ddec1fd4e3444ace1bf583df247ba168e0375862090fa4f49fa95fd28df6ce88f72e303d1f9cd4353abdd2d1a4472164d15220142e922b03eb6657152f340cf460514bbe57e892b81fe633d395773c00b91025237ed84ef5c6541d6bccb5a56f736b294fc6500aaa9e958c7b41bd422200545efbf0e5eb0ff95fc2394ab22cb543854aab426f60ab5dc04fa9ca3d9cc71a2abcf00c1ef8287f9bd9f7508fc5cc6a4513faac33b05b06304883ec50acb9c1ee3033604ce4eebe75beba7e97a731f92c6e779ecf700c3220ec992752b49708cab9d9a079803a01832cb14da7c59d069c4538cadd38c90b6502ce3b311424eaba7676f3dbc720b84ab008382582022d427bf2262a44dc3a3a2c9555696a741d779eb89afecf624e91a1ebdf3335400825820533e9251fd0ed7ac8613fe3d05813ce3bde7d96a6e446019a93421a0c6b785a50282582090b06350ad57403e8dcdb49d4f9fad5de1b244668fd5a0c98cd08b78d5f343b3010183a300581d704e3b75eb68829ca9e63b541f3645f78afe8d119932a7a2528171e74901821a001e8480a1581c5cf48c225e057f8da0787eae1e2bc62a1cf1d2ee34c7e2a9498b19b5a1484e6f64654665656401028201d8185840d87a9fd8799f581cba4ab50bdecca85162f3b8114739bc5ba3aaa6490e2b1d15ad0f9c66d8799fd8799f1b0000000da0e77c201b00000191449a86f5ffffffff82581d60ba4ab50bdecca85162f3b8114739bc5ba3aaa6490e2b1d15ad0f9c661a0089544082581d60ba4ab50bdecca85162f3b8114739bc5ba3aaa6490e2b1d15ad0f9c661a00ac398c021a00044c8c031a0409c766081a0409c6ee0b5820dbf505c29f869273f15ea579723e9ca9bcf31fdb8c4cb4e4085268f3714b99b90d818258206f8ebcf2a2b04f5cb5f977aa1e41001301634e64a532a8de1e98147cc84e6502010e81581cba4ab50bdecca85162f3b8114739bc5ba3aaa6490e2b1d15ad0f9c661082581d60ba4ab50bdecca85162f3b8114739bc5ba3aaa6490e2b1d15ad0f9c661a00524801111a00370c3f1281825820efd8a48f69e5270cb73b438067e1b566d39758329a45e5d6d36755f2c732618400ab00838258201df5c710d3251bea8457e05813101bd7f8defaba978114939ffa38035d42672a02825820e04bce69a5659295c235502d5fdb62b29e43f773e698c97e533e394d92d9228001825820ec4b6e2455e84a8aaa365cdc4c41ee446314ea74c209e698f1785c94c9952910000183a300581d704e3b75eb68829ca9e63b541f3645f78afe8d119932a7a2528171e74901821a001e8480a1581c5cf48c225e057f8da0787eae1e2bc62a1cf1d2ee34c7e2a9498b19b5a1484e6f64654665656401028201d8185840d87a9fd8799f581c3f2728ec78ef8b0f356e91a5662ff3124add324a7b7f5aeed69362f4d8799fd8799f1b0000000da0cc2bf01b00000191449a8523ffffffff82581d603f2728ec78ef8b0f356e91a5662ff3124add324a7b7f5aeed69362f41a0089544082581d603f2728ec78ef8b0f356e91a5662ff3124add324a7b7f5aeed69362f41a00a1cf51021a0004529a031a0409c766081a0409c6ee0b5820ed60fdd0e4e9cc43b5fa9a601a3d013c6a8f26916ff80e9b5c5a41813493f04e0d8182582040d412944923070bbdeca4887e30983b497e455e62a150209d522c4f2eec82f5020e81581c3f2728ec78ef8b0f356e91a5662ff3124add324a7b7f5aeed69362f41082581d603f2728ec78ef8b0f356e91a5662ff3124add324a7b7f5aeed69362f41a00455948111a00370c3f1281825820efd8a48f69e5270cb73b438067e1b566d39758329a45e5d6d36755f2c732618400ab00838258203efd5491b62cec43b8421eeb9978d9544a451cf938b454278dabfe508a867175008258204d1deb722bdab493e69840eca2fbb2af5eed36a82d2cd196754ce4af2618ecb902825820f1883a3368b6baa87f5d051d3e7fedb0c571519333f38860b54ec7b4dec3253e010183a300581d704e3b75eb68829ca9e63b541f3645f78afe8d119932a7a2528171e74901821a001e8480a1581c5cf48c225e057f8da0787eae1e2bc62a1cf1d2ee34c7e2a9498b19b5a1484e6f64654665656401028201d8185840d87a9fd8799f581c17942ff3849b623d24e31ec709c1c94c53b9240311820a9601ad4af0d8799fd8799f1b0000000da0cc2bf01b00000191449a8522ffffffff82581d6017942ff3849b623d24e31ec709c1c94c53b9240311820a9601ad4af01a0089544082581d6017942ff3849b623d24e31ec709c1c94c53b9240311820a9601ad4af01a0073c968021a00044c8c031a0409c766081a0409c6ee0b5820dbf505c29f869273f15ea579723e9ca9bcf31fdb8c4cb4e4085268f3714b99b90d818258204c6d1cb7cba6122de41fb91be0f78b61316399096d6f54bd7e3f2f76dd63b92e020e81581c17942ff3849b623d24e31ec709c1c94c53b9240311820a9601ad4af01082581d6017942ff3849b623d24e31ec709c1c94c53b9240311820a9601ad4af01a004df567111a00370c3f1281825820efd8a48f69e5270cb73b438067e1b566d39758329a45e5d6d36755f2c732618400a50081825820aa7bbea2a5101b867d969ad7e6ff7d95d440199ef4ba6621751d25521566101400018182583900eb21979c03eed7207020b2a0b47565b5aafb1a2c3849b59a1fa8e6c5e075be10ec5c575caffb68b08c31470666d4fe1aeea07c16d64739031b00000002525569af021a00032471075820420e72fc38474ce192fc9ba39380a11ddf9e5ac96a814d3fdb68c24cbf81e4600f0084a200818258209a12ec259e59bd8fc60244062f294f11555539f5fd45e77d8a57f16b5987dc4458401944ed87d70eb6646aa840b9914c462b1e4e3513831d1f5ec817a6966d00c0b6f12bf24b0576ea9593ed4f9c9dac2f0d9e15473088e64f688f3dea4416dbe90d0581840000d87980821a0012a1311a15d0fa8ba20081825820499300f082db931c561f42b9ca7d5ea4b288ae74cf1969d1fbd7bf41e7b02e8e5840504c78c1fe246730ca7c2469ba6731cf7e0aff42bd027091283568fe25d2a9f35da1593fb89e5bfd2f7d2db58316ee86ed1c8380e8fb77d61362c78f64374a010581840002d87980821a0012eff61a1622a0d4a20081825820ae01a85ba5eb8e67830cf6b422cb819cac1eaf0c7f62dbc496f12d9735b170d7584004dd05c7ea8b1ab964f67bfe4515e18451b23ee7950c8224f9b355aa1005ba1c7051951e82883c9cb25be7ada388fe2b708eaa6d9f1590516f2782f8b5ff20050581840000d87980821a0012a1311a15d0fa8ba100818258208469288efa6f9cb49040b43dcff93f40969d72433f751afde50235012c01602058401d84bfa159ac070a11834e2a41b38f205ce1628ccdc693fe2000e21c680e6ead1a23a23043bb99ac1de8460fee2f5b40d3a736566b9434d1b551b115956b5004a103a11901fda50050ca7a1457ef9f4c7f9c747f8c4a4cfa6c0150152a368b96d95f61ae69513dd5f6e92a0258204d3f576f26db29139981a69443c2325daa812cc353a31b5a4db794a5bcbb06c20b8c58401b3d030866084fcb259de07496d3197e913a39fd628a3db0a4ed6839261a00c51cb0a5b9c16194064132ace375ea23c75c60659400cba304d0d689c00086195d5840ff28714da02c35e7295815ba58b77f227e576fa254c464e2f9c6f9dfa900a0208250033c054a468c38e08819601d073c034a4727a524ff39995477443c1fca235840839c927599b253887f50487c1caf757c0aaf79bc3fcacd42252b8f2ae1f1a8b282929ca22bb5c2885cc23a66005c0cc1ca20142b82310c3a137d44c1943e40995840a7a7ce5c3475b5887a3765ede2ff3b7bfea90f255e2edf37fd44e27f26b8e6cf408aef4b20bebf7257b3dabc7eda65fff4ed278b50219f0a52367ff5b80e46b758403875f55a394d17a5d9a6b1a1deff5b2206e9e9734e9fbefa6a1cdfeb7a104546dfb6e46c46feaeb65a7f4648c276e29e87b27bc053bffef79359300220d0c3875840f2a05cc4880317358e19c758fd9ab9917551ce3987af2e35d73b6958a0f5732784621b0c92f68a93537f16f48445424890f955d7a597c13c2eb54a82b39f0307584097507df5fef916fabb6dafdfb516fb9184783e2cb4e89d048a6c1e5c04818bdb76ffb5cbef1fbe452658d904cd152ee72a3bfc6efe1199fb3b51f1979629cd4e5840fdb7df511723d4cead3d2b2eb9c1f18cbbfcf9f5cc8eac46dc03cd55fcac3303c391437f50400923e65c02e981af5461b6867a47fb25ebe9b0fb4d9e41ec210e58404b9011000206414523c0990f9ee20b5d8a745393d3febaf6413a448b994f1567eb7945df7a0ab44afd55561e0190b376d411026c5d7a4a49a19e0bd3f5addd6c5840492fde46eee8d75b587286291dfeb6a78fdf59c1a6bfa2717b1f41dfa878756140ce7c77504b64b094b870ade78569566eec66369133af5aa8c8eab9f95e29df58409ec10be251547101b24c495c8ff4fa55378dbb4a5c6e89b18a12ac033343d61c3b7f5fba725b51536d92a5cbfaef9be6d24a3e5b3d75a1c0e29e42f523567fac4d0f8200811c822d2210b97f5708186358407b322d37df11460b98e13f6c3c4d5d4985ad984768d09f77516e8e0f61ed24e646c466a995c2bf2b547302b96d4582be9b65f8d52f9fbc4857e7bef79948860180

0 comments on commit 6d8d714

Please sign in to comment.