feat(applying): implement ShelleyMA phase-1 validations (#354)
This commit is contained in:
parent
ffcd70c57a
commit
4e1eb79ef4
15 changed files with 1640 additions and 206 deletions
|
|
@ -3,7 +3,9 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use crate::types::{
|
||||
ByronProtParams, MultiEraInput, MultiEraOutput, SigningTag, UTxOs, ValidationError,
|
||||
ByronError::*,
|
||||
ByronProtParams, MultiEraInput, MultiEraOutput, SigningTag, UTxOs,
|
||||
ValidationError::{self, *},
|
||||
ValidationResult,
|
||||
};
|
||||
|
||||
|
|
@ -30,26 +32,26 @@ pub fn validate_byron_tx(
|
|||
prot_magic: &u32,
|
||||
) -> ValidationResult {
|
||||
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_outs_not_empty(tx)?;
|
||||
check_ins_in_utxos(tx, utxos)?;
|
||||
check_outs_have_lovelace(tx)?;
|
||||
check_fees(tx, &size, utxos, prot_pps)?;
|
||||
check_size(&size, prot_pps)?;
|
||||
check_fees(tx, size, utxos, prot_pps)?;
|
||||
check_size(size, prot_pps)?;
|
||||
check_witnesses(mtxp, utxos, prot_magic)
|
||||
}
|
||||
|
||||
fn check_ins_not_empty(tx: &Tx) -> ValidationResult {
|
||||
if tx.inputs.clone().to_vec().is_empty() {
|
||||
return Err(ValidationError::TxInsEmpty);
|
||||
return Err(Byron(TxInsEmpty));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_outs_not_empty(tx: &Tx) -> ValidationResult {
|
||||
if tx.outputs.clone().to_vec().is_empty() {
|
||||
return Err(ValidationError::TxOutsEmpty);
|
||||
return Err(Byron(TxOutsEmpty));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -57,7 +59,7 @@ fn check_outs_not_empty(tx: &Tx) -> ValidationResult {
|
|||
fn check_ins_in_utxos(tx: &Tx, utxos: &UTxOs) -> ValidationResult {
|
||||
for input in tx.inputs.iter() {
|
||||
if !(utxos.contains_key(&MultiEraInput::from_byron(input))) {
|
||||
return Err(ValidationError::InputMissingInUTxO);
|
||||
return Err(Byron(InputNotInUTxO));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -66,7 +68,7 @@ fn check_ins_in_utxos(tx: &Tx, utxos: &UTxOs) -> ValidationResult {
|
|||
fn check_outs_have_lovelace(tx: &Tx) -> ValidationResult {
|
||||
for output in tx.outputs.iter() {
|
||||
if output.amount == 0 {
|
||||
return Err(ValidationError::OutputWithoutLovelace);
|
||||
return Err(Byron(OutputWithoutLovelace));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -84,7 +86,7 @@ fn check_fees(tx: &Tx, size: &u64, utxos: &UTxOs, prot_pps: &ByronProtParams) ->
|
|||
.and_then(MultiEraOutput::as_byron)
|
||||
{
|
||||
Some(byron_utxo) => inputs_balance += byron_utxo.amount,
|
||||
None => return Err(ValidationError::UnableToComputeFees),
|
||||
None => return Err(Byron(UnableToComputeFees)),
|
||||
}
|
||||
}
|
||||
if only_redeem_utxos {
|
||||
|
|
@ -95,9 +97,9 @@ fn check_fees(tx: &Tx, size: &u64, utxos: &UTxOs, prot_pps: &ByronProtParams) ->
|
|||
outputs_balance += output.amount
|
||||
}
|
||||
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 {
|
||||
Err(ValidationError::FeesBelowMin)
|
||||
Err(Byron(FeesBelowMin))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -119,7 +121,7 @@ fn is_redeem_utxo(input: &TxIn, utxos: &UTxOs) -> bool {
|
|||
|
||||
fn check_size(size: &u64, prot_pps: &ByronProtParams) -> ValidationResult {
|
||||
if *size > prot_pps.max_tx_size {
|
||||
return Err(ValidationError::MaxTxSizeExceeded);
|
||||
return Err(Byron(MaxTxSizeExceeded));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -128,7 +130,7 @@ fn get_tx_size(tx: &Tx) -> Result<u64, ValidationError> {
|
|||
let mut buff: Vec<u8> = Vec::new();
|
||||
match encode(tx, &mut buff) {
|
||||
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 signature: Signature = get_signature(sign);
|
||||
if !public_key.verify(data_to_verify, &signature) {
|
||||
return Err(ValidationError::WrongSignature);
|
||||
return Err(Byron(WrongSignature));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -165,7 +167,7 @@ fn tag_witnesses(wits: &[Twit]) -> Result<Vec<(&PubKey, TaggedSignature)>, Valid
|
|||
Twit::RedeemWitness(CborWrap((pk, sig))) => {
|
||||
res.push((pk, TaggedSignature::RedeemWitness(sig)));
|
||||
}
|
||||
_ => return Err(ValidationError::UnableToProcessWitnesses),
|
||||
_ => return Err(Byron(UnableToProcessWitness)),
|
||||
}
|
||||
}
|
||||
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)));
|
||||
utxos
|
||||
.get(&key)
|
||||
.ok_or(ValidationError::InputMissingInUTxO)?
|
||||
.ok_or(Byron(InputNotInUTxO))?
|
||||
.as_byron()
|
||||
.ok_or(ValidationError::InputMissingInUTxO)
|
||||
.ok_or(Byron(InputNotInUTxO))
|
||||
}
|
||||
|
||||
fn find_raw_witness<'a>(
|
||||
|
|
@ -187,7 +189,7 @@ fn find_raw_witness<'a>(
|
|||
let address: ByronAddress = mk_byron_address(&tx_out.address);
|
||||
let addr_payload: AddressPayload = address
|
||||
.decode()
|
||||
.map_err(|_| ValidationError::UnableToProcessWitnesses)?;
|
||||
.map_err(|_| Byron(UnableToProcessWitness))?;
|
||||
let root: AddressId = addr_payload.root;
|
||||
let attr: AddrAttrs = addr_payload.attributes;
|
||||
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) {
|
||||
match addr_type {
|
||||
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 {
|
||||
|
|
@ -250,17 +252,17 @@ fn get_data_to_verify(
|
|||
match sign {
|
||||
TaggedSignature::PkWitness(_) => {
|
||||
enc.encode(SigningTag::Tx as u64)
|
||||
.map_err(|_| ValidationError::UnableToProcessWitnesses)?;
|
||||
.map_err(|_| Byron(UnableToProcessWitness))?;
|
||||
}
|
||||
TaggedSignature::RedeemWitness(_) => {
|
||||
enc.encode(SigningTag::RedeemTx as u64)
|
||||
.map_err(|_| ValidationError::UnableToProcessWitnesses)?;
|
||||
.map_err(|_| Byron(UnableToProcessWitness))?;
|
||||
}
|
||||
}
|
||||
enc.encode(prot_magic)
|
||||
.map_err(|_| ValidationError::UnableToProcessWitnesses)?;
|
||||
.map_err(|_| Byron(UnableToProcessWitness))?;
|
||||
enc.encode(tx_hash)
|
||||
.map_err(|_| ValidationError::UnableToProcessWitnesses)?;
|
||||
.map_err(|_| Byron(UnableToProcessWitness))?;
|
||||
Ok(enc.into_writer().clone())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,24 +1,41 @@
|
|||
//! Logic for validating and applying new blocks and txs to the chain state
|
||||
|
||||
pub mod byron;
|
||||
pub mod shelley_ma;
|
||||
pub mod types;
|
||||
|
||||
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, ValidationResult};
|
||||
pub use types::{
|
||||
Environment, MultiEraProtParams, UTxOs, ValidationError::TxAndProtParamsDiffer,
|
||||
ValidationResult,
|
||||
};
|
||||
|
||||
pub fn validate(metx: &MultiEraTx, utxos: &UTxOs, env: &Environment) -> ValidationResult {
|
||||
match (metx, env) {
|
||||
(
|
||||
ByronTxPayload(mtxp),
|
||||
Environment {
|
||||
prot_params: MultiEraProtParams::Byron(bpp),
|
||||
prot_magic,
|
||||
match env {
|
||||
Environment {
|
||||
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),
|
||||
// TODO: implement the rest of the eras.
|
||||
_ => Ok(()),
|
||||
_ => Err(TxAndProtParamsDiffer),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
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)]
|
||||
pub struct ByronProtParams {
|
||||
pub min_fees_const: u64,
|
||||
pub min_fees_factor: u64,
|
||||
pub fee_policy: FeePolicy,
|
||||
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.
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum MultiEraProtParams {
|
||||
Byron(ByronProtParams),
|
||||
Shelley(ShelleyProtParams),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Environment {
|
||||
pub prot_params: MultiEraProtParams,
|
||||
pub prot_magic: u32,
|
||||
pub block_slot: u64,
|
||||
pub network_id: u8,
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
|
|
@ -35,17 +50,49 @@ pub enum SigningTag {
|
|||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum ValidationError {
|
||||
InputMissingInUTxO,
|
||||
TxAndProtParamsDiffer,
|
||||
Byron(ByronError),
|
||||
Shelley(ShelleyMAError),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum ByronError {
|
||||
TxInsEmpty,
|
||||
TxOutsEmpty,
|
||||
InputNotInUTxO,
|
||||
OutputWithoutLovelace,
|
||||
UnknownTxSize,
|
||||
UnableToComputeFees,
|
||||
FeesBelowMin,
|
||||
MaxTxSizeExceeded,
|
||||
UnableToProcessWitnesses,
|
||||
UnableToProcessWitness,
|
||||
MissingWitness,
|
||||
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>;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue