Skip to content
This repository has been archived by the owner on Aug 20, 2022. It is now read-only.

Commit

Permalink
optimism-bedrock: rollup changes
Browse files Browse the repository at this point in the history
Note:
- the deposit-tx type fee payment is not final
- the deposit-tx gas handling is not final

Also Includes:
- Include invalid deposits, rewind state, but always persist mint (#10)
- Provide gas to Call/Create in deposit transactions (#12)

Co-authored-by: Diederik Loerakker <proto@protolambda.com>
  • Loading branch information
trianglesphere and protolambda committed Apr 27, 2022
1 parent 111a1b7 commit d0070e0
Show file tree
Hide file tree
Showing 15 changed files with 388 additions and 30 deletions.
1 change: 1 addition & 0 deletions accounts/abi/bind/backends/simulated.go
Expand Up @@ -812,6 +812,7 @@ func (m callMsg) Gas() uint64 { return m.CallMsg.Gas }
func (m callMsg) Value() *big.Int { return m.CallMsg.Value }
func (m callMsg) Data() []byte { return m.CallMsg.Data }
func (m callMsg) AccessList() types.AccessList { return m.CallMsg.AccessList }
func (m callMsg) Mint() *big.Int { return nil }

// filterBackend implements filters.Backend to support filtering for logs without
// taking bloom-bits acceleration structures into account.
Expand Down
26 changes: 23 additions & 3 deletions core/beacon/gen_blockparams.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion core/beacon/types.go
Expand Up @@ -33,11 +33,18 @@ type PayloadAttributesV1 struct {
Timestamp uint64 `json:"timestamp" gencodec:"required"`
Random common.Hash `json:"prevRandao" gencodec:"required"`
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`

// Transactions is a field for rollups: the transactions list is forced into the block
Transactions [][]byte `json:"transactions,omitempty" gencodec:"optional"`
// NoTxPool is a field for rollups: if true, the no transactions are taken out of the tx-pool,
// only transactions from the above Transactions list will be included.
NoTxPool bool `json:"noTxPool,omitempty" gencodec:"optional"`
}

// JSON type overrides for PayloadAttributesV1.
type payloadAttributesMarshaling struct {
Timestamp hexutil.Uint64
Timestamp hexutil.Uint64
Transactions []hexutil.Bytes
}

//go:generate go run github.com/fjl/gencodec -type ExecutableDataV1 -field-override executableDataMarshaling -out gen_ed.go
Expand Down
63 changes: 55 additions & 8 deletions core/state_transition.go
Expand Up @@ -57,6 +57,7 @@ type StateTransition struct {
gasTipCap *big.Int
initialGas uint64
value *big.Int
mint *big.Int
data []byte
state vm.StateDB
evm *vm.EVM
Expand All @@ -73,6 +74,9 @@ type Message interface {
Gas() uint64
Value() *big.Int

// Mint is nil if there is no minting
Mint() *big.Int

Nonce() uint64
IsFake() bool
Data() []byte
Expand Down Expand Up @@ -165,6 +169,7 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition
gasFeeCap: msg.GasFeeCap(),
gasTipCap: msg.GasTipCap(),
value: msg.Value(),
mint: msg.Mint(),
data: msg.Data(),
state: evm.StateDB,
}
Expand Down Expand Up @@ -212,6 +217,13 @@ func (st *StateTransition) buyGas() error {
}

func (st *StateTransition) preCheck() error {
if st.msg.Nonce() == types.DepositsNonce {
// No fee fields to check, no nonce to check, and no need to check if EOA (L1 already verified it for us)
// Gas is free, but no refunds!
st.initialGas = st.msg.Gas()
st.gas += st.msg.Gas() // Add gas here in order to be able to execute calls.
return nil
}
// Only check transactions that are not fake
if !st.msg.IsFake() {
// Make sure this transaction's nonce is correct.
Expand Down Expand Up @@ -273,6 +285,31 @@ 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 mint := st.msg.Mint(); mint != nil {
st.state.AddBalance(st.msg.From(), mint)
}
snap := st.state.Snapshot()

result, err := st.innerTransitionDb()
if err != nil { // EVM errors would have a result.Err and a nil execution err
// Failed deposits must still be included.
// On failure, we rewind any state changes from after the minting, and increment the nonce.
if st.msg.Nonce() == types.DepositsNonce {
st.state.RevertToSnapshot(snap)
// Even though we revert the state changes, always increment the nonce for the next deposit transaction
st.state.SetNonce(st.msg.From(), st.state.GetNonce(st.msg.From())+1)
result = &ExecutionResult{
UsedGas: 0, // No gas charge on non-EVM fails like balance checks, congestion is controlled on L1
Err: fmt.Errorf("failed deposit: %w", err),
ReturnData: nil,
}
err = nil
}
}
return result, err
}

func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) {
// First check this message satisfies all consensus rules before
// applying the message. The rules include these clauses
//
Expand Down Expand Up @@ -302,15 +339,17 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
contractCreation = msg.To() == nil
)

// Check clauses 4-5, subtract intrinsic gas if everything is correct
gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, rules.IsHomestead, rules.IsIstanbul)
if err != nil {
return nil, err
}
if st.gas < gas {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gas, gas)
if st.msg.Nonce() != types.DepositsNonce {
// Check clauses 4-5, subtract intrinsic gas if everything is correct
gas, err := IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, rules.IsHomestead, rules.IsIstanbul)
if err != nil {
return nil, err
}
if st.gas < gas {
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gas, gas)
}
st.gas -= gas
}
st.gas -= gas

// Check clause 6
if msg.Value().Sign() > 0 && !st.evm.Context.CanTransfer(st.state, msg.From(), msg.Value()) {
Expand All @@ -333,6 +372,14 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value)
}

// if deposit: skip refunds, skip tipping coinbase
if st.msg.Nonce() == types.DepositsNonce {
return &ExecutionResult{
UsedGas: 0, // Ignore actual used gas for deposits (until full deposit gas design is done)
Err: vmerr,
ReturnData: ret,
}, nil
}
if !rules.IsLondon {
// Before EIP-3529: refunds were capped to gasUsed / 2
st.refundGas(params.RefundQuotient)
Expand Down
16 changes: 16 additions & 0 deletions core/tx_pool.go
Expand Up @@ -584,6 +584,12 @@ func (pool *TxPool) local() map[common.Address]types.Transactions {
// validateTx checks whether a transaction is valid according to the consensus
// rules and adheres to some heuristic limits of the local node (price and size).
func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
// No unauthenticated deposits allowed in the transaction pool.
// This is for spam protection, not consensus,
// as the external engine-API user authenticates deposits.
if tx.Type() == types.DepositTxType {
return ErrTxTypeNotSupported
}
// Accept only legacy transactions until EIP-2718/2930 activates.
if !pool.eip2718 && tx.Type() != types.LegacyTxType {
return ErrTxTypeNotSupported
Expand Down Expand Up @@ -1277,6 +1283,16 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
return
}
}
// Do not insert deposit txs back into the pool
// (validateTx would still catch it if not filtered, but no need to re-inject in the first place).
j := 0
for _, tx := range discarded {
if tx.Type() != types.DepositTxType {
discarded[j] = tx
j++
}
}
discarded = discarded[:j]
reinject = types.TxDifference(discarded, included)
}
}
Expand Down
87 changes: 87 additions & 0 deletions core/types/deposit_tx.go
@@ -0,0 +1,87 @@
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package types

import (
"math/big"

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

const DepositTxType = 0x7E

type DepositTx struct {
// SourceHash uniquely identifies the source of the deposit
SourceHash common.Hash
// From is exposed through the types.Signer, not through TxData
From common.Address
// nil means contract creation
To *common.Address `rlp:"nil"`
// Mint is minted on L2, locked on L1, nil if no minting.
Mint *big.Int `rlp:"nil"`
// Value is transferred from L2 balance, executed after Mint (if any)
Value *big.Int
// gas limit
Gas uint64
Data []byte
}

// copy creates a deep copy of the transaction data and initializes all fields.
func (tx *DepositTx) copy() TxData {
cpy := &DepositTx{
SourceHash: tx.SourceHash,
From: tx.From,
To: copyAddressPtr(tx.To),
Mint: nil,
Value: new(big.Int),
Gas: tx.Gas,
Data: common.CopyBytes(tx.Data),
}
if tx.Mint != nil {
cpy.Mint = new(big.Int).Set(tx.Mint)
}
if tx.Value != nil {
cpy.Value.Set(tx.Value)
}
return cpy
}

// DepositsNonce identifies a deposit, since go-ethereum abstracts all transaction types to a core.Message.
// Deposits do not set a nonce, deposits are included by the system and cannot be repeated or included elsewhere.
const DepositsNonce uint64 = 0xffff_ffff_ffff_fffd

// accessors for innerTx.
func (tx *DepositTx) txType() byte { return DepositTxType }
func (tx *DepositTx) chainID() *big.Int { panic("deposits are not signed and do not have a chain-ID") }
func (tx *DepositTx) protected() bool { return true }
func (tx *DepositTx) accessList() AccessList { return nil }
func (tx *DepositTx) data() []byte { return tx.Data }
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 DepositsNonce }
func (tx *DepositTx) to() *common.Address { return tx.To }

func (tx *DepositTx) rawSignatureValues() (v, r, s *big.Int) {
panic("deposit tx does not have a signature")
}

func (tx *DepositTx) setSignatureValues(chainID, v, r, s *big.Int) {
panic("deposit tx does not have a signature")
}
5 changes: 4 additions & 1 deletion core/types/receipt.go
Expand Up @@ -214,7 +214,7 @@ func (r *Receipt) decodeTyped(b []byte) error {
return errShortTypedReceipt
}
switch b[0] {
case DynamicFeeTxType, AccessListTxType:
case DynamicFeeTxType, AccessListTxType, DepositTxType:
var data receiptRLP
err := rlp.DecodeBytes(b[1:], &data)
if err != nil {
Expand Down Expand Up @@ -387,6 +387,9 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) {
case DynamicFeeTxType:
w.WriteByte(DynamicFeeTxType)
rlp.Encode(w, data)
case DepositTxType:
w.WriteByte(DepositTxType)
rlp.Encode(w, data)
default:
// For unsupported types, write nothing. Since this is for
// DeriveSha, the error will be caught matching the derived hash
Expand Down
29 changes: 29 additions & 0 deletions core/types/transaction.go
Expand Up @@ -184,6 +184,10 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
var inner DynamicFeeTx
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
case DepositTxType:
var inner DepositTx
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
default:
return nil, ErrTxTypeNotSupported
}
Expand Down Expand Up @@ -285,6 +289,25 @@ func (tx *Transaction) To() *common.Address {
return copyAddressPtr(tx.inner.to())
}

// SourceHash returns the hash that uniquely identifies the source of the deposit tx,
// e.g. a user deposit event, or a L1 info deposit included in a specific L2 block height.
// Non-deposit transactions return a zeroed hash.
func (tx *Transaction) SourceHash() common.Hash {
if dep, ok := tx.inner.(*DepositTx); ok {
return dep.SourceHash
}
return common.Hash{}
}

// Mint returns the ETH to mint in the deposit tx.
// This returns nil if there is nothing to mint, or if this is not a deposit tx.
func (tx *Transaction) Mint() *big.Int {
if dep, ok := tx.inner.(*DepositTx); ok {
return dep.Mint
}
return nil
}

// Cost returns gas * gasPrice + value.
func (tx *Transaction) Cost() *big.Int {
total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas()))
Expand Down Expand Up @@ -572,6 +595,7 @@ type Message struct {
data []byte
accessList AccessList
isFake bool
mint *big.Int
}

func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice, gasFeeCap, gasTipCap *big.Int, data []byte, accessList AccessList, isFake bool) Message {
Expand All @@ -587,6 +611,7 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b
data: data,
accessList: accessList,
isFake: isFake,
mint: big.NewInt(0),
}
}

Expand All @@ -604,6 +629,9 @@ func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) {
accessList: tx.AccessList(),
isFake: false,
}
if dep, ok := tx.inner.(*DepositTx); ok {
msg.mint = dep.Mint
}
// If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil {
msg.gasPrice = math.BigMin(msg.gasPrice.Add(msg.gasTipCap, baseFee), msg.gasFeeCap)
Expand All @@ -624,6 +652,7 @@ func (m Message) Nonce() uint64 { return m.nonce }
func (m Message) Data() []byte { return m.data }
func (m Message) AccessList() AccessList { return m.accessList }
func (m Message) IsFake() bool { return m.isFake }
func (m Message) Mint() *big.Int { return m.mint }

// copyAddressPtr copies an address.
func copyAddressPtr(a *common.Address) *common.Address {
Expand Down

0 comments on commit d0070e0

Please sign in to comment.