feat(applying): implement ShelleyMA phase-1 validations (#354)
This commit is contained in:
parent
472692c4fa
commit
04232c6a4c
15 changed files with 1640 additions and 206 deletions
108
pallas-applying/docs/shelleyMA-validation-rules.md
Normal file
108
pallas-applying/docs/shelleyMA-validation-rules.md
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
# ShelleyMA transaction validation rules
|
||||||
|
|
||||||
|
This document covers the Shelley era, including its Allegra and Mary hard forks. We write *ShelleyMA* to refer to any of these ledger versions, and *Shelley*, *Allegra* or *Mary* when the discrimination is relevant. This document covers only the concepts, notation and validation rules realted to phase-1 validation in these ledger versions. For further information, refer to the corresponding white paper listed below:
|
||||||
|
- [Shelley's ledger white paper](https://github.com/input-output-hk/cardano-ledger/releases/latest/download/shelley-ledger.pdf)
|
||||||
|
- [Both Allegra's and Mary's ledger white paper](https://github.com/input-output-hk/cardano-ledger/releases/latest/download/mary-ledger.pdf)
|
||||||
|
|
||||||
|
## Definitions and notation
|
||||||
|
- **Scripts**:
|
||||||
|
- ***Script*** is the set of all possible native scripts.
|
||||||
|
- **Transactions**:
|
||||||
|
- ***Tx*** is the set of ShelleyMA transactions, composed of a transaction body and the set of witnesses.
|
||||||
|
- ***TxBody*** is the type of ShelleyMA transaction bodies. Each transaction body is composed of a set of inputs and a list of outputs.
|
||||||
|
- ***txBody(tx)*** is the transaction body of the transaction.
|
||||||
|
- ***TxOut = Addr x TA*** is the set of transaction outputs, where
|
||||||
|
- ***Addr*** is the set of transaction output addresses.
|
||||||
|
- ***TA = ℕ*** in Shelley and Allegra, while ***TA = Value*** in Mary, where ***Value*** is the type of multi-asset Mary values.
|
||||||
|
- ***txOuts(txBody) ∈ P(TxOut)*** gives the set of transaction outputs of a transaction body.
|
||||||
|
- ***balance : P(TxOut) → TA*** gives the sum of all lovelaces in a set of transaction outputs in Shelley and Allegra, while it gives the sum of all assets in a set of transaction outputs in Mary. That is, ***TA = ℕ*** in Shelley and Allegra, and ***TA = Value*** in Mary.
|
||||||
|
- ***TxIn = TxId x Ix*** is the set of transaction inputs, where
|
||||||
|
- ***TxId*** is the set of transaction IDs.
|
||||||
|
- ***Ix = ℕ*** is the set of indices (used to refer to a specific transaction output).
|
||||||
|
- ***txIns(txBody) ∈ P(TxIn)*** gives the set of transaction inputs of the transaction.
|
||||||
|
- ***utxo : TxIn → TxOut*** is a (partial) map that gives the unspent transaction output (UTxO) associated with a transaction input.
|
||||||
|
- Given ***A ⊆ dom(utxo)***, we will write ***A ◁ utxo := {to ∈ TxOut / ∃ ti ∈ dom utxo: utxo(ti) = to}***. Thus, we will write ***txIns(tx) ◁ utxo := {to ∈ TxOut / ∃ ti ∈ dom(utxo): utxo(ti) = to}*** to express the set of unspent transaction outputs associated with the set of transaction inputs of the transaction ***tx***.
|
||||||
|
- ***txTTL(txBody) ∈ Slot*** is the time-to-live of the transaction.
|
||||||
|
- ***txSize(Tx) ∈ ℕ*** gives the size of the transaction.
|
||||||
|
- ***fee(txBody) ∈ ℕ*** gives the fee paid by a transaction.
|
||||||
|
- ***minted(txBody)*** is the multiasset value minted (or burned) in the transaction.
|
||||||
|
- ***txInsScript(txBody) ⊆ P(TxIn)*** is the list of script inputs in the transaction body.
|
||||||
|
- ***consumed(pps, utxo, txBody) ∈ ℤ*** is the *consumed value* of the transaction.
|
||||||
|
- In Shelley and Allegra, this equals the sum of all lovelace in the transaction inputs.
|
||||||
|
- In Mary, this equals the sum of all multiasset values in the transaction inputs.
|
||||||
|
- ***produced(pps, txBody) ∈ ℤ*** is the *produced value* of the transaction.
|
||||||
|
- In Shelley and Allegra, this equals the sum of all lovelace in the transaction outputs plus the transaction fee.
|
||||||
|
- In Mary, this equals the sum of all multiasset values in the outputs of the transaction plus the transaction fee plus the minted value.
|
||||||
|
- **Transaction metadata**:
|
||||||
|
- ***txMD(tx)*** is the metadata of the transaction.
|
||||||
|
- ***txMDHash(txBody)*** is the metadata hash contained within the transaction body.
|
||||||
|
- ***hashMD(md)*** is the result of hasing metadata ***md***.
|
||||||
|
- **Addresses*:
|
||||||
|
- ***Addr*** is the set of all valid ShelleyMA addresses.
|
||||||
|
- ***netId(addr)*** is the network ID of the address.
|
||||||
|
- ***NetworkId*** is the global network ID.
|
||||||
|
- ***Slots***:
|
||||||
|
- ***Slot ∈ ℕ*** is the set of slots. When necessary, we write ***slot ∈ Slot*** to refer to the slot associated to the current block.
|
||||||
|
- **Serialization**:
|
||||||
|
- ***Bytes*** is the set of byte arrays (a.k.a. data, upon which signatures are built).
|
||||||
|
- ***⟦_⟧<sub>A</sub> : A -> Bytes*** takes an element of type ***A*** and returns a byte array resulting from serializing it.
|
||||||
|
- **Hashing**:
|
||||||
|
- ***KeyHash ⊆ Bytes*** is the set of fixed-size byte arrays resulting from hashing processes.
|
||||||
|
- ***hash: Bytes -> KeyHash*** is the hashing function.
|
||||||
|
- ***paymentCredential<sub>utxo</sub>(txIn) ∈ KeyHash*** gets from ***txIn*** the associated transaction output in ***utxo***, extracts the address contained in it, and returns its hash. In other words, given ***utxo*** and transaction input ***txIn*** such that ***utxo(txIn) = (a, _)***, we have that ***paymentCredential<sub>utxo</sub>(txIn) = hash(a)***.
|
||||||
|
- **Protocol Parameters**:
|
||||||
|
- We will write ***pps ∈ PParams*** to represent the set of (ShelleyMA) protocol parameters, with the following associated functions:
|
||||||
|
- ***minFees(pps, txBody) ∈ ℕ*** gives the minimum number of lovelace that must be paid for the transaction as fee.
|
||||||
|
- ***maxTxSize(pps) ∈ ℕ*** gives the (global) maximum transaction size.
|
||||||
|
- ***minUTxOValue(pps) ∈ ℕ***, the global minimum number of lovelace every UTxO must lock.
|
||||||
|
- ***Witnesses***:
|
||||||
|
- ***VKey*** is the set of verification keys (a.k.a. public keys).
|
||||||
|
- ***SKey*** is the set of signing keys (a.k.a. private keys).
|
||||||
|
- ***Sig*** is the set of signatures (i.e., the result of signing a byte array using a signing key).
|
||||||
|
- ***sig : SKey x Bytes -> Sig*** is the signing function.
|
||||||
|
- ***verify : VKey x Sig x Bytes -> Bool*** assesses whether the verification key applied to the signature yields the byte array as expected.
|
||||||
|
- The assumption is that if ***sk*** and ***vk*** are, respectively, a pair of secret and verification keys associated with one another. Thus, if ***sig(sk, d) = σ***, then it must be that ***verify(vk, σ, d) = true***.
|
||||||
|
- ***txVKWits(tx) ∈ P(VKey x Sig)*** gives the list of pairs of verification keys and signatures of the transaction.
|
||||||
|
- ***txScriptWits(tx) ⊆ P(Script)*** is the set of script witnesses of the transaction.
|
||||||
|
|
||||||
|
## Validation rules
|
||||||
|
Let ***tx ∈ Tx*** be a ShelleyMA transaction whose body is ***txBody ∈ TxBody***. ***tx*** is a phase-1 valid transaction if and only if
|
||||||
|
|
||||||
|
- **The set of transaction inputs is not empty**:
|
||||||
|
|
||||||
|
<code>txIns(txBody) ≠ ∅</code>
|
||||||
|
- **All transaction inputs are in the set of (yet) unspent transaction outputs**:
|
||||||
|
|
||||||
|
<code>txIns(txBody) ⊆ dom(utxo)</code>
|
||||||
|
- **The TTL limit of the transaction has not been exceeded**:
|
||||||
|
|
||||||
|
<code>slot ≥ txTTL(txBody)</code>
|
||||||
|
- **The transaction size does not exceed the protocol limit**:
|
||||||
|
|
||||||
|
<code>txSize(tx) ≤ maxTxSize(pps)</code>
|
||||||
|
- **All transaction outputs contain Lovelace values not under the minimum**:
|
||||||
|
|
||||||
|
<code>∀ (_, c) ∈ txOuts(txBody): minUTxOValue(pps) ≤ c</code>
|
||||||
|
- **The preservation of value property holds**: Assuming no staking or delegation actions are involved, this property takes one of the two forms below:
|
||||||
|
- In Shelley and Allegra, the equation for the preservation of value is
|
||||||
|
|
||||||
|
<code>consumed(pps, utxo, txBody) = produced(pps, poolParams, txBody) + fee(txBody)</code>,
|
||||||
|
- In Mary, the equation is:
|
||||||
|
|
||||||
|
<code>consumed(pps, utxo, txBody) = produced(pps, poolParams, txBody) + fee(txBody) + minted(txBody) </code>,
|
||||||
|
- **The fee paid by the transaction has to be greater than or equal to the minimum fee**:
|
||||||
|
|
||||||
|
<code>fee(txBody) ≥ minFees(pps, tx)</code>
|
||||||
|
- **The network ID of each output matches the global network ID**:
|
||||||
|
|
||||||
|
<code>∀(_ -> (a, _)) ∈ txOuts(txBody): netId(a) = NetworkId</code>
|
||||||
|
- **The metadata of the transaction is valid**:
|
||||||
|
|
||||||
|
<code>txMDHash(tx) = hashMD(txMD(tx))</code>
|
||||||
|
- **Verification-key witnesses**: The owner of each transaction input signed the transaction. That is, given transaction ***tx*** with body ***txBody***, then for each ***txIn ∈ txIns(txBody)*** there must exist ***(vk, σ) ∈ txVKWits(tx)*** such that:
|
||||||
|
|
||||||
|
- <code>verify(vk, σ, ⟦txBody⟧<sub>TxBody</sub>)</code>
|
||||||
|
- <code>paymentCredential<sub>utxo</sub>(txIn) = hash(vk)</code>
|
||||||
|
- **Script witnesses**: Each script address has a corresponding witness:
|
||||||
|
|
||||||
|
<code>∀ (script_hash, _) ∈ txInsScript(txBody) ◁ utxo : ∃ script ∈ txScriptWits(tx): hash(script) = script_hash</code>
|
||||||
|
|
@ -3,7 +3,9 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
ByronProtParams, MultiEraInput, MultiEraOutput, SigningTag, UTxOs, ValidationError,
|
ByronError::*,
|
||||||
|
ByronProtParams, MultiEraInput, MultiEraOutput, SigningTag, UTxOs,
|
||||||
|
ValidationError::{self, *},
|
||||||
ValidationResult,
|
ValidationResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -30,26 +32,26 @@ pub fn validate_byron_tx(
|
||||||
prot_magic: &u32,
|
prot_magic: &u32,
|
||||||
) -> ValidationResult {
|
) -> ValidationResult {
|
||||||
let tx: &Tx = &mtxp.transaction;
|
let tx: &Tx = &mtxp.transaction;
|
||||||
let size: u64 = get_tx_size(tx)?;
|
let size: &u64 = &get_tx_size(tx)?;
|
||||||
check_ins_not_empty(tx)?;
|
check_ins_not_empty(tx)?;
|
||||||
check_outs_not_empty(tx)?;
|
check_outs_not_empty(tx)?;
|
||||||
check_ins_in_utxos(tx, utxos)?;
|
check_ins_in_utxos(tx, utxos)?;
|
||||||
check_outs_have_lovelace(tx)?;
|
check_outs_have_lovelace(tx)?;
|
||||||
check_fees(tx, &size, utxos, prot_pps)?;
|
check_fees(tx, size, utxos, prot_pps)?;
|
||||||
check_size(&size, prot_pps)?;
|
check_size(size, prot_pps)?;
|
||||||
check_witnesses(mtxp, utxos, prot_magic)
|
check_witnesses(mtxp, utxos, prot_magic)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_ins_not_empty(tx: &Tx) -> ValidationResult {
|
fn check_ins_not_empty(tx: &Tx) -> ValidationResult {
|
||||||
if tx.inputs.clone().to_vec().is_empty() {
|
if tx.inputs.clone().to_vec().is_empty() {
|
||||||
return Err(ValidationError::TxInsEmpty);
|
return Err(Byron(TxInsEmpty));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_outs_not_empty(tx: &Tx) -> ValidationResult {
|
fn check_outs_not_empty(tx: &Tx) -> ValidationResult {
|
||||||
if tx.outputs.clone().to_vec().is_empty() {
|
if tx.outputs.clone().to_vec().is_empty() {
|
||||||
return Err(ValidationError::TxOutsEmpty);
|
return Err(Byron(TxOutsEmpty));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -57,7 +59,7 @@ fn check_outs_not_empty(tx: &Tx) -> ValidationResult {
|
||||||
fn check_ins_in_utxos(tx: &Tx, utxos: &UTxOs) -> ValidationResult {
|
fn check_ins_in_utxos(tx: &Tx, utxos: &UTxOs) -> ValidationResult {
|
||||||
for input in tx.inputs.iter() {
|
for input in tx.inputs.iter() {
|
||||||
if !(utxos.contains_key(&MultiEraInput::from_byron(input))) {
|
if !(utxos.contains_key(&MultiEraInput::from_byron(input))) {
|
||||||
return Err(ValidationError::InputMissingInUTxO);
|
return Err(Byron(InputNotInUTxO));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -66,7 +68,7 @@ fn check_ins_in_utxos(tx: &Tx, utxos: &UTxOs) -> ValidationResult {
|
||||||
fn check_outs_have_lovelace(tx: &Tx) -> ValidationResult {
|
fn check_outs_have_lovelace(tx: &Tx) -> ValidationResult {
|
||||||
for output in tx.outputs.iter() {
|
for output in tx.outputs.iter() {
|
||||||
if output.amount == 0 {
|
if output.amount == 0 {
|
||||||
return Err(ValidationError::OutputWithoutLovelace);
|
return Err(Byron(OutputWithoutLovelace));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -84,7 +86,7 @@ fn check_fees(tx: &Tx, size: &u64, utxos: &UTxOs, prot_pps: &ByronProtParams) ->
|
||||||
.and_then(MultiEraOutput::as_byron)
|
.and_then(MultiEraOutput::as_byron)
|
||||||
{
|
{
|
||||||
Some(byron_utxo) => inputs_balance += byron_utxo.amount,
|
Some(byron_utxo) => inputs_balance += byron_utxo.amount,
|
||||||
None => return Err(ValidationError::UnableToComputeFees),
|
None => return Err(Byron(UnableToComputeFees)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if only_redeem_utxos {
|
if only_redeem_utxos {
|
||||||
|
|
@ -95,9 +97,9 @@ fn check_fees(tx: &Tx, size: &u64, utxos: &UTxOs, prot_pps: &ByronProtParams) ->
|
||||||
outputs_balance += output.amount
|
outputs_balance += output.amount
|
||||||
}
|
}
|
||||||
let total_balance: u64 = inputs_balance - outputs_balance;
|
let total_balance: u64 = inputs_balance - outputs_balance;
|
||||||
let min_fees: u64 = prot_pps.min_fees_const + prot_pps.min_fees_factor * size;
|
let min_fees: u64 = prot_pps.fee_policy.summand + prot_pps.fee_policy.multiplier * size;
|
||||||
if total_balance < min_fees {
|
if total_balance < min_fees {
|
||||||
Err(ValidationError::FeesBelowMin)
|
Err(Byron(FeesBelowMin))
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -119,7 +121,7 @@ fn is_redeem_utxo(input: &TxIn, utxos: &UTxOs) -> bool {
|
||||||
|
|
||||||
fn check_size(size: &u64, prot_pps: &ByronProtParams) -> ValidationResult {
|
fn check_size(size: &u64, prot_pps: &ByronProtParams) -> ValidationResult {
|
||||||
if *size > prot_pps.max_tx_size {
|
if *size > prot_pps.max_tx_size {
|
||||||
return Err(ValidationError::MaxTxSizeExceeded);
|
return Err(Byron(MaxTxSizeExceeded));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -128,7 +130,7 @@ fn get_tx_size(tx: &Tx) -> Result<u64, ValidationError> {
|
||||||
let mut buff: Vec<u8> = Vec::new();
|
let mut buff: Vec<u8> = Vec::new();
|
||||||
match encode(tx, &mut buff) {
|
match encode(tx, &mut buff) {
|
||||||
Ok(()) => Ok(buff.len() as u64),
|
Ok(()) => Ok(buff.len() as u64),
|
||||||
Err(_) => Err(ValidationError::UnknownTxSize),
|
Err(_) => Err(Byron(UnknownTxSize)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,7 +151,7 @@ fn check_witnesses(mtxp: &MintedTxPayload, utxos: &UTxOs, prot_magic: &u32) -> V
|
||||||
let data_to_verify: Vec<u8> = get_data_to_verify(sign, prot_magic, &tx_hash)?;
|
let data_to_verify: Vec<u8> = get_data_to_verify(sign, prot_magic, &tx_hash)?;
|
||||||
let signature: Signature = get_signature(sign);
|
let signature: Signature = get_signature(sign);
|
||||||
if !public_key.verify(data_to_verify, &signature) {
|
if !public_key.verify(data_to_verify, &signature) {
|
||||||
return Err(ValidationError::WrongSignature);
|
return Err(Byron(WrongSignature));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -165,7 +167,7 @@ fn tag_witnesses(wits: &[Twit]) -> Result<Vec<(&PubKey, TaggedSignature)>, Valid
|
||||||
Twit::RedeemWitness(CborWrap((pk, sig))) => {
|
Twit::RedeemWitness(CborWrap((pk, sig))) => {
|
||||||
res.push((pk, TaggedSignature::RedeemWitness(sig)));
|
res.push((pk, TaggedSignature::RedeemWitness(sig)));
|
||||||
}
|
}
|
||||||
_ => return Err(ValidationError::UnableToProcessWitnesses),
|
_ => return Err(Byron(UnableToProcessWitness)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(res)
|
Ok(res)
|
||||||
|
|
@ -175,9 +177,9 @@ fn find_tx_out<'a>(input: &'a TxIn, utxos: &'a UTxOs) -> Result<&'a TxOut, Valid
|
||||||
let key: MultiEraInput = MultiEraInput::Byron(Box::new(Cow::Borrowed(input)));
|
let key: MultiEraInput = MultiEraInput::Byron(Box::new(Cow::Borrowed(input)));
|
||||||
utxos
|
utxos
|
||||||
.get(&key)
|
.get(&key)
|
||||||
.ok_or(ValidationError::InputMissingInUTxO)?
|
.ok_or(Byron(InputNotInUTxO))?
|
||||||
.as_byron()
|
.as_byron()
|
||||||
.ok_or(ValidationError::InputMissingInUTxO)
|
.ok_or(Byron(InputNotInUTxO))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_raw_witness<'a>(
|
fn find_raw_witness<'a>(
|
||||||
|
|
@ -187,7 +189,7 @@ fn find_raw_witness<'a>(
|
||||||
let address: ByronAddress = mk_byron_address(&tx_out.address);
|
let address: ByronAddress = mk_byron_address(&tx_out.address);
|
||||||
let addr_payload: AddressPayload = address
|
let addr_payload: AddressPayload = address
|
||||||
.decode()
|
.decode()
|
||||||
.map_err(|_| ValidationError::UnableToProcessWitnesses)?;
|
.map_err(|_| Byron(UnableToProcessWitness))?;
|
||||||
let root: AddressId = addr_payload.root;
|
let root: AddressId = addr_payload.root;
|
||||||
let attr: AddrAttrs = addr_payload.attributes;
|
let attr: AddrAttrs = addr_payload.attributes;
|
||||||
let addr_type: AddrType = addr_payload.addrtype;
|
let addr_type: AddrType = addr_payload.addrtype;
|
||||||
|
|
@ -195,11 +197,11 @@ fn find_raw_witness<'a>(
|
||||||
if redeems(pub_key, sign, &root, &attr, &addr_type) {
|
if redeems(pub_key, sign, &root, &attr, &addr_type) {
|
||||||
match addr_type {
|
match addr_type {
|
||||||
AddrType::PubKey | AddrType::Redeem => return Ok((pub_key, sign)),
|
AddrType::PubKey | AddrType::Redeem => return Ok((pub_key, sign)),
|
||||||
_ => return Err(ValidationError::UnableToProcessWitnesses),
|
_ => return Err(Byron(UnableToProcessWitness)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(ValidationError::MissingWitness)
|
Err(Byron(MissingWitness))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mk_byron_address(addr: &Address) -> ByronAddress {
|
fn mk_byron_address(addr: &Address) -> ByronAddress {
|
||||||
|
|
@ -250,17 +252,17 @@ fn get_data_to_verify(
|
||||||
match sign {
|
match sign {
|
||||||
TaggedSignature::PkWitness(_) => {
|
TaggedSignature::PkWitness(_) => {
|
||||||
enc.encode(SigningTag::Tx as u64)
|
enc.encode(SigningTag::Tx as u64)
|
||||||
.map_err(|_| ValidationError::UnableToProcessWitnesses)?;
|
.map_err(|_| Byron(UnableToProcessWitness))?;
|
||||||
}
|
}
|
||||||
TaggedSignature::RedeemWitness(_) => {
|
TaggedSignature::RedeemWitness(_) => {
|
||||||
enc.encode(SigningTag::RedeemTx as u64)
|
enc.encode(SigningTag::RedeemTx as u64)
|
||||||
.map_err(|_| ValidationError::UnableToProcessWitnesses)?;
|
.map_err(|_| Byron(UnableToProcessWitness))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
enc.encode(prot_magic)
|
enc.encode(prot_magic)
|
||||||
.map_err(|_| ValidationError::UnableToProcessWitnesses)?;
|
.map_err(|_| Byron(UnableToProcessWitness))?;
|
||||||
enc.encode(tx_hash)
|
enc.encode(tx_hash)
|
||||||
.map_err(|_| ValidationError::UnableToProcessWitnesses)?;
|
.map_err(|_| Byron(UnableToProcessWitness))?;
|
||||||
Ok(enc.into_writer().clone())
|
Ok(enc.into_writer().clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,41 @@
|
||||||
//! Logic for validating and applying new blocks and txs to the chain state
|
//! Logic for validating and applying new blocks and txs to the chain state
|
||||||
|
|
||||||
pub mod byron;
|
pub mod byron;
|
||||||
|
pub mod shelley_ma;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
use byron::validate_byron_tx;
|
use byron::validate_byron_tx;
|
||||||
|
use pallas_traverse::{Era, MultiEraTx};
|
||||||
|
use shelley_ma::validate_shelley_ma_tx;
|
||||||
|
|
||||||
use pallas_traverse::{MultiEraTx, MultiEraTx::Byron as ByronTxPayload};
|
pub use types::{
|
||||||
|
Environment, MultiEraProtParams, UTxOs, ValidationError::TxAndProtParamsDiffer,
|
||||||
pub use types::{Environment, MultiEraProtParams, UTxOs, ValidationResult};
|
ValidationResult,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn validate(metx: &MultiEraTx, utxos: &UTxOs, env: &Environment) -> ValidationResult {
|
pub fn validate(metx: &MultiEraTx, utxos: &UTxOs, env: &Environment) -> ValidationResult {
|
||||||
match (metx, env) {
|
match env {
|
||||||
(
|
Environment {
|
||||||
ByronTxPayload(mtxp),
|
prot_params: MultiEraProtParams::Byron(bpp),
|
||||||
Environment {
|
prot_magic,
|
||||||
prot_params: MultiEraProtParams::Byron(bpp),
|
..
|
||||||
prot_magic,
|
} => match metx {
|
||||||
|
MultiEraTx::Byron(mtxp) => validate_byron_tx(mtxp, utxos, bpp, prot_magic),
|
||||||
|
_ => Err(TxAndProtParamsDiffer),
|
||||||
|
},
|
||||||
|
Environment {
|
||||||
|
prot_params: MultiEraProtParams::Shelley(spp),
|
||||||
|
block_slot,
|
||||||
|
network_id,
|
||||||
|
..
|
||||||
|
} => match metx.era() {
|
||||||
|
Era::Shelley | Era::Allegra | Era::Mary => match metx.as_alonzo() {
|
||||||
|
Some(mtx) => {
|
||||||
|
validate_shelley_ma_tx(mtx, utxos, spp, block_slot, network_id, &metx.era())
|
||||||
|
}
|
||||||
|
None => Err(TxAndProtParamsDiffer),
|
||||||
},
|
},
|
||||||
) => validate_byron_tx(mtxp, utxos, bpp, prot_magic),
|
_ => Err(TxAndProtParamsDiffer),
|
||||||
// TODO: implement the rest of the eras.
|
},
|
||||||
_ => Ok(()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
524
pallas-applying/src/shelley_ma.rs
Normal file
524
pallas-applying/src/shelley_ma.rs
Normal file
|
|
@ -0,0 +1,524 @@
|
||||||
|
//! Utilities required for Shelley-era transaction validation.
|
||||||
|
|
||||||
|
use crate::types::{
|
||||||
|
FeePolicy,
|
||||||
|
ShelleyMAError::*,
|
||||||
|
ShelleyProtParams, UTxOs,
|
||||||
|
ValidationError::{self, *},
|
||||||
|
ValidationResult,
|
||||||
|
};
|
||||||
|
use pallas_addresses::{Address, PaymentKeyHash, ScriptHash, ShelleyAddress, ShelleyPaymentPart};
|
||||||
|
use pallas_codec::{
|
||||||
|
minicbor::encode,
|
||||||
|
utils::{Bytes, KeepRaw, KeyValuePairs},
|
||||||
|
};
|
||||||
|
use pallas_crypto::key::ed25519::{PublicKey, Signature};
|
||||||
|
use pallas_primitives::{
|
||||||
|
alonzo::{
|
||||||
|
AssetName, AuxiliaryData, Coin, MintedTx, MintedWitnessSet, Multiasset, NativeScript,
|
||||||
|
PolicyId, TransactionBody, TransactionOutput, VKeyWitness, Value,
|
||||||
|
},
|
||||||
|
byron::TxOut,
|
||||||
|
};
|
||||||
|
use pallas_traverse::{ComputeHash, Era, MultiEraInput, MultiEraOutput};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
// TODO: implement each of the validation rules.
|
||||||
|
pub fn validate_shelley_ma_tx(
|
||||||
|
mtx: &MintedTx,
|
||||||
|
utxos: &UTxOs,
|
||||||
|
prot_pps: &ShelleyProtParams,
|
||||||
|
block_slot: &u64,
|
||||||
|
network_id: &u8,
|
||||||
|
era: &Era,
|
||||||
|
) -> ValidationResult {
|
||||||
|
let tx_body: &TransactionBody = &mtx.transaction_body;
|
||||||
|
let tx_wits: &MintedWitnessSet = &mtx.transaction_witness_set;
|
||||||
|
let size: &u64 = &get_tx_size(tx_body)?;
|
||||||
|
let auxiliary_data_hash: &Option<Bytes> = &tx_body.auxiliary_data_hash;
|
||||||
|
let auxiliary_data: &Option<&[u8]> = &extract_auxiliary_data(mtx);
|
||||||
|
let minted_value: &Option<Multiasset<i64>> = &tx_body.mint;
|
||||||
|
let native_script_wits: &Option<Vec<NativeScript>> = &mtx.transaction_witness_set.native_script;
|
||||||
|
check_ins_not_empty(tx_body)?;
|
||||||
|
check_ins_in_utxos(tx_body, utxos)?;
|
||||||
|
check_ttl(tx_body, block_slot)?;
|
||||||
|
check_size(size, prot_pps)?;
|
||||||
|
check_min_lovelace(tx_body, prot_pps, era)?;
|
||||||
|
check_preservation_of_value(tx_body, utxos, era)?;
|
||||||
|
check_fees(tx_body, size, &prot_pps.fee_policy)?;
|
||||||
|
check_network_id(tx_body, network_id)?;
|
||||||
|
check_metadata(auxiliary_data_hash, auxiliary_data)?;
|
||||||
|
check_witnesses(tx_body, utxos, tx_wits)?;
|
||||||
|
check_minting(minted_value, native_script_wits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_tx_size(tx_body: &TransactionBody) -> Result<u64, ValidationError> {
|
||||||
|
let mut buff: Vec<u8> = Vec::new();
|
||||||
|
match encode(tx_body, &mut buff) {
|
||||||
|
Ok(()) => Ok(buff.len() as u64),
|
||||||
|
Err(_) => Err(Shelley(UnknownTxSize)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_auxiliary_data<'a>(mtx: &'a MintedTx) -> Option<&'a [u8]> {
|
||||||
|
Option::<KeepRaw<AuxiliaryData>>::from((mtx.auxiliary_data).clone())
|
||||||
|
.as_ref()
|
||||||
|
.map(KeepRaw::raw_cbor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_ins_not_empty(tx_body: &TransactionBody) -> ValidationResult {
|
||||||
|
if tx_body.inputs.is_empty() {
|
||||||
|
return Err(Shelley(TxInsEmpty));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_ins_in_utxos(tx_body: &TransactionBody, utxos: &UTxOs) -> ValidationResult {
|
||||||
|
for input in tx_body.inputs.iter() {
|
||||||
|
if !(utxos.contains_key(&MultiEraInput::from_alonzo_compatible(input))) {
|
||||||
|
return Err(Shelley(InputNotInUTxO));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_ttl(tx_body: &TransactionBody, block_slot: &u64) -> ValidationResult {
|
||||||
|
match tx_body.ttl {
|
||||||
|
Some(ttl) => {
|
||||||
|
if ttl < *block_slot {
|
||||||
|
Err(Shelley(TTLExceeded))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Err(Shelley(AlonzoCompNotShelley)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_size(size: &u64, prot_pps: &ShelleyProtParams) -> ValidationResult {
|
||||||
|
if *size > prot_pps.max_tx_size {
|
||||||
|
return Err(Shelley(MaxTxSizeExceeded));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_min_lovelace(
|
||||||
|
tx_body: &TransactionBody,
|
||||||
|
prot_pps: &ShelleyProtParams,
|
||||||
|
era: &Era,
|
||||||
|
) -> ValidationResult {
|
||||||
|
for TransactionOutput { amount, .. } in &tx_body.outputs {
|
||||||
|
match (era, amount) {
|
||||||
|
(Era::Shelley, Value::Coin(lovelace))
|
||||||
|
| (Era::Allegra, Value::Coin(lovelace))
|
||||||
|
| (Era::Mary, Value::Multiasset(lovelace, _)) => {
|
||||||
|
if *lovelace < prot_pps.min_lovelace {
|
||||||
|
return Err(Shelley(MinLovelaceUnreached));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return Err(Shelley(ValueNotShelley)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_preservation_of_value(
|
||||||
|
tx_body: &TransactionBody,
|
||||||
|
utxos: &UTxOs,
|
||||||
|
era: &Era,
|
||||||
|
) -> ValidationResult {
|
||||||
|
let input: Value = get_consumed(tx_body, utxos, era)?;
|
||||||
|
let produced: Value = get_produced(tx_body, era)?;
|
||||||
|
let output: Value = add_values(&produced, &Value::Coin(tx_body.fee))?;
|
||||||
|
if let Some(m) = &tx_body.mint {
|
||||||
|
add_minted_value(&output, m)?;
|
||||||
|
}
|
||||||
|
if !values_are_equal(&input, &output) {
|
||||||
|
return Err(Shelley(PreservationOfValue));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_consumed(
|
||||||
|
tx_body: &TransactionBody,
|
||||||
|
utxos: &UTxOs,
|
||||||
|
era: &Era,
|
||||||
|
) -> Result<Value, ValidationError> {
|
||||||
|
let mut res: Value = empty_value();
|
||||||
|
for input in tx_body.inputs.iter() {
|
||||||
|
let utxo_value: &MultiEraOutput = utxos
|
||||||
|
.get(&MultiEraInput::from_alonzo_compatible(input))
|
||||||
|
.ok_or(Shelley(InputNotInUTxO))?;
|
||||||
|
match MultiEraOutput::as_alonzo(utxo_value) {
|
||||||
|
Some(TransactionOutput { amount, .. }) => match (amount, era) {
|
||||||
|
(Value::Coin(..), _) => res = add_values(&res, amount)?,
|
||||||
|
(Value::Multiasset(..), Era::Shelley) => return Err(Shelley(ValueNotShelley)),
|
||||||
|
_ => res = add_values(&res, amount)?,
|
||||||
|
},
|
||||||
|
None => match MultiEraOutput::as_byron(utxo_value) {
|
||||||
|
Some(TxOut { amount, .. }) => res = add_values(&res, &Value::Coin(*amount))?,
|
||||||
|
_ => return Err(Shelley(InputNotInUTxO)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_produced(tx_body: &TransactionBody, era: &Era) -> Result<Value, ValidationError> {
|
||||||
|
let mut res: Value = empty_value();
|
||||||
|
for TransactionOutput { amount, .. } in tx_body.outputs.iter() {
|
||||||
|
match (amount, era) {
|
||||||
|
(Value::Coin(..), _) => res = add_values(&res, amount)?,
|
||||||
|
(Value::Multiasset(..), Era::Shelley) => return Err(Shelley(WrongEraOutput)),
|
||||||
|
_ => res = add_values(&res, amount)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn empty_value() -> Value {
|
||||||
|
Value::Multiasset(0, Multiasset::<Coin>::from(Vec::new()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_values(first: &Value, second: &Value) -> Result<Value, ValidationError> {
|
||||||
|
match (first, second) {
|
||||||
|
(Value::Coin(f), Value::Coin(s)) => Ok(Value::Coin(f + s)),
|
||||||
|
(Value::Multiasset(f, fma), Value::Coin(s)) => Ok(Value::Multiasset(f + s, fma.clone())),
|
||||||
|
(Value::Coin(f), Value::Multiasset(s, sma)) => Ok(Value::Multiasset(f + s, sma.clone())),
|
||||||
|
(Value::Multiasset(f, fma), Value::Multiasset(s, sma)) => Ok(Value::Multiasset(
|
||||||
|
f + s,
|
||||||
|
coerce_to_coin(&add_multiasset_values(
|
||||||
|
&coerce_to_i64(fma),
|
||||||
|
&coerce_to_i64(sma),
|
||||||
|
))?,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_minted_value(
|
||||||
|
base_value: &Value,
|
||||||
|
minted_value: &Multiasset<i64>,
|
||||||
|
) -> Result<Value, ValidationError> {
|
||||||
|
match base_value {
|
||||||
|
Value::Coin(n) => Ok(Value::Multiasset(*n, coerce_to_coin(minted_value)?)),
|
||||||
|
Value::Multiasset(n, mary_base_value) => Ok(Value::Multiasset(
|
||||||
|
*n,
|
||||||
|
coerce_to_coin(&add_multiasset_values(
|
||||||
|
&coerce_to_i64(mary_base_value),
|
||||||
|
minted_value,
|
||||||
|
))?,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn coerce_to_i64(value: &Multiasset<Coin>) -> Multiasset<i64> {
|
||||||
|
let mut res: Vec<(PolicyId, KeyValuePairs<AssetName, i64>)> = Vec::new();
|
||||||
|
for (policy, assets) in value.clone().to_vec().iter() {
|
||||||
|
let mut aa: Vec<(AssetName, i64)> = Vec::new();
|
||||||
|
for (asset_name, amount) in assets.clone().to_vec().iter() {
|
||||||
|
aa.push((asset_name.clone(), *amount as i64));
|
||||||
|
}
|
||||||
|
res.push((*policy, KeyValuePairs::<AssetName, i64>::from(aa)));
|
||||||
|
}
|
||||||
|
KeyValuePairs::<PolicyId, KeyValuePairs<AssetName, i64>>::from(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn coerce_to_coin(value: &Multiasset<i64>) -> Result<Multiasset<Coin>, ValidationError> {
|
||||||
|
let mut res: Vec<(PolicyId, KeyValuePairs<AssetName, Coin>)> = Vec::new();
|
||||||
|
for (policy, assets) in value.clone().to_vec().iter() {
|
||||||
|
let mut aa: Vec<(AssetName, Coin)> = Vec::new();
|
||||||
|
for (asset_name, amount) in assets.clone().to_vec().iter() {
|
||||||
|
if *amount < 0 {
|
||||||
|
return Err(Shelley(NegativeValue));
|
||||||
|
}
|
||||||
|
aa.push((asset_name.clone(), *amount as u64));
|
||||||
|
}
|
||||||
|
res.push((*policy, KeyValuePairs::<AssetName, Coin>::from(aa)));
|
||||||
|
}
|
||||||
|
Ok(KeyValuePairs::<PolicyId, KeyValuePairs<AssetName, Coin>>::from(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_multiasset_values(first: &Multiasset<i64>, second: &Multiasset<i64>) -> Multiasset<i64> {
|
||||||
|
let mut res: HashMap<PolicyId, HashMap<AssetName, i64>> = HashMap::new();
|
||||||
|
for (policy, new_assets) in first.iter() {
|
||||||
|
match res.get(policy) {
|
||||||
|
Some(old_assets) => res.insert(*policy, add_same_policy_assets(old_assets, new_assets)),
|
||||||
|
None => res.insert(*policy, add_same_policy_assets(&HashMap::new(), new_assets)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
for (policy, new_assets) in second.iter() {
|
||||||
|
match res.get(policy) {
|
||||||
|
Some(old_assets) => res.insert(*policy, add_same_policy_assets(old_assets, new_assets)),
|
||||||
|
None => res.insert(*policy, add_same_policy_assets(&HashMap::new(), new_assets)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
wrap_multiasset(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_same_policy_assets(
|
||||||
|
old_assets: &HashMap<AssetName, i64>,
|
||||||
|
new_assets: &KeyValuePairs<AssetName, i64>,
|
||||||
|
) -> HashMap<AssetName, i64> {
|
||||||
|
let mut res: HashMap<AssetName, i64> = old_assets.clone();
|
||||||
|
for (asset_name, new_amount) in new_assets.iter() {
|
||||||
|
match res.get(asset_name) {
|
||||||
|
Some(old_amount) => res.insert(asset_name.clone(), old_amount + *new_amount),
|
||||||
|
None => res.insert(asset_name.clone(), *new_amount),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrap_multiasset(input: HashMap<PolicyId, HashMap<AssetName, i64>>) -> Multiasset<i64> {
|
||||||
|
Multiasset::<i64>::from(
|
||||||
|
input
|
||||||
|
.into_iter()
|
||||||
|
.map(|(policy, assets)| {
|
||||||
|
(
|
||||||
|
policy,
|
||||||
|
KeyValuePairs::<AssetName, i64>::from(
|
||||||
|
assets.into_iter().collect::<Vec<(AssetName, i64)>>(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<(PolicyId, KeyValuePairs<AssetName, i64>)>>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn values_are_equal(first: &Value, second: &Value) -> bool {
|
||||||
|
match (first, second) {
|
||||||
|
(Value::Coin(f), Value::Coin(s)) => f == s,
|
||||||
|
(Value::Multiasset(..), Value::Coin(..)) => false,
|
||||||
|
(Value::Coin(..), Value::Multiasset(..)) => false,
|
||||||
|
(Value::Multiasset(f, fma), Value::Multiasset(s, sma)) => {
|
||||||
|
if f != s {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
for (fpolicy, fassets) in fma.iter() {
|
||||||
|
match find_policy(sma, fpolicy) {
|
||||||
|
Some(sassets) => {
|
||||||
|
for (fasset_name, famount) in fassets.iter() {
|
||||||
|
match find_assets(&sassets, fasset_name) {
|
||||||
|
Some(samount) => {
|
||||||
|
if *famount != samount {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => return false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_policy(
|
||||||
|
mary_value: &Multiasset<Coin>,
|
||||||
|
search_policy: &PolicyId,
|
||||||
|
) -> Option<KeyValuePairs<AssetName, Coin>> {
|
||||||
|
for (policy, assets) in mary_value.clone().to_vec().iter() {
|
||||||
|
if policy == search_policy {
|
||||||
|
return Some(assets.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_assets(assets: &KeyValuePairs<AssetName, Coin>, asset_name: &AssetName) -> Option<Coin> {
|
||||||
|
for (an, amount) in assets.clone().to_vec().iter() {
|
||||||
|
if an == asset_name {
|
||||||
|
return Some(*amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_fees(tx_body: &TransactionBody, size: &u64, fee_policy: &FeePolicy) -> ValidationResult {
|
||||||
|
if tx_body.fee < fee_policy.summand + fee_policy.multiplier * size {
|
||||||
|
return Err(Shelley(FeesBelowMin));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_network_id(tx_body: &TransactionBody, network_id: &u8) -> ValidationResult {
|
||||||
|
for output in tx_body.outputs.iter() {
|
||||||
|
let addr: ShelleyAddress = get_shelley_address(Vec::<u8>::from(output.address.clone()))?;
|
||||||
|
if addr.network().value() != *network_id {
|
||||||
|
return Err(Shelley(WrongNetworkID));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_shelley_address(address: Vec<u8>) -> Result<ShelleyAddress, ValidationError> {
|
||||||
|
match Address::from_bytes(&address) {
|
||||||
|
Ok(Address::Shelley(sa)) => Ok(sa),
|
||||||
|
Ok(_) => Err(Shelley(WrongEraOutput)),
|
||||||
|
Err(_) => Err(Shelley(AddressDecoding)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_metadata(
|
||||||
|
auxiliary_data_hash: &Option<Bytes>,
|
||||||
|
auxiliary_data_cbor: &Option<&[u8]>,
|
||||||
|
) -> ValidationResult {
|
||||||
|
match (auxiliary_data_hash, auxiliary_data_cbor) {
|
||||||
|
(Some(metadata_hash), Some(metadata)) => {
|
||||||
|
if metadata_hash.as_slice()
|
||||||
|
== pallas_crypto::hash::Hasher::<256>::hash(metadata).as_ref()
|
||||||
|
{
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Shelley(MetadataHash))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(None, None) => Ok(()),
|
||||||
|
_ => Err(Shelley(MetadataHash)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_witnesses(
|
||||||
|
tx_body: &TransactionBody,
|
||||||
|
utxos: &UTxOs,
|
||||||
|
tx_wits: &MintedWitnessSet,
|
||||||
|
) -> ValidationResult {
|
||||||
|
let wits: &mut Vec<(bool, VKeyWitness)> = &mut mk_vkwitness_check_list(&tx_wits.vkeywitness)?;
|
||||||
|
let tx_hash: &Vec<u8> = &Vec::from(tx_body.compute_hash().as_ref());
|
||||||
|
for input in tx_body.inputs.iter() {
|
||||||
|
match utxos.get(&MultiEraInput::from_alonzo_compatible(input)) {
|
||||||
|
Some(multi_era_output) => {
|
||||||
|
if let Some(alonzo_comp_output) = MultiEraOutput::as_alonzo(multi_era_output) {
|
||||||
|
match get_payment_part(alonzo_comp_output)? {
|
||||||
|
ShelleyPaymentPart::Key(payment_key_hash) => {
|
||||||
|
check_verification_key_witness(&payment_key_hash, tx_hash, wits)?
|
||||||
|
}
|
||||||
|
ShelleyPaymentPart::Script(script_hash) => {
|
||||||
|
check_native_script_witness(&script_hash, &tx_wits.native_script)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => return Err(Shelley(InputNotInUTxO)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check_remaining_verification_key_witnesses(wits, tx_hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mk_vkwitness_check_list(
|
||||||
|
wits: &Option<Vec<VKeyWitness>>,
|
||||||
|
) -> Result<Vec<(bool, VKeyWitness)>, ValidationError> {
|
||||||
|
Ok(wits
|
||||||
|
.clone()
|
||||||
|
.ok_or(Shelley(MissingVKWitness))?
|
||||||
|
.iter()
|
||||||
|
.map(|x| (false, x.clone()))
|
||||||
|
.collect::<Vec<(bool, VKeyWitness)>>())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_payment_part(tx_out: &TransactionOutput) -> Result<ShelleyPaymentPart, ValidationError> {
|
||||||
|
let addr: ShelleyAddress = get_shelley_address(Vec::<u8>::from(tx_out.address.clone()))?;
|
||||||
|
Ok(addr.payment().clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_verification_key_witness(
|
||||||
|
payment_key_hash: &PaymentKeyHash,
|
||||||
|
data_to_verify: &Vec<u8>,
|
||||||
|
wits: &mut Vec<(bool, VKeyWitness)>,
|
||||||
|
) -> ValidationResult {
|
||||||
|
for (found, VKeyWitness { vkey, signature }) in wits {
|
||||||
|
if pallas_crypto::hash::Hasher::<224>::hash(vkey) == *payment_key_hash {
|
||||||
|
let mut public_key_source: [u8; PublicKey::SIZE] = [0; PublicKey::SIZE];
|
||||||
|
public_key_source.copy_from_slice(vkey.as_slice());
|
||||||
|
let public_key: PublicKey = From::<[u8; PublicKey::SIZE]>::from(public_key_source);
|
||||||
|
let mut signature_source: [u8; Signature::SIZE] = [0; Signature::SIZE];
|
||||||
|
signature_source.copy_from_slice(signature.as_slice());
|
||||||
|
let sig: Signature = From::<[u8; Signature::SIZE]>::from(signature_source);
|
||||||
|
if public_key.verify(data_to_verify, &sig) {
|
||||||
|
*found = true;
|
||||||
|
return Ok(());
|
||||||
|
} else {
|
||||||
|
return Err(Shelley(WrongSignature));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Shelley(MissingVKWitness))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_native_script_witness(
|
||||||
|
script_hash: &ScriptHash,
|
||||||
|
wits: &Option<Vec<NativeScript>>,
|
||||||
|
) -> ValidationResult {
|
||||||
|
match wits {
|
||||||
|
Some(scripts) => {
|
||||||
|
let mut payload: Vec<u8> = vec![0u8];
|
||||||
|
for script in scripts.iter() {
|
||||||
|
let _ = encode(script, &mut payload);
|
||||||
|
if pallas_crypto::hash::Hasher::<224>::hash(&payload) == *script_hash {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Shelley(MissingScriptWitness))
|
||||||
|
}
|
||||||
|
None => Err(Shelley(MissingScriptWitness)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_remaining_verification_key_witnesses(
|
||||||
|
wits: &mut Vec<(bool, VKeyWitness)>,
|
||||||
|
data_to_verify: &Vec<u8>,
|
||||||
|
) -> ValidationResult {
|
||||||
|
for (covered, VKeyWitness { vkey, signature }) in wits {
|
||||||
|
if !*covered {
|
||||||
|
let mut public_key_source: [u8; PublicKey::SIZE] = [0; PublicKey::SIZE];
|
||||||
|
public_key_source.copy_from_slice(vkey.as_slice());
|
||||||
|
let public_key: PublicKey = From::<[u8; PublicKey::SIZE]>::from(public_key_source);
|
||||||
|
let mut signature_source: [u8; Signature::SIZE] = [0; Signature::SIZE];
|
||||||
|
signature_source.copy_from_slice(signature.as_slice());
|
||||||
|
let sig: Signature = From::<[u8; Signature::SIZE]>::from(signature_source);
|
||||||
|
if !public_key.verify(data_to_verify, &sig) {
|
||||||
|
return Err(Shelley(WrongSignature));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_minting(
|
||||||
|
values: &Option<Multiasset<i64>>,
|
||||||
|
scripts: &Option<Vec<NativeScript>>,
|
||||||
|
) -> ValidationResult {
|
||||||
|
match (values, scripts) {
|
||||||
|
(None, _) => Ok(()),
|
||||||
|
(Some(_), None) => Err(Shelley(MintingLacksPolicy)),
|
||||||
|
(Some(minted_value), Some(native_script_wits)) => {
|
||||||
|
for (policy, _) in minted_value.iter() {
|
||||||
|
if check_policy(policy, native_script_wits) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_policy(policy: &PolicyId, native_script_wits: &[NativeScript]) -> bool {
|
||||||
|
for script in native_script_wits.iter() {
|
||||||
|
let hashed_script: PolicyId = compute_script_hash(script);
|
||||||
|
if *policy == hashed_script {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_script_hash(script: &NativeScript) -> PolicyId {
|
||||||
|
let mut payload = Vec::new();
|
||||||
|
let _ = encode(script, &mut payload);
|
||||||
|
payload.insert(0, 0);
|
||||||
|
pallas_crypto::hash::Hasher::<224>::hash(&payload)
|
||||||
|
}
|
||||||
|
|
@ -8,22 +8,37 @@ pub type UTxOs<'b> = HashMap<MultiEraInput<'b>, MultiEraOutput<'b>>;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ByronProtParams {
|
pub struct ByronProtParams {
|
||||||
pub min_fees_const: u64,
|
pub fee_policy: FeePolicy,
|
||||||
pub min_fees_factor: u64,
|
|
||||||
pub max_tx_size: u64,
|
pub max_tx_size: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ShelleyProtParams {
|
||||||
|
pub fee_policy: FeePolicy,
|
||||||
|
pub max_tx_size: u64,
|
||||||
|
pub min_lovelace: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FeePolicy {
|
||||||
|
pub summand: u64,
|
||||||
|
pub multiplier: u64,
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: add variants for the other eras.
|
// TODO: add variants for the other eras.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum MultiEraProtParams {
|
pub enum MultiEraProtParams {
|
||||||
Byron(ByronProtParams),
|
Byron(ByronProtParams),
|
||||||
|
Shelley(ShelleyProtParams),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Environment {
|
pub struct Environment {
|
||||||
pub prot_params: MultiEraProtParams,
|
pub prot_params: MultiEraProtParams,
|
||||||
pub prot_magic: u32,
|
pub prot_magic: u32,
|
||||||
|
pub block_slot: u64,
|
||||||
|
pub network_id: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
|
@ -35,17 +50,49 @@ pub enum SigningTag {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum ValidationError {
|
pub enum ValidationError {
|
||||||
InputMissingInUTxO,
|
TxAndProtParamsDiffer,
|
||||||
|
Byron(ByronError),
|
||||||
|
Shelley(ShelleyMAError),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum ByronError {
|
||||||
TxInsEmpty,
|
TxInsEmpty,
|
||||||
TxOutsEmpty,
|
TxOutsEmpty,
|
||||||
|
InputNotInUTxO,
|
||||||
OutputWithoutLovelace,
|
OutputWithoutLovelace,
|
||||||
UnknownTxSize,
|
UnknownTxSize,
|
||||||
UnableToComputeFees,
|
UnableToComputeFees,
|
||||||
FeesBelowMin,
|
FeesBelowMin,
|
||||||
MaxTxSizeExceeded,
|
MaxTxSizeExceeded,
|
||||||
UnableToProcessWitnesses,
|
UnableToProcessWitness,
|
||||||
MissingWitness,
|
MissingWitness,
|
||||||
WrongSignature,
|
WrongSignature,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum ShelleyMAError {
|
||||||
|
TxInsEmpty,
|
||||||
|
InputNotInUTxO,
|
||||||
|
TTLExceeded,
|
||||||
|
AlonzoCompNotShelley,
|
||||||
|
UnknownTxSize,
|
||||||
|
MaxTxSizeExceeded,
|
||||||
|
ValueNotShelley,
|
||||||
|
MinLovelaceUnreached,
|
||||||
|
PreservationOfValue,
|
||||||
|
NegativeValue,
|
||||||
|
FeesBelowMin,
|
||||||
|
WrongEraOutput,
|
||||||
|
AddressDecoding,
|
||||||
|
WrongNetworkID,
|
||||||
|
MetadataHash,
|
||||||
|
MissingVKWitness,
|
||||||
|
MissingScriptWitness,
|
||||||
|
WrongSignature,
|
||||||
|
MintingLacksPolicy,
|
||||||
|
}
|
||||||
|
|
||||||
pub type ValidationResult = Result<(), ValidationError>;
|
pub type ValidationResult = Result<(), ValidationError>;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
use std::{borrow::Cow, vec::Vec};
|
use std::{borrow::Cow, vec::Vec};
|
||||||
|
|
||||||
use pallas_applying::{
|
use pallas_applying::{
|
||||||
types::{ByronProtParams, Environment, MultiEraProtParams, ValidationError},
|
types::{
|
||||||
validate, UTxOs, ValidationResult,
|
ByronError::*, ByronProtParams, Environment, FeePolicy, MultiEraProtParams,
|
||||||
|
ValidationError::*,
|
||||||
|
},
|
||||||
|
validate, UTxOs,
|
||||||
};
|
};
|
||||||
use pallas_codec::{
|
use pallas_codec::{
|
||||||
minicbor::{
|
minicbor::{
|
||||||
|
|
@ -10,67 +13,11 @@ use pallas_codec::{
|
||||||
decode::{Decode, Decoder},
|
decode::{Decode, Decoder},
|
||||||
encode,
|
encode,
|
||||||
},
|
},
|
||||||
utils::{CborWrap, KeepRaw, MaybeIndefArray, TagWrap},
|
utils::{CborWrap, MaybeIndefArray, TagWrap},
|
||||||
};
|
};
|
||||||
use pallas_primitives::byron::{Address, MintedTxPayload, Twit, Tx, TxIn, TxOut, Witnesses};
|
use pallas_primitives::byron::{Address, MintedTxPayload, Twit, Tx, TxIn, TxOut, Witnesses};
|
||||||
use pallas_traverse::{MultiEraInput, MultiEraOutput, MultiEraTx};
|
use pallas_traverse::{MultiEraInput, MultiEraOutput, MultiEraTx};
|
||||||
|
|
||||||
// Helper functions.
|
|
||||||
fn add_to_utxo(utxos: &mut UTxOs, tx_in: TxIn, tx_out: TxOut) {
|
|
||||||
let multi_era_in: MultiEraInput = MultiEraInput::Byron(Box::new(Cow::Owned(tx_in)));
|
|
||||||
let multi_era_out: MultiEraOutput = MultiEraOutput::Byron(Box::new(Cow::Owned(tx_out)));
|
|
||||||
utxos.insert(multi_era_in, multi_era_out);
|
|
||||||
}
|
|
||||||
|
|
||||||
// pallas_applying::validate takes a MultiEraTx, not a Tx and a Witnesses. To be
|
|
||||||
// able to build a MultiEraTx from a Tx and a Witnesses, we need to encode each
|
|
||||||
// of them and then decode them into KeepRaw<Tx> and KeepRaw<Witnesses> values,
|
|
||||||
// respectively, to be able to make the MultiEraTx value.
|
|
||||||
fn mk_byron_tx_and_validate(
|
|
||||||
tx: &Tx,
|
|
||||||
wits: &Witnesses,
|
|
||||||
utxos: &UTxOs,
|
|
||||||
env: &Environment,
|
|
||||||
) -> ValidationResult {
|
|
||||||
let mut tx_buf: Vec<u8> = Vec::new();
|
|
||||||
|
|
||||||
match encode(tx, &mut tx_buf) {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(err) => panic!("Unable to encode Tx ({:?}).", err),
|
|
||||||
};
|
|
||||||
|
|
||||||
let kptx: KeepRaw<Tx> = match Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()) {
|
|
||||||
Ok(kp) => kp,
|
|
||||||
Err(err) => panic!("Unable to decode Tx ({:?}).", err),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut wit_buf: Vec<u8> = Vec::new();
|
|
||||||
|
|
||||||
match encode(wits, &mut wit_buf) {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(err) => panic!("Unable to encode Witnesses ({:?}).", err),
|
|
||||||
};
|
|
||||||
|
|
||||||
let kpwit: KeepRaw<Witnesses> =
|
|
||||||
match Decode::decode(&mut Decoder::new(wit_buf.as_slice()), &mut ()) {
|
|
||||||
Ok(kp) => kp,
|
|
||||||
Err(err) => panic!("Unable to decode Witnesses ({:?}).", err),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mtxp: MintedTxPayload = MintedTxPayload {
|
|
||||||
transaction: kptx,
|
|
||||||
witness: kpwit,
|
|
||||||
};
|
|
||||||
|
|
||||||
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
|
|
||||||
|
|
||||||
validate(&metx, utxos, env)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_utxos<'a>() -> UTxOs<'a> {
|
|
||||||
UTxOs::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod byron_tests {
|
mod byron_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
@ -79,60 +26,66 @@ mod byron_tests {
|
||||||
hex::decode(input).unwrap()
|
hex::decode(input).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mainnet_tx_from_bytes_cbor(tx_cbor: &[u8]) -> MintedTxPayload<'_> {
|
fn tx_from_cbor<'a>(tx_cbor: &'a Vec<u8>) -> MintedTxPayload<'a> {
|
||||||
pallas_codec::minicbor::decode::<MintedTxPayload>(tx_cbor).unwrap()
|
pallas_codec::minicbor::decode::<MintedTxPayload>(&tx_cbor[..]).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Careful: this function assumes tx has exactly one input.
|
// Careful: this function assumes tx has exactly one input.
|
||||||
fn mk_utxo_for_single_input_tx<'a>(tx: &Tx, address_payload: String, amount: u64) -> UTxOs<'a> {
|
fn mk_utxo_for_single_input_tx<'a>(tx: &Tx, address_payload: String, amount: u64) -> UTxOs<'a> {
|
||||||
let mut tx_ins: Vec<TxIn> = tx.inputs.clone().to_vec();
|
let mut tx_ins: Vec<TxIn> = tx.inputs.clone().to_vec();
|
||||||
assert_eq!(tx_ins.len(), 1, "Unexpected number of inputs.");
|
assert_eq!(tx_ins.len(), 1, "Unexpected number of inputs");
|
||||||
let tx_in: TxIn = tx_ins.pop().unwrap();
|
let tx_in: TxIn = tx_ins.pop().unwrap();
|
||||||
let input_tx_out_addr: Address = match hex::decode(address_payload) {
|
let input_tx_out_addr: Address = match hex::decode(address_payload) {
|
||||||
Ok(addr_bytes) => Address {
|
Ok(addr_bytes) => Address {
|
||||||
payload: TagWrap(ByteVec::from(addr_bytes)),
|
payload: TagWrap(ByteVec::from(addr_bytes)),
|
||||||
crc: 3430631884,
|
crc: 3430631884,
|
||||||
},
|
},
|
||||||
_ => panic!("Unable to decode input address."),
|
_ => panic!("Unable to decode input address"),
|
||||||
};
|
};
|
||||||
let tx_out: TxOut = TxOut {
|
let tx_out: TxOut = TxOut {
|
||||||
address: input_tx_out_addr,
|
address: input_tx_out_addr,
|
||||||
amount,
|
amount,
|
||||||
};
|
};
|
||||||
let mut utxos: UTxOs = new_utxos();
|
let mut utxos: UTxOs = UTxOs::new();
|
||||||
add_to_utxo(&mut utxos, tx_in, tx_out);
|
add_to_utxo(&mut utxos, tx_in, tx_out);
|
||||||
utxos
|
utxos
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
// Transaction hash: a9e4413a5fb61a7a43c7df006ffcaaf3f2ffc9541f54757023968c5a8f8294fd
|
||||||
fn successful_mainnet_tx_with_genesis_utxos() {
|
fn successful_mainnet_tx_with_genesis_utxos() {
|
||||||
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron2.tx"));
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron2.tx"));
|
||||||
let mtxp: MintedTxPayload = mainnet_tx_from_bytes_cbor(&cbor_bytes);
|
let mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
|
||||||
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
||||||
&mtxp.transaction,
|
&mtxp.transaction,
|
||||||
String::from(include_str!("../../test_data/byron2.address")),
|
String::from(include_str!("../../test_data/byron2.address")),
|
||||||
// The number of lovelace in this input is irrelevant, since no fees have to be paid
|
19999000000,
|
||||||
// for this transaction.
|
|
||||||
1,
|
|
||||||
);
|
);
|
||||||
let env: Environment = Environment {
|
let env: Environment = Environment {
|
||||||
prot_params: MultiEraProtParams::Byron(ByronProtParams {
|
prot_params: MultiEraProtParams::Byron(ByronProtParams {
|
||||||
min_fees_const: 155381,
|
fee_policy: FeePolicy {
|
||||||
min_fees_factor: 44,
|
summand: 155381,
|
||||||
|
multiplier: 44,
|
||||||
|
},
|
||||||
max_tx_size: 4096,
|
max_tx_size: 4096,
|
||||||
}),
|
}),
|
||||||
prot_magic: 764824073,
|
prot_magic: 764824073,
|
||||||
|
block_slot: 6341,
|
||||||
|
network_id: 1,
|
||||||
};
|
};
|
||||||
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
|
match validate(&metx, &utxos, &env) {
|
||||||
Ok(()) => (),
|
Ok(()) => (),
|
||||||
Err(err) => panic!("Unexpected error ({:?}).", err),
|
Err(err) => panic!("Unexpected error ({:?})", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
// Transaction hash: a06e5a0150e09f8983be2deafab9e04afc60d92e7110999eb672c903343f1e26
|
||||||
fn successful_mainnet_tx() {
|
fn successful_mainnet_tx() {
|
||||||
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
|
||||||
let mtxp: MintedTxPayload = mainnet_tx_from_bytes_cbor(&cbor_bytes);
|
let mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
|
||||||
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
||||||
&mtxp.transaction,
|
&mtxp.transaction,
|
||||||
String::from(include_str!("../../test_data/byron1.address")),
|
String::from(include_str!("../../test_data/byron1.address")),
|
||||||
|
|
@ -140,15 +93,19 @@ mod byron_tests {
|
||||||
);
|
);
|
||||||
let env: Environment = Environment {
|
let env: Environment = Environment {
|
||||||
prot_params: MultiEraProtParams::Byron(ByronProtParams {
|
prot_params: MultiEraProtParams::Byron(ByronProtParams {
|
||||||
min_fees_const: 155381,
|
fee_policy: FeePolicy {
|
||||||
min_fees_factor: 44,
|
summand: 155381,
|
||||||
|
multiplier: 44,
|
||||||
|
},
|
||||||
max_tx_size: 4096,
|
max_tx_size: 4096,
|
||||||
}),
|
}),
|
||||||
prot_magic: 764824073,
|
prot_magic: 764824073,
|
||||||
|
block_slot: 3241381,
|
||||||
|
network_id: 1,
|
||||||
};
|
};
|
||||||
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
|
match validate(&metx, &utxos, &env) {
|
||||||
Ok(()) => (),
|
Ok(()) => (),
|
||||||
Err(err) => panic!("Unexpected error ({:?}).", err),
|
Err(err) => panic!("Unexpected error ({:?})", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -156,7 +113,7 @@ mod byron_tests {
|
||||||
// Identical to successful_mainnet_tx, except that all inputs are removed.
|
// Identical to successful_mainnet_tx, except that all inputs are removed.
|
||||||
fn empty_ins() {
|
fn empty_ins() {
|
||||||
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
|
||||||
let mut mtxp: MintedTxPayload = mainnet_tx_from_bytes_cbor(&cbor_bytes);
|
let mut mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
|
||||||
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
||||||
&mtxp.transaction,
|
&mtxp.transaction,
|
||||||
String::from(include_str!("../../test_data/byron1.address")),
|
String::from(include_str!("../../test_data/byron1.address")),
|
||||||
|
|
@ -168,22 +125,27 @@ mod byron_tests {
|
||||||
let mut tx_buf: Vec<u8> = Vec::new();
|
let mut tx_buf: Vec<u8> = Vec::new();
|
||||||
match encode(tx, &mut tx_buf) {
|
match encode(tx, &mut tx_buf) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(err) => panic!("Unable to encode Tx ({:?}).", err),
|
Err(err) => panic!("Unable to encode Tx ({:?})", err),
|
||||||
};
|
};
|
||||||
mtxp.transaction = Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()).unwrap();
|
mtxp.transaction = Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
|
||||||
let env: Environment = Environment {
|
let env: Environment = Environment {
|
||||||
prot_params: MultiEraProtParams::Byron(ByronProtParams {
|
prot_params: MultiEraProtParams::Byron(ByronProtParams {
|
||||||
min_fees_const: 155381,
|
fee_policy: FeePolicy {
|
||||||
min_fees_factor: 44,
|
summand: 155381,
|
||||||
|
multiplier: 44,
|
||||||
|
},
|
||||||
max_tx_size: 4096,
|
max_tx_size: 4096,
|
||||||
}),
|
}),
|
||||||
prot_magic: 764824073,
|
prot_magic: 764824073,
|
||||||
|
block_slot: 3241381,
|
||||||
|
network_id: 1,
|
||||||
};
|
};
|
||||||
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
|
match validate(&metx, &utxos, &env) {
|
||||||
Ok(()) => panic!("Inputs set should not be empty."),
|
Ok(()) => assert!(false, "Inputs set should not be empty"),
|
||||||
Err(err) => match err {
|
Err(err) => match err {
|
||||||
ValidationError::TxInsEmpty => (),
|
Byron(TxInsEmpty) => (),
|
||||||
_ => panic!("Unexpected error ({:?}).", err),
|
_ => panic!("Unexpected error ({:?})", err),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -192,34 +154,39 @@ mod byron_tests {
|
||||||
// Identical to successful_mainnet_tx, except that all outputs are removed.
|
// Identical to successful_mainnet_tx, except that all outputs are removed.
|
||||||
fn empty_outs() {
|
fn empty_outs() {
|
||||||
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
|
||||||
let mut mtxp: MintedTxPayload = mainnet_tx_from_bytes_cbor(&cbor_bytes);
|
let mut mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
|
||||||
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
|
||||||
&mtxp.transaction,
|
|
||||||
String::from(include_str!("../../test_data/byron1.address")),
|
|
||||||
19999000000,
|
|
||||||
);
|
|
||||||
// Clear the set of outputs in the transaction.
|
// Clear the set of outputs in the transaction.
|
||||||
let mut tx: Tx = (*mtxp.transaction).clone();
|
let mut tx: Tx = (*mtxp.transaction).clone();
|
||||||
tx.outputs = MaybeIndefArray::Def(Vec::new());
|
tx.outputs = MaybeIndefArray::Def(Vec::new());
|
||||||
let mut tx_buf: Vec<u8> = Vec::new();
|
let mut tx_buf: Vec<u8> = Vec::new();
|
||||||
match encode(tx, &mut tx_buf) {
|
match encode(tx, &mut tx_buf) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(err) => panic!("Unable to encode Tx ({:?}).", err),
|
Err(err) => panic!("Unable to encode Tx ({:?})", err),
|
||||||
};
|
};
|
||||||
mtxp.transaction = Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()).unwrap();
|
mtxp.transaction = Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
|
||||||
|
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
||||||
|
&mtxp.transaction,
|
||||||
|
String::from(include_str!("../../test_data/byron1.address")),
|
||||||
|
19999000000,
|
||||||
|
);
|
||||||
let env: Environment = Environment {
|
let env: Environment = Environment {
|
||||||
prot_params: MultiEraProtParams::Byron(ByronProtParams {
|
prot_params: MultiEraProtParams::Byron(ByronProtParams {
|
||||||
min_fees_const: 155381,
|
fee_policy: FeePolicy {
|
||||||
min_fees_factor: 44,
|
summand: 155381,
|
||||||
|
multiplier: 44,
|
||||||
|
},
|
||||||
max_tx_size: 4096,
|
max_tx_size: 4096,
|
||||||
}),
|
}),
|
||||||
prot_magic: 764824073,
|
prot_magic: 764824073,
|
||||||
|
block_slot: 3241381,
|
||||||
|
network_id: 1,
|
||||||
};
|
};
|
||||||
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
|
match validate(&metx, &utxos, &env) {
|
||||||
Ok(()) => panic!("Outputs set should not be empty."),
|
Ok(()) => assert!(false, "Outputs set should not be empty"),
|
||||||
Err(err) => match err {
|
Err(err) => match err {
|
||||||
ValidationError::TxOutsEmpty => (),
|
Byron(TxOutsEmpty) => (),
|
||||||
_ => panic!("Unexpected error ({:?}).", err),
|
_ => panic!("Unexpected error ({:?})", err),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -228,21 +195,26 @@ mod byron_tests {
|
||||||
// The transaction is valid, but the UTxO set is empty.
|
// The transaction is valid, but the UTxO set is empty.
|
||||||
fn unfound_utxo() {
|
fn unfound_utxo() {
|
||||||
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
|
||||||
let mtxp: MintedTxPayload = mainnet_tx_from_bytes_cbor(&cbor_bytes);
|
let mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
|
||||||
let utxos: UTxOs = UTxOs::new();
|
let utxos: UTxOs = UTxOs::new();
|
||||||
let env: Environment = Environment {
|
let env: Environment = Environment {
|
||||||
prot_params: MultiEraProtParams::Byron(ByronProtParams {
|
prot_params: MultiEraProtParams::Byron(ByronProtParams {
|
||||||
min_fees_const: 155381,
|
fee_policy: FeePolicy {
|
||||||
min_fees_factor: 44,
|
summand: 155381,
|
||||||
|
multiplier: 44,
|
||||||
|
},
|
||||||
max_tx_size: 4096,
|
max_tx_size: 4096,
|
||||||
}),
|
}),
|
||||||
prot_magic: 764824073,
|
prot_magic: 764824073,
|
||||||
|
block_slot: 3241381,
|
||||||
|
network_id: 1,
|
||||||
};
|
};
|
||||||
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
|
match validate(&metx, &utxos, &env) {
|
||||||
Ok(()) => panic!("All inputs must be within the UTxO set."),
|
Ok(()) => assert!(false, "All inputs must be within the UTxO set"),
|
||||||
Err(err) => match err {
|
Err(err) => match err {
|
||||||
ValidationError::InputMissingInUTxO => (),
|
Byron(InputNotInUTxO) => (),
|
||||||
_ => panic!("Unexpected error ({:?}).", err),
|
_ => panic!("Unexpected error ({:?})", err),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -251,12 +223,7 @@ mod byron_tests {
|
||||||
// All lovelace in one of the outputs was removed.
|
// All lovelace in one of the outputs was removed.
|
||||||
fn output_without_lovelace() {
|
fn output_without_lovelace() {
|
||||||
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
|
||||||
let mut mtxp: MintedTxPayload = mainnet_tx_from_bytes_cbor(&cbor_bytes);
|
let mut mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
|
||||||
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
|
||||||
&mtxp.transaction,
|
|
||||||
String::from(include_str!("../../test_data/byron1.address")),
|
|
||||||
19999000000,
|
|
||||||
);
|
|
||||||
// Remove lovelace from output.
|
// Remove lovelace from output.
|
||||||
let mut tx: Tx = (*mtxp.transaction).clone();
|
let mut tx: Tx = (*mtxp.transaction).clone();
|
||||||
let altered_tx_out: TxOut = TxOut {
|
let altered_tx_out: TxOut = TxOut {
|
||||||
|
|
@ -269,22 +236,32 @@ mod byron_tests {
|
||||||
let mut tx_buf: Vec<u8> = Vec::new();
|
let mut tx_buf: Vec<u8> = Vec::new();
|
||||||
match encode(tx, &mut tx_buf) {
|
match encode(tx, &mut tx_buf) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(err) => panic!("Unable to encode Tx ({:?}).", err),
|
Err(err) => panic!("Unable to encode Tx ({:?})", err),
|
||||||
};
|
};
|
||||||
mtxp.transaction = Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()).unwrap();
|
mtxp.transaction = Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
|
||||||
|
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
||||||
|
&mtxp.transaction,
|
||||||
|
String::from(include_str!("../../test_data/byron1.address")),
|
||||||
|
19999000000,
|
||||||
|
);
|
||||||
let env: Environment = Environment {
|
let env: Environment = Environment {
|
||||||
prot_params: MultiEraProtParams::Byron(ByronProtParams {
|
prot_params: MultiEraProtParams::Byron(ByronProtParams {
|
||||||
min_fees_const: 155381,
|
fee_policy: FeePolicy {
|
||||||
min_fees_factor: 44,
|
summand: 155381,
|
||||||
|
multiplier: 44,
|
||||||
|
},
|
||||||
max_tx_size: 4096,
|
max_tx_size: 4096,
|
||||||
}),
|
}),
|
||||||
prot_magic: 764824073,
|
prot_magic: 764824073,
|
||||||
|
block_slot: 3241381,
|
||||||
|
network_id: 1,
|
||||||
};
|
};
|
||||||
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
|
match validate(&metx, &utxos, &env) {
|
||||||
Ok(()) => panic!("All outputs must contain lovelace."),
|
Ok(()) => assert!(false, "All outputs must contain lovelace"),
|
||||||
Err(err) => match err {
|
Err(err) => match err {
|
||||||
ValidationError::OutputWithoutLovelace => (),
|
Byron(OutputWithoutLovelace) => (),
|
||||||
_ => panic!("Unexpected error ({:?}).", err),
|
_ => panic!("Unexpected error ({:?})", err),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -293,7 +270,8 @@ mod byron_tests {
|
||||||
// Expected fees are increased by increasing the protocol parameters.
|
// Expected fees are increased by increasing the protocol parameters.
|
||||||
fn not_enough_fees() {
|
fn not_enough_fees() {
|
||||||
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
|
||||||
let mtxp: MintedTxPayload = mainnet_tx_from_bytes_cbor(&cbor_bytes);
|
let mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
|
||||||
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
||||||
&mtxp.transaction,
|
&mtxp.transaction,
|
||||||
String::from(include_str!("../../test_data/byron1.address")),
|
String::from(include_str!("../../test_data/byron1.address")),
|
||||||
|
|
@ -301,17 +279,21 @@ mod byron_tests {
|
||||||
);
|
);
|
||||||
let env: Environment = Environment {
|
let env: Environment = Environment {
|
||||||
prot_params: MultiEraProtParams::Byron(ByronProtParams {
|
prot_params: MultiEraProtParams::Byron(ByronProtParams {
|
||||||
min_fees_const: 1000,
|
fee_policy: FeePolicy {
|
||||||
min_fees_factor: 1000,
|
summand: 1000,
|
||||||
|
multiplier: 1000,
|
||||||
|
},
|
||||||
max_tx_size: 4096,
|
max_tx_size: 4096,
|
||||||
}),
|
}),
|
||||||
prot_magic: 764824073,
|
prot_magic: 764824073,
|
||||||
|
block_slot: 3241381,
|
||||||
|
network_id: 1,
|
||||||
};
|
};
|
||||||
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
|
match validate(&metx, &utxos, &env) {
|
||||||
Ok(()) => panic!("Fees should not be below minimum."),
|
Ok(()) => assert!(false, "Fees should not be below minimum"),
|
||||||
Err(err) => match err {
|
Err(err) => match err {
|
||||||
ValidationError::FeesBelowMin => (),
|
Byron(FeesBelowMin) => (),
|
||||||
_ => panic!("Unexpected error ({:?}).", err),
|
_ => panic!("Unexpected error ({:?})", err),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -320,7 +302,8 @@ mod byron_tests {
|
||||||
// Tx size limit set by protocol parameters is established at 0.
|
// Tx size limit set by protocol parameters is established at 0.
|
||||||
fn tx_size_exceeds_max() {
|
fn tx_size_exceeds_max() {
|
||||||
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
|
||||||
let mtxp: MintedTxPayload = mainnet_tx_from_bytes_cbor(&cbor_bytes);
|
let mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
|
||||||
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
||||||
&mtxp.transaction,
|
&mtxp.transaction,
|
||||||
String::from(include_str!("../../test_data/byron1.address")),
|
String::from(include_str!("../../test_data/byron1.address")),
|
||||||
|
|
@ -328,17 +311,21 @@ mod byron_tests {
|
||||||
);
|
);
|
||||||
let env: Environment = Environment {
|
let env: Environment = Environment {
|
||||||
prot_params: MultiEraProtParams::Byron(ByronProtParams {
|
prot_params: MultiEraProtParams::Byron(ByronProtParams {
|
||||||
min_fees_const: 155381,
|
fee_policy: FeePolicy {
|
||||||
min_fees_factor: 44,
|
summand: 155381,
|
||||||
|
multiplier: 44,
|
||||||
|
},
|
||||||
max_tx_size: 0,
|
max_tx_size: 0,
|
||||||
}),
|
}),
|
||||||
prot_magic: 764824073,
|
prot_magic: 764824073,
|
||||||
|
block_slot: 3241381,
|
||||||
|
network_id: 1,
|
||||||
};
|
};
|
||||||
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
|
match validate(&metx, &utxos, &env) {
|
||||||
Ok(()) => panic!("Transaction size cannot exceed protocol limit."),
|
Ok(()) => assert!(false, "Transaction size cannot exceed protocol limit"),
|
||||||
Err(err) => match err {
|
Err(err) => match err {
|
||||||
ValidationError::MaxTxSizeExceeded => (),
|
Byron(MaxTxSizeExceeded) => (),
|
||||||
_ => panic!("Unexpected error ({:?}).", err),
|
_ => panic!("Unexpected error ({:?})", err),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -347,33 +334,38 @@ mod byron_tests {
|
||||||
// The input to the transaction does not have a corresponding witness.
|
// The input to the transaction does not have a corresponding witness.
|
||||||
fn missing_witness() {
|
fn missing_witness() {
|
||||||
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
|
||||||
let mut mtxp: MintedTxPayload = mainnet_tx_from_bytes_cbor(&cbor_bytes);
|
let mut mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
|
||||||
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
|
||||||
&mtxp.transaction,
|
|
||||||
String::from(include_str!("../../test_data/byron1.address")),
|
|
||||||
19999000000,
|
|
||||||
);
|
|
||||||
// Remove witness
|
// Remove witness
|
||||||
let new_witnesses: Witnesses = MaybeIndefArray::Def(Vec::new());
|
let new_witnesses: Witnesses = MaybeIndefArray::Def(Vec::new());
|
||||||
let mut tx_buf: Vec<u8> = Vec::new();
|
let mut tx_buf: Vec<u8> = Vec::new();
|
||||||
match encode(new_witnesses, &mut tx_buf) {
|
match encode(new_witnesses, &mut tx_buf) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(err) => panic!("Unable to encode Tx ({:?}).", err),
|
Err(err) => panic!("Unable to encode Tx ({:?})", err),
|
||||||
};
|
};
|
||||||
mtxp.witness = Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()).unwrap();
|
mtxp.witness = Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
|
||||||
|
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
||||||
|
&mtxp.transaction,
|
||||||
|
String::from(include_str!("../../test_data/byron1.address")),
|
||||||
|
19999000000,
|
||||||
|
);
|
||||||
let env: Environment = Environment {
|
let env: Environment = Environment {
|
||||||
prot_params: MultiEraProtParams::Byron(ByronProtParams {
|
prot_params: MultiEraProtParams::Byron(ByronProtParams {
|
||||||
min_fees_const: 155381,
|
fee_policy: FeePolicy {
|
||||||
min_fees_factor: 44,
|
summand: 155381,
|
||||||
|
multiplier: 44,
|
||||||
|
},
|
||||||
max_tx_size: 4096,
|
max_tx_size: 4096,
|
||||||
}),
|
}),
|
||||||
prot_magic: 764824073,
|
prot_magic: 764824073,
|
||||||
|
block_slot: 3241381,
|
||||||
|
network_id: 1,
|
||||||
};
|
};
|
||||||
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
|
match validate(&metx, &utxos, &env) {
|
||||||
Ok(()) => panic!("All inputs must have a witness signature."),
|
Ok(()) => assert!(false, "All inputs must have a witness signature"),
|
||||||
Err(err) => match err {
|
Err(err) => match err {
|
||||||
ValidationError::MissingWitness => (),
|
Byron(MissingWitness) => (),
|
||||||
_ => panic!("Unexpected error ({:?}).", err),
|
_ => panic!("Unexpected error ({:?})", err),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -383,12 +375,7 @@ mod byron_tests {
|
||||||
// wrong.
|
// wrong.
|
||||||
fn wrong_signature() {
|
fn wrong_signature() {
|
||||||
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/byron1.tx"));
|
||||||
let mut mtxp: MintedTxPayload = mainnet_tx_from_bytes_cbor(&cbor_bytes);
|
let mut mtxp: MintedTxPayload = tx_from_cbor(&cbor_bytes);
|
||||||
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
|
||||||
&mtxp.transaction,
|
|
||||||
String::from(include_str!("../../test_data/byron1.address")),
|
|
||||||
19999000000,
|
|
||||||
);
|
|
||||||
// Modify signature in witness
|
// Modify signature in witness
|
||||||
let new_wit: Twit = match mtxp.witness[0].clone() {
|
let new_wit: Twit = match mtxp.witness[0].clone() {
|
||||||
Twit::PkWitness(CborWrap((pk, _))) => {
|
Twit::PkWitness(CborWrap((pk, _))) => {
|
||||||
|
|
@ -402,26 +389,40 @@ mod byron_tests {
|
||||||
|
|
||||||
match encode(new_witnesses, &mut tx_buf) {
|
match encode(new_witnesses, &mut tx_buf) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(err) => panic!("Unable to encode Tx ({:?}).", err),
|
Err(err) => panic!("Unable to encode Tx ({:?})", err),
|
||||||
};
|
};
|
||||||
|
mtxp.witness = Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
|
||||||
mtxp.witness = Decode::decode(&mut Decoder::new(tx_buf.as_slice()), &mut ()).unwrap();
|
let metx: MultiEraTx = MultiEraTx::from_byron(&mtxp);
|
||||||
|
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
||||||
|
&mtxp.transaction,
|
||||||
|
String::from(include_str!("../../test_data/byron1.address")),
|
||||||
|
19999000000,
|
||||||
|
);
|
||||||
let env: Environment = Environment {
|
let env: Environment = Environment {
|
||||||
prot_params: MultiEraProtParams::Byron(ByronProtParams {
|
prot_params: MultiEraProtParams::Byron(ByronProtParams {
|
||||||
min_fees_const: 155381,
|
fee_policy: FeePolicy {
|
||||||
min_fees_factor: 44,
|
summand: 155381,
|
||||||
|
multiplier: 44,
|
||||||
|
},
|
||||||
max_tx_size: 4096,
|
max_tx_size: 4096,
|
||||||
}),
|
}),
|
||||||
prot_magic: 764824073,
|
prot_magic: 764824073,
|
||||||
|
block_slot: 3241381,
|
||||||
|
network_id: 1,
|
||||||
};
|
};
|
||||||
|
match validate(&metx, &utxos, &env) {
|
||||||
match mk_byron_tx_and_validate(&mtxp.transaction, &mtxp.witness, &utxos, &env) {
|
Ok(()) => assert!(false, "Witness signature should verify the transaction"),
|
||||||
Ok(()) => panic!("Witness signature should verify the transaction."),
|
|
||||||
Err(err) => match err {
|
Err(err) => match err {
|
||||||
ValidationError::WrongSignature => (),
|
Byron(WrongSignature) => (),
|
||||||
_ => panic!("Unexpected error ({:?}).", err),
|
_ => panic!("Unexpected error ({:?})", err),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper functions.
|
||||||
|
fn add_to_utxo<'a>(utxos: &mut UTxOs<'a>, tx_in: TxIn, tx_out: TxOut) {
|
||||||
|
let multi_era_in: MultiEraInput = MultiEraInput::Byron(Box::new(Cow::Owned(tx_in)));
|
||||||
|
let multi_era_out: MultiEraOutput = MultiEraOutput::Byron(Box::new(Cow::Owned(tx_out)));
|
||||||
|
utxos.insert(multi_era_in, multi_era_out);
|
||||||
|
}
|
||||||
|
|
|
||||||
727
pallas-applying/tests/shelley_ma.rs
Normal file
727
pallas-applying/tests/shelley_ma.rs
Normal file
|
|
@ -0,0 +1,727 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use pallas_addresses::{Address, Network, ShelleyAddress};
|
||||||
|
use pallas_applying::{
|
||||||
|
types::{
|
||||||
|
Environment, FeePolicy, MultiEraProtParams, ShelleyMAError::*, ShelleyProtParams,
|
||||||
|
ValidationError::*,
|
||||||
|
},
|
||||||
|
validate, UTxOs,
|
||||||
|
};
|
||||||
|
use pallas_codec::{
|
||||||
|
minicbor::{
|
||||||
|
decode::{Decode, Decoder},
|
||||||
|
encode,
|
||||||
|
},
|
||||||
|
utils::Bytes,
|
||||||
|
};
|
||||||
|
use pallas_crypto::hash::Hash;
|
||||||
|
use pallas_primitives::alonzo::{
|
||||||
|
MintedTx, MintedWitnessSet, TransactionBody, TransactionInput, TransactionOutput, VKeyWitness,
|
||||||
|
Value,
|
||||||
|
};
|
||||||
|
use pallas_traverse::{Era, MultiEraInput, MultiEraOutput, MultiEraTx};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod shelley_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn cbor_to_bytes(input: &str) -> Vec<u8> {
|
||||||
|
hex::decode(input).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn minted_tx_from_cbor<'a>(tx_cbor: &'a Vec<u8>) -> MintedTx<'a> {
|
||||||
|
pallas_codec::minicbor::decode::<MintedTx>(&tx_cbor[..]).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Careful: this function assumes tx_body has exactly one input.
|
||||||
|
fn mk_utxo_for_single_input_tx<'a>(
|
||||||
|
tx_body: &TransactionBody,
|
||||||
|
address: String,
|
||||||
|
amount: Value,
|
||||||
|
datum_hash: Option<Hash<32>>,
|
||||||
|
) -> UTxOs<'a> {
|
||||||
|
let tx_ins: &Vec<TransactionInput> = &tx_body.inputs;
|
||||||
|
assert_eq!(tx_ins.len(), 1, "Unexpected number of inputs");
|
||||||
|
let tx_in: TransactionInput = tx_ins.first().unwrap().clone();
|
||||||
|
let address_bytes: Bytes = match hex::decode(address) {
|
||||||
|
Ok(bytes_vec) => Bytes::from(bytes_vec),
|
||||||
|
_ => panic!("Unable to decode input address"),
|
||||||
|
};
|
||||||
|
let tx_out: TransactionOutput = TransactionOutput {
|
||||||
|
address: address_bytes,
|
||||||
|
amount,
|
||||||
|
datum_hash,
|
||||||
|
};
|
||||||
|
let mut utxos: UTxOs = UTxOs::new();
|
||||||
|
add_to_utxo(&mut utxos, tx_in, tx_out);
|
||||||
|
utxos
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// Transaction hash: 50eba65e73c8c5f7b09f4ea28cf15dce169f3d1c322ca3deff03725f51518bb2
|
||||||
|
fn successful_mainnet_shelley_tx() {
|
||||||
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
|
||||||
|
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
|
||||||
|
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
||||||
|
&mtx.transaction_body,
|
||||||
|
String::from(include_str!("../../test_data/shelley1.address")),
|
||||||
|
Value::Coin(2332267427205),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let env: Environment = Environment {
|
||||||
|
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
|
||||||
|
fee_policy: FeePolicy {
|
||||||
|
summand: 155381,
|
||||||
|
multiplier: 44,
|
||||||
|
},
|
||||||
|
max_tx_size: 4096,
|
||||||
|
min_lovelace: 1000000,
|
||||||
|
}),
|
||||||
|
prot_magic: 764824073,
|
||||||
|
block_slot: 5281340,
|
||||||
|
network_id: 1,
|
||||||
|
};
|
||||||
|
match validate(&metx, &utxos, &env) {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(err) => assert!(false, "Unexpected error ({:?})", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// Transaction hash: 4a3f86762383f1d228542d383ae7ac89cf75cf7ff84dec8148558ea92b0b92d0
|
||||||
|
fn successful_mainnet_shelley_tx_with_script() {
|
||||||
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley2.tx"));
|
||||||
|
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
|
||||||
|
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
||||||
|
&mtx.transaction_body,
|
||||||
|
String::from(include_str!("../../test_data/shelley2.address")),
|
||||||
|
Value::Coin(2000000),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let env: Environment = Environment {
|
||||||
|
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
|
||||||
|
fee_policy: FeePolicy {
|
||||||
|
summand: 155381,
|
||||||
|
multiplier: 44,
|
||||||
|
},
|
||||||
|
max_tx_size: 4096,
|
||||||
|
min_lovelace: 1000000,
|
||||||
|
}),
|
||||||
|
prot_magic: 764824073,
|
||||||
|
block_slot: 17584925,
|
||||||
|
network_id: 1,
|
||||||
|
};
|
||||||
|
match validate(&metx, &utxos, &env) {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(err) => assert!(false, "Unexpected error ({:?})", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// Transaction hash: c220e20cc480df9ce7cd871df491d7390c6a004b9252cf20f45fc3c968535b4a
|
||||||
|
fn successful_mainnet_shelley_tx_with_metadata() {
|
||||||
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley3.tx"));
|
||||||
|
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
|
||||||
|
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
||||||
|
&mtx.transaction_body,
|
||||||
|
String::from(include_str!("../../test_data/shelley3.address")),
|
||||||
|
Value::Coin(10000000),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let env: Environment = Environment {
|
||||||
|
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
|
||||||
|
fee_policy: FeePolicy {
|
||||||
|
summand: 155381,
|
||||||
|
multiplier: 44,
|
||||||
|
},
|
||||||
|
max_tx_size: 4096,
|
||||||
|
min_lovelace: 1000000,
|
||||||
|
}),
|
||||||
|
prot_magic: 764824073,
|
||||||
|
block_slot: 5860488,
|
||||||
|
network_id: 1,
|
||||||
|
};
|
||||||
|
match validate(&metx, &utxos, &env) {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(err) => assert!(false, "Unexpected error ({:?})", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// Transaction hash: b7b1046d1787ac6917f5bb5841e73b3f4bef8f0a6bf692d05ef18e1db9c3f519
|
||||||
|
fn successful_mainnet_mary_tx_with_minting() {
|
||||||
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/mary1.tx"));
|
||||||
|
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Mary);
|
||||||
|
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
||||||
|
&mtx.transaction_body,
|
||||||
|
String::from(include_str!("../../test_data/mary1.address")),
|
||||||
|
Value::Coin(3500000),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let env: Environment = Environment {
|
||||||
|
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
|
||||||
|
fee_policy: FeePolicy {
|
||||||
|
summand: 155381,
|
||||||
|
multiplier: 44,
|
||||||
|
},
|
||||||
|
max_tx_size: 4096,
|
||||||
|
min_lovelace: 1000000,
|
||||||
|
}),
|
||||||
|
prot_magic: 764824073,
|
||||||
|
block_slot: 24381863,
|
||||||
|
network_id: 1,
|
||||||
|
};
|
||||||
|
match validate(&metx, &utxos, &env) {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(err) => assert!(false, "Unexpected error ({:?})", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// All inputs are removed.
|
||||||
|
fn empty_ins() {
|
||||||
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
|
||||||
|
let mut mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
|
||||||
|
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
||||||
|
&mtx.transaction_body,
|
||||||
|
String::from(include_str!("../../test_data/shelley1.address")),
|
||||||
|
Value::Coin(2332267427205),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
// Clear the set of inputs in the transaction.
|
||||||
|
let mut tx_body: TransactionBody = (*mtx.transaction_body).clone();
|
||||||
|
tx_body.inputs = Vec::new();
|
||||||
|
let mut tx_buf: Vec<u8> = Vec::new();
|
||||||
|
match encode(tx_body, &mut tx_buf) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(err) => assert!(false, "Unable to encode Tx ({:?})", err),
|
||||||
|
};
|
||||||
|
mtx.transaction_body =
|
||||||
|
Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
|
||||||
|
let env: Environment = Environment {
|
||||||
|
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
|
||||||
|
fee_policy: FeePolicy {
|
||||||
|
summand: 155381,
|
||||||
|
multiplier: 44,
|
||||||
|
},
|
||||||
|
max_tx_size: 4096,
|
||||||
|
min_lovelace: 1000000,
|
||||||
|
}),
|
||||||
|
prot_magic: 764824073,
|
||||||
|
block_slot: 5281340,
|
||||||
|
network_id: 1,
|
||||||
|
};
|
||||||
|
match validate(&metx, &utxos, &env) {
|
||||||
|
Ok(()) => assert!(false, "Inputs set should not be empty"),
|
||||||
|
Err(err) => match err {
|
||||||
|
Shelley(TxInsEmpty) => (),
|
||||||
|
_ => assert!(false, "Unexpected error ({:?})", err),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// The UTxO set is empty.
|
||||||
|
fn unfound_utxo() {
|
||||||
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
|
||||||
|
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
|
||||||
|
let utxos: UTxOs = UTxOs::new();
|
||||||
|
let env: Environment = Environment {
|
||||||
|
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
|
||||||
|
fee_policy: FeePolicy {
|
||||||
|
summand: 155381,
|
||||||
|
multiplier: 44,
|
||||||
|
},
|
||||||
|
max_tx_size: 4096,
|
||||||
|
min_lovelace: 1000000,
|
||||||
|
}),
|
||||||
|
prot_magic: 764824073,
|
||||||
|
block_slot: 5281340,
|
||||||
|
network_id: 1,
|
||||||
|
};
|
||||||
|
match validate(&metx, &utxos, &env) {
|
||||||
|
Ok(()) => assert!(false, "All inputs must be within the UTxO set"),
|
||||||
|
Err(err) => match err {
|
||||||
|
Shelley(InputNotInUTxO) => (),
|
||||||
|
_ => assert!(false, "Unexpected error ({:?})", err),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// Time-to-live is missing.
|
||||||
|
fn missing_ttl() {
|
||||||
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
|
||||||
|
let mut mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
|
||||||
|
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
||||||
|
&mtx.transaction_body,
|
||||||
|
String::from(include_str!("../../test_data/shelley1.address")),
|
||||||
|
Value::Coin(2332267427205),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let mut tx_body: TransactionBody = (*mtx.transaction_body).clone();
|
||||||
|
tx_body.ttl = None;
|
||||||
|
let mut tx_buf: Vec<u8> = Vec::new();
|
||||||
|
match encode(tx_body, &mut tx_buf) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(err) => assert!(false, "Unable to encode Tx ({:?})", err),
|
||||||
|
};
|
||||||
|
mtx.transaction_body =
|
||||||
|
Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
|
||||||
|
let env: Environment = Environment {
|
||||||
|
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
|
||||||
|
fee_policy: FeePolicy {
|
||||||
|
summand: 155381,
|
||||||
|
multiplier: 44,
|
||||||
|
},
|
||||||
|
max_tx_size: 4096,
|
||||||
|
min_lovelace: 1000000,
|
||||||
|
}),
|
||||||
|
prot_magic: 764824073,
|
||||||
|
block_slot: 5281340,
|
||||||
|
network_id: 1,
|
||||||
|
};
|
||||||
|
match validate(&metx, &utxos, &env) {
|
||||||
|
Ok(()) => assert!(false, "TTL must always be present in Shelley transactions"),
|
||||||
|
Err(err) => match err {
|
||||||
|
Shelley(AlonzoCompNotShelley) => (),
|
||||||
|
_ => assert!(false, "Unexpected error ({:?})", err),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// Transaction's time-to-live is before block slot.
|
||||||
|
fn ttl_exceeded() {
|
||||||
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
|
||||||
|
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
|
||||||
|
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
||||||
|
&mtx.transaction_body,
|
||||||
|
String::from(include_str!("../../test_data/shelley1.address")),
|
||||||
|
Value::Coin(2332267427205),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let env: Environment = Environment {
|
||||||
|
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
|
||||||
|
fee_policy: FeePolicy {
|
||||||
|
summand: 155381,
|
||||||
|
multiplier: 44,
|
||||||
|
},
|
||||||
|
max_tx_size: 4096,
|
||||||
|
min_lovelace: 1000000,
|
||||||
|
}),
|
||||||
|
prot_magic: 764824073,
|
||||||
|
block_slot: 9999999,
|
||||||
|
network_id: 1,
|
||||||
|
};
|
||||||
|
match validate(&metx, &utxos, &env) {
|
||||||
|
Ok(()) => assert!(false, "TTL cannot be exceeded"),
|
||||||
|
Err(err) => match err {
|
||||||
|
Shelley(TTLExceeded) => (),
|
||||||
|
_ => assert!(false, "Unexpected error ({:?})", err),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// Transaction size exceeds max limit (namely, 0).
|
||||||
|
fn max_tx_size_exceeded() {
|
||||||
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
|
||||||
|
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
|
||||||
|
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
||||||
|
&mtx.transaction_body,
|
||||||
|
String::from(include_str!("../../test_data/shelley1.address")),
|
||||||
|
Value::Coin(2332267427205),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let env: Environment = Environment {
|
||||||
|
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
|
||||||
|
fee_policy: FeePolicy {
|
||||||
|
summand: 155381,
|
||||||
|
multiplier: 44,
|
||||||
|
},
|
||||||
|
max_tx_size: 0,
|
||||||
|
min_lovelace: 1000000,
|
||||||
|
}),
|
||||||
|
prot_magic: 764824073,
|
||||||
|
block_slot: 5281340,
|
||||||
|
network_id: 1,
|
||||||
|
};
|
||||||
|
match validate(&metx, &utxos, &env) {
|
||||||
|
Ok(()) => assert!(false, "Tx size exceeds max limit"),
|
||||||
|
Err(err) => match err {
|
||||||
|
Shelley(MaxTxSizeExceeded) => (),
|
||||||
|
_ => assert!(false, "Unexpected error ({:?})", err),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// Min lovelace per UTxO is too high (10000000000000 lovelace against 2332262258756 lovelace in
|
||||||
|
// transaction output).
|
||||||
|
fn output_below_min_lovelace() {
|
||||||
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
|
||||||
|
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
|
||||||
|
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
||||||
|
&mtx.transaction_body,
|
||||||
|
String::from(include_str!("../../test_data/shelley1.address")),
|
||||||
|
Value::Coin(2332267427205),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let env: Environment = Environment {
|
||||||
|
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
|
||||||
|
fee_policy: FeePolicy {
|
||||||
|
summand: 155381,
|
||||||
|
multiplier: 44,
|
||||||
|
},
|
||||||
|
max_tx_size: 4096,
|
||||||
|
min_lovelace: 10000000000000,
|
||||||
|
}),
|
||||||
|
prot_magic: 764824073,
|
||||||
|
block_slot: 5281340,
|
||||||
|
network_id: 1,
|
||||||
|
};
|
||||||
|
match validate(&metx, &utxos, &env) {
|
||||||
|
Ok(()) => assert!(false, "Output amount must be above min lovelace value"),
|
||||||
|
Err(err) => match err {
|
||||||
|
Shelley(MinLovelaceUnreached) => (),
|
||||||
|
_ => assert!(false, "Unexpected error ({:?})", err),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// The "preservation of value" property doesn't hold - the fee is reduced by exactly 1.
|
||||||
|
fn preservation_of_value() {
|
||||||
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
|
||||||
|
let mut mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
|
||||||
|
let mut tx_body: TransactionBody = (*mtx.transaction_body).clone();
|
||||||
|
tx_body.fee = tx_body.fee - 1;
|
||||||
|
let mut tx_buf: Vec<u8> = Vec::new();
|
||||||
|
match encode(tx_body, &mut tx_buf) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(err) => assert!(false, "Unable to encode Tx ({:?})", err),
|
||||||
|
};
|
||||||
|
mtx.transaction_body =
|
||||||
|
Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
|
||||||
|
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
||||||
|
&mtx.transaction_body,
|
||||||
|
String::from(include_str!("../../test_data/shelley1.address")),
|
||||||
|
Value::Coin(2332267427205),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let env: Environment = Environment {
|
||||||
|
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
|
||||||
|
fee_policy: FeePolicy {
|
||||||
|
summand: 155381,
|
||||||
|
multiplier: 44,
|
||||||
|
},
|
||||||
|
max_tx_size: 4096,
|
||||||
|
min_lovelace: 1000000,
|
||||||
|
}),
|
||||||
|
prot_magic: 764824073,
|
||||||
|
block_slot: 5281340,
|
||||||
|
network_id: 1,
|
||||||
|
};
|
||||||
|
match validate(&metx, &utxos, &env) {
|
||||||
|
Ok(()) => assert!(false, "Preservation of value property doesn't hold"),
|
||||||
|
Err(err) => match err {
|
||||||
|
Shelley(PreservationOfValue) => (),
|
||||||
|
_ => assert!(false, "Unexpected error ({:?})", err),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// Fee policy imposes higher fees on the transaction.
|
||||||
|
fn fee_below_minimum() {
|
||||||
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
|
||||||
|
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
|
||||||
|
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
||||||
|
&mtx.transaction_body,
|
||||||
|
String::from(include_str!("../../test_data/shelley1.address")),
|
||||||
|
Value::Coin(2332267427205),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let env: Environment = Environment {
|
||||||
|
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
|
||||||
|
fee_policy: FeePolicy {
|
||||||
|
summand: 155381,
|
||||||
|
multiplier: 70, // This value was 44 during Shelley on mainnet.
|
||||||
|
},
|
||||||
|
max_tx_size: 4096,
|
||||||
|
min_lovelace: 1000000,
|
||||||
|
}),
|
||||||
|
prot_magic: 764824073,
|
||||||
|
block_slot: 5281340,
|
||||||
|
network_id: 1,
|
||||||
|
};
|
||||||
|
match validate(&metx, &utxos, &env) {
|
||||||
|
Ok(()) => assert!(false, "Fee should not be below minimum"),
|
||||||
|
Err(err) => match err {
|
||||||
|
Shelley(FeesBelowMin) => (),
|
||||||
|
_ => assert!(false, "Unexpected error ({:?})", err),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// One of the output's address network ID is changed from the mainnet value to the testnet one.
|
||||||
|
fn wrong_network_id() {
|
||||||
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
|
||||||
|
let mut mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
|
||||||
|
// Modify the first output address.
|
||||||
|
let mut tx_body: TransactionBody = (*mtx.transaction_body).clone();
|
||||||
|
let (first_output, rest): (&TransactionOutput, &[TransactionOutput]) =
|
||||||
|
(&tx_body.outputs).split_first().unwrap();
|
||||||
|
let addr: ShelleyAddress =
|
||||||
|
match Address::from_bytes(&Vec::<u8>::from(first_output.address.clone())) {
|
||||||
|
Ok(Address::Shelley(sa)) => sa,
|
||||||
|
Ok(_) => panic!("Decoded output address and found the wrong era"),
|
||||||
|
Err(e) => panic!("Unable to parse output address ({:?})", e),
|
||||||
|
};
|
||||||
|
let altered_address: ShelleyAddress = ShelleyAddress::new(
|
||||||
|
Network::Testnet,
|
||||||
|
addr.payment().clone(),
|
||||||
|
addr.delegation().clone(),
|
||||||
|
);
|
||||||
|
let altered_output: TransactionOutput = TransactionOutput {
|
||||||
|
address: Bytes::from(altered_address.to_vec()),
|
||||||
|
amount: first_output.amount.clone(),
|
||||||
|
datum_hash: first_output.datum_hash,
|
||||||
|
};
|
||||||
|
let mut new_outputs = Vec::from(rest);
|
||||||
|
new_outputs.insert(0, altered_output);
|
||||||
|
tx_body.outputs = new_outputs;
|
||||||
|
let mut tx_buf: Vec<u8> = Vec::new();
|
||||||
|
match encode(tx_body, &mut tx_buf) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(err) => assert!(false, "Unable to encode Tx ({:?})", err),
|
||||||
|
};
|
||||||
|
mtx.transaction_body =
|
||||||
|
Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
|
||||||
|
let env: Environment = Environment {
|
||||||
|
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
|
||||||
|
fee_policy: FeePolicy {
|
||||||
|
summand: 155381,
|
||||||
|
multiplier: 44,
|
||||||
|
},
|
||||||
|
max_tx_size: 4096,
|
||||||
|
min_lovelace: 1000000,
|
||||||
|
}),
|
||||||
|
prot_magic: 764824073,
|
||||||
|
block_slot: 5281340,
|
||||||
|
network_id: 1,
|
||||||
|
};
|
||||||
|
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
||||||
|
&mtx.transaction_body,
|
||||||
|
String::from(include_str!("../../test_data/shelley1.address")),
|
||||||
|
Value::Coin(2332267427205),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
match validate(&metx, &utxos, &env) {
|
||||||
|
Ok(()) => assert!(false, "Output with wrong network ID should be rejected"),
|
||||||
|
Err(err) => match err {
|
||||||
|
Shelley(WrongNetworkID) => (),
|
||||||
|
_ => assert!(false, "Unexpected error ({:?})", err),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// Like successful_mainnet_shelley_tx_with_metadata (hash:
|
||||||
|
// c220e20cc480df9ce7cd871df491d7390c6a004b9252cf20f45fc3c968535b4a)
|
||||||
|
fn auxiliary_data_removed() {
|
||||||
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley3.tx"));
|
||||||
|
let mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
|
||||||
|
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
||||||
|
&mtx.transaction_body,
|
||||||
|
String::from(include_str!("../../test_data/shelley3.address")),
|
||||||
|
Value::Coin(10000000),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let env: Environment = Environment {
|
||||||
|
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
|
||||||
|
fee_policy: FeePolicy {
|
||||||
|
summand: 155381,
|
||||||
|
multiplier: 44,
|
||||||
|
},
|
||||||
|
max_tx_size: 4096,
|
||||||
|
min_lovelace: 1000000,
|
||||||
|
}),
|
||||||
|
prot_magic: 764824073,
|
||||||
|
block_slot: 5860488,
|
||||||
|
network_id: 1,
|
||||||
|
};
|
||||||
|
match validate(&metx, &utxos, &env) {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(err) => assert!(false, "Unexpected error ({:?})", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// Like successful_mainnet_shelley_tx (hash:
|
||||||
|
// 50eba65e73c8c5f7b09f4ea28cf15dce169f3d1c322ca3deff03725f51518bb2), but the verification-key
|
||||||
|
// witness is removed.
|
||||||
|
fn missing_vk_witness() {
|
||||||
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
|
||||||
|
let mut mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
|
||||||
|
// Modify the first output address.
|
||||||
|
let mut tx_wits: MintedWitnessSet = (*mtx.transaction_witness_set).clone();
|
||||||
|
tx_wits.vkeywitness = Some(Vec::new());
|
||||||
|
let mut tx_buf: Vec<u8> = Vec::new();
|
||||||
|
match encode(tx_wits, &mut tx_buf) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(err) => assert!(false, "Unable to encode Tx ({:?})", err),
|
||||||
|
};
|
||||||
|
mtx.transaction_witness_set =
|
||||||
|
Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
|
||||||
|
let env: Environment = Environment {
|
||||||
|
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
|
||||||
|
fee_policy: FeePolicy {
|
||||||
|
summand: 155381,
|
||||||
|
multiplier: 44,
|
||||||
|
},
|
||||||
|
max_tx_size: 4096,
|
||||||
|
min_lovelace: 1000000,
|
||||||
|
}),
|
||||||
|
prot_magic: 764824073,
|
||||||
|
block_slot: 5281340,
|
||||||
|
network_id: 1,
|
||||||
|
};
|
||||||
|
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
||||||
|
&mtx.transaction_body,
|
||||||
|
String::from(include_str!("../../test_data/shelley1.address")),
|
||||||
|
Value::Coin(2332267427205),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
match validate(&metx, &utxos, &env) {
|
||||||
|
Ok(()) => assert!(false, "Missing verification key witness"),
|
||||||
|
Err(err) => match err {
|
||||||
|
Shelley(MissingVKWitness) => (),
|
||||||
|
_ => assert!(false, "Unexpected error ({:?})", err),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// Like successful_mainnet_shelley_tx (hash:
|
||||||
|
// 50eba65e73c8c5f7b09f4ea28cf15dce169f3d1c322ca3deff03725f51518bb2), but the signature inside
|
||||||
|
// the verification-key witness is changed.
|
||||||
|
fn vk_witness_changed() {
|
||||||
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley1.tx"));
|
||||||
|
let mut mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
|
||||||
|
// Modify the first output address.
|
||||||
|
let mut tx_wits: MintedWitnessSet = (*mtx.transaction_witness_set).clone();
|
||||||
|
let mut wit: VKeyWitness = tx_wits.vkeywitness.clone().unwrap().pop().unwrap();
|
||||||
|
let mut sig_as_vec: Vec<u8> = wit.signature.to_vec();
|
||||||
|
sig_as_vec.pop();
|
||||||
|
sig_as_vec.push(0u8);
|
||||||
|
wit.signature = Bytes::from(sig_as_vec);
|
||||||
|
tx_wits.vkeywitness = Some(Vec::from([wit]));
|
||||||
|
let mut tx_buf: Vec<u8> = Vec::new();
|
||||||
|
match encode(tx_wits, &mut tx_buf) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(err) => assert!(false, "Unable to encode Tx ({:?})", err),
|
||||||
|
};
|
||||||
|
mtx.transaction_witness_set =
|
||||||
|
Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
|
||||||
|
let env: Environment = Environment {
|
||||||
|
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
|
||||||
|
fee_policy: FeePolicy {
|
||||||
|
summand: 155381,
|
||||||
|
multiplier: 44,
|
||||||
|
},
|
||||||
|
max_tx_size: 4096,
|
||||||
|
min_lovelace: 1000000,
|
||||||
|
}),
|
||||||
|
prot_magic: 764824073,
|
||||||
|
block_slot: 5281340,
|
||||||
|
network_id: 1,
|
||||||
|
};
|
||||||
|
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
||||||
|
&mtx.transaction_body,
|
||||||
|
String::from(include_str!("../../test_data/shelley1.address")),
|
||||||
|
Value::Coin(2332267427205),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
match validate(&metx, &utxos, &env) {
|
||||||
|
Ok(()) => assert!(false, "Missing verification key witness"),
|
||||||
|
Err(err) => match err {
|
||||||
|
Shelley(WrongSignature) => (),
|
||||||
|
_ => assert!(false, "Unexpected error ({:?})", err),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// Like successful_mainnet_shelley_tx_with_script(hash:
|
||||||
|
// 4a3f86762383f1d228542d383ae7ac89cf75cf7ff84dec8148558ea92b0b92d0), but the native-script
|
||||||
|
// witness is removed.
|
||||||
|
fn missing_native_script_witness() {
|
||||||
|
let cbor_bytes: Vec<u8> = cbor_to_bytes(include_str!("../../test_data/shelley2.tx"));
|
||||||
|
let mut mtx: MintedTx = minted_tx_from_cbor(&cbor_bytes);
|
||||||
|
// Modify the first output address.
|
||||||
|
let mut tx_wits: MintedWitnessSet = (*mtx.transaction_witness_set).clone();
|
||||||
|
tx_wits.native_script = Some(Vec::new());
|
||||||
|
let mut tx_buf: Vec<u8> = Vec::new();
|
||||||
|
match encode(tx_wits, &mut tx_buf) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(err) => assert!(false, "Unable to encode Tx ({:?})", err),
|
||||||
|
};
|
||||||
|
mtx.transaction_witness_set =
|
||||||
|
Decode::decode(&mut Decoder::new(&tx_buf.as_slice()), &mut ()).unwrap();
|
||||||
|
let metx: MultiEraTx = MultiEraTx::from_alonzo_compatible(&mtx, Era::Shelley);
|
||||||
|
let env: Environment = Environment {
|
||||||
|
prot_params: MultiEraProtParams::Shelley(ShelleyProtParams {
|
||||||
|
fee_policy: FeePolicy {
|
||||||
|
summand: 155381,
|
||||||
|
multiplier: 44,
|
||||||
|
},
|
||||||
|
max_tx_size: 4096,
|
||||||
|
min_lovelace: 1000000,
|
||||||
|
}),
|
||||||
|
prot_magic: 764824073,
|
||||||
|
block_slot: 5281340,
|
||||||
|
network_id: 1,
|
||||||
|
};
|
||||||
|
let utxos: UTxOs = mk_utxo_for_single_input_tx(
|
||||||
|
&mtx.transaction_body,
|
||||||
|
String::from(include_str!("../../test_data/shelley2.address")),
|
||||||
|
Value::Coin(2000000),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
match validate(&metx, &utxos, &env) {
|
||||||
|
Ok(()) => assert!(false, "Missing native script witness"),
|
||||||
|
Err(err) => match err {
|
||||||
|
Shelley(MissingScriptWitness) => (),
|
||||||
|
_ => assert!(false, "Unexpected error ({:?})", err),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions.
|
||||||
|
fn add_to_utxo<'a>(utxos: &mut UTxOs<'a>, tx_in: TransactionInput, tx_out: TransactionOutput) {
|
||||||
|
let multi_era_in: MultiEraInput = MultiEraInput::AlonzoCompatible(Box::new(Cow::Owned(tx_in)));
|
||||||
|
let multi_era_out: MultiEraOutput =
|
||||||
|
MultiEraOutput::AlonzoCompatible(Box::new(Cow::Owned(tx_out)));
|
||||||
|
utxos.insert(multi_era_in, multi_era_out);
|
||||||
|
}
|
||||||
1
test_data/mary1.address
Normal file
1
test_data/mary1.address
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
611489ac0c22c04abc9c6de7f95d71e1ba2c95c9b4e2f6f2900f682285
|
||||||
1
test_data/mary1.tx
Normal file
1
test_data/mary1.tx
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
84a5008182582027c39310c79aa7e37d1fba4e455698e9c918b41183b146a38acfcc0bc223792000018182581d611489ac0c22c04abc9c6de7f95d71e1ba2c95c9b4e2f6f2900f682285821a0032a7fba1581cf523573c4df900cf0fe16312aa7445877098b2a001dced3cc1283358a14a546f6d61746f436f696e1a000f4240021a0002bfe5031a01740de209a1581cf523573c4df900cf0fe16312aa7445877098b2a001dced3cc1283358a14a546f6d61746f436f696e1a000f4240a200828258204cbc1c2c8a6bd40ec53ef8a09cf1a2f78c301cb1185d78c5740b574399a2a71058404e0176cd7004cfe6163fce238836cf67a8fe49395d1beaa5500b05b23395482879bc9e01493429798516b9ffec98135f518943bf13513e85b6a659d290627806825820d3a8b343712f3734f39f95f01080c53514e070f2f115b5c9234ee5f7b554745b58405e35fe7f6e7f2402757c7aff566c4190a6031c47ff6dc8fc0e01b3a8323d1ecf41175c43877e34664c0f1c331a96222498f70586361d9c01f7218a26495a0d03018182018282051a01740de28200581c1d53b024073333d411cb1585adfe5f02cd2410f65e33b666d0c9633cf5f6
|
||||||
1
test_data/shelley1.address
Normal file
1
test_data/shelley1.address
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
0129bb156d52d014bb444a14138cbee36044c6faed37d0c2d49d2358315c465cbf8c5536970e8a29bb7adcda0d663b20007d481813694c64ef
|
||||||
1
test_data/shelley1.tx
Normal file
1
test_data/shelley1.tx
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
84a4008182582031cf218c94a63e2a5d1f054751c062ada6add8ae2fbe75dabaf2fe2cea9a2619000182825839019c1bb4c1b426ef53afdfc6f6011b6a8ca22b0aaa8330080cca5ab64e5c465cbf8c5536970e8a29bb7adcda0d663b20007d481813694c64ef1b0000021f05a9d84482583901988de808740f48086ae4a372f417a33eb1a4c22d24a88f30efc304b0c9fe643d0170b639353141c6cbff9dfc9eddcba34c8fbac3a4d4d83c1a004c4b40021a00029201031a0050b248a10081825820a8beae2d04b36fecbfec7eaf121656932929e150aa58ae1ff7091571872d96d15840a904723932843fc56cc57afc44d766d229413095d0027360879f09e04a867d19fdc7f8d464c75cde8cbb209760d665e18903f330d74116e188705d66c0accd02f5f6
|
||||||
1
test_data/shelley2.address
Normal file
1
test_data/shelley2.address
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
7165c197d565e88a20885e535f93755682444d3c02fd44dd70883fe89e
|
||||||
1
test_data/shelley2.tx
Normal file
1
test_data/shelley2.tx
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
84a40081825820e7db1f809fcc21d3dd108ced6218bf0f0cbb6a0f679f848ff1790b68d3a35872000181825839010c57a4aa08aaa7c42b45e4e9490151e2665dbb7d374e795ad5be5e4960562a0d213c675c2b84ee0e34eb377d4abbe82a4c256a0708baac251a0016e360021a0007a120031a010c59f8a200838258205df1be8b0071123c982a94c19c0c06485dbe9271e4381e8cf4fc2ed554ac133f5840c271a9d652ae95e6a8a5117c5026369235182da7e4fe040b02465089e5e2caf05063cf8b61601e6f6074c03f700bacaadcd62483c48d66a5f8d418a7c27c4b01825820403171966fadb1ce9b26852cb74018a04bc031a4aee92be39702b18efd75e058584039395858906ec9ab7540e79022b25a1f3bdfece09f9e2f36254eb5abe625b72dbd8179ece4c9fc1d537afce95b67d8095d29e1f3c50de4ecc30fd67e1ba440048258206311da054c5dfa3ac53c9fc3be859bd322f0712f7e093596fa5f6de031d95acd58403d7deba60f80a03f5bf172c1699f07ecef557d9509551cbe8d2b9000e903c3e3f60bb127c9c0b4cd5df84c01791b98e10fe19209088d0b085c74702b25914a0d01818201838200581ca96da581c39549aeda81f539ac3940ac0cb53657e774ca7e68f15ed98200581cccfcb3fed004562be1354c837a4a4b9f4b1c2b6705229efeedd12d4d8200581c74fcd61aecebe36aa6b6cd4314027282fa4b41c3ce8af17d9b77d0d1f5f6
|
||||||
1
test_data/shelley3.address
Normal file
1
test_data/shelley3.address
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
61c96001f4a4e10567ac18be3c47663a00a858f51c56779e94993d30ef
|
||||||
1
test_data/shelley3.tx
Normal file
1
test_data/shelley3.tx
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
84a500818258205b06f6ea129a404d5bc610880be35376625a8f7f11773bf79db1889eb3bb87eb00018182581d61c96001f4a4e10567ac18be3c47663a00a858f51c56779e94993d30ef1a0095e957021a0002ad29031a005991b0075820c2d2b42fbacf30eeddab1447f525297eec0ab134f8cddd2025a075c69d57e4bca100818258204251d746864839409bc2bb6dfbb680c503c3a2613dba0ac55c6791eaebd9ad84584057e649e46b1711bfd45cb2ae0e4ecb8c863e5c261545f0ec96fe6ee3fc8dd5b36106fd13d21e643c34e04c58b18759afaca58f990060b4342dd7369bd11b1d06f5a101a368766f7465725f69646f3132336162633030306362613332316662616c6c6f74a36669737375653163796573666973737565336e416c7068612043656e746175726966697373756532626e6f67766f74655f696469616263313233303030
|
||||||
Loading…
Add table
Add a link
Reference in a new issue