Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d3945fe
Initial release
shd Nov 4, 2025
f409f66
Elementary working pass
shd Nov 7, 2025
23ebba1
Merge branch 'main' of github.com:input-output-hk/acropolis into shd/…
shd Nov 7, 2025
c572a5e
Warning removed
shd Nov 7, 2025
8d752f9
CBOR Validation error added
shd Nov 10, 2025
d1dc077
Merge branch 'main' of github.com:input-output-hk/acropolis into shd/…
shd Nov 10, 2025
6b401a8
Merge, moved common part for tx unpacking
shd Nov 17, 2025
a0c292e
Merged
shd Nov 17, 2025
eb78754
Merge branch 'main' of github.com:input-output-hk/acropolis into shd/…
shd Nov 19, 2025
93cca84
fix: merge conflicts
golddydev Nov 20, 2025
4eff40e
feat: add transaction validation error enums for shelley era and adde…
golddydev Nov 21, 2025
d77693d
feat: added custom macros for transaction validation test cases
golddydev Nov 21, 2025
51d843a
refactor: reorganize tests folder structure
golddydev Nov 24, 2025
b3a0820
Merge branch 'main' into shd/tx-validator-phase1
golddydev Nov 24, 2025
f89bbe5
refactor: add 2 validation rules for shelley
golddydev Nov 24, 2025
7705f30
refactor: add shelley params to test context
golddydev Nov 25, 2025
1a0c692
Merge branch 'main' into shd/tx-validator-phase1
golddydev Nov 26, 2025
4a1cb36
wip: parsing pallas tx
golddydev Nov 26, 2025
8a74a54
Merge branch 'main' into shd/tx-validator-phase1
golddydev Nov 27, 2025
8e13171
feat: add more shelley era utxo rules check
golddydev Nov 27, 2025
c985d70
fix: cargo shear
golddydev Nov 27, 2025
62456e0
refactor: context for utxo rule test
golddydev Nov 28, 2025
0dfc8ce
Merge branch 'main' into shd/tx-validator-phase1
golddydev Nov 28, 2025
4f84655
refactor: mapping transaction function, and remove unused d tx valida…
golddydev Nov 28, 2025
109d632
fix: don't check byron address's network
golddydev Nov 28, 2025
20f33e8
feat: add state to tx unpacker for tx validation
golddydev Nov 28, 2025
de9ed93
fix: decode transaction for specific era
golddydev Dec 1, 2025
95a98df
refactor: add more test cases and update test utils
golddydev Dec 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
295 changes: 155 additions & 140 deletions Cargo.lock

Large diffs are not rendered by default.

115 changes: 110 additions & 5 deletions codec/src/map_parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ use pallas::ledger::{
};

