diff --git a/node/types/blob.go b/node/types/blob.go index 49ac5dc6..3dcd1750 100644 --- a/node/types/blob.go +++ b/node/types/blob.go @@ -120,77 +120,117 @@ func DecodeTxsFromBytes(txsBytes []byte) (eth.Transactions, error) { txs := make(eth.Transactions, 0) for { var ( - firstByte byte - fullTxBytes []byte - innerTx eth.TxData - err error + typeByte byte + err error ) - if err = binary.Read(reader, binary.BigEndian, &firstByte); err != nil { - // if the blob byte array is completely consumed, then break the loop + if err = binary.Read(reader, binary.BigEndian, &typeByte); err != nil { if err == io.EOF { break } return nil, err } - // zero byte is found after valid tx bytes, break the loop - if firstByte == 0 { + if typeByte == 0 { break } - switch firstByte { - case eth.AccessListTxType: - if err := binary.Read(reader, binary.BigEndian, &firstByte); err != nil { + switch typeByte { + case eth.AccessListTxType, eth.DynamicFeeTxType, eth.SetCodeTxType: + tx, err := decodeTypedTx(typeByte, reader) + if err != nil { return nil, err } - innerTx = new(eth.AccessListTx) - case eth.DynamicFeeTxType: - if err := binary.Read(reader, binary.BigEndian, &firstByte); err != nil { + txs = append(txs, tx) + + case eth.MorphTxType: + tx, err := decodeMorphTx(reader) + if err != nil { return nil, err } - innerTx = new(eth.DynamicFeeTx) - case eth.SetCodeTxType: - if err := binary.Read(reader, binary.BigEndian, &firstByte); err != nil { - return nil, err + txs = append(txs, tx) + + default: + if typeByte <= 0xf7 { + return nil, fmt.Errorf("not supported tx type: %d", typeByte) } - innerTx = new(eth.SetCodeTx) - case eth.MorphTxType: - if err := binary.Read(reader, binary.BigEndian, &firstByte); err != nil { + fullTxBytes, err := extractInnerTxFullBytes(typeByte, reader) + if err != nil { return nil, err } - innerTx = new(eth.MorphTx) - default: - if firstByte <= 0xf7 { // legacy tx first byte must be greater than 0xf7(247) - return nil, fmt.Errorf("not supported tx type: %d", firstByte) + var inner eth.LegacyTx + if err = rlp.DecodeBytes(fullTxBytes, &inner); err != nil { + return nil, err } - innerTx = new(eth.LegacyTx) + txs = append(txs, eth.NewTx(&inner)) } + } + return txs, nil +} - // we support the tx types of LegacyTxType/AccessListTxType/DynamicFeeTxType - //if firstByte == eth.AccessListTxType || firstByte == eth.DynamicFeeTxType { - // // the firstByte here is used to indicate tx type, so skip it - // if err := binary.Read(reader, binary.BigEndian, &firstByte); err != nil { - // return nil, err - // } - //} else if firstByte <= 0xf7 { // legacy tx first byte must be greater than 0xf7(247) - // return nil, fmt.Errorf("not supported tx type: %d", firstByte) - //} - fullTxBytes, err = extractInnerTxFullBytes(firstByte, reader) - if err != nil { - return nil, err - } - if err = rlp.DecodeBytes(fullTxBytes, innerTx); err != nil { +// decodeTypedTx decodes a standard EIP-2718 typed tx (AccessList, DynamicFee, SetCode) +// from the reader. The type byte has already been consumed; the next byte is the RLP prefix. +func decodeTypedTx(typeByte byte, reader io.Reader) (*eth.Transaction, error) { + var rlpPrefix byte + if err := binary.Read(reader, binary.BigEndian, &rlpPrefix); err != nil { + return nil, err + } + rlpBytes, err := extractInnerTxFullBytes(rlpPrefix, reader) + if err != nil { + return nil, err + } + txBinary := make([]byte, 0, 1+len(rlpBytes)) + txBinary = append(txBinary, typeByte) + txBinary = append(txBinary, rlpBytes...) + + var tx eth.Transaction + if err := tx.UnmarshalBinary(txBinary); err != nil { + return nil, err + } + return &tx, nil +} + +// decodeMorphTx decodes a MorphTx from the reader. The type byte (0x7f) has already +// been consumed. MorphTx has two wire formats: +// - V0: type(0x7f) || RLP(fields) — next byte is RLP prefix (>= 0xC0) +// - V1: type(0x7f) || version(0x01) || RLP(fields) — next byte is version, then RLP prefix +func decodeMorphTx(reader io.Reader) (*eth.Transaction, error) { + var nextByte byte + if err := binary.Read(reader, binary.BigEndian, &nextByte); err != nil { + return nil, err + } + + var versionPrefix []byte + rlpFirstByte := nextByte + if nextByte != 0 && nextByte < 0xC0 { + // V1+: nextByte is the version byte, read the actual RLP prefix + versionPrefix = []byte{nextByte} + if err := binary.Read(reader, binary.BigEndian, &rlpFirstByte); err != nil { return nil, err } - txs = append(txs, eth.NewTx(innerTx)) } - return txs, nil + + rlpBytes, err := extractInnerTxFullBytes(rlpFirstByte, reader) + if err != nil { + return nil, err + } + + txBinary := make([]byte, 0, 1+len(versionPrefix)+len(rlpBytes)) + txBinary = append(txBinary, eth.MorphTxType) + txBinary = append(txBinary, versionPrefix...) + txBinary = append(txBinary, rlpBytes...) + + var tx eth.Transaction + if err := tx.UnmarshalBinary(txBinary); err != nil { + return nil, err + } + return &tx, nil } func extractInnerTxFullBytes(firstByte byte, reader io.Reader) ([]byte, error) { - //the occupied byte length for storing the size of the following rlp encoded bytes sizeByteLen := firstByte - 0xf7 + if sizeByteLen > 4 { + return nil, fmt.Errorf("invalid RLP size byte length: %d (firstByte=0x%x)", sizeByteLen, firstByte) + } - // the size of the following rlp encoded bytes sizeByte := make([]byte, sizeByteLen) if err := binary.Read(reader, binary.BigEndian, sizeByte); err != nil { return nil, err diff --git a/node/types/blob_test.go b/node/types/blob_test.go index 94c08318..c7a70148 100644 --- a/node/types/blob_test.go +++ b/node/types/blob_test.go @@ -152,3 +152,52 @@ func TestDecodeTxsFromBytes(t *testing.T) { require.EqualValues(t, legacyContractTx.Hash(), txs[1].Hash()) require.EqualValues(t, contractTx.Hash(), txs[2].Hash()) } + +func TestDecodeTxsFromBytes_MorphTxV0(t *testing.T) { + morphV0Bytes := common.FromHex("0x7ff8b7820b02820374835a527f8378d6ff830186a094cfb1186f4e93d60e60a8bdd997427d1f33bc372b80b844a9059cbb000000000000000000000000b055051fb2889be5e9831524f1624941299c49bb0000000000000000000000000000000000000000000000000000000000000064c0068398968080a0953c962c4a4583dadc0ff338166d9f1176a6403a3689b7edcedf583ca401c4cba06ab5c3ef27fa3a8966ae61d994cb658c0d809434ed33dc35f90f9300fcc000c8") + txs, err := DecodeTxsFromBytes(morphV0Bytes) + require.NoError(t, err) + require.EqualValues(t, 1, txs.Len()) + require.EqualValues(t, eth.MorphTxType, txs[0].Type()) +} + +func TestDecodeTxsFromBytes_MorphTxV1(t *testing.T) { + morphV1Bytes := common.FromHex("0x7f01f8a1820b5e808326ff9b8345841b8252089425db2115628f08d952e4aacf06b341c8bc04a7f28080c00480a0deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef936d6f72706820686f6f6469207465737420747801a07c085d4f1dafac10ee14bf91dc549c6fb0b7aad797569881aa78f4461eb92903a070038e7e5551422168f4745b90854affd790c0f3a0886a926e53127ea3d0be65") + txs, err := DecodeTxsFromBytes(morphV1Bytes) + require.NoError(t, err) + require.EqualValues(t, 1, txs.Len()) + require.EqualValues(t, eth.MorphTxType, txs[0].Type()) +} + +func TestDecodeTxsFromBytes_MixedWithMorphTx(t *testing.T) { + morphV0Bytes := common.FromHex("7ff8b7820b02820374835a527f8378d6ff830186a094cfb1186f4e93d60e60a8bdd997427d1f33bc372b80b844a9059cbb000000000000000000000000b055051fb2889be5e9831524f1624941299c49bb0000000000000000000000000000000000000000000000000000000000000064c0068398968080a0953c962c4a4583dadc0ff338166d9f1176a6403a3689b7edcedf583ca401c4cba06ab5c3ef27fa3a8966ae61d994cb658c0d809434ed33dc35f90f9300fcc000c8") + morphV1Bytes := common.FromHex("7f01f8a1820b5e808326ff9b8345841b8252089425db2115628f08d952e4aacf06b341c8bc04a7f28080c00480a0deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef936d6f72706820686f6f6469207465737420747801a07c085d4f1dafac10ee14bf91dc549c6fb0b7aad797569881aa78f4461eb92903a070038e7e5551422168f4745b90854affd790c0f3a0886a926e53127ea3d0be65") + + // Generate other tx types + transferTx, err := generateTransferTx(false) + require.NoError(t, err) + transferTxBz, err := transferTx.MarshalBinary() + require.NoError(t, err) + + legacyTx, err := generateContractTx(true) + require.NoError(t, err) + legacyTxBz, err := legacyTx.MarshalBinary() + require.NoError(t, err) + + // Concatenate: DynamicFee + MorphV0 + Legacy + MorphV1 + var txsBytes []byte + txsBytes = append(txsBytes, transferTxBz...) + txsBytes = append(txsBytes, morphV0Bytes...) + txsBytes = append(txsBytes, legacyTxBz...) + txsBytes = append(txsBytes, morphV1Bytes...) + + txs, err := DecodeTxsFromBytes(txsBytes) + require.NoError(t, err) + require.EqualValues(t, 4, txs.Len()) + require.EqualValues(t, eth.DynamicFeeTxType, txs[0].Type()) + require.EqualValues(t, transferTx.Hash(), txs[0].Hash()) + require.EqualValues(t, eth.MorphTxType, txs[1].Type()) + require.EqualValues(t, eth.LegacyTxType, txs[2].Type()) + require.EqualValues(t, legacyTx.Hash(), txs[2].Hash()) + require.EqualValues(t, eth.MorphTxType, txs[3].Type()) +}