refactor(ledger): execute typed signed operations

- initialize ledger state from typed genesis operations
- consume SignedOps during transaction verification
- execute only operations in the Verified state
- propagate signed operations through Mantle and SDP state transitions
- expose the new transaction types through the Mantle API
This commit is contained in:
Alejandro Cabeza Romero
2026-08-21 17:36:42 +02:00
parent 987f808823
commit a917db2687
7 changed files with 512 additions and 309 deletions
+53 -26
View File
@@ -9,12 +9,17 @@ use crate::{
mantle::{
NoteId,
channel_notes::{self, ChannelNotes},
ledger::{self, ExecutableOperation as _},
ops::channel::{
ChannelId, ChannelKeyIndex, MsgId,
config::Keys,
inscribe::{InscriptionExecutionContext, InscriptionOp},
ledger,
ledger::verification_mode::GenesisMode,
ops::{
SignedOperation,
channel::{
ChannelId, ChannelKeyIndex, MsgId,
config::Keys,
inscribe::{InscriptionExecutionContext, InscriptionOp},
},
},
transactions::states::Verified,
},
};
@@ -133,11 +138,15 @@ impl Default for Channels {
}
impl Channels {
pub fn from_genesis(op: &InscriptionOp) -> Result<(Self, Vec<TxEvent>), Error> {
let (context, events) = op.execute(InscriptionExecutionContext {
channels: Self::default(),
block_slot: Slot::default(),
})?;
pub fn from_genesis(
signed_operation: SignedOperation<InscriptionOp, Verified, GenesisMode>,
) -> Result<(Self, Vec<TxEvent>), Error> {
let (context, events) = signed_operation
.execute(InscriptionExecutionContext {
channels: Self::default(),
block_slot: Slot::default(),
})
.map_err(|(_signed_operation, error)| error)?;
Ok((context.channels, events))
}
@@ -233,8 +242,8 @@ impl ChannelState {
#[cfg(test)]
mod tests {
use ark_ff::AdditiveGroup as _;
use lb_groth16::Fr;
use lb_key_management_system_keys::keys::{Ed25519Key, ZkKey};
use lb_groth16::{CompressedGroth16Proof, Fr};
use lb_key_management_system_keys::keys::{Ed25519Key, ZkKey, ZkSignature};
use lb_utils::blake_rng::RngCore as _;
use rand::thread_rng;
@@ -243,7 +252,7 @@ mod tests {
events::{DepositNote, TxEventPayload},
mantle::{
Note, Utxo, Value,
ledger::Utxos,
ledger::{Utxos, verification_mode::StandardMode},
ops::{
OpId as _,
channel::{
@@ -252,8 +261,9 @@ mod tests {
withdraw::{ChannelWithdrawOp, WithdrawExecutionContext},
},
},
transactions::{GasPrices, mantle_tx::MantleTxGasContext},
transactions::{GasPrices, tx_list::ops::OpsGasContext},
},
proofs::channel_multi_sig_proof::{ChannelMultiSigProof, IndexedSignatures},
};
fn test_public_key(seed: u8) -> PublicKey {
@@ -356,7 +366,7 @@ mod tests {
channel_notes: ChannelNotes::new(),
};
let gas_context = MantleTxGasContext::from_channels(&channels, GasPrices::new(0, 0));
let gas_context = OpsGasContext::from_channels(&channels, GasPrices::new(0, 0));
assert_eq!(gas_context.transfer_threshold(&first_id), Some(1));
assert_eq!(gas_context.transfer_threshold(&second_id), Some(2));
@@ -376,10 +386,14 @@ mod tests {
inputs: [note_id].into(),
metadata: Metadata::empty(),
};
let empty_proof = ZkSignature::new(CompressedGroth16Proof::from_bytes(&[0u8; 128]));
let signed_deposit: SignedOperation<_, _, StandardMode> =
SignedOperation::new(deposit_op, empty_proof).into_state_trusted();
let operation = signed_deposit.operation().clone();
let utxo_tree = utxo_tree(vec![utxo]);
let (updated, events) = deposit_op
let (updated, events) = signed_deposit
.execute(DepositExecutionContext {
channels,
utxos: utxo_tree,
@@ -389,7 +403,7 @@ mod tests {
// The deposited note is consumed and re-created as a channel note
// under a new NoteId.
let deposited = Utxo::new(deposit_op.op_id(), 0, utxo.note).id();
let deposited = Utxo::new(operation.op_id(), 0, utxo.note).id();
assert!(!updated.utxos.contains(&note_id));
assert!(!updated.channels.is_channel_note(&note_id));
assert!(updated.utxos.contains(&deposited));
@@ -419,10 +433,10 @@ mod tests {
panic!("events should include deposit event")
};
assert_eq!(*tx_hash, [0; 32].into());
assert_eq!(*op_id, deposit_op.op_id());
assert_eq!(*event_channel_id, deposit_op.channel_id);
assert_eq!(*op_id, operation.op_id());
assert_eq!(*event_channel_id, operation.channel_id);
assert_eq!(*amount, utxo.note.value);
assert_eq!(*metadata, deposit_op.metadata);
assert_eq!(*metadata, operation.metadata);
assert_eq!(
notes.clone().into_inner(),
vec![DepositNote {
@@ -445,8 +459,12 @@ mod tests {
inputs: [first.id(), second.id()].into(),
metadata: Metadata::empty(),
};
let empty_proof = ZkSignature::new(CompressedGroth16Proof::from_bytes(&[0u8; 128]));
let signed_deposit: SignedOperation<_, _, StandardMode> =
SignedOperation::new(deposit_op, empty_proof).into_state_trusted();
let op_id = signed_deposit.operation().op_id();
let (updated, _) = deposit_op
let (updated, _) = signed_deposit
.execute(DepositExecutionContext {
channels: Channels::new(),
utxos: utxo_tree(vec![first, second]),
@@ -456,8 +474,8 @@ mod tests {
// Each input is re-created at its own output index, so the two notes
// get distinct identifiers even though they share an OpId.
let first_deposited = Utxo::new(deposit_op.op_id(), 0, first.note).id();
let second_deposited = Utxo::new(deposit_op.op_id(), 1, second.note).id();
let first_deposited = Utxo::new(op_id, 0, first.note).id();
let second_deposited = Utxo::new(op_id, 1, second.note).id();
assert_ne!(first_deposited, second_deposited);
assert!(!updated.utxos.contains(&first.id()));
@@ -485,8 +503,11 @@ mod tests {
channel_id,
inputs: [note_id].into(),
};
let empty_proof = ChannelMultiSigProof::new(IndexedSignatures::try_from(vec![]).unwrap());
let signed_withdraw: SignedOperation<_, _, StandardMode> =
SignedOperation::new(withdraw_op, empty_proof).into_state_trusted();
let (updated, events) = withdraw_op
let (updated, events) = signed_withdraw
.execute(WithdrawExecutionContext {
channels,
tx_hash: [1; 32].into(),
@@ -508,15 +529,21 @@ mod tests {
channel_id,
inputs: [note_id].into(),
};
let empty_proof = ChannelMultiSigProof::new(IndexedSignatures::try_from(vec![]).unwrap());
let signed_withdraw: SignedOperation<_, _, StandardMode> =
SignedOperation::new(withdraw_op, empty_proof).into_state_trusted();
let result = withdraw_op.execute(WithdrawExecutionContext {
let result = signed_withdraw.execute(WithdrawExecutionContext {
channels,
tx_hash: [0; 32].into(),
});
assert!(matches!(
result,
Err(Error::ChannelNotes(channel_notes::Error::NotInChannel(_)))
Err((
_,
Error::ChannelNotes(channel_notes::Error::NotInChannel(_))
))
));
}
+4 -7
View File
@@ -1,4 +1,6 @@
pub mod channel;
mod channel_notes;
mod fixtures;
pub mod gas;
pub mod ledger;
pub mod mock;
@@ -6,14 +8,9 @@ pub mod ops;
pub mod traits;
pub mod transactions;
mod channel_notes;
mod fixtures;
pub use gas::{GasProfile, TxGasCalculator};
pub use ledger::{Note, NoteId, Utxo, Value};
pub use ops::{Op, OpProof};
pub use transactions::{
CryptarchiaParameter, GenesisTime, MantleTransaction, hash::TxHash, mantle_tx::RawMantleTx,
};
pub use ops::{Op, OpProof, OpProofRef, OpRef};
pub use transactions::{CryptarchiaParameter, GenesisTime, SignedOps, hash::TxHash};
pub use crate::mantle::transactions::VerificationError;
+68 -39
View File
@@ -12,10 +12,9 @@ use lb_core::{
mantle::{
NoteId, Utxo, Value,
gas::{Gas, GasCost, GasOverflow, GasPrice, GasProfile},
ledger::ExecutableOperation as _,
ops::{pow::PowTarget, transfer::TransferOp},
traits::GenesisTx,
transactions::{GENESIS_EXECUTION_GAS_PRICE, GENESIS_STORAGE_GAS_PRICE},
ledger::verification_mode::{GenesisMode, StandardMode},
ops::{SignedOperation, pow::PowTarget, transfer::TransferOp},
transactions::{GENESIS_EXECUTION_GAS_PRICE, GENESIS_STORAGE_GAS_PRICE, states::Verified},
},
proofs::leader_proof::{self, LeaderPublic},
sdp::Declarations,
@@ -556,17 +555,18 @@ impl LedgerState {
pub fn try_apply_transfer<Id, Profile: GasProfile>(
mut self,
transfer_op: &TransferOp,
signed_operation: SignedOperation<TransferOp, Verified, StandardMode>,
) -> Result<(Self, Balance, Vec<TxEvent>), LedgerError<Id>> {
let operation = signed_operation.operation();
// Compute the balance
let balance = transfer_op
let balance = operation
.balance(&self.utxos)
.map_err(mantle::Error::Transfer)?;
//execute the transfer
let (result, events) = transfer_op
let (result, events) = signed_operation
.execute(self.utxos)
.map_err(mantle::Error::Transfer)?;
.map_err(|(_signed_operation, error)| mantle::Error::Transfer(error))?;
self.utxos = result;
Ok((self, balance, events))
}
@@ -697,13 +697,16 @@ impl LedgerState {
}
pub fn from_genesis_tx<Id>(
tx: impl GenesisTx,
transfer: &SignedOperation<TransferOp, Verified, GenesisMode>,
config: &Config,
epoch_nonce: Fr,
) -> Result<Self, LedgerError<Id>> {
let transfer_op = tx.genesis_transfer();
if !transfer_op.inputs.is_empty() {
let first_input = transfer_op
let operation = transfer.operation();
// This transfer has not yet been verified despite the state saying so.
// This is its verification.
if !operation.inputs.is_empty() {
let first_input = operation
.inputs
.iter()
.next()
@@ -712,7 +715,7 @@ impl LedgerState {
return Err(LedgerError::InputInGenesis(first_input));
}
Ok(Self::from_utxos(transfer_op.utxos(), config, epoch_nonce))
Ok(Self::from_utxos(operation.utxos(), config, epoch_nonce))
}
pub fn from_utxos(utxos: impl IntoIterator<Item = Utxo>, config: &Config, nonce: Fr) -> Self {
@@ -832,23 +835,25 @@ pub mod tests {
use lb_core::{
crypto::{Digest as _, Hasher},
mantle::{
MantleTransaction, Note, Op,
Note, Op,
OpProof::ZkSig,
RawMantleTx, TxGasCalculator as _,
SignedOps, TxGasCalculator as _,
gas::MainnetGasProfile,
ledger::{Inputs, Outputs},
ops::{leader_claim::VoucherCm, sdp::SDPDeclareOp},
ops::{ZkAndEd25519Proof, leader_claim::VoucherCm, sdp::SDPDeclareOp},
traits::Hashable as _,
transactions::{
GasPrices,
GasPrices, OpProofs, Ops,
states::{Preverified, Unverified},
},
},
sdp::{Declaration, DeclarationId, Locator, ServiceParameters, ServiceType},
};
use lb_cryptarchia_engine::EpochConfig;
use lb_groth16::{AdditiveGroup as _, ModulusShift};
use lb_key_management_system_keys::keys::{Ed25519Key, Ed25519PublicKey, ZkKey, ZkSignature};
use lb_groth16::{AdditiveGroup as _, CompressedGroth16Proof, ModulusShift};
use lb_key_management_system_keys::keys::{
Ed25519Key, Ed25519PublicKey, Ed25519Signature, ZkKey, ZkSignature,
};
use lb_utils::math::{NonNegativeF64, NonNegativeRatio};
use num_bigint::BigUint;
use rand::{RngCore as _, thread_rng};
@@ -983,7 +988,7 @@ pub mod tests {
slot,
&proof,
&UncleSlots::default(),
std::iter::empty::<&MantleTransaction<Preverified>>(),
std::iter::empty::<SignedOps<Preverified, StandardMode>>(),
)?;
ledger.commit_update(id, state);
Ok(id)
@@ -1205,6 +1210,14 @@ pub mod tests {
zk_id: zk_key.to_public_key(),
locked_note_id: sdp_utxo.id(),
};
let proof = ZkAndEd25519Proof {
zk_sig: ZkSignature::new(CompressedGroth16Proof::from_bytes(&[0u8; 128])),
ed25519_sig: Ed25519Signature::zero(),
};
let signed_operation =
SignedOperation::<_, _, StandardMode>::new(declare_op.clone(), proof)
.into_state_trusted();
let config = ledger.config().clone();
let block_ledger = ledger.states.get_mut(&id).unwrap();
@@ -1212,7 +1225,7 @@ pub mod tests {
.mantle_ledger
.clone()
.try_apply_sdp_declaration(
&declare_op,
signed_operation,
block_ledger.cryptarchia_ledger.latest_utxos(),
&config,
)
@@ -1883,7 +1896,7 @@ pub mod tests {
fn create_tx_with_transfer(
inputs: &[(&ZkKey, &Utxo)],
outputs: Vec<Note>,
) -> (MantleTransaction<Unverified>, TransferOp, ZkSignature) {
) -> (SignedOps<Unverified, StandardMode>, TransferOp, ZkSignature) {
let sks = inputs
.iter()
.map(|(sk, _)| (*sk).clone())
@@ -1893,9 +1906,10 @@ pub mod tests {
Inputs::try_new(inputs).expect("Invalid inputs size"),
Outputs::try_new(outputs).expect("Invalid outputs size"),
);
let mantle_tx = RawMantleTx([Op::Transfer(transfer_op.clone())].into());
let mantle_tx = Ops::from([Op::Transfer(transfer_op.clone())]);
let transfer_sig = ZkKey::multi_sign(&sks, &mantle_tx.hash().to_fr()).unwrap();
let tx = MantleTransaction::new(mantle_tx, [ZkSig(transfer_sig.clone())].into());
let op_proofs = OpProofs::from([ZkSig(transfer_sig.clone())]);
let tx = SignedOps::from_parts(mantle_tx, op_proofs).unwrap();
(tx, transfer_op, transfer_sig)
}
@@ -1913,13 +1927,15 @@ pub mod tests {
let output_note = Note::new(200, output_note_sk.to_public_key());
let ledger_state = LedgerState::from_utxos([input_utxo], &config(), Fr::ZERO);
let (tx, transfer_op, _transfer_sig) = create_tx_with_transfer(
let (tx, transfer_op, transfer_proof) = create_tx_with_transfer(
&[(&note_sk, &input_utxo), (&note_sk, &input_utxo)],
vec![output_note],
);
let signed_operation =
SignedOperation::new(transfer_op, transfer_proof).into_state_trusted();
let _fees = tx.total_gas_cost::<MainnetGasProfile>(&GasPrices::new(0, 0));
let result = ledger_state.try_apply_transfer::<(), MainnetGasProfile>(&transfer_op);
let result = ledger_state.try_apply_transfer::<(), MainnetGasProfile>(signed_operation);
assert!(result.is_err());
}
@@ -1940,12 +1956,14 @@ pub mod tests {
let output_note2 = Note::new(3000, output_note2_sk.to_public_key());
let ledger_state = LedgerState::from_utxos([input_utxo], &config(), Fr::ZERO);
let (tx, transfer_op, _transfer_sig) =
let (tx, transfer_op, transfer_proof) =
create_tx_with_transfer(&[(&note_sk, &input_utxo)], vec![output_note1, output_note2]);
let signed_operation =
SignedOperation::new(transfer_op, transfer_proof).into_state_trusted();
let _fees = tx.total_gas_cost::<MainnetGasProfile>(&GasPrices::new(0, 0));
let (new_state, balance, events) = ledger_state
.try_apply_transfer::<(), MainnetGasProfile>(&transfer_op)
.try_apply_transfer::<(), MainnetGasProfile>(signed_operation)
.unwrap();
assert_eq!(
@@ -1967,7 +1985,7 @@ pub mod tests {
assert!(new_state.utxos.contains(&output_utxo2.id()));
// The new outputs can be spent in future transactions
let (tx, transfer_op, _transfer_sig) = create_tx_with_transfer(
let (tx, transfer_op, transfer_proof) = create_tx_with_transfer(
&[
(&output_note1_sk, &output_utxo1),
(&output_note2_sk, &output_utxo2),
@@ -1975,9 +1993,11 @@ pub mod tests {
vec![],
);
let signed_operation =
SignedOperation::new(transfer_op, transfer_proof).into_state_trusted();
let _fees = tx.total_gas_cost::<MainnetGasProfile>(&GasPrices::new(0, 0));
let (final_state, final_balance, events) = new_state
.try_apply_transfer::<(), MainnetGasProfile>(&transfer_op)
.try_apply_transfer::<(), MainnetGasProfile>(signed_operation)
.unwrap();
assert_eq!(
@@ -2026,11 +2046,14 @@ pub mod tests {
];
for non_existent_utxo in invalid_utxos {
let (_tx, transfer_op, _transfer_sig) =
let (_tx, transfer_op, transfer_proof) =
create_tx_with_transfer(&[(&ZkKey::zero(), &non_existent_utxo)], vec![]);
let signed_operation =
SignedOperation::new(transfer_op, transfer_proof).into_state_trusted();
let result = ledger_state
.clone()
.try_apply_transfer::<(), MainnetGasProfile>(&transfer_op);
.try_apply_transfer::<(), MainnetGasProfile>(signed_operation);
assert!(matches!(result, Err(LedgerError::Mantle(_))));
}
}
@@ -2048,21 +2071,25 @@ pub mod tests {
let output_note = Note::new(1, Fr::from(BigUint::from(2u8)).into());
let ledger_state = LedgerState::from_utxos([input_utxo], &config(), Fr::ZERO);
let (_tx, transfer_op, _transfer_sig) =
let (_tx, transfer_op, transfer_proof) =
create_tx_with_transfer(&[(&input_sk, &input_utxo)], vec![output_note, output_note]);
let signed_operation =
SignedOperation::new(transfer_op, transfer_proof).into_state_trusted();
let (_, balance, events) = ledger_state
.clone()
.try_apply_transfer::<(), MainnetGasProfile>(&transfer_op)
.try_apply_transfer::<(), MainnetGasProfile>(signed_operation)
.unwrap();
assert_eq!(balance, -1);
assert!(events.is_empty());
let (_tx, transfer_op, _transfer_sig) =
let (_tx, transfer_op, transfer_proof) =
create_tx_with_transfer(&[(&input_sk, &input_utxo)], vec![output_note]);
let signed_operation =
SignedOperation::new(transfer_op, transfer_proof).into_state_trusted();
assert_eq!(
ledger_state
.try_apply_transfer::<(), MainnetGasProfile>(&transfer_op,)
.try_apply_transfer::<(), MainnetGasProfile>(signed_operation)
.unwrap()
.1,
0
@@ -2080,11 +2107,13 @@ pub mod tests {
};
let ledger_state = LedgerState::from_utxos([input_utxo], &config(), Fr::ZERO);
let (tx, transfer_op, _transfer_sig) =
let (tx, transfer_op, transfer_proof) =
create_tx_with_transfer(&[(&input_sk, &input_utxo)], vec![]);
let signed_operation =
SignedOperation::new(transfer_op, transfer_proof).into_state_trusted();
let _fees = tx.total_gas_cost::<MainnetGasProfile>(&GasPrices::new(0, 0));
let result = ledger_state.try_apply_transfer::<(), MainnetGasProfile>(&transfer_op);
let result = ledger_state.try_apply_transfer::<(), MainnetGasProfile>(signed_operation);
assert!(result.is_ok());
let (new_state, balance, events) = result.unwrap();
@@ -26,8 +26,9 @@ use std::{num::NonZero, sync::Arc};
use lb_core::{
crypto::{Digest as _, Hasher},
mantle::{
MantleTransaction, Note, Utxo, Value,
Note, SignedOps, Utxo, Value,
gas::MainnetGasProfile,
ledger::verification_mode::StandardMode,
transactions::{
GENESIS_EXECUTION_GAS_PRICE, GENESIS_STORAGE_GAS_PRICE, states::Preverified,
},
@@ -384,7 +385,7 @@ fn apply_block_to_ledger(
slot,
&proof,
uncle_slots,
std::iter::empty::<&MantleTransaction<Preverified>>(),
std::iter::empty::<SignedOps<Preverified, StandardMode>>(),
)
.expect("ledger update");
ledger.commit_update(id, state);
+206 -143
View File
@@ -16,10 +16,11 @@ use lb_core::{
crypto::Hash as BlockHash,
events::{Events, HeaderEvent, TxEvent, TxEventPayload},
mantle::{
NoteId, Op, Utxo, Value, VerificationError,
NoteId, TxGasCalculator, Utxo, Value, VerificationError,
gas::{Gas, GasCost, GasOverflow, GasProfile},
ledger::ExecutableOperation as _,
ledger::verification_mode::StandardMode,
ops::{
SignedOp,
channel::{
channel_transfer::ChannelTransferExecutionContext,
deposit::DepositExecutionContext, withdraw::WithdrawExecutionContext,
@@ -27,8 +28,13 @@ use lb_core::{
leader_claim::LeaderClaimExecutionContext,
pow::{ClaimPoWRewardExecutionContext, PowReward},
},
traits::{GenesisTx, MantleTxWithProofs, PreverifiedMantleTx},
transactions::{GasPrices, MantleTxGasContext, hash::TxHash, mantle_tx::MantleTxContext},
traits::{GenesisTx, PreverifiedMantleTransaction, genesis::GenesisOps},
transactions::{
GasPrices,
hash::TxHash,
states::Verified,
tx_list::ops::{OpsContext, OpsGasContext},
},
},
proofs::leader_proof,
};
@@ -152,17 +158,17 @@ where
///
/// On success, a new [`LedgerState`] is returned, which can then be
/// committed by calling [`Self::commit_update`].
pub fn prepare_update<'tx, Tx, LeaderProof, Profile>(
pub fn prepare_update<Tx, LeaderProof, Profile>(
&self,
id: Id,
parent_id: Id,
slot: Slot,
proof: &LeaderProof,
uncle_slots: &UncleSlots,
txs: impl Iterator<Item = &'tx Tx>,
txs: impl Iterator<Item = Tx>,
) -> Result<(Id, LedgerState, Events), LedgerError<Id>>
where
Tx: PreverifiedMantleTx<Context = GasPrices> + 'tx,
Tx: PreverifiedMantleTransaction + TxGasCalculator<Context = GasPrices> + Clone,
LeaderProof: leader_proof::LeaderProof,
Profile: GasProfile,
Id: Into<BlockHash>,
@@ -227,17 +233,17 @@ pub struct LedgerState {
}
impl LedgerState {
fn try_update<'tx, Tx, LeaderProof, Id, Profile>(
fn try_update<Tx, LeaderProof, Id, Profile>(
self,
block_id: Id,
slot: Slot,
proof: &LeaderProof,
uncle_slots: &UncleSlots,
txs: impl Iterator<Item = &'tx Tx>,
txs: impl Iterator<Item = Tx>,
config: &Config,
) -> Result<(Self, Events), LedgerError<Id>>
where
Tx: PreverifiedMantleTx<Context = GasPrices> + 'tx,
Tx: PreverifiedMantleTransaction + TxGasCalculator<Context = GasPrices> + Clone,
LeaderProof: leader_proof::LeaderProof,
Profile: GasProfile,
Id: Into<BlockHash>,
@@ -447,13 +453,13 @@ impl LedgerState {
}
/// Apply the contents of an update to the ledger state.
pub fn try_apply_contents<'tx, Tx, Id, Profile: GasProfile>(
pub fn try_apply_contents<Tx, Id, Profile: GasProfile>(
mut self,
config: &Config,
txs: impl Iterator<Item = &'tx Tx>,
txs: impl Iterator<Item = Tx>,
) -> Result<(Self, Vec<TxEvent>), LedgerError<Id>>
where
Tx: PreverifiedMantleTx<Context = GasPrices> + 'tx,
Tx: PreverifiedMantleTransaction + TxGasCalculator<Context = GasPrices> + Clone,
{
let mut total_block_execution_gas: Gas = 0.into();
let mut total_block_storage_gas: Gas = 0.into();
@@ -464,7 +470,8 @@ impl LedgerState {
for tx in txs {
let balance;
let events;
(self, balance, events) = self.try_apply_tx::<_, _, Profile>(config, tx)?;
(self, balance, events) = self.try_apply_tx::<_, _, Profile>(config, tx.clone())?;
tx_events.extend(events);
let gas_prices = GasPrices {
@@ -538,9 +545,16 @@ impl LedgerState {
config: &Config,
epoch_nonce: Fr,
) -> Result<(Self, Vec<TxEvent>), LedgerError<Id>> {
let cryptarchia_ledger = CryptarchiaLedger::from_genesis_tx(&tx, config, epoch_nonce)?;
let GenesisOps {
transfer,
inscription,
declarations,
} = tx.into_genesis_ops();
let cryptarchia_ledger =
CryptarchiaLedger::from_genesis_tx(&transfer, config, epoch_nonce)?;
let (mantle_ledger, events) = MantleLedger::from_genesis_tx(
tx,
inscription,
declarations,
config,
cryptarchia_ledger.latest_utxos(),
cryptarchia_ledger.epoch_state(),
@@ -609,9 +623,9 @@ impl LedgerState {
}
#[must_use]
pub fn tx_context(&self) -> MantleTxContext {
MantleTxContext {
gas_context: MantleTxGasContext::from_channels(
pub fn tx_context(&self) -> OpsContext {
OpsContext {
gas_context: OpsGasContext::from_channels(
self.mantle_ledger().channels(),
self.get_gas_prices(),
),
@@ -625,56 +639,57 @@ impl LedgerState {
)]
fn try_apply_op<Id, Profile: GasProfile>(
mut self,
op: &Op,
signed_op: SignedOp<Verified, StandardMode>,
config: &Config,
tx_hash: &TxHash,
mut balance: Balance,
mut tx_events: Vec<TxEvent>,
) -> Result<(Self, Balance, Vec<TxEvent>), LedgerError<Id>> {
match op {
Op::ChannelInscribe(op) => {
let (result, events) = self
.mantle_ledger
.try_apply_channel_inscription(op, self.cryptarchia_ledger.slot)?;
match signed_op {
SignedOp::ChannelInscribe(signed_operation) => {
let (result, events) = self.mantle_ledger.try_apply_channel_inscription(
signed_operation,
self.cryptarchia_ledger.slot,
)?;
self.mantle_ledger = result;
tx_events.extend(events);
}
Op::ChannelConfig(op) => {
SignedOp::ChannelConfig(signed_operation) => {
let (result, events) = self
.mantle_ledger
.try_apply_channel_config(op, self.cryptarchia_ledger.slot)?;
.try_apply_channel_config(signed_operation, self.cryptarchia_ledger.slot)?;
self.mantle_ledger = result;
tx_events.extend(events);
}
Op::ChannelDeposit(op) => {
SignedOp::ChannelDeposit(signed_operation) => {
let channels = self.mantle_ledger.channels();
let utxos = self.cryptarchia_ledger.latest_utxos();
// Execute the Deposit
let (result, events) = op
let (result, events) = signed_operation
.execute(DepositExecutionContext {
channels: channels.clone(),
utxos: utxos.clone(),
tx_hash: *tx_hash,
})
.map_err(mantle::Error::Channel)?;
.map_err(|(_signed_operation, error)| mantle::Error::Channel(error))?;
self.mantle_ledger = self.mantle_ledger.update_channels(result.channels);
self.cryptarchia_ledger = self.cryptarchia_ledger.update_utxos(result.utxos);
tx_events.extend(events);
}
Op::ChannelWithdraw(op) => {
SignedOp::ChannelWithdraw(signed_operation) => {
let channels = self.mantle_ledger.channels();
let (result, events) = op
let (result, events) = signed_operation
.execute(WithdrawExecutionContext {
channels: channels.clone(),
tx_hash: *tx_hash,
})
.map_err(mantle::Error::Channel)?;
.map_err(|(_signed_operation, error)| mantle::Error::Channel(error))?;
self.mantle_ledger = self.mantle_ledger.update_channels(result.channels);
tx_events.extend(events);
}
Op::ChannelTransfer(op) => {
SignedOp::ChannelTransfer(signed_operation) => {
let channels = self.mantle_ledger.channels();
let utxos = self.cryptarchia_ledger.latest_utxos();
@@ -683,32 +698,38 @@ impl LedgerState {
utxos: utxos.clone(),
tx_hash: *tx_hash,
};
let (result, events) = op.execute(context).map_err(mantle::Error::Channel)?;
let (result, events) = signed_operation
.execute(context)
.map_err(|(_signed_operation, error)| mantle::Error::Channel(error))?;
self.mantle_ledger = self.mantle_ledger.update_channels(result.channels);
self.cryptarchia_ledger = self.cryptarchia_ledger.update_utxos(result.utxos);
tx_events.extend(events);
}
Op::SDPDeclare(op) => {
SignedOp::SDPDeclare(signed_operation) => {
let (result, events) = self.mantle_ledger.try_apply_sdp_declaration(
op,
signed_operation,
self.cryptarchia_ledger.latest_utxos(),
config,
)?;
self.mantle_ledger = result;
tx_events.extend(events);
}
Op::SDPActive(op) => {
let (result, events) = self.mantle_ledger.try_apply_sdp_active(op, config)?;
SignedOp::SDPActive(signed_operation) => {
let (result, events) = self
.mantle_ledger
.try_apply_sdp_active(signed_operation, config)?;
self.mantle_ledger = result;
tx_events.extend(events);
}
Op::SDPWithdraw(op) => {
let (result, events) = self.mantle_ledger.try_apply_sdp_withdraw(op, config)?;
SignedOp::SDPWithdraw(signed_operation) => {
let (result, events) = self
.mantle_ledger
.try_apply_sdp_withdraw(signed_operation, config)?;
self.mantle_ledger = result;
tx_events.extend(events);
}
Op::LeaderClaim(op) => {
let (result, events) = op
SignedOp::LeaderClaim(signed_operation) => {
let (result, events) = signed_operation
.execute(LeaderClaimExecutionContext {
nullifiers: self.mantle_ledger.leaders.nullifiers_cloned(),
reward_amount: self.mantle_ledger.leaders.reward_amount(),
@@ -716,7 +737,7 @@ impl LedgerState {
utxos: self.cryptarchia_ledger.latest_utxos().clone(),
tx_hash: *tx_hash,
})
.map_err(mantle::Error::LeaderClaim)?;
.map_err(|(_signed_operation, error)| mantle::Error::LeaderClaim(error))?;
self.mantle_ledger
.leaders
.update_nullifiers(result.nullifiers);
@@ -727,19 +748,19 @@ impl LedgerState {
.update_rewards(result.claimable_rewards);
tx_events.extend(events);
}
Op::Transfer(op) => {
SignedOp::Transfer(signed_operation) => {
let transfer_balance;
let events;
(self.cryptarchia_ledger, transfer_balance, events) =
self.cryptarchia_ledger
.try_apply_transfer::<_, Profile>(op)?;
.try_apply_transfer::<_, Profile>(signed_operation)?;
balance = balance
.checked_add(transfer_balance)
.ok_or(LedgerError::BalanceOverflow)?;
tx_events.extend(events);
}
Op::ClaimPowReward(claim_pow_reward) => {
let (result, events) = claim_pow_reward
SignedOp::ClaimPowReward(signed_operation) => {
let (result, events) = signed_operation
.execute(ClaimPoWRewardExecutionContext {
reward_pool: self.mantle_ledger.pow.reward_pool(),
// TODO: check correctness of epoch reward, as it should be from the op
@@ -750,7 +771,7 @@ impl LedgerState {
utxos: self.cryptarchia_ledger.latest_utxos().clone(),
block_slots: self.mantle_ledger.pow.block_slots().clone(),
})
.map_err(mantle::Error::ClaimPow)?;
.map_err(|(_signed_operation, error)| mantle::Error::ClaimPow(error))?;
self.mantle_ledger
.pow
.update_from_claim_execution_result(&result);
@@ -768,7 +789,7 @@ impl LedgerState {
///
/// Verification is interleaved with execution: each operation is verified
/// against the current ledger state (via
/// [`MantleTransaction::verified_ops`]) immediately before it is executed,
/// [`SignedOps::verified_ops`]) immediately before it is executed,
/// so an operation may depend on state produced by earlier operations
/// in the same transaction.
///
@@ -779,15 +800,15 @@ impl LedgerState {
///
/// If any operation fails verification or execution, returns a
/// [`LedgerError`] describing the failure.
fn try_apply_tx<'tx, Tx, Id, Profile: GasProfile>(
fn try_apply_tx<Tx, Id, Profile: GasProfile>(
mut self,
config: &Config,
tx: &'tx Tx,
tx: Tx,
) -> Result<(Self, Balance, Vec<TxEvent>), LedgerError<Id>>
where
Tx: PreverifiedMantleTx + 'tx + MantleTxWithProofs<Context = GasPrices>,
Tx: PreverifiedMantleTransaction,
{
let mut verified_ops = tx.verified_ops();
let mut verified_ops = tx.into_verified_operations();
let mut balance: Balance = 0;
let mut tx_events = Vec::new();
@@ -798,9 +819,14 @@ impl LedgerState {
&self.cryptarchia_ledger,
config,
);
// On Error (failed verification), the transaction is considered invalid and
// rejected.
let Some(op) = verified_ops.next(&helper).transpose()? else {
// All operations have been processed, exit the loop.
break;
};
(self, balance, tx_events) = self.try_apply_op::<_, Profile>(
op,
config,
@@ -824,11 +850,11 @@ mod tests {
use lb_core::{
events::DepositNote,
mantle::{
MantleTransaction, Note, OpProof, RawMantleTx, TxGasCalculator as _,
Note, Op, OpProof, SignedOps,
gas::MainnetGasProfile,
ledger::{Inputs, Outputs, Utxos, VerifiableOperation as _},
ops::{
OpId as _,
OpId as _, OpRef, SignedOperation,
channel::{
ChannelId, MsgId,
config::ChannelConfigOp,
@@ -844,7 +870,6 @@ mod tests {
transactions::{
OpProofs, Ops,
hash::TxHashView,
mantle_tx::MantleTx as _,
states::{Preverified, Unverified},
},
},
@@ -856,7 +881,9 @@ mod tests {
};
use lb_cryptarchia_engine::Epoch;
use lb_groth16::{CompressedGroth16Proof, Field as _};
use lb_key_management_system_keys::keys::{Ed25519Key, Ed25519PublicKey, ZkKey, ZkPublicKey};
use lb_key_management_system_keys::keys::{
Ed25519Key, Ed25519PublicKey, ZkKey, ZkPublicKey, ZkSignature,
};
use num_bigint::BigUint;
use super::*;
@@ -878,17 +905,16 @@ mod tests {
inputs: Vec<NoteId>,
outputs: Vec<Note>,
sks: &[ZkKey],
) -> MantleTransaction<Unverified> {
) -> SignedOps<Unverified, StandardMode> {
let transfer_op = TransferOp::new(
Inputs::try_new(inputs).expect("Invalid inputs size"),
Outputs::try_new(outputs).expect("Invalid outputs size"),
);
let mantle_tx = RawMantleTx([Op::Transfer(transfer_op)].into());
let ops_proofs = [OpProof::ZkSig(
let mantle_tx = Ops::from([Op::Transfer(transfer_op)]);
let ops_proofs = OpProofs::from([OpProof::ZkSig(
ZkKey::multi_sign(sks, &mantle_tx.hash().to_fr()).unwrap(),
)]
.into();
MantleTransaction::new(mantle_tx, ops_proofs)
)]);
SignedOps::from_parts(mantle_tx, ops_proofs).unwrap()
}
pub fn create_test_ledger() -> (Ledger<HeaderId>, HeaderId, Utxo) {
@@ -950,20 +976,20 @@ mod tests {
MultiSequencer(ChannelMultiSigProof),
}
fn create_signed_tx(op: Op, signing_key: &Key) -> MantleTransaction<Preverified> {
fn create_signed_tx(op: Op, signing_key: &Key) -> SignedOps<Preverified, StandardMode> {
create_multi_signed_tx(vec![op], vec![signing_key])
}
fn create_multi_signed_tx(
ops: Vec<Op>,
ops_vec: Vec<Op>,
signing_keys: Vec<&Key>,
) -> MantleTransaction<Preverified> {
let mantle_tx = RawMantleTx(Ops::new_unchecked(ops.clone()));
) -> SignedOps<Preverified, StandardMode> {
let ops = Ops::new_unchecked(ops_vec);
let tx_hash = mantle_tx.hash();
let tx_hash = ops.hash();
let ops_proofs = signing_keys
.into_iter()
.zip(ops)
.zip(ops.iter())
.map(|(key, _)| match key {
Key::Ed25519(key) => {
OpProof::Ed25519Sig(key.sign_payload(tx_hash.as_signing_bytes().as_ref()))
@@ -975,9 +1001,9 @@ mod tests {
Key::MultiSequencer(proof) => OpProof::ChannelMultiSigProof(proof.clone()),
})
.collect::<Vec<_>>();
let ops_proofs = OpProofs::try_from(ops_proofs).expect("operation proofs are bounded");
MantleTransaction::new(mantle_tx, ops_proofs)
let ops_proofs = OpProofs::new_unchecked(ops_proofs);
SignedOps::from_parts(ops, ops_proofs)
.unwrap()
.preverify()
.expect("Test transaction should have valid signatures")
}
@@ -999,7 +1025,7 @@ mod tests {
&Key::Ed25519(signing_key.clone()),
);
ledger_state
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(config, &tx)
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(config, tx)
.unwrap()
.0
}
@@ -1036,11 +1062,17 @@ mod tests {
&config.sdp_config.service_rewards_params.blend,
))),
};
let signed_operation = SignedOperation::new(
active_op,
ZkSignature::new(CompressedGroth16Proof::from_bytes(&[0u8; 128])),
)
.into_state_trusted();
let block_ledger = ledger.states.get_mut(&id).unwrap();
block_ledger.mantle_ledger = block_ledger
.mantle_ledger
.clone()
.try_apply_sdp_active(&active_op, &config)
.try_apply_sdp_active(signed_operation, &config)
.unwrap()
.0;
id
@@ -1076,7 +1108,11 @@ mod tests {
let tx = create_tx(vec![utxo.id()], vec![output_note], &[sk])
.preverify()
.unwrap();
let mantle_tx = tx.mantle_tx().clone();
let output_utxo = if let OpRef::Transfer(transfer_op) = tx.op_refs().get(0).unwrap() {
transfer_op.outputs.utxo_by_index(0, transfer_op).unwrap()
} else {
panic!("first op must be a transfer")
};
// Create a dummy proof (using same structure as in cryptarchia tests)
@@ -1094,7 +1130,7 @@ mod tests {
Slot::from(1u64),
&proof,
&UncleSlots::default(),
std::iter::once(&tx),
std::iter::once(tx),
)
.unwrap();
assert!(events.is_empty());
@@ -1105,12 +1141,7 @@ mod tests {
assert!(!new_state.latest_utxos().contains(&utxo.id()));
// Verify output was created
if let Op::Transfer(transfer_op) = &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 {
panic!("first op must be a transfer")
}
assert!(new_state.latest_utxos().contains(&output_utxo.id()));
}
#[test]
@@ -1128,7 +1159,7 @@ mod tests {
};
let tx = create_signed_tx(Op::ChannelInscribe(inscribe_op), &Key::Ed25519(signing_key));
let result = state.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, &tx);
let result = state.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, tx);
assert!(result.is_ok());
let (new_state, _, events) = result.unwrap();
@@ -1158,7 +1189,7 @@ mod tests {
transfer_threshold: 1,
};
let config_tx = RawMantleTx([Op::ChannelConfig(config_op.clone())].into());
let config_tx = Ops::from([Op::ChannelConfig(config_op.clone())]);
let config_tx_hash = config_tx.hash();
let config_proof = ChannelMultiSigProof::try_new(
[IndexedSignature::new(
@@ -1173,7 +1204,7 @@ mod tests {
Op::ChannelConfig(config_op),
&Key::MultiSequencer(config_proof),
);
let result = state.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, &tx);
let result = state.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, tx);
assert!(result.is_ok());
let (new_state, _, events) = result.unwrap();
@@ -1229,7 +1260,8 @@ mod tests {
};
let ops = vec![Op::ChannelDeposit(deposit.clone())];
let tx = create_multi_signed_tx(ops, vec![&Key::Zk(sk)]);
let result = ledger_state.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, &tx);
let tx_hash = tx.hash();
let result = ledger_state.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, tx);
let (new_state, balance, events) = result.unwrap();
// The deposited note is consumed and re-created as a channel note under
// a new NoteId.
@@ -1273,7 +1305,7 @@ mod tests {
else {
panic!("events should include deposit event")
};
assert_eq!(*event_tx_hash, tx.hash());
assert_eq!(*event_tx_hash, tx_hash);
assert_eq!(*op_id, deposit.op_id());
assert_eq!(*event_channel_id, deposit.channel_id);
assert_eq!(*amount, utxo.note.value);
@@ -1314,7 +1346,7 @@ mod tests {
let deposit_ops = vec![Op::ChannelDeposit(deposit)];
let tx = create_multi_signed_tx(deposit_ops, vec![&Key::Zk(sk)]);
ledger_state = ledger_state
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, &tx)
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, tx)
.unwrap()
.0;
@@ -1331,7 +1363,7 @@ mod tests {
channel_id,
inputs: Inputs::new([deposited]),
};
let withdraw_tx = RawMantleTx([Op::ChannelWithdraw(withdraw)].into());
let withdraw_tx = Ops::from([Op::ChannelWithdraw(withdraw)]);
let withdraw_tx_hash = withdraw_tx.hash();
let withdraw_proof = ChannelMultiSigProof::try_new(
[IndexedSignature::new(
@@ -1343,12 +1375,12 @@ mod tests {
.unwrap();
let signed_tx = create_multi_signed_tx(
withdraw_tx.0.to_vec(),
withdraw_tx.to_vec(),
vec![&Key::MultiSequencer(withdraw_proof)],
);
let result =
ledger_state.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, &signed_tx);
ledger_state.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, signed_tx);
assert!(result.is_ok());
let (new_state, tx_balance, events) = result.unwrap();
@@ -1389,19 +1421,16 @@ mod tests {
let deposit_tx =
create_multi_signed_tx(vec![Op::ChannelDeposit(deposit)], vec![&Key::Zk(sk)]);
ledger_state = ledger_state
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, &deposit_tx)
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, deposit_tx.clone())
.unwrap()
.0;
// Withdraw releases the channel note under the NoteId the deposit gave
// it, so the original input never comes back to the ledger.
let withdraw_tx = RawMantleTx(
[Op::ChannelWithdraw(ChannelWithdrawOp {
channel_id,
inputs: Inputs::new([deposited]),
})]
.into(),
);
let withdraw_tx = Ops::from([Op::ChannelWithdraw(ChannelWithdrawOp {
channel_id,
inputs: Inputs::new([deposited]),
})]);
let withdraw_proof = ChannelMultiSigProof::try_new(
[IndexedSignature::new(
0,
@@ -1411,11 +1440,11 @@ mod tests {
)
.unwrap();
let signed_withdraw = create_multi_signed_tx(
withdraw_tx.0.to_vec(),
withdraw_tx.to_vec(),
vec![&Key::MultiSequencer(withdraw_proof)],
);
ledger_state = ledger_state
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, &signed_withdraw)
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, signed_withdraw)
.unwrap()
.0;
@@ -1423,7 +1452,7 @@ mod tests {
// Replaying the signed deposit fails: its input no longer exists.
let result =
ledger_state.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, &deposit_tx);
ledger_state.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, deposit_tx);
assert!(result.is_err());
}
@@ -1453,7 +1482,7 @@ mod tests {
let deposit_ops = vec![Op::ChannelDeposit(deposit)];
let tx = create_multi_signed_tx(deposit_ops, vec![&Key::Zk(sk)]);
ledger_state = ledger_state
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, &tx)
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, tx)
.unwrap()
.0;
// The deposit re-created the note as a channel note
@@ -1470,7 +1499,7 @@ mod tests {
inputs: Inputs::new([deposited]),
};
let wrong_key = Ed25519Key::from_bytes(&[42; 32]);
let withdraw_tx = RawMantleTx([Op::ChannelWithdraw(withdraw)].into());
let withdraw_tx = Ops::from([Op::ChannelWithdraw(withdraw)]);
let withdraw_tx_hash = withdraw_tx.hash();
let invalid_proof = ChannelMultiSigProof::try_new(
[IndexedSignature::new(
@@ -1482,13 +1511,13 @@ mod tests {
.unwrap();
let signed_tx = create_multi_signed_tx(
withdraw_tx.0.to_vec(),
withdraw_tx.to_vec(),
vec![&Key::MultiSequencer(invalid_proof), &Key::EmptyZk],
);
let err = ledger_state
.clone()
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, &signed_tx)
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, signed_tx)
.unwrap_err();
assert_eq!(
err,
@@ -1526,7 +1555,7 @@ mod tests {
&Key::Ed25519(signing_key.clone()),
);
state = state
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, &first_tx)
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, first_tx)
.unwrap()
.0;
@@ -1545,7 +1574,7 @@ mod tests {
);
let result = state
.clone()
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, &second_tx);
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, second_tx);
assert!(matches!(
result,
Err(LedgerError::VerificationError(
@@ -1569,7 +1598,7 @@ mod tests {
&Key::Ed25519(signing_key),
);
let empty_result =
state.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, &empty_tx);
state.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, empty_tx);
assert!(matches!(
empty_result,
Err(LedgerError::VerificationError(
@@ -1602,7 +1631,7 @@ mod tests {
&Key::Ed25519(signing_key),
);
state = state
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, &first_tx)
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, first_tx)
.unwrap()
.0;
@@ -1618,7 +1647,7 @@ mod tests {
Op::ChannelInscribe(second_inscribe),
&Key::Ed25519(unauthorized_signing_key),
);
let result = state.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, &second_tx);
let result = state.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, second_tx);
assert!(matches!(
result,
Err(LedgerError::VerificationError(
@@ -1681,7 +1710,7 @@ mod tests {
Op::ChannelConfig(config_op),
Op::ChannelInscribe(inscribe_op3.clone()),
];
let config_tx = RawMantleTx(Ops::new_unchecked(ops.clone()));
let config_tx = Ops::new_unchecked(ops.clone());
let config_tx_hash = config_tx.hash();
let config_proof = ChannelMultiSigProof::try_new(
[IndexedSignature::new(
@@ -1703,7 +1732,7 @@ mod tests {
);
let result = state
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, &tx)
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&test_config, tx)
.unwrap()
.0;
@@ -1854,7 +1883,10 @@ mod tests {
let result = ledger
.clone()
.try_apply_contents::<_, HeaderId, MainnetGasProfile>(&config, std::iter::once(&tx));
.try_apply_contents::<_, HeaderId, MainnetGasProfile>(
&config,
std::iter::once(tx.clone()),
);
// The `unwrap` should succeed because the user pays at least the base fee of
// 2705
result.unwrap();
@@ -1862,7 +1894,7 @@ mod tests {
ledger.cryptarchia_ledger = ledger.cryptarchia_ledger.set_execution_base_fee(10.into());
let err = ledger
.try_apply_contents::<_, HeaderId, MainnetGasProfile>(&config, std::iter::once(&tx))
.try_apply_contents::<_, HeaderId, MainnetGasProfile>(&config, std::iter::once(tx))
.unwrap_err();
// The transaction should be rejected because the price indicated for execution
// doesn't cover the base fee that cost 27 050
@@ -1899,7 +1931,7 @@ mod tests {
let result = ledger
.clone()
.try_apply_contents::<_, HeaderId, MainnetGasProfile>(&config, std::iter::once(&tx));
.try_apply_contents::<_, HeaderId, MainnetGasProfile>(&config, std::iter::once(tx));
// The `unwrap` should succeed because the user pays at least the base fee of
// 794
let (no_priority_fee_ledger, events) = result.unwrap();
@@ -1917,7 +1949,7 @@ mod tests {
.unwrap();
let result = ledger
.try_apply_contents::<_, HeaderId, MainnetGasProfile>(&config, std::iter::once(&tx));
.try_apply_contents::<_, HeaderId, MainnetGasProfile>(&config, std::iter::once(tx));
// The `unwrap` should succeed because the user pays at least the base fee of
// 794
let (priority_fee_ledger, events) = result.unwrap();
@@ -1959,7 +1991,7 @@ mod tests {
assert!(storage_gas.into_inner() > 0);
let (applied, _) = ledger
.try_apply_contents::<_, HeaderId, MainnetGasProfile>(&config, std::iter::once(&tx))
.try_apply_contents::<_, HeaderId, MainnetGasProfile>(&config, std::iter::once(tx))
.unwrap();
// Storage gas consumed by the tx should be accumulated in the ledger
@@ -1994,8 +2026,13 @@ mod tests {
voucher_nullifier: nf.into(),
pk: ZkPublicKey::zero(),
};
let signed_operation = SignedOperation::<_, _, StandardMode>::new(
op,
Groth16LeaderClaimProof::new(CompressedGroth16Proof::from_bytes(&[0u8; 128])),
)
.into_state_trusted();
// Skip `op.validate` in this test to avoid having to generate a valid proof
let (result, _events) = op
let (result, _events) = signed_operation
.execute(LeaderClaimExecutionContext {
nullifiers: leaders.nullifiers_cloned(),
reward_amount: leaders.reward_amount(),
@@ -2033,8 +2070,15 @@ mod tests {
voucher_nullifier: Fr::ZERO.into(), // nf of the 1st voucher
pk: ZkPublicKey::zero(),
};
let signed_operation = SignedOperation::<_, _, StandardMode>::new(
op,
Groth16LeaderClaimProof::new(CompressedGroth16Proof::from_bytes(&[0u8; 128])),
);
let preverified_signed_operation = signed_operation.into_state_trusted::<Preverified>();
let executable_signed_operation = preverified_signed_operation.clone().into_state_trusted();
// Skip `op.validate` in this test to avoid having to generate a valid proof
let (result, _events) = op
let (result, _events) = executable_signed_operation
.execute(LeaderClaimExecutionContext {
nullifiers: leaders.nullifiers_cloned(),
reward_amount: leaders.reward_amount(),
@@ -2053,16 +2097,15 @@ mod tests {
let tx_hash = TxHash::from([0u8; 32]);
let tx_hash_view = TxHashView::from(tx_hash);
// Use a dummy proof since duplication is detected before proof verification
let proof = Groth16LeaderClaimProof::new(CompressedGroth16Proof::from_bytes(&[0u8; 128]));
let err = op
.verify(
&proof,
&LeaderClaimVerificationContext {
nullifiers: leaders.nullifiers(),
claimable_vouchers_root: leaders.vouchers_snapshot_root(),
tx_hash_view: &tx_hash_view,
},
)
// let proof =
// Groth16LeaderClaimProof::new(CompressedGroth16Proof::from_bytes(&[0u8;
// 128]));
let err = preverified_signed_operation
.verify(&LeaderClaimVerificationContext {
nullifiers: leaders.nullifiers(),
claimable_vouchers_root: leaders.vouchers_snapshot_root(),
tx_hash_view: &tx_hash_view,
})
.unwrap_err();
assert_eq!(err, LeaderClaimError::DuplicatedVoucherNullifier);
}
@@ -2190,9 +2233,11 @@ mod tests {
assert!(difficulty_at(&test_ledger, block_1) > genesis_difficulty);
}
fn claim_tx() -> MantleTransaction<Preverified> {
let mantle_tx = RawMantleTx([Op::ClaimPowReward(claim_op())].into());
MantleTransaction::new(mantle_tx, [OpProof::None(NoOpProof)].into())
fn claim_tx() -> SignedOps<Preverified, StandardMode> {
let mantle_tx = Ops::from([Op::ClaimPowReward(claim_op())]);
let op_proofs = OpProofs::from([OpProof::None(NoOpProof)]);
SignedOps::from_parts(mantle_tx, op_proofs)
.unwrap()
.preverify()
.expect("claim op with OpProof::None should pass preverification")
}
@@ -2220,7 +2265,7 @@ mod tests {
assert_eq!(state.mantle_ledger.pow.epoch_reward(), 0);
let err = state
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&config, &claim_tx())
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&config, claim_tx())
.expect_err("claim should fail validation");
assert!(matches!(
@@ -2239,7 +2284,7 @@ mod tests {
let (state, config) = pow_ledger_state(1_000);
let err = state
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&config, &claim_tx())
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&config, claim_tx())
.expect_err("claim should fail validation");
assert!(matches!(
@@ -2276,7 +2321,7 @@ mod tests {
let epoch_reward = state.mantle_ledger.pow.epoch_reward();
let (state, _balance, events) = state
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&config, &claim_tx())
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&config, claim_tx())
.expect("claim should validate and execute");
assert_eq!(
@@ -2315,11 +2360,11 @@ mod tests {
// during tx-level validation.
let (state, config) = claim_accepting_state();
let (state, _, _) = state
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&config, &claim_tx())
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&config, claim_tx())
.expect("first claim should succeed");
let err = state
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&config, &claim_tx())
.try_apply_tx::<_, HeaderId, MainnetGasProfile>(&config, claim_tx())
.expect_err("second claim should be rejected");
assert!(matches!(
@@ -2346,11 +2391,14 @@ mod tests {
let pool_before = state.mantle_ledger.pow.reward_pool();
let epoch_reward = state.mantle_ledger.pow.epoch_reward();
let op = claim_op();
let signed_operation =
SignedOperation::<_, _, StandardMode>::new(op.clone(), NoOpProof)
.into_state_trusted();
let tx_hash = TxHash::from([9u8; 32]);
let (state, _balance, events) = state
.try_apply_op::<HeaderId, MainnetGasProfile>(
&Op::ClaimPowReward(op.clone()),
signed_operation.into(),
&config,
&tx_hash,
0,
@@ -2414,14 +2462,29 @@ mod tests {
.add_seen_block_slots(claim_op().block_hash, Slot::from(0u64));
let pool_before = state.mantle_ledger.pow.reward_pool();
let epoch_reward = state.mantle_ledger.pow.epoch_reward();
let op = Op::ClaimPowReward(claim_op());
let op = claim_op();
let signed_operation =
SignedOperation::<_, _, StandardMode>::new(op, NoOpProof).into_state_trusted();
let tx_hash = TxHash::from([9u8; 32]);
let (state, _, _) = state
.try_apply_op::<HeaderId, MainnetGasProfile>(&op, &config, &tx_hash, 0, Vec::new())
.try_apply_op::<HeaderId, MainnetGasProfile>(
signed_operation.clone().into(),
&config,
&tx_hash,
0,
Vec::new(),
)
.expect("first claim should succeed");
let (state, _, _) = state
.try_apply_op::<HeaderId, MainnetGasProfile>(&op, &config, &tx_hash, 0, Vec::new())
.try_apply_op::<HeaderId, MainnetGasProfile>(
signed_operation.into(),
&config,
&tx_hash,
0,
Vec::new(),
)
.expect("second claim currently also succeeds (no validation)");
assert_eq!(
+15 -11
View File
@@ -9,8 +9,9 @@ use lb_core::{
events::TxEvent,
mantle::{
NoteId, Value,
ledger::ExecutableOperation as _,
ledger::verification_mode::{GenesisMode, StandardMode},
ops::{
SignedOperation,
channel::{
config::{ChannelConfigExecutionContext, ChannelConfigOp},
inscribe::{InscriptionExecutionContext, InscriptionOp},
@@ -20,7 +21,7 @@ use lb_core::{
sdp::{SDPActiveOp, SDPDeclareOp, SDPWithdrawOp},
transfer::TransferError,
},
traits::GenesisTx,
transactions::states::{Preverified, Verified},
},
sdp::locked_notes::LockedNotes,
};
@@ -76,14 +77,15 @@ impl LedgerState {
}
pub fn from_genesis_tx(
tx: impl GenesisTx,
signed_operation_inscription: SignedOperation<InscriptionOp, Verified, GenesisMode>,
signed_operation_declarations: Vec<SignedOperation<SDPDeclareOp, Preverified, GenesisMode>>,
config: &Config,
utxo_tree: &UtxoTree,
epoch_state: &EpochState,
) -> Result<(Self, Vec<TxEvent>), Error> {
let mut tx_events = Vec::new();
let (channels, events) = channel::Channels::from_genesis(tx.genesis_inscription())?;
let (channels, events) = channel::Channels::from_genesis(signed_operation_inscription)?;
tx_events.extend(events);
let (sdp, events) = sdp::SdpLedger::from_genesis(
@@ -91,7 +93,7 @@ impl LedgerState {
utxo_tree,
&channels,
epoch_state,
tx.sdp_declarations(),
signed_operation_declarations,
)?;
tx_events.extend(events);
@@ -175,14 +177,15 @@ impl LedgerState {
pub fn try_apply_channel_inscription(
mut self,
inscription_op: &InscriptionOp,
signed_operation: SignedOperation<InscriptionOp, Verified, StandardMode>,
block_slot: Slot,
) -> Result<(Self, Vec<TxEvent>), Error> {
let (result, events) = inscription_op
let (result, events) = signed_operation
.execute(InscriptionExecutionContext {
channels: self.channels,
block_slot,
})
.map_err(|(_signed_operation, error)| error)
.inspect_err(
|err| error!(target: LOG_TARGET, %err, "failed to apply channel inscribe message"),
)?;
@@ -193,7 +196,7 @@ impl LedgerState {
pub fn try_apply_channel_config(
mut self,
config_op: &ChannelConfigOp,
config_op: SignedOperation<ChannelConfigOp, Verified, StandardMode>,
block_slot: Slot,
) -> Result<(Self, Vec<TxEvent>), Error> {
let (result, events) = config_op
@@ -201,6 +204,7 @@ impl LedgerState {
channels: self.channels,
block_slot,
})
.map_err(|(_signed_operation, error)| error)
.inspect_err(
|err| error!(target: LOG_TARGET, %err, "failed to apply channel set-keys message"),
)?;
@@ -211,7 +215,7 @@ impl LedgerState {
pub fn try_apply_sdp_declaration(
mut self,
sdp_declare_op: &SDPDeclareOp,
sdp_declare_op: SignedOperation<SDPDeclareOp, Verified, StandardMode>,
utxo_tree: &UtxoTree,
config: &Config,
) -> Result<(Self, Vec<TxEvent>), Error> {
@@ -227,7 +231,7 @@ impl LedgerState {
pub fn try_apply_sdp_active(
mut self,
sdp_active_op: &SDPActiveOp,
sdp_active_op: SignedOperation<SDPActiveOp, Verified, StandardMode>,
config: &Config,
) -> Result<(Self, Vec<TxEvent>), Error> {
let (result, events) = self
@@ -242,7 +246,7 @@ impl LedgerState {
pub fn try_apply_sdp_withdraw(
mut self,
sdp_withdraw_op: &SDPWithdrawOp,
sdp_withdraw_op: SignedOperation<SDPWithdrawOp, Verified, StandardMode>,
config: &Config,
) -> Result<(Self, Vec<TxEvent>), Error> {
let (result, events) = self
+163 -81
View File
@@ -9,14 +9,18 @@ use lb_core::{
block::BlockNumber,
events::{HeaderEvent, TxEvent},
mantle::{
NoteId, OpProof, Utxo, Value,
NoteId, Utxo, Value,
channel::Channels,
ledger::{ExecutableOperation, VerifiableOperation, verification_mode::GenesisMode},
ops::sdp::{
SDPActiveExecutionContext, SDPActiveOp, SDPDeclareExecutionContext, SDPDeclareOp,
SDPWithdrawExecutionContext, SDPWithdrawOp,
declare::SDPDeclareGenesisValidationContext,
ledger::verification_mode::{GenesisMode, StandardMode},
ops::{
SignedOperation,
sdp::{
SDPActiveExecutionContext, SDPActiveOp, SDPDeclareExecutionContext, SDPDeclareOp,
SDPWithdrawExecutionContext, SDPWithdrawOp,
declare::SDPDeclareGenesisValidationContext,
},
},
transactions::states::{Preverified, Verified},
},
sdp::{
ActivityMetadata, Declaration, DeclarationId, MinStake, Nonce, ProviderId,
@@ -300,42 +304,37 @@ impl SdpLedger {
}
}
pub fn from_genesis<'a>(
pub fn from_genesis(
config: &Config,
utxo_tree: &UtxoTree,
channels: &Channels,
epoch_state: &EpochState,
ops: impl Iterator<Item = (&'a SDPDeclareOp, &'a OpProof)> + 'a,
declarations: impl IntoIterator<Item = SignedOperation<SDPDeclareOp, Preverified, GenesisMode>>,
) -> Result<(Self, Vec<TxEvent>), Error> {
let mut sdp = Self::new(epoch_state.epoch())
.with_blend_service(&config.service_rewards_params.blend, epoch_state);
let mut all_events = Vec::new();
for (op, proof) in ops {
// TODO: remove this match once op/proof pairing is enforced by
// construction (e.g. via `SignedOp`) instead of at this call site.
let OpProof::ZkAndEd25519Sigs(proof) = proof else {
return Err(Error::InvalidProof);
for declaration in declarations {
let service_state = {
let operation = declaration.operation();
sdp.services
.get(&operation.service_type)
.ok_or(Error::ServiceNotFound(operation.service_type))?
};
let service_state = sdp
.services
.get(&op.service_type)
.ok_or(Error::ServiceNotFound(op.service_type))?;
<SDPDeclareOp as VerifiableOperation<GenesisMode>>::verify(
op,
proof,
&SDPDeclareGenesisValidationContext {
let verified_declaration = declaration
.into_verified(&SDPDeclareGenesisValidationContext {
utxo_tree,
channels,
locked_notes: &sdp.locked_notes,
declarations: service_state.declarations(),
min_stake: &config.min_stake,
},
)?;
})
.map_err(|(_signed_operation, error)| error)?;
let (result, events) = sdp.try_apply_genesis_sdp_declaration(utxo_tree, op, config)?;
let (result, events) =
sdp.try_apply_genesis_sdp_declaration(utxo_tree, verified_declaration, config)?;
sdp = result;
all_events.extend(events);
}
@@ -417,24 +416,25 @@ impl SdpLedger {
pub fn try_apply_genesis_sdp_declaration(
mut self,
utxo_tree: &UtxoTree,
op: &SDPDeclareOp,
declaration: SignedOperation<SDPDeclareOp, Verified, GenesisMode>,
config: &Config,
) -> Result<(Self, Vec<TxEvent>), Error> {
let Some(service_state) = self.services.get_mut(&op.service_type) else {
return Err(Error::ServiceNotFound(op.service_type));
let operation = declaration.operation();
let Some(service_state) = self.services.get_mut(&operation.service_type) else {
return Err(Error::ServiceNotFound(operation.service_type));
};
// Execute SDP Declare
let (result, events) = <SDPDeclareOp as ExecutableOperation>::execute(
op,
SDPDeclareExecutionContext {
let (result, events) = declaration
.execute(SDPDeclareExecutionContext {
utxo_tree: utxo_tree.clone(),
epoch: self.epoch,
declarations: service_state.declarations_clone(),
locked_notes: self.locked_notes.clone(),
min_stake: config.min_stake,
},
)?;
})
.map_err(|(_signed_operation, error)| error)?;
self.locked_notes = result.locked_notes;
service_state.update_declarations(result.declarations);
@@ -444,23 +444,23 @@ impl SdpLedger {
pub fn try_apply_sdp_declaration(
mut self,
utxo_tree: &UtxoTree,
op: &SDPDeclareOp,
signed_operation: SignedOperation<SDPDeclareOp, Verified, StandardMode>,
config: &Config,
) -> Result<(Self, Vec<TxEvent>), Error> {
let Some(service_state) = self.services.get_mut(&op.service_type) else {
return Err(Error::ServiceNotFound(op.service_type));
let operation = signed_operation.operation();
let Some(service_state) = self.services.get_mut(&operation.service_type) else {
return Err(Error::ServiceNotFound(operation.service_type));
};
let (result, events) = <SDPDeclareOp as ExecutableOperation>::execute(
op,
SDPDeclareExecutionContext {
let (result, events) = signed_operation
.execute(SDPDeclareExecutionContext {
utxo_tree: utxo_tree.clone(),
epoch: self.epoch,
declarations: service_state.declarations_clone(),
locked_notes: self.locked_notes.clone(),
min_stake: config.min_stake,
},
)?;
})
.map_err(|(_signed_operation, error)| error)?;
self.locked_notes = result.locked_notes;
service_state.update_declarations(result.declarations);
@@ -469,46 +469,60 @@ impl SdpLedger {
pub fn apply_active_msg(
mut self,
op: &SDPActiveOp,
signed_operation: SignedOperation<SDPActiveOp, Verified, StandardMode>,
config: &Config,
) -> Result<(Self, Vec<TxEvent>), Error> {
let (service, _) = self.get_service(&op.declaration_id, config)?;
let operation = signed_operation.operation();
let operation_declaration_id = operation.declaration_id;
let operation_metadata = operation.metadata.clone();
let (service, _) = self.get_service(&operation.declaration_id, config)?;
let Some(service_state) = self.services.get_mut(&service) else {
return Err(Error::ServiceNotFound(service));
};
let (result, events) = op.execute(SDPActiveExecutionContext {
epoch: self.epoch,
declarations: service_state.declarations_clone(),
})?;
let (result, events) = signed_operation
.execute(SDPActiveExecutionContext {
epoch: self.epoch,
declarations: service_state.declarations_clone(),
})
.map_err(|(_signed_operation, error)| error)?;
let provider_id = result
.declarations
.get(&op.declaration_id)
.get(&operation_declaration_id)
.expect("the declaration should be in the list after execution")
.provider_id;
service_state.update_declarations(result.declarations);
service_state.update_rewards(provider_id, &op.metadata, &config.service_rewards_params)?;
service_state.update_rewards(
provider_id,
&operation_metadata,
&config.service_rewards_params,
)?;
Ok((self, events))
}
pub fn apply_withdrawn_msg(
mut self,
op: &SDPWithdrawOp,
signed_operation: SignedOperation<SDPWithdrawOp, Verified, StandardMode>,
config: &Config,
) -> Result<(Self, Vec<TxEvent>), Error> {
let (service, _) = self.get_service(&op.declaration_id, config)?;
let operation = signed_operation.operation();
let (service, _) = self.get_service(&operation.declaration_id, config)?;
let Some(service_state) = self.services.get_mut(&service) else {
return Err(Error::ServiceNotFound(service));
};
let (result, events) = op.execute(SDPWithdrawExecutionContext {
declarations: service_state.declarations_clone(),
locked_notes: self.locked_notes.clone(),
epoch: self.epoch,
})?;
let (result, events) = signed_operation
.execute(SDPWithdrawExecutionContext {
declarations: service_state.declarations_clone(),
locked_notes: self.locked_notes.clone(),
epoch: self.epoch,
})
.map_err(|(_signed_operation, error)| error)?;
self.locked_notes = result.locked_notes;
service_state.update_declarations(result.declarations);
@@ -637,11 +651,11 @@ mod tests {
use std::{num::NonZeroU64, sync::Arc};
use lb_core::{
mantle::ledger::Utxos,
mantle::{ledger::Utxos, ops::ZkAndEd25519Proof},
sdp::{Locator, SNAPSHOT_FINALIZATION_DELAY},
};
use lb_groth16::{AdditiveGroup as _, Fr};
use lb_key_management_system_keys::keys::{Ed25519Key, ZkKey};
use lb_groth16::{AdditiveGroup as _, CompressedGroth16Proof, Fr};
use lb_key_management_system_keys::keys::{Ed25519Key, Ed25519Signature, ZkKey, ZkSignature};
use lb_utils::math::NonNegativeF64;
use num_bigint::BigUint;
@@ -768,7 +782,7 @@ mod tests {
let (_utxo_sk, utxo) = utxo_with_sk();
let signing_key = create_signing_key();
let zk_key = create_zk_key(1);
let declare_op = &SDPDeclareOp {
let declare_op = SDPDeclareOp {
service_type: ServiceType::BlendNetwork,
locked_note_id: utxo.id(),
zk_id: zk_key.to_public_key(),
@@ -776,8 +790,14 @@ mod tests {
locators: "/ip4/1.1.1.1/udp/0".parse::<Locator>().unwrap().into(),
};
let declaration_id = declare_op.id();
let proof = ZkAndEd25519Proof {
zk_sig: ZkSignature::new(CompressedGroth16Proof::from_bytes(&[0u8; 128])),
ed25519_sig: Ed25519Signature::zero(),
};
let signed_operation = SignedOperation::new(declare_op, proof).into_state_trusted();
let ledger = ledger
.try_apply_sdp_declaration(&utxo_tree(vec![utxo]), declare_op, &config)
.try_apply_sdp_declaration(&utxo_tree(vec![utxo]), signed_operation, &config)
.map(|(sdp_ledger, _)| sdp_ledger)
.unwrap();
@@ -821,7 +841,7 @@ mod tests {
let (_utxo_sk, utxo) = utxo_with_sk();
let signing_key = create_signing_key();
let zk_key = create_zk_key(1);
let declare_op = &SDPDeclareOp {
let declare_op = SDPDeclareOp {
service_type: ServiceType::BlendNetwork,
locked_note_id: utxo.id(),
zk_id: zk_key.to_public_key(),
@@ -829,8 +849,14 @@ mod tests {
locators: "/ip4/1.1.1.1/udp/0".parse::<Locator>().unwrap().into(),
};
let declaration_id = declare_op.id();
let proof = ZkAndEd25519Proof {
zk_sig: ZkSignature::new(CompressedGroth16Proof::from_bytes(&[0u8; 128])),
ed25519_sig: Ed25519Signature::zero(),
};
let signed_operation = SignedOperation::new(declare_op, proof).into_state_trusted();
let ledger = ledger
.try_apply_sdp_declaration(&utxo_tree(vec![utxo]), declare_op, &config)
.try_apply_sdp_declaration(&utxo_tree(vec![utxo]), signed_operation, &config)
.map(|(sdp_ledger, _)| sdp_ledger)
.unwrap();
@@ -874,7 +900,7 @@ mod tests {
let note_id = utxo.id();
let signing_key = create_signing_key();
let zk_key = create_zk_key(1);
let declare_op = &SDPDeclareOp {
let declare_op = SDPDeclareOp {
service_type: ServiceType::BlendNetwork,
locked_note_id: note_id,
zk_id: zk_key.to_public_key(),
@@ -882,19 +908,29 @@ mod tests {
locators: "/ip4/1.1.1.1/udp/0".parse::<Locator>().unwrap().into(),
};
let declaration_id = declare_op.id();
let proof = ZkAndEd25519Proof {
zk_sig: ZkSignature::new(CompressedGroth16Proof::from_bytes(&[0u8; 128])),
ed25519_sig: Ed25519Signature::zero(),
};
let signed_operation_declare = SignedOperation::new(declare_op, proof).into_state_trusted();
let ledger = ledger
.try_apply_sdp_declaration(&utxo_tree(vec![utxo]), declare_op, &config)
.try_apply_sdp_declaration(&utxo_tree(vec![utxo]), signed_operation_declare, &config)
.map(|(sdp_ledger, _)| sdp_ledger)
.unwrap();
// Withdraw at epoch 1: `withdrawn = 1 + SNAPSHOT_FINALIZATION_DELAY = 3`.
let withdraw_op = &SDPWithdrawOp {
let withdraw_op = SDPWithdrawOp {
declaration_id,
nonce: 1,
locked_note_id: note_id,
};
let proof = ZkSignature::new(CompressedGroth16Proof::from_bytes(&[0u8; 128]));
let signed_operation_withdraw =
SignedOperation::new(withdraw_op, proof).into_state_trusted();
let ledger = ledger
.apply_withdrawn_msg(withdraw_op, &config)
.apply_withdrawn_msg(signed_operation_withdraw, &config)
.map(|(sdp_ledger, _)| sdp_ledger)
.unwrap();
let withdraw_at = ledger
@@ -957,8 +993,15 @@ mod tests {
locators: "/ip4/1.1.1.1/udp/0".parse::<Locator>().unwrap().into(),
};
let declaration_id = declare_op.id();
let proof = ZkAndEd25519Proof {
zk_sig: ZkSignature::new(CompressedGroth16Proof::from_bytes(&[0u8; 128])),
ed25519_sig: Ed25519Signature::zero(),
};
let signed_operation_declare =
SignedOperation::new(declare_op.clone(), proof).into_state_trusted();
ledger = ledger
.try_apply_sdp_declaration(&utxo_tree(vec![utxo]), declare_op, &config)
.try_apply_sdp_declaration(&utxo_tree(vec![utxo]), signed_operation_declare, &config)
.map(|(sdp_ledger, _)| sdp_ledger)
.unwrap();
let declarations = ledger
@@ -994,8 +1037,11 @@ mod tests {
&config.service_rewards_params.blend,
))),
};
let proof = ZkSignature::new(CompressedGroth16Proof::from_bytes(&[0u8; 128]));
let signed_operation_active = SignedOperation::new(active_op, proof).into_state_trusted();
ledger = ledger
.apply_active_msg(&active_op, &config)
.apply_active_msg(signed_operation_active, &config)
.map(|(sdp_ledger, _)| sdp_ledger)
.unwrap();
let declarations = ledger
@@ -1054,7 +1100,7 @@ mod tests {
let note_id = utxo.id();
let signing_key = create_signing_key();
let zk_key = create_zk_key(1);
let declare_op = &SDPDeclareOp {
let declare_op = SDPDeclareOp {
service_type: ServiceType::BlendNetwork,
locked_note_id: note_id,
zk_id: zk_key.to_public_key(),
@@ -1062,8 +1108,14 @@ mod tests {
locators: "/ip4/1.1.1.1/udp/0".parse::<Locator>().unwrap().into(),
};
let declaration_id = declare_op.id();
let proof = ZkAndEd25519Proof {
zk_sig: ZkSignature::new(CompressedGroth16Proof::from_bytes(&[0u8; 128])),
ed25519_sig: Ed25519Signature::zero(),
};
let signed_operation_declare = SignedOperation::new(declare_op, proof).into_state_trusted();
ledger = ledger
.try_apply_sdp_declaration(&utxo_tree(vec![utxo]), declare_op, &config)
.try_apply_sdp_declaration(&utxo_tree(vec![utxo]), signed_operation_declare, &config)
.map(|(sdp_ledger, _)| sdp_ledger)
.unwrap();
@@ -1092,8 +1144,11 @@ mod tests {
&config.service_rewards_params.blend,
))),
};
let proof = ZkSignature::new(CompressedGroth16Proof::from_bytes(&[0u8; 128]));
let signed_operation_active = SignedOperation::new(active_op, proof).into_state_trusted();
let ledger = ledger
.apply_active_msg(&active_op, &config)
.apply_active_msg(signed_operation_active, &config)
.map(|(sdp_ledger, _)| sdp_ledger)
.unwrap();
@@ -1161,13 +1216,19 @@ mod tests {
locators: "/ip4/1.1.1.1/udp/0".parse::<Locator>().unwrap().into(),
};
let declaration_id_a = declare_a.id();
let proof_a = ZkAndEd25519Proof {
zk_sig: ZkSignature::new(CompressedGroth16Proof::from_bytes(&[0u8; 128])),
ed25519_sig: Ed25519Signature::zero(),
};
let signed_operation_declare_a =
SignedOperation::new(declare_a, proof_a).into_state_trusted();
let epoch0 = dummy_epoch_state(0.into());
let sdp_ledger = dummy_sdp_ledger(0.into(), &config);
let utxos = utxo_tree(vec![utxo_a, utxo_b]);
let sdp_ledger = sdp_ledger
.try_apply_sdp_declaration(&utxos, &declare_a, &config)
.try_apply_sdp_declaration(&utxos, signed_operation_declare_a, &config)
.map(|(sdp_ledger, _)| sdp_ledger)
.unwrap();
@@ -1177,8 +1238,12 @@ mod tests {
nonce: 1,
locked_note_id: utxo_a.id(),
};
let proof_withdraw = ZkSignature::new(CompressedGroth16Proof::from_bytes(&[0u8; 128]));
let signed_operation_withdraw =
SignedOperation::new(withdraw_op, proof_withdraw).into_state_trusted();
let (sdp_ledger, _events) = sdp_ledger
.apply_withdrawn_msg(&withdraw_op, &config)
.apply_withdrawn_msg(signed_operation_withdraw, &config)
.unwrap();
let withdraw_epoch = sdp_ledger
@@ -1211,8 +1276,15 @@ mod tests {
provider_id: ProviderId(signing_key.public_key()),
locators: "/ip4/2.2.2.2/udp/0".parse::<Locator>().unwrap().into(),
};
let proof_b = ZkAndEd25519Proof {
zk_sig: ZkSignature::new(CompressedGroth16Proof::from_bytes(&[0u8; 128])),
ed25519_sig: Ed25519Signature::zero(),
};
let signed_operation_declare_b =
SignedOperation::new(declare_b, proof_b).into_state_trusted();
sdp_ledger
.try_apply_sdp_declaration(&utxos, &declare_b, &config)
.try_apply_sdp_declaration(&utxos, signed_operation_declare_b, &config)
.expect(
"Declaration reusing A's provider_id and zk_id must be accepted after A is removed",
);
@@ -1231,14 +1303,20 @@ mod tests {
let signing_key = create_signing_key();
let zk_key = create_zk_key(1);
let declare_op = &SDPDeclareOp {
let declare_op = SDPDeclareOp {
service_type: service_a,
locked_note_id: note_id,
zk_id: zk_key.to_public_key(),
provider_id: ProviderId(signing_key.public_key()),
locators: "/ip4/1.1.1.1/udp/0".parse::<Locator>().unwrap().into(),
};
let declare_locked_note_id = declare_op.locked_note_id;
let declaration_id = declare_op.id();
let proof = ZkAndEd25519Proof {
zk_sig: ZkSignature::new(CompressedGroth16Proof::from_bytes(&[0u8; 128])),
ed25519_sig: Ed25519Signature::zero(),
};
let signed_operation_declare = SignedOperation::new(declare_op, proof).into_state_trusted();
// Initialize ledger with service config and declare
let epoch0 = dummy_epoch_state(0.into());
@@ -1246,7 +1324,7 @@ mod tests {
let utxo_tree = utxo_tree(vec![utxo]);
let sdp_ledger = sdp_ledger
.try_apply_sdp_declaration(&utxo_tree, declare_op, &config)
.try_apply_sdp_declaration(&utxo_tree, signed_operation_declare, &config)
.map(|(sdp_ledger, _)| sdp_ledger)
.unwrap();
@@ -1254,13 +1332,17 @@ mod tests {
assert!(sdp_ledger.get_declaration(&declaration_id).is_some());
// Withdraw the declaration
let withdraw_op = &SDPWithdrawOp {
let withdraw_op = SDPWithdrawOp {
declaration_id,
nonce: 1,
locked_note_id: note_id,
};
let proof_withdraw = ZkSignature::new(CompressedGroth16Proof::from_bytes(&[0u8; 128]));
let signed_operation_withdraw =
SignedOperation::new(withdraw_op, proof_withdraw).into_state_trusted();
let sdp_ledger = sdp_ledger
.apply_withdrawn_msg(withdraw_op, &config)
.apply_withdrawn_msg(signed_operation_withdraw, &config)
.map(|(sdp_ledger, _)| sdp_ledger)
.unwrap();
@@ -1293,7 +1375,7 @@ mod tests {
assert!(
sdp_ledger
.locked_notes()
.is_locked_for_service(&declare_op.locked_note_id, &ServiceType::BlendNetwork),
.is_locked_for_service(&declare_locked_note_id, &ServiceType::BlendNetwork),
"the provider's note must still be locked before the withdrawn epoch is reached"
);
@@ -1315,7 +1397,7 @@ mod tests {
assert!(
!sdp_ledger
.locked_notes()
.is_locked_for_service(&declare_op.locked_note_id, &ServiceType::BlendNetwork),
.is_locked_for_service(&declare_locked_note_id, &ServiceType::BlendNetwork),
"the provider's note must be unlocked at the withdrawn epoch"
);
}