diff --git a/crates/ethcore/res/chainspec/test/contract_ver_4.sol b/crates/ethcore/res/chainspec/test/contract_ver_4.sol new file mode 100644 index 000000000..66ab051a4 --- /dev/null +++ b/crates/ethcore/res/chainspec/test/contract_ver_4.sol @@ -0,0 +1,65 @@ +pragma solidity ^0.4.20; + +// Adapted from https://gist.github.com/VladLupashevskyi/84f18eabb1e4afadf572cf92af3e7e7f +// and: https://github.com/poanetwork/posdao-contracts/blob/master/contracts/TxPermission.sol + +contract TxPermission { + /// Allowed transaction types mask + uint32 constant None = 0; + uint32 constant All = 0xffffffff; + uint32 constant Basic = 0x01; + uint32 constant Call = 0x02; + uint32 constant Create = 0x04; + uint32 constant Private = 0x08; + + /// Contract name + function contractName() public constant returns (string) { + return "TX_PERMISSION_CONTRACT"; + } + + /// Contract name hash + function contractNameHash() public constant returns (bytes32) { + return keccak256(contractName()); + } + + /// Contract version + function contractVersion() public constant returns (uint256) { + return 4; + } + + /// @dev Defines the allowed transaction types which may be initiated by the specified sender with + /// the specified gas price and data. Used by node's engine each time a transaction is about to be + /// included into a block. See https://openethereum.github.io/Permissioning.html#how-it-works-1 + /// @param _sender Transaction sender address. + /// @param _to Transaction recipient address. If creating a contract, the `_to` address is zero. + /// @param _value Transaction amount in wei. + /// @param _maxFeePerGas The `maxFeePerGas` in Wei for EIP-1559 transaction, or gas price for a legacy transaction. + /// @param _maxInclusionFeePerGas The `maxInclusionFeePerGas` in Wei for EIP-1559 transaction. + /// Equals to gas price for a legacy transaction. + /// @param _gasLimit Gas limit for the transaction. + /// @param _data Transaction data. + /// @return `uint32 typesMask` - Set of allowed transactions for `_sender` depending on tx `_to` address, + /// `_gasPrice`, and `_data`. The result is represented as a set of flags: + /// 0x01 - basic transaction (e.g. ether transferring to user wallet); + /// 0x02 - contract call; + /// 0x04 - contract creation; + /// 0x08 - private transaction. + /// `bool cache` - If `true` is returned, the same permissions will be applied from the same + /// `_sender` without calling this contract again. + function allowedTxTypes( + address _sender, + address _to, + uint256 _value, + uint256 _maxFeePerGas, // equals to gasPrice for legacy transactions + uint256 _maxInclusionFeePerGas, // equals to gasPrice for legacy transactions + uint256 _gasLimit, + bytes memory _data + ) + public + view + returns(uint32 typesMask, bool cache) + { + if (_maxFeePerGas > 0 || _data.length < 4) return (All, false); + return (None, false); + } +} \ No newline at end of file diff --git a/crates/ethcore/res/chainspec/test/contract_ver_4_genesis.json b/crates/ethcore/res/chainspec/test/contract_ver_4_genesis.json new file mode 100644 index 000000000..c6e855238 --- /dev/null +++ b/crates/ethcore/res/chainspec/test/contract_ver_4_genesis.json @@ -0,0 +1,44 @@ +{ + "name": "TestNodeFilterContract", + "engine": { + "authorityRound": { + "params": { + "stepDuration": 1, + "startStep": 2, + "validators": { + "contract": "0x0000000000000000000000000000000000000000" + } + } + } + }, + "params": { + "accountStartNonce": "0x0", + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x1388", + "networkID" : "0x69", + "gasLimitBoundDivisor": "0x0400", + "transactionPermissionContract": "0xAB5b100cf7C8deFB3c8f3C48474223997A50fB13", + "transactionPermissionContractTransition": "1" + }, + "genesis": { + "seal": { + "generic": "0xc180" + }, + "difficulty": "0x20000", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x", + "gasLimit": "0x222222" + }, + "accounts": { + "0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, + "0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, + "0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, + "0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, + "0xAB5b100cf7C8deFB3c8f3C48474223997A50fB13": { + "balance": "1", + "constructor": "608060405234801561001057600080fd5b50610370806100206000396000f300608060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063469ab1e31461006757806375d0c0dc1461009a578063a0a8e4601461012a578063e4e3b5e514610155575b600080fd5b34801561007357600080fd5b5061007c610251565b60405180826000191660001916815260200191505060405180910390f35b3480156100a657600080fd5b506100af6102c2565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100ef5780820151818401526020810190506100d4565b50505050905090810190601f16801561011c5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561013657600080fd5b5061013f6102ff565b6040518082815260200191505060405180910390f35b34801561016157600080fd5b50610224600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001909291908035906020019092919080359060200190929190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290505050610308565b604051808363ffffffff1663ffffffff168152602001821515151581526020019250505060405180910390f35b600061025b6102c2565b6040518082805190602001908083835b602083101515610290578051825260208201915060208101905060208303925061026b565b6001836020036101000a0380198251168184511680821785525050505050509050019150506040518091039020905090565b60606040805190810160405280601681526020017f54585f5045524d495353494f4e5f434f4e545241435400000000000000000000815250905090565b60006004905090565b600080600086118061031b575060048351105b156103305763ffffffff600091509150610338565b600080915091505b975097955050505050505600a165627a7a72305820592b45ee74cc856b6c84f99ed8ddd4790d844a9065a07c7bbfc5dfa05cc394c50029" + } + } +} \ No newline at end of file diff --git a/crates/ethcore/res/contracts/tx_acl_1559.json b/crates/ethcore/res/contracts/tx_acl_1559.json new file mode 100644 index 000000000..8b8ba7bcd --- /dev/null +++ b/crates/ethcore/res/contracts/tx_acl_1559.json @@ -0,0 +1,91 @@ +[ + { + "constant": true, + "inputs": [], + "name": "contractNameHash", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "contractName", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "contractVersion", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "sender", + "type": "address" + }, + { + "name": "to", + "type": "address" + }, + { + "name": "value", + "type": "uint256" + }, + { + "name": "maxFeePerGas", + "type": "uint256" + }, + { + "name": "maxPriorityFeePerGas", + "type": "uint256" + }, + { + "name": "gasLimit", + "type": "uint256" + }, + { + "name": "data", + "type": "bytes" + } + ], + "name": "allowedTxTypes", + "outputs": [ + { + "name": "", + "type": "uint32" + }, + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/crates/ethcore/src/tx_filter.rs b/crates/ethcore/src/tx_filter.rs index 30f0744e7..118d776b8 100644 --- a/crates/ethcore/src/tx_filter.rs +++ b/crates/ethcore/src/tx_filter.rs @@ -39,6 +39,10 @@ use_contract!( transact_acl_gas_price, "res/contracts/tx_acl_gas_price.json" ); +use_contract!( + transact_acl_1559, + "res/contracts/tx_acl_1559.json" +); const MAX_CACHE_SIZE: usize = 4096; @@ -104,6 +108,8 @@ impl TransactionFilter { let sender = transaction.sender(); let value = transaction.tx().value; let gas_price = transaction.tx().gas_price; + let max_priority_fee_per_gas = transaction.max_priority_fee_per_gas(); + let gas_limit = transaction.tx().gas; let key = (*parent_hash, sender); if let Some(permissions) = permission_cache.get_mut(&key) { @@ -161,6 +167,26 @@ impl TransactionFilter { (tx_permissions::NONE, true) }) } + 4 => { + trace!(target: "tx_filter", "Using filter with maxFeePerGas and maxPriorityFeePerGas and data"); + let (data, decoder) = + transact_acl_1559::functions::allowed_tx_types::call( + sender, + to, + value, + gas_price, + max_priority_fee_per_gas, + gas_limit, + transaction.tx().data.clone(), + ); + client.call_contract(BlockId::Hash(*parent_hash), contract_address, data) + .and_then(|value| decoder.decode(&value).map_err(|e| e.to_string())) + .map(|(p, f)| (p.low_u32(), f)) + .unwrap_or_else(|e| { + error!(target: "tx_filter", "Error calling tx permissions contract: {:?}", e); + (tx_permissions::NONE, true) + }) + } _ => { error!(target: "tx_filter", "Unknown version of tx permissions contract is used"); (tx_permissions::NONE, true) @@ -201,7 +227,7 @@ mod test { use std::{str::FromStr, sync::Arc}; use tempdir::TempDir; use test_helpers; - use types::transaction::{Action, Transaction, TypedTransaction}; + use types::transaction::{Action, Transaction, TypedTransaction, AccessListTx, EIP1559TransactionTx}; /// Contract code: https://gist.github.com/VladLupashevskyi/84f18eabb1e4afadf572cf92af3e7e7f #[test] @@ -438,7 +464,7 @@ mod test { )); } - /// Contract code: res/tx_permission_tests/contract_ver_3.sol + /// Contract code: res/chainspec/test/contract_ver_3.sol #[test] fn transaction_filter_ver_3() { let spec_data = include_str!("../res/chainspec/test/contract_ver_3_genesis.json"); @@ -506,6 +532,148 @@ mod test { )); } + /// Contract code: res/chainspec/test/contract_ver_4.sol + #[test] + fn transaction_filter_ver_4_legacy() { + let spec_data = include_str!("../res/chainspec/test/contract_ver_4_genesis.json"); + + let db = test_helpers::new_db(); + let tempdir = TempDir::new("").unwrap(); + let spec = Spec::load(&tempdir.path(), spec_data.as_bytes()).unwrap(); + + let client = Client::new( + ClientConfig::default(), + &spec, + db, + Arc::new(Miner::new_for_tests(&spec, None)), + IoChannel::disconnected(), + ) + .unwrap(); + let key1 = KeyPair::from_secret( + Secret::from_str("0000000000000000000000000000000000000000000000000000000000000001") + .unwrap(), + ) + .unwrap(); + + // The only difference to version 2 is that the contract now knows the transaction's gas price and data. + // So we only test those: The contract allows only transactions with either nonzero gas price or short data. + + let filter = TransactionFilter::from_params(spec.params()).unwrap(); + let mut tx = TypedTransaction::Legacy(Transaction::default()); + tx.tx_mut().action = + Action::Call(Address::from_str("0000000000000000000000000000000000000042").unwrap()); + tx.tx_mut().data = b"01234567".to_vec(); + tx.tx_mut().gas_price = 0.into(); + + let genesis = client.block_hash(BlockId::Latest).unwrap(); + let block_number = 1; + + // Data too long and gas price zero. This transaction is not allowed. + assert!(!filter.transaction_allowed( + &genesis, + block_number, + &tx.clone().sign(key1.secret(), None), + &*client + )); + + // But if we either set a nonzero gas price or short data or both, it is allowed. + tx.tx_mut().gas_price = 1.into(); + assert!(filter.transaction_allowed( + &genesis, + block_number, + &tx.clone().sign(key1.secret(), None), + &*client + )); + tx.tx_mut().data = b"01".to_vec(); + assert!(filter.transaction_allowed( + &genesis, + block_number, + &tx.clone().sign(key1.secret(), None), + &*client + )); + tx.tx_mut().gas_price = 0.into(); + assert!(filter.transaction_allowed( + &genesis, + block_number, + &tx.clone().sign(key1.secret(), None), + &*client + )); + } + + /// Contract code: res/chainspec/test/contract_ver_4.sol + #[test] + fn transaction_filter_ver_4_1559() { + let spec_data = include_str!("../res/chainspec/test/contract_ver_4_genesis.json"); + + let db = test_helpers::new_db(); + let tempdir = TempDir::new("").unwrap(); + let spec = Spec::load(&tempdir.path(), spec_data.as_bytes()).unwrap(); + + let client = Client::new( + ClientConfig::default(), + &spec, + db, + Arc::new(Miner::new_for_tests(&spec, None)), + IoChannel::disconnected(), + ) + .unwrap(); + let key1 = KeyPair::from_secret( + Secret::from_str("0000000000000000000000000000000000000000000000000000000000000001") + .unwrap(), + ) + .unwrap(); + + // The only difference to version 2 is that the contract now knows the transaction's gas price and data. + // So we only test those: The contract allows only transactions with either nonzero gas price or short data. + + let filter = TransactionFilter::from_params(spec.params()).unwrap(); + let mut tx = TypedTransaction::EIP1559Transaction(EIP1559TransactionTx { + transaction: AccessListTx::new( + Transaction::default(), + vec![], + ), + max_priority_fee_per_gas: U256::from(0), + }); + tx.tx_mut().action = + Action::Call(Address::from_str("0000000000000000000000000000000000000042").unwrap()); + tx.tx_mut().data = b"01234567".to_vec(); + tx.tx_mut().gas_price = 0.into(); + + let genesis = client.block_hash(BlockId::Latest).unwrap(); + let block_number = 1; + + // Data too long and gas price zero. This transaction is not allowed. + assert!(!filter.transaction_allowed( + &genesis, + block_number, + &tx.clone().sign(key1.secret(), None), + &*client + )); + + // But if we either set a nonzero gas price or short data or both, it is allowed. + tx.tx_mut().gas_price = 1.into(); + assert!(filter.transaction_allowed( + &genesis, + block_number, + &tx.clone().sign(key1.secret(), None), + &*client + )); + tx.tx_mut().data = b"01".to_vec(); + assert!(filter.transaction_allowed( + &genesis, + block_number, + &tx.clone().sign(key1.secret(), None), + &*client + )); + tx.tx_mut().gas_price = 0.into(); + assert!(filter.transaction_allowed( + &genesis, + block_number, + &tx.clone().sign(key1.secret(), None), + &*client + )); + } + /// Contract code: https://gist.github.com/arkpar/38a87cb50165b7e683585eec71acb05a #[test] fn transaction_filter_deprecated() {