use ethereum_types::H256;
use futures::future::TryFutureExt;
use jsonrpsee::core::RpcResult;
use sc_client_api::backend::{Backend, StorageProvider};
use sc_transaction_pool::ChainApi;
use sc_transaction_pool_api::TransactionPool;
use sp_api::{ApiExt, ProvideRuntimeApi};
use sp_block_builder::BlockBuilder as BlockBuilderApi;
use sp_blockchain::HeaderBackend;
use sp_inherents::CreateInherentDataProviders;
use sp_runtime::{traits::Block as BlockT, transaction_validity::TransactionSource};
use fc_rpc_core::types::*;
use fp_rpc::{ConvertTransaction, ConvertTransactionRuntimeApi, EthereumRuntimeRPCApi};
use crate::{
eth::{format, Eth},
internal_err,
};
impl<B, C, P, CT, BE, A, CIDP, EC> Eth<B, C, P, CT, BE, A, CIDP, EC>
where
B: BlockT,
C: ProvideRuntimeApi<B>,
C::Api: BlockBuilderApi<B> + ConvertTransactionRuntimeApi<B> + EthereumRuntimeRPCApi<B>,
C: HeaderBackend<B> + StorageProvider<B, BE> + 'static,
BE: Backend<B> + 'static,
P: TransactionPool<Block = B> + 'static,
CT: ConvertTransaction<<B as BlockT>::Extrinsic> + 'static,
A: ChainApi<Block = B>,
CIDP: CreateInherentDataProviders<B, ()> + Send + 'static,
{
pub async fn send_transaction(&self, request: TransactionRequest) -> RpcResult<H256> {
let from = match request.from {
Some(from) => from,
None => {
let accounts = match self.accounts() {
Ok(accounts) => accounts,
Err(e) => return Err(e),
};
match accounts.first() {
Some(account) => *account,
None => return Err(internal_err("no signer available")),
}
}
};
let nonce = match request.nonce {
Some(nonce) => nonce,
None => match self.transaction_count(from, None).await {
Ok(nonce) => nonce,
Err(e) => return Err(e),
},
};
let chain_id = match (request.chain_id, self.chain_id()) {
(Some(id), Ok(Some(chain_id))) if id != chain_id => {
return Err(internal_err("chain id is mismatch"))
}
(_, Ok(Some(chain_id))) => chain_id.as_u64(),
(_, Ok(None)) => return Err(internal_err("chain id not available")),
(_, Err(e)) => return Err(e),
};
let block_hash = self.client.info().best_hash;
let gas_price = request.gas_price;
let gas_limit = match request.gas {
Some(gas_limit) => gas_limit,
None => {
if let Ok(Some(block)) = self.client.runtime_api().current_block(block_hash) {
block.header.gas_limit
} else {
return Err(internal_err("block unavailable, cannot query gas limit"));
}
}
};
let max_fee_per_gas = request.max_fee_per_gas;
let message: Option<TransactionMessage> = request.into();
let message = match message {
Some(TransactionMessage::Legacy(mut m)) => {
m.nonce = nonce;
m.chain_id = Some(chain_id);
m.gas_limit = gas_limit;
if gas_price.is_none() {
m.gas_price = self.gas_price().unwrap_or_default();
}
TransactionMessage::Legacy(m)
}
Some(TransactionMessage::EIP2930(mut m)) => {
m.nonce = nonce;
m.chain_id = chain_id;
m.gas_limit = gas_limit;
if gas_price.is_none() {
m.gas_price = self.gas_price().unwrap_or_default();
}
TransactionMessage::EIP2930(m)
}
Some(TransactionMessage::EIP1559(mut m)) => {
m.nonce = nonce;
m.chain_id = chain_id;
m.gas_limit = gas_limit;
if max_fee_per_gas.is_none() {
m.max_fee_per_gas = self.gas_price().unwrap_or_default();
}
TransactionMessage::EIP1559(m)
}
_ => return Err(internal_err("invalid transaction parameters")),
};
let mut transaction = None;
for signer in &self.signers {
if signer.accounts().contains(&from) {
match signer.sign(message, &from) {
Ok(t) => transaction = Some(t),
Err(e) => return Err(e),
}
break;
}
}
let transaction = match transaction {
Some(transaction) => transaction,
None => return Err(internal_err("no signer available")),
};
let transaction_hash = transaction.hash();
let extrinsic = self.convert_transaction(block_hash, transaction)?;
self.pool
.submit_one(block_hash, TransactionSource::Local, extrinsic)
.map_ok(move |_| transaction_hash)
.map_err(|err| internal_err(format::Geth::pool_error(err)))
.await
}
pub async fn send_raw_transaction(&self, bytes: Bytes) -> RpcResult<H256> {
let bytes = bytes.into_vec();
if bytes.is_empty() {
return Err(internal_err("transaction data is empty"));
}
let transaction: ethereum::TransactionV2 =
match ethereum::EnvelopedDecodable::decode(&bytes) {
Ok(transaction) => transaction,
Err(_) => return Err(internal_err("decode transaction failed")),
};
let transaction_hash = transaction.hash();
let block_hash = self.client.info().best_hash;
let extrinsic = self.convert_transaction(block_hash, transaction)?;
self.pool
.submit_one(block_hash, TransactionSource::Local, extrinsic)
.map_ok(move |_| transaction_hash)
.map_err(|err| internal_err(format::Geth::pool_error(err)))
.await
}
fn convert_transaction(
&self,
block_hash: B::Hash,
transaction: ethereum::TransactionV2,
) -> RpcResult<B::Extrinsic> {
let api_version = match self
.client
.runtime_api()
.api_version::<dyn ConvertTransactionRuntimeApi<B>>(block_hash)
{
Ok(api_version) => api_version,
_ => return Err(internal_err("cannot access `ConvertTransactionRuntimeApi`")),
};
match api_version {
Some(2) => match self
.client
.runtime_api()
.convert_transaction(block_hash, transaction)
{
Ok(extrinsic) => Ok(extrinsic),
Err(_) => Err(internal_err("cannot access `ConvertTransactionRuntimeApi`")),
},
Some(1) => {
if let ethereum::TransactionV2::Legacy(legacy_transaction) = transaction {
#[allow(deprecated)]
match self
.client
.runtime_api()
.convert_transaction_before_version_2(block_hash, legacy_transaction)
{
Ok(extrinsic) => Ok(extrinsic),
Err(_) => Err(internal_err("cannot access `ConvertTransactionRuntimeApi`")),
}
} else {
Err(internal_err(
"Ethereum transactions v2 is not supported by the runtime",
))
}
}
None => {
if let Some(ref convert_transaction) = self.convert_transaction {
Ok(convert_transaction.convert_transaction(transaction.clone()))
} else {
Err(internal_err(
"`ConvertTransactionRuntimeApi` is not found and no `TransactionConverter` is provided"
))
}
}
_ => Err(internal_err(
"`ConvertTransactionRuntimeApi` is not supported",
)),
}
}
}