Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: extracting transaction witness #327

Merged
merged 13 commits into from
Aug 13, 2024
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);
stevenj marked this conversation as resolved.
Show resolved Hide resolved

#[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
Loading