Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Return error if RLP size of transaction exceeds the limit #8473

Merged
merged 3 commits into from
Apr 27, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions ethcore/src/client/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ use ethcore_miner::pool::VerifiedTransaction;
use parking_lot::{Mutex, RwLock};
use rand::OsRng;
use receipt::{Receipt, LocalizedReceipt};
use rlp::Rlp;
use snapshot::{self, io as snapshot_io};
use spec::Spec;
use state_db::StateDB;
Expand Down Expand Up @@ -995,7 +994,7 @@ impl Client {

let txs: Vec<UnverifiedTransaction> = transactions
.iter()
.filter_map(|bytes| Rlp::new(bytes).as_val().ok())
.filter_map(|bytes| self.engine().decode_transaction(bytes.to_vec()).ok())
.collect();

self.notify(|notify| {
Expand Down
5 changes: 5 additions & 0 deletions ethcore/src/engines/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,11 @@ pub trait EthEngine: Engine<::machine::EthereumMachine> {
fn additional_params(&self) -> HashMap<String, String> {
self.machine().additional_params()
}

/// Performs pre-validation of RLP decoded transaction before other processing
fn decode_transaction(&self, transaction: Bytes) -> Result<UnverifiedTransaction, transaction::Error> {
self.machine().decode_transaction(transaction)
}
}

// convenience wrappers for existing functions.
Expand Down
13 changes: 12 additions & 1 deletion ethcore/src/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ use transaction::{self, SYSTEM_ADDRESS, UnverifiedTransaction, SignedTransaction
use tx_filter::TransactionFilter;

use ethereum_types::{U256, Address};
use bytes::BytesRef;
use bytes::{BytesRef, Bytes};
use rlp::Rlp;
use vm::{CallType, ActionParams, ActionValue, ParamsType};
use vm::{EnvInfo, Schedule, CreateContractAddress};

Expand Down Expand Up @@ -376,6 +377,16 @@ impl EthereumMachine {
"registrar".to_owned() => format!("{:x}", self.params.registrar)
]
}

/// Performs pre-validation of RLP decoded transaction before other processing
pub fn decode_transaction(&self, transaction: Bytes) -> Result<UnverifiedTransaction, transaction::Error> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use &[u8] here to avoid unnecessary copying.

let rlp = Rlp::new(&transaction);
if rlp.as_raw().len() > self.params().max_transaction_size {
debug!("Rejected oversized transaction of {} bytes", rlp.as_raw().len());
return Err(transaction::Error::TooBig)
}
rlp.as_val().map_err(|e| transaction::Error::InvalidRlp(e.to_string()))
}
}

/// Auxiliary data fetcher for an Ethereum machine. In Ethereum-like machines
Expand Down
6 changes: 6 additions & 0 deletions ethcore/src/miner/miner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ use header::{Header, BlockNumber};
use miner;
use miner::pool_client::{PoolClient, CachedNonceClient};
use receipt::{Receipt, RichReceipt};
use rlp::Encodable;
use spec::Spec;
use state::State;

Expand Down Expand Up @@ -768,6 +769,11 @@ impl miner::MinerService for Miner {

trace!(target: "own_tx", "Importing transaction: {:?}", pending);

// Verify RLP payload first
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd see it more in the RPC layer than here.

Copy link
Collaborator Author

@grbIzl grbIzl Apr 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't put it into RPC dispatcher, because import_own_transaction called not only from RPC. There are at least 2 other usages (in private transactions and in secret store). IMHO these cases (when transactions are created not from RPC but by some logic) also should be guarded with this check.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be part of verification pipeline then + the additional check for all external transactions to avoid RLP decoding them and hashing large amounts of data.

if let Err(err) = self.engine.decode_transaction(pending.transaction.rlp_bytes().to_vec()) {
return Err(err);
}

let client = self.pool_client(chain);
let imported = self.transaction_queue.import(
client,
Expand Down
5 changes: 5 additions & 0 deletions ethcore/src/spec/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ use trace::{NoopTracer, NoopVMTracer};

pub use ethash::OptimizeFor;

const MAX_TRANSACTION_SIZE: usize = 300 * 1024;

// helper for formatting errors.
fn fmt_err<F: ::std::fmt::Display>(f: F) -> String {
format!("Spec json is invalid: {}", f)
Expand Down Expand Up @@ -123,6 +125,8 @@ pub struct CommonParams {
pub max_code_size_transition: BlockNumber,
/// Transaction permission managing contract address.
pub transaction_permission_contract: Option<Address>,
/// Maximum size of transaction's RLP payload
pub max_transaction_size: usize,
}

impl CommonParams {
Expand Down Expand Up @@ -238,6 +242,7 @@ impl From<ethjson::spec::Params> for CommonParams {
registrar: p.registrar.map_or_else(Address::new, Into::into),
node_permission_contract: p.node_permission_contract.map(Into::into),
max_code_size: p.max_code_size.map_or(u64::max_value(), Into::into),
max_transaction_size: p.max_transaction_size.map_or(MAX_TRANSACTION_SIZE, Into::into),
max_code_size_transition: p.max_code_size_transition.map_or(0, Into::into),
transaction_permission_contract: p.transaction_permission_contract.map(Into::into),
wasm_activation_transition: p.wasm_activation_transition.map_or(
Expand Down
5 changes: 0 additions & 5 deletions ethcore/sync/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,6 @@ const MAX_PEERS_PROPAGATION: usize = 128;
const MAX_PEER_LAG_PROPAGATION: BlockNumber = 20;
const MAX_NEW_HASHES: usize = 64;
const MAX_NEW_BLOCK_AGE: BlockNumber = 20;
const MAX_TRANSACTION_SIZE: usize = 300*1024;
// maximal packet size with transactions (cannot be greater than 16MB - protocol limitation).
const MAX_TRANSACTION_PACKET_SIZE: usize = 8 * 1024 * 1024;
// Maximal number of transactions in sent in single packet.
Expand Down Expand Up @@ -1517,10 +1516,6 @@ impl ChainSync {
let mut transactions = Vec::with_capacity(item_count);
for i in 0 .. item_count {
let rlp = r.at(i)?;
if rlp.as_raw().len() > MAX_TRANSACTION_SIZE {
debug!("Skipped oversized transaction of {} bytes", rlp.as_raw().len());
continue;
}
let tx = rlp.as_raw().to_vec();
transactions.push(tx);
}
Expand Down
13 changes: 13 additions & 0 deletions ethcore/transaction/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use std::{fmt, error};

use ethereum_types::U256;
use ethkey;
use rlp;
use unexpected::OutOfBounds;

#[derive(Debug, PartialEq, Clone)]
Expand Down Expand Up @@ -74,6 +75,10 @@ pub enum Error {
NotAllowed,
/// Signature error
InvalidSignature(String),
/// Transaction too big for transmition (its RLP payload size exceeds the limit)
TooBig,
/// Invalid RLP encoding
InvalidRlp(String),
}

impl From<ethkey::Error> for Error {
Expand All @@ -82,6 +87,12 @@ impl From<ethkey::Error> for Error {
}
}

impl From<rlp::DecoderError> for Error {
fn from(err: rlp::DecoderError) -> Self {
Error::InvalidRlp(format!("{}", err))
}
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
Expand All @@ -106,6 +117,8 @@ impl fmt::Display for Error {
InvalidChainId => "Transaction of this chain ID is not allowed on this chain.".into(),
InvalidSignature(ref err) => format!("Transaction has invalid signature: {}.", err),
NotAllowed => "Sender does not have permissions to execute this type of transction".into(),
TooBig => "Transaction too big for transmition".into(),
InvalidRlp(ref err) => format!("Transaction has invalid RLP structure: {}.", err),
};

f.write_fmt(format_args!("Transaction error ({})", msg))
Expand Down
3 changes: 3 additions & 0 deletions json/src/spec/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ pub struct Params {
/// See main EthashParams docs.
#[serde(rename="maxCodeSize")]
pub max_code_size: Option<Uint>,
/// Maximum size of transaction RLP payload.
#[serde(rename="maxTransactionSize")]
pub max_transaction_size: Option<Uint>,
/// See main EthashParams docs.
#[serde(rename="maxCodeSizeTransition")]
pub max_code_size_transition: Option<Uint>,
Expand Down
2 changes: 2 additions & 0 deletions rpc/src/v1/helpers/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,8 @@ pub fn transaction_message(error: &TransactionError) -> String {
RecipientBanned => "Recipient is banned in local queue.".into(),
CodeBanned => "Code is banned in local queue.".into(),
NotAllowed => "Transaction is not permitted.".into(),
TooBig => "Transaction is too big for transmition, see chain specification for the limit.".into(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Transmission, or just too big should be enough.

InvalidRlp(ref descr) => format!("Invalid RLP data: {}", descr),
}
}

Expand Down