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

Add tipping tx subtype #260

Closed
wants to merge 41 commits into from
Closed
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
4fb310d
add ArbitrumExtendedTx transaction type
magicxyyz Feb 24, 2023
e1cc692
remove flags field, rename the new tx to ArbitrumTippingTx
magicxyyz Feb 27, 2023
de1b3e6
remove unused const
magicxyyz Mar 1, 2023
dbd9780
Merge branch 'master' into tipping-tx-type
magicxyyz Mar 1, 2023
da0917b
Merge remote-tracking branch 'origin/ws_jwt_client' into tipping-tx-type
magicxyyz Mar 1, 2023
3fdc17c
add support for tipping tx to londonSinger
magicxyyz Mar 2, 2023
8c86873
add flat rlp tag
magicxyyz Mar 5, 2023
d21ef14
make tipping tx a subtype of subtyped tx
magicxyyz Mar 5, 2023
66e7bdb
Merge branch 'master' into tipping-tx-type
magicxyyz Mar 6, 2023
cf55b99
fix json decoded type check
magicxyyz Mar 7, 2023
964d825
revert unneeded whitespace change in a comment
magicxyyz Mar 7, 2023
1126004
fix RPCTransaction subtype json field name
magicxyyz Mar 7, 2023
b9c1c4e
latest tipping tx fixes
rauljordan Sep 29, 2023
9380b87
latest
rauljordan Sep 29, 2023
a9739e1
sync
rauljordan Sep 29, 2023
bcec1f8
latest tests from master
rauljordan Sep 29, 2023
35b2da6
marshaling
rauljordan Sep 29, 2023
47eac23
fix subtype json field name in receipt
magicxyyz Oct 2, 2023
d650824
remove ineffectual assignment to inner
magicxyyz Oct 2, 2023
3a54079
remove space in tag
magicxyyz Oct 2, 2023
43a7d13
fix json tag of subtype in RPCTransaction
magicxyyz Oct 2, 2023
d4d6370
remove unnecessary trailing newline
magicxyyz Oct 2, 2023
e4d6af3
Merge branch 'master' into latest-tipping-tx
magicxyyz Oct 12, 2023
ce5b737
Merge branch 'master' into latest-tipping-tx
magicxyyz Oct 31, 2023
873384d
Merge branch 'master' into latest-tipping-tx
magicxyyz Nov 27, 2023
88498e8
Merge branch 'master' into latest-tipping-tx
magicxyyz Dec 11, 2023
8952bb9
drop rlp flat tag
magicxyyz Dec 21, 2023
d59a026
drop subtype from receipt
magicxyyz Dec 21, 2023
fe9d3e3
move subtyped tx signing to arbitrum signer
magicxyyz Dec 21, 2023
980abc5
include subtype in transaction signing
magicxyyz Dec 22, 2023
bd4b275
Merge branch 'master' into latest-tipping-tx
magicxyyz Jan 8, 2024
1324ffd
refactor ArbitrumSubtypedTx encoding and decoding
magicxyyz Jan 9, 2024
b430ae6
cleanup removed flat rlp flag
magicxyyz Jan 9, 2024
bcb7de4
drop DecodeTxJSON and EncodeTxJSON methods
magicxyyz Jan 9, 2024
c7b244d
remove arbitrumSubtypeOffset constant
magicxyyz Jan 9, 2024
0f09b6d
add empty line before upstream tx type consts
magicxyyz Jan 13, 2024
43ab2a1
remove repeated access list setting when unmarshalling json
magicxyyz Jan 13, 2024
8d925b3
re-add removed empty line
magicxyyz Jan 13, 2024
2cf65c2
set yparity in tipping tx rpc result
magicxyyz Jan 13, 2024
7594ef3
make fake tx in arbitrumSigner.SignatureValues, add comment in .Sender
magicxyyz Jan 13, 2024
8ac00e8
move AccessList coping in json marshalling
magicxyyz Jan 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions accounts/external/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,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
54 changes: 54 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"
"math/big"

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

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

