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

feat: implement DepositTx type and handling #45

Merged
merged 5 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 19 additions & 0 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ type Message struct {
AccessList types.AccessList
BlobGasFeeCap *big.Int
BlobHashes []common.Hash
IsDepositTx bool

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

// TransactionToMessage converts a transaction into a Message.
func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) {
isDepositTx := tx.Type() == types.DepositTxType

msg := &Message{
Nonce: tx.Nonce(),
GasLimit: tx.Gas(),
Expand All @@ -160,11 +163,17 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
SkipAccountChecks: false,
BlobHashes: tx.BlobHashes(),
BlobGasFeeCap: tx.BlobGasFeeCap(),
IsDepositTx: isDepositTx,
}
// If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil {
msg.GasPrice = cmath.BigMin(msg.GasPrice.Add(msg.GasTipCap, baseFee), msg.GasFeeCap)
}
if isDepositTx {
msg.From = tx.From()
return msg, nil
}

var err error
msg.From, err = types.Sender(s, tx)
return msg, err
Expand Down Expand Up @@ -353,6 +362,16 @@ func (st *StateTransition) preCheck() error {
// However if any consensus issue encountered, return the error directly with
// nil evm execution result.
func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
// if this is a deposit tx, we only need to mint funds and no gas is used.
if st.msg.IsDepositTx {
st.state.AddBalance(st.msg.From, st.msg.Value)
return &ExecutionResult{
UsedGas: 0,
Err: nil,
ReturnData: nil,
}, nil
}

// First check this message satisfies all consensus rules before
// applying the message. The rules include these clauses
//
Expand Down
6 changes: 3 additions & 3 deletions core/txpool/blobpool/blobpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,9 +334,9 @@ func New(config Config, chain BlockChain) *BlobPool {
}
}

func (p *BlobPool) SetAstriaOrdered(rawTxs [][]byte) {}
func (p *BlobPool) ClearAstriaOrdered() {}
func (p *BlobPool) AstriaOrdered() *types.Transactions { return &types.Transactions{} }
func (p *BlobPool) SetAstriaOrdered(types.Transactions) {}
func (p *BlobPool) ClearAstriaOrdered() {}
func (p *BlobPool) AstriaOrdered() *types.Transactions { return &types.Transactions{} }

