Skip to content

Commit e8800cf

Browse files
authored
feat(sdm): core/types: add post-exec tx encoding support (#789)
* feat(sdm): post-exec transaction * feat(sdm): unit tests for error cases
1 parent df72b42 commit e8800cf

6 files changed

Lines changed: 243 additions & 1 deletion

File tree

core/types/post_exec_tx.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package types
2+
3+
import (
4+
"bytes"
5+
"math/big"
6+
7+
"github.com/ethereum/go-ethereum/common"
8+
)
9+
10+
const PostExecTxType = 0x7D
11+
12+
// PostExecTx is a synthetic OP Stack transaction used to carry post-exec metadata.
13+
type PostExecTx struct {
14+
Data []byte
15+
}
16+
17+
func (tx *PostExecTx) copy() TxData {
18+
return &PostExecTx{Data: common.CopyBytes(tx.Data)}
19+
}
20+
21+
func (tx *PostExecTx) txType() byte { return PostExecTxType }
22+
func (tx *PostExecTx) chainID() *big.Int { return common.Big0 }
23+
func (tx *PostExecTx) accessList() AccessList { return nil }
24+
func (tx *PostExecTx) data() []byte { return tx.Data }
25+
func (tx *PostExecTx) gas() uint64 { return 0 }
26+
func (tx *PostExecTx) gasFeeCap() *big.Int { return new(big.Int) }
27+
func (tx *PostExecTx) gasTipCap() *big.Int { return new(big.Int) }
28+
func (tx *PostExecTx) gasPrice() *big.Int { return new(big.Int) }
29+
func (tx *PostExecTx) value() *big.Int { return new(big.Int) }
30+
func (tx *PostExecTx) nonce() uint64 { return 0 }
31+
func (tx *PostExecTx) to() *common.Address { return nil }
32+
func (tx *PostExecTx) isSystemTx() bool { return false }
33+
34+
func (tx *PostExecTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int {
35+
return dst.Set(new(big.Int))
36+
}
37+
38+
func (tx *PostExecTx) effectiveNonce() *uint64 { return nil }
39+
40+
func (tx *PostExecTx) sigHash(*big.Int) common.Hash {
41+
panic("post-exec transaction cannot be signed")
42+
}
43+
44+
func (tx *PostExecTx) rawSignatureValues() (v, r, s *big.Int) {
45+
return common.Big0, common.Big0, common.Big0
46+
}
47+
48+
func (tx *PostExecTx) setSignatureValues(chainID, v, r, s *big.Int) {}
49+
50+
func (tx *PostExecTx) encode(b *bytes.Buffer) error {
51+
_, err := b.Write(tx.Data)
52+
return err
53+
}
54+
55+
func (tx *PostExecTx) decode(input []byte) error {
56+
tx.Data = common.CopyBytes(input)
57+
return nil
58+
}

core/types/post_exec_tx_test.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package types
2+
3+
import (
4+
"encoding/json"
5+
"math/big"
6+
"testing"
7+
8+
"github.com/ethereum/go-ethereum/common"
9+
"github.com/ethereum/go-ethereum/common/hexutil"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestPostExecTxUnmarshalJSON(t *testing.T) {
14+
var tx Transaction
15+
err := json.Unmarshal([]byte(`{
16+
"type":"0x7d",
17+
"gas":"0x0",
18+
"value":"0x0",
19+
"input":"0xc201c0"
20+
}`), &tx)
21+
require.NoError(t, err)
22+
require.Equal(t, uint8(PostExecTxType), tx.Type())
23+
24+
inner, ok := tx.inner.(*PostExecTx)
25+
require.True(t, ok)
26+
require.Equal(t, hexutil.MustDecode("0xc201c0"), inner.Data)
27+
}
28+
29+
func TestPostExecTxUnmarshalJSONWithRPCMetadata(t *testing.T) {
30+
var tx Transaction
31+
err := json.Unmarshal([]byte(`{
32+
"type":"0x7d",
33+
"from":"0x0000000000000000000000000000000000000000",
34+
"nonce":"0x0",
35+
"gasPrice":"0x1",
36+
"maxFeePerGas":"0x2",
37+
"maxPriorityFeePerGas":"0x3",
38+
"input":"0xc201c0",
39+
"v":"0x0",
40+
"r":"0x0",
41+
"s":"0x0"
42+
}`), &tx)
43+
require.NoError(t, err)
44+
require.Equal(t, uint8(PostExecTxType), tx.Type())
45+
}
46+
47+
func TestPostExecTxUnmarshalJSONErrors(t *testing.T) {
48+
tests := []struct {
49+
name string
50+
json string
51+
expectedError string
52+
}{
53+
{
54+
name: "non-empty accessList",
55+
json: `{"type":"0x7d","input":"0xc201c0","accessList":[{"address":"0x0000000000000000000000000000000000000001","storageKeys":[]}]}`,
56+
expectedError: "unexpected field(s) in post-exec transaction",
57+
},
58+
{
59+
name: "unepxpected isSystemTx field",
60+
json: `{"type":"0x7d","input":"0xc201c0","isSystemTx":true}`,
61+
expectedError: "unexpected field(s) in post-exec transaction",
62+
},
63+
{
64+
name: "unexpected to field",
65+
json: `{"type":"0x7d","input":"0xc201c0","to":"0x0000000000000000000000000000000000000001"}`,
66+
expectedError: "unexpected field(s) in post-exec transaction",
67+
},
68+
{
69+
name: "non-zero from",
70+
json: `{"type":"0x7d","input":"0xc201c0","from":"0x0000000000000000000000000000000000000001"}`,
71+
expectedError: "post-exec transaction from must be zero address or unset",
72+
},
73+
{
74+
name: "non-zero nonce",
75+
json: `{"type":"0x7d","input":"0xc201c0","nonce":"0x1"}`,
76+
expectedError: "post-exec transaction nonce must be 0 or unset",
77+
},
78+
{
79+
name: "non-zero value",
80+
json: `{"type":"0x7d","input":"0xc201c0","value":"0x1"}`,
81+
expectedError: "post-exec transaction value must be 0",
82+
},
83+
{
84+
name: "non-zero gas",
85+
json: `{"type":"0x7d","input":"0xc201c0","gas":"0x1"}`,
86+
expectedError: "post-exec transaction gas must be 0",
87+
},
88+
{
89+
name: "non-zero v",
90+
json: `{"type":"0x7d","input":"0xc201c0","v":"0x1","r":"0x0","s":"0x0"}`,
91+
expectedError: "post-exec transaction signature must be 0 or unset",
92+
},
93+
{
94+
name: "non-zero r",
95+
json: `{"type":"0x7d","input":"0xc201c0","v":"0x0","r":"0x1","s":"0x0"}`,
96+
expectedError: "post-exec transaction signature must be 0 or unset",
97+
},
98+
{
99+
name: "non-zero s",
100+
json: `{"type":"0x7d","input":"0xc201c0","v":"0x0","r":"0x0","s":"0x1"}`,
101+
expectedError: "post-exec transaction signature must be 0 or unset",
102+
},
103+
{
104+
name: "missing input",
105+
json: `{"type":"0x7d"}`,
106+
expectedError: "missing required field 'input'",
107+
},
108+
}
109+
for _, test := range tests {
110+
t.Run(test.name, func(t *testing.T) {
111+
var tx Transaction
112+
err := json.Unmarshal([]byte(test.json), &tx)
113+
require.ErrorContains(t, err, test.expectedError)
114+
})
115+
}
116+
}
117+
118+
func TestPostExecTxRoundTrips(t *testing.T) {
119+
original := NewTx(&PostExecTx{Data: hexutil.MustDecode("0xc201c0")})
120+
121+
jsonBytes, err := original.MarshalJSON()
122+
require.NoError(t, err)
123+
124+
var fromJSON Transaction
125+
require.NoError(t, fromJSON.UnmarshalJSON(jsonBytes))
126+
require.Equal(t, original.Type(), fromJSON.Type())
127+
require.Equal(t, original.Hash(), fromJSON.Hash())
128+
require.Equal(t, original.Data(), fromJSON.Data())
129+
130+
bin, err := original.MarshalBinary()
131+
require.NoError(t, err)
132+
require.Equal(t, hexutil.MustDecode("0x7dc201c0"), bin)
133+
134+
var fromBinary Transaction
135+
require.NoError(t, fromBinary.UnmarshalBinary(bin))
136+
require.Equal(t, original.Type(), fromBinary.Type())
137+
require.Equal(t, original.Hash(), fromBinary.Hash())
138+
require.Equal(t, original.Data(), fromBinary.Data())
139+
}
140+
141+
func TestPostExecTxSenderIsZeroAddress(t *testing.T) {
142+
tx := NewTx(&PostExecTx{Data: hexutil.MustDecode("0xc201c0")})
143+
144+
signer := NewLondonSigner(big.NewInt(123))
145+
sender, err := signer.Sender(tx)
146+
require.NoError(t, err)
147+
require.Equal(t, common.Address{}, sender)
148+
}

core/types/receipt.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ func (r *Receipt) decodeTyped(b []byte) error {
341341
return errShortTypedReceipt
342342
}
343343
switch b[0] {
344-
case DynamicFeeTxType, AccessListTxType, BlobTxType, SetCodeTxType:
344+
case DynamicFeeTxType, AccessListTxType, BlobTxType, SetCodeTxType, PostExecTxType:
345345
var data receiptRLP
346346
err := rlp.DecodeBytes(b[1:], &data)
347347
if err != nil {

core/types/transaction.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
223223
inner = new(BlobTx)
224224
case SetCodeTxType:
225225
inner = new(SetCodeTx)
226+
case PostExecTxType:
227+
inner = new(PostExecTx)
226228
case DepositTxType:
227229
inner = new(DepositTx)
228230
default:

core/types/transaction_marshalling.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,11 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) {
179179
yparity := itx.V.Uint64()
180180
enc.YParity = (*hexutil.Uint64)(&yparity)
181181

182+
case *PostExecTx:
183+
enc.Gas = (*hexutil.Uint64)(new(uint64))
184+
enc.Value = (*hexutil.Big)(new(big.Int))
185+
enc.Input = (*hexutil.Bytes)(&itx.Data)
186+
182187
case *DepositTx:
183188
enc.Gas = (*hexutil.Uint64)(&itx.Gas)
184189
enc.Value = (*hexutil.Big)(itx.Value)
@@ -590,6 +595,32 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
590595
if dec.Nonce != nil {
591596
inner = &depositTxWithNonce{DepositTx: itx, EffectiveNonce: uint64(*dec.Nonce)}
592597
}
598+
case PostExecTxType:
599+
if dec.AccessList != nil && len(*dec.AccessList) != 0 ||
600+
dec.Mint != nil || dec.SourceHash != nil || dec.IsSystemTx != nil || dec.To != nil {
601+
return errors.New("unexpected field(s) in post-exec transaction")
602+
}
603+
if dec.From != nil && *dec.From != (common.Address{}) {
604+
return errors.New("post-exec transaction from must be zero address or unset")
605+
}
606+
if dec.Nonce != nil && uint64(*dec.Nonce) != 0 {
607+
return errors.New("post-exec transaction nonce must be 0 or unset")
608+
}
609+
if dec.Value != nil && dec.Value.ToInt().Cmp(common.Big0) != 0 {
610+
return errors.New("post-exec transaction value must be 0")
611+
}
612+
if dec.Gas != nil && uint64(*dec.Gas) != 0 {
613+
return errors.New("post-exec transaction gas must be 0")
614+
}
615+
if (dec.V != nil && dec.V.ToInt().Cmp(common.Big0) != 0) ||
616+
(dec.R != nil && dec.R.ToInt().Cmp(common.Big0) != 0) ||
617+
(dec.S != nil && dec.S.ToInt().Cmp(common.Big0) != 0) {
618+
return errors.New("post-exec transaction signature must be 0 or unset")
619+
}
620+
if dec.Input == nil {
621+
return errors.New("missing required field 'input' in transaction")
622+
}
623+
inner = &PostExecTx{Data: *dec.Input}
593624
default:
594625
return ErrTxTypeNotSupported
595626
}

core/types/transaction_signing.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,9 @@ func (s *modernSigner) Sender(tx *Transaction) (common.Address, error) {
271271
return tx.inner.(*depositTxWithNonce).From, nil
272272
}
273273
}
274+
if tt == PostExecTxType {
275+
return common.Address{}, nil
276+
}
274277

275278
if !s.supportsType(tt) {
276279
return common.Address{}, ErrTxTypeNotSupported

0 commit comments

Comments
 (0)