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 35 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
97 changes: 97 additions & 0 deletions core/types/arb_types.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
package types

import (
"bytes"
"context"
"encoding/binary"
"errors"
"io"
"math/big"

"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"

"github.com/ethereum/go-ethereum/common"
)

const (
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 +525,91 @@ 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 (tx *ArbitrumSubtypedTx) EncodeRLP(w io.Writer) error {
buf := encodeBufferPool.Get().(*bytes.Buffer)
defer encodeBufferPool.Put(buf)
buf.Reset()
buf.WriteByte(tx.TxSubtype())
if err := rlp.Encode(buf, tx.TxData); err != nil {
return err
}
return rlp.Encode(w, buf.Bytes())
}

func (tx *ArbitrumSubtypedTx) DecodeRLP(s *rlp.Stream) error {
b, err := s.Bytes()
if err != nil {
return err
}
if len(b) < 1 {
return errShortTypedTx
}
switch b[0] {
case ArbitrumTippingTxSubtype:
var tipping ArbitrumTippingTx
err := rlp.DecodeBytes(b[1:], &tipping)
tx.TxData = &tipping
return err
default:
return ErrTxTypeNotSupported
}
}

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 }

func (tx *ArbitrumTippingTx) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, tx.DynamicFeeTx)
}

func (tx *ArbitrumTippingTx) DecodeRLP(s *rlp.Stream) error {
var dynamic DynamicFeeTx
err := s.Decode(&dynamic)
tx.DynamicFeeTx = dynamic
return err
}
57 changes: 52 additions & 5 deletions core/types/arbitrum_signer.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package types

import (
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -40,6 +41,20 @@ func (s arbitrumSigner) Sender(tx *Transaction) (common.Address, error) {
}
fakeTx := NewTx(&legacyData.LegacyTx)
return s.Signer.Sender(fakeTx)
case *ArbitrumSubtypedTx:
switch inner.TxData.(type) {
case *ArbitrumTippingTx:
V, R, S := tx.RawSignatureValues()
Copy link
Contributor

Choose a reason for hiding this comment

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

3 options, I'm o.k. with any:

  1. add a comment that this comes from london signer as these are assentially DynamicFeeTxType
  2. create a fake DynamicFeeTx and use the internal signer, similar to ArbitrumLegacyTx
  3. separate the code that extract the sender inside london-signer (without checking tx type), call this func from both london signer after checking type and from here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

2 - we can't make fake DynamicFeeTx and pass it to londonSigner because Hash method must be called on arbitrumSigner to include transaction subtype in hashed data
3 - probably would create some unnecessary merge conflicts against upstream

so I went with 1 and added a comment.

In SignatureValues method I used the fake tx pattern, as there it was possible.

// DynamicFee txs are defined to use 0 and 1 as their recovery
// id, add 27 to become equivalent to unprotected Homestead signatures.
V = new(big.Int).Add(V, big.NewInt(27))
if tx.ChainId().Cmp(s.Signer.ChainID()) != 0 {
return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.Signer.ChainID())
}
return recoverPlain(s.Hash(tx), R, S, V, true)
default:
return common.Address{}, ErrTxTypeNotSupported
}
default:
return s.Signer.Sender(tx)
}
Expand All @@ -51,7 +66,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 +80,22 @@ 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 txdata := inner.TxData.(type) {
case *ArbitrumTippingTx:
// 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.Signer.ChainID()) != 0 {
return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, txdata.ChainID, s.Signer.ChainID())
}
R, S, _ = decodeSignature(sig)
V = big.NewInt(int64(sig[64]))
return R, S, V, nil
default:
return nil, nil, nil, ErrTxTypeNotSupported
}
default:
return s.Signer.SignatureValues(tx, sig)
}
Expand All @@ -76,9 +104,28 @@ 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 inner.TxData.(type) {
case *ArbitrumTippingTx:
return prefixedRlpHash(
tx.Type(),
[]interface{}{
inner.TxSubtype(),
s.Signer.ChainID(),
tx.Nonce(),
tx.GasTipCap(),
tx.GasFeeCap(),
tx.Gas(),
tx.To(),
tx.Value(),
tx.Data(),
tx.AccessList(),
})
}
}
return s.Signer.Hash(tx)
}
28 changes: 16 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 @@ -244,6 +244,10 @@ func (tx *Transaction) decodeTyped(b []byte, arbParsing bool) (TxData, error) {
var inner BlobTx
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
case ArbitrumSubtypedTxType:
Copy link
Contributor

Choose a reason for hiding this comment

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

this belongs a few lines up - under
if arbParsing

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.

we need to parse ArbitrumSubtypedTxType even when arbParsing is false to be able to decode binary encoding with UnmarshalBinary to support SendRawTransaction and SendRawTransactionConditional. If we don't want to support it, then we probably need to add custom RPC method to allow sending subtyped txes (?).

var inner ArbitrumSubtypedTx
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
default:
return nil, ErrTxTypeNotSupported
}
Expand Down
Loading