// Returns true if nonce checks should be skipped based on inner's isFake()
// This also disables requiring that sender is an EOA and not a contract
func (tx *Transaction) SkipAccountChecks() bool {
Expand Down Expand Up @@ -516,3 +523,50 @@ func DeserializeHeaderExtraInformation(header *Header) HeaderInfo {
extra.ArbOSFormatVersion = binary.BigEndian.Uint64(header.MixDigest[16:24])
return extra
}

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() }
Copy link
Contributor

Choose a reason for hiding this comment

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

I think I get it but I still find it kind of confusing and a room for error..
could we add a separate subtype field in SubtypedTx and use that?

Copy link
Contributor Author

@magicxyyz magicxyyz Jan 13, 2024

Choose a reason for hiding this comment

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

It'd be great to keep the subtype a constant, but adding a field invalidates the property

Maybe I should add a func (tx *ArbitrumSubtypedTx) SubTx() TxData method, and then we'd use it this way: tx.SubTx().Type(), would it be better?


func GetArbitrumTxSubtype(tx *Transaction) byte {
switch inner := tx.inner.(type) {
case *ArbitrumSubtypedTx:
return inner.TxSubtype()
default:
return ArbitrumInvalidSubtype
}
}

type ArbitrumTippingTx struct {
DynamicFeeTx
}

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 }
32 changes: 27 additions & 5 deletions core/types/arbitrum_signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ func (s arbitrumSigner) Sender(tx *Transaction) (common.Address, error) {
}
fakeTx := NewTx(&legacyData.LegacyTx)
return s.Signer.Sender(fakeTx)
case *ArbitrumSubtypedTx:
switch subtx := inner.TxData.(type) {
case *ArbitrumTippingTx:
fakeTx := NewTx(&subtx.DynamicFeeTx)
return s.Signer.Sender(fakeTx)
default:
return common.Address{}, ErrTxTypeNotSupported
}
default:
return s.Signer.Sender(tx)
}
Expand All @@ -51,7 +59,7 @@ func (s arbitrumSigner) Equal(s2 Signer) bool {
}

