Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: reject transactions has less gas than the intrinsic gas #725

Merged
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
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
@@ -1,6 +1,7 @@
pub mod exponential_backoff;
pub mod fee;
pub mod genesis_info;
pub mod polyjuice_parser;
pub mod script_log;
pub mod since;
pub mod transaction_skeleton;
Expand Down
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::*};

jjyr marked this conversation as resolved.
Show resolved Hide resolved
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()]
}
}