Skip to content

Commit

Permalink
fix: reject transactions has less gas than the intrinsic gas
Browse files Browse the repository at this point in the history
  • Loading branch information
jjyr committed Jun 13, 2022
1 parent ede7f55 commit a737640
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 98 deletions.
42 changes: 7 additions & 35 deletions crates/generator/src/account_lock_manage/secp256k1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use gw_types::{
bytes::Bytes,
packed::{L2Transaction, RawL2Transaction, Script},
};
use gw_utils::polyjuice_parser::PolyjuiceParser;
use lazy_static::lazy_static;
use secp256k1::recovery::{RecoverableSignature, RecoveryId};
use sha3::{Digest, Keccak256};
Expand Down Expand Up @@ -376,30 +377,14 @@ fn calc_godwoken_signing_message(
}

fn try_assemble_polyjuice_args(raw_tx: RawL2Transaction, receiver_script: Script) -> Option<Bytes> {
let args: Bytes = raw_tx.args().unpack();
if args.len() < 52 {
return None;
}
if args[0..7] != b"\xFF\xFF\xFFPOLY"[..] {
return None;
}
let parser = PolyjuiceParser::from_raw_l2_tx(&raw_tx)?;
let mut stream = rlp::RlpStream::new();
stream.begin_unbounded_list();
let nonce: u32 = raw_tx.nonce().unpack();
stream.append(&nonce);
let gas_price = {
let mut data = [0u8; 16];
data.copy_from_slice(&args[16..32]);
u128::from_le_bytes(data)
};
stream.append(&gas_price);
let gas_limit = {
let mut data = [0u8; 8];
data.copy_from_slice(&args[8..16]);
u64::from_le_bytes(data)
};
stream.append(&gas_limit);
let to = if args[7] == 3 {
stream.append(&parser.gas_price());
stream.append(&parser.gas());
let to = if parser.is_create() {
// 3 for EVMC_CREATE
vec![0u8; 0]
} else {
Expand All @@ -418,21 +403,8 @@ fn try_assemble_polyjuice_args(raw_tx: RawL2Transaction, receiver_script: Script
to
};
stream.append(&to);
let value = {
let mut data = [0u8; 16];
data.copy_from_slice(&args[32..48]);
u128::from_le_bytes(data)
};
stream.append(&value);
let payload_length = {
let mut data = [0u8; 4];
data.copy_from_slice(&args[48..52]);
u32::from_le_bytes(data)
} as usize;
if args.len() != 52 + payload_length {
return None;
}
stream.append(&args[52..52 + payload_length].to_vec());
stream.append(&parser.value());
stream.append(&parser.data().to_vec());
stream.append(&raw_tx.chain_id().unpack());
stream.append(&0u8);
stream.append(&0u8);
Expand Down
2 changes: 2 additions & 0 deletions crates/generator/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ pub enum TransactionError {
NoCost,
#[error("Nonce Overflow")]
NonceOverflow,
#[error("Intrinsic gas")]
IntrinsicGas,
}

impl From<VMError> for TransactionError {
Expand Down
6 changes: 3 additions & 3 deletions crates/generator/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -689,17 +689,17 @@ impl Generator {
{
run_result.write.logs.push(log);
}
let args = tx.extract_tx_args().ok_or(TransactionError::NoCost)?;
let parser = tx.parser().ok_or(TransactionError::NoCost)?;
let gas_used = match read_polyjuice_gas_used(&run_result) {
Some(gas_used) => gas_used,
None => {
log::warn!(
"[gw-generator] failed to parse gas_used, use gas_limit instead"
);
args.gas_limit
parser.gas()
}
};
gw_types::U256::from(gas_used).checked_mul(args.gas_price.into())
gw_types::U256::from(gas_used).checked_mul(parser.gas_price().into())
}
}
.ok_or(TransactionError::NoCost)?;
Expand Down
92 changes: 43 additions & 49 deletions crates/generator/src/typed_transaction/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use gw_types::{
prelude::*,
U256,
};
use gw_utils::polyjuice_parser::PolyjuiceParser;

/// Types Transaction
pub enum TypedRawTransaction {
Expand Down Expand Up @@ -123,64 +124,57 @@ impl SimpleUDTTx {
}
}

pub struct PolyjuiceTxArgs {
pub value: u128,
pub gas_price: u128,
pub gas_limit: u64,
}

pub struct PolyjuiceTx(RawL2Transaction);
impl PolyjuiceTx {
pub fn extract_tx_args(&self) -> Option<PolyjuiceTxArgs> {
let args: Bytes = self.0.args().unpack();
if args.len() < 52 {
log::error!(
"[gw-generator] parse PolyjuiceTx error, wrong args.len expected: >= 52, actual: {}",
args.len()
);
return None;
}
if args[0..7] != b"\xFF\xFF\xFFPOLY"[..] {
log::error!("[gw-generator] parse PolyjuiceTx error, invalid args",);
return None;
}

// parse gas price, gas limit, value
let gas_price = {
let mut data = [0u8; 16];
data.copy_from_slice(&args[16..32]);
u128::from_le_bytes(data)
};
let gas_limit = {
let mut data = [0u8; 8];
data.copy_from_slice(&args[8..16]);
u64::from_le_bytes(data)
};

let value = {
let mut data = [0u8; 16];
data.copy_from_slice(&args[32..48]);
u128::from_le_bytes(data)
};
Some(PolyjuiceTxArgs {
value,
gas_price,
gas_limit,
})
pub fn parser(&self) -> Option<PolyjuiceParser> {
PolyjuiceParser::from_raw_l2_tx(&self.0)
}

/// Total cost of a tx, sender's balance must sufficient to pay Cost(value + gas_price * gas_limit)
pub fn cost(&self) -> Option<U256> {
match self.extract_tx_args() {
Some(PolyjuiceTxArgs {
value,
gas_price,
gas_limit,
}) => {
let cost = value.checked_add(gas_price.checked_mul(gas_limit.into())?)?;
match self.parser() {
Some(parser) => {
let cost = parser
.value()
.checked_add(parser.gas_price().checked_mul(parser.gas().into())?)?;
cost.try_into().ok()
}
None => None,
}
}

/// Intrinsic gas
pub fn intrinsic_gas(&self) -> Option<u64> {
// Minimal gas of a normal transaction
const MIN_TX_GAS: u64 = 21000;
// Minimal gas of a transaction that creates a contract
const MIN_CONTRACT_CREATION_TX_GAS: u64 = 53000;
// Gas per byte of non zero data attached to a transaction
const DATA_NONE_ZERO_GAS: u64 = 16;
// Gas per byte of data attached to a transaction
const DATA_ZERO_GAS: u64 = 4;

let p = self.parser()?;

// Set the starting gas for the raw transaction
let mut gas = if p.is_create() {
MIN_CONTRACT_CREATION_TX_GAS
} else {
MIN_TX_GAS
};
if p.data_size() > 0 {
let mut non_zeros = 0u64;
for &b in p.data() {
if b != 0 {
non_zeros += 1;
}
}
// nonzero bytes gas
gas = gas.checked_add(non_zeros.checked_mul(DATA_NONE_ZERO_GAS)?)?;
let zeros = p.data_size() as u64 - non_zeros;
// zero bytes gas
gas = gas.checked_add(zeros.checked_mul(DATA_ZERO_GAS)?)?;
}
Some(gas)
}
}
28 changes: 17 additions & 11 deletions crates/generator/src/verification/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,23 +65,29 @@ impl<'a, S: State + CodeStore> TransactionVerifier<'a, S> {
.state
.get_registry_address_by_script_hash(ETH_REGISTRY_ACCOUNT_ID, &sender_script_hash)?
.ok_or(AccountError::RegistryAddressNotFound)?;
// get balance
let balance = self
.state
.get_sudt_balance(CKB_SUDT_ACCOUNT_ID, &sender_address)?;
// get balance
let tx_cost = {
let tx_type = get_tx_type(self.rollup_context, self.state, &tx.raw())?;
let typed_tx = TypedRawTransaction::from_tx(tx.raw(), tx_type)
.ok_or(AccountError::UnknownScript)?;
// reject txs has no cost, these transaction can only be execute without modify state tree
typed_tx
.cost()
.map(Into::into)
.ok_or(TransactionError::NoCost)?
};
let tx_type = get_tx_type(self.rollup_context, self.state, &tx.raw())?;
let typed_tx =
TypedRawTransaction::from_tx(tx.raw(), tx_type).ok_or(AccountError::UnknownScript)?;
// reject txs has no cost, these transaction can only be execute without modify state tree
let tx_cost = typed_tx
.cost()
.map(Into::into)
.ok_or(TransactionError::NoCost)?;
if balance < tx_cost {
return Err(TransactionError::InsufficientBalance.into());
}
// Intrinsic Gas
if let TypedRawTransaction::Polyjuice(tx) = typed_tx {
let p = tx.parser().ok_or(TransactionError::IntrinsicGas)?;
let intrinsic_gas = tx.intrinsic_gas().ok_or(TransactionError::IntrinsicGas)?;
if p.gas() < intrinsic_gas {
return Err(TransactionError::IntrinsicGas.into());
}
}

Ok(())
}
Expand Down
1 change: 1 addition & 0 deletions crates/utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ pub mod since;
pub mod transaction_skeleton;
pub mod wallet;
pub mod withdrawal;
pub mod polyjuice_parser;
55 changes: 55 additions & 0 deletions crates/utils/src/polyjuice_parser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use gw_types::{bytes::Bytes, packed::RawL2Transaction, prelude::*};

pub struct PolyjuiceParser(Bytes);

impl PolyjuiceParser {
pub fn from_raw_l2_tx(raw_tx: &RawL2Transaction) -> Option<Self> {
let args: Bytes = raw_tx.args().unpack();
let args_len = args.len();
if args_len < 52 {
return None;
}
if args[0..7] != b"\xFF\xFF\xFFPOLY"[..] {
return None;
}
let parser = Self(args);
// check data size
if args_len != 52 + parser.data_size() {
return None;
}
Some(parser)
}

pub fn gas(&self) -> u64 {
let mut data = [0u8; 8];
data.copy_from_slice(&self.0[8..16]);
u64::from_le_bytes(data)
}

pub fn gas_price(&self) -> u128 {
let mut data = [0u8; 16];
data.copy_from_slice(&self.0[16..32]);
u128::from_le_bytes(data)
}

pub fn is_create(&self) -> bool {
// 3 for EVMC_CREATE
self.0[7] == 3
}

pub fn value(&self) -> u128 {
let mut data = [0u8; 16];
data.copy_from_slice(&self.0[32..48]);
u128::from_le_bytes(data)
}

pub fn data_size(&self) -> usize {
let mut data = [0u8; 4];
data.copy_from_slice(&self.0[48..52]);
u32::from_le_bytes(data) as usize
}

pub fn data(&self) -> &[u8] {
&self.0[52..52 + self.data_size()]
}
}

0 comments on commit a737640

Please sign in to comment.