use acropolis_common::hash::Hash;
use acropolis_common::validation::ValidationError;
use acropolis_common::{
protocol_params::{Nonce, NonceVariant, ProtocolVersion},
rational_number::RationalNumber,
*,
};
use pallas_primitives::conway::PseudoScript;
use pallas_traverse::{MultiEraInput, MultiEraTx};
use std::{
collections::{HashMap, HashSet},
net::{Ipv4Addr, Ipv6Addr},
Expand Down Expand Up @@ -265,16 +267,14 @@ pub fn to_pool_reg(
reward_account: if force_reward_network_id {
StakeAddress::new(
StakeAddress::from_binary(reward_account)?.credential,
network_id.clone(),
network_id,
)
} else {
StakeAddress::from_binary(reward_account)?
},
pool_owners: pool_owners
.iter()
.map(|v| {
StakeAddress::new(StakeCredential::AddrKeyHash(to_hash(v)), network_id.clone())
})
.map(|v| StakeAddress::new(StakeCredential::AddrKeyHash(to_hash(v)), network_id))
.collect(),
relays: relays.iter().map(map_relay).collect(),
pool_metadata: match pool_metadata {
Expand Down Expand Up @@ -379,7 +379,7 @@ pub fn map_certificate(
InstantaneousRewardTarget::StakeAddresses(
creds
.iter()
.map(|(sc, v)| (map_stake_address(sc, network_id.clone()), *v))
.map(|(sc, v)| (map_stake_address(sc, network_id), *v))
.collect(),
)
}
Expand Down Expand Up @@ -990,6 +990,111 @@ pub fn map_all_governance_voting_procedures(
Ok(procs)
}

// pub struct TransactionRefInfo {}

// pub fn map_transaction_refs(
// inputs: &Vec<MultiEraInput>,
// outputs: &Vec<(usize, MultiEraOutput)>,
// ) -> (Vec<TxOutRef>, Vec<TxOutRef>) {
// let mut ref_inps = Vec::new();
// for input in inputs {
// // MultiEraInput
// let oref = input.output_ref();
// let tx_ref = TxOutRef::new(TxHash::from(**oref.hash()), oref.index() as u16);
// ref_inps.push(tx_ref);
// }

// let mut ref_outs = Vec::new();
// for (index, output) in outputs {}

// (ref_inps, ref_outs)
// }

pub fn map_transaction_inputs(inputs: &Vec<MultiEraInput>) -> Vec<TxOutRef> {
let mut parsed_inputs = Vec::new();
for input in inputs {
// MultiEraInput
let oref = input.output_ref();
let tx_ref = TxOutRef::new(TxHash::from(**oref.hash()), oref.index() as u16);

parsed_inputs.push(tx_ref);
}

parsed_inputs
}

pub fn map_transaction_inputs_outputs(
block_number: u32,
tx_index: u16,
tx: &MultiEraTx,
) -> (
Vec<TxOutRef>,
Vec<(TxOutRef, TxOutput)>,
Vec<ValidationError>,
) {
let mut parsed_inputs = Vec::new();
let mut parsed_outputs = Vec::new();
let mut errors = Vec::new();

let Ok(tx_hash) = tx.hash().to_vec().try_into() else {
errors.push(ValidationError::MalformedTransaction(
tx_index,
format!("Tx has incorrect hash length ({:?})", tx.hash().to_vec()),
));
return (parsed_inputs, parsed_outputs, errors);
};

let inputs = tx.consumes();
let outputs = tx.produces();

for input in inputs {
let tx_ref = TxOutRef::new(
TxHash::from(**input.output_ref().hash()),
input.output_ref().index() as u16,
);
parsed_inputs.push(tx_ref);
}

for (index, output) in outputs {
let tx_out_ref = TxOutRef {
tx_hash,
output_index: index as u16,
};

let utxo_id = UTxOIdentifier::new(block_number, tx_index, tx_out_ref.output_index);

match output.address() {
Ok(pallas_address) => match map_address(&pallas_address) {
Ok(address) => {
// Add TxOutput to utxo_deltas
parsed_outputs.push((
tx_out_ref,
TxOutput {
utxo_identifier: utxo_id,
address,
value: map_value(&output.value()),
datum: map_datum(&output.datum()),
reference_script: map_reference_script(&output.script_ref()),
},
));
}
Err(e) => {
errors.push(ValidationError::MalformedTransaction(
tx_index,
format!("Output {index} has been ignored: {e}"),
));
}
},
Err(e) => errors.push(ValidationError::MalformedTransaction(
tx_index,
format!("Can't parse output {index} in tx: {e}"),
)),
}
}

(parsed_inputs, parsed_outputs, errors)
}

pub fn map_value(pallas_value: &MultiEraValue) -> Value {
let lovelace = pallas_value.coin();
let pallas_assets = pallas_value.assets();
Expand Down
30 changes: 24 additions & 6 deletions common/src/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,10 @@ mod tests {
});

let text = address.to_string().unwrap();
assert_eq!(text, "addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgse35a3x");
assert_eq!(
text,
"addr1qx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgse35a3x"
);

let unpacked = Address::from_string(&text).unwrap();
assert_eq!(address, unpacked);
Expand All @@ -791,7 +794,10 @@ mod tests {
});

let text = address.to_string().unwrap();
assert_eq!(text, "addr1z8phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gten0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgs9yc0hh");
assert_eq!(
text,
"addr1z8phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gten0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgs9yc0hh"
);

let unpacked = Address::from_string(&text).unwrap();
assert_eq!(address, unpacked);
Expand All @@ -806,7 +812,10 @@ mod tests {
});

let text = address.to_string().unwrap();
assert_eq!(text, "addr1yx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzerkr0vd4msrxnuwnccdxlhdjar77j6lg0wypcc9uar5d2shs2z78ve");
assert_eq!(
text,
"addr1yx2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzerkr0vd4msrxnuwnccdxlhdjar77j6lg0wypcc9uar5d2shs2z78ve"
);

let unpacked = Address::from_string(&text).unwrap();
assert_eq!(address, unpacked);
Expand All @@ -821,7 +830,10 @@ mod tests {
});

let text = address.to_string().unwrap();
assert_eq!(text, "addr1x8phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gt7r0vd4msrxnuwnccdxlhdjar77j6lg0wypcc9uar5d2shskhj42g");
assert_eq!(
text,
"addr1x8phkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gt7r0vd4msrxnuwnccdxlhdjar77j6lg0wypcc9uar5d2shskhj42g"
);

