diff --git a/ethcore/machine/res/tx_acl_gas_price.json b/ethcore/machine/res/tx_acl_gas_price.json new file mode 100644 index 000000000..bc355dee7 --- /dev/null +++ b/ethcore/machine/res/tx_acl_gas_price.json @@ -0,0 +1,83 @@ +[ + { + "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": "gasPrice", + "type": "uint256" + }, + { + "name": "data", + "type": "bytes" + } + ], + "name": "allowedTxTypes", + "outputs": [ + { + "name": "", + "type": "uint32" + }, + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } +] diff --git a/ethcore/machine/src/tx_filter.rs b/ethcore/machine/src/tx_filter.rs index 9d479fe24..18074330e 100644 --- a/ethcore/machine/src/tx_filter.rs +++ b/ethcore/machine/src/tx_filter.rs @@ -35,6 +35,7 @@ use keccak_hash::KECCAK_EMPTY; use_contract!(transact_acl_deprecated, "res/tx_acl_deprecated.json"); use_contract!(transact_acl, "res/tx_acl.json"); +use_contract!(transact_acl_gas_price, "res/tx_acl_gas_price.json"); const MAX_CACHE_SIZE: usize = 4096; @@ -86,6 +87,7 @@ impl TransactionFilter { let sender = transaction.sender(); let value = transaction.value; + let gas_price = transaction.gas_price; let key = (*parent_hash, sender); if let Some(permissions) = permission_cache.get_mut(&key) { @@ -115,6 +117,19 @@ impl TransactionFilter { (tx_permissions::NONE, true) }) }, + 3 => { + trace!(target: "tx_filter", "Using filter with gas price and data"); + let (data, decoder) = transact_acl_gas_price::functions::allowed_tx_types::call( + sender, to, value, gas_price, transaction.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) @@ -138,8 +153,8 @@ impl TransactionFilter { permission_cache.insert((*parent_hash, sender), permissions); } trace!(target: "tx_filter", - "Given transaction data: sender: {:?} to: {:?} value: {}. Permissions required: {:X}, got: {:X}", - sender, to, value, tx_type, permissions + "Given transaction data: sender: {:?} to: {:?} value: {}, gas_price: {}. Permissions required: {:X}, got: {:X}", + sender, to, value, gas_price, tx_type, permissions ); permissions & tx_type != 0 } @@ -171,7 +186,7 @@ mod test { /// Contract code: https://gist.github.com/VladLupashevskyi/84f18eabb1e4afadf572cf92af3e7e7f #[test] - fn transaction_filter() { + fn transaction_filter_ver_2() { let spec_data = include_str!("../../res/tx_permission_tests/contract_ver_2_genesis.json"); let db = test_helpers::new_db(); @@ -248,6 +263,48 @@ mod test { assert!(!filter.transaction_allowed(&genesis, block_number, &basic_tx_with_ether_and_to_key6.clone().sign(key7.secret(), None), &*client)); } + /// Contract code: res/tx_permission_tests/contract_ver_3.sol + #[test] + fn transaction_filter_ver_3() { + let spec_data = include_str!("../../res/tx_permission_tests/contract_ver_3_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("0000000000000000000000000000000000000000000000000000000000000001")).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 = Transaction::default(); + tx.action = Action::Call(Address::from_str("0000000000000000000000000000000000000042").unwrap()); + tx.data = b"01234567".to_vec(); + tx.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.gas_price = 1.into(); + assert!(filter.transaction_allowed(&genesis, block_number, &tx.clone().sign(key1.secret(), None), &*client)); + tx.data = b"01".to_vec(); + assert!(filter.transaction_allowed(&genesis, block_number, &tx.clone().sign(key1.secret(), None), &*client)); + tx.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() { diff --git a/ethcore/res/tx_permission_tests/contract_ver_3.sol b/ethcore/res/tx_permission_tests/contract_ver_3.sol new file mode 100644 index 000000000..b361815dc --- /dev/null +++ b/ethcore/res/tx_permission_tests/contract_ver_3.sol @@ -0,0 +1,60 @@ +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 3; + } + + /// @dev Defines the allowed transaction types which may be initiated by the specified sender with + /// the specified gas price and data. Used by the Parity engine each time a transaction is about to be + /// included into a block. See https://wiki.parity.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 _gasPrice Gas price in wei 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 _gasPrice, + bytes memory _data + ) + public + view + returns(uint32 typesMask, bool cache) + { + if (_gasPrice > 0 || _data.length < 4) return (All, false); + return (None, false); + } +} diff --git a/ethcore/res/tx_permission_tests/contract_ver_3_genesis.json b/ethcore/res/tx_permission_tests/contract_ver_3_genesis.json new file mode 100644 index 000000000..a40b6be05 --- /dev/null +++ b/ethcore/res/tx_permission_tests/contract_ver_3_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": "0x0000000000000000000000000000000000000005", + "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 } } } }, + "0000000000000000000000000000000000000005": { + "balance": "1", + "constructor": "6060604052341561000f57600080fd5b61035e8061001e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063469ab1e31461006757806375d0c0dc14610098578063a0a8e46014610126578063b9056afa1461014f575b600080fd5b341561007257600080fd5b61007a610227565b60405180826000191660001916815260200191505060405180910390f35b34156100a357600080fd5b6100ab610298565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100eb5780820151818401526020810190506100d0565b50505050905090810190601f1680156101185780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561013157600080fd5b6101396102db565b6040518082815260200191505060405180910390f35b341561015a57600080fd5b6101fa600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190803590602001909190803590602001908201803590602001908080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050919050506102e4565b604051808363ffffffff1663ffffffff168152602001821515151581526020019250505060405180910390f35b6000610231610298565b6040518082805190602001908083835b6020831015156102665780518252602082019150602081019050602083039250610241565b6001836020036101000a0380198251168184511680821785525050505050509050019150506040518091039020905090565b6102a061031e565b6040805190810160405280601681526020017f54585f5045524d495353494f4e5f434f4e545241435400000000000000000000815250905090565b60006003905090565b60008060008411806102f7575060048351105b1561030c5763ffffffff600091509150610314565b600080915091505b9550959350505050565b6020604051908101604052806000815250905600a165627a7a72305820be61565bc09fec6e9223a1fecd2e94783ca5c6f506c03f71d479a8c3285493310029" + } + } +}