From 8b3cf73ff7871bdb937efdb8bf6644bfc8424693 Mon Sep 17 00:00:00 2001 From: thomaslavaur <82142582+thomaslavaur@users.noreply.github.com> Date: Wed, 6 May 2026 10:07:05 +0200 Subject: [PATCH] refactor(txhash): Simplify Mantle Transaction (2/2) (#2670) --- core/benches/mantle_tx_components.rs | 17 +- core/src/block/genesis.rs | 17 +- core/src/block/mod.rs | 8 +- core/src/mantle/channel.rs | 15 +- core/src/mantle/encoding.rs | 227 ++++++------------ core/src/mantle/gas.rs | 15 +- core/src/mantle/genesis_tx.rs | 61 +---- core/src/mantle/mod.rs | 50 +++- core/src/mantle/tx.rs | 159 +++++++----- core/src/mantle/tx_builder.rs | 39 ++- ledger/src/cryptarchia/mod.rs | 28 ++- ledger/src/lib.rs | 227 +++++++----------- .../src/api/serializers/transactions.rs | 5 +- .../binary/src/config/deployment/devnet.rs | 52 ++-- nodes/node/genesis/genesis_block.yaml | 60 +++-- nodes/node/standalone-deployment-config.yaml | 64 +++-- services/chain/chain-service/src/lib.rs | 8 +- services/wallet/src/api.rs | 4 +- services/wallet/src/lib.rs | 2 +- .../archiver/src/block.rs | 2 +- .../sequencer/src/sequencer.rs | 12 +- tests/src/common/mantle_inscription.rs | 26 +- tests/src/common/mantle_tx.rs | 36 +-- .../manual_transactions/chunked_transfers.rs | 2 +- .../tracked_transactions.rs | 7 +- .../steps/manual_transactions/utils.rs | 18 +- tests/src/tests/mantle/sdp/ops.rs | 17 +- .../src/workloads/inscription/workload.rs | 7 +- .../src/workloads/transaction/workload.rs | 9 +- tools/config/src/consensus.rs | 8 +- tools/config/src/sdp.rs | 2 +- wallet/src/lib.rs | 71 +++--- zone-sdk/src/adapter.rs | 4 +- zone-sdk/src/sequencer.rs | 36 +-- zone-sdk/src/state.rs | 16 +- 35 files changed, 579 insertions(+), 752 deletions(-) diff --git a/core/benches/mantle_tx_components.rs b/core/benches/mantle_tx_components.rs index 2a3f3bbd2..933039843 100644 --- a/core/benches/mantle_tx_components.rs +++ b/core/benches/mantle_tx_components.rs @@ -46,16 +46,13 @@ const SIZES: &[usize] = &[ // Helper fn to create an inscription `MantleTx`, no ledger inputs ot outputs. fn make_inscription_tx(payload_size: usize) -> MantleTx { let signing_key = Ed25519Key::from_bytes(&[1; 32]); - MantleTx { - ops: vec![Op::ChannelInscribe(InscriptionOp { - channel_id: ChannelId::from([0xAA; 32]), - inscription: vec![0xAB; payload_size], - parent: MsgId::from([0xBB; 32]), - signer: signing_key.public_key(), - })], - execution_gas_price: 100.into(), - storage_gas_price: 50.into(), - } + vec![Op::ChannelInscribe(InscriptionOp { + channel_id: ChannelId::from([0xAA; 32]), + inscription: vec![0xAB; payload_size], + parent: MsgId::from([0xBB; 32]), + signer: signing_key.public_key(), + })] + .into() } // Helper fn to create a `SignedMantleTx`. diff --git a/core/src/block/genesis.rs b/core/src/block/genesis.rs index b3812579c..83d41a9c0 100644 --- a/core/src/block/genesis.rs +++ b/core/src/block/genesis.rs @@ -6,8 +6,7 @@ use crate::{ block::Block, header::Header, mantle::{ - MantleTx, Note, Op, OpProof, SignedMantleTx, - gas::GasPrice, + Note, Op, OpProof, SignedMantleTx, genesis_tx::{self, GenesisTx}, ledger::{Inputs, Outputs}, ops::{channel::inscribe::InscriptionOp, sdp::SDPDeclareOp, transfer::TransferOp}, @@ -1095,11 +1094,7 @@ impl GenesisBlockBuilder { .collect(); let n = ops.len(); let signed_tx = SignedMantleTx::new_unverified( - MantleTx { - ops, - execution_gas_price: GasPrice::new(0), - storage_gas_price: GasPrice::new(0), - }, + ops.into(), vec![OpProof::Ed25519Sig(Ed25519Signature::zero()); n], ); Ok(GenesisBlock::genesis(GenesisTx::from_tx(signed_tx)?)) @@ -1202,11 +1197,7 @@ mod tests { ops.extend(extra_ops); let n = ops.len(); SignedMantleTx::new_unverified( - MantleTx { - ops, - execution_gas_price: GasPrice::new(0), - storage_gas_price: GasPrice::new(0), - }, + ops.into(), vec![OpProof::Ed25519Sig(Ed25519Signature::from_bytes(&[0u8; 64])); n], ) } @@ -1512,7 +1503,7 @@ mod tests { .unwrap(); let tx = block.transactions().next().unwrap(); - let ops = &tx.mantle_tx().ops; + let ops = tx.mantle_tx().ops(); assert!(matches!(ops[0], Op::Transfer(_))); assert!(matches!(ops[1], Op::ChannelInscribe(_))); assert!(matches!(ops[2], Op::SDPDeclare(_))); diff --git a/core/src/block/mod.rs b/core/src/block/mod.rs index 28539bf44..39cde56fe 100644 --- a/core/src/block/mod.rs +++ b/core/src/block/mod.rs @@ -323,13 +323,7 @@ mod tests { } fn create_tx(count: usize) -> Vec { - iter::repeat_with(|| MantleTx { - ops: vec![], - execution_gas_price: 0.into(), - storage_gas_price: 0.into(), - }) - .take(count) - .collect() + iter::repeat_with(|| MantleTx(vec![])).take(count).collect() } #[test] diff --git a/core/src/mantle/channel.rs b/core/src/mantle/channel.rs index 9aafe8f45..14c1f4792 100644 --- a/core/src/mantle/channel.rs +++ b/core/src/mantle/channel.rs @@ -8,7 +8,6 @@ use crate::mantle::{ ops::channel::{ ChannelId, ChannelKeyIndex, Ed25519PublicKey as PublicKey, MsgId, inscribe::InscriptionOp, }, - tx::MantleTxGasContext, }; #[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)] @@ -57,17 +56,6 @@ pub struct Channels { pub channels: rpds::HashTrieMapSync, } -impl From<&Channels> for MantleTxGasContext { - fn from(value: &Channels) -> Self { - let withdraw_thresholds = value - .channels - .iter() - .map(|(channel_id, channel)| (*channel_id, channel.withdraw_threshold)) - .collect(); - Self::new(withdraw_thresholds) - } -} - #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ChannelState { pub tip: MsgId, @@ -141,6 +129,7 @@ mod tests { deposit::{DepositExecutionContext, DepositOp}, withdraw::{ChannelWithdrawOp, WithdrawExecutionContext}, }, + tx::{GasPrices, MantleTxGasContext}, }, sdp::locked_notes::LockedNotes, }; @@ -217,7 +206,7 @@ mod tests { ), }; - let gas_context = MantleTxGasContext::from(&channels); + let gas_context = MantleTxGasContext::from_channels(&channels, GasPrices::new(0, 0)); assert_eq!(gas_context.withdraw_threshold(&first_id), Some(1)); assert_eq!(gas_context.withdraw_threshold(&second_id), Some(2)); diff --git a/core/src/mantle/encoding.rs b/core/src/mantle/encoding.rs index b85bc93d9..49a749b17 100644 --- a/core/src/mantle/encoding.rs +++ b/core/src/mantle/encoding.rs @@ -57,7 +57,7 @@ const LOCATOR_BYTES_SIZE_LIMIT: usize = 329usize; pub fn decode_signed_mantle_tx(input: &[u8]) -> IResult<&[u8], SignedMantleTx> { // SignedMantleTx = MantleTx OpsProofs let (input, mantle_tx) = decode_mantle_tx(input)?; - let (input, ops_proofs) = decode_ops_proofs(input, &mantle_tx.ops)?; + let (input, ops_proofs) = decode_ops_proofs(input, mantle_tx.ops())?; let signed_tx = SignedMantleTx::new(mantle_tx, ops_proofs) .map_err(|_| nom::Err::Error(Error::new(input, ErrorKind::Verify)))?; @@ -68,17 +68,8 @@ pub fn decode_signed_mantle_tx(input: &[u8]) -> IResult<&[u8], SignedMantleTx> { pub fn decode_mantle_tx(input: &[u8]) -> IResult<&[u8], MantleTx> { // MantleTx = Ops ExecutionGasPrice StorageGasPrice let (input, ops) = decode_ops(input)?; - let (input, execution_gas_price) = decode_uint64(input)?; - let (input, storage_gas_price) = decode_uint64(input)?; - Ok(( - input, - MantleTx { - ops, - execution_gas_price: execution_gas_price.into(), - storage_gas_price: storage_gas_price.into(), - }, - )) + Ok((input, ops.into())) } // ============================================================================== @@ -900,18 +891,14 @@ fn encode_ops_proofs(proofs: &[OpProof], ops: &[Op]) -> Vec { /// Encode top-level transactions #[must_use] pub fn encode_mantle_tx(tx: &MantleTx) -> Vec { - let mut bytes = Vec::new(); - bytes.extend(encode_ops(&tx.ops)); - bytes.extend(encode_uint64(tx.execution_gas_price.into_inner())); - bytes.extend(encode_uint64(tx.storage_gas_price.into_inner())); - bytes + encode_ops(tx.ops()) } #[must_use] pub fn encode_signed_mantle_tx(tx: &SignedMantleTx) -> Vec { let mut bytes = Vec::new(); bytes.extend(encode_mantle_tx(&tx.mantle_tx)); - bytes.extend(encode_ops_proofs(&tx.ops_proofs, &tx.mantle_tx.ops)); + bytes.extend(encode_ops_proofs(&tx.ops_proofs, tx.mantle_tx.ops())); bytes } @@ -919,7 +906,7 @@ pub(crate) fn predict_signed_mantle_tx_size(tx: &MantleTx, context: &MantleTxGas let mantle_tx_size = encode_mantle_tx(tx).len(); let ops_proofs_size = tx - .ops + .ops() .iter() .map(|op| match op { // Ed25519SigProof = Ed25519Signature @@ -959,7 +946,10 @@ mod tests { use num_bigint::BigUint; use super::*; - use crate::{mantle::Transaction as _, sdp::blend::ActivityProof}; + use crate::{ + mantle::{Transaction as _, tx::GasPrices}, + sdp::blend::ActivityProof, + }; fn dbg_test_vector(actual: &str, expected: &str) { println!("{:32} {:32}", "actual", "expected"); @@ -1030,11 +1020,7 @@ mod tests { #[test] fn test_decode_signed_mantle_tx_empty() { - let mantle_tx = MantleTx { - ops: vec![], - execution_gas_price: 100.into(), - storage_gas_price: 50.into(), - }; + let mantle_tx = MantleTx(vec![]); let signed_tx = SignedMantleTx { mantle_tx, @@ -1045,10 +1031,7 @@ mod tests { clippy::string_add, reason = "Recommended String::push_str does not support chaining" )] - let test_vector = String::new() - + "00" // OpCount=0u8 - + "6400000000000000" // ExecutionGasPrice - + "3200000000000000"; // StorageGasPrice + let test_vector = String::new() + "00"; // OpCount=0u8 // ENCODING let encoded = hex::encode(encode_signed_mantle_tx(&signed_tx)); @@ -1067,16 +1050,12 @@ mod tests { #[test] fn test_decode_signed_mantle_tx_with_inscribe() { let signing_key = Ed25519Key::from_bytes(&[4u8; 32]); - let mantle_tx = MantleTx { - ops: vec![Op::ChannelInscribe(InscriptionOp { - channel_id: ChannelId::from([0xAA; 32]), - inscription: b"hello".to_vec(), - parent: MsgId::from([0xBB; 32]), - signer: signing_key.public_key(), - })], - execution_gas_price: 100.into(), - storage_gas_price: 50.into(), - }; + let mantle_tx = MantleTx(vec![Op::ChannelInscribe(InscriptionOp { + channel_id: ChannelId::from([0xAA; 32]), + inscription: b"hello".to_vec(), + parent: MsgId::from([0xBB; 32]), + signer: signing_key.public_key(), + })]); let txhash = mantle_tx.hash(); let inscribe_sig = @@ -1095,10 +1074,8 @@ mod tests { + "68656c6c6f" // Inscription + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" // Parent (32Byte) + "ca93ac1705187071d67b83c7ff0efe8108e8ec4530575d7726879333dbdabe7c" // Signer (32Byte) - + "6400000000000000" // ExecutionGasPrice - + "3200000000000000" // StorageGasPrice - + "a53621dc1a5d7be2d8f8771139df91f961a60e546e9cdeff499d0c28ae10165c" // Signature (64Byte) - + "7dd0e314f2ba839dd0bff3a567be06f3aafdeddddd9de00e5117ab1584cd3201"; + + "4ec789fc67b7f7bfba02f8cc7f3f671a107225faefbe60ca0b8e9e7e8e43e8db" // Signature (64Byte) + + "835075aed539fac37e0fdc03acc2aba873e43eef8a835476c4c6bdaaba866901"; // ENCODING let encoded = hex::encode(encode_signed_mantle_tx(&signed_tx)); @@ -1116,22 +1093,18 @@ mod tests { #[test] fn test_decode_signed_mantle_tx_with_multiple_ops() { let signing_key = Ed25519Key::from_bytes(&[4u8; 32]); - let mantle_tx = MantleTx { - ops: vec![ - Op::ChannelInscribe(InscriptionOp { - channel_id: ChannelId::from([0x11; 32]), - inscription: b"first".to_vec(), - parent: MsgId::from([0x00; 32]), - signer: signing_key.public_key(), - }), - Op::ChannelSetKeys(SetKeysOp { - channel: ChannelId::from([0x22; 32]), - keys: vec![signing_key.public_key()], - }), - ], - execution_gas_price: 100.into(), - storage_gas_price: 50.into(), - }; + let mantle_tx = MantleTx(vec![ + Op::ChannelInscribe(InscriptionOp { + channel_id: ChannelId::from([0x11; 32]), + inscription: b"first".to_vec(), + parent: MsgId::from([0x00; 32]), + signer: signing_key.public_key(), + }), + Op::ChannelSetKeys(SetKeysOp { + channel: ChannelId::from([0x22; 32]), + keys: vec![signing_key.public_key()], + }), + ]); let txhash = mantle_tx.hash(); let sig = signing_key.sign_payload(&txhash.as_signing_bytes()); @@ -1173,11 +1146,7 @@ mod tests { signer: signing_key.public_key(), }; - let mantle_tx = MantleTx { - ops: vec![Op::ChannelInscribe(inscribe_op)], - execution_gas_price: 100.into(), - storage_gas_price: 50.into(), - }; + let mantle_tx = MantleTx(vec![Op::ChannelInscribe(inscribe_op)]); let txhash = mantle_tx.hash(); let op_sig = signing_key.sign_payload(&txhash.as_signing_bytes()); @@ -1186,7 +1155,7 @@ mod tests { let encoded = encode_signed_mantle_tx(&signed_tx); - let gas_context = MantleTxGasContext::new(HashMap::new()); + let gas_context = MantleTxGasContext::new(HashMap::new(), GasPrices::new(0, 0)); let predicted_size = predict_signed_mantle_tx_size(&signed_tx.mantle_tx, &gas_context); assert_eq!( @@ -1220,11 +1189,7 @@ mod tests { #[test] fn test_encode_decode_roundtrip_empty_tx() { // Create an empty MantleTx - let original_tx = MantleTx { - ops: vec![], - execution_gas_price: 100.into(), - storage_gas_price: 50.into(), - }; + let original_tx = MantleTx(vec![]); // Encode let encoded = encode_mantle_tx(&original_tx); @@ -1247,11 +1212,7 @@ mod tests { let note_id = NoteId(BigUint::from(123u64).into()); let transfer_op = TransferOp::new(Inputs::new(vec![note_id]), Outputs::new(vec![note])); - let original_tx = MantleTx { - ops: vec![Op::Transfer(transfer_op)], - execution_gas_price: 100.into(), - storage_gas_price: 50.into(), - }; + let original_tx = MantleTx(vec![Op::Transfer(transfer_op)]); // Encode let encoded = encode_mantle_tx(&original_tx); @@ -1267,11 +1228,7 @@ mod tests { #[test] fn test_encode_decode_roundtrip_signed_tx() { // Create a simple SignedMantleTx - let mantle_tx = MantleTx { - ops: vec![], - execution_gas_price: 100.into(), - storage_gas_price: 50.into(), - }; + let mantle_tx = MantleTx(vec![]); let original_tx = SignedMantleTx::new(mantle_tx, vec![]).unwrap(); // Encode @@ -1288,14 +1245,10 @@ mod tests { #[test] fn test_predict_signed_mantle_tx_size_empty_tx() { // Create an empty MantleTx - let mantle_tx = MantleTx { - ops: vec![], - execution_gas_price: 100.into(), - storage_gas_price: 50.into(), - }; + let mantle_tx = MantleTx(vec![]); // Predict size - let gas_context = MantleTxGasContext::new(HashMap::new()); + let gas_context = MantleTxGasContext::new(HashMap::new(), GasPrices::new(0, 0)); let predicted_size = predict_signed_mantle_tx_size(&mantle_tx, &gas_context); // Create a signed tx and encode it to get actual size @@ -1316,14 +1269,10 @@ mod tests { signer: signing_key.public_key(), }; - let mantle_tx = MantleTx { - ops: vec![Op::ChannelInscribe(inscribe_op)], - execution_gas_price: 100.into(), - storage_gas_price: 50.into(), - }; + let mantle_tx = MantleTx(vec![Op::ChannelInscribe(inscribe_op)]); // Predict size - let gas_context = MantleTxGasContext::new(HashMap::new()); + let gas_context = MantleTxGasContext::new(HashMap::new(), GasPrices::new(0, 0)); let predicted_size = predict_signed_mantle_tx_size(&mantle_tx, &gas_context); // Create a signed tx and encode it to get actual size @@ -1351,14 +1300,10 @@ mod tests { ], }; - let mantle_tx = MantleTx { - ops: vec![Op::ChannelSetKeys(set_keys_op)], - execution_gas_price: 100.into(), - storage_gas_price: 50.into(), - }; + let mantle_tx = MantleTx(vec![Op::ChannelSetKeys(set_keys_op)]); // Predict size - let gas_context = MantleTxGasContext::new(HashMap::new()); + let gas_context = MantleTxGasContext::new(HashMap::new(), GasPrices::new(0, 0)); let predicted_size = predict_signed_mantle_tx_size(&mantle_tx, &gas_context); // Create a signed tx and encode it to get actual size @@ -1397,14 +1342,10 @@ mod tests { locked_note_id: locked_note.id(), }; - let mantle_tx = MantleTx { - ops: vec![Op::SDPDeclare(sdp_declare_op)], - execution_gas_price: 100.into(), - storage_gas_price: 50.into(), - }; + let mantle_tx = MantleTx(vec![Op::SDPDeclare(sdp_declare_op)]); // Predict size - let gas_context = MantleTxGasContext::new(HashMap::new()); + let gas_context = MantleTxGasContext::new(HashMap::new(), GasPrices::new(0, 0)); let predicted_size = predict_signed_mantle_tx_size(&mantle_tx, &gas_context); // Create a signed tx and encode it to get actual size @@ -1433,16 +1374,12 @@ mod tests { locked_note_id, }; - let mantle_tx = MantleTx { - ops: vec![Op::SDPWithdraw(sdp_withdraw_op)], - execution_gas_price: 100.into(), - storage_gas_price: 50.into(), - }; + let mantle_tx = MantleTx(vec![Op::SDPWithdraw(sdp_withdraw_op)]); let txhash = mantle_tx.hash(); // Predict size - let gas_context = MantleTxGasContext::new(HashMap::new()); + let gas_context = MantleTxGasContext::new(HashMap::new(), GasPrices::new(0, 0)); let predicted_size = predict_signed_mantle_tx_size(&mantle_tx, &gas_context); // Create a signed tx and encode it to get actual size @@ -1479,13 +1416,9 @@ mod tests { metadata, }; - let mantle_tx = MantleTx { - ops: vec![Op::SDPActive(sdp_active_op)], - execution_gas_price: 100.into(), - storage_gas_price: 50.into(), - }; + let mantle_tx = MantleTx(vec![Op::SDPActive(sdp_active_op)]); - let gas_context = MantleTxGasContext::new(HashMap::new()); + let gas_context = MantleTxGasContext::new(HashMap::new(), GasPrices::new(0, 0)); let predicted_size = predict_signed_mantle_tx_size(&mantle_tx, &gas_context); let txhash = mantle_tx.hash(); @@ -1534,18 +1467,14 @@ mod tests { metadata: ActivityMetadata::Blend(Box::new(blend_proof)), }; - let mantle_tx = MantleTx { - ops: vec![ - Op::ChannelInscribe(inscribe_op), - Op::ChannelSetKeys(set_keys_op), - Op::SDPActive(sdp_active_op), - ], - execution_gas_price: 100.into(), - storage_gas_price: 50.into(), - }; + let mantle_tx = MantleTx(vec![ + Op::ChannelInscribe(inscribe_op), + Op::ChannelSetKeys(set_keys_op), + Op::SDPActive(sdp_active_op), + ]); // Predict size - let gas_context = MantleTxGasContext::new(HashMap::new()); + let gas_context = MantleTxGasContext::new(HashMap::new(), GasPrices::new(0, 0)); let predicted_size = predict_signed_mantle_tx_size(&mantle_tx, &gas_context); let txhash = mantle_tx.hash(); @@ -1585,14 +1514,10 @@ mod tests { Outputs::new(vec![note1, note2]), ); - let mantle_tx = MantleTx { - ops: vec![Op::Transfer(transfer_op)], - execution_gas_price: 100.into(), - storage_gas_price: 50.into(), - }; + let mantle_tx = MantleTx(vec![Op::Transfer(transfer_op)]); // Predict size - let gas_context = MantleTxGasContext::new(HashMap::new()); + let gas_context = MantleTxGasContext::new(HashMap::new(), GasPrices::new(0, 0)); let predicted_size = predict_signed_mantle_tx_size(&mantle_tx, &gas_context); // Create a signed tx and encode it to get actual size @@ -1646,19 +1571,15 @@ mod tests { .id(), }; - let mantle_tx = MantleTx { - ops: vec![ - Op::ChannelInscribe(inscribe_op), - Op::ChannelSetKeys(set_keys_op), - Op::SDPDeclare(sdp_declare_op), - Op::Transfer(transfer_op), - ], - execution_gas_price: 150.into(), - storage_gas_price: 75.into(), - }; + let mantle_tx = MantleTx(vec![ + Op::ChannelInscribe(inscribe_op), + Op::ChannelSetKeys(set_keys_op), + Op::SDPDeclare(sdp_declare_op), + Op::Transfer(transfer_op), + ]); // Predict size - let gas_context = MantleTxGasContext::new(HashMap::new()); + let gas_context = MantleTxGasContext::new(HashMap::new(), GasPrices::new(0, 0)); let predicted_size = predict_signed_mantle_tx_size(&mantle_tx, &gas_context); // Create a signed tx and encode it to get actual size @@ -1693,13 +1614,9 @@ mod tests { pk: ZkPublicKey::from(BigUint::from(0u64)), }; - let mantle_tx = MantleTx { - ops: vec![Op::LeaderClaim(leader_claim_op.clone())], - execution_gas_price: 100.into(), - storage_gas_price: 50.into(), - }; + let mantle_tx = MantleTx(vec![Op::LeaderClaim(leader_claim_op.clone())]); - let empty_gas_context = MantleTxGasContext::new(HashMap::new()); + let empty_gas_context = MantleTxGasContext::new(HashMap::new(), GasPrices::new(0, 0)); let predicted_size = predict_signed_mantle_tx_size(&mantle_tx, &empty_gas_context); let poc_proof = Groth16LeaderClaimProof::new( @@ -1773,15 +1690,11 @@ mod tests { let note2 = Note::new(2000, pk2); let signing_key = Ed25519Key::from_bytes(&[21u8; 32]); - let mantle_tx = MantleTx { - ops: vec![Op::ChannelWithdraw(ChannelWithdrawOp { - channel_id: ChannelId::from([0xAB; 32]), - outputs: Outputs::new(vec![note1, note2]), - withdraw_nonce: 0, - })], - execution_gas_price: 100.into(), - storage_gas_price: 50.into(), - }; + let mantle_tx = MantleTx(vec![Op::ChannelWithdraw(ChannelWithdrawOp { + channel_id: ChannelId::from([0xAB; 32]), + outputs: Outputs::new(vec![note1, note2]), + withdraw_nonce: 0, + })]); let tx_hash = mantle_tx.hash(); let proof = ChannelWithdrawProof::new(vec![WithdrawSignature::new( 0, diff --git a/core/src/mantle/gas.rs b/core/src/mantle/gas.rs index e0a7da44d..f29ce2ef2 100644 --- a/core/src/mantle/gas.rs +++ b/core/src/mantle/gas.rs @@ -1,4 +1,7 @@ -use std::fmt::{self, Display}; +use std::{ + fmt::{self, Display}, + ops::Add, +}; use serde::{Deserialize, Serialize}; @@ -29,7 +32,7 @@ impl From for Gas { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct GasPrice(Value); impl GasPrice { @@ -44,6 +47,14 @@ impl GasPrice { } } +impl Add for GasPrice { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 + rhs.0) + } +} + impl From for GasPrice { fn from(value: Value) -> Self { Self(value) diff --git a/core/src/mantle/genesis_tx.rs b/core/src/mantle/genesis_tx.rs index 5af7e633f..59810b674 100644 --- a/core/src/mantle/genesis_tx.rs +++ b/core/src/mantle/genesis_tx.rs @@ -66,17 +66,9 @@ impl GenesisTx { pub fn from_tx(signed_mantle_tx: SignedMantleTx) -> Result { let mantle_tx = &signed_mantle_tx.mantle_tx; - // Genesis transactions must have execution gas price and storage gas price - // matching the expected genesis values - if mantle_tx.execution_gas_price != GENESIS_EXECUTION_GAS_PRICE - || mantle_tx.storage_gas_price != GENESIS_STORAGE_GAS_PRICE - { - return Err(Error::InvalidGenesisGasPrice); - } - // Genesis transactions must contain exactly one transfer as the first op, // one inscription as the second op, and then may contain other SDP declarations - let cryptarchia_parameter = match mantle_tx.ops.as_slice() { + let cryptarchia_parameter = match mantle_tx.ops().as_slice() { [ Op::Transfer(transfer), Op::ChannelInscribe(inscription), @@ -174,7 +166,7 @@ impl GasCalculator for GenesisTx { impl crate::mantle::GenesisTx for GenesisTx { fn genesis_inscription(&self) -> &InscriptionOp { // Safe to unwrap because we validated this in from_tx - match &self.mantle_tx().ops[1] { + match &self.mantle_tx().ops()[1] { Op::ChannelInscribe(op) => op, _ => unreachable!("GenesisTx always has a valid inscription as second op"), } @@ -182,7 +174,7 @@ impl crate::mantle::GenesisTx for GenesisTx { fn genesis_transfer(&self) -> &TransferOp { // Safe to unwrap because we validated this in from_tx - match &self.mantle_tx().ops[0] { + match &self.mantle_tx().ops()[0] { Op::Transfer(op) => op, _ => unreachable!("GenesisTx always has a valid transfer as first op"), } @@ -194,7 +186,7 @@ impl crate::mantle::GenesisTx for GenesisTx { fn sdp_declarations(&self) -> impl Iterator { self.mantle_tx() - .ops + .ops() .iter() .zip(self.tx.ops_proofs.iter()) .filter_map(|(op, proof)| { @@ -354,11 +346,7 @@ mod tests { ); let mut new_ops = vec![Op::Transfer(transfer_op)]; new_ops.append(&mut ops); - let mantle_tx = MantleTx { - ops: new_ops, - execution_gas_price: GENESIS_EXECUTION_GAS_PRICE, - storage_gas_price: GENESIS_STORAGE_GAS_PRICE, - }; + let mantle_tx = MantleTx(new_ops); let mut new_op_proofs = vec![OpProof::ZkSig( ZkKey::multi_sign(&[], &mantle_tx.hash().to_fr()).unwrap(), )]; @@ -534,45 +522,6 @@ mod tests { } } - #[test] - fn test_genesis_fees() { - // Should succeed with execution_gas_price=GENESIS_EXECUTION_GAS_PRICE - // and storage_gas_price=GENESIS_STORAGE_GAS_PRICE - let mut signed_mantle_tx = create_tx( - vec![Op::ChannelInscribe(inscription_op( - ChannelId::from([0; 32]), - &cryptarchia_param(), - MsgId::root(), - Ed25519PublicKey::from_bytes(&[0; 32]).unwrap(), - ))], - vec![OpProof::Ed25519Sig(Ed25519Signature::from_bytes( - &[0u8; 64], - ))], - ); - assert!(GenesisTx::from_tx(signed_mantle_tx.clone()).is_ok()); - - // Test with wrong execution gas price - signed_mantle_tx.mantle_tx.execution_gas_price = - (GENESIS_EXECUTION_GAS_PRICE.into_inner() + 1).into(); - let result = GenesisTx::from_tx(signed_mantle_tx.clone()); - assert_eq!(result, Err(Error::InvalidGenesisGasPrice)); - - // Test with wrong storage gas price - signed_mantle_tx.mantle_tx.storage_gas_price = - (GENESIS_STORAGE_GAS_PRICE.into_inner() + 1).into(); - signed_mantle_tx.mantle_tx.execution_gas_price = 0.into(); - let result = GenesisTx::from_tx(signed_mantle_tx.clone()); - assert_eq!(result, Err(Error::InvalidGenesisGasPrice)); - - // Test with wrong storage/execution gas prices - signed_mantle_tx.mantle_tx.storage_gas_price = - (GENESIS_STORAGE_GAS_PRICE.into_inner() + 1).into(); - signed_mantle_tx.mantle_tx.execution_gas_price = - (GENESIS_EXECUTION_GAS_PRICE.into_inner() + 1).into(); - let result = GenesisTx::from_tx(signed_mantle_tx); - assert_eq!(result, Err(Error::InvalidGenesisGasPrice)); - } - #[test] fn test_genesis_tx_serde() { // Create a genesis transaction with inscription (no signature proof required) diff --git a/core/src/mantle/mod.rs b/core/src/mantle/mod.rs index 3299833fb..43ff1077d 100644 --- a/core/src/mantle/mod.rs +++ b/core/src/mantle/mod.rs @@ -49,16 +49,29 @@ pub trait Transaction { } pub trait AuthenticatedMantleTx: Transaction + GasCalculator + StorageSize { + type Context; /// Returns the underlying `MantleTx` that this transaction represents. fn mantle_tx(&self) -> &MantleTx; fn ops_with_proof(&self) -> impl Iterator; // Gas Cost functions with context already handled - fn total_gas_cost(&self) -> Result; - fn storage_gas_cost(&self) -> Result; - fn execution_gas_consumption(&self) -> Result; - fn storage_gas_consumption(&self) -> Result; + fn total_gas_cost( + &self, + context: ::Context, + ) -> Result; + fn storage_gas_cost( + &self, + context: ::Context, + ) -> Result; + fn execution_gas_consumption( + &self, + context: ::Context, + ) -> Result; + fn storage_gas_consumption( + &self, + context: ::Context, + ) -> Result; fn verify_ops_proofs_with_helper( &self, @@ -92,6 +105,7 @@ impl StorageSize for &T { } impl AuthenticatedMantleTx for &T { + type Context = ::Context; fn mantle_tx(&self) -> &MantleTx { T::mantle_tx(self) } @@ -100,20 +114,32 @@ impl AuthenticatedMantleTx for &T { T::ops_with_proof(self) } - fn total_gas_cost(&self) -> Result { - ::total_gas_cost::(self) + fn total_gas_cost( + &self, + context: ::Context, + ) -> Result { + ::total_gas_cost::(self, context) } - fn storage_gas_cost(&self) -> Result { - ::storage_gas_cost(self) + fn storage_gas_cost( + &self, + context: ::Context, + ) -> Result { + ::storage_gas_cost(self, context) } - fn execution_gas_consumption(&self) -> Result { - ::execution_gas_consumption::(self) + fn execution_gas_consumption( + &self, + context: ::Context, + ) -> Result { + ::execution_gas_consumption::(self, context) } - fn storage_gas_consumption(&self) -> Result { - ::storage_gas_consumption(self) + fn storage_gas_consumption( + &self, + context: ::Context, + ) -> Result { + ::storage_gas_consumption(self, context) } fn verify_ops_proofs_with_helper( diff --git a/core/src/mantle/tx.rs b/core/src/mantle/tx.rs index 8f5e8b301..c258744d6 100644 --- a/core/src/mantle/tx.rs +++ b/core/src/mantle/tx.rs @@ -13,8 +13,10 @@ use crate::{ crypto::{Digest as _, Hash, Hasher}, mantle::{ AuthenticatedMantleTx, StorageSize, Transaction, TransactionHasher, Value, + channel::Channels, encoding::{decode_mantle_tx, encode_mantle_tx, encode_signed_mantle_tx}, gas::{Gas, GasCalculator, GasConstants, GasCost, GasOverflow, GasPrice}, + genesis_tx::{GENESIS_EXECUTION_GAS_PRICE, GENESIS_STORAGE_GAS_PRICE}, ops::{ Op, OpProof, channel::{ChannelId, ChannelKeyIndex, withdraw::ChannelWithdrawOp}, @@ -80,8 +82,6 @@ impl TxHash { #[derive(Serialize, Deserialize)] struct MantleTxDeSerImpl { pub ops: Vec, - pub execution_gas_price: GasPrice, - pub storage_gas_price: GasPrice, } #[derive(Debug, Clone, Default)] @@ -93,13 +93,43 @@ pub struct MantleTxContext { #[derive(Debug, Clone, Default)] pub struct MantleTxGasContext { withdraw_thresholds: HashMap, + gas_prices: GasPrices, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct GasPrices { + pub execution_base_gas_price: GasPrice, + pub storage_gas_price: GasPrice, +} + +impl GasPrices { + #[must_use] + pub fn new(execution: u64, storage: u64) -> Self { + Self { + execution_base_gas_price: execution.into(), + storage_gas_price: storage.into(), + } + } +} + +impl Default for GasPrices { + fn default() -> Self { + Self { + execution_base_gas_price: GENESIS_EXECUTION_GAS_PRICE, + storage_gas_price: GENESIS_STORAGE_GAS_PRICE, + } + } } impl MantleTxGasContext { #[must_use] - pub const fn new(withdraw_thresholds: HashMap) -> Self { + pub const fn new( + withdraw_thresholds: HashMap, + gas_prices: GasPrices, + ) -> Self { Self { withdraw_thresholds, + gas_prices, } } @@ -107,44 +137,41 @@ impl MantleTxGasContext { pub fn withdraw_threshold(&self, channel_id: &ChannelId) -> Option { self.withdraw_thresholds.get(channel_id).copied() } + + #[must_use] + pub fn from_channels(value: &Channels, base_prices: GasPrices) -> Self { + let withdraw_thresholds = value + .channels + .iter() + .map(|(channel_id, channel)| (*channel_id, channel.withdraw_threshold)) + .collect(); + Self::new(withdraw_thresholds, base_prices) + } + + #[must_use] + pub fn get_gas_prices(&self) -> GasPrices { + self.gas_prices.clone() + } } #[derive(Clone, Debug, PartialEq, Eq)] -pub struct MantleTx { - pub ops: Vec, - pub execution_gas_price: GasPrice, - pub storage_gas_price: GasPrice, -} +pub struct MantleTx(pub Vec); impl From for MantleTx { - fn from( - MantleTxDeSerImpl { - ops, - execution_gas_price, - storage_gas_price, - }: MantleTxDeSerImpl, - ) -> Self { - Self { - ops, - execution_gas_price, - storage_gas_price, - } + fn from(MantleTxDeSerImpl { ops }: MantleTxDeSerImpl) -> Self { + Self(ops) } } impl From for MantleTxDeSerImpl { - fn from( - MantleTx { - ops, - execution_gas_price, - storage_gas_price, - }: MantleTx, - ) -> Self { - Self { - ops, - execution_gas_price, - storage_gas_price, - } + fn from(MantleTx(ops): MantleTx) -> Self { + Self { ops } + } +} + +impl> From for MantleTx { + fn from(ops: T) -> Self { + Self(ops.into_iter().collect()) } } @@ -187,7 +214,8 @@ impl GasCalculator for MantleTx { context: &Self::Context, ) -> Result { let execution_gas = self.execution_gas_consumption::(context); - let execution_gas_cost = GasCost::calculate(execution_gas?, self.execution_gas_price)?; + let execution_gas_cost = + GasCost::calculate(execution_gas?, context.gas_prices.execution_base_gas_price)?; let storage_gas_cost = self.storage_gas_cost(context)?; execution_gas_cost.checked_add(storage_gas_cost) @@ -196,7 +224,7 @@ impl GasCalculator for MantleTx { fn storage_gas_cost(&self, context: &Self::Context) -> Result { GasCost::calculate( self.storage_gas_consumption(context)?, - self.storage_gas_price, + context.gas_prices.storage_gas_price, ) } @@ -204,7 +232,7 @@ impl GasCalculator for MantleTx { &self, _context: &Self::Context, ) -> Result { - self.ops + self.ops() .iter() .map(Op::execution_gas::) .try_fold(Gas::from(0), Gas::checked_add) @@ -224,13 +252,18 @@ impl MantleTx { #[must_use] pub fn transfers(&self) -> Vec { let mut transfers: Vec = vec![]; - for op in self.ops.clone() { + for op in self.ops().clone() { if let Op::Transfer(transfer_op) = op { transfers.push(transfer_op); } } transfers } + + #[must_use] + pub const fn ops(&self) -> &Vec { + &self.0 + } } static MANTLE_TXHASH_V1_BYTES: LazyLock> = LazyLock::new(|| b"MANTLE_TXHASH_V1".to_vec()); @@ -360,9 +393,9 @@ impl SignedMantleTx { // TODO: might drop proofs after verification fn verify_ops_proofs(&self) -> Result<(), VerificationError> { // Check that we have the same number of proofs as ops - if self.mantle_tx.ops.len() != self.ops_proofs.len() { + if self.mantle_tx.ops().len() != self.ops_proofs.len() { return Err(VerificationError::ProofCountMismatch { - ops_count: self.mantle_tx.ops.len(), + ops_count: self.mantle_tx.ops().len(), proofs_count: self.ops_proofs.len(), }); } @@ -372,7 +405,7 @@ impl SignedMantleTx { for (idx, (op, proof)) in self .mantle_tx - .ops + .ops() .iter() .zip(self.ops_proofs.iter()) .enumerate() @@ -421,7 +454,7 @@ impl SignedMantleTx { for (idx, (op, proof)) in self .mantle_tx - .ops + .ops() .iter() .zip(self.ops_proofs.iter()) .enumerate() @@ -515,28 +548,42 @@ impl Transaction for SignedMantleTx { } impl AuthenticatedMantleTx for SignedMantleTx { + type Context = GasPrices; + fn mantle_tx(&self) -> &MantleTx { &self.mantle_tx } fn ops_with_proof(&self) -> impl Iterator { - self.mantle_tx.ops.iter().zip(self.ops_proofs.iter()) + self.mantle_tx.ops().iter().zip(self.ops_proofs.iter()) } - fn total_gas_cost(&self) -> Result { - GasCalculator::total_gas_cost::(&self, &()) + fn total_gas_cost( + &self, + context: ::Context, + ) -> Result { + GasCalculator::total_gas_cost::(&self, &context) } - fn storage_gas_cost(&self) -> Result { - GasCalculator::storage_gas_cost(&self, &()) + fn storage_gas_cost( + &self, + context: ::Context, + ) -> Result { + GasCalculator::storage_gas_cost(&self, &context) } - fn execution_gas_consumption(&self) -> Result { - GasCalculator::execution_gas_consumption::(&self, &()) + fn execution_gas_consumption( + &self, + context: ::Context, + ) -> Result { + GasCalculator::execution_gas_consumption::(&self, &context) } - fn storage_gas_consumption(&self) -> Result { - GasCalculator::storage_gas_consumption(&self, &()) + fn storage_gas_consumption( + &self, + context: ::Context, + ) -> Result { + GasCalculator::storage_gas_consumption(&self, &context) } fn verify_ops_proofs_with_helper( @@ -548,7 +595,7 @@ impl AuthenticatedMantleTx for SignedMantleTx { } impl GasCalculator for SignedMantleTx { - type Context = (); + type Context = GasPrices; fn total_gas_cost( &self, @@ -556,7 +603,7 @@ impl GasCalculator for SignedMantleTx { ) -> Result { let execution_gas = GasCalculator::execution_gas_consumption::(&self, context)?; let execution_gas_cost = - GasCost::calculate(execution_gas, self.mantle_tx.execution_gas_price)?; + GasCost::calculate(execution_gas, context.execution_base_gas_price)?; let storage_gas_cost = GasCalculator::storage_gas_cost(self, context)?; execution_gas_cost.checked_add(storage_gas_cost) @@ -564,7 +611,7 @@ impl GasCalculator for SignedMantleTx { fn storage_gas_cost(&self, context: &Self::Context) -> Result { let storage_gas = GasCalculator::storage_gas_consumption(&self, context)?; - GasCost::calculate(storage_gas, self.mantle_tx.storage_gas_price) + GasCost::calculate(storage_gas, context.storage_gas_price) } fn execution_gas_consumption( @@ -572,7 +619,7 @@ impl GasCalculator for SignedMantleTx { _context: &Self::Context, ) -> Result { self.mantle_tx - .ops + .ops() .iter() .map(Op::execution_gas::) .try_fold(Gas::from(0), Gas::checked_add) @@ -617,11 +664,7 @@ mod tests { }; fn create_test_mantle_tx(ops: Vec) -> MantleTx { - MantleTx { - ops, - execution_gas_price: 1.into(), - storage_gas_price: 1.into(), - } + ops.into() } fn create_test_inscribe_op(signing_key: &Ed25519Key) -> InscriptionOp { diff --git a/core/src/mantle/tx_builder.rs b/core/src/mantle/tx_builder.rs index a481ebd63..3d5bfbe54 100644 --- a/core/src/mantle/tx_builder.rs +++ b/core/src/mantle/tx_builder.rs @@ -6,10 +6,10 @@ use super::{GasCalculator as _, GasConstants, MantleTx, Note, Op, Utxo}; use crate::{ mantle::{ NoteId, - gas::{GasCost, GasOverflow, GasPrice}, + gas::{GasCost, GasOverflow}, ledger::{Inputs, Outputs}, ops::{channel::withdraw::ChannelWithdrawOp, transfer::TransferOp}, - tx::MantleTxContext, + tx::{GasPrices, MantleTxContext}, }, proofs::channel_withdraw_proof::ChannelWithdrawProof, }; @@ -29,11 +29,7 @@ impl MantleTxBuilder { #[must_use] pub fn new(context: MantleTxContext) -> Self { Self { - mantle_tx: MantleTx { - ops: vec![], - execution_gas_price: 0.into(), - storage_gas_price: 0.into(), - }, + mantle_tx: vec![].into(), ledger_inputs: vec![], pending_transfer: TransferOp::new(Inputs::new(vec![]), Outputs::new(vec![])), channel_withdraw_proofs: HashMap::new(), @@ -41,6 +37,11 @@ impl MantleTxBuilder { } } + #[must_use] + pub fn get_gas_prices(&self) -> GasPrices { + self.context.gas_context.get_gas_prices() + } + #[must_use] pub fn push_op(self, op: Op) -> Self { self.extend_ops([op]) @@ -48,14 +49,14 @@ impl MantleTxBuilder { #[must_use] pub fn extend_ops(mut self, ops: impl IntoIterator) -> Self { - self.mantle_tx.ops.extend(ops); + self.mantle_tx.0.extend(ops); self } #[must_use] pub fn push_channel_withdraw(self, op: ChannelWithdrawOp, proof: ChannelWithdrawProof) -> Self { let mut builder = self.push_op(Op::ChannelWithdraw(op)); - let index = builder.mantle_tx.ops.len() - 1; + let index = builder.mantle_tx.ops().len() - 1; builder.channel_withdraw_proofs.insert(index, proof); builder } @@ -85,18 +86,6 @@ impl MantleTxBuilder { self } - #[must_use] - pub const fn set_execution_gas_price(mut self, price: GasPrice) -> Self { - self.mantle_tx.execution_gas_price = price; - self - } - - #[must_use] - pub const fn set_storage_gas_price(mut self, price: GasPrice) -> Self { - self.mantle_tx.storage_gas_price = price; - self - } - pub fn return_change( self, change_pk: ZkPublicKey, @@ -174,7 +163,7 @@ impl MantleTxBuilder { /// build. pub fn consumed_or_locked_notes(&self) -> impl Iterator { self.mantle_tx - .ops + .ops() .iter() .flat_map(|op| { let inputs: &[NoteId] = match op { @@ -204,7 +193,7 @@ impl MantleTxBuilder { #[must_use] pub fn build(mut self) -> MantleTx { - self.mantle_tx.ops.push(Op::Transfer(self.pending_transfer)); + self.mantle_tx.0.push(Op::Transfer(self.pending_transfer)); self.mantle_tx } } @@ -286,7 +275,7 @@ mod tests { // Init a tx builder let context = MantleTxContext { - gas_context: MantleTxGasContext::new([(op.channel_id, 1)].into()), + gas_context: MantleTxGasContext::new([(op.channel_id, 1)].into(), GasPrices::new(0, 0)), leader_reward_amount: 30, }; let builder = MantleTxBuilder::new(context).push_op(Op::ChannelWithdraw(op)); @@ -354,7 +343,7 @@ mod tests { // Init a tx builder for sending 30 to the recipient let channel_id = ChannelId::from([0; 32]); let context = MantleTxContext { - gas_context: MantleTxGasContext::new([(channel_id, 1)].into()), + gas_context: MantleTxGasContext::new([(channel_id, 1)].into(), GasPrices::new(0, 0)), leader_reward_amount: 30, }; let withdraw_note = Note { diff --git a/ledger/src/cryptarchia/mod.rs b/ledger/src/cryptarchia/mod.rs index 84327516d..ccf4c7259 100644 --- a/ledger/src/cryptarchia/mod.rs +++ b/ledger/src/cryptarchia/mod.rs @@ -662,6 +662,7 @@ pub mod tests { gas::MainnetGasConstants, ledger::{Inputs, Outputs}, ops::leader_claim::VoucherCm, + tx::GasPrices, }, sdp::ServiceParameters, }; @@ -738,6 +739,15 @@ pub mod tests { ..self } } + + #[cfg(test)] + #[must_use] + pub fn set_storage_price(self, new_storage_price: GasPrice) -> Self { + Self { + storage_gas_price: new_storage_price, + ..self + } + } } fn update_ledger( @@ -1256,11 +1266,7 @@ pub mod tests { .collect::>(); let inputs = inputs.iter().map(|(_, utxo)| utxo.id()).collect::>(); let transfer_op = TransferOp::new(Inputs::new(inputs), Outputs::new(outputs)); - let mantle_tx = MantleTx { - ops: vec![Op::Transfer(transfer_op.clone())], - execution_gas_price: GENESIS_EXECUTION_GAS_PRICE, - storage_gas_price: GENESIS_STORAGE_GAS_PRICE, - }; + let mantle_tx = MantleTx(vec![Op::Transfer(transfer_op.clone())]); let transfer_sig = ZkKey::multi_sign(&sks, &mantle_tx.hash().to_fr()).unwrap(); ( SignedMantleTx { @@ -1292,7 +1298,8 @@ pub mod tests { vec![output_note], ); - let _fees = AuthenticatedMantleTx::total_gas_cost::(&tx); + let _fees = + AuthenticatedMantleTx::total_gas_cost::(&tx, GasPrices::new(0, 0)); let result = ledger_state.try_apply_transfer::<(), MainnetGasConstants>( &locked_notes, &transfer_op, @@ -1323,7 +1330,8 @@ pub mod tests { let (tx, transfer_op, transfer_sig) = create_tx_with_transfer(&[(¬e_sk, &input_utxo)], vec![output_note1, output_note2]); - let _fees = AuthenticatedMantleTx::total_gas_cost::(&tx); + let _fees = + AuthenticatedMantleTx::total_gas_cost::(&tx, GasPrices::new(0, 0)); let (new_state, balance) = ledger_state .try_apply_transfer::<(), MainnetGasConstants>( &locked_notes, @@ -1359,7 +1367,8 @@ pub mod tests { vec![], ); let locked_notes = LockedNotes::new(); - let _fees = AuthenticatedMantleTx::total_gas_cost::(&tx); + let _fees = + AuthenticatedMantleTx::total_gas_cost::(&tx, GasPrices::new(0, 0)); let (final_state, final_balance) = new_state .try_apply_transfer::<(), MainnetGasConstants>( &locked_notes, @@ -1487,7 +1496,8 @@ pub mod tests { let (tx, transfer_op, transfer_sig) = create_tx_with_transfer(&[(&input_sk, &input_utxo)], vec![]); - let _fees = AuthenticatedMantleTx::total_gas_cost::(&tx); + let _fees = + AuthenticatedMantleTx::total_gas_cost::(&tx, GasPrices::new(0, 0)); let result = ledger_state.try_apply_transfer::<(), MainnetGasConstants>( &locked_notes, &transfer_op, diff --git a/ledger/src/lib.rs b/ledger/src/lib.rs index 3b0218f54..694670cb8 100644 --- a/ledger/src/lib.rs +++ b/ledger/src/lib.rs @@ -6,7 +6,7 @@ mod config; pub mod cryptarchia; pub mod mantle; -use std::{cmp::Ordering, collections::HashMap, hash::Hash}; +use std::{collections::HashMap, hash::Hash}; pub use config::Config; use cryptarchia::LedgerState as CryptarchiaLedger; @@ -24,7 +24,7 @@ use lb_core::{ }, leader_claim::{LeaderClaimExecutionContext, LeaderClaimValidationContext}, }, - tx::MantleTxContext, + tx::{GasPrices, MantleTxContext, MantleTxGasContext}, }, proofs::leader_proof, sdp::{Declaration, DeclarationId, ProviderId, ProviderInfo, ServiceType, SessionNumber}, @@ -143,7 +143,7 @@ where parent_id: Id, slot: Slot, proof: &LeaderProof, - txs: impl Iterator, + txs: impl Iterator>, ) -> Result<(Id, LedgerState), LedgerError> where LeaderProof: leader_proof::LeaderProof, @@ -218,7 +218,7 @@ impl LedgerState { self, slot: Slot, proof: &LeaderProof, - txs: impl Iterator, + txs: impl Iterator>, config: &Config, ) -> Result> where @@ -265,6 +265,14 @@ impl LedgerState { }) } + #[must_use] + pub const fn get_gas_prices(&self) -> GasPrices { + GasPrices { + execution_base_gas_price: *self.cryptarchia_ledger.execution_base_fee(), + storage_gas_price: *self.cryptarchia_ledger.storage_gas_price(), + } + } + /// total estimated stake and on the average of fees consumed per block over /// the last `BLOCK_REWARD_WINDOW_SIZE` blocks. See the block rewards /// specification: @@ -337,7 +345,7 @@ impl LedgerState { pub fn try_apply_contents( mut self, config: &Config, - txs: impl Iterator, + txs: impl Iterator>, ) -> Result> { let mut total_block_execution_gas: Gas = 0.into(); let mut total_fee_burned: GasCost = 0.into(); @@ -346,44 +354,46 @@ impl LedgerState { let balance; (self, balance) = self.try_apply_tx::<_, Constants>(config, &tx)?; + let gas_prices = GasPrices { + execution_base_gas_price: *self.cryptarchia_ledger.execution_base_fee(), + storage_gas_price: *self.cryptarchia_ledger.storage_gas_price(), + }; // Check the transaction is balanced - let total_gas_cost = AuthenticatedMantleTx::total_gas_cost::(&tx)?; + let total_gas_cost = + AuthenticatedMantleTx::total_gas_cost::(&tx, gas_prices.clone())?; tracing::debug!( balance, total_gas_cost = total_gas_cost.into_inner(), - storage_gas_price = ?tx.mantle_tx().storage_gas_price, - execution_gas_price = ?tx.mantle_tx().execution_gas_price, + storage_gas_price = ?self.cryptarchia_ledger.storage_gas_price(), + execution_gas_price = ?self.cryptarchia_ledger.execution_base_fee(), "tx balance check" ); - match balance.cmp(&Balance::from(total_gas_cost.into_inner())) { - Ordering::Less => return Err(LedgerError::InsufficientBalance), - Ordering::Greater => return Err(LedgerError::UnbalancedTransaction), - Ordering::Equal => {} // OK! + + // Check that the transaction at least pays for the base execution fee and + // storage + if balance < Balance::from(total_gas_cost.into_inner()) { + return Err(LedgerError::InsufficientBalance); } // Update the total of fee burned and tipped in the block let tx_fee_burned = GasCost::calculate( - AuthenticatedMantleTx::execution_gas_consumption::(&tx)?, - *self.cryptarchia_ledger.execution_base_fee(), + AuthenticatedMantleTx::execution_gas_consumption::( + &tx, + gas_prices.clone(), + )?, + gas_prices.execution_base_gas_price, )? - .checked_add(AuthenticatedMantleTx::storage_gas_cost(&tx)?)?; + .checked_add(AuthenticatedMantleTx::storage_gas_cost( + &tx, + gas_prices.clone(), + )?)?; - // Check that the transaction at least pays for the base fee - if balance < Balance::from(tx_fee_burned.into_inner()) { - return Err(LedgerError::InsufficientExecutionFee); - } - - // Check that the transaction pays the correct storage fees - // TODO: remove the storage price from the Mantle Transaction and wallet should - // pull the price from ledger to get the fees to pay - if tx.mantle_tx().storage_gas_price != *self.cryptarchia_ledger.storage_gas_price() { - return Err(LedgerError::InvalidStoragePrice); - } let tx_fee_tip = GasCost::from(balance as Value).checked_sub(tx_fee_burned)?; total_fee_burned = total_fee_burned.checked_add(tx_fee_burned)?; total_fee_tip = total_fee_tip.checked_add(tx_fee_tip)?; - total_block_execution_gas = total_block_execution_gas - .checked_add(AuthenticatedMantleTx::execution_gas_consumption::(&tx)?)?; + total_block_execution_gas = total_block_execution_gas.checked_add( + AuthenticatedMantleTx::execution_gas_consumption::(&tx, gas_prices)?, + )?; // Check that the block is not exceeding the Gas limit if total_block_execution_gas > EXECUTION_GAS_LIMIT { @@ -495,7 +505,10 @@ impl LedgerState { #[must_use] pub fn tx_context(&self) -> MantleTxContext { MantleTxContext { - gas_context: self.mantle_ledger().channels().into(), + gas_context: MantleTxGasContext::from_channels( + self.mantle_ledger().channels(), + self.get_gas_prices(), + ), leader_reward_amount: self.mantle_ledger().leader_reward_amount(), } } @@ -689,8 +702,7 @@ mod tests { use lb_core::{ mantle::{ MantleTx, Note, SignedMantleTx, Transaction as _, - gas::{GasPrice, MainnetGasConstants}, - genesis_tx::{GENESIS_EXECUTION_GAS_PRICE, GENESIS_STORAGE_GAS_PRICE}, + gas::MainnetGasConstants, ledger::{Inputs, Outputs}, ops::{ channel::{ @@ -714,19 +726,9 @@ mod tests { type HeaderId = [u8; 32]; - fn create_tx( - inputs: Vec, - outputs: Vec, - sks: &[ZkKey], - execution_price: GasPrice, - storage_price: GasPrice, - ) -> SignedMantleTx { + fn create_tx(inputs: Vec, outputs: Vec, sks: &[ZkKey]) -> SignedMantleTx { let transfer_op = TransferOp::new(Inputs::new(inputs), Outputs::new(outputs)); - let mantle_tx = MantleTx { - ops: vec![Op::Transfer(transfer_op)], - execution_gas_price: execution_price, - storage_gas_price: storage_price, - }; + let mantle_tx = MantleTx(vec![Op::Transfer(transfer_op)]); SignedMantleTx { ops_proofs: vec![OpProof::ZkSig( ZkKey::multi_sign(sks, &mantle_tx.hash().to_fr()).unwrap(), @@ -749,6 +751,17 @@ mod tests { (signing_key, verifying_key) } + fn update_ledger_prices(ledger_state: &mut LedgerState, new_execution: u64, new_storage: u64) { + ledger_state.cryptarchia_ledger = ledger_state + .cryptarchia_ledger + .clone() + .set_storage_price(new_storage.into()); + ledger_state.cryptarchia_ledger = ledger_state + .cryptarchia_ledger + .clone() + .set_execution_base_fee(new_execution.into()); + } + enum Key { Ed25519(Ed25519Key), Zk(ZkKey), @@ -761,11 +774,7 @@ mod tests { } fn create_multi_signed_tx(ops: Vec, signing_keys: Vec<&Key>) -> SignedMantleTx { - let mantle_tx = MantleTx { - ops: ops.clone(), - execution_gas_price: 0.into(), - storage_gas_price: 0.into(), - }; + let mantle_tx = MantleTx(ops.clone()); let tx_hash = mantle_tx.hash(); let ops_proofs = signing_keys @@ -830,18 +839,12 @@ mod tests { vec![utxo.id()], vec![output_note], std::slice::from_ref(&sk), - GENESIS_EXECUTION_GAS_PRICE, - GENESIS_STORAGE_GAS_PRICE, ); - let fees = AuthenticatedMantleTx::total_gas_cost::(&tx).unwrap(); + let fees = + AuthenticatedMantleTx::total_gas_cost::(&tx, GasPrices::default()) + .unwrap(); output_note.value = utxo.note.value - fees.into_inner(); - let tx = create_tx( - vec![utxo.id()], - vec![output_note], - &[sk], - GENESIS_EXECUTION_GAS_PRICE, - GENESIS_STORAGE_GAS_PRICE, - ); + let tx = create_tx(vec![utxo.id()], vec![output_note], &[sk]); // Create a dummy proof (using same structure as in cryptarchia tests) @@ -868,7 +871,7 @@ mod tests { assert!(!new_state.latest_utxos().contains(&utxo.id())); // Verify output was created - if let Op::Transfer(transfer_op) = &tx.mantle_tx.ops[0] { + if let Op::Transfer(transfer_op) = &tx.mantle_tx.ops()[0] { let output_utxo = transfer_op.outputs.utxo_by_index(0, transfer_op).unwrap(); assert!(new_state.latest_utxos().contains(&output_utxo.id())); } else { @@ -1043,11 +1046,7 @@ mod tests { outputs: Outputs::new(vec![withdraw_note]), withdraw_nonce: 0, }; - let withdraw_tx = MantleTx { - ops: vec![Op::ChannelWithdraw(withdraw.clone())], - execution_gas_price: 0.into(), - storage_gas_price: 0.into(), - }; + let withdraw_tx = MantleTx(vec![Op::ChannelWithdraw(withdraw.clone())]); let withdraw_tx_hash = withdraw_tx.hash(); let withdraw_proof = ChannelWithdrawProof::new(vec![WithdrawSignature::new( 0, @@ -1055,8 +1054,7 @@ mod tests { )]) .unwrap(); - let signed_tx = - create_multi_signed_tx(withdraw_tx.ops, vec![&Key::Withdraw(withdraw_proof)]); + let signed_tx = create_multi_signed_tx(withdraw_tx.0, vec![&Key::Withdraw(withdraw_proof)]); let result = ledger_state.try_apply_tx::(&test_config, signed_tx); @@ -1132,11 +1130,7 @@ mod tests { withdraw_nonce: 0, }; let wrong_key = Ed25519Key::from_bytes(&[42; 32]); - let withdraw_tx = MantleTx { - ops: vec![Op::ChannelWithdraw(withdraw.clone())], - execution_gas_price: 0.into(), - storage_gas_price: 0.into(), - }; + let withdraw_tx = MantleTx(vec![Op::ChannelWithdraw(withdraw.clone())]); let withdraw_tx_hash = withdraw_tx.hash(); let invalid_proof = ChannelWithdrawProof::new(vec![WithdrawSignature::new( 0, @@ -1145,7 +1139,7 @@ mod tests { .unwrap(); let signed_tx = create_multi_signed_tx( - withdraw_tx.ops, + withdraw_tx.0, vec![&Key::Withdraw(invalid_proof), &Key::EmptyZk], ); @@ -1421,42 +1415,13 @@ mod tests { // This test has been disabled pending API updates } - #[test] - fn test_storage_price_rejection() { - let utxo = utxo(); - let config = config(); - let ledger = LedgerState::from_utxos([utxo], &config); - - let mut output_note = Note::new(1, ZkPublicKey::new(BigUint::from(1u8).into())); - let sk = ZkKey::from(BigUint::from(0u8)); - let tx = create_tx( - vec![utxo.id()], - vec![output_note], - std::slice::from_ref(&sk), - GENESIS_EXECUTION_GAS_PRICE, - (GENESIS_STORAGE_GAS_PRICE.into_inner() + 1).into(), // wrong storage gas price - ); - let fees = AuthenticatedMantleTx::total_gas_cost::(&tx).unwrap(); - output_note.value = utxo.note.value - fees.into_inner(); - let tx = create_tx( - vec![utxo.id()], - vec![output_note], - &[sk], - GENESIS_EXECUTION_GAS_PRICE, - (GENESIS_STORAGE_GAS_PRICE.into_inner() + 1).into(), // wrong storage gas price - ); - - let result = ledger - .try_apply_contents::(&config, std::iter::once(&tx)); - assert_eq!(result, Err(LedgerError::InvalidStoragePrice)); - } - #[test] #[ignore = "TODO: enable once we determine non-zero genesis execution gas price"] - fn test_base_fee_rejection() { + fn test_fee_rejection() { let utxo = utxo(); let config = config(); let mut ledger = LedgerState::from_utxos([utxo], &config); + update_ledger_prices(&mut ledger, 1, 1); let mut output_note = Note::new(1, ZkPublicKey::new(BigUint::from(0u8).into())); let sk = ZkKey::from(BigUint::from(0u8)); @@ -1464,19 +1429,15 @@ mod tests { vec![utxo.id()], vec![output_note], std::slice::from_ref(&sk), - 1.into(), - 1.into(), ); // Pays 2925 fees = 2705 execution base fee + 0 execution tip + 220 storage - let fees = AuthenticatedMantleTx::total_gas_cost::(&tx).unwrap(); + let fees = AuthenticatedMantleTx::total_gas_cost::( + &tx, + ledger.get_gas_prices(), + ) + .unwrap(); output_note.value = utxo.note.value - fees.into_inner(); - let tx = create_tx( - vec![utxo.id()], - vec![output_note], - &[sk], - 1.into(), - 1.into(), - ); + let tx = create_tx(vec![utxo.id()], vec![output_note], &[sk]); let result = ledger .clone() @@ -1490,7 +1451,7 @@ mod tests { .try_apply_contents::(&config, std::iter::once(&tx)); // The transaction should be rejected because the price indicated for execution // doesn't cover the base fee that cost 27 050 - assert_eq!(result, Err(LedgerError::InsufficientExecutionFee)); + assert_eq!(result, Err(LedgerError::InsufficientBalance)); } #[test] @@ -1498,7 +1459,7 @@ mod tests { fn test_priority_fees_go_to_leader() { let utxo = utxo(); let config = config(); - let ledger = LedgerState::from_utxos([utxo], &config); + let mut ledger = LedgerState::from_utxos([utxo], &config); let mut output_note = Note::new(1, ZkPublicKey::new(BigUint::from(0u8).into())); let sk = ZkKey::from(BigUint::from(0u8)); @@ -1506,48 +1467,40 @@ mod tests { vec![utxo.id()], vec![output_note], std::slice::from_ref(&sk), - 1.into(), - 1.into(), ); - // The tx ays 2925 fees = 2705 execution base fee + 0 execution tip + 220 + update_ledger_prices(&mut ledger, 1, 1); + // The tx pays 794 fees = 590 execution base fee + 0 execution tip + 204 // storage - let fees = AuthenticatedMantleTx::total_gas_cost::(&tx).unwrap(); + let fees = AuthenticatedMantleTx::total_gas_cost::( + &tx, + ledger.get_gas_prices(), + ) + .unwrap(); output_note.value = utxo.note.value - fees.into_inner(); let tx = create_tx( vec![utxo.id()], vec![output_note], std::slice::from_ref(&sk), - 1.into(), - 1.into(), ); let result = ledger .clone() .try_apply_contents::(&config, std::iter::once(&tx)); - // The unwrap should succeed because the user pays at least the base fee of 2705 + // The unwrap should succeed because the user pays at least the base fee of 794 let no_priority_fee_ledger = result.unwrap(); + // The tx ays 1794 fees = 590 execution base fee + 1000 execution tip + 204 + // storage + output_note.value = utxo.note.value - fees.into_inner() - 1000; let tx = create_tx( vec![utxo.id()], vec![output_note], std::slice::from_ref(&sk), - 2.into(), - 1.into(), - ); - // The tx ays 5630 fees = 2705 execution base fee + 2705 execution tip + 220 - // storage - let fees = AuthenticatedMantleTx::total_gas_cost::(&tx).unwrap(); - output_note.value = utxo.note.value - fees.into_inner(); - let tx = create_tx( - vec![utxo.id()], - vec![output_note], - &[sk], - 2.into(), - 1.into(), ); + let result = ledger .try_apply_contents::(&config, std::iter::once(&tx)); - // The unwrap should succeed because the user pays at least the base fee of 2705 + // The unwrap should succeed because the user pays at least the base fee of 794 let priority_fee_ledger = result.unwrap(); assert_eq!( @@ -1555,7 +1508,7 @@ mod tests { .mantle_ledger .leaders .get_pending_rewards() - + 2705, + + 1000, priority_fee_ledger .mantle_ledger .leaders diff --git a/nodes/node/binary/src/api/serializers/transactions.rs b/nodes/node/binary/src/api/serializers/transactions.rs index 3d361256f..ae548e42b 100644 --- a/nodes/node/binary/src/api/serializers/transactions.rs +++ b/nodes/node/binary/src/api/serializers/transactions.rs @@ -1,4 +1,4 @@ -use lb_core::mantle::{MantleTx, Op, OpProof, SignedMantleTx, TxHash, gas::GasPrice}; +use lb_core::mantle::{MantleTx, Op, OpProof, SignedMantleTx, TxHash}; use serde::Serialize; #[derive(Serialize)] @@ -6,9 +6,8 @@ use serde::Serialize; pub struct ApiTransactionSerializer { #[serde(getter = "::hash")] hash: TxHash, + #[serde(getter = "MantleTx::ops")] ops: Vec, - execution_gas_price: GasPrice, - storage_gas_price: GasPrice, } #[derive(Serialize)] diff --git a/nodes/node/binary/src/config/deployment/devnet.rs b/nodes/node/binary/src/config/deployment/devnet.rs index eee210978..8a77587e3 100644 --- a/nodes/node/binary/src/config/deployment/devnet.rs +++ b/nodes/node/binary/src/config/deployment/devnet.rs @@ -57,33 +57,31 @@ cryptarchia: transactions: - mantle_tx: ops: - - opcode: 0 - payload: - inputs: [] - outputs: - - value: 1 - pk: d204000000000000000000000000000000000000000000000000000000000000 - - value: 100 - pk: '2e03b2eff5a45478e7e79668d2a146cf2c5c7925bce927f2b1c67f2ab4fc0d26' - - value: 1 - pk: ed266e6e887b9b97059dc1aa1b7b2e19b934291753c6336a163fe4ebaa28e717 - - opcode: 17 - payload: - channel_id: '0000000000000000000000000000000000000000000000000000000000000000' - # chain_id_len=12 (u64_le), chain_id=logos-devnet (utf-8), - # genesis_time=2026-01-10T07:47:56Z (u64_le), epoch_nonce=[0u8; 32] - inscription: '0c000000000000006c6f676f732d6465766e65742c046269000000000000000000000000000000000000000000000000000000000000000000000000' - parent: '0000000000000000000000000000000000000000000000000000000000000000' - signer: '0000000000000000000000000000000000000000000000000000000000000000' - - opcode: 32 - payload: - service_type: BN - locators: [] - provider_id: '86c8519f00178e9eb1fe5f4247e4bed77d4c9f6da2fb10e8a1fdd7ba6bc79fa0' - zk_id: '64249c75c2cb813578b75d05b215fc95f67cea5862fff047228183f22e63460e' - locked_note_id: '0000000000000000000000000000000000000000000000000000000000000000' - execution_gas_price: 0 - storage_gas_price: 0 + - opcode: 0 + payload: + inputs: [] + outputs: + - value: 1 + pk: d204000000000000000000000000000000000000000000000000000000000000 + - value: 100 + pk: '2e03b2eff5a45478e7e79668d2a146cf2c5c7925bce927f2b1c67f2ab4fc0d26' + - value: 1 + pk: ed266e6e887b9b97059dc1aa1b7b2e19b934291753c6336a163fe4ebaa28e717 + - opcode: 17 + payload: + channel_id: '0000000000000000000000000000000000000000000000000000000000000000' + # chain_id_len=12 (u64_le), chain_id=logos-devnet (utf-8), + # genesis_time=2026-01-10T07:47:56Z (u64_le), epoch_nonce=[0u8; 32] + inscription: '0c000000000000006c6f676f732d6465766e65742c046269000000000000000000000000000000000000000000000000000000000000000000000000' + parent: '0000000000000000000000000000000000000000000000000000000000000000' + signer: '0000000000000000000000000000000000000000000000000000000000000000' + - opcode: 32 + payload: + service_type: BN + locators: [] + provider_id: '86c8519f00178e9eb1fe5f4247e4bed77d4c9f6da2fb10e8a1fdd7ba6bc79fa0' + zk_id: '64249c75c2cb813578b75d05b215fc95f67cea5862fff047228183f22e63460e' + locked_note_id: '0000000000000000000000000000000000000000000000000000000000000000' ops_proofs: - !Ed25519Sig '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' - !Ed25519Sig '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' diff --git a/nodes/node/genesis/genesis_block.yaml b/nodes/node/genesis/genesis_block.yaml index aa2a258a0..9a43241ba 100644 --- a/nodes/node/genesis/genesis_block.yaml +++ b/nodes/node/genesis/genesis_block.yaml @@ -10,34 +10,32 @@ header: voucher_cm: '0000000000000000000000000000000000000000000000000000000000000000' signature: '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' transactions: -- mantle_tx: - ops: - - opcode: 0 - payload: - inputs: [] - outputs: - - value: 1 - pk: d204000000000000000000000000000000000000000000000000000000000000 - - value: 100 - pk: '2e03b2eff5a45478e7e79668d2a146cf2c5c7925bce927f2b1c67f2ab4fc0d26' - - value: 1 - pk: ed266e6e887b9b97059dc1aa1b7b2e19b934291753c6336a163fe4ebaa28e717 - - opcode: 17 - payload: - channel_id: '0000000000000000000000000000000000000000000000000000000000000000' - inscription: '67656e65736973' - parent: '0000000000000000000000000000000000000000000000000000000000000000' - signer: '0000000000000000000000000000000000000000000000000000000000000000' - - opcode: 32 - payload: - service_type: BN - locators: [] - provider_id: '86c8519f00178e9eb1fe5f4247e4bed77d4c9f6da2fb10e8a1fdd7ba6bc79fa0' - zk_id: '64249c75c2cb813578b75d05b215fc95f67cea5862fff047228183f22e63460e' - locked_note_id: '0000000000000000000000000000000000000000000000000000000000000000' - execution_gas_price: 0 - storage_gas_price: 0 - ops_proofs: - - !Ed25519Sig '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' - - !Ed25519Sig '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' - - !Ed25519Sig '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + - mantle_tx: + ops: + - opcode: 0 + payload: + inputs: [ ] + outputs: + - value: 1 + pk: d204000000000000000000000000000000000000000000000000000000000000 + - value: 100 + pk: '2e03b2eff5a45478e7e79668d2a146cf2c5c7925bce927f2b1c67f2ab4fc0d26' + - value: 1 + pk: ed266e6e887b9b97059dc1aa1b7b2e19b934291753c6336a163fe4ebaa28e717 + - opcode: 17 + payload: + channel_id: '0000000000000000000000000000000000000000000000000000000000000000' + inscription: '67656e65736973' + parent: '0000000000000000000000000000000000000000000000000000000000000000' + signer: '0000000000000000000000000000000000000000000000000000000000000000' + - opcode: 32 + payload: + service_type: BN + locators: [ ] + provider_id: '86c8519f00178e9eb1fe5f4247e4bed77d4c9f6da2fb10e8a1fdd7ba6bc79fa0' + zk_id: '64249c75c2cb813578b75d05b215fc95f67cea5862fff047228183f22e63460e' + locked_note_id: '0000000000000000000000000000000000000000000000000000000000000000' + ops_proofs: + - !Ed25519Sig '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + - !Ed25519Sig '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + - !Ed25519Sig '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' diff --git a/nodes/node/standalone-deployment-config.yaml b/nodes/node/standalone-deployment-config.yaml index 330ff8f7a..90ced3027 100644 --- a/nodes/node/standalone-deployment-config.yaml +++ b/nodes/node/standalone-deployment-config.yaml @@ -52,39 +52,37 @@ cryptarchia: voucher_cm: '0000000000000000000000000000000000000000000000000000000000000000' signature: '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' transactions: - - mantle_tx: - ops: - - opcode: 0 - payload: - inputs: [] - outputs: - - value: 1 - pk: d204000000000000000000000000000000000000000000000000000000000000 - - value: 100 - pk: '2e03b2eff5a45478e7e79668d2a146cf2c5c7925bce927f2b1c67f2ab4fc0d26' - - value: 1 - pk: ed266e6e887b9b97059dc1aa1b7b2e19b934291753c6336a163fe4ebaa28e717 - - opcode: 17 - payload: - channel_id: '0000000000000000000000000000000000000000000000000000000000000000' - # chain_id_len=12 (u64_le), chain_id=logos-devnet (utf-8), - # genesis_time=2026-01-10T07:47:56Z (u64_le), epoch_nonce=[0u8; 32] - inscription: '0c000000000000006c6f676f732d6465766e65742c046269000000000000000000000000000000000000000000000000000000000000000000000000' - parent: '0000000000000000000000000000000000000000000000000000000000000000' - signer: '0000000000000000000000000000000000000000000000000000000000000000' - - opcode: 32 - payload: - service_type: BN - locators: [] - provider_id: '86c8519f00178e9eb1fe5f4247e4bed77d4c9f6da2fb10e8a1fdd7ba6bc79fa0' - zk_id: '64249c75c2cb813578b75d05b215fc95f67cea5862fff047228183f22e63460e' - locked_note_id: '0000000000000000000000000000000000000000000000000000000000000000' - execution_gas_price: 0 - storage_gas_price: 0 - ops_proofs: - - !Ed25519Sig '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' - - !Ed25519Sig '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' - - !Ed25519Sig '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + - mantle_tx: + ops: + - opcode: 0 + payload: + inputs: [ ] + outputs: + - value: 1 + pk: d204000000000000000000000000000000000000000000000000000000000000 + - value: 100 + pk: '2e03b2eff5a45478e7e79668d2a146cf2c5c7925bce927f2b1c67f2ab4fc0d26' + - value: 1 + pk: ed266e6e887b9b97059dc1aa1b7b2e19b934291753c6336a163fe4ebaa28e717 + - opcode: 17 + payload: + channel_id: '0000000000000000000000000000000000000000000000000000000000000000' + # chain_id_len=12 (u64_le), chain_id=logos-devnet (utf-8), + # genesis_time=2026-01-10T07:47:56Z (u64_le), epoch_nonce=[0u8; 32] + inscription: '0c000000000000006c6f676f732d6465766e65742c046269000000000000000000000000000000000000000000000000000000000000000000000000' + parent: '0000000000000000000000000000000000000000000000000000000000000000' + signer: '0000000000000000000000000000000000000000000000000000000000000000' + - opcode: 32 + payload: + service_type: BN + locators: [ ] + provider_id: '86c8519f00178e9eb1fe5f4247e4bed77d4c9f6da2fb10e8a1fdd7ba6bc79fa0' + zk_id: '64249c75c2cb813578b75d05b215fc95f67cea5862fff047228183f22e63460e' + locked_note_id: '0000000000000000000000000000000000000000000000000000000000000000' + ops_proofs: + - !Ed25519Sig '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + - !Ed25519Sig '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + - !Ed25519Sig '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' time: slot_duration: '1.0' mempool: diff --git a/services/chain/chain-service/src/lib.rs b/services/chain/chain-service/src/lib.rs index bddf7bd4d..c677ef70c 100644 --- a/services/chain/chain-service/src/lib.rs +++ b/services/chain/chain-service/src/lib.rs @@ -29,7 +29,7 @@ use lb_chain_broadcast_service::{ use lb_core::{ block::{Block, genesis::GenesisBlock}, header::HeaderId, - mantle::{AuthenticatedMantleTx, Transaction, TxHash, gas::MainnetGasConstants}, + mantle::{AuthenticatedMantleTx, Transaction, TxHash, gas::MainnetGasConstants, tx::GasPrices}, sdp::{Declaration, DeclarationId, ProviderId, ProviderInfo, ServiceType}, }; use lb_cryptarchia_engine::{Branch, PrunedBlocks, ReorgedBlocks}; @@ -297,7 +297,7 @@ impl Cryptarchia { current_slot: Slot, ) -> Result<(PrunedBlocks, ReorgedBlocks), Error> where - Tx: AuthenticatedMantleTx, + Tx: AuthenticatedMantleTx, { let header = block.header(); let id = header.id(); @@ -512,7 +512,7 @@ impl ServiceCore for CryptarchiaConsensus where Tx: Transaction - + AuthenticatedMantleTx + + AuthenticatedMantleTx + Debug + Clone + Eq @@ -720,7 +720,7 @@ impl CryptarchiaConsensus where Tx: Transaction - + AuthenticatedMantleTx + + AuthenticatedMantleTx + Debug + Clone + Eq diff --git a/services/wallet/src/api.rs b/services/wallet/src/api.rs index 85b18faeb..d6bb8f705 100644 --- a/services/wallet/src/api.rs +++ b/services/wallet/src/api.rs @@ -253,7 +253,7 @@ mod tests { use lb_core::mantle::{ ops::channel::{ChannelId, ChannelKeyIndex}, - tx::MantleTxGasContext, + tx::{GasPrices, MantleTxGasContext}, }; use overwatch::services::state::{NoOperator, NoState}; use tokio::sync::mpsc; @@ -294,6 +294,7 @@ mod tests { let expected_block_id = HeaderId::from([7u8; 32]); let expected_channel_id = ChannelId::from([9u8; 32]); let expected_threshold: ChannelKeyIndex = 2; + let expected_gas_prices = GasPrices::new(3, 7); let (msg_sender, mut msg_receiver) = mpsc::channel(1); tokio::spawn(async move { @@ -303,6 +304,7 @@ mod tests { let context = MantleTxContext { gas_context: MantleTxGasContext::new( std::iter::once((expected_channel_id, expected_threshold)).collect(), + expected_gas_prices, ), leader_reward_amount: 0, }; diff --git a/services/wallet/src/lib.rs b/services/wallet/src/lib.rs index d63789ffa..570d23935 100644 --- a/services/wallet/src/lib.rs +++ b/services/wallet/src/lib.rs @@ -748,7 +748,7 @@ where let tx_hash = mantle_tx.hash(); let mut ops_proofs = Vec::new(); - for (i, op) in mantle_tx.ops.iter().enumerate() { + for (i, op) in mantle_tx.ops().iter().enumerate() { let proof = match op { Op::ChannelInscribe(inscribe_op) => { Self::sign_inscription(tx_hash, inscribe_op, kms).await? diff --git a/testnet/l2-sequencer-archival-demo/archiver/src/block.rs b/testnet/l2-sequencer-archival-demo/archiver/src/block.rs index 1d0718d9b..afe393c70 100644 --- a/testnet/l2-sequencer-archival-demo/archiver/src/block.rs +++ b/testnet/l2-sequencer-archival-demo/archiver/src/block.rs @@ -90,7 +90,7 @@ fn extract_l2_blocks( .flat_map(|tx| { let tx_hash = tx.mantle_tx.hash(); tx.mantle_tx - .ops + .0 .iter() .filter_map(|op| match op { Op::ChannelInscribe(InscriptionOp { diff --git a/testnet/l2-sequencer-archival-demo/sequencer/src/sequencer.rs b/testnet/l2-sequencer-archival-demo/sequencer/src/sequencer.rs index 6c973d7d8..6affa39c5 100644 --- a/testnet/l2-sequencer-archival-demo/sequencer/src/sequencer.rs +++ b/testnet/l2-sequencer-archival-demo/sequencer/src/sequencer.rs @@ -165,11 +165,7 @@ impl Sequencer { signer: verifying_key, }; - let inscribe_tx = MantleTx { - ops: vec![Op::ChannelInscribe(inscribe_op)], - storage_gas_price: 0.into(), - execution_gas_price: 0.into(), - }; + let inscribe_tx = MantleTx(vec![Op::ChannelInscribe(inscribe_op)]); let tx_hash = inscribe_tx.hash(); let signature_bytes = self @@ -206,7 +202,7 @@ impl Sequencer { block_id: HeaderId, ) -> bool { for tx in &block.transactions { - for op in &tx.mantle_tx.ops { + for op in tx.mantle_tx.ops() { if let Op::ChannelInscribe(inscribe) = op { tracing::debug!( "Found inscription: channel={}, parent={}", @@ -273,7 +269,7 @@ impl Sequencer { fn get_expected_inscription(tx: &SignedMantleTx) -> &InscriptionOp { let expected_op = tx .mantle_tx - .ops + .ops() .first() .expect("transaction should have at least one op"); @@ -513,7 +509,7 @@ impl Sequencer { let parent = self.get_last_msg_id().await?; let tx = self.create_inscribe_tx(inscription_data, parent); - let new_msg_id = match tx.mantle_tx.ops.first() { + let new_msg_id = match tx.mantle_tx.ops().first() { Some(Op::ChannelInscribe(inscribe)) => inscribe.id(), _ => panic!("Expected ChannelInscribe op"), }; diff --git a/tests/src/common/mantle_inscription.rs b/tests/src/common/mantle_inscription.rs index 09cdf6e48..e9e271562 100644 --- a/tests/src/common/mantle_inscription.rs +++ b/tests/src/common/mantle_inscription.rs @@ -2,12 +2,13 @@ use std::collections::HashMap; use lb_core::mantle::{ OpProof, TxHash, + gas::GasPrice, genesis_tx::GENESIS_STORAGE_GAS_PRICE, ops::{ Op, channel::{ChannelId, MsgId, inscribe::InscriptionOp}, }, - tx::{MantleTxContext, MantleTxGasContext}, + tx::{GasPrices, MantleTxContext, MantleTxGasContext}, tx_builder::MantleTxBuilder, }; use lb_key_management_system_service::keys::{Ed25519Key, Ed25519Signature}; @@ -19,19 +20,22 @@ pub fn build_inscription_tx_builder( parent: Option, ) -> MantleTxBuilder { let tx_context = MantleTxContext { - gas_context: MantleTxGasContext::new(HashMap::new()), + gas_context: MantleTxGasContext::new( + HashMap::new(), + GasPrices { + execution_base_gas_price: GasPrice::new(0), + storage_gas_price: GENESIS_STORAGE_GAS_PRICE, + }, + ), leader_reward_amount: 0, }; - MantleTxBuilder::new(tx_context) - .push_op(Op::ChannelInscribe(InscriptionOp { - channel_id, - inscription, - parent: parent.unwrap_or_else(MsgId::root), - signer: signing_key.public_key(), - })) - .set_storage_gas_price(GENESIS_STORAGE_GAS_PRICE) - .set_execution_gas_price(0.into()) + MantleTxBuilder::new(tx_context).push_op(Op::ChannelInscribe(InscriptionOp { + channel_id, + inscription, + parent: parent.unwrap_or_else(MsgId::root), + signer: signing_key.public_key(), + })) } #[must_use] diff --git a/tests/src/common/mantle_tx.rs b/tests/src/common/mantle_tx.rs index e7fe79e34..2e540df1a 100644 --- a/tests/src/common/mantle_tx.rs +++ b/tests/src/common/mantle_tx.rs @@ -38,11 +38,7 @@ pub fn create_channel_inscribe_tx( signer: verifying_key, }; - let inscribe_tx = MantleTx { - ops: vec![Op::ChannelInscribe(inscribe_op)], - storage_gas_price: 0.into(), - execution_gas_price: 0.into(), - }; + let inscribe_tx = MantleTx(vec![Op::ChannelInscribe(inscribe_op)]); let tx_hash = inscribe_tx.hash(); let signature_bytes = signing_key @@ -67,11 +63,7 @@ pub fn create_channel_set_keys_tx( keys, }; - let set_keys_tx = MantleTx { - ops: vec![Op::ChannelSetKeys(set_keys_op)], - storage_gas_price: 0.into(), - execution_gas_price: 0.into(), - }; + let set_keys_tx = MantleTx(vec![Op::ChannelSetKeys(set_keys_op)]); let tx_hash = set_keys_tx.hash(); let signature_bytes = signing_key @@ -107,11 +99,7 @@ pub fn create_sdp_declare_tx( locked_note_id, }; - let mantle_tx = MantleTx { - ops: vec![Op::SDPDeclare(declaration.clone())], - execution_gas_price: 0.into(), - storage_gas_price: 0.into(), - }; + let mantle_tx = MantleTx(vec![Op::SDPDeclare(declaration.clone())]); let tx_hash = mantle_tx.hash(); @@ -139,11 +127,7 @@ pub fn create_sdp_active_tx( zk_sk: &ZkKey, note_sk: &ZkKey, ) -> SignedMantleTx { - let mantle_tx = MantleTx { - ops: vec![Op::SDPActive(active.clone())], - execution_gas_price: 0.into(), - storage_gas_price: 0.into(), - }; + let mantle_tx = MantleTx(vec![Op::SDPActive(active.clone())]); let tx_hash = mantle_tx.hash(); let zk_sig = prove_zk_signature(&tx_hash, &[note_sk.clone(), zk_sk.clone()]); @@ -160,11 +144,7 @@ pub fn create_sdp_withdraw_tx( zk_sk: &ZkKey, note_sk: &ZkKey, ) -> SignedMantleTx { - let mantle_tx = MantleTx { - ops: vec![Op::SDPWithdraw(withdraw)], - execution_gas_price: 0.into(), - storage_gas_price: 0.into(), - }; + let mantle_tx = MantleTx(vec![Op::SDPWithdraw(withdraw)]); let tx_hash = mantle_tx.hash(); let zk_sig = prove_zk_signature(&tx_hash, &[note_sk.clone(), zk_sk.clone()]); @@ -193,11 +173,7 @@ pub fn create_inscription_transaction_with_id( signer, }; - let mantle_tx = MantleTx { - ops: vec![Op::ChannelInscribe(inscription_op)], - storage_gas_price: 0.into(), - execution_gas_price: 0.into(), - }; + let mantle_tx = MantleTx(vec![Op::ChannelInscribe(inscription_op)]); let tx_hash = mantle_tx.hash(); let signature = signing_key.sign_payload(&tx_hash.as_signing_bytes()); diff --git a/tests/src/cucumber/steps/manual_transactions/chunked_transfers.rs b/tests/src/cucumber/steps/manual_transactions/chunked_transfers.rs index 45064fbb2..bd660f17d 100644 --- a/tests/src/cucumber/steps/manual_transactions/chunked_transfers.rs +++ b/tests/src/cucumber/steps/manual_transactions/chunked_transfers.rs @@ -147,7 +147,7 @@ fn with_transfer_input_chunks( /// Reads the builder-authored pending transfer outputs so caller-side chunking /// can preserve them on the final transfer chunk. fn pending_transfer_output_sum(tx_builder: &MantleTxBuilder) -> u128 { - match tx_builder.clone().build().ops.pop() { + match tx_builder.clone().build().0.pop() { Some(Op::Transfer(transfer)) => transfer .outputs .iter() diff --git a/tests/src/cucumber/steps/manual_transactions/tracked_transactions.rs b/tests/src/cucumber/steps/manual_transactions/tracked_transactions.rs index ec9678cc9..2712b13b4 100644 --- a/tests/src/cucumber/steps/manual_transactions/tracked_transactions.rs +++ b/tests/src/cucumber/steps/manual_transactions/tracked_transactions.rs @@ -3,7 +3,6 @@ use std::{collections::HashSet, time::Duration}; use lb_common_http_client::ApiBlock; use lb_core::mantle::{ MantleTx, Note, Op, OpProof, SignedMantleTx, Transaction as _, TxHash, - genesis_tx::GENESIS_STORAGE_GAS_PRICE, ledger::{Inputs, Outputs}, ops::transfer::TransferOp, }; @@ -184,11 +183,7 @@ fn create_invalid_transaction() -> SignedMantleTx { let output_note = Note::new(1000, ZkPublicKey::new(1u8.into())); let transfer_op = TransferOp::new(Inputs::new(vec![]), Outputs::new(vec![output_note])); - let mantle_tx = MantleTx { - ops: vec![Op::Transfer(transfer_op)], - storage_gas_price: GENESIS_STORAGE_GAS_PRICE, - execution_gas_price: 0.into(), - }; + let mantle_tx = MantleTx(vec![Op::Transfer(transfer_op)]); let transfer_proof = ZkKey::multi_sign(&[], &mantle_tx.hash().to_fr()) .expect("invalid transfer proof should still be constructible"); diff --git a/tests/src/cucumber/steps/manual_transactions/utils.rs b/tests/src/cucumber/steps/manual_transactions/utils.rs index e8b0ba996..6cf038d5c 100644 --- a/tests/src/cucumber/steps/manual_transactions/utils.rs +++ b/tests/src/cucumber/steps/manual_transactions/utils.rs @@ -15,7 +15,7 @@ use lb_core::{ TxHash, Utxo, gas::MainnetGasConstants, ops::Op, - tx::{MantleTxContext, MantleTxGasContext}, + tx::{GasPrices, MantleTxContext, MantleTxGasContext}, tx_builder::MantleTxBuilder, }, }; @@ -237,7 +237,8 @@ pub(crate) async fn prepare_user_wallet_built_transaction_submission( let mantle_tx = funded_builder.clone().build(); let tx_hash = mantle_tx.hash(); - let transfer_proofs = build_transfer_proofs(step, &mantle_tx.ops, &tx_hash, &transfer_signers)?; + let transfer_proofs = + build_transfer_proofs(step, mantle_tx.ops(), &tx_hash, &transfer_signers)?; Ok(PreparedUserWalletSubmission { wallet, @@ -302,6 +303,7 @@ pub(crate) async fn submit_prepared_user_wallet_transaction( newly_encumbered_fee, } = prepared; let sender_wallet_name = wallet.wallet_name.as_str(); + let gas_prices = funded_builder.get_gas_prices(); let mantle_tx = funded_builder.build(); extra_op_proofs.extend(transfer_proofs); @@ -337,7 +339,7 @@ pub(crate) async fn submit_prepared_user_wallet_transaction( world.record_tracked_spent_fee( sender_wallet_name, signed_tx - .total_gas_cost::() + .total_gas_cost::(gas_prices) .map_err(|e| StepError::LogicalError { message: format!("Step `{step}` error: failed to compute gas cost: {e}"), }) @@ -422,7 +424,7 @@ fn funding_inputs_from_transfers( step: &str, ) -> Result, StepError> { mantle_tx - .ops + .ops() .iter() .filter_map(|op| match op { Op::Transfer(transfer_op) => Some(transfer_op), @@ -796,11 +798,13 @@ fn log_wallet_balance( fn base_user_wallet_transaction(receivers: &[(ZkPublicKey, u64)]) -> MantleTxBuilder { let empty_context = MantleTxContext { - gas_context: MantleTxGasContext::new(HashMap::new()), + gas_context: MantleTxGasContext::new( + HashMap::new(), + GasPrices::new(0, DEFAULT_STORAGE_GAS_PRICE), + ), ..MantleTxContext::default() }; - let mut tx_builder = - MantleTxBuilder::new(empty_context).set_storage_gas_price(DEFAULT_STORAGE_GAS_PRICE.into()); + let mut tx_builder = MantleTxBuilder::new(empty_context); for (receiver_pk, value) in receivers { tx_builder = tx_builder.add_ledger_output(Note::new(*value, *receiver_pk)); diff --git a/tests/src/tests/mantle/sdp/ops.rs b/tests/src/tests/mantle/sdp/ops.rs index 2468fd390..9a3c08a05 100644 --- a/tests/src/tests/mantle/sdp/ops.rs +++ b/tests/src/tests/mantle/sdp/ops.rs @@ -8,7 +8,9 @@ use std::{ use lb_core::{ mantle::{ GenesisTx as _, MantleTx, NoteId, OpProof, SignedMantleTx, Transaction as _, Utxo, - genesis_tx::GENESIS_STORAGE_GAS_PRICE, ops::Op, tx::MantleTxGasContext, + genesis_tx::GENESIS_STORAGE_GAS_PRICE, + ops::Op, + tx::{GasPrices, MantleTxGasContext}, tx_builder::MantleTxBuilder, }, sdp::{Declaration, DeclarationMessage, Locator, ServiceType, WithdrawMessage}, @@ -464,15 +466,18 @@ async fn fund_sdp_transaction( let funding_public_key = funding_secret_key.to_public_key(); let funding_utxos = current_utxos_for_public_key(node, genesis_utxos, funding_public_key).await; - let empty_context = MantleTxGasContext::new(HashMap::new()); + let empty_context = MantleTxGasContext::new( + HashMap::new(), + GasPrices { + execution_base_gas_price: 0.into(), + storage_gas_price: GENESIS_STORAGE_GAS_PRICE, + }, + ); let tx_context = lb_core::mantle::tx::MantleTxContext { gas_context: empty_context, leader_reward_amount: 0, }; - let tx_builder = MantleTxBuilder::new(tx_context) - .push_op(extra_op) - .set_storage_gas_price(GENESIS_STORAGE_GAS_PRICE) - .set_execution_gas_price(0.into()); + let tx_builder = MantleTxBuilder::new(tx_context).push_op(extra_op); let funded_builder = fund_transfer_builder_from_utxos(funding_utxos, &tx_builder, funding_public_key) diff --git a/tests/testing_framework/src/workloads/inscription/workload.rs b/tests/testing_framework/src/workloads/inscription/workload.rs index 06a7423e6..dad2212a9 100644 --- a/tests/testing_framework/src/workloads/inscription/workload.rs +++ b/tests/testing_framework/src/workloads/inscription/workload.rs @@ -9,7 +9,6 @@ use std::{ use async_trait::async_trait; use lb_core::mantle::{ MantleTx, SignedMantleTx, Transaction as _, - genesis_tx::GENESIS_STORAGE_GAS_PRICE, ops::{ Op, OpProof, channel::{ChannelId, MsgId, inscribe::InscriptionOp}, @@ -389,11 +388,7 @@ fn build_inscription_transaction( }; let msg_id = op.id(); - let mantle_tx = MantleTx { - ops: vec![Op::ChannelInscribe(op)], - storage_gas_price: GENESIS_STORAGE_GAS_PRICE, - execution_gas_price: 0.into(), - }; + let mantle_tx = MantleTx(vec![Op::ChannelInscribe(op)]); let tx_hash = mantle_tx.hash(); let ed25519_signature = channel diff --git a/tests/testing_framework/src/workloads/transaction/workload.rs b/tests/testing_framework/src/workloads/transaction/workload.rs index 27b7f4cc9..1e9372138 100644 --- a/tests/testing_framework/src/workloads/transaction/workload.rs +++ b/tests/testing_framework/src/workloads/transaction/workload.rs @@ -11,9 +11,8 @@ use async_trait::async_trait; use lb_core::mantle::{ GasCalculator as _, GenesisTx as _, Note, OpProof, SignedMantleTx, Transaction as _, Utxo, gas::MainnetGasConstants, - genesis_tx::GENESIS_STORAGE_GAS_PRICE, ops::OpId as _, - tx::{MantleTxContext, MantleTxGasContext}, + tx::{GasPrices, MantleTxContext, MantleTxGasContext}, tx_builder::MantleTxBuilder, }; use lb_key_management_system_service::keys::{ZkKey, ZkPublicKey}; @@ -193,7 +192,7 @@ impl<'a, E: LbcScenarioEnv> Submission<'a, E> { } async fn execute(mut self) -> Result<(), DynError> { - let gas_context = MantleTxGasContext::new(HashMap::new()); + let gas_context = MantleTxGasContext::new(HashMap::new(), GasPrices::new(0, 0)); while let Some(input) = self.plan.pop_front() { submit_wallet_transaction(self.ctx, &input, gas_context.clone()).await?; if !self.interval.is_zero() { @@ -285,8 +284,6 @@ fn build_wallet_transaction( }; let provisional_tx = MantleTxBuilder::new(tx_context.clone()) - .set_execution_gas_price(0.into()) - .set_storage_gas_price(GENESIS_STORAGE_GAS_PRICE) .add_ledger_input(input.utxo) .add_ledger_output(Note::new(input.utxo.note.value, receiver)) .build(); @@ -302,8 +299,6 @@ fn build_wallet_transaction( })?; let tx = MantleTxBuilder::new(tx_context) - .set_execution_gas_price(0.into()) - .set_storage_gas_price(GENESIS_STORAGE_GAS_PRICE) .add_ledger_input(input.utxo) .add_ledger_output(Note::new(output_value, receiver)) .build(); diff --git a/tools/config/src/consensus.rs b/tools/config/src/consensus.rs index b5b5364b2..ce668740a 100644 --- a/tools/config/src/consensus.rs +++ b/tools/config/src/consensus.rs @@ -5,7 +5,7 @@ use lb_core::{ block::genesis::{GenesisBlock, GenesisBlockBuilder}, mantle::{ CryptarchiaParameter, MantleTx, Note, NoteId, OpProof, Utxo, - genesis_tx::{GENESIS_EXECUTION_GAS_PRICE, GENESIS_STORAGE_GAS_PRICE, GenesisTx}, + genesis_tx::GenesisTx, ops::{ Op, OpId as _, channel::{ChannelId, Ed25519PublicKey, MsgId, inscribe::InscriptionOp}, @@ -290,11 +290,7 @@ pub fn create_genesis_block_with_declarations( ops.push(Op::SDPDeclare(declaration)); } - let mantle_tx = MantleTx { - ops, - execution_gas_price: GENESIS_EXECUTION_GAS_PRICE, - storage_gas_price: GENESIS_STORAGE_GAS_PRICE, - }; + let mantle_tx = MantleTx(ops); let mantle_tx_hash = mantle_tx.hash(); let mut ops_proofs = vec![ diff --git a/tools/config/src/sdp.rs b/tools/config/src/sdp.rs index f995a945a..c5e5e7266 100644 --- a/tools/config/src/sdp.rs +++ b/tools/config/src/sdp.rs @@ -14,7 +14,7 @@ pub struct GeneralSdpConfig { pub fn create_sdp_configs(genesis_tx: &GenesisTx, count: usize) -> Vec { let mut configs = genesis_tx .mantle_tx() - .ops + .ops() .iter() .filter_map(|op| match op { Op::SDPDeclare(decl) => Some(GeneralSdpConfig { diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 87183e2c5..bad4b94ef 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -58,7 +58,7 @@ impl WalletBlock { let mut unlocked_notes = HashSet::new(); for auth_tx in block.transactions() { - for op in &auth_tx.mantle_tx().ops { + for op in auth_tx.mantle_tx().ops() { match op { Op::ChannelDeposit(deposit) => { spent_notes.extend(deposit.inputs.iter().copied()); @@ -578,10 +578,11 @@ mod tests { crypto::{Hash, ZkDigest as _}, mantle::{ Note, + channel::Channels, gas::MainnetGasConstants as Gas, ledger::{Inputs, Outputs}, ops::channel::{ChannelId, MsgId, inscribe::InscriptionOp}, - tx::MantleTxContext, + tx::{GasPrices, MantleTxContext, MantleTxGasContext}, }, sdp::{MinStake, ServiceParameters, ServiceType}, }; @@ -822,9 +823,13 @@ mod tests { // Lock `utxo1` deliberately to ensure that `fund_tx` excludes locked notes wallet_state.locked_notes = wallet_state.locked_notes.insert(utxo1.id()); - let tx_builder = MantleTxBuilder::new(ledger_state.tx_context()) - .set_execution_gas_price(1.into()) - .set_storage_gas_price(1.into()); + let tx_builder = MantleTxBuilder::new(MantleTxContext { + gas_context: MantleTxGasContext::from_channels( + &Channels::default(), + GasPrices::new(1, 1), + ), + leader_reward_amount: 0, + }); // Fund the transaction let funded_tx_builder = wallet_state @@ -832,22 +837,22 @@ mod tests { .unwrap(); assert_eq!( - 810, + 794, funded_tx_builder.gas_cost::().unwrap().into_inner() ); - assert_eq!(810, funded_tx_builder.net_balance()); + assert_eq!(794, funded_tx_builder.net_balance()); assert_eq!(0, funded_tx_builder.funding_delta::().unwrap()); let funded_tx = funded_tx_builder.build(); - if let Op::Transfer(transfer_op) = &funded_tx.ops[funded_tx.ops.len() - 1] { + if let Op::Transfer(transfer_op) = &funded_tx.ops()[funded_tx.ops().len() - 1] { // ensure alices utxo was used to pay the fee assert_eq!(transfer_op.inputs, Inputs::new(vec![utxo2.id()])); // ensure change was returned to alice assert_eq!( transfer_op.outputs, Outputs::new(vec![Note { - value: 4190, + value: 4206, pk: alice, }]) ); @@ -869,11 +874,17 @@ mod tests { &ledger_config(), ); + let builder_context = MantleTxContext { + gas_context: MantleTxGasContext::from_channels( + ledger_state.mantle_ledger().channels(), + GasPrices::new(1, 1), + ), + leader_reward_amount: ledger_state.mantle_ledger().leader_reward_amount(), + }; + let wallet_state = WalletState::from_ledger(&HashMap::from_iter([(alice, 1)]), &ledger_state); - let mut tx_builder = MantleTxBuilder::new(ledger_state.tx_context()) - .set_execution_gas_price(1.into()) - .set_storage_gas_price(1.into()); + let mut tx_builder = MantleTxBuilder::new(builder_context); // Add a costly inscription let signing_key = Ed25519Key::from_bytes(&[1; 32]); @@ -903,9 +914,7 @@ mod tests { let wallet_state = WalletState::from_ledger(&HashMap::from_iter([(alice, 1)]), &ledger_state); - let tx_builder = MantleTxBuilder::new(ledger_state.tx_context()) - .set_execution_gas_price(1.into()) - .set_storage_gas_price(1.into()); + let tx_builder = MantleTxBuilder::new(ledger_state.tx_context()); // Fund the transaction let fund_attempt = wallet_state.fund_tx::(&tx_builder, alice, [alice]); @@ -927,9 +936,7 @@ mod tests { // Lock `utxo` deliberately to ensure that `fund_tx` excludes locked notes wallet_state.locked_notes = wallet_state.locked_notes.insert(utxo.id()); - let tx_builder = MantleTxBuilder::new(ledger_state.tx_context()) - .set_execution_gas_price(1.into()) - .set_storage_gas_price(1.into()); + let tx_builder = MantleTxBuilder::new(ledger_state.tx_context()); // Fund the transaction let fund_attempt = wallet_state.fund_tx::(&tx_builder, alice, [alice]); @@ -952,9 +959,7 @@ mod tests { let wallet_state = WalletState::from_ledger(&HashMap::from_iter([(alice, 1), (bob, 2)]), &ledger_state); - let tx_builder = MantleTxBuilder::new(ledger_state.tx_context()) - .set_execution_gas_price(1.into()) - .set_storage_gas_price(1.into()); + let tx_builder = MantleTxBuilder::new(ledger_state.tx_context()); // Attempt to fund the transaction with Alice's notes. let fund_attempt = wallet_state.fund_tx::(&tx_builder, alice, [alice]); @@ -974,13 +979,17 @@ mod tests { fn test_fund_tx_unfundable_region() { let alice = pk(1); - let tx_builder = MantleTxBuilder::new(MantleTxContext::default()) - .set_execution_gas_price(1.into()) - .set_storage_gas_price(1.into()); + let tx_builder = MantleTxBuilder::new(MantleTxContext { + gas_context: MantleTxGasContext::from_channels( + &Channels::default(), + GasPrices::new(1, 1), + ), + leader_reward_amount: 0, + }); // Determine gas cost without change note assert_eq!( - 770, + 754, tx_builder .clone() .add_ledger_input(Utxo::new(tx_hash(0), 0, Note::new(0, pk(0)))) @@ -994,7 +1003,7 @@ mod tests { let wallet_state = WalletState::from_ledger( &HashMap::from_iter([(alice, 1)]), &LedgerState::from_utxos( - [Utxo::new(tx_hash(0), 0, Note::new(770, alice))], + [Utxo::new(tx_hash(0), 0, Note::new(754, alice))], &ledger_config(), ), ); @@ -1006,7 +1015,7 @@ mod tests { // verify that no change output was used. if let Op::Transfer(transfer_op) = - &funded_tx_wo_change.ops[funded_tx_wo_change.ops.len() - 1] + &funded_tx_wo_change.ops()[funded_tx_wo_change.ops().len() - 1] { assert_eq!(transfer_op.outputs, Outputs::new(vec![])); } else { @@ -1015,7 +1024,7 @@ mod tests { // Determine gas cost with change note assert_eq!( - 810, + 794, tx_builder .clone() .add_ledger_input(Utxo::new(tx_hash(0), 0, Note::new(0, pk(0)))) @@ -1025,7 +1034,7 @@ mod tests { .into_inner() ); - for value in 771..=810 { + for value in 755..=794 { // this region of note values will fail to fund the tx. // We can fund the tx if the note value is exactly the gas cost without change // note @@ -1049,7 +1058,7 @@ mod tests { let wallet_state = WalletState::from_ledger( &HashMap::from_iter([(alice, 1)]), &LedgerState::from_utxos( - [Utxo::new(tx_hash(0), 0, Note::new(811, alice))], + [Utxo::new(tx_hash(0), 0, Note::new(795, alice))], &ledger_config(), ), ); @@ -1061,7 +1070,7 @@ mod tests { // verify that indeed a change output was used. if let Op::Transfer(transfer_op) = - &funded_tx_wo_change.ops[funded_tx_wo_change.ops.len() - 1] + &funded_tx_wo_change.ops()[funded_tx_wo_change.ops().len() - 1] { assert_eq!(transfer_op.outputs, Outputs::new(vec![Note::new(1, alice)])); } else { diff --git a/zone-sdk/src/adapter.rs b/zone-sdk/src/adapter.rs index f0802cca4..d61dce6d2 100644 --- a/zone-sdk/src/adapter.rs +++ b/zone-sdk/src/adapter.rs @@ -101,7 +101,7 @@ impl Node for NodeHttpClient { Ok(Box::pin(stream::iter( transactions .into_iter() - .flat_map(|tx| tx.mantle_tx.ops) + .flat_map(|tx| tx.mantle_tx.0) .filter_map(move |op| op_to_zone_message(&op, channel_id)), ))) } @@ -127,7 +127,7 @@ impl Node for NodeHttpClient { block .transactions .into_iter() - .flat_map(|tx| tx.mantle_tx.ops) + .flat_map(|tx| tx.mantle_tx.0) .filter_map(move |op| op_to_zone_message(&op, channel_id)) .map(move |msg| (msg, slot)) }, diff --git a/zone-sdk/src/sequencer.rs b/zone-sdk/src/sequencer.rs index 47d9fe628..e8aeb4634 100644 --- a/zone-sdk/src/sequencer.rs +++ b/zone-sdk/src/sequencer.rs @@ -459,7 +459,7 @@ where // Filter by `channel_id` — a checkpoint can in principle carry // txs for other channels if the caller reused it. let mut is_inscription = false; - for op in &tx.mantle_tx.ops { + for op in tx.mantle_tx.ops() { if let Op::ChannelInscribe(inscribe) = op && inscribe.channel_id == channel_id { @@ -1315,7 +1315,7 @@ fn enqueue_resubmit( for (id, tx) in &pending { let payloads: Vec = tx .mantle_tx - .ops + .ops() .iter() .filter_map(|op| { if let Op::ChannelInscribe(ins) = op { @@ -1355,7 +1355,7 @@ fn extract_inscriptions(txs: &[SignedMantleTx], channel_id: ChannelId) -> Vec = txs .iter() .flat_map(|tx| { - tx.mantle_tx.ops.iter().filter_map(|op| { + tx.mantle_tx.ops().iter().filter_map(|op| { if let Op::ChannelInscribe(inscribe) = op && inscribe.channel_id == channel_id { @@ -1398,7 +1398,7 @@ fn extract_inscriptions(txs: &[SignedMantleTx], channel_id: ChannelId) -> Vec bool { - tx.mantle_tx.ops.iter().any(|op| match op { + tx.mantle_tx.ops().iter().any(|op| match op { Op::ChannelInscribe(inscribe) => inscribe.channel_id == channel_id, Op::ChannelSetKeys(set_keys) => set_keys.channel == channel_id, _ => false, @@ -1422,11 +1422,7 @@ fn create_inscribe_tx( let msg_id = inscribe_op.id(); // TODO: set realistic gas prices and fund tx - let inscribe_tx = MantleTx { - ops: vec![Op::ChannelInscribe(inscribe_op)], - storage_gas_price: 0.into(), - execution_gas_price: 0.into(), - }; + let inscribe_tx = MantleTx(vec![Op::ChannelInscribe(inscribe_op)]); let tx_hash = inscribe_tx.hash(); let signature = sign_tx(tx_hash, signing_key); @@ -1449,12 +1445,8 @@ fn create_set_keys_tx( keys, }; - // TODO: set realistic gas prices and fund tx - let set_keys_tx = MantleTx { - ops: vec![Op::ChannelSetKeys(set_keys_op)], - storage_gas_price: 0.into(), - execution_gas_price: 0.into(), - }; + // TODO: fund tx + let set_keys_tx = MantleTx(vec![Op::ChannelSetKeys(set_keys_op)]); let tx_hash = set_keys_tx.hash(); let signature = sign_tx(tx_hash, signing_key); @@ -1481,12 +1473,8 @@ fn prepare_tx( let msg_id = inscription_op.id(); ops.push(Op::ChannelInscribe(inscription_op)); - // TODO: set realistic gas prices and fund tx - let tx = MantleTx { - ops, - storage_gas_price: 0.into(), - execution_gas_price: 0.into(), - }; + // TODO: fund tx + let tx = MantleTx(ops); let inscription_sig = sign_tx(tx.hash(), signing_key); @@ -1562,9 +1550,9 @@ mod tests { _ = sequencer.next_event() => {} } }; - assert_eq!(tx.ops.len(), 2); - assert_eq!(&tx.ops[0], &Op::ChannelDeposit(deposit_op)); - assert!(matches!(&tx.ops[1], &Op::ChannelInscribe(_))); + assert_eq!(tx.ops().len(), 2); + assert_eq!(&tx.ops()[0], &Op::ChannelDeposit(deposit_op)); + assert!(matches!(&tx.ops()[1], &Op::ChannelInscribe(_))); // Sign the `MantleTx` let signed_tx = SignedMantleTx::new( diff --git a/zone-sdk/src/state.rs b/zone-sdk/src/state.rs index e95a9268b..a931dd8ef 100644 --- a/zone-sdk/src/state.rs +++ b/zone-sdk/src/state.rs @@ -583,7 +583,10 @@ impl TxState { #[cfg(test)] mod tests { - use lb_core::mantle::{MantleTx, Transaction as _}; + use lb_core::mantle::{ + MantleTx, Op::ChannelInscribe, Transaction as _, ops::channel::inscribe::InscriptionOp, + }; + use lb_key_management_system_service::keys::Ed25519PublicKey; use super::*; @@ -594,11 +597,12 @@ mod tests { } fn make_dummy_tx(data: u8) -> SignedMantleTx { - let mantle_tx = MantleTx { - ops: vec![], - storage_gas_price: 0.into(), - execution_gas_price: u64::from(data).into(), - }; + let mantle_tx = MantleTx(vec![ChannelInscribe(InscriptionOp { + channel_id: [0u8; 32].into(), + inscription: vec![data], + parent: [0u8; 32].into(), + signer: Ed25519PublicKey::from_bytes(&[0u8; 32]).unwrap(), + })]); SignedMantleTx { ops_proofs: vec![], mantle_tx,