mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-05-17 21:49:35 +00:00
refactor so that indexer checks clock constraints
This commit is contained in:
parent
6a467da3b1
commit
b525447e2d
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,6 +1,6 @@
|
|||||||
use borsh::{BorshDeserialize, BorshSerialize};
|
use borsh::{BorshDeserialize, BorshSerialize};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use nssa::{AccountId, V03State};
|
use nssa::{AccountId, V03State, ValidatedStateDiff};
|
||||||
use nssa_core::{BlockId, Timestamp};
|
use nssa_core::{BlockId, Timestamp};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -83,21 +83,53 @@ impl NSSATransaction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Validates the transaction against the current state and returns the resulting diff
|
||||||
|
/// without applying it. Rejects transactions that modify clock system accounts.
|
||||||
|
pub fn validate_on_state(
|
||||||
|
&self,
|
||||||
|
state: &V03State,
|
||||||
|
block_id: BlockId,
|
||||||
|
timestamp: Timestamp,
|
||||||
|
) -> Result<ValidatedStateDiff, nssa::error::NssaError> {
|
||||||
|
let diff = match self {
|
||||||
|
Self::Public(tx) => {
|
||||||
|
ValidatedStateDiff::from_public_transaction(tx, state, block_id, timestamp)
|
||||||
|
}
|
||||||
|
Self::PrivacyPreserving(tx) => ValidatedStateDiff::from_privacy_preserving_transaction(
|
||||||
|
tx, state, block_id, timestamp,
|
||||||
|
),
|
||||||
|
Self::ProgramDeployment(tx) => {
|
||||||
|
ValidatedStateDiff::from_program_deployment_transaction(tx, state)
|
||||||
|
}
|
||||||
|
}?;
|
||||||
|
|
||||||
|
let public_diff = diff.public_diff();
|
||||||
|
let touches_clock = nssa::CLOCK_PROGRAM_ACCOUNT_IDS.iter().any(|id| {
|
||||||
|
public_diff
|
||||||
|
.get(id)
|
||||||
|
.is_some_and(|post| *post != state.get_account_by_id(*id))
|
||||||
|
});
|
||||||
|
if touches_clock {
|
||||||
|
return Err(nssa::error::NssaError::InvalidInput(
|
||||||
|
"Transaction modifies system clock accounts".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(diff)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates the transaction against the current state, rejects modifications to clock
|
||||||
|
/// system accounts, and applies the resulting diff to the state.
|
||||||
pub fn execute_check_on_state(
|
pub fn execute_check_on_state(
|
||||||
self,
|
self,
|
||||||
state: &mut V03State,
|
state: &mut V03State,
|
||||||
block_id: BlockId,
|
block_id: BlockId,
|
||||||
timestamp: Timestamp,
|
timestamp: Timestamp,
|
||||||
) -> Result<Self, nssa::error::NssaError> {
|
) -> Result<Self, nssa::error::NssaError> {
|
||||||
match &self {
|
let diff = self
|
||||||
Self::Public(tx) => state.transition_from_public_transaction(tx, block_id, timestamp),
|
.validate_on_state(state, block_id, timestamp)
|
||||||
Self::PrivacyPreserving(tx) => {
|
.inspect_err(|err| warn!("Error at transition {err:#?}"))?;
|
||||||
state.transition_from_privacy_preserving_transaction(tx, block_id, timestamp)
|
state.apply_state_diff(diff);
|
||||||
}
|
|
||||||
Self::ProgramDeployment(tx) => state.transition_from_program_deployment_transaction(tx),
|
|
||||||
}
|
|
||||||
.inspect_err(|err| warn!("Error at transition {err:#?}"))?;
|
|
||||||
|
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -122,7 +122,18 @@ impl IndexerStore {
|
|||||||
{
|
{
|
||||||
let mut state_guard = self.current_state.write().await;
|
let mut state_guard = self.current_state.write().await;
|
||||||
|
|
||||||
for transaction in &block.body.transactions {
|
let (clock_tx, user_txs) = block
|
||||||
|
.body
|
||||||
|
.transactions
|
||||||
|
.split_last()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Block has no transactions"))?;
|
||||||
|
|
||||||
|
anyhow::ensure!(
|
||||||
|
*clock_tx == NSSATransaction::clock_invocation(block.header.timestamp),
|
||||||
|
"Last transaction in block must be the clock invocation for the block timestamp"
|
||||||
|
);
|
||||||
|
|
||||||
|
for transaction in user_txs {
|
||||||
transaction
|
transaction
|
||||||
.clone()
|
.clone()
|
||||||
.transaction_stateless_check()?
|
.transaction_stateless_check()?
|
||||||
@ -132,6 +143,16 @@ impl IndexerStore {
|
|||||||
block.header.timestamp,
|
block.header.timestamp,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply the clock invocation directly (it is expected to modify clock accounts).
|
||||||
|
let NSSATransaction::Public(clock_public_tx) = clock_tx else {
|
||||||
|
anyhow::bail!("Clock invocation must be a public transaction");
|
||||||
|
};
|
||||||
|
state_guard.transition_from_public_transaction(
|
||||||
|
clock_public_tx,
|
||||||
|
block.header.block_id,
|
||||||
|
block.header.timestamp,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToDo: Currently we are fetching only finalized blocks
|
// ToDo: Currently we are fetching only finalized blocks
|
||||||
|
|||||||
11
programs/clock/core/Cargo.toml
Normal file
11
programs/clock/core/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "clock_core"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
license = { workspace = true }
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
nssa_core.workspace = true
|
||||||
49
programs/clock/core/src/lib.rs
Normal file
49
programs/clock/core/src/lib.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
//! Core data structures and constants for the Clock Program.
|
||||||
|
|
||||||
|
use nssa_core::{Timestamp, account::AccountId};
|
||||||
|
|
||||||
|
/// The instruction type for the Clock Program. The sequencer passes the current block timestamp.
|
||||||
|
pub type Instruction = Timestamp;
|
||||||
|
|
||||||
|
pub const CLOCK_01_PROGRAM_ACCOUNT_ID: AccountId =
|
||||||
|
AccountId::new(*b"/LEZ/ClockProgramAccount/0000001");
|
||||||
|
|
||||||
|
pub const CLOCK_10_PROGRAM_ACCOUNT_ID: AccountId =
|
||||||
|
AccountId::new(*b"/LEZ/ClockProgramAccount/0000010");
|
||||||
|
|
||||||
|
pub const CLOCK_50_PROGRAM_ACCOUNT_ID: AccountId =
|
||||||
|
AccountId::new(*b"/LEZ/ClockProgramAccount/0000050");
|
||||||
|
|
||||||
|
/// All clock program account ID int the order expected by the clock program
|
||||||
|
pub const CLOCK_PROGRAM_ACCOUNT_IDS: [AccountId; 3] = [
|
||||||
|
CLOCK_01_PROGRAM_ACCOUNT_ID,
|
||||||
|
CLOCK_10_PROGRAM_ACCOUNT_ID,
|
||||||
|
CLOCK_50_PROGRAM_ACCOUNT_ID,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// The data stored in a clock account: `[block_id: u64 LE | timestamp: u64 LE]`.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct ClockAccountData {
|
||||||
|
pub block_id: u64,
|
||||||
|
pub timestamp: Timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClockAccountData {
|
||||||
|
#[must_use]
|
||||||
|
pub fn to_bytes(self) -> [u8; 16] {
|
||||||
|
let mut data = [0_u8; 16];
|
||||||
|
data[..8].copy_from_slice(&self.block_id.to_le_bytes());
|
||||||
|
data[8..].copy_from_slice(&self.timestamp.to_le_bytes());
|
||||||
|
data
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn from_bytes(bytes: &[u8; 16]) -> Self {
|
||||||
|
let block_id = u64::from_le_bytes(bytes[..8].try_into().unwrap());
|
||||||
|
let timestamp = u64::from_le_bytes(bytes[8..].try_into().unwrap());
|
||||||
|
Self {
|
||||||
|
block_id,
|
||||||
|
timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,8 +15,7 @@ use logos_blockchain_key_management_system_service::keys::{ED25519_SECRET_KEY_SI
|
|||||||
use mempool::{MemPool, MemPoolHandle};
|
use mempool::{MemPool, MemPoolHandle};
|
||||||
#[cfg(feature = "mock")]
|
#[cfg(feature = "mock")]
|
||||||
pub use mock::SequencerCoreWithMockClients;
|
pub use mock::SequencerCoreWithMockClients;
|
||||||
use nssa::{V03State, ValidatedStateDiff};
|
use nssa::V03State;
|
||||||
use nssa_core::{BlockId, Timestamp};
|
|
||||||
pub use storage::error::DbError;
|
pub use storage::error::DbError;
|
||||||
use testnet_initial_state::initial_state;
|
use testnet_initial_state::initial_state;
|
||||||
|
|
||||||
@ -164,28 +163,6 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
|
|||||||
(sequencer_core, mempool_handle)
|
(sequencer_core, mempool_handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute_check_transaction_on_state(
|
|
||||||
&mut self,
|
|
||||||
tx: NSSATransaction,
|
|
||||||
block_id: BlockId,
|
|
||||||
timestamp: Timestamp,
|
|
||||||
) -> Result<NSSATransaction, nssa::error::NssaError> {
|
|
||||||
match &tx {
|
|
||||||
NSSATransaction::Public(tx) => self
|
|
||||||
.state
|
|
||||||
.transition_from_public_transaction(tx, block_id, timestamp),
|
|
||||||
NSSATransaction::PrivacyPreserving(tx) => self
|
|
||||||
.state
|
|
||||||
.transition_from_privacy_preserving_transaction(tx, block_id, timestamp),
|
|
||||||
NSSATransaction::ProgramDeployment(tx) => self
|
|
||||||
.state
|
|
||||||
.transition_from_program_deployment_transaction(tx),
|
|
||||||
}
|
|
||||||
.inspect_err(|err| warn!("Error at transition {err:#?}"))?;
|
|
||||||
|
|
||||||
Ok(tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn produce_new_block(&mut self) -> Result<u64> {
|
pub async fn produce_new_block(&mut self) -> Result<u64> {
|
||||||
let (tx, _msg_id) = self
|
let (tx, _msg_id) = self
|
||||||
.produce_new_block_with_mempool_transactions()
|
.produce_new_block_with_mempool_transactions()
|
||||||
@ -204,30 +181,6 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
|
|||||||
Ok(self.chain_height)
|
Ok(self.chain_height)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_transaction_and_produce_state_diff(
|
|
||||||
&self,
|
|
||||||
transaction: &NSSATransaction,
|
|
||||||
block_id: BlockId,
|
|
||||||
timestamp: Timestamp,
|
|
||||||
) -> Result<ValidatedStateDiff, nssa::error::NssaError> {
|
|
||||||
match transaction {
|
|
||||||
NSSATransaction::Public(tx) => {
|
|
||||||
ValidatedStateDiff::from_public_transaction(tx, &self.state, block_id, timestamp)
|
|
||||||
}
|
|
||||||
NSSATransaction::PrivacyPreserving(tx) => {
|
|
||||||
ValidatedStateDiff::from_privacy_preserving_transaction(
|
|
||||||
tx,
|
|
||||||
&self.state,
|
|
||||||
block_id,
|
|
||||||
timestamp,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
NSSATransaction::ProgramDeployment(tx) => {
|
|
||||||
ValidatedStateDiff::from_program_deployment_transaction(tx, &self.state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Produces new block from transactions in mempool and packs it into a `SignedMantleTx`.
|
/// Produces new block from transactions in mempool and packs it into a `SignedMantleTx`.
|
||||||
pub fn produce_new_block_with_mempool_transactions(
|
pub fn produce_new_block_with_mempool_transactions(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -249,33 +202,15 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
|
|||||||
let new_block_timestamp = u64::try_from(chrono::Utc::now().timestamp_millis())
|
let new_block_timestamp = u64::try_from(chrono::Utc::now().timestamp_millis())
|
||||||
.expect("Timestamp must be positive");
|
.expect("Timestamp must be positive");
|
||||||
|
|
||||||
// Note: the clock accounts are only modified by the clock program, which is invoked
|
|
||||||
// exclusively by the sequencer as the mandatory last transaction in each block. All user
|
|
||||||
// transactions are processed before that invocation, so this snapshot is always current
|
|
||||||
// and constant for all transactions in the block
|
|
||||||
let clock_accounts_pre =
|
|
||||||
nssa::CLOCK_PROGRAM_ACCOUNT_IDS.map(|id| (id, self.state.get_account_by_id(id)));
|
|
||||||
|
|
||||||
while let Some(tx) = self.mempool.pop() {
|
while let Some(tx) = self.mempool.pop() {
|
||||||
let tx_hash = tx.hash();
|
let tx_hash = tx.hash();
|
||||||
|
|
||||||
let validated_diff = match self.validate_transaction_and_produce_state_diff(
|
let validated_diff = match tx.validate_on_state(
|
||||||
&tx,
|
&self.state,
|
||||||
new_block_height,
|
new_block_height,
|
||||||
new_block_timestamp,
|
new_block_timestamp,
|
||||||
) {
|
) {
|
||||||
Ok(diff) => {
|
Ok(diff) => diff,
|
||||||
let touches_system = clock_accounts_pre.iter().any(|(id, pre)| {
|
|
||||||
diff.public_diff().get(id).is_some_and(|post| post != pre)
|
|
||||||
});
|
|
||||||
if touches_system {
|
|
||||||
warn!(
|
|
||||||
"Dropping transaction from mempool: user transactions may not modify the system clock account"
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
diff
|
|
||||||
}
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!(
|
error!(
|
||||||
"Transaction with hash {tx_hash} failed execution check with error: {err:#?}, skipping it",
|
"Transaction with hash {tx_hash} failed execution check with error: {err:#?}, skipping it",
|
||||||
@ -319,13 +254,17 @@ impl<BC: BlockSettlementClientTrait, IC: IndexerClientTrait> SequencerCore<BC, I
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Append the Block Context Program invocation as the mandatory last transaction.
|
// Append the Block Context Program invocation as the mandatory last transaction.
|
||||||
let clock_nssa_tx = self
|
let clock_nssa_tx = NSSATransaction::clock_invocation(new_block_timestamp);
|
||||||
.execute_check_transaction_on_state(
|
self.state
|
||||||
NSSATransaction::clock_invocation(new_block_timestamp),
|
.transition_from_public_transaction(
|
||||||
|
match &clock_nssa_tx {
|
||||||
|
NSSATransaction::Public(tx) => tx,
|
||||||
|
_ => unreachable!("clock_invocation always returns Public"),
|
||||||
|
},
|
||||||
new_block_height,
|
new_block_height,
|
||||||
new_block_timestamp,
|
new_block_timestamp,
|
||||||
)
|
)
|
||||||
.context("Clock transaction failed \u{2014} aborting block production")?;
|
.context("Clock transaction failed. Aborting block production.")?;
|
||||||
valid_transactions.push(clock_nssa_tx);
|
valid_transactions.push(clock_nssa_tx);
|
||||||
|
|
||||||
let hashable_data = HashableBlockData {
|
let hashable_data = HashableBlockData {
|
||||||
@ -454,8 +393,6 @@ mod tests {
|
|||||||
use common::{test_utils::sequencer_sign_key_for_testing, transaction::NSSATransaction};
|
use common::{test_utils::sequencer_sign_key_for_testing, transaction::NSSATransaction};
|
||||||
use logos_blockchain_core::mantle::ops::channel::ChannelId;
|
use logos_blockchain_core::mantle::ops::channel::ChannelId;
|
||||||
use mempool::MemPoolHandle;
|
use mempool::MemPoolHandle;
|
||||||
|
|
||||||
|
|
||||||
use testnet_initial_state::{initial_accounts, initial_pub_accounts_private_keys};
|
use testnet_initial_state::{initial_accounts, initial_pub_accounts_private_keys};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -582,7 +519,7 @@ mod tests {
|
|||||||
let tx = tx.transaction_stateless_check().unwrap();
|
let tx = tx.transaction_stateless_check().unwrap();
|
||||||
|
|
||||||
// Signature is not from sender. Execution fails
|
// Signature is not from sender. Execution fails
|
||||||
let result = sequencer.execute_check_transaction_on_state(tx, 0, 0);
|
let result = tx.execute_check_on_state(&mut sequencer.state, 0, 0);
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
result,
|
result,
|
||||||
@ -608,7 +545,9 @@ mod tests {
|
|||||||
// Passed pre-check
|
// Passed pre-check
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
|
||||||
let result = sequencer.execute_check_transaction_on_state(result.unwrap(), 0, 0);
|
let result = result
|
||||||
|
.unwrap()
|
||||||
|
.execute_check_on_state(&mut sequencer.state, 0, 0);
|
||||||
let is_failed_at_balance_mismatch = matches!(
|
let is_failed_at_balance_mismatch = matches!(
|
||||||
result.err().unwrap(),
|
result.err().unwrap(),
|
||||||
nssa::error::NssaError::ProgramExecutionFailed(_)
|
nssa::error::NssaError::ProgramExecutionFailed(_)
|
||||||
@ -630,8 +569,7 @@ mod tests {
|
|||||||
acc1, 0, acc2, 100, &sign_key1,
|
acc1, 0, acc2, 100, &sign_key1,
|
||||||
);
|
);
|
||||||
|
|
||||||
sequencer
|
tx.execute_check_on_state(&mut sequencer.state, 0, 0)
|
||||||
.execute_check_transaction_on_state(tx, 0, 0)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let bal_from = sequencer.state.get_account_by_id(acc1).balance;
|
let bal_from = sequencer.state.get_account_by_id(acc1).balance;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user