Skip to main content

ethexe_common/
malachite.rs

1// Copyright (C) Gear Technologies Inc.
2// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
3
4//! Application-level block shape produced by the Malachite sequencer
5//! and consumed by the ethexe executor.
6//!
7//! [`Transactions`] is the application's `BlockPayload` — an ordered
8//! list of [`Transaction`]s. Block-level identity (parent linkage,
9//! height) lives in [`crate::db::CompactMb`], indexed by the
10//! `ethexe_malachite_core::Block` envelope hash. The transaction list
11//! itself is stored in the content-addressed half of the ethexe db
12//! and referenced by `CompactMb::transactions_hash`.
13//!
14//! These types live in `ethexe-common` (rather than inside
15//! `ethexe-malachite`) so `ethexe-processor` can accept them without
16//! depending on the consensus layer.
17
18use crate::injected::SignedInjectedTransaction;
19use alloc::vec::Vec;
20use derive_more::{Deref, DerefMut, IntoIterator};
21use gprimitives::H256;
22use parity_scale_codec::{Decode, Encode};
23use scale_info::TypeInfo;
24
25#[cfg(feature = "std")]
26use serde::{Deserialize, Serialize};
27
28/// A single transaction in the malachite block.
29#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, TypeInfo)]
30#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
31pub enum Transaction {
32    /// Pin executor's view to a quarantine-passed Ethereum block.
33    AdvanceTillEthereumBlock { block_hash: H256 },
34
35    /// Progress scheduled tasks (mailbox/waitlist/reservation cleanup).
36    ProgressTasks { limits: ProgressTasksLimits },
37
38    /// Drain message queues within `gas_allowance`; producer emits last.
39    ProcessQueues { limits: ProcessQueuesLimits },
40
41    /// User-submitted transaction from the mempool.
42    Injected(SignedInjectedTransaction),
43}
44
45/// Placeholder; shape firms up once executor plumbing lands.
46#[derive(Clone, Debug, Default, PartialEq, Eq, Encode, Decode, TypeInfo)]
47#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
48pub struct ProgressTasksLimits {}
49
50/// Per-MB execution budget, carried on the wire.
51#[derive(Clone, Debug, Default, PartialEq, Eq, Encode, Decode, TypeInfo)]
52#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
53pub struct ProcessQueuesLimits {
54    pub gas_allowance: u64,
55}
56
57impl Transaction {
58    /// Short human-readable tag, used in logs and debug dumps.
59    pub fn tag(&self) -> &'static str {
60        match self {
61            Self::AdvanceTillEthereumBlock { .. } => "advance-eth-block",
62            Self::ProgressTasks { .. } => "progress-tasks",
63            Self::ProcessQueues { .. } => "process-queues",
64            Self::Injected(_) => "injected",
65        }
66    }
67}
68
69/// `BlockPayload`: ordered transactions; CAS key = Blake2b-256 of the SCALE-encoded list.
70#[derive(
71    Clone, Debug, Default, PartialEq, Eq, Encode, Decode, TypeInfo, Deref, DerefMut, IntoIterator,
72)]
73#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
74pub struct Transactions(pub Vec<Transaction>);
75
76impl Transactions {
77    pub fn new(transactions: Vec<Transaction>) -> Self {
78        Self(transactions)
79    }
80
81    /// CAS key: Blake2b-256 over the SCALE-encoded list.
82    pub fn hash(&self) -> H256 {
83        gear_core::utils::hash(&self.encode()).into()
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    fn empty_txs() -> Transactions {
92        Transactions::new(alloc::vec![
93            Transaction::ProgressTasks {
94                limits: ProgressTasksLimits::default(),
95            },
96            Transaction::ProcessQueues {
97                limits: ProcessQueuesLimits::default(),
98            },
99        ])
100    }
101
102    #[test]
103    fn hash_is_deterministic_for_same_content() {
104        let a = empty_txs();
105        let b = empty_txs();
106        assert_eq!(a.hash(), b.hash());
107    }
108
109    #[test]
110    fn hash_changes_when_transactions_change() {
111        let mut a = empty_txs();
112        let b = empty_txs();
113        a.push(Transaction::AdvanceTillEthereumBlock {
114            block_hash: H256::from_low_u64_be(0xEB),
115        });
116        assert_ne!(a.hash(), b.hash());
117    }
118
119    #[test]
120    fn transaction_tag_distinguishes_variants() {
121        let advance = Transaction::AdvanceTillEthereumBlock {
122            block_hash: H256::zero(),
123        };
124        let progress = Transaction::ProgressTasks {
125            limits: ProgressTasksLimits::default(),
126        };
127        let queues = Transaction::ProcessQueues {
128            limits: ProcessQueuesLimits::default(),
129        };
130        assert_eq!(advance.tag(), "advance-eth-block");
131        assert_eq!(progress.tag(), "progress-tasks");
132        assert_eq!(queues.tag(), "process-queues");
133    }
134
135    #[test]
136    fn scale_round_trip_preserves_hash() {
137        // `Transactions` is SCALE-encoded for both the CAS payload
138        // and the consensus wire payload — make sure round-trip is
139        // hash-preserving so peers and the executor agree on the
140        // CAS key.
141        use parity_scale_codec::Decode;
142
143        let original = Transactions::new(alloc::vec![Transaction::AdvanceTillEthereumBlock {
144            block_hash: H256::from_low_u64_be(0xEB)
145        }]);
146        let encoded = original.encode();
147        let decoded = Transactions::decode(&mut encoded.as_slice()).expect("decode");
148        assert_eq!(original, decoded);
149        assert_eq!(original.hash(), decoded.hash());
150    }
151}