From 4fb310de0e930c3d8a39067e874f234c38113ec4 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Fri, 24 Feb 2023 23:16:40 +0000 Subject: [PATCH 1/9] add ArbitrumExtendedTx transaction type --- core/types/arb_types.go | 36 +++++++++++++ core/types/transaction.go | 5 ++ core/types/transaction_marshalling.go | 78 +++++++++++++++++++++++++++ 3 files changed, 119 insertions(+) diff --git a/core/types/arb_types.go b/core/types/arb_types.go index 84be0cbc57..181e8d1a4f 100644 --- a/core/types/arb_types.go +++ b/core/types/arb_types.go @@ -3,6 +3,7 @@ package types import ( "context" "encoding/binary" + "errors" "fmt" "math/big" @@ -13,6 +14,10 @@ import ( "github.com/ethereum/go-ethereum/common" ) +const ( + ArbitrumExtendedTxFlagEnableTip uint64 = (1 << 0) +) + type fallbackError struct { } @@ -460,3 +465,34 @@ func DeserializeHeaderExtraInformation(header *Header) (HeaderInfo, error) { extra.ArbOSFormatVersion = binary.BigEndian.Uint64(header.MixDigest[16:24]) return extra, nil } + +type ArbitrumExtendedTxData struct { + DynamicFeeTx + Flags uint64 +} + +func NewArbitrumExtendedTx(origTx *Transaction, flags uint64) (*Transaction, error) { + if origTx.Type() != DynamicFeeTxType { + return nil, errors.New("attempt to arbitrum-wrap into extended transaction a transaction that is not a dynamic fee transaction") + } + dynamicPtr := origTx.GetInner().(*DynamicFeeTx) + inner := ArbitrumExtendedTxData{ + DynamicFeeTx: *dynamicPtr, + Flags: flags, + } + return NewTx(&inner), nil +} + +func (tx *ArbitrumExtendedTxData) copy() TxData { + dynamicCopy := tx.DynamicFeeTx.copy().(*DynamicFeeTx) + return &ArbitrumExtendedTxData{ + DynamicFeeTx: *dynamicCopy, + Flags: tx.Flags, + } +} + +func (tx *ArbitrumExtendedTxData) txType() byte { return ArbitrumExtendedTxType } + +func (tx *ArbitrumExtendedTxData) EnableTipFlag() bool { + return (tx.Flags & ArbitrumExtendedTxFlagEnableTip) != 0 +} diff --git a/core/types/transaction.go b/core/types/transaction.go index 5debed4493..2c2aeee0fb 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -45,6 +45,7 @@ const ( LegacyTxType = iota AccessListTxType DynamicFeeTxType + ArbitrumExtendedTxType = 99 ArbitrumDepositTxType = 100 ArbitrumUnsignedTxType = 101 ArbitrumContractTxType = 102 @@ -228,6 +229,10 @@ func (tx *Transaction) decodeTyped(b []byte, arbParsing bool) (TxData, error) { var inner DynamicFeeTx err := rlp.DecodeBytes(b[1:], &inner) return &inner, err + case ArbitrumExtendedTxType: + var inner ArbitrumExtendedTxData + err := rlp.DecodeBytes(b[1:], &inner) + return &inner, err default: return nil, ErrTxTypeNotSupported } diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go index a5b42f3faf..05508a882b 100644 --- a/core/types/transaction_marshalling.go +++ b/core/types/transaction_marshalling.go @@ -62,6 +62,7 @@ type txJSON struct { MaxSubmissionFee *hexutil.Big `json:"maxSubmissionFee,omitempty"` // SubmitRetryable EffectiveGasPrice *hexutil.Uint64 `json:"effectiveGasPrice,omitempty"` // ArbLegacy L1BlockNumber *hexutil.Uint64 `json:"l1BlockNumber,omitempty"` // ArbLegacy + Flags *hexutil.Uint64 `json:"flags,omitempty"` // ArbExtendedTx // Only used for encoding - and for ArbLegacy Hash common.Hash `json:"hash"` @@ -122,6 +123,20 @@ func (t *Transaction) MarshalJSON() ([]byte, error) { enc.V = (*hexutil.Big)(tx.V) enc.R = (*hexutil.Big)(tx.R) enc.S = (*hexutil.Big)(tx.S) + case *ArbitrumExtendedTxData: + enc.ChainID = (*hexutil.Big)(tx.ChainID) + enc.AccessList = &tx.AccessList + enc.Nonce = (*hexutil.Uint64)(&tx.Nonce) + enc.Gas = (*hexutil.Uint64)(&tx.Gas) + enc.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap) + enc.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap) + enc.Value = (*hexutil.Big)(tx.Value) + enc.Data = (*hexutil.Bytes)(&tx.Data) + enc.To = t.To() + enc.V = (*hexutil.Big)(tx.V) + enc.R = (*hexutil.Big)(tx.R) + enc.S = (*hexutil.Big)(tx.S) + enc.Flags = (*hexutil.Uint64)(&tx.Flags) case *ArbitrumLegacyTxData: enc.Nonce = (*hexutil.Uint64)(&tx.Nonce) enc.Gas = (*hexutil.Uint64)(&tx.Gas) @@ -361,6 +376,69 @@ func (t *Transaction) UnmarshalJSON(input []byte) error { } } + case ArbitrumExtendedTxType: + var itx DynamicFeeTx + // Access list is optional for now. + if dec.AccessList != nil { + itx.AccessList = *dec.AccessList + } + if dec.ChainID == nil { + return errors.New("missing required field 'chainId' in transaction") + } + itx.ChainID = (*big.Int)(dec.ChainID) + if dec.To != nil { + itx.To = dec.To + } + if dec.Nonce == nil { + return errors.New("missing required field 'nonce' in transaction") + } + itx.Nonce = uint64(*dec.Nonce) + if dec.MaxPriorityFeePerGas == nil { + return errors.New("missing required field 'maxPriorityFeePerGas' for txdata") + } + itx.GasTipCap = (*big.Int)(dec.MaxPriorityFeePerGas) + if dec.MaxFeePerGas == nil { + return errors.New("missing required field 'maxFeePerGas' for txdata") + } + itx.GasFeeCap = (*big.Int)(dec.MaxFeePerGas) + if dec.Gas == nil { + return errors.New("missing required field 'gas' for txdata") + } + itx.Gas = uint64(*dec.Gas) + if dec.Value == nil { + return errors.New("missing required field 'value' in transaction") + } + itx.Value = (*big.Int)(dec.Value) + if dec.Data == nil { + return errors.New("missing required field 'input' in transaction") + } + itx.Data = *dec.Data + if dec.V == nil { + return errors.New("missing required field 'v' in transaction") + } + itx.V = (*big.Int)(dec.V) + if dec.R == nil { + return errors.New("missing required field 'r' in transaction") + } + itx.R = (*big.Int)(dec.R) + if dec.S == nil { + return errors.New("missing required field 's' in transaction") + } + itx.S = (*big.Int)(dec.S) + withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 + if withSignature { + if err := sanityCheckSignature(itx.V, itx.R, itx.S, false); err != nil { + return err + } + } + if dec.Flags == nil { + return errors.New("missing required field 'Flags' in transaction") + } + inner = &ArbitrumExtendedTxData{ + DynamicFeeTx: itx, + Flags: uint64(*dec.Flags), + } + case ArbitrumLegacyTxType: var itx LegacyTx if dec.To != nil { From e1cc692a9ee89491029e5428fbc9b12ebbe9f67c Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Mon, 27 Feb 2023 21:56:33 +0000 Subject: [PATCH 2/9] remove flags field, rename the new tx to ArbitrumTippingTx --- core/types/arb_types.go | 25 ++++----- core/types/transaction.go | 6 +-- core/types/transaction_marshalling.go | 74 +++------------------------ 3 files changed, 20 insertions(+), 85 deletions(-) diff --git a/core/types/arb_types.go b/core/types/arb_types.go index 181e8d1a4f..b31c947b74 100644 --- a/core/types/arb_types.go +++ b/core/types/arb_types.go @@ -15,7 +15,7 @@ import ( ) const ( - ArbitrumExtendedTxFlagEnableTip uint64 = (1 << 0) + ArbitrumTippingTxFlagEnableTip uint64 = (1 << 0) ) type fallbackError struct { @@ -466,33 +466,26 @@ func DeserializeHeaderExtraInformation(header *Header) (HeaderInfo, error) { return extra, nil } -type ArbitrumExtendedTxData struct { +type ArbitrumTippingTx struct { DynamicFeeTx - Flags uint64 } -func NewArbitrumExtendedTx(origTx *Transaction, flags uint64) (*Transaction, error) { - if origTx.Type() != DynamicFeeTxType { +func NewArbitrumTippingTx(origTx *Transaction) (*Transaction, error) { + dynamicPtr, ok := origTx.GetInner().(*DynamicFeeTx) + if origTx.Type() != DynamicFeeTxType || !ok { return nil, errors.New("attempt to arbitrum-wrap into extended transaction a transaction that is not a dynamic fee transaction") } - dynamicPtr := origTx.GetInner().(*DynamicFeeTx) - inner := ArbitrumExtendedTxData{ + inner := ArbitrumTippingTx{ DynamicFeeTx: *dynamicPtr, - Flags: flags, } return NewTx(&inner), nil } -func (tx *ArbitrumExtendedTxData) copy() TxData { +func (tx *ArbitrumTippingTx) copy() TxData { dynamicCopy := tx.DynamicFeeTx.copy().(*DynamicFeeTx) - return &ArbitrumExtendedTxData{ + return &ArbitrumTippingTx{ DynamicFeeTx: *dynamicCopy, - Flags: tx.Flags, } } -func (tx *ArbitrumExtendedTxData) txType() byte { return ArbitrumExtendedTxType } - -func (tx *ArbitrumExtendedTxData) EnableTipFlag() bool { - return (tx.Flags & ArbitrumExtendedTxFlagEnableTip) != 0 -} +func (tx *ArbitrumTippingTx) txType() byte { return ArbitrumTippingTxType } diff --git a/core/types/transaction.go b/core/types/transaction.go index 2c2aeee0fb..4abf03c911 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -45,7 +45,7 @@ const ( LegacyTxType = iota AccessListTxType DynamicFeeTxType - ArbitrumExtendedTxType = 99 + ArbitrumTippingTxType = 99 ArbitrumDepositTxType = 100 ArbitrumUnsignedTxType = 101 ArbitrumContractTxType = 102 @@ -229,8 +229,8 @@ func (tx *Transaction) decodeTyped(b []byte, arbParsing bool) (TxData, error) { var inner DynamicFeeTx err := rlp.DecodeBytes(b[1:], &inner) return &inner, err - case ArbitrumExtendedTxType: - var inner ArbitrumExtendedTxData + case ArbitrumTippingTxType: + var inner ArbitrumTippingTx err := rlp.DecodeBytes(b[1:], &inner) return &inner, err default: diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go index 05508a882b..facfbf0f8e 100644 --- a/core/types/transaction_marshalling.go +++ b/core/types/transaction_marshalling.go @@ -62,7 +62,6 @@ type txJSON struct { MaxSubmissionFee *hexutil.Big `json:"maxSubmissionFee,omitempty"` // SubmitRetryable EffectiveGasPrice *hexutil.Uint64 `json:"effectiveGasPrice,omitempty"` // ArbLegacy L1BlockNumber *hexutil.Uint64 `json:"l1BlockNumber,omitempty"` // ArbLegacy - Flags *hexutil.Uint64 `json:"flags,omitempty"` // ArbExtendedTx // Only used for encoding - and for ArbLegacy Hash common.Hash `json:"hash"` @@ -123,7 +122,8 @@ func (t *Transaction) MarshalJSON() ([]byte, error) { enc.V = (*hexutil.Big)(tx.V) enc.R = (*hexutil.Big)(tx.R) enc.S = (*hexutil.Big)(tx.S) - case *ArbitrumExtendedTxData: + // TODO could we collapse it to: `case *DynamicFeeTx, *ArbitrumTippingTx:` + case *ArbitrumTippingTx: enc.ChainID = (*hexutil.Big)(tx.ChainID) enc.AccessList = &tx.AccessList enc.Nonce = (*hexutil.Uint64)(&tx.Nonce) @@ -136,7 +136,6 @@ func (t *Transaction) MarshalJSON() ([]byte, error) { enc.V = (*hexutil.Big)(tx.V) enc.R = (*hexutil.Big)(tx.R) enc.S = (*hexutil.Big)(tx.S) - enc.Flags = (*hexutil.Uint64)(&tx.Flags) case *ArbitrumLegacyTxData: enc.Nonce = (*hexutil.Uint64)(&tx.Nonce) enc.Gas = (*hexutil.Uint64)(&tx.Gas) @@ -319,9 +318,8 @@ func (t *Transaction) UnmarshalJSON(input []byte) error { } } - case DynamicFeeTxType: + case DynamicFeeTxType, ArbitrumTippingTxType: var itx DynamicFeeTx - inner = &itx // Access list is optional for now. if dec.AccessList != nil { itx.AccessList = *dec.AccessList @@ -375,68 +373,12 @@ func (t *Transaction) UnmarshalJSON(input []byte) error { return err } } - - case ArbitrumExtendedTxType: - var itx DynamicFeeTx - // Access list is optional for now. - if dec.AccessList != nil { - itx.AccessList = *dec.AccessList - } - if dec.ChainID == nil { - return errors.New("missing required field 'chainId' in transaction") - } - itx.ChainID = (*big.Int)(dec.ChainID) - if dec.To != nil { - itx.To = dec.To - } - if dec.Nonce == nil { - return errors.New("missing required field 'nonce' in transaction") - } - itx.Nonce = uint64(*dec.Nonce) - if dec.MaxPriorityFeePerGas == nil { - return errors.New("missing required field 'maxPriorityFeePerGas' for txdata") - } - itx.GasTipCap = (*big.Int)(dec.MaxPriorityFeePerGas) - if dec.MaxFeePerGas == nil { - return errors.New("missing required field 'maxFeePerGas' for txdata") - } - itx.GasFeeCap = (*big.Int)(dec.MaxFeePerGas) - if dec.Gas == nil { - return errors.New("missing required field 'gas' for txdata") - } - itx.Gas = uint64(*dec.Gas) - if dec.Value == nil { - return errors.New("missing required field 'value' in transaction") - } - itx.Value = (*big.Int)(dec.Value) - if dec.Data == nil { - return errors.New("missing required field 'input' in transaction") - } - itx.Data = *dec.Data - if dec.V == nil { - return errors.New("missing required field 'v' in transaction") - } - itx.V = (*big.Int)(dec.V) - if dec.R == nil { - return errors.New("missing required field 'r' in transaction") - } - itx.R = (*big.Int)(dec.R) - if dec.S == nil { - return errors.New("missing required field 's' in transaction") - } - itx.S = (*big.Int)(dec.S) - withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 - if withSignature { - if err := sanityCheckSignature(itx.V, itx.R, itx.S, false); err != nil { - return err + if dec.Type == ArbitrumTippingTxType { + inner = &ArbitrumTippingTx{ + DynamicFeeTx: itx, } - } - if dec.Flags == nil { - return errors.New("missing required field 'Flags' in transaction") - } - inner = &ArbitrumExtendedTxData{ - DynamicFeeTx: itx, - Flags: uint64(*dec.Flags), + } else { + inner = &itx } case ArbitrumLegacyTxType: From de1b3e65be81d2e8c4f108d63f7a46eab9f6f5c2 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Wed, 1 Mar 2023 01:13:10 +0000 Subject: [PATCH 3/9] remove unused const --- core/types/arb_types.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/types/arb_types.go b/core/types/arb_types.go index b31c947b74..097d60755b 100644 --- a/core/types/arb_types.go +++ b/core/types/arb_types.go @@ -14,10 +14,6 @@ import ( "github.com/ethereum/go-ethereum/common" ) -const ( - ArbitrumTippingTxFlagEnableTip uint64 = (1 << 0) -) - type fallbackError struct { } From 3fdc17c76f0d7c205e83c1d2109e8c65c5b496fe Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Thu, 2 Mar 2023 22:14:54 +0000 Subject: [PATCH 4/9] add support for tipping tx to londonSinger --- core/types/arb_types.go | 2 +- core/types/transaction_signing.go | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/core/types/arb_types.go b/core/types/arb_types.go index 097d60755b..14aef0186d 100644 --- a/core/types/arb_types.go +++ b/core/types/arb_types.go @@ -469,7 +469,7 @@ type ArbitrumTippingTx struct { func NewArbitrumTippingTx(origTx *Transaction) (*Transaction, error) { dynamicPtr, ok := origTx.GetInner().(*DynamicFeeTx) if origTx.Type() != DynamicFeeTxType || !ok { - return nil, errors.New("attempt to arbitrum-wrap into extended transaction a transaction that is not a dynamic fee transaction") + return nil, errors.New("attempt to arbitrum-wrap into tipping transaction a transaction that is not a dynamic fee transaction") } inner := ArbitrumTippingTx{ DynamicFeeTx: *dynamicPtr, diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index ad12fa98c8..9fb7370cc5 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -193,7 +193,7 @@ func NewLondonSigner(chainId *big.Int) Signer { } func (s londonSigner) Sender(tx *Transaction) (common.Address, error) { - if tx.Type() != DynamicFeeTxType { + if tx.Type() != DynamicFeeTxType && tx.Type() != ArbitrumTippingTxType { return s.eip2930Signer.Sender(tx) } V, R, S := tx.RawSignatureValues() @@ -212,13 +212,18 @@ func (s londonSigner) Equal(s2 Signer) bool { } func (s londonSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { - txdata, ok := tx.inner.(*DynamicFeeTx) + var txdata TxData + var ok bool + txdata, ok = tx.inner.(*DynamicFeeTx) if !ok { - return s.eip2930Signer.SignatureValues(tx, sig) + txdata, ok = tx.inner.(*ArbitrumTippingTx) + if !ok { + return s.eip2930Signer.SignatureValues(tx, sig) + } } // Check that chain ID of tx matches the signer. We also accept ID zero here, // because it indicates that the chain ID was not specified in the tx. - if txdata.ChainID.Sign() != 0 && txdata.ChainID.Cmp(s.chainId) != 0 { + if txdata.chainID().Sign() != 0 && txdata.chainID().Cmp(s.chainId) != 0 { return nil, nil, nil, ErrInvalidChainId } R, S, _ = decodeSignature(sig) @@ -229,7 +234,7 @@ func (s londonSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big // Hash returns the hash to be signed by the sender. // It does not uniquely identify the transaction. func (s londonSigner) Hash(tx *Transaction) common.Hash { - if tx.Type() != DynamicFeeTxType { + if tx.Type() != DynamicFeeTxType && tx.Type() != ArbitrumTippingTxType { return s.eip2930Signer.Hash(tx) } return prefixedRlpHash( From 8c86873d82f3b439e3be294b22500f5c65fc40fb Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Sun, 5 Mar 2023 16:41:22 +0000 Subject: [PATCH 5/9] add flat rlp tag --- core/types/arb_types.go | 2 +- rlp/decode.go | 15 ++++++++++----- rlp/encode.go | 22 ++++++++++++++++------ rlp/internal/rlpstruct/rlpstruct.go | 5 +++++ 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/core/types/arb_types.go b/core/types/arb_types.go index 14aef0186d..2a881be0a9 100644 --- a/core/types/arb_types.go +++ b/core/types/arb_types.go @@ -463,7 +463,7 @@ func DeserializeHeaderExtraInformation(header *Header) (HeaderInfo, error) { } type ArbitrumTippingTx struct { - DynamicFeeTx + DynamicFeeTx `rlp:"flat"` } func NewArbitrumTippingTx(origTx *Transaction) (*Transaction, error) { diff --git a/rlp/decode.go b/rlp/decode.go index 9214dbfb37..114e2adf0c 100644 --- a/rlp/decode.go +++ b/rlp/decode.go @@ -76,7 +76,7 @@ type Decoder interface { // Note that Decode does not set an input limit for all readers and may be vulnerable to // panics cause by huge value sizes. If you need an input limit, use // -// NewStream(r, limit).Decode(val) +// NewStream(r, limit).Decode(val) func Decode(r io.Reader, val interface{}) error { stream := streamPool.Get().(*Stream) defer streamPool.Put(stream) @@ -172,7 +172,7 @@ func makeDecoder(typ reflect.Type, tags rlpstruct.Tags) (dec decoder, err error) case kind == reflect.Slice || kind == reflect.Array: return makeListDecoder(typ, tags) case kind == reflect.Struct: - return makeStructDecoder(typ) + return makeStructDecoder(typ, tags.Flat) case kind == reflect.Interface: return decodeInterface, nil default: @@ -376,7 +376,7 @@ func decodeByteArray(s *Stream, val reflect.Value) error { return nil } -func makeStructDecoder(typ reflect.Type) (decoder, error) { +func makeStructDecoder(typ reflect.Type, flat bool) (decoder, error) { fields, err := structFields(typ) if err != nil { return nil, err @@ -387,8 +387,10 @@ func makeStructDecoder(typ reflect.Type) (decoder, error) { } } dec := func(s *Stream, val reflect.Value) (err error) { - if _, err := s.List(); err != nil { - return wrapStreamError(err, typ) + if !flat { + if _, err := s.List(); err != nil { + return wrapStreamError(err, typ) + } } for i, f := range fields { err := f.info.decoder(s, val.Field(f.index)) @@ -405,6 +407,9 @@ func makeStructDecoder(typ reflect.Type) (decoder, error) { return addErrorContext(err, "."+typ.Field(f.index).Name) } } + if flat { + return nil + } return wrapStreamError(s.ListEnd(), typ) } return dec, nil diff --git a/rlp/encode.go b/rlp/encode.go index b96505f56d..639c2e56e7 100644 --- a/rlp/encode.go +++ b/rlp/encode.go @@ -158,7 +158,7 @@ func makeWriter(typ reflect.Type, ts rlpstruct.Tags) (writer, error) { case kind == reflect.Slice || kind == reflect.Array: return makeSliceWriter(typ, ts) case kind == reflect.Struct: - return makeStructWriter(typ) + return makeStructWriter(typ, ts.Flat) case kind == reflect.Interface: return writeInterface, nil default: @@ -315,7 +315,7 @@ func makeSliceWriter(typ reflect.Type, ts rlpstruct.Tags) (writer, error) { return wfn, nil } -func makeStructWriter(typ reflect.Type) (writer, error) { +func makeStructWriter(typ reflect.Type, flat bool) (writer, error) { fields, err := structFields(typ) if err != nil { return nil, err @@ -331,13 +331,18 @@ func makeStructWriter(typ reflect.Type) (writer, error) { if firstOptionalField == len(fields) { // This is the writer function for structs without any optional fields. writer = func(val reflect.Value, w *encBuffer) error { - lh := w.list() + var lh int + if !flat { + lh = w.list() + } for _, f := range fields { if err := f.info.writer(val.Field(f.index), w); err != nil { return err } } - w.listEnd(lh) + if !flat { + w.listEnd(lh) + } return nil } } else { @@ -350,13 +355,18 @@ func makeStructWriter(typ reflect.Type) (writer, error) { break } } - lh := w.list() + var lh int + if !flat { + lh = w.list() + } for i := 0; i <= lastField; i++ { if err := fields[i].info.writer(val.Field(fields[i].index), w); err != nil { return err } } - w.listEnd(lh) + if !flat { + w.listEnd(lh) + } return nil } } diff --git a/rlp/internal/rlpstruct/rlpstruct.go b/rlp/internal/rlpstruct/rlpstruct.go index 1edead96ce..1456cc3c07 100644 --- a/rlp/internal/rlpstruct/rlpstruct.go +++ b/rlp/internal/rlpstruct/rlpstruct.go @@ -79,6 +79,9 @@ type Tags struct { // rlp:"-" ignores fields. Ignored bool + + // rlp:"flat" flattens field. + Flat bool } // TagError is raised for invalid struct tags. @@ -183,6 +186,8 @@ func parseTag(field Field, lastPublic int) (Tags, error) { if field.Type.Kind != reflect.Slice { return ts, TagError{Field: name, Tag: t, Err: "field type is not slice"} } + case "flat": + ts.Flat = true default: return ts, TagError{Field: name, Tag: t, Err: "unknown tag"} } From d21ef14db3a8e14767fc098cd3865010a5a10b0b Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Sun, 5 Mar 2023 22:13:39 +0000 Subject: [PATCH 6/9] make tipping tx a subtype of subtyped tx --- accounts/external/backend.go | 5 +++ core/types/arb_types.go | 41 +++++++++++++++++-- core/types/receipt.go | 18 +++++++++ core/types/transaction.go | 27 ++++++++++--- core/types/transaction_marshalling.go | 58 ++++++++++++++++++--------- core/types/transaction_signing.go | 24 +++++++++-- internal/ethapi/api.go | 21 ++++++++++ 7 files changed, 162 insertions(+), 32 deletions(-) diff --git a/accounts/external/backend.go b/accounts/external/backend.go index d403b7e562..b19a7634fd 100644 --- a/accounts/external/backend.go +++ b/accounts/external/backend.go @@ -217,6 +217,11 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio case types.DynamicFeeTxType: args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap()) args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap()) + case types.ArbitrumSubtypedTxType: + if types.GetArbitrumTxSubtype(tx) == types.ArbitrumTippingTxSubtype { + args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap()) + args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap()) + } default: return nil, fmt.Errorf("unsupported tx type %d", tx.Type()) } diff --git a/core/types/arb_types.go b/core/types/arb_types.go index 2a881be0a9..6e2553b5a4 100644 --- a/core/types/arb_types.go +++ b/core/types/arb_types.go @@ -14,6 +14,12 @@ import ( "github.com/ethereum/go-ethereum/common" ) +const ( + arbitrumSubtypeOffset = 0xff + ArbitrumInvalidSubtype = 0 + ArbitrumTippingTxSubtype = 1 +) + type fallbackError struct { } @@ -462,6 +468,32 @@ func DeserializeHeaderExtraInformation(header *Header) (HeaderInfo, error) { return extra, nil } +type ArbitrumSubtypedTx struct { + TxData +} + +func (tx *ArbitrumSubtypedTx) copy() TxData { + return &ArbitrumSubtypedTx{ + TxData: tx.TxData.copy(), + } +} + +func (tx *ArbitrumSubtypedTx) txType() byte { return ArbitrumSubtypedTxType } +func (tx *ArbitrumSubtypedTx) TxSubtype() byte { return tx.TxData.txType() } + +func signSubtypedLikeDynamicFeeTx(tx *Transaction) bool { + return GetArbitrumTxSubtype(tx) == ArbitrumTippingTxSubtype +} + +func GetArbitrumTxSubtype(tx *Transaction) byte { + switch inner := tx.inner.(type) { + case *ArbitrumSubtypedTx: + return inner.TxSubtype() + default: + return ArbitrumInvalidSubtype + } +} + type ArbitrumTippingTx struct { DynamicFeeTx `rlp:"flat"` } @@ -471,9 +503,10 @@ func NewArbitrumTippingTx(origTx *Transaction) (*Transaction, error) { if origTx.Type() != DynamicFeeTxType || !ok { return nil, errors.New("attempt to arbitrum-wrap into tipping transaction a transaction that is not a dynamic fee transaction") } - inner := ArbitrumTippingTx{ - DynamicFeeTx: *dynamicPtr, - } + inner := ArbitrumSubtypedTx{ + TxData: &ArbitrumTippingTx{ + DynamicFeeTx: *dynamicPtr, + }} return NewTx(&inner), nil } @@ -484,4 +517,4 @@ func (tx *ArbitrumTippingTx) copy() TxData { } } -func (tx *ArbitrumTippingTx) txType() byte { return ArbitrumTippingTxType } +func (tx *ArbitrumTippingTx) txType() byte { return ArbitrumTippingTxSubtype } diff --git a/core/types/receipt.go b/core/types/receipt.go index 3b58034161..533f3e01bd 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -73,6 +73,8 @@ type Receipt struct { BlockHash common.Hash `json:"blockHash,omitempty"` BlockNumber *big.Int `json:"blockNumber,omitempty"` TransactionIndex uint `json:"transactionIndex"` + + Subtype uint8 `json:"type,omitempty"` } type receiptMarshaling struct { @@ -170,6 +172,9 @@ func (r *Receipt) EncodeRLP(w io.Writer) error { // encodeTyped writes the canonical encoding of a typed receipt to w. func (r *Receipt) encodeTyped(data *receiptRLP, w *bytes.Buffer) error { w.WriteByte(r.Type) + if r.Type == ArbitrumSubtypedTxType { + w.WriteByte(r.Subtype) + } return rlp.Encode(w, data) } @@ -240,6 +245,19 @@ func (r *Receipt) decodeTyped(b []byte) error { } r.Type = b[0] return r.setFromRLP(data) + case ArbitrumSubtypedTxType: + if len(b) <= 2 { + return errShortTypedReceipt + } + var data receiptRLP + err := rlp.DecodeBytes(b[2:], &data) + if err != nil { + return err + } + r.Type = b[0] + r.Subtype = b[1] + return r.setFromRLP(data) + default: return ErrTxTypeNotSupported } diff --git a/core/types/transaction.go b/core/types/transaction.go index 4abf03c911..76509363c7 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -45,7 +45,7 @@ const ( LegacyTxType = iota AccessListTxType DynamicFeeTxType - ArbitrumTippingTxType = 99 + ArbitrumSubtypedTxType = 99 ArbitrumDepositTxType = 100 ArbitrumUnsignedTxType = 101 ArbitrumContractTxType = 102 @@ -117,6 +117,11 @@ func (tx *Transaction) EncodeRLP(w io.Writer) error { // encodeTyped writes the canonical encoding of a typed transaction to w. func (tx *Transaction) encodeTyped(w *bytes.Buffer) error { + if tx.Type() == ArbitrumSubtypedTxType { + w.WriteByte(tx.Type()) + w.WriteByte(tx.inner.(*ArbitrumSubtypedTx).TxSubtype()) + return rlp.Encode(w, tx.inner.(*ArbitrumSubtypedTx).TxData) + } w.WriteByte(tx.Type()) return rlp.Encode(w, tx.inner) } @@ -188,6 +193,22 @@ func (tx *Transaction) decodeTyped(b []byte, arbParsing bool) (TxData, error) { if len(b) <= 1 { return nil, errShortTypedTx } + txType := uint64(b[0]) + if txType == ArbitrumSubtypedTxType { + if len(b) <= 2 { + return nil, errShortTypedTx + } + var inner ArbitrumSubtypedTx + switch b[1] { + case ArbitrumTippingTxSubtype: + var tipping ArbitrumTippingTx + err := rlp.DecodeBytes(b[2:], &tipping) + inner.TxData = &tipping + return &inner, err + default: + return nil, ErrTxTypeNotSupported + } + } if arbParsing { switch b[0] { case ArbitrumDepositTxType: @@ -229,10 +250,6 @@ func (tx *Transaction) decodeTyped(b []byte, arbParsing bool) (TxData, error) { var inner DynamicFeeTx err := rlp.DecodeBytes(b[1:], &inner) return &inner, err - case ArbitrumTippingTxType: - var inner ArbitrumTippingTx - err := rlp.DecodeBytes(b[1:], &inner) - return &inner, err default: return nil, ErrTxTypeNotSupported } diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go index facfbf0f8e..824ba764a6 100644 --- a/core/types/transaction_marshalling.go +++ b/core/types/transaction_marshalling.go @@ -47,6 +47,7 @@ type txJSON struct { AccessList *AccessList `json:"accessList,omitempty"` // Arbitrum fields: + Subtype *hexutil.Uint64 `json:"subtype,omitempty"` // ArbitrumSubtypedTx From *common.Address `json:"from,omitempty"` // Contract SubmitRetryable Unsigned Retry RequestId *common.Hash `json:"requestId,omitempty"` // Contract SubmitRetryable Deposit TicketId *common.Hash `json:"ticketId,omitempty"` // Retry @@ -122,20 +123,6 @@ func (t *Transaction) MarshalJSON() ([]byte, error) { enc.V = (*hexutil.Big)(tx.V) enc.R = (*hexutil.Big)(tx.R) enc.S = (*hexutil.Big)(tx.S) - // TODO could we collapse it to: `case *DynamicFeeTx, *ArbitrumTippingTx:` - case *ArbitrumTippingTx: - enc.ChainID = (*hexutil.Big)(tx.ChainID) - enc.AccessList = &tx.AccessList - enc.Nonce = (*hexutil.Uint64)(&tx.Nonce) - enc.Gas = (*hexutil.Uint64)(&tx.Gas) - enc.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap) - enc.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap) - enc.Value = (*hexutil.Big)(tx.Value) - enc.Data = (*hexutil.Bytes)(&tx.Data) - enc.To = t.To() - enc.V = (*hexutil.Big)(tx.V) - enc.R = (*hexutil.Big)(tx.R) - enc.S = (*hexutil.Big)(tx.S) case *ArbitrumLegacyTxData: enc.Nonce = (*hexutil.Uint64)(&tx.Nonce) enc.Gas = (*hexutil.Uint64)(&tx.Gas) @@ -206,6 +193,29 @@ func (t *Transaction) MarshalJSON() ([]byte, error) { data := tx.data() enc.Data = (*hexutil.Bytes)(&data) enc.To = t.To() + case *ArbitrumSubtypedTx: + subtype := uint64(tx.TxSubtype()) + enc.Subtype = (*hexutil.Uint64)(&subtype) + switch subtype { + case ArbitrumTippingTxSubtype: + enc.ChainID = (*hexutil.Big)(tx.chainID()) + accessList := tx.accessList() + enc.AccessList = &accessList + nonce := tx.nonce() + enc.Nonce = (*hexutil.Uint64)(&nonce) + gas := tx.gas() + enc.Gas = (*hexutil.Uint64)(&gas) + enc.MaxFeePerGas = (*hexutil.Big)(tx.gasFeeCap()) + enc.MaxPriorityFeePerGas = (*hexutil.Big)(tx.gasTipCap()) + enc.Value = (*hexutil.Big)(tx.value()) + data := tx.data() + enc.Data = (*hexutil.Bytes)(&data) + enc.To = t.To() + v, r, s := tx.rawSignatureValues() + enc.V = (*hexutil.Big)(v) + enc.R = (*hexutil.Big)(r) + enc.S = (*hexutil.Big)(s) + } } return json.Marshal(&enc) } @@ -219,7 +229,15 @@ func (t *Transaction) UnmarshalJSON(input []byte) error { // Decode / verify fields according to transaction type. var inner TxData - switch dec.Type { + decType := uint64(dec.Type) + if decType == ArbitrumSubtypedTxType { + if dec.Subtype != nil { + decType = uint64(*dec.Subtype) + arbitrumSubtypeOffset + } else { + return errors.New("missing required field 'subtype' in transaction") + } + } + switch decType { case LegacyTxType: var itx LegacyTx inner = &itx @@ -318,7 +336,7 @@ func (t *Transaction) UnmarshalJSON(input []byte) error { } } - case DynamicFeeTxType, ArbitrumTippingTxType: + case DynamicFeeTxType, ArbitrumTippingTxSubtype + arbitrumSubtypeOffset: var itx DynamicFeeTx // Access list is optional for now. if dec.AccessList != nil { @@ -373,9 +391,11 @@ func (t *Transaction) UnmarshalJSON(input []byte) error { return err } } - if dec.Type == ArbitrumTippingTxType { - inner = &ArbitrumTippingTx{ - DynamicFeeTx: itx, + if dec.Type == ArbitrumTippingTxSubtype+arbitrumSubtypeOffset { + inner = &ArbitrumSubtypedTx{ + TxData: &ArbitrumTippingTx{ + DynamicFeeTx: itx, + }, } } else { inner = &itx diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index 9fb7370cc5..567ac18466 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -193,7 +193,7 @@ func NewLondonSigner(chainId *big.Int) Signer { } func (s londonSigner) Sender(tx *Transaction) (common.Address, error) { - if tx.Type() != DynamicFeeTxType && tx.Type() != ArbitrumTippingTxType { + if tx.Type() != DynamicFeeTxType && !signSubtypedLikeDynamicFeeTx(tx) { return s.eip2930Signer.Sender(tx) } V, R, S := tx.RawSignatureValues() @@ -216,8 +216,8 @@ func (s londonSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big var ok bool txdata, ok = tx.inner.(*DynamicFeeTx) if !ok { - txdata, ok = tx.inner.(*ArbitrumTippingTx) - if !ok { + txdata, ok = tx.inner.(*ArbitrumSubtypedTx) + if !ok || !signSubtypedLikeDynamicFeeTx(tx) { return s.eip2930Signer.SignatureValues(tx, sig) } } @@ -234,7 +234,23 @@ func (s londonSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big // Hash returns the hash to be signed by the sender. // It does not uniquely identify the transaction. func (s londonSigner) Hash(tx *Transaction) common.Hash { - if tx.Type() != DynamicFeeTxType && tx.Type() != ArbitrumTippingTxType { + if signSubtypedLikeDynamicFeeTx(tx) { + return prefixedRlpHash( + tx.Type(), + []interface{}{ + tx.inner.(*ArbitrumSubtypedTx).TxSubtype(), + s.chainId, + tx.Nonce(), + tx.GasTipCap(), + tx.GasFeeCap(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), + tx.AccessList(), + }) + } + if tx.Type() != DynamicFeeTxType { return s.eip2930Signer.Hash(tx) } return prefixedRlpHash( diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 842da79bcd..477a789209 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1406,6 +1406,7 @@ type RPCTransaction struct { S *hexutil.Big `json:"s"` // Arbitrum fields: + Subtype hexutil.Uint64 `json:"type", omitempty` // ArbiturumSubtypedTx RequestId *common.Hash `json:"requestId,omitempty"` // Contract SubmitRetryable Deposit TicketId *common.Hash `json:"ticketId,omitempty"` // Retry MaxRefund *hexutil.Big `json:"maxRefund,omitempty"` // Retry @@ -1501,6 +1502,26 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber result.MaxSubmissionFee = (*hexutil.Big)(inner.MaxSubmissionFee) result.GasFeeCap = (*hexutil.Big)(inner.GasFeeCap) result.ChainID = (*hexutil.Big)(inner.ChainId) + case *types.ArbitrumSubtypedTx: + subtype := inner.TxSubtype() + result.Subtype = hexutil.Uint64(subtype) + switch subtype { + case types.ArbitrumTippingTxSubtype: + al := tx.AccessList() + result.Accesses = &al + result.ChainID = (*hexutil.Big)(tx.ChainId()) + result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap()) + result.GasTipCap = (*hexutil.Big)(tx.GasTipCap()) + // if the transaction has been mined, compute the effective gas price + if baseFee != nil && blockHash != (common.Hash{}) { + // price = min(tip, gasFeeCap - baseFee) + baseFee + price := math.BigMin(new(big.Int).Add(tx.GasTipCap(), baseFee), tx.GasFeeCap()) + result.GasPrice = (*hexutil.Big)(price) + } else { + result.GasPrice = (*hexutil.Big)(tx.GasFeeCap()) + } + } + } return result } From cf55b9954c9d44bce0a99ae8a42c53ab9a3b5f47 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Tue, 7 Mar 2023 14:11:02 +0000 Subject: [PATCH 7/9] fix json decoded type check --- core/types/transaction_marshalling.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go index 824ba764a6..9a42a55254 100644 --- a/core/types/transaction_marshalling.go +++ b/core/types/transaction_marshalling.go @@ -391,7 +391,7 @@ func (t *Transaction) UnmarshalJSON(input []byte) error { return err } } - if dec.Type == ArbitrumTippingTxSubtype+arbitrumSubtypeOffset { + if decType == ArbitrumTippingTxSubtype+arbitrumSubtypeOffset { inner = &ArbitrumSubtypedTx{ TxData: &ArbitrumTippingTx{ DynamicFeeTx: itx, From 964d825b131d144d94f8595764f8032a55d11dce Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Tue, 7 Mar 2023 14:11:37 +0000 Subject: [PATCH 8/9] revert unneeded whitespace change in a comment --- rlp/decode.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rlp/decode.go b/rlp/decode.go index 114e2adf0c..29c701723c 100644 --- a/rlp/decode.go +++ b/rlp/decode.go @@ -76,7 +76,7 @@ type Decoder interface { // Note that Decode does not set an input limit for all readers and may be vulnerable to // panics cause by huge value sizes. If you need an input limit, use // -// NewStream(r, limit).Decode(val) +// NewStream(r, limit).Decode(val) func Decode(r io.Reader, val interface{}) error { stream := streamPool.Get().(*Stream) defer streamPool.Put(stream) From 112600460f385d78ab9487e5ce619c03ad829e4e Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Tue, 7 Mar 2023 15:30:52 +0000 Subject: [PATCH 9/9] fix RPCTransaction subtype json field name --- internal/ethapi/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 477a789209..326d44ef7c 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1406,7 +1406,7 @@ type RPCTransaction struct { S *hexutil.Big `json:"s"` // Arbitrum fields: - Subtype hexutil.Uint64 `json:"type", omitempty` // ArbiturumSubtypedTx + Subtype hexutil.Uint64 `json:"subtype",omitempty` // ArbiturumSubtypedTx RequestId *common.Hash `json:"requestId,omitempty"` // Contract SubmitRetryable Deposit TicketId *common.Hash `json:"ticketId,omitempty"` // Retry MaxRefund *hexutil.Big `json:"maxRefund,omitempty"` // Retry