func (s arbitrumSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
switch tx.inner.(type) {
switch inner := tx.inner.(type) {
case *ArbitrumUnsignedTx:
return bigZero, bigZero, bigZero, nil
case *ArbitrumContractTx:
Expand All @@ -65,9 +73,16 @@ func (s arbitrumSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *b
case *ArbitrumSubmitRetryableTx:
return bigZero, bigZero, bigZero, nil
case *ArbitrumLegacyTxData:
legacyData := tx.inner.(*ArbitrumLegacyTxData)
fakeTx := NewTx(&legacyData.LegacyTx)
fakeTx := NewTx(&inner.LegacyTx)
return s.Signer.SignatureValues(fakeTx, sig)
case *ArbitrumSubtypedTx:
switch subtx := inner.TxData.(type) {
case *ArbitrumTippingTx:
fakeTx := NewTx(&subtx.DynamicFeeTx)
return s.Signer.SignatureValues(fakeTx, sig)
default:
return nil, nil, nil, ErrTxTypeNotSupported
}
default:
return s.Signer.SignatureValues(tx, sig)
}
Expand All @@ -76,9 +91,16 @@ func (s arbitrumSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *b
// Hash returns the hash to be signed by the sender.
// It does not uniquely identify the transaction.
func (s arbitrumSigner) Hash(tx *Transaction) common.Hash {
if legacyData, isArbLegacy := tx.inner.(*ArbitrumLegacyTxData); isArbLegacy {
fakeTx := NewTx(&legacyData.LegacyTx)
switch inner := tx.inner.(type) {
case *ArbitrumLegacyTxData:
fakeTx := NewTx(&inner.LegacyTx)
return s.Signer.Hash(fakeTx)
case *ArbitrumSubtypedTx:
switch subtx := inner.TxData.(type) {
case *ArbitrumTippingTx:
fakeTx := NewTx(&subtx.DynamicFeeTx)
return s.Signer.Hash(fakeTx)
}
}
return s.Signer.Hash(tx)
}
45 changes: 33 additions & 12 deletions core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,18 @@ var (

// Transaction types.
const (
ArbitrumDepositTxType = 0x64
ArbitrumUnsignedTxType = 0x65
ArbitrumContractTxType = 0x66
ArbitrumRetryTxType = 0x68
ArbitrumSubmitRetryableTxType = 0x69
ArbitrumInternalTxType = 0x6A
ArbitrumLegacyTxType = 0x78

LegacyTxType = 0x00
AccessListTxType = 0x01
DynamicFeeTxType = 0x02
BlobTxType = 0x03
ArbitrumSubtypedTxType = 99
ArbitrumDepositTxType = 100
ArbitrumUnsignedTxType = 101
ArbitrumContractTxType = 102
ArbitrumRetryTxType = 104
ArbitrumSubmitRetryableTxType = 105
ArbitrumInternalTxType = 106
ArbitrumLegacyTxType = 120
LegacyTxType = 0x00
magicxyyz marked this conversation as resolved.
Show resolved Hide resolved
AccessListTxType = 0x01
DynamicFeeTxType = 0x02
BlobTxType = 0x03
)

// Transaction is an Ethereum transaction.
Expand Down Expand Up @@ -128,6 +128,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)
}
magicxyyz marked this conversation as resolved.
Show resolved Hide resolved
w.WriteByte(tx.Type())
return rlp.Encode(w, tx.inner)
}
Expand Down Expand Up @@ -199,6 +204,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
51 changes: 47 additions & 4 deletions core/types/transaction_marshalling.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type txJSON struct {
YParity *hexutil.Uint64 `json:"yParity,omitempty"`

// Arbitrum fields:
Subtype *hexutil.Uint64 `json:"subtype,omitempty"` // ArbitrumSubtypedTx
Copy link
Contributor

Choose a reason for hiding this comment

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

why uint64 and not byte?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. to make it consistent with type of "type" field which is also hexutil.Uint64
  2. there's no hexutil.Byte, and hexautil.Uint64 does all format checking and decoding from string

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 @@ -223,7 +224,29 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) {
data := itx.data()
enc.Input = (*hexutil.Bytes)(&data)
enc.To = tx.To()

magicxyyz marked this conversation as resolved.
Show resolved Hide resolved
case *ArbitrumSubtypedTx:
subtype := uint64(itx.TxSubtype())
enc.Subtype = (*hexutil.Uint64)(&subtype)
switch subtype {
case ArbitrumTippingTxSubtype:
enc.ChainID = (*hexutil.Big)(itx.chainID())
accessList := itx.accessList()
enc.AccessList = &accessList
nonce := itx.nonce()
enc.Nonce = (*hexutil.Uint64)(&nonce)
gas := itx.gas()
enc.Gas = (*hexutil.Uint64)(&gas)
enc.MaxFeePerGas = (*hexutil.Big)(itx.gasFeeCap())
enc.MaxPriorityFeePerGas = (*hexutil.Big)(itx.gasTipCap())
enc.Value = (*hexutil.Big)(itx.value())
data := itx.data()
enc.Input = (*hexutil.Bytes)(&data)
enc.To = tx.To()
v, r, s := itx.rawSignatureValues()
enc.V = (*hexutil.Big)(v)
enc.R = (*hexutil.Big)(r)
enc.S = (*hexutil.Big)(s)
}
case *BlobTx:
enc.ChainID = (*hexutil.Big)(itx.ChainID.ToBig())
enc.Nonce = (*hexutil.Uint64)(&itx.Nonce)
Expand Down Expand Up @@ -255,7 +278,15 @@ func (tx *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 @@ -359,9 +390,12 @@ func (tx *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
}
magicxyyz marked this conversation as resolved.
Show resolved Hide resolved
if dec.ChainID == nil {
return errors.New("missing required field 'chainId' in transaction")
}
Expand Down Expand Up @@ -420,6 +454,15 @@ func (tx *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
20 changes: 20 additions & 0 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1521,6 +1521,7 @@ type RPCTransaction struct {
YParity *hexutil.Uint64 `json:"yParity,omitempty"`

// 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 @@ -1625,6 +1626,25 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
result.ChainID = (*hexutil.Big)(inner.ChainId)
case *types.ArbitrumUnsignedTx:
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())
magicxyyz marked this conversation as resolved.
Show resolved Hide resolved
// 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
5 changes: 5 additions & 0 deletions rlp/internal/rlpstruct/rlpstruct.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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"}
}
Expand Down