mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-04-30 03:01:58 -04:00
feat: use custom tx in custom-node (#16054)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
committed by
GitHub
parent
1dcb3dcfc0
commit
da95e5745e
27
examples/custom-node/src/consensus.rs
Normal file
27
examples/custom-node/src/consensus.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use reth_node_builder::{
|
||||
components::ConsensusBuilder, BuilderContext, FullNodeTypes, NodePrimitives, NodeTypes,
|
||||
};
|
||||
use reth_op::DepositReceipt;
|
||||
use reth_optimism_consensus::OpBeaconConsensus;
|
||||
use reth_optimism_forks::OpHardforks;
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct CustomConsensusBuilder;
|
||||
|
||||
impl<Node> ConsensusBuilder<Node> for CustomConsensusBuilder
|
||||
where
|
||||
Node: FullNodeTypes<
|
||||
Types: NodeTypes<
|
||||
ChainSpec: OpHardforks,
|
||||
Primitives: NodePrimitives<Receipt: DepositReceipt>,
|
||||
>,
|
||||
>,
|
||||
{
|
||||
type Consensus = Arc<OpBeaconConsensus<<Node::Types as NodeTypes>::ChainSpec>>;
|
||||
|
||||
async fn build_consensus(self, ctx: &BuilderContext<Node>) -> eyre::Result<Self::Consensus> {
|
||||
Ok(Arc::new(OpBeaconConsensus::new(ctx.chain_spec())))
|
||||
}
|
||||
}
|
||||
@@ -8,20 +8,21 @@
|
||||
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
|
||||
|
||||
use chainspec::CustomChainSpec;
|
||||
use consensus::CustomConsensusBuilder;
|
||||
use engine::CustomPayloadTypes;
|
||||
use pool::CustomPoolBuilder;
|
||||
use primitives::CustomNodePrimitives;
|
||||
use reth_ethereum::node::api::{FullNodeTypes, NodeTypes};
|
||||
use reth_node_builder::{components::ComponentsBuilder, Node, NodeComponentsBuilder};
|
||||
use reth_op::node::{
|
||||
node::{OpConsensusBuilder, OpPoolBuilder, OpStorage},
|
||||
OpNode,
|
||||
};
|
||||
use reth_op::node::{node::OpStorage, OpNode};
|
||||
|
||||
pub mod chainspec;
|
||||
pub mod consensus;
|
||||
pub mod engine;
|
||||
pub mod engine_api;
|
||||
pub mod evm;
|
||||
pub mod network;
|
||||
pub mod pool;
|
||||
pub mod primitives;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -45,17 +46,19 @@ where
|
||||
Storage = OpStorage,
|
||||
>,
|
||||
>,
|
||||
ComponentsBuilder<N, OpPoolBuilder, (), (), (), OpConsensusBuilder>: NodeComponentsBuilder<N>,
|
||||
ComponentsBuilder<N, CustomPoolBuilder, (), (), (), CustomConsensusBuilder>:
|
||||
NodeComponentsBuilder<N>,
|
||||
{
|
||||
type ComponentsBuilder = ComponentsBuilder<N, OpPoolBuilder, (), (), (), OpConsensusBuilder>;
|
||||
type ComponentsBuilder =
|
||||
ComponentsBuilder<N, CustomPoolBuilder, (), (), (), CustomConsensusBuilder>;
|
||||
|
||||
type AddOns = ();
|
||||
|
||||
fn components_builder(&self) -> Self::ComponentsBuilder {
|
||||
ComponentsBuilder::default()
|
||||
.node_types::<N>()
|
||||
.pool(OpPoolBuilder::default())
|
||||
.consensus(OpConsensusBuilder::default())
|
||||
.pool(CustomPoolBuilder::default())
|
||||
.consensus(CustomConsensusBuilder)
|
||||
}
|
||||
|
||||
fn add_ons(&self) -> Self::AddOns {}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use crate::{
|
||||
chainspec::CustomChainSpec,
|
||||
primitives::{CustomHeader, CustomNodePrimitives},
|
||||
primitives::{
|
||||
CustomHeader, CustomNodePrimitives, CustomTransactionEnvelope, ExtendedOpTxEnvelope,
|
||||
ExtendedTxEnvelope,
|
||||
},
|
||||
};
|
||||
use alloy_consensus::{Block, BlockBody};
|
||||
use eyre::Result;
|
||||
@@ -12,7 +15,7 @@ use reth_ethereum::{
|
||||
pool::{PoolTransaction, TransactionPool},
|
||||
};
|
||||
use reth_node_builder::{components::NetworkBuilder, BuilderContext};
|
||||
use reth_op::{OpReceipt, OpTransactionSigned};
|
||||
use reth_op::OpReceipt;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[non_exhaustive]
|
||||
@@ -20,10 +23,10 @@ pub struct CustomNetworkPrimitives;
|
||||
|
||||
impl NetworkPrimitives for CustomNetworkPrimitives {
|
||||
type BlockHeader = CustomHeader;
|
||||
type BlockBody = BlockBody<OpTransactionSigned, CustomHeader>;
|
||||
type Block = Block<OpTransactionSigned, CustomHeader>;
|
||||
type BroadcastedTransaction = OpTransactionSigned;
|
||||
type PooledTransaction = OpPooledTransaction;
|
||||
type BlockBody = BlockBody<ExtendedOpTxEnvelope<CustomTransactionEnvelope>, CustomHeader>;
|
||||
type Block = Block<ExtendedOpTxEnvelope<CustomTransactionEnvelope>, CustomHeader>;
|
||||
type BroadcastedTransaction = ExtendedOpTxEnvelope<CustomTransactionEnvelope>;
|
||||
type PooledTransaction = ExtendedTxEnvelope<OpPooledTransaction, CustomTransactionEnvelope>;
|
||||
type Receipt = OpReceipt;
|
||||
}
|
||||
|
||||
@@ -77,7 +80,7 @@ where
|
||||
Pool: TransactionPool<
|
||||
Transaction: PoolTransaction<
|
||||
Consensus = TxTy<Node::Types>,
|
||||
Pooled = OpPooledTransaction,
|
||||
Pooled = ExtendedTxEnvelope<OpPooledTransaction, CustomTransactionEnvelope>,
|
||||
>,
|
||||
> + Unpin
|
||||
+ 'static,
|
||||
|
||||
214
examples/custom-node/src/pool.rs
Normal file
214
examples/custom-node/src/pool.rs
Normal file
@@ -0,0 +1,214 @@
|
||||
// use jsonrpsee::tracing::{debug, info};
|
||||
use op_alloy_consensus::{interop::SafetyLevel, OpTxEnvelope};
|
||||
use reth_chain_state::CanonStateSubscriptions;
|
||||
use reth_node_builder::{
|
||||
components::{PoolBuilder, PoolBuilderConfigOverrides},
|
||||
node::{FullNodeTypes, NodeTypes},
|
||||
BuilderContext, NodePrimitives,
|
||||
};
|
||||
use reth_op::{
|
||||
node::txpool::{
|
||||
conditional::MaybeConditionalTransaction,
|
||||
interop::MaybeInteropTransaction,
|
||||
supervisor::{SupervisorClient, DEFAULT_SUPERVISOR_URL},
|
||||
OpPooledTransaction, OpTransactionPool, OpTransactionValidator,
|
||||
},
|
||||
pool::{
|
||||
blobstore::DiskFileBlobStore, CoinbaseTipOrdering, EthPoolTransaction,
|
||||
TransactionValidationTaskExecutor,
|
||||
},
|
||||
};
|
||||
use reth_optimism_forks::OpHardforks;
|
||||
|
||||
use crate::primitives::{CustomTransactionEnvelope, ExtendedTxEnvelope};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CustomPoolBuilder<
|
||||
T = OpPooledTransaction<ExtendedTxEnvelope<OpTxEnvelope, CustomTransactionEnvelope>>,
|
||||
> {
|
||||
/// Enforced overrides that are applied to the pool config.
|
||||
pub pool_config_overrides: PoolBuilderConfigOverrides,
|
||||
/// Enable transaction conditionals.
|
||||
pub enable_tx_conditional: bool,
|
||||
/// Supervisor client url
|
||||
pub supervisor_http: String,
|
||||
/// Supervisor safety level
|
||||
pub supervisor_safety_level: SafetyLevel,
|
||||
/// Marker for the pooled transaction type.
|
||||
_pd: core::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> Default for CustomPoolBuilder<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
pool_config_overrides: Default::default(),
|
||||
enable_tx_conditional: false,
|
||||
supervisor_http: DEFAULT_SUPERVISOR_URL.to_string(),
|
||||
supervisor_safety_level: SafetyLevel::CrossUnsafe,
|
||||
_pd: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> CustomPoolBuilder<T> {
|
||||
/// Sets the enable_tx_conditional flag on the pool builder.
|
||||
pub const fn with_enable_tx_conditional(mut self, enable_tx_conditional: bool) -> Self {
|
||||
self.enable_tx_conditional = enable_tx_conditional;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [PoolBuilderConfigOverrides] on the pool builder.
|
||||
pub fn with_pool_config_overrides(
|
||||
mut self,
|
||||
pool_config_overrides: PoolBuilderConfigOverrides,
|
||||
) -> Self {
|
||||
self.pool_config_overrides = pool_config_overrides;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the supervisor client
|
||||
pub fn with_supervisor(
|
||||
mut self,
|
||||
supervisor_client: String,
|
||||
supervisor_safety_level: SafetyLevel,
|
||||
) -> Self {
|
||||
self.supervisor_http = supervisor_client;
|
||||
self.supervisor_safety_level = supervisor_safety_level;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Node, T> PoolBuilder<Node> for CustomPoolBuilder<T>
|
||||
where
|
||||
Node: FullNodeTypes<Types: NodeTypes<ChainSpec: OpHardforks>>,
|
||||
<Node::Types as NodeTypes>::Primitives:
|
||||
NodePrimitives<SignedTx = ExtendedTxEnvelope<OpTxEnvelope, CustomTransactionEnvelope>>,
|
||||
T: EthPoolTransaction<Consensus = ExtendedTxEnvelope<OpTxEnvelope, CustomTransactionEnvelope>>
|
||||
+ MaybeConditionalTransaction
|
||||
+ MaybeInteropTransaction,
|
||||
{
|
||||
type Pool = OpTransactionPool<Node::Provider, DiskFileBlobStore, T>;
|
||||
|
||||
async fn build_pool(self, ctx: &BuilderContext<Node>) -> eyre::Result<Self::Pool> {
|
||||
let Self { pool_config_overrides, .. } = self;
|
||||
let data_dir = ctx.config().datadir();
|
||||
let blob_store = DiskFileBlobStore::open(data_dir.blobstore(), Default::default())?;
|
||||
// supervisor used for interop
|
||||
if ctx.chain_spec().is_interop_active_at_timestamp(ctx.head().timestamp) &&
|
||||
self.supervisor_http == DEFAULT_SUPERVISOR_URL
|
||||
{
|
||||
// info!(target: "reth::cli",
|
||||
// url=%DEFAULT_SUPERVISOR_URL,
|
||||
// "Default supervisor url is used, consider changing --rollup.supervisor-http."
|
||||
// );
|
||||
}
|
||||
let supervisor_client = SupervisorClient::builder(self.supervisor_http.clone())
|
||||
.minimum_safety(self.supervisor_safety_level)
|
||||
.build()
|
||||
.await;
|
||||
|
||||
let validator = TransactionValidationTaskExecutor::eth_builder(ctx.provider().clone())
|
||||
.no_eip4844()
|
||||
.with_head_timestamp(ctx.head().timestamp)
|
||||
.kzg_settings(ctx.kzg_settings()?)
|
||||
.set_tx_fee_cap(ctx.config().rpc.rpc_tx_fee_cap)
|
||||
.with_additional_tasks(
|
||||
pool_config_overrides
|
||||
.additional_validation_tasks
|
||||
.unwrap_or_else(|| ctx.config().txpool.additional_validation_tasks),
|
||||
)
|
||||
.build_with_tasks(ctx.task_executor().clone(), blob_store.clone())
|
||||
.map(|validator| {
|
||||
OpTransactionValidator::new(validator)
|
||||
// In --dev mode we can't require gas fees because we're unable to decode
|
||||
// the L1 block info
|
||||
.require_l1_data_gas_fee(!ctx.config().dev.dev)
|
||||
.with_supervisor(supervisor_client.clone())
|
||||
});
|
||||
|
||||
let transaction_pool = reth_ethereum::pool::Pool::new(
|
||||
validator,
|
||||
CoinbaseTipOrdering::default(),
|
||||
blob_store,
|
||||
pool_config_overrides.apply(ctx.pool_config()),
|
||||
);
|
||||
// info!(target: "reth::cli", "Transaction pool initialized";);
|
||||
|
||||
// spawn txpool maintenance tasks
|
||||
{
|
||||
let pool = transaction_pool.clone();
|
||||
let chain_events = ctx.provider().canonical_state_stream();
|
||||
let client = ctx.provider().clone();
|
||||
if !ctx.config().txpool.disable_transactions_backup {
|
||||
// Use configured backup path or default to data dir
|
||||
let transactions_path = ctx
|
||||
.config()
|
||||
.txpool
|
||||
.transactions_backup_path
|
||||
.clone()
|
||||
.unwrap_or_else(|| data_dir.txpool_transactions());
|
||||
|
||||
let transactions_backup_config =
|
||||
reth_ethereum::pool::maintain::LocalTransactionBackupConfig::with_local_txs_backup(transactions_path);
|
||||
|
||||
ctx.task_executor().spawn_critical_with_graceful_shutdown_signal(
|
||||
"local transactions backup task",
|
||||
|shutdown| {
|
||||
reth_ethereum::pool::maintain::backup_local_transactions_task(
|
||||
shutdown,
|
||||
pool.clone(),
|
||||
transactions_backup_config,
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// spawn the main maintenance task
|
||||
ctx.task_executor().spawn_critical(
|
||||
"txpool maintenance task",
|
||||
reth_ethereum::pool::maintain::maintain_transaction_pool_future(
|
||||
client,
|
||||
pool.clone(),
|
||||
chain_events,
|
||||
ctx.task_executor().clone(),
|
||||
reth_ethereum::pool::maintain::MaintainPoolConfig {
|
||||
max_tx_lifetime: pool.config().max_queued_lifetime,
|
||||
no_local_exemptions: transaction_pool
|
||||
.config()
|
||||
.local_transactions_config
|
||||
.no_exemptions,
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
);
|
||||
// debug!(target: "reth::cli", "Spawned txpool maintenance task");
|
||||
|
||||
// spawn the Op txpool maintenance task
|
||||
let chain_events = ctx.provider().canonical_state_stream();
|
||||
ctx.task_executor().spawn_critical(
|
||||
"Op txpool interop maintenance task",
|
||||
reth_op::node::txpool::maintain::maintain_transaction_pool_interop_future(
|
||||
pool.clone(),
|
||||
chain_events,
|
||||
supervisor_client,
|
||||
),
|
||||
);
|
||||
// debug!(target: "reth::cli", "Spawned Op interop txpool maintenance task");
|
||||
|
||||
if self.enable_tx_conditional {
|
||||
// spawn the Op txpool maintenance task
|
||||
let chain_events = ctx.provider().canonical_state_stream();
|
||||
ctx.task_executor().spawn_critical(
|
||||
"Op txpool conditional maintenance task",
|
||||
reth_op::node::txpool::maintain::maintain_transaction_pool_conditional_future(
|
||||
pool,
|
||||
chain_events,
|
||||
),
|
||||
);
|
||||
// debug!(target: "reth::cli", "Spawned Op conditional txpool maintenance task");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(transaction_pool)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
use crate::primitives::CustomHeader;
|
||||
use reth_op::OpTransactionSigned;
|
||||
|
||||
use super::{CustomTransactionEnvelope, ExtendedOpTxEnvelope};
|
||||
|
||||
/// The Block type of this node
|
||||
pub type Block = alloy_consensus::Block<OpTransactionSigned, CustomHeader>;
|
||||
pub type Block =
|
||||
alloy_consensus::Block<ExtendedOpTxEnvelope<CustomTransactionEnvelope>, CustomHeader>;
|
||||
|
||||
/// The body type of this node
|
||||
pub type BlockBody = alloy_consensus::BlockBody<OpTransactionSigned, CustomHeader>;
|
||||
pub type BlockBody =
|
||||
alloy_consensus::BlockBody<ExtendedOpTxEnvelope<CustomTransactionEnvelope>, CustomHeader>;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use alloy_consensus::Transaction;
|
||||
use alloy_consensus::{error::ValueError, Transaction};
|
||||
use alloy_eips::{
|
||||
eip2718::{Eip2718Error, Eip2718Result, IsTyped2718},
|
||||
eip2930::AccessList,
|
||||
eip7702::SignedAuthorization,
|
||||
Decodable2718, Encodable2718, Typed2718,
|
||||
};
|
||||
use alloy_primitives::{bytes::Buf, ChainId, TxHash};
|
||||
use alloy_primitives::{bytes::Buf, ChainId, Signature, TxHash};
|
||||
use alloy_rlp::{BufMut, Decodable, Encodable, Result as RlpResult};
|
||||
use op_alloy_consensus::OpTxEnvelope;
|
||||
use op_alloy_consensus::{OpPooledTransaction, OpTxEnvelope};
|
||||
use reth_codecs::Compact;
|
||||
use reth_ethereum::primitives::{
|
||||
serde_bincode_compat::SerdeBincodeCompat, transaction::signed::RecoveryError, InMemorySize,
|
||||
@@ -15,6 +15,8 @@ use reth_ethereum::primitives::{
|
||||
};
|
||||
use revm_primitives::{Address, Bytes, TxKind, B256, U256};
|
||||
|
||||
use super::CustomTransactionEnvelope;
|
||||
|
||||
macro_rules! delegate {
|
||||
($self:expr => $tx:ident.$method:ident($($arg:expr),*)) => {
|
||||
match $self {
|
||||
@@ -39,6 +41,50 @@ pub enum ExtendedTxEnvelope<BuiltIn, Other> {
|
||||
|
||||
pub type ExtendedOpTxEnvelope<T> = ExtendedTxEnvelope<OpTxEnvelope, T>;
|
||||
|
||||
impl TryFrom<ExtendedTxEnvelope<OpTxEnvelope, CustomTransactionEnvelope>>
|
||||
for ExtendedTxEnvelope<OpPooledTransaction, CustomTransactionEnvelope>
|
||||
{
|
||||
type Error = OpTxEnvelope;
|
||||
|
||||
fn try_from(
|
||||
value: ExtendedTxEnvelope<OpTxEnvelope, CustomTransactionEnvelope>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
ExtendedTxEnvelope::BuiltIn(tx) => {
|
||||
let converted_tx: OpPooledTransaction = tx.clone().try_into().map_err(|_| tx)?;
|
||||
Ok(ExtendedTxEnvelope::BuiltIn(converted_tx))
|
||||
}
|
||||
ExtendedTxEnvelope::Other(tx) => Ok(ExtendedTxEnvelope::Other(tx)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OpPooledTransaction> for ExtendedTxEnvelope<OpTxEnvelope, CustomTransactionEnvelope> {
|
||||
fn from(tx: OpPooledTransaction) -> Self {
|
||||
ExtendedTxEnvelope::BuiltIn(tx.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ExtendedTxEnvelope<OpTxEnvelope, CustomTransactionEnvelope>> for OpPooledTransaction {
|
||||
type Error = ValueError<OpTxEnvelope>;
|
||||
|
||||
fn try_from(
|
||||
_tx: ExtendedTxEnvelope<OpTxEnvelope, CustomTransactionEnvelope>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
match _tx {
|
||||
ExtendedTxEnvelope::BuiltIn(inner) => inner.try_into(),
|
||||
ExtendedTxEnvelope::Other(_tx) => Err(ValueError::new(
|
||||
OpTxEnvelope::Legacy(alloy_consensus::Signed::new_unchecked(
|
||||
alloy_consensus::TxLegacy::default(),
|
||||
Signature::decode_rlp_vrs(&mut &[0u8; 65][..], |_| Ok(false)).unwrap(),
|
||||
B256::default(),
|
||||
)),
|
||||
"Cannot convert custom transaction to OpPooledTransaction",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, T> Transaction for ExtendedTxEnvelope<B, T>
|
||||
where
|
||||
B: Transaction,
|
||||
|
||||
@@ -15,7 +15,7 @@ pub mod tx_custom;
|
||||
pub use tx_custom::*;
|
||||
|
||||
use reth_ethereum::primitives::NodePrimitives;
|
||||
use reth_op::{OpReceipt, OpTransactionSigned};
|
||||
use reth_op::OpReceipt;
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct CustomNodePrimitives;
|
||||
@@ -24,6 +24,6 @@ impl NodePrimitives for CustomNodePrimitives {
|
||||
type Block = Block;
|
||||
type BlockHeader = CustomHeader;
|
||||
type BlockBody = BlockBody;
|
||||
type SignedTx = OpTransactionSigned;
|
||||
type SignedTx = ExtendedOpTxEnvelope<CustomTransactionEnvelope>;
|
||||
type Receipt = OpReceipt;
|
||||
}
|
||||
|
||||
@@ -204,18 +204,18 @@ impl reth_codecs::alloy::transaction::Envelope for CustomTransactionEnvelope {
|
||||
}
|
||||
}
|
||||
|
||||
// impl Compact for CustomTransactionEnvelope {
|
||||
// fn to_compact<B>(&self, buf: &mut B) -> usize
|
||||
// where
|
||||
// B: alloy_rlp::bytes::BufMut + AsMut<[u8]>,
|
||||
// {
|
||||
// self.inner.tx().to_compact(buf)
|
||||
// }
|
||||
//
|
||||
// fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) {
|
||||
// let (signature, rest) = Signature::from_compact(buf, len);
|
||||
// let (inner, buf) = <TxCustom as Compact>::from_compact(rest, len);
|
||||
// let signed = Signed::new_unhashed(inner, signature);
|
||||
// (CustomTransactionEnvelope { inner: signed }, buf)
|
||||
// }
|
||||
// }
|
||||
impl Compact for CustomTransactionEnvelope {
|
||||
fn to_compact<B>(&self, buf: &mut B) -> usize
|
||||
where
|
||||
B: alloy_rlp::bytes::BufMut + AsMut<[u8]>,
|
||||
{
|
||||
self.inner.tx().to_compact(buf)
|
||||
}
|
||||
|
||||
fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) {
|
||||
let (signature, rest) = Signature::from_compact(buf, len);
|
||||
let (inner, buf) = <TxCustom as Compact>::from_compact(rest, len);
|
||||
let signed = Signed::new_unhashed(inner, signature);
|
||||
(CustomTransactionEnvelope { inner: signed }, buf)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user