gakonst / ethers-rs

Complete Ethereum & Celo library and wallet implementation in Rust. https://docs.rs/ethers
Apache License 2.0
2.48k stars 797 forks source link

Bad RLP Payload with Very High Gas Limit #1617

Open benorgera opened 2 years ago

benorgera commented 2 years ago

Version

├── ethers v0.17.0
│   ├── ethers-addressbook v0.17.0
│   │   ├── ethers-core v0.17.0
│   ├── ethers-contract v0.17.0
│   │   ├── ethers-contract-abigen v0.17.0
│   │   │   ├── ethers-core v0.17.0 (*)
│   │   ├── ethers-contract-derive v0.17.0 (proc-macro)
│   │   │   ├── ethers-contract-abigen v0.17.0
│   │   │   │   ├── ethers-core v0.17.0
│   │   │   ├── ethers-core v0.17.0 (*)
│   │   ├── ethers-core v0.17.0 (*)
│   │   ├── ethers-providers v0.17.0
│   │   │   ├── ethers-core v0.17.0 (*)
│   ├── ethers-core v0.17.0 (*)
│   ├── ethers-etherscan v0.17.0
│   │   ├── ethers-core v0.17.0 (*)
│   ├── ethers-middleware v0.17.0
│   │   ├── ethers-contract v0.17.0 (*)
│   │   ├── ethers-core v0.17.0 (*)
│   │   ├── ethers-etherscan v0.17.0 (*)
│   │   ├── ethers-providers v0.17.0 (*)
│   │   ├── ethers-signers v0.17.0
│   │   │   ├── ethers-core v0.17.0 (*)
│   ├── ethers-providers v0.17.0 (*)
│   └── ethers-signers v0.17.0 (*)
├── ethers-flashbots v0.10.0
│   ├── ethers v0.17.0 (*)

Platform Linux 4.15.0-20-generic #21-Ubuntu SMP Tue Apr 24 06:16:15 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

Description When signing an EIP1559 TypedTransaction via tx.rlp_signed(...) and submitting it to mainnet using send_raw_transaction(...), my RPC replies with JsonRpcClientError(JsonRpcError(JsonRpcError { code: -32600, message: \"reading transaction object failed.

Upon closer inspection, the rawTx payload is not well-formed RLP.

I used this code to generate, sign, and send the transaction:

let calldata = ABIGENED_CONTRACT.METHOD_NAME().calldata();

let tx: TypedTransaction = TypedTransaction::Eip1559(Eip1559TransactionRequest {
        from: Some(self.our_addr),
        to: Some(NameOrAddress::Address(self.contract_addr)),
        value: Some(price),
        data: calldata,
        nonce: Some(self.our_addr_next_nonce),
        chain_id: Some(U64::from(u64::from(Chain::Mainnet))),
        access_list: AccessList::default(),
        gas: Some(U256::MAX),
        max_priority_fee_per_gas: Some(max_priority),
        max_fee_per_gas: Some(max_gas),
});

let wallet : Wallet<_> = PRIVATE_KEY.parse();

let signature = wallet.sign_transaction_sync(tx);

let provider = Provider::<Http>::try_from(RPC_URL).unwrap();

let rawTx : Bytes = tx.rlp_signed(&signature);

provider.send_raw_transaction(rawTx).await;

A sample rawTx I got from this code is 0x02f8ec010c8080a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9423706b3d3a6667fafa53fcbf54f4933e5f912471872276193e524000b8645bebdaf60000000000000000000000000000000000000000000000000000000000000045000000000000000000000000000000000000000000000000002276193e5240000000000000000000000000000000000000000000000000000000000000000000c001a03cd81a267232917ebe4b9320ee12160157106da4630e71ed47b61617deadeb99a0388707564882939ad16d0a0675ffcab0a1a214d533f0880d9c285f9554a94dd1.

Plugging this rawTx into a few RLP decoders (ie. https://gist.github.com/miguelmota/9099b705cf433336036065ab748c8404) I see that this is a bad payload and failing assertions that a remainder must be zero.

I'm not an RLP expert but I believe I should be getting well formed RLP for my rawTx regardless of the fields I set in my Eip1559TransactionRequest. But maybe this has something to do with the specific data in my tx (ie. gas limit int max). Here is a sample tx which I got from the above code which produces a bad payload (addresses changed for anonymity):

Eip1559(
    Eip1559TransactionRequest {
        from: Some(
            0xa281aa6e20b174764549592bd6a305f38d949db5,
        ),
        to: Some(
            Address(
                0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48,
            ),
        ),
        gas: Some(
            115792089237316195423570985008687907853269984665640564039457584007913129639935,
        ),
        value: Some(
            9600000000000002,
        ),
        data: Some(
            Bytes(
                b"[\xeb\xda\xf6\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0E\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\"\x1b&-\xd8\0\x02\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
            ),
        ),
        nonce: Some(
            12,
        ),
        access_list: AccessList(
            [],
        ),
        max_priority_fee_per_gas: Some(
            1000000000,
        ),
        max_fee_per_gas: Some(
            29031605598,
        ),
        chain_id: Some(
            1,
        ),
    },
)
benorgera commented 2 years ago

It appears using INT_MAX as the gas limit did cause the bad RLP. Same issue using INT_MAX - 1. The intention here was to have transactions gas only limited by the network.

To get this behavior, I am able to specify some sufficiently large number, ie. 300000000000 works (10000 times the current ethereum block gas limit). I don't know what is the first number large enough to cause this RLP bug, but there is probably an overflowing type conversion somewhere. AFAIK you should be able to specify INT_MAX without error

mattsse commented 2 years ago

cc @Rjected

maybe this has something to do with geth gaslimit type which is perhaps an u64?

in any case, even it would be rlp deserialized correctly it would be rejected right away

Rjected commented 2 years ago

yeah, the gaslimit in geth seems to be a u64, we can probably safely change that (as well as nonce) from a Option<U256> to a Option<U64>. The only thing that I'm wondering is why 300000000000 would cause an issue, since that's not over 64 bits.

should check this out more and make sure the tx types are consistent with geth

gakonst commented 1 year ago

Is it OK for us to change the gas limit to U64? If it doesn't break any RPC decoding stuff it might be fine. I think revm also uses u64.