feat(primitives): Improve ergonomics for Byron primitives (#47)

* feat(primitives): Improve ergonomics for Byron primitives
* fix(ci): Fix tests
This commit is contained in:
Santiago Carmuega 2022-02-12 19:44:32 -03:00 committed by GitHub
parent 82c581f3a3
commit c3662e199d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 293 additions and 45 deletions

View file

@ -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"

View file

@ -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<String, Error> {
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
);
}
}
}
}

View file

@ -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))
}
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))
}
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 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_transaction(data: &TransactionBody) -> Hash<32> {
// Hasher::<256>::hash_cbor(data)
//}
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))
}
}
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(),
}
}
}
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)
}

View file

@ -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<u64, Error> {
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<u64, Error> {
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);
}
}
}

View file

@ -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;

View file

@ -173,13 +173,39 @@ impl minicbor::Encode for AddrAttrProperty {
pub type AddrAttr = OrderPreservingProperties<AddrAttrProperty>;
#[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<AddressPayload>,
#[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<TxIn>, Vec<TxOut>, Attributes);
#[derive(Debug, Encode, Decode)]
pub struct Tx {
#[n(0)]
pub inputs: MaybeIndefArray<TxIn>,
#[n(1)]
pub outputs: MaybeIndefArray<TxOut>,
#[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<W>,
) -> Result<(), minicbor::encode::Error<W::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<ByteVec>);
pub type VssProof = (ByteVec, ByteVec, ByteVec, MaybeIndefArray<ByteVec>);
//ssccomm = [pubkey, [{vsspubkey => vssenc},vssproof], signature]
pub type SscComm = (
@ -781,7 +820,14 @@ pub struct BlockHead {
}
// [tx, [* twit]]
pub type TxPayload = (Tx, Vec<Twit>);
#[derive(Debug, Encode, Decode)]
pub struct TxPayload {
#[n(0)]
pub transaction: Tx,
#[n(1)]
pub witness: MaybeIndefArray<Twit>,
}
#[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));

View file

@ -0,0 +1 @@
820183851a2d964a095820b5bdd15fbfbe7f618d2b7db0b20632658466b1b17654e9a8b865ec0e9fdccd4e8483015820c2c44f1f28599c08c6c1da0e375dc995d2b686c445b19110fa96ca68dfb2880e5820314b3c77650d6eba459864966dbcda18c438ee21bdd7e6b3272bf1e6b241d75d83025820d36a2619a672494604e11bb447cbcf5231e9f2ba25c2169177edc941bd50ad6c5820d36a2619a672494604e11bb447cbcf5231e9f2ba25c2169177edc941bd50ad6c5820afc0da64183bf2664f3d4eec7238d524ba607faeeab24fc100eb861dba69971b58204e66280cd94d591072349bec0a3090a53aa945562efb6d08d56e53654b0e4098848201195457584026566e86fc6b9b177c8480e275b2b112b573f6d073f9deea53b8d99c4ed976b335b2b3842f0e380001f090bc923caa9691ed9115e286da9421e2745c7acc87f18119a89f8202828400584026566e86fc6b9b177c8480e275b2b112b573f6d073f9deea53b8d99c4ed976b335b2b3842f0e380001f090bc923caa9691ed9115e286da9421e2745c7acc87f15840f14f712dc600d793052d4842d50cefa4e65884ea6cf83707079eb8ce302efc85dae922d5eb3838d2b91784f04824d26767bfb65bd36a36e74fec46d09d98858d58408ab43e904b06e799c1817c5ced4f3a7bbe15cdbf422dea9d2d5dc2c6105ce2f4d4c71e5d4779f6c44b770a133636109949e1f7786acb5a732bcdea0470fea4065840cfd641e91f908471af31762e7124147e8d3b27036d436f24d785de7330ebe33c03dae5ec27eb2944a82545fbcc30016737ba7696fb5e3fcaf8963ea12dbb87098483000000826a63617264616e6f2d736c01a058204ba92aa320c60acc9ad7b9a64f2eda55c4d2ec28e604faf186708b4f0c4e8edf849f82839f8200d81858248258200ca95f3bb516e3fa36b3c5ce18316a3d197b4faf2e36635baecae47e8a714b8d00ff9f8282d818584283581caca526063940ef762c899b92f20134264a43c5ab36d46cc6d36540d1a101581e581c2729cbfd641133bd0633ff422b246fa0d95cc2bef293d39adf3fd22b001aa25f5bd41b0000021a9f3ab0c28282d818584283581ce0751a974c40abdac7e71ee5b09d12b0591b1f4ecab73062ac8f96caa101581e581cca3e553c9c63c531002ff143535ea35088673bf86d25026baf12db3e001afdf29bac1a02625a00ffa0818200d81858858258408b9397dce473d3f296ad24e24e48795a495ad2f1602896e913fdc7e55e55e21f6fa53491a197e86428c86fadc0253ddec8d88bee623d474632603633643b26eb5840555e2f86fd803d1ed5bb3a3bc7f08dd82896744c4a0d99ce7b61f696ef632b12f6d8bd4787fcfe27de2bb7b1127fb1646d8d2f26755b4186f605210709016709ff8302a0d90102809fff82809fff81a0

View file

@ -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);
}
}

View file

@ -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>(T);
pub struct CborWrap<T>(pub T);
impl<'b, T> minicbor::Decode<'b> for CborWrap<T>
where
@ -257,6 +258,14 @@ where
}
}
impl<T> Deref for CborWrap<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug)]
pub struct TagWrap<I, const T: u64>(I);