766 lines
25 KiB
Rust
Raw Normal View History

2025-10-24 22:18:41 -03:00
use std::{fmt::Display, time::Instant};
2024-11-25 07:26:16 +02:00
use anyhow::Result;
#[cfg(feature = "testnet")]
use common::PINATA_BASE58;
2025-09-08 10:11:04 +03:00
use common::{
2025-10-17 16:04:09 -03:00
HashType,
2025-09-08 10:11:04 +03:00
block::HashableBlockData,
transaction::{EncodedTransaction, NSSATransaction},
};
2025-04-16 16:17:53 +03:00
use config::SequencerConfig;
2025-09-08 10:11:04 +03:00
use log::warn;
2025-11-18 19:31:03 +03:00
use mempool::{MemPool, MemPoolHandle};
2025-04-16 16:17:53 +03:00
use serde::{Deserialize, Serialize};
2024-11-25 07:26:16 +02:00
2025-11-18 19:31:03 +03:00
use crate::block_store::SequencerBlockStore;
2025-10-25 00:30:04 -03:00
2025-10-28 13:01:54 -03:00
pub mod block_store;
2024-11-25 07:26:16 +02:00
pub mod config;
pub struct SequencerCore {
2025-11-18 19:31:03 +03:00
state: nssa::V02State,
block_store: SequencerBlockStore,
mempool: MemPool<EncodedTransaction>,
sequencer_config: SequencerConfig,
chain_height: u64,
2024-11-25 07:26:16 +02:00
}
2025-07-29 14:20:03 +03:00
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2025-10-17 15:56:26 -03:00
pub enum TransactionMalformationError {
InvalidSignature,
2025-10-17 16:04:09 -03:00
FailedToDecode { tx: HashType },
}
2025-10-17 15:56:26 -03:00
impl Display for TransactionMalformationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:#?}")
}
}
2025-10-17 15:56:26 -03:00
impl std::error::Error for TransactionMalformationError {}
2024-11-25 07:26:16 +02:00
impl SequencerCore {
2025-11-18 19:31:03 +03:00
/// Start Sequencer from configuration and construct transaction sender
pub fn start_from_config(config: SequencerConfig) -> (Self, MemPoolHandle<EncodedTransaction>) {
2025-10-28 13:01:54 -03:00
let hashable_data = HashableBlockData {
block_id: config.genesis_id,
transactions: vec![],
prev_block_hash: [0; 32],
timestamp: 0,
};
let signing_key = nssa::PrivateKey::try_new(config.signing_key).unwrap();
let genesis_block = hashable_data.into_block(&signing_key);
//Sequencer should panic if unable to open db,
//as fixing this issue may require actions non-native to program scope
2025-11-18 19:31:03 +03:00
let block_store = SequencerBlockStore::open_db_with_genesis(
2025-10-28 13:01:54 -03:00
&config.home.join("rocksdb"),
Some(genesis_block),
signing_key,
)
.unwrap();
2025-09-24 14:29:56 +03:00
let mut initial_commitments = vec![];
for init_comm_data in config.initial_commitments.clone() {
let npk = init_comm_data.npk;
let mut acc = init_comm_data.account;
acc.program_owner = nssa::program::Program::authenticated_transfer_program().id();
let comm = nssa_core::Commitment::new(&npk, &acc);
initial_commitments.push(comm);
}
let init_accs: Vec<(nssa::AccountId, u128)> = config
2025-10-28 13:01:54 -03:00
.initial_accounts
.iter()
.map(|acc_data| (acc_data.account_id.parse().unwrap(), acc_data.balance))
2025-10-28 13:01:54 -03:00
.collect();
let mut state = nssa::V02State::new_with_genesis_accounts(&init_accs, &initial_commitments);
2025-10-25 00:30:04 -03:00
2025-10-28 13:01:54 -03:00
#[cfg(feature = "testnet")]
state.add_pinata_program(PINATA_BASE58.parse().unwrap());
2025-10-25 00:30:04 -03:00
2025-11-18 19:31:03 +03:00
let (mempool, mempool_handle) = MemPool::new(config.mempool_max_size);
2025-10-25 00:30:04 -03:00
let mut this = Self {
2025-10-28 13:01:54 -03:00
state,
block_store,
2025-11-18 19:31:03 +03:00
mempool,
2024-11-25 07:26:16 +02:00
chain_height: config.genesis_id,
sequencer_config: config,
2025-10-25 00:30:04 -03:00
};
2025-10-28 13:01:54 -03:00
this.sync_state_with_stored_blocks();
2025-11-18 19:31:03 +03:00
(this, mempool_handle)
2025-10-28 13:01:54 -03:00
}
/// If there are stored blocks ahead of the current height, this method will load and process all transaction
/// in them in the order they are stored. The NSSA state will be updated accordingly.
2025-10-28 13:01:54 -03:00
fn sync_state_with_stored_blocks(&mut self) {
let mut next_block_id = self.sequencer_config.genesis_id + 1;
while let Ok(block) = self.block_store.get_block_at_id(next_block_id) {
2025-10-25 00:30:04 -03:00
for encoded_transaction in block.body.transactions {
let transaction = NSSATransaction::try_from(&encoded_transaction).unwrap();
// Process transaction and update state
2025-10-28 13:01:54 -03:00
self.execute_check_transaction_on_state(transaction)
2025-10-27 16:04:12 -03:00
.unwrap();
// Update the tx hash to block id map.
2025-11-18 19:31:03 +03:00
self.block_store.insert(&encoded_transaction, next_block_id);
2025-10-25 00:30:04 -03:00
}
2025-10-28 13:01:54 -03:00
self.chain_height = next_block_id;
next_block_id += 1;
2024-11-25 07:26:16 +02:00
}
}
fn execute_check_transaction_on_state(
&mut self,
2025-09-03 10:29:51 +03:00
tx: NSSATransaction,
) -> Result<NSSATransaction, nssa::error::NssaError> {
match &tx {
2025-11-18 19:31:03 +03:00
NSSATransaction::Public(tx) => self.state.transition_from_public_transaction(tx),
NSSATransaction::PrivacyPreserving(tx) => self
.state
.transition_from_privacy_preserving_transaction(tx),
NSSATransaction::ProgramDeployment(tx) => self
.state
.transition_from_program_deployment_transaction(tx),
2025-09-03 10:29:51 +03:00
}
2025-11-18 19:31:03 +03:00
.inspect_err(|err| warn!("Error at transition {err:#?}"))?;
2025-08-07 15:19:06 -03:00
Ok(tx)
}
2025-11-18 19:31:03 +03:00
/// Produces new block from transactions in mempool
pub fn produce_new_block_with_mempool_transactions(&mut self) -> Result<u64> {
2025-10-24 22:18:41 -03:00
let now = Instant::now();
let new_block_height = self.chain_height + 1;
2025-09-08 10:11:04 +03:00
let mut valid_transactions = vec![];
2025-09-03 10:29:51 +03:00
2025-11-18 19:31:03 +03:00
while let Some(tx) = self.mempool.pop() {
2025-09-03 10:29:51 +03:00
let nssa_transaction = NSSATransaction::try_from(&tx)
2025-10-17 15:56:26 -03:00
.map_err(|_| TransactionMalformationError::FailedToDecode { tx: tx.hash() })?;
2025-09-03 10:29:51 +03:00
2025-09-08 10:11:04 +03:00
if let Ok(valid_tx) = self.execute_check_transaction_on_state(nssa_transaction) {
valid_transactions.push(valid_tx.into());
2025-11-18 19:31:03 +03:00
if valid_transactions.len() >= self.sequencer_config.max_num_tx_in_block {
2025-09-08 10:11:04 +03:00
break;
}
2025-11-18 19:31:03 +03:00
} else {
// Probably need to handle unsuccessful transaction execution?
2025-09-08 10:11:04 +03:00
}
}
let prev_block_hash = self
.block_store
.get_block_at_id(self.chain_height)?
2025-09-03 10:29:51 +03:00
.header
.hash;
2025-09-03 10:29:51 +03:00
let curr_time = chrono::Utc::now().timestamp_millis() as u64;
2025-10-24 22:18:41 -03:00
let num_txs_in_block = valid_transactions.len();
2024-11-25 07:26:16 +02:00
let hashable_data = HashableBlockData {
block_id: new_block_height,
transactions: valid_transactions,
prev_block_hash,
2025-09-03 10:29:51 +03:00
timestamp: curr_time,
2024-11-25 07:26:16 +02:00
};
2025-11-18 19:31:03 +03:00
let block = hashable_data.into_block(self.block_store.signing_key());
2024-11-25 07:26:16 +02:00
2025-10-28 13:01:54 -03:00
self.block_store.put_block_at_id(block)?;
2024-11-25 07:26:16 +02:00
2025-07-28 15:21:35 -03:00
self.chain_height = new_block_height;
2024-11-25 07:26:16 +02:00
2025-11-18 19:31:03 +03:00
// TODO: Consider switching to `tracing` crate to have more structured and consistent logs e.g.
//
// ```
// info!(num_txs = num_txs_in_block, time = now.elapsed(), "Created block");
// ```
2025-10-24 22:18:41 -03:00
log::info!(
"Created block with {} transactions in {} seconds",
num_txs_in_block,
now.elapsed().as_secs()
);
2025-07-28 15:21:35 -03:00
Ok(self.chain_height)
2024-11-25 07:26:16 +02:00
}
2025-11-18 19:31:03 +03:00
pub fn state(&self) -> &nssa::V02State {
&self.state
}
pub fn block_store(&self) -> &SequencerBlockStore {
&self.block_store
}
pub fn chain_height(&self) -> u64 {
self.chain_height
}
pub fn sequencer_config(&self) -> &SequencerConfig {
&self.sequencer_config
}
}
// TODO: Introduce type-safe wrapper around checked transaction, e.g. AuthenticatedTransaction
pub fn transaction_pre_check(
tx: NSSATransaction,
) -> Result<NSSATransaction, TransactionMalformationError> {
// Stateless checks here
match tx {
NSSATransaction::Public(tx) => {
if tx.witness_set().is_valid_for(tx.message()) {
Ok(NSSATransaction::Public(tx))
} else {
Err(TransactionMalformationError::InvalidSignature)
}
}
NSSATransaction::PrivacyPreserving(tx) => {
if tx.witness_set().signatures_are_valid_for(tx.message()) {
Ok(NSSATransaction::PrivacyPreserving(tx))
} else {
Err(TransactionMalformationError::InvalidSignature)
}
}
NSSATransaction::ProgramDeployment(tx) => Ok(NSSATransaction::ProgramDeployment(tx)),
}
2024-11-25 07:26:16 +02:00
}
2025-01-27 13:42:11 +01:00
#[cfg(test)]
mod tests {
2025-11-18 19:31:03 +03:00
use std::pin::pin;
2025-10-23 17:33:25 +03:00
use base58::{FromBase58, ToBase58};
2025-09-03 10:29:51 +03:00
use common::test_utils::sequencer_sign_key_for_testing;
use nssa::PrivateKey;
2025-09-03 10:29:51 +03:00
2025-07-29 14:20:03 +03:00
use crate::config::AccountInitialData;
2025-01-27 13:42:11 +01:00
use super::*;
2025-09-08 10:11:04 +03:00
fn parse_unwrap_tx_body_into_nssa_tx(tx_body: EncodedTransaction) -> NSSATransaction {
2025-09-03 10:29:51 +03:00
NSSATransaction::try_from(&tx_body)
2025-10-17 15:56:26 -03:00
.map_err(|_| TransactionMalformationError::FailedToDecode { tx: tx_body.hash() })
2025-09-03 10:29:51 +03:00
.unwrap()
}
fn setup_sequencer_config_variable_initial_accounts(
2025-07-29 14:20:03 +03:00
initial_accounts: Vec<AccountInitialData>,
) -> SequencerConfig {
2025-07-29 15:29:00 -03:00
let tempdir = tempfile::tempdir().unwrap();
let home = tempdir.path().to_path_buf();
2025-01-27 13:42:11 +01:00
SequencerConfig {
2025-07-29 15:29:00 -03:00
home,
2025-01-27 13:42:11 +01:00
override_rust_log: Some("info".to_string()),
genesis_id: 1,
is_genesis_random: false,
max_num_tx_in_block: 10,
2025-10-23 16:23:47 -03:00
mempool_max_size: 10000,
2025-01-27 13:42:11 +01:00
block_create_timeout_millis: 1000,
port: 8080,
initial_accounts,
2025-09-18 15:59:17 +03:00
initial_commitments: vec![],
2025-09-03 10:29:51 +03:00
signing_key: *sequencer_sign_key_for_testing().value(),
2025-01-27 13:42:11 +01:00
}
}
2025-07-17 08:09:27 +03:00
fn setup_sequencer_config() -> SequencerConfig {
let acc1_account_id: Vec<u8> = vec![
208, 122, 210, 232, 75, 39, 250, 0, 194, 98, 240, 161, 238, 160, 255, 53, 202, 9, 115,
84, 126, 106, 16, 111, 114, 241, 147, 194, 220, 131, 139, 68,
2025-07-29 14:20:03 +03:00
];
let acc2_account_id: Vec<u8> = vec![
231, 174, 119, 197, 239, 26, 5, 153, 147, 68, 175, 73, 159, 199, 138, 23, 5, 57, 141,
98, 237, 6, 207, 46, 20, 121, 246, 222, 248, 154, 57, 188,
2025-07-29 14:20:03 +03:00
];
let initial_acc1 = AccountInitialData {
account_id: acc1_account_id.to_base58(),
2025-07-29 14:20:03 +03:00
balance: 10000,
};
let initial_acc2 = AccountInitialData {
account_id: acc2_account_id.to_base58(),
2025-07-29 14:20:03 +03:00
balance: 20000,
};
2025-07-25 10:00:27 +03:00
let initial_accounts = vec![initial_acc1, initial_acc2];
2025-07-17 08:09:27 +03:00
setup_sequencer_config_variable_initial_accounts(initial_accounts)
}
2025-08-07 15:19:06 -03:00
fn create_signing_key_for_account1() -> nssa::PrivateKey {
2025-08-11 20:38:29 -03:00
nssa::PrivateKey::try_new([1; 32]).unwrap()
2025-07-29 14:20:03 +03:00
}
2025-08-07 15:19:06 -03:00
fn create_signing_key_for_account2() -> nssa::PrivateKey {
2025-08-11 20:38:29 -03:00
nssa::PrivateKey::try_new([2; 32]).unwrap()
2025-07-29 14:20:03 +03:00
}
2025-11-18 19:31:03 +03:00
async fn common_setup() -> (SequencerCore, MemPoolHandle<EncodedTransaction>) {
let config = setup_sequencer_config();
common_setup_with_config(config).await
}
async fn common_setup_with_config(
config: SequencerConfig,
) -> (SequencerCore, MemPoolHandle<EncodedTransaction>) {
let (mut sequencer, mempool_handle) = SequencerCore::start_from_config(config);
let tx = common::test_utils::produce_dummy_empty_transaction();
2025-11-18 19:31:03 +03:00
mempool_handle.push(tx).await.unwrap();
2025-01-27 13:42:43 +01:00
2025-06-18 13:56:09 +03:00
sequencer
.produce_new_block_with_mempool_transactions()
.unwrap();
2025-11-18 19:31:03 +03:00
(sequencer, mempool_handle)
2025-01-27 13:42:43 +01:00
}
2025-01-27 13:42:11 +01:00
2025-01-27 13:43:17 +01:00
#[test]
fn test_start_from_config() {
let config = setup_sequencer_config();
2025-11-18 19:31:03 +03:00
let (sequencer, _mempool_handle) = SequencerCore::start_from_config(config.clone());
2025-01-27 13:43:17 +01:00
assert_eq!(sequencer.chain_height, config.genesis_id);
assert_eq!(sequencer.sequencer_config.max_num_tx_in_block, 10);
assert_eq!(sequencer.sequencer_config.port, 8080);
2025-07-14 10:43:35 +03:00
let acc1_account_id = config.initial_accounts[0]
.account_id
2025-10-23 17:33:25 +03:00
.clone()
.from_base58()
2025-07-29 14:20:03 +03:00
.unwrap()
.try_into()
.unwrap();
let acc2_account_id = config.initial_accounts[1]
.account_id
2025-10-23 17:33:25 +03:00
.clone()
.from_base58()
2025-07-29 14:20:03 +03:00
.unwrap()
.try_into()
.unwrap();
2025-07-14 10:43:35 +03:00
2025-08-08 16:53:15 -03:00
let balance_acc_1 = sequencer
.state
.get_account_by_id(&nssa::AccountId::new(acc1_account_id))
2025-08-08 16:53:15 -03:00
.balance;
let balance_acc_2 = sequencer
.state
.get_account_by_id(&nssa::AccountId::new(acc2_account_id))
2025-08-08 16:53:15 -03:00
.balance;
2025-07-14 10:43:35 +03:00
2025-08-08 16:53:15 -03:00
assert_eq!(10000, balance_acc_1);
assert_eq!(20000, balance_acc_2);
2025-01-27 13:43:17 +01:00
}
#[test]
2025-07-25 10:00:27 +03:00
fn test_start_different_intial_accounts_balances() {
let acc1_account_id: Vec<u8> = vec![
2025-08-13 03:01:54 -03:00
27, 132, 197, 86, 123, 18, 100, 64, 153, 93, 62, 213, 170, 186, 5, 101, 215, 30, 24,
52, 96, 72, 25, 255, 156, 23, 245, 233, 213, 221, 7, 143,
];
2025-07-29 14:20:03 +03:00
let acc2_account_id: Vec<u8> = vec![
2025-08-13 03:01:54 -03:00
77, 75, 108, 209, 54, 16, 50, 202, 155, 210, 174, 185, 217, 0, 170, 77, 69, 217, 234,
216, 10, 201, 66, 51, 116, 196, 81, 167, 37, 77, 7, 102,
];
2025-07-29 14:20:03 +03:00
let initial_acc1 = AccountInitialData {
account_id: acc1_account_id.to_base58(),
2025-07-29 14:20:03 +03:00
balance: 10000,
};
let initial_acc2 = AccountInitialData {
account_id: acc2_account_id.to_base58(),
2025-07-29 14:20:03 +03:00
balance: 20000,
};
2025-07-25 10:00:27 +03:00
let initial_accounts = vec![initial_acc1, initial_acc2];
let config = setup_sequencer_config_variable_initial_accounts(initial_accounts);
2025-11-18 19:31:03 +03:00
let (sequencer, _mempool_handle) = SequencerCore::start_from_config(config.clone());
let acc1_account_id = config.initial_accounts[0]
.account_id
2025-10-23 17:33:25 +03:00
.clone()
.from_base58()
2025-07-29 14:20:03 +03:00
.unwrap()
.try_into()
.unwrap();
let acc2_account_id = config.initial_accounts[1]
.account_id
2025-10-23 17:33:25 +03:00
.clone()
.from_base58()
2025-07-29 14:20:03 +03:00
.unwrap()
.try_into()
.unwrap();
assert_eq!(
2025-07-29 14:20:03 +03:00
10000,
2025-08-08 16:53:15 -03:00
sequencer
.state
.get_account_by_id(&nssa::AccountId::new(acc1_account_id))
2025-08-08 16:53:15 -03:00
.balance
);
assert_eq!(
2025-07-29 14:20:03 +03:00
20000,
2025-08-08 16:53:15 -03:00
sequencer
.state
.get_account_by_id(&nssa::AccountId::new(acc2_account_id))
2025-08-08 16:53:15 -03:00
.balance
);
2025-01-27 13:43:17 +01:00
}
2025-01-27 13:44:07 +01:00
#[test]
fn test_transaction_pre_check_pass() {
let tx = common::test_utils::produce_dummy_empty_transaction();
2025-11-18 19:31:03 +03:00
let result = transaction_pre_check(parse_unwrap_tx_body_into_nssa_tx(tx));
2025-01-27 13:44:07 +01:00
assert!(result.is_ok());
}
2025-11-18 19:31:03 +03:00
#[tokio::test]
async fn test_transaction_pre_check_native_transfer_valid() {
let (sequencer, _mempool_handle) = common_setup().await;
2025-07-29 14:20:03 +03:00
2025-10-23 17:33:25 +03:00
let acc1 = sequencer.sequencer_config.initial_accounts[0]
.account_id
2025-10-23 17:33:25 +03:00
.clone()
.from_base58()
2025-07-29 14:20:03 +03:00
.unwrap()
.try_into()
.unwrap();
2025-10-23 17:33:25 +03:00
let acc2 = sequencer.sequencer_config.initial_accounts[1]
.account_id
2025-10-23 17:33:25 +03:00
.clone()
.from_base58()
2025-07-29 14:20:03 +03:00
.unwrap()
.try_into()
.unwrap();
let sign_key1 = create_signing_key_for_account1();
2025-08-09 20:25:58 -03:00
let tx = common::test_utils::create_transaction_native_token_transfer(
2025-08-05 14:59:20 +03:00
acc1, 0, acc2, 10, sign_key1,
);
2025-11-18 19:31:03 +03:00
let result = transaction_pre_check(parse_unwrap_tx_body_into_nssa_tx(tx));
2025-07-29 14:20:03 +03:00
assert!(result.is_ok());
}
2025-11-18 19:31:03 +03:00
#[tokio::test]
async fn test_transaction_pre_check_native_transfer_other_signature() {
let (mut sequencer, _mempool_handle) = common_setup().await;
2025-07-29 14:20:03 +03:00
2025-10-23 17:33:25 +03:00
let acc1 = sequencer.sequencer_config.initial_accounts[0]
.account_id
2025-10-23 17:33:25 +03:00
.clone()
.from_base58()
2025-07-29 14:20:03 +03:00
.unwrap()
.try_into()
.unwrap();
2025-10-23 17:33:25 +03:00
let acc2 = sequencer.sequencer_config.initial_accounts[1]
.account_id
2025-10-23 17:33:25 +03:00
.clone()
.from_base58()
2025-07-29 14:20:03 +03:00
.unwrap()
.try_into()
.unwrap();
let sign_key2 = create_signing_key_for_account2();
2025-08-09 20:25:58 -03:00
let tx = common::test_utils::create_transaction_native_token_transfer(
2025-08-05 14:59:20 +03:00
acc1, 0, acc2, 10, sign_key2,
);
2025-08-09 19:05:49 -03:00
// Signature is valid, stateless check pass
2025-11-18 19:31:03 +03:00
let tx = transaction_pre_check(parse_unwrap_tx_body_into_nssa_tx(tx)).unwrap();
2025-07-29 14:20:03 +03:00
2025-08-09 19:05:49 -03:00
// Signature is not from sender. Execution fails
2025-08-08 16:53:15 -03:00
let result = sequencer.execute_check_transaction_on_state(tx);
2025-08-07 15:19:06 -03:00
2025-08-09 20:25:58 -03:00
assert!(matches!(
result,
Err(nssa::error::NssaError::ProgramExecutionFailed(_))
));
2025-07-29 14:20:03 +03:00
}
2025-11-18 19:31:03 +03:00
#[tokio::test]
async fn test_transaction_pre_check_native_transfer_sent_too_much() {
let (mut sequencer, _mempool_handle) = common_setup().await;
2025-07-29 14:20:03 +03:00
2025-10-23 17:33:25 +03:00
let acc1 = sequencer.sequencer_config.initial_accounts[0]
.account_id
2025-10-23 17:33:25 +03:00
.clone()
.from_base58()
2025-07-29 14:20:03 +03:00
.unwrap()
.try_into()
.unwrap();
2025-10-23 17:33:25 +03:00
let acc2 = sequencer.sequencer_config.initial_accounts[1]
.account_id
2025-10-23 17:33:25 +03:00
.clone()
.from_base58()
2025-07-29 14:20:03 +03:00
.unwrap()
.try_into()
.unwrap();
let sign_key1 = create_signing_key_for_account1();
2025-08-09 20:25:58 -03:00
let tx = common::test_utils::create_transaction_native_token_transfer(
2025-08-05 14:59:20 +03:00
acc1, 0, acc2, 10000000, sign_key1,
);
2025-08-11 08:55:08 +03:00
2025-11-18 19:31:03 +03:00
let result = transaction_pre_check(parse_unwrap_tx_body_into_nssa_tx(tx));
2025-07-29 14:20:03 +03:00
2025-11-18 19:31:03 +03:00
// Passed pre-check
2025-07-29 15:04:38 +03:00
assert!(result.is_ok());
2025-08-08 16:53:15 -03:00
let result = sequencer.execute_check_transaction_on_state(result.unwrap());
2025-07-29 14:20:03 +03:00
let is_failed_at_balance_mismatch = matches!(
result.err().unwrap(),
2025-08-09 19:49:07 -03:00
nssa::error::NssaError::ProgramExecutionFailed(_)
2025-07-29 14:20:03 +03:00
);
assert!(is_failed_at_balance_mismatch);
}
2025-11-18 19:31:03 +03:00
#[tokio::test]
async fn test_transaction_execute_native_transfer() {
let (mut sequencer, _mempool_handle) = common_setup().await;
2025-07-29 14:20:03 +03:00
2025-10-23 17:33:25 +03:00
let acc1 = sequencer.sequencer_config.initial_accounts[0]
.account_id
2025-10-23 17:33:25 +03:00
.clone()
.from_base58()
2025-07-29 14:20:03 +03:00
.unwrap()
.try_into()
.unwrap();
2025-10-23 17:33:25 +03:00
let acc2 = sequencer.sequencer_config.initial_accounts[1]
.account_id
2025-10-23 17:33:25 +03:00
.clone()
.from_base58()
2025-07-29 14:20:03 +03:00
.unwrap()
.try_into()
.unwrap();
let sign_key1 = create_signing_key_for_account1();
2025-08-09 20:25:58 -03:00
let tx = common::test_utils::create_transaction_native_token_transfer(
2025-08-05 14:59:20 +03:00
acc1, 0, acc2, 100, sign_key1,
);
2025-07-29 14:20:03 +03:00
2025-09-03 10:29:51 +03:00
sequencer
.execute_check_transaction_on_state(parse_unwrap_tx_body_into_nssa_tx(tx))
.unwrap();
2025-07-29 14:20:03 +03:00
2025-08-08 16:53:15 -03:00
let bal_from = sequencer
.state
.get_account_by_id(&nssa::AccountId::new(acc1))
2025-08-08 16:53:15 -03:00
.balance;
let bal_to = sequencer
.state
.get_account_by_id(&nssa::AccountId::new(acc2))
2025-08-08 16:53:15 -03:00
.balance;
2025-07-29 14:20:03 +03:00
assert_eq!(bal_from, 9900);
assert_eq!(bal_to, 20100);
}
2025-11-18 19:31:03 +03:00
#[tokio::test]
async fn test_push_tx_into_mempool_blocks_until_mempool_is_full() {
let config = SequencerConfig {
2025-10-23 16:23:47 -03:00
mempool_max_size: 1,
..setup_sequencer_config()
};
2025-11-18 19:31:03 +03:00
let (mut sequencer, mempool_handle) = common_setup_with_config(config).await;
let tx = common::test_utils::produce_dummy_empty_transaction();
// Fill the mempool
2025-11-18 19:31:03 +03:00
mempool_handle.push(tx.clone()).await.unwrap();
2025-11-18 19:31:03 +03:00
// Check that pushing another transaction will block
let mut push_fut = pin!(mempool_handle.push(tx.clone()));
let poll = futures::poll!(push_fut.as_mut());
assert!(poll.is_pending());
2025-11-18 19:31:03 +03:00
// Empty the mempool by producing a block
sequencer
.produce_new_block_with_mempool_transactions()
.unwrap();
2025-11-18 19:31:03 +03:00
// Resolve the pending push
assert!(push_fut.await.is_ok());
}
2025-01-27 13:43:17 +01:00
2025-11-18 19:31:03 +03:00
#[tokio::test]
async fn test_produce_new_block_with_mempool_transactions() {
let (mut sequencer, mempool_handle) = common_setup().await;
2025-07-28 15:21:35 -03:00
let genesis_height = sequencer.chain_height;
let tx = common::test_utils::produce_dummy_empty_transaction();
2025-11-18 19:31:03 +03:00
mempool_handle.push(tx).await.unwrap();
let block_id = sequencer.produce_new_block_with_mempool_transactions();
assert!(block_id.is_ok());
2025-07-28 15:21:35 -03:00
assert_eq!(block_id.unwrap(), genesis_height + 1);
}
2025-11-18 19:31:03 +03:00
#[tokio::test]
async fn test_replay_transactions_are_rejected_in_the_same_block() {
let (mut sequencer, mempool_handle) = common_setup().await;
2025-07-29 15:41:57 -03:00
2025-10-23 17:33:25 +03:00
let acc1 = sequencer.sequencer_config.initial_accounts[0]
.account_id
2025-10-23 17:33:25 +03:00
.clone()
.from_base58()
2025-07-29 15:41:57 -03:00
.unwrap()
.try_into()
.unwrap();
2025-10-23 17:33:25 +03:00
let acc2 = sequencer.sequencer_config.initial_accounts[1]
.account_id
2025-10-23 17:33:25 +03:00
.clone()
.from_base58()
2025-07-29 15:41:57 -03:00
.unwrap()
.try_into()
.unwrap();
let sign_key1 = create_signing_key_for_account1();
2025-08-09 20:25:58 -03:00
let tx = common::test_utils::create_transaction_native_token_transfer(
2025-08-05 14:59:20 +03:00
acc1, 0, acc2, 100, sign_key1,
);
2025-07-29 15:41:57 -03:00
2025-08-08 16:53:15 -03:00
let tx_original = tx.clone();
let tx_replay = tx.clone();
2025-07-29 15:41:57 -03:00
// Pushing two copies of the same tx to the mempool
2025-11-18 19:31:03 +03:00
mempool_handle.push(tx_original).await.unwrap();
mempool_handle.push(tx_replay).await.unwrap();
2025-07-29 15:41:57 -03:00
// Create block
let current_height = sequencer
.produce_new_block_with_mempool_transactions()
.unwrap();
let block = sequencer
.block_store
.get_block_at_id(current_height)
.unwrap();
// Only one should be included in the block
2025-09-03 10:29:51 +03:00
assert_eq!(block.body.transactions, vec![tx.clone()]);
2025-07-29 15:41:57 -03:00
}
2025-11-18 19:31:03 +03:00
#[tokio::test]
async fn test_replay_transactions_are_rejected_in_different_blocks() {
let (mut sequencer, mempool_handle) = common_setup().await;
2025-10-23 17:33:25 +03:00
let acc1 = sequencer.sequencer_config.initial_accounts[0]
.account_id
2025-10-23 17:33:25 +03:00
.clone()
.from_base58()
.unwrap()
.try_into()
.unwrap();
2025-10-23 17:33:25 +03:00
let acc2 = sequencer.sequencer_config.initial_accounts[1]
.account_id
2025-10-23 17:33:25 +03:00
.clone()
.from_base58()
.unwrap()
.try_into()
.unwrap();
let sign_key1 = create_signing_key_for_account1();
2025-08-09 20:25:58 -03:00
let tx = common::test_utils::create_transaction_native_token_transfer(
2025-08-05 14:59:20 +03:00
acc1, 0, acc2, 100, sign_key1,
);
2025-07-28 15:21:35 -03:00
// The transaction should be included the first time
2025-11-18 19:31:03 +03:00
mempool_handle.push(tx.clone()).await.unwrap();
2025-07-28 15:21:35 -03:00
let current_height = sequencer
.produce_new_block_with_mempool_transactions()
.unwrap();
let block = sequencer
.block_store
.get_block_at_id(current_height)
.unwrap();
2025-09-03 10:29:51 +03:00
assert_eq!(block.body.transactions, vec![tx.clone()]);
2025-07-28 15:21:35 -03:00
// Add same transaction should fail
2025-11-18 19:31:03 +03:00
mempool_handle.push(tx.clone()).await.unwrap();
2025-07-28 15:21:35 -03:00
let current_height = sequencer
.produce_new_block_with_mempool_transactions()
.unwrap();
let block = sequencer
.block_store
.get_block_at_id(current_height)
.unwrap();
2025-09-03 10:29:51 +03:00
assert!(block.body.transactions.is_empty());
}
2025-11-18 19:31:03 +03:00
#[tokio::test]
async fn test_restart_from_storage() {
let config = setup_sequencer_config();
let acc1_account_id: nssa::AccountId =
config.initial_accounts[0].account_id.parse().unwrap();
let acc2_account_id: nssa::AccountId =
config.initial_accounts[1].account_id.parse().unwrap();
let balance_to_move = 13;
// In the following code block a transaction will be processed that moves `balance_to_move`
// from `acc_1` to `acc_2`. The block created with that transaction will be kept stored in
// the temporary directory for the block storage of this test.
{
2025-11-18 19:31:03 +03:00
let (mut sequencer, mempool_handle) = SequencerCore::start_from_config(config.clone());
let signing_key = PrivateKey::try_new([1; 32]).unwrap();
let tx = common::test_utils::create_transaction_native_token_transfer(
*acc1_account_id.value(),
0,
*acc2_account_id.value(),
balance_to_move,
signing_key,
);
2025-11-18 19:31:03 +03:00
mempool_handle.push(tx.clone()).await.unwrap();
let current_height = sequencer
.produce_new_block_with_mempool_transactions()
.unwrap();
let block = sequencer
.block_store
.get_block_at_id(current_height)
.unwrap();
assert_eq!(block.body.transactions, vec![tx.clone()]);
}
// Instantiating a new sequencer from the same config. This should load the existing block
// with the above transaction and update the state to reflect that.
2025-11-18 19:31:03 +03:00
let (sequencer, _mempool_handle) = SequencerCore::start_from_config(config.clone());
let balance_acc_1 = sequencer.state.get_account_by_id(&acc1_account_id).balance;
let balance_acc_2 = sequencer.state.get_account_by_id(&acc2_account_id).balance;
// Balances should be consistent with the stored block
assert_eq!(
balance_acc_1,
config.initial_accounts[0].balance - balance_to_move
);
assert_eq!(
balance_acc_2,
config.initial_accounts[1].balance + balance_to_move
);
}
2025-01-27 13:51:27 +01:00
}