let unpacked = Address::from_string(&text).unwrap();
assert_eq!(address, unpacked);
Expand Down Expand Up @@ -935,8 +947,14 @@ mod tests {

#[test]
fn shelley_to_stake_address_string_mainnet() {
let normal_address = ShelleyAddress::from_string("addr1q82peck5fynytkgjsp9vnpul59zswsd4jqnzafd0mfzykma625r684xsx574ltpznecr9cnc7n9e2hfq9lyart3h5hpszffds5").expect("valid normal address");
let script_address = ShelleyAddress::from_string("addr1zx0whlxaw4ksygvuljw8jxqlw906tlql06ern0gtvvzhh0c6409492020k6xml8uvwn34wrexagjh5fsk5xk96jyxk2qhlj6gf").expect("valid script address");
let normal_address = ShelleyAddress::from_string(
"addr1q82peck5fynytkgjsp9vnpul59zswsd4jqnzafd0mfzykma625r684xsx574ltpznecr9cnc7n9e2hfq9lyart3h5hpszffds5",
)
.expect("valid normal address");
let script_address = ShelleyAddress::from_string(
"addr1zx0whlxaw4ksygvuljw8jxqlw906tlql06ern0gtvvzhh0c6409492020k6xml8uvwn34wrexagjh5fsk5xk96jyxk2qhlj6gf",
)
.expect("valid script address");

let normal_stake_address = normal_address
.stake_address_string()
Expand Down
20 changes: 19 additions & 1 deletion common/src/protocol_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ pub struct ProtocolParams {
pub conway: Option<ConwayParams>,
}

impl ProtocolParams {
/// Calculate Transaction's Mininum required fee for shelley Era
/// Reference: https://github.com/IntersectMBO/cardano-ledger/blob/24ef1741c5e0109e4d73685a24d8e753e225656d/eras/shelley/impl/src/Cardano/Ledger/Shelley/Tx.hs#L254
pub fn shelley_min_fee(&self, tx_bytes: u32) -> Result<u64> {
self.shelley
.as_ref()
.ok_or_else(|| anyhow::anyhow!("Shelley params are not set"))
.map(|shelley_params| shelley_params.min_fee(tx_bytes))
}
}

//
// Byron protocol parameters
//
Expand Down Expand Up @@ -134,6 +145,13 @@ pub struct ShelleyParams {
pub gen_delegs: HashMap<PoolId, GenesisDelegate>,
}

impl ShelleyParams {
pub fn min_fee(&self, tx_bytes: u32) -> u64 {
(tx_bytes as u64 * self.protocol_params.minfee_a as u64)
+ (self.protocol_params.minfee_b as u64)
}
}

#[serde_as]
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
Expand Down Expand Up @@ -190,7 +208,7 @@ impl From<&ShelleyParams> for PraosParams {
epoch_length: params.epoch_length,
max_kes_evolutions: params.max_kes_evolutions,
max_lovelace_supply: params.max_lovelace_supply,
network_id: params.network_id.clone(),
network_id: params.network_id,
slot_length: params.slot_length,
slots_per_kes_period: params.slots_per_kes_period,

Expand Down
35 changes: 31 additions & 4 deletions common/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use std::{
/// Network identifier
#[derive(
Debug,
Copy,
Clone,
Default,
PartialEq,
Expand Down Expand Up @@ -61,6 +62,19 @@ impl From<String> for NetworkId {
}
}

impl Display for NetworkId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
NetworkId::Mainnet => "mainnet",
NetworkId::Testnet => "testnet",
}
)
}
}

/// Protocol era
#[derive(
Debug,
Expand Down Expand Up @@ -323,7 +337,14 @@ impl AssetName {
}

#[derive(
Debug, Clone, serde::Serialize, serde::Deserialize, minicbor::Encode, minicbor::Decode,
Debug,
Clone,
serde::Serialize,
serde::Deserialize,
minicbor::Encode,
minicbor::Decode,
PartialEq,
Eq,
)]
pub struct NativeAsset {
#[n(0)]
Expand All @@ -343,7 +364,7 @@ pub struct NativeAssetDelta {
}

/// Datum (inline or hash)
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub enum Datum {
Hash(Vec<u8>),
Inline(Vec<u8>),
Expand All @@ -359,7 +380,7 @@ pub enum ReferenceScript {
}

/// Value (lovelace + multiasset)
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default, PartialEq, Eq)]
pub struct Value {
pub lovelace: u64,
pub assets: NativeAssets,
Expand Down Expand Up @@ -562,7 +583,7 @@ pub struct UTXOValue {
}

/// Transaction output (UTXO)
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct TxOutput {
/// Identifier for this UTxO
pub utxo_identifier: UTxOIdentifier,
Expand Down Expand Up @@ -721,6 +742,12 @@ impl TxOutRef {
}
}

impl Display for TxOutRef {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}#{}", self.tx_hash, self.output_index)
}
}

/// Slot
pub type Slot = u64;

Expand Down
Loading