feat: add Babbage phase-1 validations (#405)

This commit is contained in:
Maico Leberle 2024-02-20 20:30:09 -03:00 committed by GitHub
parent 7a67453b58
commit eade9eca82
32 changed files with 4584 additions and 143 deletions

View file

@ -1,9 +1,10 @@
//! Utilities required for Shelley-era transaction validation.
//! Utilities required for Alonzo-era transaction validation.
use crate::utils::{
add_minted_value, add_values, empty_value, extract_auxiliary_data, get_alonzo_comp_tx_size,
get_lovelace_from_alonzo_val, get_network_id_value, get_payment_part, get_shelley_address,
get_val_size_in_words, mk_alonzo_vk_wits_check_list, values_are_equal, verify_signature,
add_minted_value, add_values, aux_data_from_alonzo_minted_tx, compute_native_script_hash,
compute_plutus_script_hash, empty_value, get_alonzo_comp_tx_size, get_lovelace_from_alonzo_val,
get_network_id_value, get_payment_part, get_shelley_address, get_val_size_in_words,
mk_alonzo_vk_wits_check_list, values_are_equal, verify_signature,
AlonzoError::*,
AlonzoProtParams, FeePolicy, UTxOs,
ValidationError::{self, *},
@ -18,9 +19,9 @@ use pallas_codec::{
use pallas_crypto::hash::Hash;
use pallas_primitives::{
alonzo::{
AddrKeyhash, Mint, MintedTx, MintedWitnessSet, NativeScript, PlutusData, PlutusScript,
PolicyId, Redeemer, RedeemerPointer, RedeemerTag, RequiredSigners, TransactionBody,
TransactionInput, TransactionOutput, VKeyWitness, Value,
AddrKeyhash, Mint, MintedTx, MintedWitnessSet, Multiasset, NativeScript, PlutusData,
PlutusScript, PolicyId, Redeemer, RedeemerPointer, RedeemerTag, RequiredSigners,
TransactionBody, TransactionInput, TransactionOutput, VKeyWitness, Value,
},
byron::TxOut,
};
@ -48,7 +49,7 @@ pub fn validate_alonzo_tx(
check_tx_ex_units(mtx, prot_pps)?;
check_witness_set(mtx, utxos)?;
check_languages(mtx, prot_pps)?;
check_metadata(tx_body, mtx)?;
check_auxiliary_data(tx_body, mtx)?;
check_script_data_hash(tx_body, mtx)?;
check_minting(tx_body, mtx)
}
@ -201,14 +202,15 @@ fn check_collaterals_address(collaterals: &[TransactionInput], utxos: &UTxOs) ->
Some(multi_era_output) => {
if let Some(alonzo_comp_output) = MultiEraOutput::as_alonzo(multi_era_output) {
if let ShelleyPaymentPart::Script(_) =
get_payment_part(alonzo_comp_output).ok_or(Alonzo(InputDecoding))?
get_payment_part(&alonzo_comp_output.address)
.ok_or(Alonzo(InputDecoding))?
{
return Err(Alonzo(CollateralNotVKeyLocked));
}
}
}
None => return Err(Alonzo(CollateralNotInUTxO)),
};
}
}
Ok(())
}
@ -258,12 +260,11 @@ fn check_collaterals_assets(
// The preservation of value property holds.
fn check_preservation_of_value(tx_body: &TransactionBody, utxos: &UTxOs) -> ValidationResult {
let neg_val_err: ValidationError = Alonzo(NegativeValue);
let input: Value = get_consumed(tx_body, utxos)?;
let mut input: Value = get_consumed(tx_body, utxos)?;
let produced: Value = get_produced(tx_body)?;
let output: Value = add_values(&produced, &Value::Coin(tx_body.fee), &neg_val_err)?;
let output: Value = add_values(&produced, &Value::Coin(tx_body.fee), &Alonzo(NegativeValue))?;
if let Some(m) = &tx_body.mint {
add_minted_value(&output, m, &neg_val_err)?;
input = add_minted_value(&input, m, &Alonzo(NegativeValue))?;
}
if !values_are_equal(&input, &output) {
return Err(Alonzo(PreservationOfValue));
@ -272,17 +273,18 @@ fn check_preservation_of_value(tx_body: &TransactionBody, utxos: &UTxOs) -> Vali
}
fn get_consumed(tx_body: &TransactionBody, utxos: &UTxOs) -> Result<Value, ValidationError> {
let neg_val_err: ValidationError = Alonzo(NegativeValue);
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(Alonzo(InputNotInUTxO))?;
match MultiEraOutput::as_alonzo(utxo_value) {
Some(TransactionOutput { amount, .. }) => res = add_values(&res, amount, &neg_val_err)?,
Some(TransactionOutput { amount, .. }) => {
res = add_values(&res, amount, &Alonzo(NegativeValue))?
}
None => match MultiEraOutput::as_byron(utxo_value) {
Some(TxOut { amount, .. }) => {
res = add_values(&res, &Value::Coin(*amount), &neg_val_err)?
res = add_values(&res, &Value::Coin(*amount), &Alonzo(NegativeValue))?
}
_ => return Err(Alonzo(InputNotInUTxO)),
},
@ -292,10 +294,9 @@ fn get_consumed(tx_body: &TransactionBody, utxos: &UTxOs) -> Result<Value, Valid
}
fn get_produced(tx_body: &TransactionBody) -> Result<Value, ValidationError> {
let neg_val_err: ValidationError = Alonzo(NegativeValue);
let mut res: Value = empty_value();
for TransactionOutput { amount, .. } in tx_body.outputs.iter() {
res = add_values(&res, amount, &neg_val_err)?;
res = add_values(&res, amount, &Alonzo(NegativeValue))?;
}
Ok(res)
}
@ -311,12 +312,12 @@ fn check_min_lovelace(tx_body: &TransactionBody, prot_pps: &AlonzoProtParams) ->
}
fn compute_min_lovelace(output: &TransactionOutput, prot_pps: &AlonzoProtParams) -> u64 {
let utxo_entry_size: u64 = get_val_size_in_words(&output.amount)
let output_entry_size: u64 = get_val_size_in_words(&output.amount)
+ match output.datum_hash {
Some(_) => 37, // utxoEntrySizeWithoutVal (27) + dataHashSize (10)
None => 27, // utxoEntrySizeWithoutVal
};
prot_pps.coins_per_utxo_word * utxo_entry_size
prot_pps.coins_per_utxo_word * output_entry_size
}
// The size of the value in each of the outputs should not be greater than the
@ -433,7 +434,7 @@ fn check_needed_scripts_are_included(
return Err(Alonzo(UnneededNativeScript));
}
}
for (plutus_script_covered, _) in native_scripts.iter() {
for (plutus_script_covered, _) in plutus_scripts.iter() {
if !plutus_script_covered {
return Err(Alonzo(UnneededPlutusScript));
}
@ -533,19 +534,23 @@ fn check_redeemers(
.collect(),
None => Vec::new(),
};
let plutus_scripts: Vec<RedeemerPointer> =
mk_plutus_script_redeemer_pointers(tx_body, tx_wits, utxos);
let plutus_scripts: Vec<RedeemerPointer> = mk_plutus_script_redeemer_pointers(
&sort_inputs(&tx_body.inputs),
&tx_body.mint,
tx_wits,
utxos,
);
redeemer_pointers_coincide(&redeemer_pointers, &plutus_scripts)
}
fn mk_plutus_script_redeemer_pointers(
tx_body: &TransactionBody,
sorted_inputs: &[TransactionInput],
mint: &Option<Multiasset<i64>>,
tx_wits: &MintedWitnessSet,
utxos: &UTxOs,
) -> Vec<RedeemerPointer> {
match &tx_wits.plutus_script {
Some(plutus_scripts) => {
let sorted_inputs: Vec<TransactionInput> = sort_inputs(&tx_body.inputs);
let mut res: Vec<RedeemerPointer> = Vec::new();
for (index, input) in sorted_inputs.iter().enumerate() {
if let Some(script_hash) = get_script_hash_from_input(input, utxos) {
@ -560,7 +565,7 @@ fn mk_plutus_script_redeemer_pointers(
}
}
}
match &tx_body.mint {
match mint {
Some(minted_value) => {
let sorted_policies: Vec<PolicyId> = sort_policies(minted_value);
for (index, policy) in sorted_policies.iter().enumerate() {
@ -664,7 +669,7 @@ fn get_script_hash_from_input(input: &TransactionInput, utxos: &UTxOs) -> Option
utxos
.get(&MultiEraInput::from_alonzo_compatible(input))
.and_then(MultiEraOutput::as_alonzo)
.and_then(get_payment_part)
.and_then(|tx_out| get_payment_part(&tx_out.address))
.and_then(|payment_part| match payment_part {
ShelleyPaymentPart::Script(script_hash) => Some(script_hash),
_ => None,
@ -707,19 +712,6 @@ fn check_minting_policies(
}
}
fn compute_native_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)
}
fn compute_plutus_script_hash(script: &PlutusScript) -> PolicyId {
let mut payload: Vec<u8> = Vec::from(script.as_ref());
payload.insert(0, 1);
pallas_crypto::hash::Hasher::<224>::hash(&payload)
}
// The owner of each transaction input and each collateral input should have
// signed the transaction.
fn check_vkey_input_wits(
@ -741,7 +733,9 @@ fn check_vkey_input_wits(
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).ok_or(Alonzo(InputDecoding))? {
match get_payment_part(&alonzo_comp_output.address)
.ok_or(Alonzo(InputDecoding))?
{
ShelleyPaymentPart::Key(payment_key_hash) => {
check_vk_wit(&payment_key_hash, vk_wits, tx_hash)?
}
@ -833,8 +827,11 @@ fn check_languages(_mtx: &MintedTx, _prot_pps: &AlonzoProtParams) -> ValidationR
}
// The metadata of the transaction is valid.
fn check_metadata(tx_body: &TransactionBody, mtx: &MintedTx) -> ValidationResult {
match (&tx_body.auxiliary_data_hash, extract_auxiliary_data(mtx)) {
fn check_auxiliary_data(tx_body: &TransactionBody, mtx: &MintedTx) -> ValidationResult {
match (
&tx_body.auxiliary_data_hash,
aux_data_from_alonzo_minted_tx(mtx),
) {
(Some(metadata_hash), Some(metadata)) => {
if metadata_hash.as_slice()
== pallas_crypto::hash::Hasher::<256>::hash(metadata).as_ref()
@ -925,7 +922,11 @@ fn check_minting(tx_body: &TransactionBody, mtx: &MintedTx) -> ValidationResult
.map(|x| x.clone().unwrap())
.collect(),
};
let plutus_script_wits: Vec<PlutusScript> = Vec::new();
let plutus_script_wits: Vec<PlutusScript> =
match &mtx.transaction_witness_set.plutus_script {
None => Vec::new(),
Some(plutus_script_wits) => plutus_script_wits.clone(),
};
for (policy, _) in minted_value.iter() {
if native_script_wits
.iter()

File diff suppressed because it is too large Load diff

View file

@ -1,11 +1,13 @@
//! Logic for validating and applying new blocks and txs to the chain state
pub mod alonzo;
pub mod babbage;
pub mod byron;
pub mod shelley_ma;
pub mod utils;
use alonzo::validate_alonzo_tx;
use babbage::validate_babbage_tx;
use byron::validate_byron_tx;
use pallas_traverse::{Era, MultiEraTx};
use shelley_ma::validate_shelley_ma_tx;
@ -40,5 +42,11 @@ pub fn validate(metx: &MultiEraTx, utxos: &UTxOs, env: &Environment) -> Validati
}
_ => Err(TxAndProtParamsDiffer),
},
MultiEraProtParams::Babbage(bpp) => match metx {
MultiEraTx::Babbage(mtx) => {
validate_babbage_tx(mtx, utxos, bpp, env.block_slot(), env.network_id())
}
_ => Err(TxAndProtParamsDiffer),
},
}
}

View file

@ -1,9 +1,10 @@
//! Utilities required for ShelleyMA-era transaction validation.
use crate::utils::{
add_minted_value, add_values, empty_value, extract_auxiliary_data, get_alonzo_comp_tx_size,
get_lovelace_from_alonzo_val, get_payment_part, get_shelley_address, get_val_size_in_words,
mk_alonzo_vk_wits_check_list, values_are_equal, verify_signature, FeePolicy,
add_minted_value, add_values, aux_data_from_alonzo_minted_tx, empty_value,
get_alonzo_comp_tx_size, get_lovelace_from_alonzo_val, get_payment_part, get_shelley_address,
get_val_size_in_words, mk_alonzo_vk_wits_check_list, values_are_equal, verify_signature,
FeePolicy,
ShelleyMAError::*,
ShelleyProtParams, UTxOs,
ValidationError::{self, *},
@ -195,7 +196,10 @@ fn check_network_id(tx_body: &TransactionBody, network_id: &u8) -> ValidationRes
}
fn check_metadata(tx_body: &TransactionBody, mtx: &MintedTx) -> ValidationResult {
match (&tx_body.auxiliary_data_hash, extract_auxiliary_data(mtx)) {
match (
&tx_body.auxiliary_data_hash,
aux_data_from_alonzo_minted_tx(mtx),
) {
(Some(metadata_hash), Some(metadata)) => {
if metadata_hash.as_slice()
== pallas_crypto::hash::Hasher::<256>::hash(metadata).as_ref()
@ -222,7 +226,9 @@ fn check_witnesses(
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).ok_or(ShelleyMA(AddressDecoding))? {
match get_payment_part(&alonzo_comp_output.address)
.ok_or(ShelleyMA(AddressDecoding))?
{
ShelleyPaymentPart::Key(payment_key_hash) => {
check_vk_wit(&payment_key_hash, tx_hash, vk_wits)?
}

View file

@ -10,9 +10,12 @@ use pallas_codec::{
utils::{Bytes, KeepRaw, KeyValuePairs},
};
use pallas_crypto::key::ed25519::{PublicKey, Signature};
use pallas_primitives::alonzo::{
AssetName, AuxiliaryData, Coin, MintedTx, Multiasset, NetworkId, PolicyId, TransactionBody,
TransactionOutput, VKeyWitness, Value,
use pallas_primitives::{
alonzo::{
AssetName, AuxiliaryData, Coin, MintedTx as AlonzoMintedTx, Multiasset, NativeScript,
NetworkId, PlutusScript, PolicyId, TransactionBody, VKeyWitness, Value,
},
babbage::{MintedTransactionBody, MintedTx as BabbageMintedTx, PlutusV2Script},
};
use pallas_traverse::{MultiEraInput, MultiEraOutput};
use std::collections::HashMap;
@ -29,6 +32,14 @@ pub fn get_alonzo_comp_tx_size(tx_body: &TransactionBody) -> Option<u64> {
}
}
pub fn get_babbage_tx_size(tx_body: &MintedTransactionBody) -> Option<u64> {
let mut buff: Vec<u8> = Vec::new();
match encode(tx_body, &mut buff) {
Ok(()) => Some(buff.len() as u64),
Err(_) => None,
}
}
pub fn empty_value() -> Value {
Value::Multiasset(0, Multiasset::<Coin>::from(Vec::new()))
}
@ -52,6 +63,61 @@ pub fn add_values(
}
}
pub fn lovelace_diff_or_fail(
first: &Value,
second: &Value,
err: &ValidationError,
) -> Result<u64, ValidationError> {
match (first, second) {
(Value::Coin(f), Value::Coin(s)) => {
if f >= s {
Ok(f - s)
} else {
Err(err.clone())
}
}
(Value::Coin(_), Value::Multiasset(_, _)) => Err(err.clone()),
(Value::Multiasset(f, fma), Value::Coin(s)) => {
if f >= s && fma.is_empty() {
Ok(f - s)
} else {
Err(err.clone())
}
}
(Value::Multiasset(f, fma), Value::Multiasset(s, sma)) => {
if f >= s && multi_assets_are_equal(fma, sma) {
Ok(f - s)
} else {
Err(err.clone())
}
}
}
}
pub fn multi_assets_are_equal(fma: &Multiasset<Coin>, sma: &Multiasset<Coin>) -> bool {
for (fpolicy, fassets) in fma.iter() {
match find_policy(sma, fpolicy) {
Some(sassets) => {
for (fasset_name, famount) in fassets.iter() {
// Discard the case where there is 0 of an asset
if *famount != 0 {
match find_assets(&sassets, fasset_name) {
Some(samount) => {
if *famount != samount {
return false;
}
}
None => return false,
};
}
}
}
None => return false,
}
}
true
}
pub fn add_minted_value(
base_value: &Value,
minted_value: &Multiasset<i64>,
@ -155,24 +221,7 @@ pub fn values_are_equal(first: &Value, second: &Value) -> bool {
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
multi_assets_are_equal(fma, sma)
}
}
}
@ -235,8 +284,8 @@ pub fn verify_signature(vk_wit: &VKeyWitness, data_to_verify: &[u8]) -> bool {
public_key.verify(data_to_verify, &sig)
}
pub fn get_payment_part(tx_out: &TransactionOutput) -> Option<ShelleyPaymentPart> {
let addr: ShelleyAddress = get_shelley_address(Bytes::deref(&tx_out.address))?;
pub fn get_payment_part(address: &Bytes) -> Option<ShelleyPaymentPart> {
let addr: ShelleyAddress = get_shelley_address(Bytes::deref(address))?;
Some(addr.payment().clone())
}
@ -247,7 +296,17 @@ pub fn get_shelley_address(address: &[u8]) -> Option<ShelleyAddress> {
}
}
pub fn extract_auxiliary_data<'a>(mtx: &'a MintedTx) -> Option<&'a [u8]> {
pub fn is_byron_address(address: &[u8]) -> bool {
matches!(Address::from_bytes(address), Ok(Address::Byron(_)))
}
pub fn aux_data_from_alonzo_minted_tx<'a>(mtx: &'a AlonzoMintedTx) -> Option<&'a [u8]> {
Option::<KeepRaw<AuxiliaryData>>::from((mtx.auxiliary_data).clone())
.as_ref()
.map(KeepRaw::raw_cbor)
}
pub fn aux_data_from_babbage_minted_tx<'a>(mtx: &'a BabbageMintedTx) -> Option<&'a [u8]> {
Option::<KeepRaw<AuxiliaryData>>::from((mtx.auxiliary_data).clone())
.as_ref()
.map(KeepRaw::raw_cbor)
@ -258,3 +317,22 @@ pub fn get_val_size_in_words(val: &Value) -> u64 {
let _ = encode(val, &mut tx_buf);
(tx_buf.len() as u64 + 7) / 8 // ceiling of the result of dividing
}
pub fn compute_native_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)
}
pub fn compute_plutus_script_hash(script: &PlutusScript) -> PolicyId {
let mut payload: Vec<u8> = Vec::from(script.as_ref());
payload.insert(0, 1);
pallas_crypto::hash::Hasher::<224>::hash(&payload)
}
pub fn compute_plutus_v2_script_hash(script: &PlutusV2Script) -> PolicyId {
let mut payload: Vec<u8> = Vec::from(script.as_ref());
payload.insert(0, 1);
pallas_crypto::hash::Hasher::<224>::hash(&payload)
}

View file

@ -16,6 +16,7 @@ pub enum MultiEraProtParams {
Byron(ByronProtParams),
Shelley(ShelleyProtParams),
Alonzo(AlonzoProtParams),
Babbage(BabbageProtParams),
}
#[derive(Debug, Clone)]
@ -51,6 +52,20 @@ pub struct AlonzoProtParams {
pub coins_per_utxo_word: u64,
}
#[derive(Debug, Clone)]
pub struct BabbageProtParams {
pub fee_policy: FeePolicy,
pub max_tx_size: u64,
pub max_block_ex_mem: u64,
pub max_block_ex_steps: u64,
pub max_tx_ex_mem: u32,
pub max_tx_ex_steps: u64,
pub max_val_size: u64,
pub collateral_percent: u64,
pub max_collateral_inputs: u64,
pub coins_per_utxo_word: u64,
}
impl Environment {
pub fn prot_params(&self) -> &MultiEraProtParams {
&self.prot_params

View file

@ -7,6 +7,7 @@ pub enum ValidationError {
Byron(ByronError),
ShelleyMA(ShelleyMAError),
Alonzo(AlonzoError),
Babbage(BabbageError),
}
#[derive(Debug, Clone)]
@ -91,4 +92,51 @@ pub enum AlonzoError {
ScriptIntegrityHash,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum BabbageError {
UnknownTxSize,
TxInsEmpty,
InputNotInUTxO,
CollateralNotInUTxO,
ReferenceInputNotInUTxO,
RefInputNotInUTxO,
BlockPrecedesValInt,
BlockExceedsValInt,
FeeBelowMin,
CollateralMissing,
TooManyCollaterals,
InputDecoding,
CollateralNotVKeyLocked,
CollateralMinLovelace,
NonLovelaceCollateral,
CollateralWrongAssets,
NegativeValue,
CollateralAnnotation,
PreservationOfValue,
MinLovelaceUnreached,
MaxValSizeExceeded,
AddressDecoding,
OutputWrongNetworkID,
TxWrongNetworkID,
TxExUnitsExceeded,
RedeemerMissing,
UnneededRedeemer,
MaxTxSizeExceeded,
MintingLacksPolicy,
MetadataHash,
DatumMissing,
UnneededDatum,
ScriptWitnessMissing,
UnneededNativeScript,
UnneededPlutusV1Script,
UnneededPlutusV2Script,
ReqSignerMissing,
ReqSignerWrongSig,
VKWitnessMissing,
VKWrongSignature,
UnsupportedPlutusLanguage,
ScriptIntegrityHash,
}
pub type ValidationResult = Result<(), ValidationError>;