Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tipping tx type #204

Closed
wants to merge 12 commits into from
5 changes: 5 additions & 0 deletions accounts/external/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
Expand Down
58 changes: 58 additions & 0 deletions core/types/arb_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package types
import (
"context"
"encoding/binary"
"errors"
"fmt"
"math/big"

Expand All @@ -13,6 +14,12 @@ import (
"github.com/ethereum/go-ethereum/common"
)

const (
arbitrumSubtypeOffset = 0xff
ArbitrumInvalidSubtype = 0
ArbitrumTippingTxSubtype = 1
)

type fallbackError struct {
}

Expand Down Expand Up @@ -460,3 +467,54 @@ func DeserializeHeaderExtraInformation(header *Header) (HeaderInfo, error) {
extra.ArbOSFormatVersion = binary.BigEndian.Uint64(header.MixDigest[16:24])
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"`
}

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 tipping transaction a transaction that is not a dynamic fee transaction")
}
inner := ArbitrumSubtypedTx{
TxData: &ArbitrumTippingTx{
DynamicFeeTx: *dynamicPtr,
}}
return NewTx(&inner), nil
}

func (tx *ArbitrumTippingTx) copy() TxData {
dynamicCopy := tx.DynamicFeeTx.copy().(*DynamicFeeTx)
return &ArbitrumTippingTx{
DynamicFeeTx: *dynamicCopy,
}
}

func (tx *ArbitrumTippingTx) txType() byte { return ArbitrumTippingTxSubtype }
18 changes: 18 additions & 0 deletions core/types/receipt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -171,6 +173,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)
}

Expand Down Expand Up @@ -241,6 +246,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
}
Expand Down
22 changes: 22 additions & 0 deletions core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const (
LegacyTxType = iota
AccessListTxType
DynamicFeeTxType
ArbitrumSubtypedTxType = 99
ArbitrumDepositTxType = 100
ArbitrumUnsignedTxType = 101
ArbitrumContractTxType = 102
Expand Down Expand Up @@ -116,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)
}
Expand Down Expand Up @@ -187,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:
Expand Down
46 changes: 43 additions & 3 deletions core/types/transaction_marshalling.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -192,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:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alternatively we could call here a method of ArbitrumSubtypedTx (something like tx.(*ArbitrumSubtypedTx).EncodeJSON(&enc) ) and have the specific tx subtypes implement the encoding. The similar thing could be done for RLP encoding.
This could be nice to have almost everything that's needed to implement new tx subtype in one place, but also it would take it out of context, so initially I followed geths file split.

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)
}
Expand All @@ -205,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
Expand Down Expand Up @@ -304,9 +336,8 @@ func (t *Transaction) UnmarshalJSON(input []byte) error {
}
}

case DynamicFeeTxType:
case DynamicFeeTxType, ArbitrumTippingTxSubtype + arbitrumSubtypeOffset:
var itx DynamicFeeTx
inner = &itx
// Access list is optional for now.
if dec.AccessList != nil {
itx.AccessList = *dec.AccessList
Expand Down Expand Up @@ -360,6 +391,15 @@ func (t *Transaction) UnmarshalJSON(input []byte) error {
return err
}
}
if decType == ArbitrumTippingTxSubtype+arbitrumSubtypeOffset {
inner = &ArbitrumSubtypedTx{
TxData: &ArbitrumTippingTx{
DynamicFeeTx: itx,
},
}
} else {
inner = &itx
}

case ArbitrumLegacyTxType:
var itx LegacyTx
Expand Down
29 changes: 25 additions & 4 deletions core/types/transaction_signing.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 && !signSubtypedLikeDynamicFeeTx(tx) {
return s.eip2930Signer.Sender(tx)
}
V, R, S := tx.RawSignatureValues()
Expand All @@ -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.(*ArbitrumSubtypedTx)
if !ok || !signSubtypedLikeDynamicFeeTx(tx) {
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)
Expand All @@ -229,6 +234,22 @@ 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 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)
}
Expand Down
21 changes: 21 additions & 0 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1406,6 +1406,7 @@ type RPCTransaction struct {
S *hexutil.Big `json:"s"`

// Arbitrum fields:
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
Expand Down Expand Up @@ -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
}
Expand Down
13 changes: 9 additions & 4 deletions rlp/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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))
Expand All @@ -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
Expand Down
Loading