// Filter returns whether the given transaction can be consumed by the blob pool.
func (p *BlobPool) Filter(tx *types.Transaction) bool {
Expand Down
21 changes: 7 additions & 14 deletions core/txpool/legacypool/legacypool.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,23 +301,15 @@ func (ao *astriaOrdered) clear() {
ao.parsed = types.Transactions{}
}

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

valid := []*types.Transaction{}
parsed := []*types.Transaction{}
for idx, rawTx := range rawTxs {
tx := new(types.Transaction)
err := tx.UnmarshalBinary(rawTx)
for idx, tx := range txs {
err := pool.validateTxBasics(tx, false)
if err != nil {
log.Warn("failed to unmarshal raw astria tx bytes", rawTx, "at index", idx, "error:", err)
continue
}
parsed = append(parsed, tx)

err = pool.validateTxBasics(tx, false)
if err != nil {
log.Warn("astria tx failed validation at index: ", idx, "hash: ", tx.Hash(), "error:", err)
log.Warn("astria tx failed validation", "index", idx, "hash", tx.Hash(), "error", err)
continue
}

Expand Down Expand Up @@ -667,7 +659,8 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction, local bool) erro
Accept: 0 |
1<<types.LegacyTxType |
1<<types.AccessListTxType |
1<<types.DynamicFeeTxType,
1<<types.DynamicFeeTxType |
1<<types.DepositTxType,
MaxSize: txMaxSize,
MinTip: pool.gasTip.Load(),
}
Expand Down
2 changes: 1 addition & 1 deletion core/txpool/subpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ type SubPool interface {
// identified by their hashes.
Status(hash common.Hash) TxStatus

SetAstriaOrdered(rawTxs [][]byte)
SetAstriaOrdered(types.Transactions)
ClearAstriaOrdered()
AstriaOrdered() *types.Transactions
}
4 changes: 2 additions & 2 deletions core/txpool/txpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,9 +261,9 @@ func (p *TxPool) Get(hash common.Hash) *types.Transaction {
}

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

Expand Down
36 changes: 19 additions & 17 deletions core/txpool/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,23 +90,25 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
if tx.GasFeeCapIntCmp(tx.GasTipCap()) < 0 {
return core.ErrTipAboveFeeCap
}
// Make sure the transaction is signed properly
if _, err := types.Sender(signer, tx); err != nil {
return ErrInvalidSender
}
// Ensure the transaction has more gas than the bare minimum needed to cover
// the transaction metadata
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, opts.Config.IsIstanbul(head.Number), opts.Config.IsShanghai(head.Number, head.Time))
if err != nil {
return err
}
if tx.Gas() < intrGas {
return fmt.Errorf("%w: needed %v, allowed %v", core.ErrIntrinsicGas, intrGas, tx.Gas())
}
// Ensure the gasprice is high enough to cover the requirement of the calling
// pool and/or block producer
if tx.GasTipCapIntCmp(opts.MinTip) < 0 {
return fmt.Errorf("%w: tip needed %v, tip permitted %v", ErrUnderpriced, opts.MinTip, tx.GasTipCap())
if tx.Type() != types.DepositTxType {
// Make sure the transaction is signed properly
if _, err := types.Sender(signer, tx); err != nil {
return ErrInvalidSender
}
// Ensure the transaction has more gas than the bare minimum needed to cover
// the transaction metadata
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, opts.Config.IsIstanbul(head.Number), opts.Config.IsShanghai(head.Number, head.Time))
if err != nil {
return err
}
if tx.Gas() < intrGas {
return fmt.Errorf("%w: needed %v, allowed %v", core.ErrIntrinsicGas, intrGas, tx.Gas())
}
// Ensure the gasprice is high enough to cover the requirement of the calling
// pool and/or block producer
if tx.GasTipCapIntCmp(opts.MinTip) < 0 {
return fmt.Errorf("%w: tip needed %v, tip permitted %v", ErrUnderpriced, opts.MinTip, tx.GasTipCap())
}
}
// Ensure blob transactions have valid commitments
if tx.Type() == types.BlobTxType {
Expand Down
64 changes: 64 additions & 0 deletions core/types/deposit_tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package types

import (
"bytes"
"math/big"

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

var _ TxData = &DepositTx{}

type DepositTx struct {
// the address of the account that initiated the deposit
From common.Address
// value to be minted to `From`
Value *big.Int
// gas limit
Gas uint64
}

func (tx *DepositTx) copy() TxData {
cpy := &DepositTx{
From: tx.From,
Value: new(big.Int),
Gas: tx.Gas,
}
if tx.Value != nil {
cpy.Value.Set(tx.Value)
}
return cpy
}

func (tx *DepositTx) txType() byte { return DepositTxType }
func (tx *DepositTx) chainID() *big.Int { return common.Big0 }
func (tx *DepositTx) accessList() AccessList { return nil }
func (tx *DepositTx) data() []byte { return nil }
func (tx *DepositTx) gas() uint64 { return tx.Gas }
func (tx *DepositTx) gasFeeCap() *big.Int { return new(big.Int) }
func (tx *DepositTx) gasTipCap() *big.Int { return new(big.Int) }
func (tx *DepositTx) gasPrice() *big.Int { return new(big.Int) }
func (tx *DepositTx) value() *big.Int { return tx.Value }
func (tx *DepositTx) nonce() uint64 { return 0 }
func (tx *DepositTx) to() *common.Address { return nil }

func (tx *DepositTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int {
return dst.Set(new(big.Int))
}

func (tx *DepositTx) rawSignatureValues() (v, r, s *big.Int) {
return common.Big0, common.Big0, common.Big0
}

func (tx *DepositTx) setSignatureValues(chainID, v, r, s *big.Int) {
// noop
}

func (tx *DepositTx) encode(b *bytes.Buffer) error {
return rlp.Encode(b, tx)
}

func (tx *DepositTx) decode(input []byte) error {
return rlp.DecodeBytes(input, tx)
}
2 changes: 1 addition & 1 deletion core/types/receipt.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ func (r *Receipt) decodeTyped(b []byte) error {
return errShortTypedReceipt
}
switch b[0] {
case DynamicFeeTxType, AccessListTxType, BlobTxType:
case DynamicFeeTxType, AccessListTxType, BlobTxType, DepositTxType:
var data receiptRLP
err := rlp.DecodeBytes(b[1:], &data)
if err != nil {
Expand Down
16 changes: 15 additions & 1 deletion core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const (
AccessListTxType = 0x01
DynamicFeeTxType = 0x02
BlobTxType = 0x03
DepositTxType = 0x04
)

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

// TxData is the underlying data of a transaction.
//
// This is implemented by DynamicFeeTx, LegacyTx and AccessListTx.
// This is implemented by DynamicFeeTx, LegacyTx, AccessListTx and DepositTx.
type TxData interface {
txType() byte // returns the type ID
copy() TxData // creates a deep copy and initializes all fields
Expand Down Expand Up @@ -98,6 +99,17 @@ type TxData interface {
decode([]byte) error
}

// From returns the sender of the transaction
// only for deposit transactions.
func (tx *Transaction) From() common.Address {
if tx.Type() != DepositTxType {
return common.Address{}
}

deposit := tx.inner.(*DepositTx)
return deposit.From
}

// EncodeRLP implements rlp.Encoder
func (tx *Transaction) EncodeRLP(w io.Writer) error {
if tx.Type() == LegacyTxType {
Expand Down Expand Up @@ -202,6 +214,8 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
inner = new(DynamicFeeTx)
case BlobTxType:
inner = new(BlobTx)
case DepositTxType:
inner = new(DepositTx)
default:
return nil, ErrTxTypeNotSupported
}
Expand Down
13 changes: 7 additions & 6 deletions genesis.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,16 @@
"shanghaiTime": 0,
"terminalTotalDifficulty": 0,
"terminalTotalDifficultyPassed": true,
"ethash": {}
"ethash": {},
"astriaRollupName": "astria",
"astriaOverrideGenesisExtraData": true,
"astriaSequencerInitialHeight": 2,
"astriaCelestiaInitialHeight": 2,
"astriaCelestiaHeightVariance": 10
},
"difficulty": "10000000",
"gasLimit": "8000000",
"alloc": {
"0x46B77EFDFB20979E1C29ec98DcE73e3eCbF64102": { "balance": "300000000000000000000" }
},
"astriaOverrideGenesisExtraData": true,
"astriaSequencerInitialHeight": 1,
"astriaDataAvailabilityInitialHeight": 1,
"astriaDataAvailabilityHeightVariance": 50
}
}
34 changes: 28 additions & 6 deletions grpc/execution/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import (
"context"
"crypto/sha256"
"fmt"
"math/big"
"sync"
"time"

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

func protoU128ToBigInt(u128 *primitivev1.Uint128) *big.Int {
lo := big.NewInt(0).SetUint64(u128.Lo)
hi := big.NewInt(0).SetUint64(u128.Hi)
hi.Lsh(hi, 64)
return lo.Add(lo, hi)
}

// ExecuteBlock drives deterministic derivation of a rollup block from sequencer
// block data
func (s *ExecutionServiceServerV1Alpha2) ExecuteBlock(ctx context.Context, req *astriaPb.ExecuteBlockRequest) (*astriaPb.Block, error) {
Expand All @@ -163,13 +172,26 @@ func (s *ExecutionServiceServerV1Alpha2) ExecuteBlock(ctx context.Context, req *
return nil, status.Error(codes.FailedPrecondition, "Block can only be created on top of soft block.")
}

// Filter out any Deposit txs since we don't currently support them
txsToProcess := [][]byte{}
for idx, tx := range req.Transactions {
if tx.GetDeposit() != nil {
log.Info("Deposit transactions detected, not implemented for chain, skipping", "index", idx)
txsToProcess := types.Transactions{}
for _, tx := range req.Transactions {
if deposit := tx.GetDeposit(); deposit != nil {
address := common.HexToAddress(deposit.DestinationChainAddress)
txdata := types.DepositTx{
From: address,
Value: protoU128ToBigInt(deposit.Amount),
Gas: 0,
}

tx := types.NewTx(&txdata)
txsToProcess = append(txsToProcess, tx)
} else {
txsToProcess = append(txsToProcess, tx.GetSequencedData())
ethTx := new(types.Transaction)
err := ethTx.UnmarshalBinary(tx.GetSequencedData())
if err != nil {
log.Error("failed to unmarshal sequenced data into transaction, ignoring", "tx hash", sha256.Sum256(tx.GetSequencedData()), "err", err)
continue
}
txsToProcess = append(txsToProcess, ethTx)
}
}

Expand Down
Loading