refactor(txhash): Simplify Mantle Transaction (2/2) (#2670)

This commit is contained in:
thomaslavaur
2026-05-06 10:07:05 +02:00
committed by GitHub
parent 7de7acf510
commit 8b3cf73ff7
35 changed files with 579 additions and 752 deletions
+7 -10
View File
@@ -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`.
+4 -13
View File
@@ -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<WithAll> {
.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(_)));
+1 -7
View File
@@ -323,13 +323,7 @@ mod tests {
}
fn create_tx(count: usize) -> Vec<MantleTx> {
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]
+2 -13
View File
@@ -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<ChannelId, ChannelState>,
}
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));
+70 -157
View File
@@ -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<u8> {
/// Encode top-level transactions
#[must_use]
pub fn encode_mantle_tx(tx: &MantleTx) -> Vec<u8> {
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<u8> {
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,
+13 -2
View File
@@ -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<Value> 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<Value> for GasPrice {
fn from(value: Value) -> Self {
Self(value)
+5 -56
View File
@@ -66,17 +66,9 @@ impl GenesisTx {
pub fn from_tx(signed_mantle_tx: SignedMantleTx) -> Result<Self, Error> {
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<Item = (&SDPDeclareOp, &OpProof)> {
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)
+38 -12
View File
@@ -49,16 +49,29 @@ pub trait Transaction {
}
pub trait AuthenticatedMantleTx: Transaction<Hash = TxHash> + GasCalculator + StorageSize {
type Context;
/// Returns the underlying `MantleTx` that this transaction represents.
fn mantle_tx(&self) -> &MantleTx;
fn ops_with_proof(&self) -> impl Iterator<Item = (&Op, &OpProof)>;
// Gas Cost functions with context already handled
fn total_gas_cost<Constants: GasConstants>(&self) -> Result<GasCost, GasOverflow>;
fn storage_gas_cost(&self) -> Result<GasCost, GasOverflow>;
fn execution_gas_consumption<Constants: GasConstants>(&self) -> Result<Gas, GasOverflow>;
fn storage_gas_consumption(&self) -> Result<Gas, GasOverflow>;
fn total_gas_cost<Constants: GasConstants>(
&self,
context: <Self as AuthenticatedMantleTx>::Context,
) -> Result<GasCost, GasOverflow>;
fn storage_gas_cost(
&self,
context: <Self as AuthenticatedMantleTx>::Context,
) -> Result<GasCost, GasOverflow>;
fn execution_gas_consumption<Constants: GasConstants>(
&self,
context: <Self as AuthenticatedMantleTx>::Context,
) -> Result<Gas, GasOverflow>;
fn storage_gas_consumption(
&self,
context: <Self as AuthenticatedMantleTx>::Context,
) -> Result<Gas, GasOverflow>;
fn verify_ops_proofs_with_helper(
&self,
@@ -92,6 +105,7 @@ impl<T: StorageSize> StorageSize for &T {
}
impl<T: AuthenticatedMantleTx> AuthenticatedMantleTx for &T {
type Context = <T as AuthenticatedMantleTx>::Context;
fn mantle_tx(&self) -> &MantleTx {
T::mantle_tx(self)
}
@@ -100,20 +114,32 @@ impl<T: AuthenticatedMantleTx> AuthenticatedMantleTx for &T {
T::ops_with_proof(self)
}
fn total_gas_cost<Constants: GasConstants>(&self) -> Result<GasCost, GasOverflow> {
<T as AuthenticatedMantleTx>::total_gas_cost::<Constants>(self)
fn total_gas_cost<Constants: GasConstants>(
&self,
context: <Self as AuthenticatedMantleTx>::Context,
) -> Result<GasCost, GasOverflow> {
<T as AuthenticatedMantleTx>::total_gas_cost::<Constants>(self, context)
}
fn storage_gas_cost(&self) -> Result<GasCost, GasOverflow> {
<T as AuthenticatedMantleTx>::storage_gas_cost(self)
fn storage_gas_cost(
&self,
context: <Self as AuthenticatedMantleTx>::Context,
) -> Result<GasCost, GasOverflow> {
<T as AuthenticatedMantleTx>::storage_gas_cost(self, context)
}
fn execution_gas_consumption<Constants: GasConstants>(&self) -> Result<Gas, GasOverflow> {
<T as AuthenticatedMantleTx>::execution_gas_consumption::<Constants>(self)
fn execution_gas_consumption<Constants: GasConstants>(
&self,
context: <Self as AuthenticatedMantleTx>::Context,
) -> Result<Gas, GasOverflow> {
<T as AuthenticatedMantleTx>::execution_gas_consumption::<Constants>(self, context)
}
fn storage_gas_consumption(&self) -> Result<Gas, GasOverflow> {
<T as AuthenticatedMantleTx>::storage_gas_consumption(self)
fn storage_gas_consumption(
&self,
context: <Self as AuthenticatedMantleTx>::Context,
) -> Result<Gas, GasOverflow> {
<T as AuthenticatedMantleTx>::storage_gas_consumption(self, context)
}
fn verify_ops_proofs_with_helper(
+101 -58
View File
@@ -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<Op>,
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<ChannelId, ChannelKeyIndex>,
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<ChannelId, ChannelKeyIndex>) -> Self {
pub const fn new(
withdraw_thresholds: HashMap<ChannelId, ChannelKeyIndex>,
gas_prices: GasPrices,
) -> Self {
Self {
withdraw_thresholds,
gas_prices,
}
}
@@ -107,44 +137,41 @@ impl MantleTxGasContext {
pub fn withdraw_threshold(&self, channel_id: &ChannelId) -> Option<ChannelKeyIndex> {
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<Op>,
pub execution_gas_price: GasPrice,
pub storage_gas_price: GasPrice,
}
pub struct MantleTx(pub Vec<Op>);
impl From<MantleTxDeSerImpl> 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<MantleTx> 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<T: IntoIterator<Item = Op>> From<T> for MantleTx {
fn from(ops: T) -> Self {
Self(ops.into_iter().collect())
}
}
@@ -187,7 +214,8 @@ impl GasCalculator for MantleTx {
context: &Self::Context,
) -> Result<GasCost, GasOverflow> {
let execution_gas = self.execution_gas_consumption::<Constants>(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, GasOverflow> {
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<Gas, GasOverflow> {
self.ops
self.ops()
.iter()
.map(Op::execution_gas::<Constants>)
.try_fold(Gas::from(0), Gas::checked_add)
@@ -224,13 +252,18 @@ impl MantleTx {
#[must_use]
pub fn transfers(&self) -> Vec<TransferOp> {
let mut transfers: Vec<TransferOp> = 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<Op> {
&self.0
}
}
static MANTLE_TXHASH_V1_BYTES: LazyLock<Vec<u8>> = 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<Item = (&Op, &OpProof)> {
self.mantle_tx.ops.iter().zip(self.ops_proofs.iter())
self.mantle_tx.ops().iter().zip(self.ops_proofs.iter())
}
fn total_gas_cost<Constants: GasConstants>(&self) -> Result<GasCost, GasOverflow> {
GasCalculator::total_gas_cost::<Constants>(&self, &())
fn total_gas_cost<Constants: GasConstants>(
&self,
context: <Self as AuthenticatedMantleTx>::Context,
) -> Result<GasCost, GasOverflow> {
GasCalculator::total_gas_cost::<Constants>(&self, &context)
}
fn storage_gas_cost(&self) -> Result<GasCost, GasOverflow> {
GasCalculator::storage_gas_cost(&self, &())
fn storage_gas_cost(
&self,
context: <Self as AuthenticatedMantleTx>::Context,
) -> Result<GasCost, GasOverflow> {
GasCalculator::storage_gas_cost(&self, &context)
}
fn execution_gas_consumption<Constants: GasConstants>(&self) -> Result<Gas, GasOverflow> {
GasCalculator::execution_gas_consumption::<Constants>(&self, &())
fn execution_gas_consumption<Constants: GasConstants>(
&self,
context: <Self as AuthenticatedMantleTx>::Context,
) -> Result<Gas, GasOverflow> {
GasCalculator::execution_gas_consumption::<Constants>(&self, &context)
}
fn storage_gas_consumption(&self) -> Result<Gas, GasOverflow> {
GasCalculator::storage_gas_consumption(&self, &())
fn storage_gas_consumption(
&self,
context: <Self as AuthenticatedMantleTx>::Context,
) -> Result<Gas, GasOverflow> {
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<Constants: GasConstants>(
&self,
@@ -556,7 +603,7 @@ impl GasCalculator for SignedMantleTx {
) -> Result<GasCost, GasOverflow> {
let execution_gas = GasCalculator::execution_gas_consumption::<Constants>(&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<GasCost, GasOverflow> {
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<Constants: GasConstants>(
@@ -572,7 +619,7 @@ impl GasCalculator for SignedMantleTx {
_context: &Self::Context,
) -> Result<Gas, GasOverflow> {
self.mantle_tx
.ops
.ops()
.iter()
.map(Op::execution_gas::<Constants>)
.try_fold(Gas::from(0), Gas::checked_add)
@@ -617,11 +664,7 @@ mod tests {
};
fn create_test_mantle_tx(ops: Vec<Op>) -> MantleTx {
MantleTx {
ops,
execution_gas_price: 1.into(),
storage_gas_price: 1.into(),
}
ops.into()
}
fn create_test_inscribe_op(signing_key: &Ed25519Key) -> InscriptionOp {
+14 -25
View File
@@ -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<Item = Op>) -> 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<G: GasConstants>(
self,
change_pk: ZkPublicKey,
@@ -174,7 +163,7 @@ impl MantleTxBuilder {
/// build.
pub fn consumed_or_locked_notes(&self) -> impl Iterator<Item = NoteId> {
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 {
+19 -9
View File
@@ -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::<Vec<_>>();
let inputs = inputs.iter().map(|(_, utxo)| utxo.id()).collect::<Vec<_>>();
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::<MainnetGasConstants>(&tx);
let _fees =
AuthenticatedMantleTx::total_gas_cost::<MainnetGasConstants>(&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(&[(&note_sk, &input_utxo)], vec![output_note1, output_note2]);
let _fees = AuthenticatedMantleTx::total_gas_cost::<MainnetGasConstants>(&tx);
let _fees =
AuthenticatedMantleTx::total_gas_cost::<MainnetGasConstants>(&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::<MainnetGasConstants>(&tx);
let _fees =
AuthenticatedMantleTx::total_gas_cost::<MainnetGasConstants>(&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::<MainnetGasConstants>(&tx);
let _fees =
AuthenticatedMantleTx::total_gas_cost::<MainnetGasConstants>(&tx, GasPrices::new(0, 0));
let result = ledger_state.try_apply_transfer::<(), MainnetGasConstants>(
&locked_notes,
&transfer_op,
+90 -137
View File
@@ -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<Item = impl AuthenticatedMantleTx>,
txs: impl Iterator<Item = impl AuthenticatedMantleTx<Context = GasPrices>>,
) -> Result<(Id, LedgerState), LedgerError<Id>>
where
LeaderProof: leader_proof::LeaderProof,
@@ -218,7 +218,7 @@ impl LedgerState {
self,
slot: Slot,
proof: &LeaderProof,
txs: impl Iterator<Item = impl AuthenticatedMantleTx>,
txs: impl Iterator<Item = impl AuthenticatedMantleTx<Context = GasPrices>>,
config: &Config,
) -> Result<Self, LedgerError<Id>>
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: <https://www.notion.so/nomos-tech/v1-1-Block-Rewards-Specification-326261aa09df80579edddaf092057b3d>
@@ -337,7 +345,7 @@ impl LedgerState {
pub fn try_apply_contents<Id, Constants: GasConstants>(
mut self,
config: &Config,
txs: impl Iterator<Item = impl AuthenticatedMantleTx>,
txs: impl Iterator<Item = impl AuthenticatedMantleTx<Context = GasPrices>>,
) -> Result<Self, LedgerError<Id>> {
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::<Constants>(&tx)?;
let total_gas_cost =
AuthenticatedMantleTx::total_gas_cost::<Constants>(&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::<Constants>(&tx)?,
*self.cryptarchia_ledger.execution_base_fee(),
AuthenticatedMantleTx::execution_gas_consumption::<Constants>(
&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::<Constants>(&tx)?)?;
total_block_execution_gas = total_block_execution_gas.checked_add(
AuthenticatedMantleTx::execution_gas_consumption::<Constants>(&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<NoteId>,
outputs: Vec<Note>,
sks: &[ZkKey],
execution_price: GasPrice,
storage_price: GasPrice,
) -> SignedMantleTx {
fn create_tx(inputs: Vec<NoteId>, outputs: Vec<Note>, 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<Op>, 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::<MainnetGasConstants>(&tx).unwrap();
let fees =
AuthenticatedMantleTx::total_gas_cost::<MainnetGasConstants>(&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::<HeaderId, MainnetGasConstants>(&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::<MainnetGasConstants>(&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::<HeaderId, MainnetGasConstants>(&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::<MainnetGasConstants>(&tx).unwrap();
let fees = AuthenticatedMantleTx::total_gas_cost::<MainnetGasConstants>(
&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::<HeaderId, MainnetGasConstants>(&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::<MainnetGasConstants>(&tx).unwrap();
let fees = AuthenticatedMantleTx::total_gas_cost::<MainnetGasConstants>(
&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::<HeaderId, MainnetGasConstants>(&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::<MainnetGasConstants>(&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::<HeaderId, MainnetGasConstants>(&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
@@ -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 = "<MantleTx as lb_core::mantle::Transaction>::hash")]
hash: TxHash,
#[serde(getter = "MantleTx::ops")]
ops: Vec<Op>,
execution_gas_price: GasPrice,
storage_gas_price: GasPrice,
}
#[derive(Serialize)]
@@ -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'
+29 -31
View File
@@ -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'
+31 -33
View File
@@ -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:
+4 -4
View File
@@ -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<HeaderId>, ReorgedBlocks<HeaderId>), Error>
where
Tx: AuthenticatedMantleTx,
Tx: AuthenticatedMantleTx<Context = GasPrices>,
{
let header = block.header();
let id = header.id();
@@ -512,7 +512,7 @@ impl<Tx, Storage, TimeBackend, RuntimeServiceId> ServiceCore<RuntimeServiceId>
for CryptarchiaConsensus<Tx, Storage, TimeBackend, RuntimeServiceId>
where
Tx: Transaction<Hash = TxHash>
+ AuthenticatedMantleTx
+ AuthenticatedMantleTx<Context = GasPrices>
+ Debug
+ Clone
+ Eq
@@ -720,7 +720,7 @@ impl<Tx, Storage, TimeBackend, RuntimeServiceId>
CryptarchiaConsensus<Tx, Storage, TimeBackend, RuntimeServiceId>
where
Tx: Transaction<Hash = TxHash>
+ AuthenticatedMantleTx
+ AuthenticatedMantleTx<Context = GasPrices>
+ Debug
+ Clone
+ Eq
+3 -1
View File
@@ -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,
};
+1 -1
View File
@@ -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?
@@ -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 {
@@ -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"),
};
+15 -11
View File
@@ -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<MsgId>,
) -> 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]
+6 -30
View File
@@ -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());
@@ -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()
@@ -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");
@@ -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::<MainnetGasConstants>()
.total_gas_cost::<MainnetGasConstants>(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<Vec<Utxo>, 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));
+11 -6
View File
@@ -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)
@@ -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
@@ -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();
+2 -6
View File
@@ -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![
+1 -1
View File
@@ -14,7 +14,7 @@ pub struct GeneralSdpConfig {
pub fn create_sdp_configs(genesis_tx: &GenesisTx, count: usize) -> Vec<GeneralSdpConfig> {
let mut configs = genesis_tx
.mantle_tx()
.ops
.ops()
.iter()
.filter_map(|op| match op {
Op::SDPDeclare(decl) => Some(GeneralSdpConfig {
+40 -31
View File
@@ -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::<Gas>().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::<Gas>().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::<Gas>(&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::<Gas>(&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::<Gas>(&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 {
+2 -2
View File
@@ -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))
},
+12 -24
View File
@@ -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<Node>(
for (id, tx) in &pending {
let payloads: Vec<String> = 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<In
let items: Vec<InscriptionInfo> = 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<In
}
fn matches_channel(tx: &SignedMantleTx, channel_id: ChannelId) -> 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(
+10 -6
View File
@@ -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,