diff --git a/pallas-primitives/Cargo.toml b/pallas-primitives/Cargo.toml index 15ae3489..94aa8c36 100644 --- a/pallas-primitives/Cargo.toml +++ b/pallas-primitives/Cargo.toml @@ -19,3 +19,4 @@ minicbor-derive = "0.8.0" hex = "0.4.3" log = "0.4.14" pallas-crypto = { version = "0.5.0-alpha.0", path = "../pallas-crypto" } +base58 = "0.2.0" diff --git a/pallas-primitives/src/byron/address.rs b/pallas-primitives/src/byron/address.rs new file mode 100644 index 00000000..9688c3a1 --- /dev/null +++ b/pallas-primitives/src/byron/address.rs @@ -0,0 +1,62 @@ +use crate::Error; + +use super::Address; +use base58::ToBase58; +use minicbor::to_vec; + +impl Address { + pub fn to_addr_string(&self) -> Result { + let cbor = to_vec(self)?; + Ok(cbor.to_base58()) + } +} + +#[cfg(test)] +mod tests { + use std::ops::Deref; + + use crate::byron::Block; + use crate::Fragment; + + const KNOWN_ADDRESSES: &[&str] = &[ + "DdzFFzCqrht8QHTQXbWy2qoyPaqTN8BjyfKygGmpy9dtot1tvkBfCaVTnR22XCaaDVn3M1U6aiMShoCLzw6VWSwzQKhhJrM3YjYp3wyy", + "DdzFFzCqrhsjUjMRukzoFx8sToHzCt4iidB17STXk9adAoVMNus5SvAjS1cXfPKbuNbPUZ5xQG25sMK85n9GdMkqo2ytqBnKWC68s8P3", + "Ae2tdPwUPEZFBnsqpm2RkDQfwJseUrBKrTECCDom4bAqNsxTNwbMPCZtbyJ", + "DdzFFzCqrhsvNcX48aHrYdcQhKhAwEdLebH7xPskKtQnZucNQmXXmEMJEU2NDqipuDTZKFec5UMCtm4vYoUgjixxULJexAzHGa3Bmctk", + "Ae2tdPwUPEZGEC75fV3vktzbwxhkD71JHxSYVgiNCgKB7Yo1rWamWVJDFsV", + "DdzFFzCqrht1K1sR9fQWEdcxhTxgqKQqwPHucez1McJy14uqbxu1UKnnq12EdHr9NxYs8RqKnSvswegh8wLvfC4fw6arB3nyqC5Wy4Ky", + "DdzFFzCqrhtC8HauYJMa59DzoAeTLnpDcdst1hFWmjkTdg5Xu55ougiBpAmwuo2Coe2DfAj26m52aF4e2yk8v5GQc4umZxsXUT2CuTB2", + "DdzFFzCqrht4Zsigv43q9LRHNsgu6TWdASuGe6tCYuv2B9H2wTggkvyuwHMb5WALqWDDiNQEHYq7BvnFJ65UDzKi6ThdZusVYmYLpJg9", + ]; + + #[test] + fn known_address_matches() { + // TODO: expand this test to include more test blocks + let block_idx = 1; + let block_str = include_str!("test_data/test2.block"); + + let block_bytes = hex::decode(block_str).expect(&format!("bad block file {}", block_idx)); + let block = Block::decode_fragment(&block_bytes[..]) + .expect(&format!("error decoding cbor for file {}", block_idx)); + + let block = match block { + Block::MainBlock(x) => x, + Block::EbBlock(_) => panic!(), + }; + + // don't want to pass if we don't have tx in the block + assert!(block.body.tx_payload.len() > 0); + + for tx in block.body.tx_payload.iter() { + for output in tx.deref().transaction.outputs.iter() { + let addr_str = output.address.to_addr_string().unwrap(); + + assert!( + KNOWN_ADDRESSES.contains(&addr_str.as_str()), + "address {} not in known list", + addr_str + ); + } + } + } +} diff --git a/pallas-primitives/src/byron/crypto.rs b/pallas-primitives/src/byron/crypto.rs index a9bc6084..ad681d4e 100644 --- a/pallas-primitives/src/byron/crypto.rs +++ b/pallas-primitives/src/byron/crypto.rs @@ -1,26 +1,34 @@ -use super::{Block, BlockHead, EbbHead}; +use super::{Block, BlockHead, EbbHead, Tx}; use pallas_crypto::hash::{Hash, Hasher}; -pub fn hash_boundary_block_header(header: &EbbHead) -> Hash<32> { - // hash expects to have a prefix for the type of block - Hasher::<256>::hash_cbor(&(0, header)) +impl EbbHead { + pub fn to_hash(&self) -> Hash<32> { + // hash expects to have a prefix for the type of block + Hasher::<256>::hash_cbor(&(0, self)) + } } -pub fn hash_main_block_header(header: &BlockHead) -> Hash<32> { - // hash expects to have a prefix for the type of block - Hasher::<256>::hash_cbor(&(1, header)) +impl BlockHead { + pub fn to_hash(&self) -> Hash<32> { + // hash expects to have a prefix for the type of block + Hasher::<256>::hash_cbor(&(1, self)) + } } -pub fn hash_block_header(block: &Block) -> Hash<32> { - match block { - Block::EbBlock(x) => hash_boundary_block_header(&x.header), - Block::MainBlock(x) => hash_main_block_header(&x.header), +impl Block { + pub fn to_hash(&self) -> Hash<32> { + match self { + Block::EbBlock(x) => x.header.to_hash(), + Block::MainBlock(x) => x.header.to_hash(), + } } } -//pub fn hash_transaction(data: &TransactionBody) -> Hash<32> { -// Hasher::<256>::hash_cbor(data) -//} +impl Tx { + pub fn to_hash(&self) -> Hash<32> { + Hasher::<256>::hash_cbor(self) + } +} #[cfg(test)] mod tests { @@ -40,7 +48,7 @@ mod tests { let block_model = Block::decode_fragment(&block_bytes[..]) .expect(&format!("error decoding cbor for file {}", block_idx)); - let computed_hash = super::hash_block_header(&block_model); + let computed_hash = block_model.to_hash(); assert_eq!(hex::encode(computed_hash), KNOWN_HASH) } diff --git a/pallas-primitives/src/byron/fees.rs b/pallas-primitives/src/byron/fees.rs new file mode 100644 index 00000000..aee131d4 --- /dev/null +++ b/pallas-primitives/src/byron/fees.rs @@ -0,0 +1,76 @@ +use crate::Error; + +use super::TxPayload; +use minicbor::to_vec; + +pub struct PolicyParams { + constant: u64, + size_coeficient: u64, +} + +impl Default for PolicyParams { + fn default() -> Self { + Self { + constant: 155_381_000_000_000u64, + size_coeficient: 43_946_000_000u64, + } + } +} + +fn compute_linear_fee_policy(tx_size: u64, params: &PolicyParams) -> u64 { + println!("tx size: {}", tx_size); + let nanos = params.constant + (tx_size * params.size_coeficient); + + let loves = nanos / 1_000_000_000; + + let rem = match nanos % 1_000_000_000 { + 0 => 0u64, + _ => 1u64, + }; + + loves + rem +} + +impl TxPayload { + pub fn compute_fee(&self, params: &PolicyParams) -> Result { + let tx_size = to_vec(&self)?.len(); + let fee = compute_linear_fee_policy(tx_size as u64, params); + + Ok(fee) + } + + pub fn compute_fee_with_defaults(&self) -> Result { + self.compute_fee(&PolicyParams::default()) + } +} + +#[cfg(test)] +mod tests { + use crate::byron::Block; + use crate::Fragment; + + #[test] + fn known_fee_matches() { + // TODO: expand this test to include more test blocks + let block_idx = 1; + let block_str = include_str!("test_data/test4.block"); + + let block_bytes = hex::decode(block_str).expect(&format!("bad block file {}", block_idx)); + let block = Block::decode_fragment(&block_bytes[..]) + .expect(&format!("error decoding cbor for file {}", block_idx)); + + let block = match block { + Block::MainBlock(x) => x, + Block::EbBlock(_) => panic!(), + }; + + // don't want to pass if we don't have tx in the block + assert!(block.body.tx_payload.len() > 0); + + for tx in block.body.tx_payload.iter().take(1) { + println!("{}", tx.transaction.to_hash()); + let fee = tx.compute_fee_with_defaults().unwrap(); + assert_eq!(fee, 171070); + } + } +} diff --git a/pallas-primitives/src/byron/mod.rs b/pallas-primitives/src/byron/mod.rs index 394d2389..41289726 100644 --- a/pallas-primitives/src/byron/mod.rs +++ b/pallas-primitives/src/byron/mod.rs @@ -1,7 +1,9 @@ //! Ledger primitives and cbor codec for the Byron era +mod address; +mod crypto; +mod fees; mod model; +mod time; pub use model::*; - -pub mod crypto; diff --git a/pallas-primitives/src/byron/model.rs b/pallas-primitives/src/byron/model.rs index fac4b187..38e93a3c 100644 --- a/pallas-primitives/src/byron/model.rs +++ b/pallas-primitives/src/byron/model.rs @@ -173,13 +173,39 @@ impl minicbor::Encode for AddrAttrProperty { pub type AddrAttr = OrderPreservingProperties; +#[derive(Debug, Encode, Decode)] +pub struct AddressPayload { + #[n(0)] + pub root: AddressId, + + #[n(1)] + pub attributes: AddrAttr, + + #[n(2)] + pub addrtype: AddrType, +} + // address = [ #6.24(bytes .cbor ([addressid, addrattr, addrtype])), u64 ] -pub type Address = (CborWrap<(AddressId, AddrAttr, AddrType)>, u64); +#[derive(Debug, Encode, Decode)] +pub struct Address { + #[n(0)] + pub payload: CborWrap, + + #[n(1)] + pub crc: u64, +} // Transactions // txout = [address, u64] -pub type TxOut = (Address, u64); +#[derive(Debug, Encode, Decode)] +pub struct TxOut { + #[n(0)] + pub address: Address, + + #[n(1)] + pub amount: u64, +} #[derive(Debug)] pub enum TxIn { @@ -228,21 +254,34 @@ impl minicbor::Encode for TxIn { } // tx = [[+ txin], [+ txout], attributes] -pub type Tx = (Vec, Vec, Attributes); +#[derive(Debug, Encode, Decode)] +pub struct Tx { + #[n(0)] + pub inputs: MaybeIndefArray, + + #[n(1)] + pub outputs: MaybeIndefArray, + + #[n(2)] + pub attributes: Attributes, +} // txproof = [u32, hash, hash] pub type TxProof = (u32, ByronHash, ByronHash); +pub type ValidatorScript = (u16, ByteVec); +pub type RedeemerScript = (u16, ByteVec); + #[derive(Debug)] pub enum Twit { // [0, #6.24(bytes .cbor ([pubkey, signature]))] - Variant0(CborWrap<(PubKey, Signature)>), + PkWitness(CborWrap<(PubKey, Signature)>), // [1, #6.24(bytes .cbor ([[u16, bytes], [u16, bytes]]))] - Variant1(CborWrap<((u16, ByteVec), (u16, ByteVec))>), + ScriptWitness(CborWrap<(ValidatorScript, RedeemerScript)>), // [2, #6.24(bytes .cbor ([pubkey, signature]))] - Variant2(CborWrap<(PubKey, Signature)>), + RedeemWitness(CborWrap<(PubKey, Signature)>), // [u8 .gt 2, encoded-cbor] Other(u8, ByteVec), @@ -255,9 +294,9 @@ impl<'b> minicbor::Decode<'b> for Twit { let variant = d.u8()?; match variant { - 0 => Ok(Twit::Variant0(d.decode()?)), - 1 => Ok(Twit::Variant1(d.decode()?)), - 2 => Ok(Twit::Variant2(d.decode()?)), + 0 => Ok(Twit::PkWitness(d.decode()?)), + 1 => Ok(Twit::ScriptWitness(d.decode()?)), + 2 => Ok(Twit::RedeemWitness(d.decode()?)), x => Ok(Twit::Other(x, d.decode()?)), } } @@ -269,21 +308,21 @@ impl minicbor::Encode for Twit { e: &mut minicbor::Encoder, ) -> Result<(), minicbor::encode::Error> { match self { - Twit::Variant0(x) => { + Twit::PkWitness(x) => { e.array(2)?; e.u8(0)?; e.encode(x)?; Ok(()) } - Twit::Variant1(x) => { + Twit::ScriptWitness(x) => { e.array(2)?; e.u8(1)?; e.encode(x)?; Ok(()) } - Twit::Variant2(x) => { + Twit::RedeemWitness(x) => { e.array(2)?; e.u8(2)?; e.encode(x)?; @@ -327,7 +366,7 @@ pub type VssDec = ByteVec; // cddl note: // This is encoded using the // 'Binary' instance for Scrape.Proof -pub type VssProof = (ByteVec, ByteVec, ByteVec, Vec); +pub type VssProof = (ByteVec, ByteVec, ByteVec, MaybeIndefArray); //ssccomm = [pubkey, [{vsspubkey => vssenc},vssproof], signature] pub type SscComm = ( @@ -781,7 +820,14 @@ pub struct BlockHead { } // [tx, [* twit]] -pub type TxPayload = (Tx, Vec); +#[derive(Debug, Encode, Decode)] +pub struct TxPayload { + #[n(0)] + pub transaction: Tx, + + #[n(1)] + pub witness: MaybeIndefArray, +} #[derive(Encode, Decode, Debug)] pub struct BlockBody { @@ -920,12 +966,10 @@ mod tests { let block = Block::decode_fragment(&bytes[..]) .expect(&format!("error decoding cbor for file {}", idx)); - let _bytes2 = + let bytes2 = to_vec(block).expect(&format!("error encoding block cbor for file {}", idx)); - // HACK: we ommit the ismorphic requirement until we find the - // offending difference - // assert_eq!(bytes, bytes2); + assert_eq!(hex::encode(bytes), hex::encode(bytes2)); } } @@ -940,8 +984,6 @@ mod tests { let block = BlockHead::decode_fragment(&bytes[..]) .expect(&format!("error decoding cbor for file {}", idx)); - println!("{:?}", block); - let bytes2 = to_vec(block).expect(&format!("error encoding header cbor for file {}", idx)); diff --git a/pallas-primitives/src/byron/test_data/test4.block b/pallas-primitives/src/byron/test_data/test4.block new file mode 100644 index 00000000..4e4831aa --- /dev/null +++ b/pallas-primitives/src/byron/test_data/test4.block @@ -0,0 +1 @@ +820183851a2d964a095820b5bdd15fbfbe7f618d2b7db0b20632658466b1b17654e9a8b865ec0e9fdccd4e8483015820c2c44f1f28599c08c6c1da0e375dc995d2b686c445b19110fa96ca68dfb2880e5820314b3c77650d6eba459864966dbcda18c438ee21bdd7e6b3272bf1e6b241d75d83025820d36a2619a672494604e11bb447cbcf5231e9f2ba25c2169177edc941bd50ad6c5820d36a2619a672494604e11bb447cbcf5231e9f2ba25c2169177edc941bd50ad6c5820afc0da64183bf2664f3d4eec7238d524ba607faeeab24fc100eb861dba69971b58204e66280cd94d591072349bec0a3090a53aa945562efb6d08d56e53654b0e4098848201195457584026566e86fc6b9b177c8480e275b2b112b573f6d073f9deea53b8d99c4ed976b335b2b3842f0e380001f090bc923caa9691ed9115e286da9421e2745c7acc87f18119a89f8202828400584026566e86fc6b9b177c8480e275b2b112b573f6d073f9deea53b8d99c4ed976b335b2b3842f0e380001f090bc923caa9691ed9115e286da9421e2745c7acc87f15840f14f712dc600d793052d4842d50cefa4e65884ea6cf83707079eb8ce302efc85dae922d5eb3838d2b91784f04824d26767bfb65bd36a36e74fec46d09d98858d58408ab43e904b06e799c1817c5ced4f3a7bbe15cdbf422dea9d2d5dc2c6105ce2f4d4c71e5d4779f6c44b770a133636109949e1f7786acb5a732bcdea0470fea4065840cfd641e91f908471af31762e7124147e8d3b27036d436f24d785de7330ebe33c03dae5ec27eb2944a82545fbcc30016737ba7696fb5e3fcaf8963ea12dbb87098483000000826a63617264616e6f2d736c01a058204ba92aa320c60acc9ad7b9a64f2eda55c4d2ec28e604faf186708b4f0c4e8edf849f82839f8200d81858248258200ca95f3bb516e3fa36b3c5ce18316a3d197b4faf2e36635baecae47e8a714b8d00ff9f8282d818584283581caca526063940ef762c899b92f20134264a43c5ab36d46cc6d36540d1a101581e581c2729cbfd641133bd0633ff422b246fa0d95cc2bef293d39adf3fd22b001aa25f5bd41b0000021a9f3ab0c28282d818584283581ce0751a974c40abdac7e71ee5b09d12b0591b1f4ecab73062ac8f96caa101581e581cca3e553c9c63c531002ff143535ea35088673bf86d25026baf12db3e001afdf29bac1a02625a00ffa0818200d81858858258408b9397dce473d3f296ad24e24e48795a495ad2f1602896e913fdc7e55e55e21f6fa53491a197e86428c86fadc0253ddec8d88bee623d474632603633643b26eb5840555e2f86fd803d1ed5bb3a3bc7f08dd82896744c4a0d99ce7b61f696ef632b12f6d8bd4787fcfe27de2bb7b1127fb1646d8d2f26755b4186f605210709016709ff8302a0d90102809fff82809fff81a0 \ No newline at end of file diff --git a/pallas-primitives/src/byron/time.rs b/pallas-primitives/src/byron/time.rs new file mode 100644 index 00000000..86218bcc --- /dev/null +++ b/pallas-primitives/src/byron/time.rs @@ -0,0 +1,47 @@ +use super::{EbbHead, SlotId}; + +// TODO: is it safe to hardcode these values? +const WELLKNOWN_SLOT_LENGTH: u64 = 20; // 20 secs +const WELLKNOWN_EPOCH_LENGTH: u64 = 5 * 24 * 60 * 60; // 5 days + +fn epoch_slot_to_absolute(epoch: u64, sub_epoch_slot: u64) -> u64 { + ((epoch * WELLKNOWN_EPOCH_LENGTH) / WELLKNOWN_SLOT_LENGTH) + sub_epoch_slot +} + +impl SlotId { + pub fn to_abs_slot(&self) -> u64 { + epoch_slot_to_absolute(self.epoch, self.slot) + } +} + +impl EbbHead { + pub fn to_abs_slot(&self) -> u64 { + epoch_slot_to_absolute(self.consensus_data.epoch_id, 0) + } +} + +#[cfg(test)] +mod tests { + use crate::byron::Block; + use crate::Fragment; + + #[test] + fn knwon_slot_matches() { + // TODO: expand this test to include more test blocks + let block_idx = 1; + let block_str = include_str!("test_data/test1.block"); + + let block_bytes = hex::decode(block_str).expect(&format!("bad block file {}", block_idx)); + let block = Block::decode_fragment(&block_bytes[..]) + .expect(&format!("error decoding cbor for file {}", block_idx)); + + let block = match block { + Block::MainBlock(x) => x, + Block::EbBlock(_) => panic!(), + }; + + let computed_slot = block.header.consensus_data.0.to_abs_slot(); + + assert_eq!(computed_slot, 4492794); + } +} diff --git a/pallas-primitives/src/utils.rs b/pallas-primitives/src/utils.rs index 4b8bcf92..b9e08e27 100644 --- a/pallas-primitives/src/utils.rs +++ b/pallas-primitives/src/utils.rs @@ -153,9 +153,10 @@ where MaybeIndefArray::Def(x) => { e.encode(x)?; } - MaybeIndefArray::Indef(x) if x.is_empty() => { - e.encode(x)?; - } + // TODO: this seemed necesary on alonzo, but breaks on byron. We need to double check. + //MaybeIndefArray::Indef(x) if x.is_empty() => { + // e.encode(x)?; + //} MaybeIndefArray::Indef(x) => { e.begin_array()?; @@ -223,7 +224,7 @@ where /// Wraps a struct so that it is encoded/decoded as a cbor bytes #[derive(Debug)] -pub struct CborWrap(T); +pub struct CborWrap(pub T); impl<'b, T> minicbor::Decode<'b> for CborWrap where @@ -257,6 +258,14 @@ where } } +impl Deref for CborWrap { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + #[derive(Debug)] pub struct TagWrap(I);