diff --git a/.gitignore b/.gitignore index e5300442..0fef8b69 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ Cargo.lock scratchpad .DS_Store -RELEASE.md \ No newline at end of file +RELEASE.md +.idea diff --git a/pallas-codec/src/utils.rs b/pallas-codec/src/utils.rs index c487bb77..a98ebe22 100644 --- a/pallas-codec/src/utils.rs +++ b/pallas-codec/src/utils.rs @@ -229,7 +229,7 @@ where } /// Wraps a struct so that it is encoded/decoded as a cbor bytes -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, PartialOrd)] pub struct CborWrap(pub T); impl<'b, C, T> minicbor::Decode<'b, C> for CborWrap diff --git a/pallas-miniprotocols/src/handshake/n2n.rs b/pallas-miniprotocols/src/handshake/n2n.rs index 5de76215..e9cad835 100644 --- a/pallas-miniprotocols/src/handshake/n2n.rs +++ b/pallas-miniprotocols/src/handshake/n2n.rs @@ -8,6 +8,9 @@ const PROTOCOL_V4: u64 = 4; const PROTOCOL_V5: u64 = 5; const PROTOCOL_V6: u64 = 6; const PROTOCOL_V7: u64 = 7; +const PROTOCOL_V8: u64 = 8; +const PROTOCOL_V9: u64 = 9; +const PROTOCOL_V10: u64 = 10; impl VersionTable { pub fn v4_and_above(network_magic: u64) -> VersionTable { @@ -16,6 +19,9 @@ impl VersionTable { (PROTOCOL_V5, VersionData::new(network_magic, false)), (PROTOCOL_V6, VersionData::new(network_magic, false)), (PROTOCOL_V7, VersionData::new(network_magic, false)), + (PROTOCOL_V8, VersionData::new(network_magic, false)), + (PROTOCOL_V9, VersionData::new(network_magic, false)), + (PROTOCOL_V10, VersionData::new(network_magic, false)), ] .into_iter() .collect::>(); @@ -27,6 +33,22 @@ impl VersionTable { let values = vec![ (PROTOCOL_V6, VersionData::new(network_magic, false)), (PROTOCOL_V7, VersionData::new(network_magic, false)), + (PROTOCOL_V8, VersionData::new(network_magic, false)), + (PROTOCOL_V9, VersionData::new(network_magic, false)), + (PROTOCOL_V10, VersionData::new(network_magic, false)), + ] + .into_iter() + .collect::>(); + + VersionTable { values } + } + + pub fn v7_and_above(network_magic: u64) -> VersionTable { + let values = vec![ + (PROTOCOL_V7, VersionData::new(network_magic, false)), + (PROTOCOL_V8, VersionData::new(network_magic, false)), + (PROTOCOL_V9, VersionData::new(network_magic, false)), + (PROTOCOL_V10, VersionData::new(network_magic, false)), ] .into_iter() .collect::>(); diff --git a/pallas-multiplexer/src/bearers.rs b/pallas-multiplexer/src/bearers.rs index ea3c91ea..6a746618 100644 --- a/pallas-multiplexer/src/bearers.rs +++ b/pallas-multiplexer/src/bearers.rs @@ -1,13 +1,14 @@ use byteorder::{ByteOrder, NetworkEndian, WriteBytesExt}; use log::{debug, log_enabled, trace}; use std::io::{Read, Write}; -use std::net::{TcpListener, ToSocketAddrs}; +use std::net::{SocketAddr, TcpListener, ToSocketAddrs}; use std::{net::TcpStream, time::Instant}; use crate::Payload; #[cfg(target_family = "unix")] use std::os::unix::net::UnixStream; +use std::time::Duration; pub struct Segment { pub protocol: u16, @@ -110,6 +111,16 @@ impl Bearer { Ok(Bearer::Tcp(bearer)) } + pub fn connect_tcp_timeout( + addr: &SocketAddr, + timeout: Duration, + ) -> Result { + let bearer = TcpStream::connect_timeout(addr, timeout)?; + bearer.set_nodelay(true)?; + + Ok(Bearer::Tcp(bearer)) + } + pub fn accept_tcp(server: TcpListener) -> Result { let (bearer, _) = server.accept().unwrap(); bearer.set_nodelay(true)?; diff --git a/pallas-primitives/src/alonzo/defs.cddl b/pallas-primitives/src/alonzo/defs.cddl new file mode 100644 index 00000000..ffb591cc --- /dev/null +++ b/pallas-primitives/src/alonzo/defs.cddl @@ -0,0 +1,394 @@ +block = + [ header + , transaction_bodies : [* transaction_body] + , transaction_witness_sets : [* transaction_witness_set] + , auxiliary_data_set : {* transaction_index => auxiliary_data } + , invalid_transactions : [* transaction_index ] + ]; Valid blocks must also satisfy the following two constraints: + ; 1) the length of transaction_bodies and transaction_witness_sets + ; must be the same + ; 2) every transaction_index must be strictly smaller than the + ; length of transaction_bodies + +transaction = + [ transaction_body + , transaction_witness_set + , bool + , auxiliary_data / null + ] + +transaction_index = uint .size 2 + +header = + [ header_body + , body_signature : $kes_signature + ] + +header_body = + [ block_number : uint + , slot : uint + , prev_hash : $hash32 / null + , issuer_vkey : $vkey + , vrf_vkey : $vrf_vkey + , nonce_vrf : $vrf_cert + , leader_vrf : $vrf_cert + , block_body_size : uint + , block_body_hash : $hash32 ; merkle triple root + , operational_cert + , protocol_version + ] + +operational_cert = + ( hot_vkey : $kes_vkey + , sequence_number : uint + , kes_period : uint + , sigma : $signature + ) + +protocol_version = (uint, uint) + +transaction_body = + { 0 : set ; inputs + , 1 : [* transaction_output] + , 2 : coin ; fee + , ? 3 : uint ; time to live + , ? 4 : [* certificate] + , ? 5 : withdrawals + , ? 6 : update + , ? 7 : auxiliary_data_hash + , ? 8 : uint ; validity interval start + , ? 9 : mint + , ? 11 : script_data_hash + , ? 13 : set ; collateral inputs + , ? 14 : required_signers + , ? 15 : network_id + } + +required_signers = set<$addr_keyhash> + +transaction_input = [ transaction_id : $hash32 + , index : uint + ] + +transaction_output = + [ address + , amount : value + , ? datum_hash : $hash32 + ] + +script_data_hash = $hash32 +; This is a hash of data which may affect evaluation of a script. +; This data consists of: +; - The redeemers from the transaction_witness_set (the value of field 5). +; - The datums from the transaction_witness_set (the value of field 4). +; - The value in the costmdls map corresponding to the script's language +; (in field 18 of protocol_param_update.) +; (In the future it may contain additional protocol parameters.) +; +; Since this data does not exist in contiguous form inside a transaction, it needs +; to be independently constructed by each recipient. +; +; script data format: +; [ redeemers | datums | language views ] +; The redeemers are exactly the data present in the transaction witness set. +; Similarly for the datums, if present. If no datums are provided, the middle +; field is an empty string. +; +; language views CDDL: +; { * language => script_integrity_data } +; +; This must be encoded canonically, using the same scheme as in +; RFC7049 section 3.9: +; - Maps, strings, and bytestrings must use a definite-length encoding +; - Integers must be as small as possible. +; - The expressions for map length, string length, and bytestring length +; must be as short as possible. +; - The keys in the map must be sorted as follows: +; - If two keys have different lengths, the shorter one sorts earlier. +; - If two keys have the same length, the one with the lower value +; in (byte-wise) lexical order sorts earlier. +; +; For PlutusV1 (language id 0), the language view is the following: +; - the value of costmdls map at key 0 is encoded as an indefinite length +; list and the result is encoded as a bytestring. (our apologies) +; - the language ID tag is also encoded twice. first as a uint then as +; a bytestring. (our apologies) +; +; If there is no value for key 0, then the corresponding scripts cannot execute. +; Regardless of what the script integrity data is. +; +; Finally, note that in the case that a transaction includes datums but does not +; include any redeemers, the script data format becomes (in hex): +; [ 80 | datums | A0 ] +; corresponding to a CBOR empty list and an empty map. + +; address = bytes +; reward_account = bytes + +; address format: +; [ 8 bit header | payload ]; +; +; shelley payment addresses: +; bit 7: 0 +; bit 6: base/other +; bit 5: pointer/enterprise [for base: stake cred is keyhash/scripthash] +; bit 4: payment cred is keyhash/scripthash +; bits 3-0: network id +; +; reward addresses: +; bits 7-5: 111 +; bit 4: credential is keyhash/scripthash +; bits 3-0: network id +; +; byron addresses: +; bits 7-4: 1000 + +; 0000: base address: keyhash28,keyhash28 +; 0001: base address: scripthash28,keyhash28 +; 0010: base address: keyhash28,scripthash28 +; 0011: base address: scripthash28,scripthash28 +; 0100: pointer address: keyhash28, 3 variable length uint +; 0101: pointer address: scripthash28, 3 variable length uint +; 0110: enterprise address: keyhash28 +; 0111: enterprise address: scripthash28 +; 1000: byron address +; 1110: reward account: keyhash28 +; 1111: reward account: scripthash28 +; 1001 - 1101: future formats + +certificate = + [ stake_registration + // stake_deregistration + // stake_delegation + // pool_registration + // pool_retirement + // genesis_key_delegation + // move_instantaneous_rewards_cert + ] + +stake_registration = (0, stake_credential) +stake_deregistration = (1, stake_credential) +stake_delegation = (2, stake_credential, pool_keyhash) +pool_registration = (3, pool_params) +pool_retirement = (4, pool_keyhash, epoch) +genesis_key_delegation = (5, genesishash, genesis_delegate_hash, vrf_keyhash) +move_instantaneous_rewards_cert = (6, move_instantaneous_reward) + +move_instantaneous_reward = [ 0 / 1, { * stake_credential => delta_coin } / coin ] +; The first field determines where the funds are drawn from. +; 0 denotes the reserves, 1 denotes the treasury. +; If the second field is a map, funds are moved to stake credentials, +; otherwise the funds are given to the other accounting pot. + +delta_coin = int + +stake_credential = + [ 0, addr_keyhash + // 1, scripthash + ] + +pool_params = ( operator: pool_keyhash + , vrf_keyhash: vrf_keyhash + , pledge: coin + , cost: coin + , margin: unit_interval + , reward_account: reward_account + , pool_owners: set + , relays: [* relay] + , pool_metadata: pool_metadata / null + ) + +port = uint .le 65535 +ipv4 = bytes .size 4 +ipv6 = bytes .size 16 +dns_name = tstr .size (0..64) + +single_host_addr = ( 0 + , port / null + , ipv4 / null + , ipv6 / null + ) +single_host_name = ( 1 + , port / null + , dns_name ; An A or AAAA DNS record + ) +multi_host_name = ( 2 + , dns_name ; A SRV DNS record + ) +relay = + [ single_host_addr + // single_host_name + // multi_host_name + ] + +pool_metadata = [url, pool_metadata_hash] +url = tstr .size (0..64) + +withdrawals = { * reward_account => coin } + +update = [ proposed_protocol_parameter_updates + , epoch + ] + +proposed_protocol_parameter_updates = + { * genesishash => protocol_param_update } + +protocol_param_update = + { ? 0: uint ; minfee A + , ? 1: uint ; minfee B + , ? 2: uint ; max block body size + , ? 3: uint ; max transaction size + , ? 4: uint ; max block header size + , ? 5: coin ; key deposit + , ? 6: coin ; pool deposit + , ? 7: epoch ; maximum epoch + , ? 8: uint ; n_opt: desired number of stake pools + , ? 9: rational ; pool pledge influence + , ? 10: unit_interval ; expansion rate + , ? 11: unit_interval ; treasury growth rate + , ? 12: unit_interval ; d. decentralization constant + , ? 13: $nonce ; extra entropy + , ? 14: [protocol_version] ; protocol version + , ? 16: coin ; min pool cost + , ? 17: coin ; ada per utxo byte + , ? 18: costmdls ; cost models for script languages + , ? 19: ex_unit_prices ; execution costs + , ? 20: ex_units ; max tx ex units + , ? 21: ex_units ; max block ex units + , ? 22: uint ; max value size + , ? 23: uint ; collateral percentage + , ? 24: uint ; max collateral inputs + } + +transaction_witness_set = + { ? 0: [* vkeywitness ] + , ? 1: [* native_script ] + , ? 2: [* bootstrap_witness ] + , ? 3: [* plutus_script ] + , ? 4: [* plutus_data ] + , ? 5: [* redeemer ] + } + +plutus_script = bytes + +plutus_data = + constr + / { * plutus_data => plutus_data } + / [ * plutus_data ] + / big_int + / bounded_bytes + +big_int = int / big_uint / big_nint +big_uint = #6.2(bounded_bytes) +big_nint = #6.3(bounded_bytes) + +constr = + #6.121([* a]) + / #6.122([* a]) + / #6.123([* a]) + / #6.124([* a]) + / #6.125([* a]) + / #6.126([* a]) + / #6.127([* a]) + ; similarly for tag range: 6.1280 .. 6.1400 inclusive + / #6.102([uint, [* a]]) + +redeemer = [ tag: redeemer_tag, index: uint, data: plutus_data, ex_units: ex_units ] +redeemer_tag = + 0 ; inputTag "Spend" + / 1 ; mintTag "Mint" + / 2 ; certTag "Cert" + / 3 ; wdrlTag "Reward" +ex_units = [mem: uint, steps: uint] + +ex_unit_prices = + [ mem_price: sub_coin, step_price: sub_coin ] + +; This is an enumeration. for now there's only one value ; New +language = 0 ; Plutus v1 + +costmdls = { * language => cost_model } ; New + +; The keys to the cost model map are not present in the serialization. +; The values in the serialization are assumed to be ordered +; lexicographically by their correpsonding key value. +; The key values are listed in sorted_cost_model_keys.txt. +cost_model = [ 166*166 int ] ; New + +transaction_metadatum = + { * transaction_metadatum => transaction_metadatum } + / [ * transaction_metadatum ] + / int + / bytes .size (0..64) + / text .size (0..64) + +transaction_metadatum_label = uint +metadata = { * transaction_metadatum_label => transaction_metadatum } + +auxiliary_data = + metadata ; Shelley + / [ transaction_metadata: metadata ; Shelley-ma + , auxiliary_scripts: [ * native_script ] + ] + / #6.259({ ? 0 => metadata ; Alonzo and beyond + , ? 1 => [ * native_script ] + , ? 2 => [ * plutus_script ] + }) + + + +vkeywitness = [ $vkey, $signature ] + +bootstrap_witness = + [ public_key : $vkey + , signature : $signature + , chain_code : bytes .size 32 + , attributes : bytes + ] + +native_script = + [ script_pubkey + // script_all + // script_any + // script_n_of_k + // invalid_before + ; Timelock validity intervals are half-open intervals [a, b). + ; This field specifies the left (included) endpoint a. + // invalid_hereafter + ; Timelock validity intervals are half-open intervals [a, b). + ; This field specifies the right (excluded) endpoint b. + ] + +script_pubkey = (0, addr_keyhash) +script_all = (1, [ * native_script ]) +script_any = (2, [ * native_script ]) +script_n_of_k = (3, n: uint, [ * native_script ]) +invalid_before = (4, uint) +invalid_hereafter = (5, uint) + +coin = uint + +sub_coin = positive_interval + +multiasset = { * policy_id => { * asset_name => a } } +policy_id = scripthash +asset_name = bytes .size (0..32) + +value = coin / [coin,multiasset] +mint = multiasset + +int64 = -9223372036854775808 .. 9223372036854775807 + +network_id = 0 / 1 + +epoch = uint + +addr_keyhash = $hash28 +scripthash = $hash28 +genesis_delegate_hash = $hash28 +pool_keyhash = $hash28 +genesishash = $hash28 + +vrf_keyhash = $hash32 +auxiliary_data_hash = $hash32 +pool_metadata_hash = $hash32 \ No newline at end of file diff --git a/pallas-primitives/src/alonzo/model.rs b/pallas-primitives/src/alonzo/model.rs index dfd4e990..91bed21c 100644 --- a/pallas-primitives/src/alonzo/model.rs +++ b/pallas-primitives/src/alonzo/model.rs @@ -43,24 +43,26 @@ pub struct HeaderBody { pub block_body_hash: Hash<32>, #[n(9)] - pub operational_cert: ByteVec, + pub operational_cert_hot_vkey: ByteVec, #[n(10)] - pub unknown_0: u64, + pub operational_cert_sequence_number: u64, #[n(11)] - pub unknown_1: u64, + pub operational_cert_kes_period: u64, #[n(12)] - pub unknown_2: ByteVec, + pub operational_cert_sigma: ByteVec, #[n(13)] - pub protocol_version_major: u64, + pub protocol_major: u64, #[n(14)] - pub protocol_version_minor: u64, + pub protocol_minor: u64, } +pub type ProtocolVersion = (u64, u64); + #[derive(Encode, Decode, Debug, PartialEq)] pub struct KesSignature {} @@ -634,8 +636,6 @@ pub type CostModel = MaybeIndefArray; pub type CostMdls = KeyValuePairs; -pub type ProtocolVersion = (u32, u32); - #[derive(Encode, Decode, Debug, PartialEq, Clone)] #[cbor(map)] pub struct ProtocolParamUpdate { @@ -1146,11 +1146,13 @@ pub struct TransactionWitnessSet { #[derive(Encode, Decode, Debug, PartialEq, Clone)] #[cbor(map)] -pub struct AlonzoAuxiliaryData { +pub struct PostAlonzoAuxiliaryData { #[n(0)] pub metadata: Option, + #[n(1)] pub native_scripts: Option>, + #[n(2)] pub plutus_scripts: Option>, } @@ -1246,14 +1248,20 @@ pub type MetadatumLabel = AnyUInt; pub type Metadata = KeyValuePairs; +#[derive(Encode, Decode, Debug, PartialEq, Clone)] +pub struct ShelleyMaAuxiliaryDAta { + #[n(0)] + transaction_metadata: Metadata, + + #[n(1)] + auxiliary_scripts: Option>, +} + #[derive(Debug, PartialEq, Clone)] pub enum AuxiliaryData { Shelley(Metadata), - ShelleyMa { - transaction_metadata: Metadata, - auxiliary_scripts: Option>, - }, - Alonzo(AlonzoAuxiliaryData), + ShelleyMa(ShelleyMaAuxiliaryDAta), + PostAlonzo(PostAlonzoAuxiliaryData), } impl<'b, C> minicbor::Decode<'b, C> for AuxiliaryData { @@ -1262,18 +1270,10 @@ impl<'b, C> minicbor::Decode<'b, C> for AuxiliaryData { minicbor::data::Type::Map | minicbor::data::Type::MapIndef => { Ok(AuxiliaryData::Shelley(d.decode_with(ctx)?)) } - minicbor::data::Type::Array => { - d.array()?; - let transaction_metadata = d.decode_with(ctx)?; - let auxiliary_scripts = d.decode_with(ctx)?; - Ok(AuxiliaryData::ShelleyMa { - transaction_metadata, - auxiliary_scripts, - }) - } + minicbor::data::Type::Array => Ok(AuxiliaryData::ShelleyMa(d.decode_with(ctx)?)), minicbor::data::Type::Tag => { d.tag()?; - Ok(AuxiliaryData::Alonzo(d.decode_with(ctx)?)) + Ok(AuxiliaryData::PostAlonzo(d.decode_with(ctx)?)) } _ => Err(minicbor::decode::Error::message( "Can't infer variant from data type for AuxiliaryData", @@ -1292,15 +1292,10 @@ impl minicbor::Encode for AuxiliaryData { AuxiliaryData::Shelley(m) => { e.encode_with(m, ctx)?; } - AuxiliaryData::ShelleyMa { - transaction_metadata, - auxiliary_scripts, - } => { - e.array(2)?; - e.encode_with(transaction_metadata, ctx)?; - e.encode_with(auxiliary_scripts, ctx)?; + AuxiliaryData::ShelleyMa(m) => { + e.encode_with(m, ctx)?; } - AuxiliaryData::Alonzo(v) => { + AuxiliaryData::PostAlonzo(v) => { // TODO: check if this is the correct tag e.tag(Tag::Unassigned(259))?; e.encode_with(v, ctx)?; diff --git a/pallas-primitives/src/babbage/address.rs b/pallas-primitives/src/babbage/address.rs new file mode 100644 index 00000000..49243e1b --- /dev/null +++ b/pallas-primitives/src/babbage/address.rs @@ -0,0 +1,57 @@ +use crate::Error; + +use super::TransactionOutput; +use bech32::{self, ToBase32}; + +pub fn encode_bech32_address(data: &[u8], hrp: &str) -> Result { + bech32::encode(hrp, data.to_base32(), bech32::Variant::Bech32).map_err(|e| e.into()) +} + +impl TransactionOutput { + pub fn to_bech32_address(&self, hrp: &str) -> Result { + let address = match self { + TransactionOutput::Legacy(x) => &x.address, + TransactionOutput::PostAlonzo(x) => &x.address, + }; + + encode_bech32_address(address.as_slice(), hrp) + } +} + +#[cfg(test)] +mod tests { + use pallas_codec::minicbor; + + use crate::babbage::Block; + + type BlockWrapper = (u16, Block); + + const KNOWN_ADDRESSES: &[&str] = + &["addr_test1vpfwv0ezc5g8a4mkku8hhy3y3vp92t7s3ul8g778g5yegsgalc6gc"]; + + #[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/babbage1.block"); + + let block_bytes = hex::decode(block_str).expect(&format!("bad block file {}", block_idx)); + let (_, block): BlockWrapper = minicbor::decode(&block_bytes[..]) + .expect(&format!("error decoding cbor for file {}", block_idx)); + + // don't want to pass if we don't have tx in the block + assert!(block.transaction_bodies.len() > 0); + + for tx in block.transaction_bodies.iter() { + for output in tx.outputs.iter() { + let addr_str = output.to_bech32_address("addr_test").unwrap(); + + assert!( + KNOWN_ADDRESSES.contains(&addr_str.as_str()), + "address {} not in known list", + addr_str + ); + } + } + } +} diff --git a/pallas-primitives/src/babbage/crypto.rs b/pallas-primitives/src/babbage/crypto.rs new file mode 100644 index 00000000..87d8bab5 --- /dev/null +++ b/pallas-primitives/src/babbage/crypto.rs @@ -0,0 +1,58 @@ +use crate::ToHash; + +use super::{Header, PlutusV2Script, TransactionBody}; +use pallas_codec::utils::KeepRaw; +use pallas_crypto::hash::{Hash, Hasher}; + +impl ToHash<32> for Header { + fn to_hash(&self) -> pallas_crypto::hash::Hash<32> { + Hasher::<256>::hash_cbor(self) + } +} + +impl ToHash<28> for PlutusV2Script { + fn to_hash(&self) -> Hash<28> { + Hasher::<224>::hash_tagged_cbor(self, 1) + } +} + +impl ToHash<32> for TransactionBody { + fn to_hash(&self) -> Hash<32> { + Hasher::<256>::hash_cbor(self) + } +} + +impl ToHash<32> for KeepRaw<'_, TransactionBody> { + fn to_hash(&self) -> pallas_crypto::hash::Hash<32> { + Hasher::<256>::hash(self.raw_cbor()) + } +} + +#[cfg(test)] +mod tests { + use pallas_codec::minicbor; + + use crate::babbage::MintedBlock; + use crate::ToHash; + + type BlockWrapper<'b> = (u16, MintedBlock<'b>); + + #[test] + fn transaction_hash_works() { + // TODO: expand this test to include more test blocks + let block_idx = 1; + let block_str = include_str!("../../../test_data/babbage1.block"); + + let block_bytes = hex::decode(block_str).expect(&format!("bad block file {}", block_idx)); + let (_, block_model): BlockWrapper = minicbor::decode(&block_bytes[..]) + .expect(&format!("error decoding cbor for file {}", block_idx)); + + let valid_hashes = vec!["3fad302595665b004971a6b76909854a39a0a7ecdbff3692f37b77ae37dbe882"]; + + for (tx_idx, tx) in block_model.transaction_bodies.iter().enumerate() { + let computed_hash = tx.to_hash(); + let known_hash = valid_hashes[tx_idx]; + assert_eq!(hex::encode(computed_hash), known_hash) + } + } +} diff --git a/pallas-primitives/src/babbage/defs.cddl b/pallas-primitives/src/babbage/defs.cddl new file mode 100644 index 00000000..570f961f --- /dev/null +++ b/pallas-primitives/src/babbage/defs.cddl @@ -0,0 +1,412 @@ +block = + [ header + , transaction_bodies : [* transaction_body] + , transaction_witness_sets : [* transaction_witness_set] + , auxiliary_data_set : {* transaction_index => auxiliary_data } + , invalid_transactions : [* transaction_index ] + ]; Valid blocks must also satisfy the following two constraints: + ; 1) the length of transaction_bodies and transaction_witness_sets + ; must be the same + ; 2) every transaction_index must be strictly smaller than the + ; length of transaction_bodies + +transaction = + [ transaction_body + , transaction_witness_set + , bool + , auxiliary_data / null + ] + +transaction_index = uint .size 2 + +header = + [ header_body + , body_signature : $kes_signature + ] + +header_body = + [ block_number : uint + , slot : uint + , prev_hash : $hash32 / null + , issuer_vkey : $vkey + , vrf_vkey : $vrf_vkey + , vrf_result : $vrf_cert ; New, replaces nonce_vrf and leader_vrf + , block_body_size : uint + , block_body_hash : $hash32 ; merkle triple root + , operational_cert + , protocol_version + ] + +operational_cert = + ( hot_vkey : $kes_vkey + , sequence_number : uint + , kes_period : uint + , sigma : $signature + ) + +protocol_version = (uint, uint) + +transaction_body = + { 0 : set ; inputs + , 1 : [* transaction_output] + , 2 : coin ; fee + , ? 3 : uint ; time to live + , ? 4 : [* certificate] + , ? 5 : withdrawals + , ? 6 : update + , ? 7 : auxiliary_data_hash + , ? 8 : uint ; validity interval start + , ? 9 : mint + , ? 11 : script_data_hash + , ? 13 : set ; collateral inputs + , ? 14 : required_signers + , ? 15 : network_id + , ? 16 : transaction_output ; collateral return; New + , ? 17 : coin ; total collateral; New + , ? 18 : set ; reference inputs; New + } + +required_signers = set<$addr_keyhash> + +transaction_input = [ transaction_id : $hash32 + , index : uint + ] + +transaction_output = legacy_transaction_output / post_alonzo_transaction_output ; New + +legacy_transaction_output = + [ address + , amount : value + , ? datum_hash : $hash32 + ] + +post_alonzo_transaction_output = + { 0 : address + , 1 : value + , ? 2 : datum_option ; New; datum option + , ? 3 : script_ref ; New; script reference + } + +script_data_hash = $hash32 +; This is a hash of data which may affect evaluation of a script. +; This data consists of: +; - The redeemers from the transaction_witness_set (the value of field 5). +; - The datums from the transaction_witness_set (the value of field 4). +; - The value in the costmdls map corresponding to the script's language +; (in field 18 of protocol_param_update.) +; (In the future it may contain additional protocol parameters.) +; +; Since this data does not exist in contiguous form inside a transaction, it needs +; to be independently constructed by each recipient. +; +; script data format: +; [ redeemers | datums | language views ] +; The redeemers are exactly the data present in the transaction witness set. +; Similarly for the datums, if present. If no datums are provided, the middle +; field is an empty string. +; +; language views CDDL: +; { * language => script_integrity_data } +; +; This must be encoded canonically, using the same scheme as in +; RFC7049 section 3.9: +; - Maps, strings, and bytestrings must use a definite-length encoding +; - Integers must be as small as possible. +; - The expressions for map length, string length, and bytestring length +; must be as short as possible. +; - The keys in the map must be sorted as follows: +; - If two keys have different lengths, the shorter one sorts earlier. +; - If two keys have the same length, the one with the lower value +; in (byte-wise) lexical order sorts earlier. +; +; For PlutusV1 (language id 0), the language view is the following: +; - the value of costmdls map at key 0 is encoded as an indefinite length +; list and the result is encoded as a bytestring. (our apologies) +; - the language ID tag is also encoded twice. first as a uint then as +; a bytestring. (our apologies) +; For PlutusV2 (language id 1), the language view is the following: +; - the value of costmdls map at key 1 is encoded as an definite length list. +; +; If there is no value for key 0, then the corresponding scripts cannot execute. +; Regardless of what the script integrity data is. +; +; Finally, note that in the case that a transaction includes datums but does not +; include any redeemers, the script data format becomes (in hex): +; [ 80 | datums | A0 ] +; corresponding to a CBOR empty list and an empty map. + +; address = bytes +; reward_account = bytes + +; address format: +; [ 8 bit header | payload ]; +; +; shelley payment addresses: +; bit 7: 0 +; bit 6: base/other +; bit 5: pointer/enterprise [for base: stake cred is keyhash/scripthash] +; bit 4: payment cred is keyhash/scripthash +; bits 3-0: network id +; +; reward addresses: +; bits 7-5: 111 +; bit 4: credential is keyhash/scripthash +; bits 3-0: network id +; +; byron addresses: +; bits 7-4: 1000 + +; 0000: base address: keyhash28,keyhash28 +; 0001: base address: scripthash28,keyhash28 +; 0010: base address: keyhash28,scripthash28 +; 0011: base address: scripthash28,scripthash28 +; 0100: pointer address: keyhash28, 3 variable length uint +; 0101: pointer address: scripthash28, 3 variable length uint +; 0110: enterprise address: keyhash28 +; 0111: enterprise address: scripthash28 +; 1000: byron address +; 1110: reward account: keyhash28 +; 1111: reward account: scripthash28 +; 1001 - 1101: future formats + +certificate = + [ stake_registration + // stake_deregistration + // stake_delegation + // pool_registration + // pool_retirement + // genesis_key_delegation + // move_instantaneous_rewards_cert + ] + +stake_registration = (0, stake_credential) +stake_deregistration = (1, stake_credential) +stake_delegation = (2, stake_credential, pool_keyhash) +pool_registration = (3, pool_params) +pool_retirement = (4, pool_keyhash, epoch) +genesis_key_delegation = (5, genesishash, genesis_delegate_hash, vrf_keyhash) +move_instantaneous_rewards_cert = (6, move_instantaneous_reward) + +move_instantaneous_reward = [ 0 / 1, { * stake_credential => delta_coin } / coin ] +; The first field determines where the funds are drawn from. +; 0 denotes the reserves, 1 denotes the treasury. +; If the second field is a map, funds are moved to stake credentials, +; otherwise the funds are given to the other accounting pot. + +delta_coin = int + +stake_credential = + [ 0, addr_keyhash + // 1, scripthash + ] + +pool_params = ( operator: pool_keyhash + , vrf_keyhash: vrf_keyhash + , pledge: coin + , cost: coin + , margin: unit_interval + , reward_account: reward_account + , pool_owners: set + , relays: [* relay] + , pool_metadata: pool_metadata / null + ) + +port = uint .le 65535 +ipv4 = bytes .size 4 +ipv6 = bytes .size 16 +dns_name = tstr .size (0..64) + +single_host_addr = ( 0 + , port / null + , ipv4 / null + , ipv6 / null + ) +single_host_name = ( 1 + , port / null + , dns_name ; An A or AAAA DNS record + ) +multi_host_name = ( 2 + , dns_name ; A SRV DNS record + ) +relay = + [ single_host_addr + // single_host_name + // multi_host_name + ] + +pool_metadata = [url, pool_metadata_hash] +url = tstr .size (0..64) + +withdrawals = { * reward_account => coin } + +update = [ proposed_protocol_parameter_updates + , epoch + ] + +proposed_protocol_parameter_updates = + { * genesishash => protocol_param_update } + +protocol_param_update = + { ? 0: uint ; minfee A + , ? 1: uint ; minfee B + , ? 2: uint ; max block body size + , ? 3: uint ; max transaction size + , ? 4: uint ; max block header size + , ? 5: coin ; key deposit + , ? 6: coin ; pool deposit + , ? 7: epoch ; maximum epoch + , ? 8: uint ; n_opt: desired number of stake pools + , ? 9: rational ; pool pledge influence + , ? 10: unit_interval ; expansion rate + , ? 11: unit_interval ; treasury growth rate + , ? 14: [protocol_version] ; protocol version + , ? 16: coin ; min pool cost + , ? 17: coin ; ada per utxo byte + , ? 18: costmdls ; cost models for script languages + , ? 19: ex_unit_prices ; execution costs + , ? 20: ex_units ; max tx ex units + , ? 21: ex_units ; max block ex units + , ? 22: uint ; max value size + , ? 23: uint ; collateral percentage + , ? 24: uint ; max collateral inputs + } + +transaction_witness_set = + { ? 0: [* vkeywitness ] + , ? 1: [* native_script ] + , ? 2: [* bootstrap_witness ] + , ? 3: [* plutus_v1_script ] + , ? 4: [* plutus_data ] + , ? 5: [* redeemer ] + , ? 6: [* plutus_v2_script ] ; New + } + +plutus_v1_script = bytes +plutus_v2_script = bytes + +plutus_data = + constr + / { * plutus_data => plutus_data } + / [ * plutus_data ] + / big_int + / bounded_bytes + +big_int = int / big_uint / big_nint +big_uint = #6.2(bounded_bytes) +big_nint = #6.3(bounded_bytes) + +constr = + #6.121([* a]) + / #6.122([* a]) + / #6.123([* a]) + / #6.124([* a]) + / #6.125([* a]) + / #6.126([* a]) + / #6.127([* a]) + ; similarly for tag range: 6.1280 .. 6.1400 inclusive + / #6.102([uint, [* a]]) + +redeemer = [ tag: redeemer_tag, index: uint, data: plutus_data, ex_units: ex_units ] +redeemer_tag = + 0 ; inputTag "Spend" + / 1 ; mintTag "Mint" + / 2 ; certTag "Cert" + / 3 ; wdrlTag "Reward" +ex_units = [mem: uint, steps: uint] + +ex_unit_prices = + [ mem_price: sub_coin, step_price: sub_coin ] + +language = 0 ; Plutus v1 + / 1 ; Plutus v2 + +costmdls = + { ? 0 : [ 166*166 int ] ; Plutus v1 + , ? 1 : [ 175*175 int ] ; Plutus v2 + } + +transaction_metadatum = + { * transaction_metadatum => transaction_metadatum } + / [ * transaction_metadatum ] + / int + / bytes .size (0..64) + / text .size (0..64) + +transaction_metadatum_label = uint +metadata = { * transaction_metadatum_label => transaction_metadatum } + +auxiliary_data = + metadata ; Shelley + / [ transaction_metadata: metadata ; Shelley-ma + , auxiliary_scripts: [ * native_script ] + ] + / #6.259({ ? 0 => metadata ; Alonzo and beyond + , ? 1 => [ * native_script ] + , ? 2 => [ * plutus_v1_script ] + , ? 3 => [ * plutus_v2_script ] + }) + +vkeywitness = [ $vkey, $signature ] + +bootstrap_witness = + [ public_key : $vkey + , signature : $signature + , chain_code : bytes .size 32 + , attributes : bytes + ] + +native_script = + [ script_pubkey + // script_all + // script_any + // script_n_of_k + // invalid_before + ; Timelock validity intervals are half-open intervals [a, b). + ; This field specifies the left (included) endpoint a. + // invalid_hereafter + ; Timelock validity intervals are half-open intervals [a, b). + ; This field specifies the right (excluded) endpoint b. + ] + +script_pubkey = (0, addr_keyhash) +script_all = (1, [ * native_script ]) +script_any = (2, [ * native_script ]) +script_n_of_k = (3, n: uint, [ * native_script ]) +invalid_before = (4, uint) +invalid_hereafter = (5, uint) + +coin = uint + +sub_coin = positive_interval + +multiasset = { * policy_id => { * asset_name => a } } +policy_id = scripthash +asset_name = bytes .size (0..32) + +value = coin / [coin,multiasset] +mint = multiasset + +int64 = -9223372036854775808 .. 9223372036854775807 + +network_id = 0 / 1 + +epoch = uint + +addr_keyhash = $hash28 +scripthash = $hash28 +genesis_delegate_hash = $hash28 +pool_keyhash = $hash28 +genesishash = $hash28 + +vrf_keyhash = $hash32 +auxiliary_data_hash = $hash32 +pool_metadata_hash = $hash32 + +datum_hash = $hash32 +data = #6.24(bytes .cbor plutus_data) + +datum_option = [ 0, $hash32 // 1, data ] + +script_ref = #6.24(bytes .cbor script) + +script = [ 0, native_script // 1, plutus_v1_script // 2, plutus_v2_script ] \ No newline at end of file diff --git a/pallas-primitives/src/babbage/mod.rs b/pallas-primitives/src/babbage/mod.rs new file mode 100644 index 00000000..4a16d543 --- /dev/null +++ b/pallas-primitives/src/babbage/mod.rs @@ -0,0 +1,5 @@ +mod address; +mod crypto; +mod model; + +pub use model::*; diff --git a/pallas-primitives/src/babbage/model.rs b/pallas-primitives/src/babbage/model.rs new file mode 100644 index 00000000..8725f580 --- /dev/null +++ b/pallas-primitives/src/babbage/model.rs @@ -0,0 +1,607 @@ +//! Ledger primitives and cbor codec for the Alonzo era +//! +//! Handcrafted, idiomatic rust artifacts based on based on the [Babbage CDDL](https://github.com/input-output-hk/cardano-ledger/blob/master/eras/babbage/test-suite/cddl-files/babbage.cddl) file in IOHK repo. + +use pallas_codec::minicbor::{bytes::ByteVec, Decode, Encode}; +use pallas_crypto::hash::Hash; + +use pallas_codec::utils::{CborWrap, KeepRaw, KeyValuePairs, MaybeIndefArray}; + +// required for derive attrs to work +use pallas_codec::minicbor; + +pub use crate::alonzo::VrfCert; + +#[derive(Encode, Decode, Debug, PartialEq, Clone)] +pub struct HeaderBody { + #[n(0)] + pub block_number: u64, + + #[n(1)] + pub slot: u64, + + #[n(2)] + pub prev_hash: Hash<32>, + + #[n(3)] + pub issuer_vkey: ByteVec, + + #[n(4)] + pub vrf_vkey: ByteVec, + + #[n(5)] + pub vrf_result: VrfCert, + + #[n(6)] + pub block_body_size: u64, + + #[n(7)] + pub block_body_hash: Hash<32>, + + #[n(8)] + pub operational_cert: OperationalCert, + + #[n(9)] + pub protocol_version: ProtocolVersion, +} + +#[derive(Encode, Decode, Debug, Clone, PartialEq, PartialOrd)] +pub struct OperationalCert { + #[n(0)] + pub operational_cert_hot_vkey: ByteVec, + + #[n(1)] + pub operational_cert_sequence_number: u64, + + #[n(2)] + pub operational_cert_kes_period: u64, + + #[n(3)] + pub operational_cert_sigma: ByteVec, +} + +pub use crate::alonzo::ProtocolVersion; + +pub use crate::alonzo::KesSignature; + +#[derive(Encode, Decode, Debug, PartialEq, Clone)] +pub struct Header { + #[n(0)] + pub header_body: HeaderBody, + + #[n(1)] + pub body_signature: ByteVec, +} + +pub use crate::alonzo::TransactionInput; + +pub use crate::alonzo::NonceVariant; + +pub use crate::alonzo::Nonce; + +pub use crate::alonzo::ScriptHash; + +pub use crate::alonzo::PolicyId; + +pub use crate::alonzo::AssetName; + +pub use crate::alonzo::Multiasset; + +pub use crate::alonzo::Mint; + +pub use crate::alonzo::Coin; + +pub use crate::alonzo::Value; + +pub use crate::alonzo::TransactionOutput as LegacyTransacionOutput; + +pub use crate::alonzo::PoolKeyhash; + +pub use crate::alonzo::Epoch; + +pub use crate::alonzo::Genesishash; + +pub use crate::alonzo::GenesisDelegateHash; + +pub use crate::alonzo::VrfKeyhash; + +pub use crate::alonzo::InstantaneousRewardSource; + +pub use crate::alonzo::InstantaneousRewardTarget; + +pub use crate::alonzo::MoveInstantaneousReward; + +pub use crate::alonzo::RewardAccount; + +pub use crate::alonzo::Port; + +pub use crate::alonzo::IPv4; + +pub use crate::alonzo::IPv6; + +pub use crate::alonzo::DnsName; + +pub use crate::alonzo::Relay; + +pub use crate::alonzo::PoolMetadataHash; + +pub use crate::alonzo::PoolMetadata; + +pub use crate::alonzo::AddrKeyhash; + +pub use crate::alonzo::Scripthash; + +pub use crate::alonzo::RationalNumber; + +pub use crate::alonzo::UnitInterval; + +pub use crate::alonzo::PositiveInterval; + +pub use crate::alonzo::StakeCredential; + +pub use crate::alonzo::Certificate; + +pub use crate::alonzo::NetworkId; + +#[derive(Encode, Decode, Debug, PartialEq, Clone)] +#[cbor(index_only)] +pub enum Language { + #[n(0)] + PlutusV1, + + #[n(1)] + PlutusV2, +} + +pub use crate::alonzo::CostModel; + +#[derive(Encode, Decode, Debug, PartialEq, Clone)] +#[cbor(map)] +pub struct CostMdls { + #[n(0)] + pub plutus_v1: CostModel, + + #[n(1)] + pub plutus_v2: CostModel, +} + +#[derive(Encode, Decode, Debug, PartialEq, Clone)] +#[cbor(map)] +pub struct ProtocolParamUpdate { + #[n(0)] + pub minfee_a: Option, + #[n(1)] + pub minfee_b: Option, + #[n(2)] + pub max_block_body_size: Option, + #[n(3)] + pub max_transaction_size: Option, + #[n(4)] + pub max_block_header_size: Option, + #[n(5)] + pub key_deposit: Option, + #[n(6)] + pub pool_deposit: Option, + #[n(7)] + pub maximum_epoch: Option, + #[n(8)] + pub desired_number_of_stake_pools: Option, + #[n(9)] + pub pool_pledge_influence: Option, + #[n(10)] + pub expansion_rate: Option, + #[n(11)] + pub treasury_growth_rate: Option, + + #[n(14)] + pub protocol_version: Option, + #[n(16)] + pub min_pool_cost: Option, + #[n(17)] + pub ada_per_utxo_byte: Option, + #[n(18)] + pub cost_models_for_script_languages: Option, + #[n(19)] + pub execution_costs: Option, + #[n(20)] + pub max_tx_ex_units: Option, + #[n(21)] + pub max_block_ex_units: Option, + #[n(22)] + pub max_value_size: Option, + #[n(23)] + pub collateral_percentage: Option, + #[n(24)] + pub max_collateral_inputs: Option, +} + +#[derive(Encode, Decode, Debug, PartialEq, Clone)] +pub struct Update { + #[n(0)] + pub proposed_protocol_parameter_updates: KeyValuePairs, + + #[n(1)] + pub epoch: Epoch, +} + +#[derive(Encode, Decode, Debug, PartialEq, Clone)] +#[cbor(map)] +pub struct TransactionBody { + #[n(0)] + pub inputs: MaybeIndefArray, + + #[n(1)] + pub outputs: MaybeIndefArray, + + #[n(2)] + pub fee: u64, + + #[n(3)] + pub ttl: Option, + + #[n(4)] + pub certificates: Option>, + + #[n(5)] + pub withdrawals: Option>, + + #[n(6)] + pub update: Option, + + #[n(7)] + pub auxiliary_data_hash: Option, + + #[n(8)] + pub validity_interval_start: Option, + + #[n(9)] + pub mint: Option>, + + #[n(11)] + pub script_data_hash: Option>, + + #[n(13)] + pub collateral: Option>, + + #[n(14)] + pub required_signers: Option>, + + #[n(15)] + pub network_id: Option, + + #[n(16)] + pub collateral_return: Option, + + #[n(17)] + pub total_collateral: Option, + + #[n(18)] + pub reference_inputs: Option>, +} + +#[derive(Debug, PartialEq, Clone)] +pub enum TransactionOutput { + Legacy(LegacyTransacionOutput), + PostAlonzo(PostAlonzoTransactionOutput), +} + +impl<'b, C> minicbor::Decode<'b, C> for TransactionOutput { + fn decode( + d: &mut minicbor::Decoder<'b>, + _ctx: &mut C, + ) -> Result { + match d.datatype()? { + minicbor::data::Type::Array | minicbor::data::Type::ArrayIndef => { + Ok(TransactionOutput::Legacy(d.decode()?)) + } + minicbor::data::Type::Map | minicbor::data::Type::MapIndef => { + Ok(TransactionOutput::PostAlonzo(d.decode()?)) + } + _ => Err(minicbor::decode::Error::message( + "invalid type for transaction output struct", + )), + } + } +} + +impl minicbor::Encode for TransactionOutput { + fn encode( + &self, + e: &mut minicbor::Encoder, + ctx: &mut C, + ) -> Result<(), minicbor::encode::Error> { + match self { + TransactionOutput::Legacy(x) => x.encode(e, ctx), + TransactionOutput::PostAlonzo(x) => x.encode(e, ctx), + } + } +} + +#[derive(Encode, Decode, Debug, PartialEq, Clone)] +#[cbor(map)] +pub struct PostAlonzoTransactionOutput { + #[n(0)] + pub address: ByteVec, + + #[n(1)] + pub value: Value, + + #[n(2)] + pub datum_option: Option, + + #[n(3)] + pub script_ref: Option, +} + +pub use crate::alonzo::VKeyWitness; + +pub use crate::alonzo::NativeScript; + +pub use crate::alonzo::PlutusScript as PlutusV1Script; + +#[derive(Encode, Decode, Debug, PartialEq, Clone)] +#[cbor(transparent)] +pub struct PlutusV2Script(#[n(0)] pub ByteVec); + +impl AsRef<[u8]> for PlutusV2Script { + fn as_ref(&self) -> &[u8] { + self.0.as_slice() + } +} + +pub use crate::alonzo::BigInt; + +pub use crate::alonzo::PlutusData; + +pub use crate::alonzo::Constr; + +pub use crate::alonzo::ExUnits; + +pub use crate::alonzo::ExUnitPrices; + +pub use crate::alonzo::RedeemerTag; + +pub use crate::alonzo::Redeemer; + +pub use crate::alonzo::BootstrapWitness; + +#[derive(Encode, Decode, Debug, PartialEq, Clone)] +#[cbor(map)] +pub struct TransactionWitnessSet { + #[n(0)] + pub vkeywitness: Option>, + + #[n(1)] + pub native_script: Option>, + + #[n(2)] + pub bootstrap_witness: Option>, + + #[n(3)] + pub plutus_v1_script: Option>, + + #[n(4)] + pub plutus_data: Option>, + + #[n(5)] + pub redeemer: Option>, + + #[n(6)] + pub plutus_v2_script: Option>, +} + +#[derive(Encode, Decode, Debug, PartialEq, Clone)] +#[cbor(map)] +pub struct PostAlonzoAuxiliaryData { + #[n(0)] + pub metadata: Option, + + #[n(1)] + pub native_scripts: Option>, + + #[n(2)] + pub plutus_v1_scripts: Option>, + + #[n(3)] + pub plutus_v2_scripts: Option>, +} + +pub type DatumHash = Hash<32>; + +pub type Data = CborWrap; + +// datum_option = [ 0, $hash32 // 1, data ] +#[derive(Debug, PartialEq, Clone)] +pub enum DatumOption { + Hash(Hash<32>), + Data(Data), +} + +impl<'b, C> minicbor::Decode<'b, C> for DatumOption { + fn decode( + d: &mut minicbor::Decoder<'b>, + _ctx: &mut C, + ) -> Result { + d.array()?; + + match d.u8()? { + 0 => Ok(Self::Hash(d.decode()?)), + 1 => Ok(Self::Data(d.decode()?)), + _ => Err(minicbor::decode::Error::message( + "invalid variant for datum option enum", + )), + } + } +} + +impl minicbor::Encode for DatumOption { + fn encode( + &self, + e: &mut minicbor::Encoder, + ctx: &mut C, + ) -> Result<(), minicbor::encode::Error> { + match self { + Self::Hash(x) => e.encode_with((0, x), ctx)?, + Self::Data(x) => e.encode_with((1, x), ctx)?, + }; + + Ok(()) + } +} + +// script_ref = #6.24(bytes .cbor script) +pub type ScriptRef = CborWrap