Skip to main content

pallas_primitives/alonzo/
model.rs

1//! Ledger primitives and cbor codec for the Alonzo era
2//!
3//! Handcrafted, idiomatic rust artifacts based on based on the [Alonzo CDDL](https://github.com/input-output-hk/cardano-ledger/blob/master/eras/alonzo/test-suite/cddl-files/alonzo.cddl) file in IOHK repo.
4
5use serde::{Deserialize, Serialize};
6
7use pallas_codec::minicbor::{self, Decode, Encode};
8
9pub use pallas_codec::codec_by_datatype;
10
11pub use crate::{
12    AddrKeyhash, AssetName, Bytes, Coin, CostModel, DatumHash, DnsName, Epoch, ExUnitPrices,
13    ExUnits, GenesisDelegateHash, Genesishash, Hash, IPv4, IPv6, Int, KeepRaw, Metadata, Metadatum,
14    MetadatumLabel, NetworkId, Nonce, NonceVariant, Nullable, PlutusScript, PolicyId, PoolKeyhash,
15    PoolMetadata, PoolMetadataHash, Port, PositiveInterval, ProtocolVersion, RationalNumber, Relay,
16    RewardAccount, ScriptHash, StakeCredential, TransactionIndex, TransactionInput, UnitInterval,
17    VrfCert, VrfKeyhash, plutus_data::*,
18};
19
20use crate::BTreeMap;
21
22#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
23pub struct HeaderBody {
24    #[n(0)]
25    pub block_number: u64,
26
27    #[n(1)]
28    pub slot: u64,
29
30    #[n(2)]
31    pub prev_hash: Option<Hash<32>>,
32
33    #[n(3)]
34    pub issuer_vkey: Bytes,
35
36    #[n(4)]
37    pub vrf_vkey: Bytes,
38
39    #[n(5)]
40    pub nonce_vrf: VrfCert,
41
42    #[n(6)]
43    pub leader_vrf: VrfCert,
44
45    #[n(7)]
46    pub block_body_size: u64,
47
48    #[n(8)]
49    pub block_body_hash: Hash<32>,
50
51    #[n(9)]
52    pub operational_cert_hot_vkey: Bytes,
53
54    #[n(10)]
55    pub operational_cert_sequence_number: u64,
56
57    #[n(11)]
58    pub operational_cert_kes_period: u64,
59
60    #[n(12)]
61    pub operational_cert_sigma: Bytes,
62
63    #[n(13)]
64    pub protocol_major: u64,
65
66    #[n(14)]
67    pub protocol_minor: u64,
68}
69
70#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
71pub struct Header {
72    #[n(0)]
73    pub header_body: HeaderBody,
74
75    #[n(1)]
76    pub body_signature: Bytes,
77}
78
79#[deprecated(since = "1.0.0-alpha", note = "use `KeepRaw<'_, Header>` instead")]
80pub type MintedHeader<'a> = KeepRaw<'a, Header>;
81
82pub type Multiasset<A> = BTreeMap<PolicyId, BTreeMap<AssetName, A>>;
83
84pub type Mint = Multiasset<i64>;
85
86#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
87pub enum Value {
88    Coin(Coin),
89    Multiasset(Coin, Multiasset<Coin>),
90}
91
92codec_by_datatype! {
93    Value,
94    U8 | U16 | U32 | U64 => Coin,
95    (coin, multi => Multiasset)
96}
97
98#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
99pub struct TransactionOutput {
100    #[n(0)]
101    pub address: Bytes,
102
103    #[n(1)]
104    pub amount: Value,
105
106    #[n(2)]
107    pub datum_hash: Option<DatumHash>,
108}
109
110/* move_instantaneous_reward = [ 0 / 1, { * stake_credential => delta_coin } / coin ]
111; The first field determines where the funds are drawn from.
112; 0 denotes the reserves, 1 denotes the treasury.
113; If the second field is a map, funds are moved to stake credentials,
114; otherwise the funds are given to the other accounting pot.
115 */
116
117#[derive(Encode, Decode, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
118#[cbor(index_only)]
119pub enum InstantaneousRewardSource {
120    #[n(0)]
121    Reserves,
122    #[n(1)]
123    Treasury,
124}
125
126#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
127pub enum InstantaneousRewardTarget {
128    StakeCredentials(BTreeMap<StakeCredential, i64>),
129    OtherAccountingPot(Coin),
130}
131
132codec_by_datatype! {
133    InstantaneousRewardTarget,
134    Map | MapIndef => StakeCredentials,
135    U8 | U16 | U32 | U64 | I8 | I16 | I32 | I64 | Int => OtherAccountingPot,
136    ()
137}
138
139#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
140#[cbor()]
141pub struct MoveInstantaneousReward {
142    #[n(0)]
143    pub source: InstantaneousRewardSource,
144
145    #[n(1)]
146    pub target: InstantaneousRewardTarget,
147}
148
149pub type Withdrawals = BTreeMap<RewardAccount, Coin>;
150
151pub type RequiredSigners = Vec<AddrKeyhash>;
152
153#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Encode, Decode)]
154#[cbor(flat)]
155pub enum Certificate {
156    #[n(0)]
157    StakeRegistration(#[n(0)] StakeCredential),
158    #[n(1)]
159    StakeDeregistration(#[n(0)] StakeCredential),
160    #[n(2)]
161    StakeDelegation(#[n(0)] StakeCredential, #[n(1)] PoolKeyhash),
162    #[n(3)]
163    PoolRegistration {
164        #[n(0)]
165        operator: PoolKeyhash,
166        #[n(1)]
167        vrf_keyhash: VrfKeyhash,
168        #[n(2)]
169        pledge: Coin,
170        #[n(3)]
171        cost: Coin,
172        #[n(4)]
173        margin: UnitInterval,
174        #[n(5)]
175        reward_account: RewardAccount,
176        #[n(6)]
177        pool_owners: Vec<AddrKeyhash>,
178        #[n(7)]
179        relays: Vec<Relay>,
180        #[n(8)]
181        pool_metadata: Option<PoolMetadata>,
182    },
183    #[n(4)]
184    PoolRetirement(#[n(0)] PoolKeyhash, #[n(1)] Epoch),
185    #[n(5)]
186    GenesisKeyDelegation(
187        #[n(0)] Genesishash,
188        #[n(1)] GenesisDelegateHash,
189        #[n(2)] VrfKeyhash,
190    ),
191    #[n(6)]
192    MoveInstantaneousRewardsCert(#[n(0)] MoveInstantaneousReward),
193}
194
195#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
196#[cbor(index_only)]
197pub enum Language {
198    #[n(0)]
199    PlutusV1,
200}
201
202#[deprecated(since = "0.31.0", note = "use `CostModels` instead")]
203pub type CostMdls = CostModels;
204
205pub type CostModels = BTreeMap<Language, CostModel>;
206
207#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
208#[cbor(map)]
209pub struct ProtocolParamUpdate {
210    #[n(0)]
211    pub minfee_a: Option<u32>,
212    #[n(1)]
213    pub minfee_b: Option<u32>,
214    #[n(2)]
215    pub max_block_body_size: Option<u32>,
216    #[n(3)]
217    pub max_transaction_size: Option<u32>,
218    #[n(4)]
219    pub max_block_header_size: Option<u32>,
220    #[n(5)]
221    pub key_deposit: Option<Coin>,
222    #[n(6)]
223    pub pool_deposit: Option<Coin>,
224    #[n(7)]
225    pub maximum_epoch: Option<Epoch>,
226    #[n(8)]
227    pub desired_number_of_stake_pools: Option<u32>,
228    #[n(9)]
229    pub pool_pledge_influence: Option<RationalNumber>,
230    #[n(10)]
231    pub expansion_rate: Option<UnitInterval>,
232    #[n(11)]
233    pub treasury_growth_rate: Option<UnitInterval>,
234    #[n(12)]
235    pub decentralization_constant: Option<UnitInterval>,
236    #[n(13)]
237    pub extra_entropy: Option<Nonce>,
238    #[n(14)]
239    pub protocol_version: Option<ProtocolVersion>,
240    #[n(16)]
241    pub min_pool_cost: Option<Coin>,
242    #[n(17)]
243    pub ada_per_utxo_byte: Option<Coin>,
244    #[n(18)]
245    pub cost_models_for_script_languages: Option<CostModels>,
246    #[n(19)]
247    pub execution_costs: Option<ExUnitPrices>,
248    #[n(20)]
249    pub max_tx_ex_units: Option<ExUnits>,
250    #[n(21)]
251    pub max_block_ex_units: Option<ExUnits>,
252    #[n(22)]
253    pub max_value_size: Option<u32>,
254    #[n(23)]
255    pub collateral_percentage: Option<u32>,
256    #[n(24)]
257    pub max_collateral_inputs: Option<u32>,
258}
259
260#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
261pub struct Update {
262    #[n(0)]
263    pub proposed_protocol_parameter_updates: BTreeMap<Genesishash, ProtocolParamUpdate>,
264
265    #[n(1)]
266    pub epoch: Epoch,
267}
268
269// Can't derive encode for TransactionBody because it seems to require a very
270// particular order for each key in the map
271#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
272#[cbor(map)]
273pub struct TransactionBody {
274    #[n(0)]
275    pub inputs: Vec<TransactionInput>,
276
277    #[n(1)]
278    pub outputs: Vec<TransactionOutput>,
279
280    #[n(2)]
281    pub fee: u64,
282
283    #[n(3)]
284    pub ttl: Option<u64>,
285
286    #[n(4)]
287    pub certificates: Option<Vec<Certificate>>,
288
289    #[n(5)]
290    pub withdrawals: Option<Withdrawals>,
291
292    #[n(6)]
293    pub update: Option<Update>,
294
295    #[n(7)]
296    pub auxiliary_data_hash: Option<Hash<32>>,
297
298    #[n(8)]
299    pub validity_interval_start: Option<u64>,
300
301    #[n(9)]
302    pub mint: Option<Multiasset<i64>>,
303
304    #[n(11)]
305    pub script_data_hash: Option<Hash<32>>,
306
307    #[n(13)]
308    pub collateral: Option<Vec<TransactionInput>>,
309
310    #[n(14)]
311    pub required_signers: Option<RequiredSigners>,
312
313    #[n(15)]
314    pub network_id: Option<NetworkId>,
315}
316
317#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
318pub struct VKeyWitness {
319    #[n(0)]
320    pub vkey: Bytes,
321
322    #[n(1)]
323    pub signature: Bytes,
324}
325
326/// A native script tree with stack-safe CBOR, clone, equality and drop operations.
327///
328/// This type implements `Drop` to dismantle deep trees iteratively. To consume
329/// a child list, match by mutable reference and use [`std::mem::take`]. This
330/// stack-safety guarantee does not cover Serde, debug formatting or downstream
331/// tree visitors.
332#[derive(Serialize, Deserialize, Debug, Eq)]
333pub enum NativeScript {
334    ScriptPubkey(AddrKeyhash),
335    ScriptAll(Vec<NativeScript>),
336    ScriptAny(Vec<NativeScript>),
337    /// The ledger CDDL types this threshold signed (`int32` in Shelley,
338    /// widened to `int64` in Allegra), not `uint`; do not narrow it back.
339    ScriptNOfK(i64, Vec<NativeScript>),
340    InvalidBefore(u64),
341    InvalidHereafter(u64),
342}
343
344#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone, Copy)]
345#[cbor(index_only)]
346pub enum RedeemerTag {
347    #[n(0)]
348    Spend,
349    #[n(1)]
350    Mint,
351    #[n(2)]
352    Cert,
353    #[n(3)]
354    Reward,
355}
356
357#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
358pub struct Redeemer {
359    #[n(0)]
360    pub tag: RedeemerTag,
361
362    #[n(1)]
363    pub index: u32,
364
365    #[n(2)]
366    pub data: PlutusData,
367
368    #[n(3)]
369    pub ex_units: ExUnits,
370}
371
372#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone, Copy)]
373pub struct RedeemerPointer {
374    #[n(0)]
375    pub tag: RedeemerTag,
376
377    #[n(1)]
378    pub index: u32,
379}
380
381/* bootstrap_witness =
382[ public_key : $vkey
383, signature  : $signature
384, chain_code : bytes .size 32
385, attributes : bytes
386] */
387
388#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
389pub struct BootstrapWitness {
390    #[n(0)]
391    pub public_key: Bytes,
392
393    #[n(1)]
394    pub signature: Bytes,
395
396    #[n(2)]
397    pub chain_code: Bytes,
398
399    #[n(3)]
400    pub attributes: Bytes,
401}
402
403#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone)]
404#[cbor(map)]
405pub struct WitnessSet<'b> {
406    #[n(0)]
407    pub vkeywitness: Option<Vec<VKeyWitness>>,
408
409    #[n(1)]
410    pub native_script: Option<Vec<KeepRaw<'b, NativeScript>>>,
411
412    #[n(2)]
413    pub bootstrap_witness: Option<Vec<BootstrapWitness>>,
414
415    #[n(3)]
416    pub plutus_script: Option<Vec<PlutusScript<1>>>,
417
418    #[b(4)]
419    pub plutus_data: Option<Vec<KeepRaw<'b, PlutusData>>>,
420
421    #[n(5)]
422    pub redeemer: Option<Vec<Redeemer>>,
423}
424
425#[deprecated(since = "1.0.0-alpha", note = "use `WitnessSet` instead")]
426pub type MintedWitnessSet<'b> = WitnessSet<'b>;
427
428#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone, Eq)]
429#[cbor(map, tag(259))]
430pub struct PostAlonzoAuxiliaryData {
431    #[n(0)]
432    pub metadata: Option<Metadata>,
433
434    #[n(1)]
435    pub native_scripts: Option<Vec<NativeScript>>,
436
437    #[n(2)]
438    pub plutus_scripts: Option<Vec<PlutusScript<1>>>,
439}
440
441#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone, Eq)]
442pub struct ShelleyMaAuxiliaryData {
443    #[n(0)]
444    pub transaction_metadata: Metadata,
445
446    #[n(1)]
447    pub auxiliary_scripts: Option<Vec<NativeScript>>,
448}
449
450#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Eq)]
451pub enum AuxiliaryData {
452    Shelley(Metadata),
453    ShelleyMa(ShelleyMaAuxiliaryData),
454    PostAlonzo(PostAlonzoAuxiliaryData),
455}
456
457codec_by_datatype! {
458    AuxiliaryData,
459    Map | MapIndef => Shelley,
460    Array | ArrayIndef => ShelleyMa,
461    Tag => PostAlonzo,
462    ()
463}
464
465/// A memory representation of an already minted block
466///
467/// This structure allows to retrieve the
468/// original CBOR bytes for each structure that might require hashing. In this
469/// way, we make sure that the resulting hash matches what exists on-chain.
470#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone)]
471pub struct Block<'b> {
472    #[n(0)]
473    pub header: KeepRaw<'b, Header>,
474
475    #[b(1)]
476    pub transaction_bodies: Vec<KeepRaw<'b, TransactionBody>>,
477
478    #[n(2)]
479    pub transaction_witness_sets: Vec<KeepRaw<'b, WitnessSet<'b>>>,
480
481    #[n(3)]
482    pub auxiliary_data_set: BTreeMap<TransactionIndex, KeepRaw<'b, AuxiliaryData>>,
483
484    #[n(4)]
485    pub invalid_transactions: Option<Vec<TransactionIndex>>,
486}
487
488#[deprecated(since = "1.0.0-alpha", note = "use `Block` instead")]
489pub type MintedBlock<'b> = Block<'b>;
490
491#[derive(Encode, Decode, Debug, Clone)]
492pub struct Tx<'b> {
493    #[b(0)]
494    pub transaction_body: KeepRaw<'b, TransactionBody>,
495
496    #[n(1)]
497    pub transaction_witness_set: KeepRaw<'b, WitnessSet<'b>>,
498
499    #[n(2)]
500    pub success: bool,
501
502    #[n(3)]
503    pub auxiliary_data: Nullable<KeepRaw<'b, AuxiliaryData>>,
504}
505
506#[deprecated(since = "1.0.0-alpha", note = "use `Tx` instead")]
507pub type MintedTx<'b> = Tx<'b>;
508
509#[cfg(test)]
510mod tests {
511    use pallas_codec::minicbor::{self, to_vec};
512
513    use crate::{Fragment, alonzo::PlutusData};
514
515    use super::{Block, Header};
516
517    type BlockWrapper<'b> = (u16, Block<'b>);
518
519    #[test]
520    fn block_isomorphic_decoding_encoding() {
521        let test_blocks = vec![
522            include_str!("../../../test_data/alonzo1.block"),
523            include_str!("../../../test_data/alonzo2.block"),
524            include_str!("../../../test_data/alonzo3.block"),
525            include_str!("../../../test_data/alonzo4.block"),
526            include_str!("../../../test_data/alonzo5.block"),
527            include_str!("../../../test_data/alonzo6.block"),
528            include_str!("../../../test_data/alonzo7.block"),
529            include_str!("../../../test_data/alonzo8.block"),
530            // include_str!("../../../test_data/alonzo9.block"),
531            // old block without invalid_transactions fields
532            include_str!("../../../test_data/alonzo10.block"),
533            // peculiar block with protocol update params
534            include_str!("../../../test_data/alonzo11.block"),
535            // peculiar block with decoding issue
536            // https://github.com/txpipe/oura/issues/37
537            include_str!("../../../test_data/alonzo12.block"),
538            // peculiar block with protocol update params, including nonce
539            include_str!("../../../test_data/alonzo13.block"),
540            // peculiar block with overflow crash
541            // https://github.com/txpipe/oura/issues/113
542            include_str!("../../../test_data/alonzo14.block"),
543            // peculiar block with many move-instantaneous-rewards certs
544            include_str!("../../../test_data/alonzo15.block"),
545            // peculiar block with protocol update values
546            include_str!("../../../test_data/alonzo16.block"),
547            // peculiar block with missing nonce hash
548            include_str!("../../../test_data/alonzo17.block"),
549            // peculiar block with strange AuxiliaryData variant
550            include_str!("../../../test_data/alonzo18.block"),
551            // peculiar block with strange AuxiliaryData variant
552            include_str!("../../../test_data/alonzo18.block"),
553            // peculiar block with nevative i64 overflow
554            include_str!("../../../test_data/alonzo19.block"),
555            // peculiar block with very BigInt in plutus code
556            include_str!("../../../test_data/alonzo20.block"),
557            // // peculiar block with bad tx hash
558            // include_str!("../../../test_data/alonzo21.block"),
559            // peculiar block with bad tx hash
560            include_str!("../../../test_data/alonzo22.block"),
561            // peculiar block with indef byte array in plutus data
562            include_str!("../../../test_data/alonzo23.block"),
563            // peculiar block with invalid address (pointer overflow)
564            include_str!("../../../test_data/alonzo27.block"),
565        ];
566
567        for (idx, block_str) in test_blocks.iter().enumerate() {
568            println!("decoding test block {}", idx + 1);
569            let bytes = hex::decode(block_str).unwrap_or_else(|_| panic!("bad block file {idx}"));
570
571            let block: BlockWrapper = minicbor::decode(&bytes[..])
572                .unwrap_or_else(|_| panic!("error decoding cbor for file {idx}"));
573
574            let bytes2 = to_vec(block)
575                .unwrap_or_else(|_| panic!("error encoding block cbor for file {idx}"));
576
577            assert!(bytes.eq(&bytes2), "re-encoded bytes didn't match original");
578        }
579    }
580
581    #[test]
582    fn header_isomorphic_decoding_encoding() {
583        let test_headers = [
584            // peculiar alonzo header used as origin for a vasil devnet
585            include_str!("../../../test_data/alonzo26.header"),
586        ];
587
588        for (idx, header_str) in test_headers.iter().enumerate() {
589            println!("decoding test header {}", idx + 1);
590            let bytes = hex::decode(header_str).unwrap_or_else(|_| panic!("bad header file {idx}"));
591
592            let header: Header = minicbor::decode(&bytes[..])
593                .unwrap_or_else(|_| panic!("error decoding cbor for file {idx}"));
594
595            let bytes2 = to_vec(header)
596                .unwrap_or_else(|_| panic!("error encoding header cbor for file {idx}"));
597
598            assert!(bytes.eq(&bytes2), "re-encoded bytes didn't match original");
599        }
600    }
601
602    #[test]
603    fn plutus_data_isomorphic_decoding_encoding() {
604        let datas = [
605            // unit = Constr 0 []
606            "d87980",
607            // pltmap = Map [(I 1, unit), (I 2, pltlist)]
608            "a201d87980029f000102ff",
609            // pltlist = List [I 0, I 1, I 2]
610            "9f000102ff",
611            // Constr 5 [pltmap, Constr 5 [Map [(pltmap, toData True), (pltlist, pltmap), (List [], List [I 1])], unit, toData (0, 1)]]
612            "d87e9fa201d87980029f000102ffd87e9fa3a201d87980029f000102ffd87a809f000102ffa201d87980029f000102ff809f01ffd87980d8799f0001ffffff",
613            // Constr 5 [List [], List [I 1], Map [], Map [(I 1, unit), (I 2, Constr 2 [I 2])]]
614            "d87e9f809f01ffa0a201d8798002d87b9f02ffff",
615            // B (B.replicate 32 105)
616            "58206969696969696969696969696969696969696969696969696969696969696969",
617            // B (B.replicate 67 105)
618            "5f58406969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696943696969ff",
619            // B B.empty
620            "40",
621        ];
622        for data_hex in datas {
623            let data_bytes = hex::decode(data_hex).unwrap();
624            let data = PlutusData::decode_fragment(&data_bytes).unwrap();
625            assert_eq!(data.encode_fragment().unwrap(), data_bytes);
626        }
627    }
628}