Skip to content

Commit a04a97d

Browse files
authored
Merge pull request #45 from astriaorg/noot/deposits
feat: implement `DepositTx` type and handling
2 parents 5c03068 + 6eccacc commit a04a97d

File tree

11 files changed

+166
-51
lines changed

11 files changed

+166
-51
lines changed

core/state_transition.go

+19
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ type Message struct {
138138
AccessList types.AccessList
139139
BlobGasFeeCap *big.Int
140140
BlobHashes []common.Hash
141+
IsDepositTx bool
141142

142143
// When SkipAccountChecks is true, the message nonce is not checked against the
143144
// account nonce in state. It also disables checking that the sender is an EOA.
@@ -147,6 +148,8 @@ type Message struct {
147148

148149
// TransactionToMessage converts a transaction into a Message.
149150
func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) {
151+
isDepositTx := tx.Type() == types.DepositTxType
152+
150153
msg := &Message{
151154
Nonce: tx.Nonce(),
152155
GasLimit: tx.Gas(),
@@ -160,11 +163,17 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
160163
SkipAccountChecks: false,
161164
BlobHashes: tx.BlobHashes(),
162165
BlobGasFeeCap: tx.BlobGasFeeCap(),
166+
IsDepositTx: isDepositTx,
163167
}
164168
// If baseFee provided, set gasPrice to effectiveGasPrice.
165169
if baseFee != nil {
166170
msg.GasPrice = cmath.BigMin(msg.GasPrice.Add(msg.GasTipCap, baseFee), msg.GasFeeCap)
167171
}
172+
if isDepositTx {
173+
msg.From = tx.From()
174+
return msg, nil
175+
}
176+
168177
var err error
169178
msg.From, err = types.Sender(s, tx)
170179
return msg, err
@@ -353,6 +362,16 @@ func (st *StateTransition) preCheck() error {
353362
// However if any consensus issue encountered, return the error directly with
354363
// nil evm execution result.
355364
func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
365+
// if this is a deposit tx, we only need to mint funds and no gas is used.
366+
if st.msg.IsDepositTx {
367+
st.state.AddBalance(st.msg.From, st.msg.Value)
368+
return &ExecutionResult{
369+
UsedGas: 0,
370+
Err: nil,
371+
ReturnData: nil,
372+
}, nil
373+
}
374+
356375
// First check this message satisfies all consensus rules before
357376
// applying the message. The rules include these clauses
358377
//

core/txpool/blobpool/blobpool.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -334,9 +334,9 @@ func New(config Config, chain BlockChain) *BlobPool {
334334
}
335335
}
336336

337-
func (p *BlobPool) SetAstriaOrdered(rawTxs [][]byte) {}
338-
func (p *BlobPool) ClearAstriaOrdered() {}
339-
func (p *BlobPool) AstriaOrdered() *types.Transactions { return &types.Transactions{} }
337+
func (p *BlobPool) SetAstriaOrdered(types.Transactions) {}
338+
func (p *BlobPool) ClearAstriaOrdered() {}
339+
func (p *BlobPool) AstriaOrdered() *types.Transactions { return &types.Transactions{} }
340340

341341
// Filter returns whether the given transaction can be consumed by the blob pool.
342342
func (p *BlobPool) Filter(tx *types.Transaction) bool {

core/txpool/legacypool/legacypool.go

+7-14
Original file line numberDiff line numberDiff line change
@@ -301,23 +301,15 @@ func (ao *astriaOrdered) clear() {
301301
ao.parsed = types.Transactions{}
302302
}
303303

304-
func (pool *LegacyPool) SetAstriaOrdered(rawTxs [][]byte) {
305-
astriaRequestedMeter.Mark(int64(len(rawTxs)))
304+
func (pool *LegacyPool) SetAstriaOrdered(txs types.Transactions) {
305+
astriaRequestedMeter.Mark(int64(len(txs)))
306306

307307
valid := []*types.Transaction{}
308308
parsed := []*types.Transaction{}
309-
for idx, rawTx := range rawTxs {
310-
tx := new(types.Transaction)
311-
err := tx.UnmarshalBinary(rawTx)
309+
for idx, tx := range txs {
310+
err := pool.validateTxBasics(tx, false)
312311
if err != nil {
313-
log.Warn("failed to unmarshal raw astria tx bytes", rawTx, "at index", idx, "error:", err)
314-
continue
315-
}
316-
parsed = append(parsed, tx)
317-
318-
err = pool.validateTxBasics(tx, false)
319-
if err != nil {
320-
log.Warn("astria tx failed validation at index: ", idx, "hash: ", tx.Hash(), "error:", err)
312+
log.Warn("astria tx failed validation", "index", idx, "hash", tx.Hash(), "error", err)
321313
continue
322314
}
323315

@@ -667,7 +659,8 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction, local bool) erro
667659
Accept: 0 |
668660
1<<types.LegacyTxType |
669661
1<<types.AccessListTxType |
670-
1<<types.DynamicFeeTxType,
662+
1<<types.DynamicFeeTxType |
663+
1<<types.DepositTxType,
671664
MaxSize: txMaxSize,
672665
MinTip: pool.gasTip.Load(),
673666
}

core/txpool/subpool.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ type SubPool interface {
138138
// identified by their hashes.
139139
Status(hash common.Hash) TxStatus
140140

141-
SetAstriaOrdered(rawTxs [][]byte)
141+
SetAstriaOrdered(types.Transactions)
142142
ClearAstriaOrdered()
143143
AstriaOrdered() *types.Transactions
144144
}

core/txpool/txpool.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -261,9 +261,9 @@ func (p *TxPool) Get(hash common.Hash) *types.Transaction {
261261
}
262262

263263
// Get returns a transaction if it is contained in the pool, or nil otherwise.
264-
func (p *TxPool) SetAstriaOrdered(rawTxs [][]byte) {
264+
func (p *TxPool) SetAstriaOrdered(txs types.Transactions) {
265265
for _, subpool := range p.subpools {
266-
subpool.SetAstriaOrdered(rawTxs)
266+
subpool.SetAstriaOrdered(txs)
267267
}
268268
}
269269

core/txpool/validation.go

+19-17
Original file line numberDiff line numberDiff line change
@@ -90,23 +90,25 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
9090
if tx.GasFeeCapIntCmp(tx.GasTipCap()) < 0 {
9191
return core.ErrTipAboveFeeCap
9292
}
93-
// Make sure the transaction is signed properly
94-
if _, err := types.Sender(signer, tx); err != nil {
95-
return ErrInvalidSender
96-
}
97-
// Ensure the transaction has more gas than the bare minimum needed to cover
98-
// the transaction metadata
99-
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, opts.Config.IsIstanbul(head.Number), opts.Config.IsShanghai(head.Number, head.Time))
100-
if err != nil {
101-
return err
102-
}
103-
if tx.Gas() < intrGas {
104-
return fmt.Errorf("%w: needed %v, allowed %v", core.ErrIntrinsicGas, intrGas, tx.Gas())
105-
}
106-
// Ensure the gasprice is high enough to cover the requirement of the calling
107-
// pool and/or block producer
108-
if tx.GasTipCapIntCmp(opts.MinTip) < 0 {
109-
return fmt.Errorf("%w: tip needed %v, tip permitted %v", ErrUnderpriced, opts.MinTip, tx.GasTipCap())
93+
if tx.Type() != types.DepositTxType {
94+
// Make sure the transaction is signed properly
95+
if _, err := types.Sender(signer, tx); err != nil {
96+
return ErrInvalidSender
97+
}
98+
// Ensure the transaction has more gas than the bare minimum needed to cover
99+
// the transaction metadata
100+
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, opts.Config.IsIstanbul(head.Number), opts.Config.IsShanghai(head.Number, head.Time))
101+
if err != nil {
102+
return err
103+
}
104+
if tx.Gas() < intrGas {
105+
return fmt.Errorf("%w: needed %v, allowed %v", core.ErrIntrinsicGas, intrGas, tx.Gas())
106+
}
107+
// Ensure the gasprice is high enough to cover the requirement of the calling
108+
// pool and/or block producer
109+
if tx.GasTipCapIntCmp(opts.MinTip) < 0 {
110+
return fmt.Errorf("%w: tip needed %v, tip permitted %v", ErrUnderpriced, opts.MinTip, tx.GasTipCap())
111+
}
110112
}
111113
// Ensure blob transactions have valid commitments
112114
if tx.Type() == types.BlobTxType {

core/types/deposit_tx.go

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package types
2+
3+
import (
4+
"bytes"
5+
"math/big"
6+
7+
"github.com/ethereum/go-ethereum/common"
8+
"github.com/ethereum/go-ethereum/rlp"
9+
)
10+
11+
var _ TxData = &DepositTx{}
12+
13+
type DepositTx struct {
14+
// the address of the account that initiated the deposit
15+
From common.Address
16+
// value to be minted to `From`
17+
Value *big.Int
18+
// gas limit
19+
Gas uint64
20+
}
21+
22+
func (tx *DepositTx) copy() TxData {
23+
cpy := &DepositTx{
24+
From: tx.From,
25+
Value: new(big.Int),
26+
Gas: tx.Gas,
27+
}
28+
if tx.Value != nil {
29+
cpy.Value.Set(tx.Value)
30+
}
31+
return cpy
32+
}
33+
34+
func (tx *DepositTx) txType() byte { return DepositTxType }
35+
func (tx *DepositTx) chainID() *big.Int { return common.Big0 }
36+
func (tx *DepositTx) accessList() AccessList { return nil }
37+
func (tx *DepositTx) data() []byte { return nil }
38+
func (tx *DepositTx) gas() uint64 { return tx.Gas }
39+
func (tx *DepositTx) gasFeeCap() *big.Int { return new(big.Int) }
40+
func (tx *DepositTx) gasTipCap() *big.Int { return new(big.Int) }
41+
func (tx *DepositTx) gasPrice() *big.Int { return new(big.Int) }
42+
func (tx *DepositTx) value() *big.Int { return tx.Value }
43+
func (tx *DepositTx) nonce() uint64 { return 0 }
44+
func (tx *DepositTx) to() *common.Address { return nil }
45+
46+
func (tx *DepositTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int {
47+
return dst.Set(new(big.Int))
48+
}
49+
50+
func (tx *DepositTx) rawSignatureValues() (v, r, s *big.Int) {
51+
return common.Big0, common.Big0, common.Big0
52+
}
53+
54+
func (tx *DepositTx) setSignatureValues(chainID, v, r, s *big.Int) {
55+
// noop
56+
}
57+
58+
func (tx *DepositTx) encode(b *bytes.Buffer) error {
59+
return rlp.Encode(b, tx)
60+
}
61+
62+
func (tx *DepositTx) decode(input []byte) error {
63+
return rlp.DecodeBytes(input, tx)
64+
}

core/types/receipt.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ func (r *Receipt) decodeTyped(b []byte) error {
204204
return errShortTypedReceipt
205205
}
206206
switch b[0] {
207-
case DynamicFeeTxType, AccessListTxType, BlobTxType:
207+
case DynamicFeeTxType, AccessListTxType, BlobTxType, DepositTxType:
208208
var data receiptRLP
209209
err := rlp.DecodeBytes(b[1:], &data)
210210
if err != nil {

core/types/transaction.go

+15-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const (
4545
AccessListTxType = 0x01
4646
DynamicFeeTxType = 0x02
4747
BlobTxType = 0x03
48+
DepositTxType = 0x04
4849
)
4950

5051
// Transaction is an Ethereum transaction.
@@ -67,7 +68,7 @@ func NewTx(inner TxData) *Transaction {
6768

6869
// TxData is the underlying data of a transaction.
6970
//
70-
// This is implemented by DynamicFeeTx, LegacyTx and AccessListTx.
71+
// This is implemented by DynamicFeeTx, LegacyTx, AccessListTx and DepositTx.
7172
type TxData interface {
7273
txType() byte // returns the type ID
7374
copy() TxData // creates a deep copy and initializes all fields
@@ -98,6 +99,17 @@ type TxData interface {
9899
decode([]byte) error
99100
}
100101

102+
// From returns the sender of the transaction
103+
// only for deposit transactions.
104+
func (tx *Transaction) From() common.Address {
105+
if tx.Type() != DepositTxType {
106+
return common.Address{}
107+
}
108+
109+
deposit := tx.inner.(*DepositTx)
110+
return deposit.From
111+
}
112+
101113
// EncodeRLP implements rlp.Encoder
102114
func (tx *Transaction) EncodeRLP(w io.Writer) error {
103115
if tx.Type() == LegacyTxType {
@@ -202,6 +214,8 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
202214
inner = new(DynamicFeeTx)
203215
case BlobTxType:
204216
inner = new(BlobTx)
217+
case DepositTxType:
218+
inner = new(DepositTx)
205219
default:
206220
return nil, ErrTxTypeNotSupported
207221
}

genesis.json

+7-6
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,16 @@
1414
"shanghaiTime": 0,
1515
"terminalTotalDifficulty": 0,
1616
"terminalTotalDifficultyPassed": true,
17-
"ethash": {}
17+
"ethash": {},
18+
"astriaRollupName": "astria",
19+
"astriaOverrideGenesisExtraData": true,
20+
"astriaSequencerInitialHeight": 2,
21+
"astriaCelestiaInitialHeight": 2,
22+
"astriaCelestiaHeightVariance": 10
1823
},
1924
"difficulty": "10000000",
2025
"gasLimit": "8000000",
2126
"alloc": {
2227
"0x46B77EFDFB20979E1C29ec98DcE73e3eCbF64102": { "balance": "300000000000000000000" }
23-
},
24-
"astriaOverrideGenesisExtraData": true,
25-
"astriaSequencerInitialHeight": 1,
26-
"astriaDataAvailabilityInitialHeight": 1,
27-
"astriaDataAvailabilityHeightVariance": 50
28+
}
2829
}

grpc/execution/server.go

+28-6
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import (
88
"context"
99
"crypto/sha256"
1010
"fmt"
11+
"math/big"
1112
"sync"
1213
"time"
1314

15+
primitivev1 "buf.build/gen/go/astria/astria/protocolbuffers/go/astria/primitive/v1"
1416
astriaGrpc "buf.build/gen/go/astria/execution-apis/grpc/go/astria/execution/v1alpha2/executionv1alpha2grpc"
1517
astriaPb "buf.build/gen/go/astria/execution-apis/protocolbuffers/go/astria/execution/v1alpha2"
1618
"github.com/ethereum/go-ethereum/beacon/engine"
@@ -140,6 +142,13 @@ func (s *ExecutionServiceServerV1Alpha2) BatchGetBlocks(ctx context.Context, req
140142
return res, nil
141143
}
142144

145+
func protoU128ToBigInt(u128 *primitivev1.Uint128) *big.Int {
146+
lo := big.NewInt(0).SetUint64(u128.Lo)
147+
hi := big.NewInt(0).SetUint64(u128.Hi)
148+
hi.Lsh(hi, 64)
149+
return lo.Add(lo, hi)
150+
}
151+
143152
// ExecuteBlock drives deterministic derivation of a rollup block from sequencer
144153
// block data
145154
func (s *ExecutionServiceServerV1Alpha2) ExecuteBlock(ctx context.Context, req *astriaPb.ExecuteBlockRequest) (*astriaPb.Block, error) {
@@ -163,13 +172,26 @@ func (s *ExecutionServiceServerV1Alpha2) ExecuteBlock(ctx context.Context, req *
163172
return nil, status.Error(codes.FailedPrecondition, "Block can only be created on top of soft block.")
164173
}
165174

166-
// Filter out any Deposit txs since we don't currently support them
167-
txsToProcess := [][]byte{}
168-
for idx, tx := range req.Transactions {
169-
if tx.GetDeposit() != nil {
170-
log.Info("Deposit transactions detected, not implemented for chain, skipping", "index", idx)
175+
txsToProcess := types.Transactions{}
176+
for _, tx := range req.Transactions {
177+
if deposit := tx.GetDeposit(); deposit != nil {
178+
address := common.HexToAddress(deposit.DestinationChainAddress)
179+
txdata := types.DepositTx{
180+
From: address,
181+
Value: protoU128ToBigInt(deposit.Amount),
182+
Gas: 0,
183+
}
184+
185+
tx := types.NewTx(&txdata)
186+
txsToProcess = append(txsToProcess, tx)
171187
} else {
172-
txsToProcess = append(txsToProcess, tx.GetSequencedData())
188+
ethTx := new(types.Transaction)
189+
err := ethTx.UnmarshalBinary(tx.GetSequencedData())
190+
if err != nil {
191+
log.Error("failed to unmarshal sequenced data into transaction, ignoring", "tx hash", sha256.Sum256(tx.GetSequencedData()), "err", err)
192+
continue
193+
}
194+
txsToProcess = append(txsToProcess, ethTx)
173195
}
174196
}
175197

0 commit comments

Comments
 (0)