conflux-rust icon indicating copy to clipboard operation
conflux-rust copied to clipboard

Implement EIP-2718 (Typed Transaction Envelope) for Both Core Space and eSpace

Open darwintree opened this issue 10 months ago • 7 comments

EIP-2718 should be implemented before EIP-1559 be implemented. This EIP can be accomplished for core space at the same time if more transaction types need to be introduced

darwintree avatar Apr 17 '24 03:04 darwintree

Current Ethereum EIP-1559 design:

Raw transaction structure: 0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, amount, data, access_list, signature_y_parity, signature_r, signature_s]).

The signature_y_parity, signature_r, signature_s elements of this transaction represent a secp256k1 signature over keccak256(0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, amount, data, access_list]))

darwintree avatar Apr 19 '24 07:04 darwintree

What to do first: Refactor the transaction decoding: Currently, the transaction decoding is somewhat innatural due to some reason.

  1. transaction sent from cfx/eth_sendRawTransaction will be processed by the same decoder
  2. the transaction is decoded regardless of its source rpc, but depends on the transaction structure:

impl Decodable for TransactionWithSignatureSerializePart {
    fn decode(rlp: &Rlp) -> Result<Self, DecoderError> {
        match rlp.item_count()? {
            4 => {
                let unsigned: NativeTransaction = rlp.val_at(0)?;
                let v: u8 = rlp.val_at(1)?;
                let r: U256 = rlp.val_at(2)?;
                let s: U256 = rlp.val_at(3)?;
                Ok(TransactionWithSignatureSerializePart {
                    unsigned: Transaction::Native(unsigned),
                    v,
                    r,
                    s,
                })
            }
            9 => {
                let nonce: U256 = rlp.val_at(0)?;
                let gas_price: U256 = rlp.val_at(1)?;
                let gas: U256 = rlp.val_at(2)?;
                let action: Action = rlp.val_at(3)?;
                let value: U256 = rlp.val_at(4)?;
                let data: Vec<u8> = rlp.val_at(5)?;
                let legacy_v: u64 = rlp.val_at(6)?;
                let r: U256 = rlp.val_at(7)?;
                let s: U256 = rlp.val_at(8)?;

                let v = eip155_signature::extract_standard_v(legacy_v);
                let chain_id =
                    match eip155_signature::extract_chain_id_from_legacy_v(
                        legacy_v,
                    ) {
                        Some(chain_id) if chain_id > (u32::MAX as u64) => {
                            return Err(DecoderError::Custom(
                                "Does not support chain_id >= 2^32",
                            ));
                        }
                        chain_id => chain_id.map(|x| x as u32),
                    };

                Ok(TransactionWithSignatureSerializePart {
                    unsigned: Transaction::Ethereum(Eip155Transaction {
                        nonce,
                        gas_price,
                        gas,
                        action,
                        value,
                        chain_id,
                        data,
                    }),
                    v,
                    r,
                    s,
                })
            }
            _ => Err(DecoderError::RlpInvalidLength),
        }
    }
}

  1. the rpc handler checks if the transaction space is as expected, and may reject the transaction if the decoded transaction type does not match

What is expected: Invoking decoder based on the handler

darwintree avatar Apr 19 '24 09:04 darwintree

Espace and core space transactions will be packed in the same block, and it's still up to the decoder to decide the transaction type only based on the transaction structure. In this case, there seems no need to add another simpler decoder for each transaction type?

peilun-conflux avatar Apr 22 '24 05:04 peilun-conflux

Data deserialization should always be independent of the system-level logic. So the decoder must be able to distinguish the core transaction and space transaction from the data structure only.

ChenxingLi avatar Apr 22 '24 05:04 ChenxingLi

Data deserialization should always be independent of the system-level logic. So the decoder must be able to distinguish the core transaction and space transaction from the data structure only.

Espace and core space transactions will be packed in the same block, and it's still up to the decoder to decide the transaction type only based on the transaction structure. In this case, there seems no need to add another simpler decoder for each transaction type?

What about adding more spaces, for example, supposing another space is added and in the specific space it just simply reuses the ethereum transaction structure? Is it possible to distinguish the espace transaction and another space transaction without extra refactoring for the code?

darwintree avatar Apr 22 '24 07:04 darwintree

What about adding more spaces, for example, supposing another space is added and in the specific space it just simply reuses the ethereum transaction structure? Is it possible to distinguish the espace transaction and another space transaction without extra refactoring for the code?

I think the main problem is that our espace transaction structure must be compatible with Eth transactions, which leaves us no room for customization. However, if we must use the same structure in another space for some reason, they can be distinguished by the chain id.

peilun-conflux avatar Apr 22 '24 08:04 peilun-conflux

What about adding more spaces, for example, supposing another space is added and in the specific space it just simply reuses the ethereum transaction structure? Is it possible to distinguish the espace transaction and another space transaction without extra refactoring for the code?

I think the main problem is that our espace transaction structure must be compatible with Eth transactions, which leaves us no room for customization. However, if we must use the same structure in another space for some reason, they can be distinguished by the chain id.

Then that makes sense, in that way the space() method implementation needs changing. But a refactor might still be required. Currently, transaction decoded from rlp but from 2718, the transaction needs to be decoded from bytes EIP1559 format: 0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, amount, data, access_list, signature_y_parity, signature_r, signature_s])

darwintree avatar Apr 22 '24 09:04 darwintree