feat(all): changes based on Taiko protocol

This commit is contained in:
David 2024-05-04 15:20:40 +08:00
parent c5ba367eb6
commit 7e1b8b65a3
59 changed files with 4758 additions and 73 deletions

33
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,33 @@
name: "Unit tests"
on:
pull_request:
branches: [taiko]
push:
branches: [taiko]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- uses: actions/checkout@v3
with:
repository: taikoxyz/taiko-geth
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: '1.21'
- name: Lint
run: make lint
- name: Test
env:
TAIKO_TEST: true
run: make test

View file

@ -0,0 +1,22 @@
name: Close inactive issues
on:
schedule:
- cron: "30 1 * * *"
jobs:
close-issues:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v5
with:
days-before-issue-stale: 30
days-before-issue-close: 7
stale-issue-label: "stale"
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
close-issue-message: "This issue was closed because it has been inactive for a week since being marked as stale."
days-before-pr-stale: -1
days-before-pr-close: -1
repo-token: ${{ secrets.GITHUB_TOKEN }}

47
.github/workflows/docker.yml vendored Normal file
View file

@ -0,0 +1,47 @@
name: "Push docker image to GAR"
on:
push:
branches: [taiko]
tags:
- "v*"
jobs:
push-docker-image:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Login to GAR
uses: docker/login-action@v2
with:
registry: us-docker.pkg.dev
username: _json_key
password: ${{ secrets.GAR_JSON_KEY }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
us-docker.pkg.dev/evmchain/images/taiko-geth
tags: |
type=ref,event=branch
type=ref,event=pr
type=ref,event=tag
type=sha
- name: Build and push
uses: docker/build-push-action@v2
with:
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

14
.github/workflows/lint-pr.yml vendored Normal file
View file

@ -0,0 +1,14 @@
name: "Lint PR"
on:
pull_request:
types: [opened, edited, synchronize, reopened]
jobs:
main:
name: Validate PR title
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

36
.github/workflows/openai-review.yml vendored Normal file
View file

@ -0,0 +1,36 @@
on: [pull_request]
jobs:
add_pr_comment:
permissions: write-all
runs-on: ubuntu-latest
name: OpenAI PR Comment
if: "contains(github.event.pull_request.labels.*.name, 'option.review-by-ai')"
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 2
- id: patch
run: |
patch_output=$(curl --silent --request GET \
--url https://api.github.com/repos/$PATCH_REPO/pulls/$PATCH_PR \
--header "Accept: application/vnd.github.v3.patch" \
--header "Authorization: Bearer $PATCH_GITHUB_TOKEN")
echo $patch_output
echo "GIT_PATCH_OUTPUT=$(echo $patch_output)" >> $GITHUB_ENV
env:
PATCH_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PATCH_PR: ${{ github.event.pull_request.number }}
PATCH_REPO: ${{ github.repository }}
- id: review
uses: taikoxyz/openai-review-action@main
with:
GIT_COMMIT_HASH: ${{ github.event.pull_request.head.sha }}
GIT_PATCH_OUTPUT: ${{ env.GIT_PATCH_OUTPUT }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_ORG_KEY: ${{ secrets.OPENAI_ORG_KEY }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_TITLE: ${{ github.event.pull_request.title }}
REPOSITORY_NAME: ${{ github.repository }}

View file

@ -1,3 +1,13 @@
# taiko-geth
[![CI](https://github.com/taikoxyz/taiko-geth/actions/workflows/ci.yml/badge.svg)](https://github.com/taikoxyz/taiko-geth/actions/workflows/ci.yml)
The codebase is based on [go-ethereum v1.13.15](https://github.com/ethereum/go-ethereum/releases/tag/v1.13.15).
## Tracing changes
All source code changes by [taiko](https://taiko.xyz) team are commented with `"CHANGE(taiko): ...."`, and the filenames of all additional files have the prefix `"taiko_"` (`taiko_*.go`).
## Go Ethereum ## Go Ethereum
Golang execution layer implementation of the Ethereum protocol. Golang execution layer implementation of the Ethereum protocol.

View file

@ -0,0 +1,82 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package engine
import (
"encoding/json"
"errors"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
var _ = (*blockMetadataMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (b BlockMetadata) MarshalJSON() ([]byte, error) {
type BlockMetadata struct {
Beneficiary common.Address `json:"beneficiary" gencodec:"required"`
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
MixHash common.Hash `json:"mixHash" gencodec:"required"`
TxList hexutil.Bytes `json:"txList" gencodec:"required"`
HighestBlockID *big.Int `json:"highestBlockID" gencodec:"required"`
ExtraData []byte `json:"extraData" gencodec:"required"`
}
var enc BlockMetadata
enc.Beneficiary = b.Beneficiary
enc.GasLimit = b.GasLimit
enc.Timestamp = hexutil.Uint64(b.Timestamp)
enc.MixHash = b.MixHash
enc.TxList = b.TxList
enc.HighestBlockID = b.HighestBlockID
enc.ExtraData = b.ExtraData
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (b *BlockMetadata) UnmarshalJSON(input []byte) error {
type BlockMetadata struct {
Beneficiary *common.Address `json:"beneficiary" gencodec:"required"`
GasLimit *uint64 `json:"gasLimit" gencodec:"required"`
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
MixHash *common.Hash `json:"mixHash" gencodec:"required"`
TxList *hexutil.Bytes `json:"txList" gencodec:"required"`
HighestBlockID *big.Int `json:"highestBlockID" gencodec:"required"`
ExtraData []byte `json:"extraData" gencodec:"required"`
}
var dec BlockMetadata
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.Beneficiary == nil {
return errors.New("missing required field 'beneficiary' for BlockMetadata")
}
b.Beneficiary = *dec.Beneficiary
if dec.GasLimit == nil {
return errors.New("missing required field 'gasLimit' for BlockMetadata")
}
b.GasLimit = *dec.GasLimit
if dec.Timestamp == nil {
return errors.New("missing required field 'timestamp' for BlockMetadata")
}
b.Timestamp = uint64(*dec.Timestamp)
if dec.MixHash == nil {
return errors.New("missing required field 'mixHash' for BlockMetadata")
}
b.MixHash = *dec.MixHash
if dec.TxList == nil {
return errors.New("missing required field 'txList' for BlockMetadata")
}
b.TxList = *dec.TxList
if dec.HighestBlockID == nil {
return errors.New("missing required field 'highestBlockID' for BlockMetadata")
}
b.HighestBlockID = dec.HighestBlockID
if dec.ExtraData == nil {
return errors.New("missing required field 'extraData' for BlockMetadata")
}
b.ExtraData = dec.ExtraData
return nil
}

View file

@ -5,9 +5,11 @@ package engine
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
) )
@ -21,6 +23,9 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) {
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"` BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
BlockMetadata *BlockMetadata `json:"blockMetadata" gencodec:"required"`
L1Origin *rawdb.L1Origin `json:"l1Origin" gencodec:"required"`
} }
var enc PayloadAttributes var enc PayloadAttributes
enc.Timestamp = hexutil.Uint64(p.Timestamp) enc.Timestamp = hexutil.Uint64(p.Timestamp)
@ -28,6 +33,9 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) {
enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient
enc.Withdrawals = p.Withdrawals enc.Withdrawals = p.Withdrawals
enc.BeaconRoot = p.BeaconRoot enc.BeaconRoot = p.BeaconRoot
enc.BaseFeePerGas = p.BaseFeePerGas
enc.BlockMetadata = p.BlockMetadata
enc.L1Origin = p.L1Origin
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -39,6 +47,9 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"` SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"` BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
BlockMetadata *BlockMetadata `json:"blockMetadata" gencodec:"required"`
L1Origin *rawdb.L1Origin `json:"l1Origin" gencodec:"required"`
} }
var dec PayloadAttributes var dec PayloadAttributes
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -62,5 +73,17 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
if dec.BeaconRoot != nil { if dec.BeaconRoot != nil {
p.BeaconRoot = dec.BeaconRoot p.BeaconRoot = dec.BeaconRoot
} }
if dec.BaseFeePerGas == nil {
return errors.New("missing required field 'baseFeePerGas' for PayloadAttributes")
}
p.BaseFeePerGas = dec.BaseFeePerGas
if dec.BlockMetadata == nil {
return errors.New("missing required field 'blockMetadata' for PayloadAttributes")
}
p.BlockMetadata = dec.BlockMetadata
if dec.L1Origin == nil {
return errors.New("missing required field 'l1Origin' for PayloadAttributes")
}
p.L1Origin = dec.L1Origin
return nil return nil
} }

View file

@ -17,23 +17,26 @@ var _ = (*executableDataMarshaling)(nil)
// MarshalJSON marshals as JSON. // MarshalJSON marshals as JSON.
func (e ExecutableData) MarshalJSON() ([]byte, error) { func (e ExecutableData) MarshalJSON() ([]byte, error) {
type ExecutableData struct { type ExecutableData struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"` ParentHash common.Hash `json:"parentHash" gencodec:"required"`
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"` FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
StateRoot common.Hash `json:"stateRoot" gencodec:"required"` StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"` LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"`
Random common.Hash `json:"prevRandao" gencodec:"required"` Random common.Hash `json:"prevRandao" gencodec:"required"`
Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"` Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"` Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"` ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"`
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"` BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
BlockHash common.Hash `json:"blockHash" gencodec:"required"` BlockHash common.Hash `json:"blockHash" gencodec:"required"`
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` Transactions []hexutil.Bytes `json:"transactions"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
TxHash common.Hash `json:"txHash"`
WithdrawalsHash common.Hash `json:"withdrawalsHash"`
TaikoBlock bool
} }
var enc ExecutableData var enc ExecutableData
enc.ParentHash = e.ParentHash enc.ParentHash = e.ParentHash
@ -58,29 +61,35 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
enc.Withdrawals = e.Withdrawals enc.Withdrawals = e.Withdrawals
enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed) enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed)
enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas) enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas)
enc.TxHash = e.TxHash
enc.WithdrawalsHash = e.WithdrawalsHash
enc.TaikoBlock = e.TaikoBlock
return json.Marshal(&enc) return json.Marshal(&enc)
} }
// UnmarshalJSON unmarshals from JSON. // UnmarshalJSON unmarshals from JSON.
func (e *ExecutableData) UnmarshalJSON(input []byte) error { func (e *ExecutableData) UnmarshalJSON(input []byte) error {
type ExecutableData struct { type ExecutableData struct {
ParentHash *common.Hash `json:"parentHash" gencodec:"required"` ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"` FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"`
StateRoot *common.Hash `json:"stateRoot" gencodec:"required"` StateRoot *common.Hash `json:"stateRoot" gencodec:"required"`
ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"` ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"`
LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"` LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"`
Random *common.Hash `json:"prevRandao" gencodec:"required"` Random *common.Hash `json:"prevRandao" gencodec:"required"`
Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"` Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"` Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"` ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"`
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"` BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
BlockHash *common.Hash `json:"blockHash" gencodec:"required"` BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` Transactions []hexutil.Bytes `json:"transactions"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
TxHash *common.Hash `json:"txHash"`
WithdrawalsHash *common.Hash `json:"withdrawalsHash"`
TaikoBlock *bool
} }
var dec ExecutableData var dec ExecutableData
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -138,12 +147,11 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
return errors.New("missing required field 'blockHash' for ExecutableData") return errors.New("missing required field 'blockHash' for ExecutableData")
} }
e.BlockHash = *dec.BlockHash e.BlockHash = *dec.BlockHash
if dec.Transactions == nil { if dec.Transactions != nil {
return errors.New("missing required field 'transactions' for ExecutableData") e.Transactions = make([][]byte, len(dec.Transactions))
} for k, v := range dec.Transactions {
e.Transactions = make([][]byte, len(dec.Transactions)) e.Transactions[k] = v
for k, v := range dec.Transactions { }
e.Transactions[k] = v
} }
if dec.Withdrawals != nil { if dec.Withdrawals != nil {
e.Withdrawals = dec.Withdrawals e.Withdrawals = dec.Withdrawals
@ -154,5 +162,14 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
if dec.ExcessBlobGas != nil { if dec.ExcessBlobGas != nil {
e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas) e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas)
} }
if dec.TxHash != nil {
e.TxHash = *dec.TxHash
}
if dec.WithdrawalsHash != nil {
e.WithdrawalsHash = *dec.WithdrawalsHash
}
if dec.TaikoBlock != nil {
e.TaikoBlock = *dec.TaikoBlock
}
return nil return nil
} }

View file

@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
) )
@ -46,6 +47,11 @@ type PayloadAttributes struct {
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"` BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
// CHANGE(taiko): extra fields.
BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
BlockMetadata *BlockMetadata `json:"blockMetadata" gencodec:"required"`
L1Origin *rawdb.L1Origin `json:"l1Origin" gencodec:"required"`
} }
// JSON type overrides for PayloadAttributes. // JSON type overrides for PayloadAttributes.
@ -53,6 +59,29 @@ type payloadAttributesMarshaling struct {
Timestamp hexutil.Uint64 Timestamp hexutil.Uint64
} }
//go:generate go run github.com/fjl/gencodec -type BlockMetadata -field-override blockMetadataMarshaling -out gen_blockmetadata.go
// CHANGE(taiko): BlockMetadata represents a `BlockMetadata` struct defined in
// protocol.
type BlockMetadata struct {
// Fields defined in `LibData.blockMetadata`.
Beneficiary common.Address `json:"beneficiary" gencodec:"required"`
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
Timestamp uint64 `json:"timestamp" gencodec:"required"`
MixHash common.Hash `json:"mixHash" gencodec:"required"`
// Extra fields required in taiko-geth.
TxList []byte `json:"txList" gencodec:"required"`
HighestBlockID *big.Int `json:"highestBlockID" gencodec:"required"`
ExtraData []byte `json:"extraData" gencodec:"required"`
}
// CHANGE(taiko): JSON type overrides for BlockMetadata.
type blockMetadataMarshaling struct {
Timestamp hexutil.Uint64
TxList hexutil.Bytes
}
//go:generate go run github.com/fjl/gencodec -type ExecutableData -field-override executableDataMarshaling -out gen_ed.go //go:generate go run github.com/fjl/gencodec -type ExecutableData -field-override executableDataMarshaling -out gen_ed.go
// ExecutableData is the data necessary to execute an EL payload. // ExecutableData is the data necessary to execute an EL payload.
@ -70,10 +99,14 @@ type ExecutableData struct {
ExtraData []byte `json:"extraData" gencodec:"required"` ExtraData []byte `json:"extraData" gencodec:"required"`
BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"` BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
BlockHash common.Hash `json:"blockHash" gencodec:"required"` BlockHash common.Hash `json:"blockHash" gencodec:"required"`
Transactions [][]byte `json:"transactions" gencodec:"required"` Transactions [][]byte `json:"transactions"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BlobGasUsed *uint64 `json:"blobGasUsed"` BlobGasUsed *uint64 `json:"blobGasUsed"`
ExcessBlobGas *uint64 `json:"excessBlobGas"` ExcessBlobGas *uint64 `json:"excessBlobGas"`
TxHash common.Hash `json:"txHash"` // CHANGE(taiko): allow passing txHash directly instead of transactions list
WithdrawalsHash common.Hash `json:"withdrawalsHash"` // CHANGE(taiko): allow passing WithdrawalsHash directly instead of withdrawals
TaikoBlock bool // CHANGE(taiko): whether this is a Taiko L2 block, only used by ExecutableDataToBlock
} }
// JSON type overrides for executableData. // JSON type overrides for executableData.

View file

@ -179,6 +179,9 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
} }
backend, eth := utils.RegisterEthService(stack, &cfg.Eth) backend, eth := utils.RegisterEthService(stack, &cfg.Eth)
// CHANGE(TAIKO): register Taiko RPC APIs.
utils.RegisterTaikoAPIs(stack, &cfg.Eth, eth)
// Create gauge with geth system and build information // Create gauge with geth system and build information
if eth != nil { // The 'eth' backend may be nil in light mode if eth != nil { // The 'eth' backend may be nil in light mode
var protos []string var protos []string

View file

@ -248,6 +248,9 @@ func init() {
debug.Flags, debug.Flags,
metricsFlags, metricsFlags,
) )
// CHANGE(taiko): append Taiko flags into the original GETH flags
app.Flags = append(app.Flags, &utils.TaikoFlag)
flags.AutoEnvVars(app.Flags, "GETH") flags.AutoEnvVars(app.Flags, "GETH")
app.Before = func(ctx *cli.Context) error { app.Before = func(ctx *cli.Context) error {

View file

@ -1730,6 +1730,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
} }
// Override any default configs for hard coded networks. // Override any default configs for hard coded networks.
switch { switch {
// CHANGE(taiko): when --taiko flag is set, use the Taiko genesis.
case ctx.IsSet(TaikoFlag.Name):
cfg.Genesis = core.TaikoGenesisBlock(cfg.NetworkId)
case ctx.Bool(MainnetFlag.Name): case ctx.Bool(MainnetFlag.Name):
if !ctx.IsSet(NetworkIdFlag.Name) { if !ctx.IsSet(NetworkIdFlag.Name) {
cfg.NetworkId = 1 cfg.NetworkId = 1

41
cmd/utils/taiko_flags.go Normal file
View file

@ -0,0 +1,41 @@
package utils
import (
"os"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/urfave/cli/v2"
)
var (
TaikoFlag = cli.BoolFlag{
Name: "taiko",
Usage: "Taiko network",
}
)
// RegisterTaikoAPIs initializes and registers the Taiko RPC APIs.
func RegisterTaikoAPIs(stack *node.Node, cfg *ethconfig.Config, backend *eth.Ethereum) {
if os.Getenv("TAIKO_TEST") != "" {
return
}
// Add methods under "taiko_" RPC namespace to the available APIs list
stack.RegisterAPIs([]rpc.API{
{
Namespace: "taiko",
Version: params.VersionWithMeta,
Service: eth.NewTaikoAPIBackend(backend),
Public: true,
},
{
Namespace: "taikoAuth",
Version: params.VersionWithMeta,
Service: eth.NewTaikoAuthAPIBackend(backend),
Authenticated: true,
},
})
}

View file

@ -0,0 +1,358 @@
package taiko
import (
"bytes"
"errors"
"fmt"
"math/big"
"runtime"
"strings"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/trie"
"github.com/holiman/uint256"
)
var (
ErrOlderBlockTime = errors.New("timestamp older than parent")
ErrUnclesNotEmpty = errors.New("uncles not empty")
ErrEmptyBasefee = errors.New("empty base fee")
ErrEmptyWithdrawalsHash = errors.New("withdrawals hash missing")
ErrAnchorTxNotFound = errors.New("anchor transaction not found")
GoldenTouchAccount = common.HexToAddress("0x0000777735367b36bC9B61C50022d9D0700dB4Ec")
TaikoL2AddressSuffix = "10001"
AnchorSelector = crypto.Keccak256([]byte("anchor(bytes32,bytes32,uint64,uint32)"))[:4]
AnchorGasLimit = uint64(250_000)
)
// Taiko is a consensus engine used by L2 rollup.
type Taiko struct {
chainConfig *params.ChainConfig
taikoL2Address common.Address
}
var _ = new(Taiko)
func New(chainConfig *params.ChainConfig) *Taiko {
taikoL2AddressPrefix := strings.TrimPrefix(chainConfig.ChainID.String(), "0")
return &Taiko{
chainConfig: chainConfig,
taikoL2Address: common.HexToAddress(
"0x" +
taikoL2AddressPrefix +
strings.Repeat("0", common.AddressLength*2-len(taikoL2AddressPrefix)-len(TaikoL2AddressSuffix)) +
TaikoL2AddressSuffix,
),
}
}
// check all method stubs for interface `Engine` without affect performance.
var _ consensus.Engine = (*Taiko)(nil)
// Author retrieves the Ethereum address of the account that minted the given
// block, who proposes the block (not the prover).
func (t *Taiko) Author(header *types.Header) (common.Address, error) {
return header.Coinbase, nil
}
// VerifyHeader checks whether a header conforms to the consensus rules of a
// given engine. Verifying the seal may be done optionally here, or explicitly
// via the VerifySeal method.
func (t *Taiko) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header) error {
// Short circuit if the header is known, or its parent not
number := header.Number.Uint64()
if chain.GetHeader(header.Hash(), number) != nil {
return nil
}
parent := chain.GetHeader(header.ParentHash, number-1)
if parent == nil {
return consensus.ErrUnknownAncestor
}
// Sanity checks passed, do a proper verification
return t.verifyHeader(chain, header, parent, time.Now().Unix())
}
// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers
// concurrently. The method returns a quit channel to abort the operations and
// a results channel to retrieve the async verifications (the order is that of
// the input slice).
func (t *Taiko) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header) (chan<- struct{}, <-chan error) {
// Spawn as many workers as allowed threads
workers := runtime.GOMAXPROCS(0)
if len(headers) < workers {
workers = len(headers)
}
// Create a task channel and spawn the verifiers
var (
inputs = make(chan int)
done = make(chan int, workers)
errors = make([]error, len(headers))
abort = make(chan struct{})
unixNow = time.Now().Unix()
)
for i := 0; i < workers; i++ {
go func() {
for index := range inputs {
errors[index] = t.verifyHeaderWorker(chain, headers, index, unixNow)
done <- index
}
}()
}
errorsOut := make(chan error, len(headers))
go func() {
defer close(inputs)
var (
in, out = 0, 0
checked = make([]bool, len(headers))
inputs = inputs
)
for {
select {
case inputs <- in:
if in++; in == len(headers) {
// Reached end of headers. Stop sending to workers.
inputs = nil
}
case index := <-done:
for checked[index] = true; checked[out]; out++ {
errorsOut <- errors[out]
if out == len(headers)-1 {
return
}
}
case <-abort:
return
}
}
}()
return abort, errorsOut
}
func (t *Taiko) verifyHeader(chain consensus.ChainHeaderReader, header, parent *types.Header, unixNow int64) error {
if header.Time > uint64(unixNow) {
return consensus.ErrFutureBlock
}
// Ensure that the header's extra-data section is of a reasonable size (<= 32 bytes)
if uint64(len(header.Extra)) > params.MaximumExtraDataSize {
return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize)
}
// Timestamp should later than or equal to parent (when many L2 blocks included in one L1 block)
if header.Time < parent.Time {
return ErrOlderBlockTime
}
// Verify that the block number is parent's +1
if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 {
return consensus.ErrInvalidNumber
}
// Difficulty should always be zero
if header.Difficulty != nil && header.Difficulty.Cmp(common.Big0) != 0 {
return fmt.Errorf("invalid difficulty: have %v, want %v", header.Difficulty, common.Big0)
}
// Verify that the gas limit is <= 2^63-1
if header.GasLimit > params.MaxGasLimit {
return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit)
}
// Verify that the gasUsed is <= gasLimit
if header.GasUsed > header.GasLimit {
return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit)
}
// Uncles should be empty
if header.UncleHash != types.CalcUncleHash(nil) {
return ErrUnclesNotEmpty
}
// BaseFee should not be empty
if header.BaseFee == nil {
return ErrEmptyBasefee
}
// WithdrawalsHash should not be empty
if header.WithdrawalsHash == nil {
return ErrEmptyWithdrawalsHash
}
return nil
}
func (t *Taiko) verifyHeaderWorker(chain consensus.ChainHeaderReader, headers []*types.Header, index int, unixNow int64) error {
var parent *types.Header
if index == 0 {
parent = chain.GetHeader(headers[0].ParentHash, headers[0].Number.Uint64()-1)
} else if headers[index-1].Hash() == headers[index].ParentHash {
parent = headers[index-1]
}
if parent == nil {
return consensus.ErrUnknownAncestor
}
return t.verifyHeader(chain, headers[index], parent, unixNow)
}
// VerifyUncles verifies that the given block's uncles conform to the consensus
// rules of a given engine.
//
// always returning an error for any uncles as this consensus mechanism doesn't permit uncles.
func (t *Taiko) VerifyUncles(chain consensus.ChainReader, block *types.Block) error {
if len(block.Uncles()) > 0 {
return ErrUnclesNotEmpty
}
return nil
}
// Prepare initializes the consensus fields of a block header according to the
// rules of a particular engine. The changes are executed inline.
func (t *Taiko) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1)
if parent == nil {
return consensus.ErrUnknownAncestor
}
header.Difficulty = common.Big0
return nil
}
// Finalize runs any post-transaction state modifications (e.g. block rewards)
// but does not assemble the block.
//
// Note: The block header and state database might be updated to reflect any
// consensus rules that happen at finalization (e.g. block rewards).
func (t *Taiko) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) {
// no block rewards in l2
header.UncleHash = types.CalcUncleHash(nil)
header.Difficulty = common.Big0
// Withdrawals processing.
for _, w := range withdrawals {
state.AddBalance(w.Address, uint256.MustFromBig(new(big.Int).SetUint64(w.Amount)))
}
header.Root = state.IntermediateRoot(true)
}
// FinalizeAndAssemble runs any post-transaction state modifications (e.g. block
// rewards) and assembles the final block.
//
// Note: The block header and state database might be updated to reflect any
// consensus rules that happen at finalization (e.g. block rewards).
func (t *Taiko) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt, withdrawals []*types.Withdrawal) (*types.Block, error) {
if withdrawals == nil {
withdrawals = make([]*types.Withdrawal, 0)
}
// Verify anchor transaction
if len(txs) != 0 { // Transactions list might be empty when building empty payload.
isAnchor, err := t.ValidateAnchorTx(txs[0], header)
if err != nil {
return nil, err
}
if !isAnchor {
return nil, ErrAnchorTxNotFound
}
}
// Finalize block
t.Finalize(chain, header, state, txs, uncles, withdrawals)
return types.NewBlockWithWithdrawals(
header, txs, nil /* ignore uncles */, receipts, withdrawals, trie.NewStackTrie(nil),
), nil
}
// Seal generates a new sealing request for the given input block and pushes
// the result into the given channel.
//
// Note, the method returns immediately and will send the result async. More
// than one result may also be returned depending on the consensus algorithm.
func (t *Taiko) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
header := block.Header()
// Sealing the genesis block is not supported
number := header.Number.Uint64()
if number == 0 {
return consensus.ErrInvalidNumber
}
select {
case results <- block.WithSeal(header):
case <-stop:
return nil
default:
log.Warn("Sealing result is not read by miner", "sealHash", t.SealHash(header))
}
return nil
}
// SealHash returns the hash of a block prior to it being sealed.
func (t *Taiko) SealHash(header *types.Header) common.Hash {
// Keccak(rlp(header))
return header.Hash()
}
// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty
// that a new block should have.
func (t *Taiko) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int {
return common.Big0
}
// ValidateAnchorTx checks if the given transaction is a valid TaikoL2.anchor transaction.
func (t *Taiko) ValidateAnchorTx(tx *types.Transaction, header *types.Header) (bool, error) {
if tx.Type() != types.DynamicFeeTxType {
return false, nil
}
if tx.To() == nil || *tx.To() != t.taikoL2Address {
return false, nil
}
if !bytes.HasPrefix(tx.Data(), AnchorSelector) {
return false, nil
}
if tx.Value().Cmp(common.Big0) != 0 {
return false, nil
}
if tx.Gas() != AnchorGasLimit {
return false, nil
}
if tx.GasFeeCap().Cmp(header.BaseFee) != 0 {
return false, nil
}
s := types.MakeSigner(t.chainConfig, header.Number, header.Time)
addr, err := s.Sender(tx)
if err != nil {
return false, err
}
return strings.EqualFold(addr.String(), GoldenTouchAccount.String()), nil
}
// APIs returns the RPC APIs this consensus engine provides.
func (t *Taiko) APIs(chain consensus.ChainHeaderReader) []rpc.API {
return nil
}
// Close terminates any background threads maintained by the consensus engine.
func (t *Taiko) Close() error {
return nil
}

View file

@ -0,0 +1,237 @@
package taiko_test
import (
"bytes"
"math/big"
"strings"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/taiko"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/triedb"
"github.com/stretchr/testify/assert"
)
var (
testL2RollupAddress = common.HexToAddress("0x79fcdef22feed20eddacbb2587640e45491b757f")
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
goldenTouchKey, _ = crypto.HexToECDSA("92954368afd3caa1f3ce3ead0069c1af414054aefe1ef9aeacc1bf426222ce38")
testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
genesis *core.Genesis
txs []*types.Transaction
testEngine *taiko.Taiko
)
func init() {
config := params.TestChainConfig
config.GrayGlacierBlock = nil
config.ArrowGlacierBlock = nil
config.Ethash = nil
config.Taiko = true
testEngine = taiko.New(config)
taikoL2AddressPrefix := strings.TrimPrefix(config.ChainID.String(), "0")
taikoL2Address := common.HexToAddress(
"0x" +
taikoL2AddressPrefix +
strings.Repeat("0", common.AddressLength*2-len(taikoL2AddressPrefix)-len(taiko.TaikoL2AddressSuffix)) +
taiko.TaikoL2AddressSuffix,
)
genesis = &core.Genesis{
Config: config,
Alloc: types.GenesisAlloc{testAddr: {Balance: big.NewInt(2e15)}},
ExtraData: []byte("test genesis"),
Timestamp: 9000,
Difficulty: common.Big0,
BaseFee: big.NewInt(params.InitialBaseFee),
}
txs = []*types.Transaction{
types.MustSignNewTx(goldenTouchKey, types.LatestSigner(genesis.Config), &types.DynamicFeeTx{
Nonce: 0,
GasTipCap: common.Big0,
GasFeeCap: new(big.Int).SetUint64(875_000_000),
Data: taiko.AnchorSelector,
Gas: taiko.AnchorGasLimit,
To: &taikoL2Address,
}),
types.MustSignNewTx(testKey, types.LatestSigner(genesis.Config), &types.LegacyTx{
Nonce: 0,
Value: big.NewInt(12),
GasPrice: big.NewInt(params.InitialBaseFee),
Gas: params.TxGas,
To: &common.Address{2},
}),
types.MustSignNewTx(testKey, types.LatestSigner(genesis.Config), &types.LegacyTx{
Nonce: 1,
Value: big.NewInt(8),
GasPrice: big.NewInt(params.InitialBaseFee),
Gas: params.TxGas,
To: &common.Address{2},
}),
// prepareBlockTx
types.MustSignNewTx(testKey, types.LatestSigner(genesis.Config), &types.LegacyTx{
Nonce: 2,
Value: big.NewInt(8),
GasPrice: big.NewInt(params.InitialBaseFee),
Gas: params.TxGas,
To: &testL2RollupAddress,
}),
}
}
func newTestBackend(t *testing.T) (*eth.Ethereum, []*types.Block) {
// Generate test chain.
blocks := generateTestChain()
// Create node
n, err := node.New(&node.Config{})
if err != nil {
t.Fatalf("can't create new node: %v", err)
}
// Create Ethereum Service
config := &ethconfig.Config{
Genesis: genesis,
}
ethservice, err := eth.New(n, config)
if err != nil {
t.Fatalf("can't create new ethereum service: %v", err)
}
// Import the test chain.
if err := n.Start(); err != nil {
t.Fatalf("can't start test node: %v", err)
}
if _, err := ethservice.BlockChain().InsertChain(blocks[1:]); err != nil {
t.Fatalf("can't import test blocks: %v", err)
}
if _, ok := ethservice.Engine().(*taiko.Taiko); !ok {
t.Fatalf("not use taiko engine")
}
return ethservice, blocks
}
func generateTestChain() []*types.Block {
db := rawdb.NewMemoryDatabase()
generate := func(i int, g *core.BlockGen) {
g.OffsetTime(5)
g.SetExtra([]byte("test_taiko"))
g.SetDifficulty(common.Big0)
for i, tx := range txs {
if i == 0 {
if err := tx.MarkAsAnchor(); err != nil {
panic(err)
}
}
g.AddTx(tx)
}
}
gblock := genesis.MustCommit(db, triedb.NewDatabase(db, triedb.HashDefaults))
blocks, _ := core.GenerateChain(genesis.Config, gblock, testEngine, db, 1, generate)
blocks = append([]*types.Block{gblock}, blocks...)
return blocks
}
func TestVerifyHeader(t *testing.T) {
ethService, blocks := newTestBackend(t)
for _, b := range blocks {
err := testEngine.VerifyHeader(ethService.BlockChain(), b.Header())
assert.NoErrorf(t, err, "VerifyHeader error: %s", err)
}
err := testEngine.VerifyHeader(ethService.BlockChain(), &types.Header{
Number: common.Big1,
Time: uint64(time.Now().Unix()),
BaseFee: big.NewInt(params.InitialBaseFee),
WithdrawalsHash: &types.EmptyWithdrawalsHash,
UncleHash: types.EmptyUncleHash,
})
assert.ErrorIs(t, err, consensus.ErrUnknownAncestor, "VerifyHeader should throw ErrUnknownAncestor when parentHash is unknown")
err = testEngine.VerifyHeader(ethService.BlockChain(), &types.Header{
ParentHash: blocks[len(blocks)-1].Hash(),
Number: common.Big0,
Time: uint64(time.Now().Unix()),
BaseFee: big.NewInt(params.InitialBaseFee),
WithdrawalsHash: &types.EmptyWithdrawalsHash,
UncleHash: types.EmptyUncleHash,
})
assert.ErrorIs(t, err, consensus.ErrInvalidNumber, "VerifyHeader should throw ErrInvalidNumber when the block number is wrong")
err = testEngine.VerifyHeader(ethService.BlockChain(), &types.Header{
ParentHash: blocks[len(blocks)-1].Hash(),
Number: new(big.Int).SetInt64(int64(len(blocks))),
Time: uint64(time.Now().Unix()),
Extra: bytes.Repeat([]byte{1}, int(params.MaximumExtraDataSize+1)),
BaseFee: big.NewInt(params.InitialBaseFee),
WithdrawalsHash: &types.EmptyWithdrawalsHash,
UncleHash: types.EmptyUncleHash,
})
assert.ErrorContains(t, err, "extra-data too long", "VerifyHeader should throw ErrExtraDataTooLong when the block has too much extra data")
err = testEngine.VerifyHeader(ethService.BlockChain(), &types.Header{
ParentHash: blocks[len(blocks)-1].Hash(),
Number: new(big.Int).SetInt64(int64(len(blocks))),
Time: uint64(time.Now().Unix()),
Difficulty: common.Big1,
BaseFee: big.NewInt(params.InitialBaseFee),
WithdrawalsHash: &types.EmptyWithdrawalsHash,
UncleHash: types.EmptyUncleHash,
})
assert.ErrorContains(t, err, "invalid difficulty", "VerifyHeader should throw ErrInvalidDifficulty when difficulty is not 0")
err = testEngine.VerifyHeader(ethService.BlockChain(), &types.Header{
ParentHash: blocks[len(blocks)-1].Hash(),
Number: new(big.Int).SetInt64(int64(len(blocks))),
Time: uint64(time.Now().Unix()),
GasLimit: params.MaxGasLimit + 1,
BaseFee: big.NewInt(params.InitialBaseFee),
WithdrawalsHash: &types.EmptyWithdrawalsHash,
UncleHash: types.EmptyUncleHash,
})
assert.ErrorContains(t, err, "invalid gasLimit", "VerifyHeader should throw ErrInvalidGasLimit when gasLimit is higher than the limit")
err = testEngine.VerifyHeader(ethService.BlockChain(), &types.Header{
ParentHash: blocks[len(blocks)-1].Hash(),
Number: new(big.Int).SetInt64(int64(len(blocks))),
Time: uint64(time.Now().Unix()),
GasLimit: params.MaxGasLimit,
BaseFee: big.NewInt(params.InitialBaseFee),
UncleHash: types.EmptyUncleHash,
})
assert.ErrorContains(t, err, "withdrawals hash missing", "VerifyHeader should throw ErrWithdrawalsHashMissing withdrawalshash is nil")
err = testEngine.VerifyHeader(ethService.BlockChain(), &types.Header{
ParentHash: blocks[len(blocks)-1].Hash(),
Number: new(big.Int).SetInt64(int64(len(blocks))),
Time: uint64(time.Now().Unix()),
GasLimit: params.MaxGasLimit,
BaseFee: big.NewInt(params.InitialBaseFee),
WithdrawalsHash: &types.EmptyWithdrawalsHash,
})
assert.ErrorContains(t, err, "uncles not empty", "VerifyHeader should throw ErrUnclesNotEmpty if uncles is not the empty hash")
}

View file

@ -0,0 +1,60 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package rawdb
import (
"encoding/json"
"errors"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
)
var _ = (*l1OriginMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (l L1Origin) MarshalJSON() ([]byte, error) {
type L1Origin struct {
BlockID *math.HexOrDecimal256 `json:"blockID" gencodec:"required"`
L2BlockHash common.Hash `json:"l2BlockHash"`
L1BlockHeight *math.HexOrDecimal256 `json:"l1BlockHeight" gencodec:"required"`
L1BlockHash common.Hash `json:"l1BlockHash" gencodec:"required"`
}
var enc L1Origin
enc.BlockID = (*math.HexOrDecimal256)(l.BlockID)
enc.L2BlockHash = l.L2BlockHash
enc.L1BlockHeight = (*math.HexOrDecimal256)(l.L1BlockHeight)
enc.L1BlockHash = l.L1BlockHash
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (l *L1Origin) UnmarshalJSON(input []byte) error {
type L1Origin struct {
BlockID *math.HexOrDecimal256 `json:"blockID" gencodec:"required"`
L2BlockHash *common.Hash `json:"l2BlockHash"`
L1BlockHeight *math.HexOrDecimal256 `json:"l1BlockHeight" gencodec:"required"`
L1BlockHash *common.Hash `json:"l1BlockHash" gencodec:"required"`
}
var dec L1Origin
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.BlockID == nil {
return errors.New("missing required field 'blockID' for L1Origin")
}
l.BlockID = (*big.Int)(dec.BlockID)
if dec.L2BlockHash != nil {
l.L2BlockHash = *dec.L2BlockHash
}
if dec.L1BlockHeight == nil {
return errors.New("missing required field 'l1BlockHeight' for L1Origin")
}
l.L1BlockHeight = (*big.Int)(dec.L1BlockHeight)
if dec.L1BlockHash == nil {
return errors.New("missing required field 'l1BlockHash' for L1Origin")
}
l.L1BlockHash = *dec.L1BlockHash
return nil
}

View file

@ -0,0 +1,92 @@
package rawdb
import (
"bytes"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
)
var (
// Database key prefix for L2 block's L1Origin.
l1OriginPrefix = []byte("TKO:L1O")
headL1OriginKey = []byte("TKO:LastL1O")
)
// l1OriginKey calculates the L1Origin key.
// l1OriginPrefix + l2HeaderHash -> l1OriginKey
func l1OriginKey(blockID *big.Int) []byte {
data, _ := (*math.HexOrDecimal256)(blockID).MarshalText()
return append(l1OriginPrefix, data...)
}
//go:generate go run github.com/fjl/gencodec -type L1Origin -field-override l1OriginMarshaling -out gen_taiko_l1_origin.go
// L1Origin represents a L1Origin of a L2 block.
type L1Origin struct {
BlockID *big.Int `json:"blockID" gencodec:"required"`
L2BlockHash common.Hash `json:"l2BlockHash"`
L1BlockHeight *big.Int `json:"l1BlockHeight" gencodec:"required"`
L1BlockHash common.Hash `json:"l1BlockHash" gencodec:"required"`
}
type l1OriginMarshaling struct {
BlockID *math.HexOrDecimal256
L1BlockHeight *math.HexOrDecimal256
}
// WriteL1Origin stores a L1Origin into the database.
func WriteL1Origin(db ethdb.KeyValueWriter, blockID *big.Int, l1Origin *L1Origin) {
data, err := rlp.EncodeToBytes(l1Origin)
if err != nil {
log.Crit("Failed to encode L1Origin", "err", err)
}
if err := db.Put(l1OriginKey(blockID), data); err != nil {
log.Crit("Failed to store L1Origin", "err", err)
}
}
// ReadL1Origin retrieves the given L2 block's L1Origin from database.
func ReadL1Origin(db ethdb.KeyValueReader, blockID *big.Int) (*L1Origin, error) {
data, _ := db.Get(l1OriginKey(blockID))
if len(data) == 0 {
return nil, nil
}
l1Origin := new(L1Origin)
if err := rlp.Decode(bytes.NewReader(data), l1Origin); err != nil {
return nil, fmt.Errorf("invalid L1Origin RLP bytes: %w", err)
}
return l1Origin, nil
}
// WriteHeadL1Origin stores the given L1Origin as the last L1Origin.
func WriteHeadL1Origin(db ethdb.KeyValueWriter, blockID *big.Int) {
data, _ := (*math.HexOrDecimal256)(blockID).MarshalText()
if err := db.Put(headL1OriginKey, data); err != nil {
log.Crit("Failed to store head L1Origin", "error", err)
}
}
// ReadHeadL1Origin retrieves the last L1Origin from database.
func ReadHeadL1Origin(db ethdb.KeyValueReader) (*big.Int, error) {
data, _ := db.Get(headL1OriginKey)
if len(data) == 0 {
return nil, nil
}
blockID := new(math.HexOrDecimal256)
if err := blockID.UnmarshalText(data); err != nil {
log.Error("Unmarshal L1Origin unmarshal error", "error", err)
return nil, fmt.Errorf("invalid L1Origin unmarshal: %w", err)
}
return (*big.Int)(blockID), nil
}

View file

@ -0,0 +1,59 @@
package rawdb
import (
"crypto/rand"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// randomBigInt generates a random big integer.
func randomBigInt() *big.Int {
randomBigInt, err := rand.Int(rand.Reader, common.Big256)
if err != nil {
log.Crit(err.Error())
}
return randomBigInt
}
// randomHash generates a random blob of data and returns it as a hash.
func randomHash() common.Hash {
var hash common.Hash
if n, err := rand.Read(hash[:]); n != common.HashLength || err != nil {
panic(err)
}
return hash
}
func TestL1Origin(t *testing.T) {
db := NewMemoryDatabase()
testL1Origin := &L1Origin{
BlockID: randomBigInt(),
L2BlockHash: randomHash(),
L1BlockHeight: randomBigInt(),
L1BlockHash: randomHash(),
}
WriteL1Origin(db, testL1Origin.BlockID, testL1Origin)
l1Origin, err := ReadL1Origin(db, testL1Origin.BlockID)
require.Nil(t, err)
require.NotNil(t, l1Origin)
assert.Equal(t, testL1Origin.BlockID, l1Origin.BlockID)
assert.Equal(t, testL1Origin.L2BlockHash, l1Origin.L2BlockHash)
assert.Equal(t, testL1Origin.L1BlockHeight, l1Origin.L1BlockHeight)
assert.Equal(t, testL1Origin.L1BlockHash, l1Origin.L1BlockHash)
}
func TestHeadL1Origin(t *testing.T) {
db := NewMemoryDatabase()
testBlockID := randomBigInt()
WriteHeadL1Origin(db, testBlockID)
blockID, err := ReadHeadL1Origin(db)
require.Nil(t, err)
require.NotNil(t, blockID)
assert.Equal(t, testBlockID, blockID)
}

View file

@ -81,6 +81,11 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
} }
// Iterate over and process the individual transactions // Iterate over and process the individual transactions
for i, tx := range block.Transactions() { for i, tx := range block.Transactions() {
if i == 0 && p.config.Taiko {
if err := tx.MarkAsAnchor(); err != nil {
return nil, nil, 0, err
}
}
msg, err := TransactionToMessage(tx, signer, header.BaseFee) msg, err := TransactionToMessage(tx, signer, header.BaseFee)
if err != nil { if err != nil {
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)

View file

@ -20,6 +20,7 @@ import (
"fmt" "fmt"
"math" "math"
"math/big" "math/big"
"strings"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
cmath "github.com/ethereum/go-ethereum/common/math" cmath "github.com/ethereum/go-ethereum/common/math"
@ -145,6 +146,9 @@ type Message struct {
// account nonce in state. It also disables checking that the sender is an EOA. // account nonce in state. It also disables checking that the sender is an EOA.
// This field will be set to true for operations like RPC eth_call. // This field will be set to true for operations like RPC eth_call.
SkipAccountChecks bool SkipAccountChecks bool
// CHANGE(taiko): whether the current transaction is the first TaikoL2.anchor transaction in a block.
IsAnchor bool
} }
// TransactionToMessage converts a transaction into a Message. // TransactionToMessage converts a transaction into a Message.
@ -162,6 +166,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
SkipAccountChecks: false, SkipAccountChecks: false,
BlobHashes: tx.BlobHashes(), BlobHashes: tx.BlobHashes(),
BlobGasFeeCap: tx.BlobGasFeeCap(), BlobGasFeeCap: tx.BlobGasFeeCap(),
IsAnchor: tx.IsAnchor(),
} }
// If baseFee provided, set gasPrice to effectiveGasPrice. // If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil { if baseFee != nil {
@ -257,6 +262,10 @@ func (st *StateTransition) buyGas() error {
if overflow { if overflow {
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex()) return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
} }
if st.msg.IsAnchor {
balanceCheckU256 = common.U2560
mgval = common.Big0
}
if have, want := st.state.GetBalance(st.msg.From), balanceCheckU256; have.Cmp(want) < 0 { if have, want := st.state.GetBalance(st.msg.From), balanceCheckU256; have.Cmp(want) < 0 {
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want) return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
} }
@ -297,7 +306,8 @@ func (st *StateTransition) preCheck() error {
// Make sure that transaction gasFeeCap is greater than the baseFee (post london) // Make sure that transaction gasFeeCap is greater than the baseFee (post london)
if st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) { if st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) {
// Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call) // Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call)
skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0 skipCheck := (st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0) ||
st.msg.IsAnchor
if !skipCheck { if !skipCheck {
if l := msg.GasFeeCap.BitLen(); l > 256 { if l := msg.GasFeeCap.BitLen(); l > 256 {
return fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh, return fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh,
@ -457,6 +467,13 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
fee := new(uint256.Int).SetUint64(st.gasUsed()) fee := new(uint256.Int).SetUint64(st.gasUsed())
fee.Mul(fee, effectiveTipU256) fee.Mul(fee, effectiveTipU256)
st.state.AddBalance(st.evm.Context.Coinbase, fee) st.state.AddBalance(st.evm.Context.Coinbase, fee)
// CHANGE(taiko): basefee is not burnt, but sent to a treasury instead.
if st.evm.ChainConfig().Taiko && st.evm.Context.BaseFee != nil && !st.msg.IsAnchor {
st.state.AddBalance(
st.getTreasuryAddress(),
uint256.MustFromBig(new(big.Int).Mul(st.evm.Context.BaseFee, new(big.Int).SetUint64(st.gasUsed()))),
)
}
} }
return &ExecutionResult{ return &ExecutionResult{
@ -475,10 +492,13 @@ func (st *StateTransition) refundGas(refundQuotient uint64) uint64 {
} }
st.gasRemaining += refund st.gasRemaining += refund
// Return ETH for remaining gas, exchanged at the original rate. // Do not change the balance in anchor transactions.
remaining := uint256.NewInt(st.gasRemaining) if !st.msg.IsAnchor {
remaining = remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice)) // Return ETH for remaining gas, exchanged at the original rate.
st.state.AddBalance(st.msg.From, remaining) remaining := uint256.NewInt(st.gasRemaining)
remaining = remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice))
st.state.AddBalance(st.msg.From, remaining)
}
// Also return remaining gas to the block gas counter so it is // Also return remaining gas to the block gas counter so it is
// available for the next transaction. // available for the next transaction.
@ -496,3 +516,17 @@ func (st *StateTransition) gasUsed() uint64 {
func (st *StateTransition) blobGasUsed() uint64 { func (st *StateTransition) blobGasUsed() uint64 {
return uint64(len(st.msg.BlobHashes) * params.BlobTxBlobGasPerBlob) return uint64(len(st.msg.BlobHashes) * params.BlobTxBlobGasPerBlob)
} }
// CHANGE(taiko): returns the treasury address based on chain ID.
func (st *StateTransition) getTreasuryAddress() common.Address {
var (
prefix = st.evm.ChainConfig().ChainID.String()
suffix = "10001"
)
return common.HexToAddress(
"0x" +
prefix +
strings.Repeat("0", common.AddressLength*2-len(prefix)-len(suffix)) +
suffix,
)
}

67
core/taiko_genesis.go Normal file
View file

@ -0,0 +1,67 @@
package core
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
taikoGenesis "github.com/ethereum/go-ethereum/core/taiko_genesis"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
)
// TaikoGenesisBlock returns the Taiko network genesis block configs.
func TaikoGenesisBlock(networkID uint64) *Genesis {
chainConfig := params.TaikoChainConfig
var allocJSON []byte
switch networkID {
case params.TaikoMainnetNetworkID.Uint64():
chainConfig.ChainID = params.TaikoMainnetNetworkID
allocJSON = taikoGenesis.MainnetGenesisAllocJSON
case params.TaikoInternalL2ANetworkID.Uint64():
chainConfig.ChainID = params.TaikoInternalL2ANetworkID
allocJSON = taikoGenesis.InternalL2AGenesisAllocJSON
case params.TaikoInternalL2BNetworkID.Uint64():
chainConfig.ChainID = params.TaikoInternalL2BNetworkID
allocJSON = taikoGenesis.InternalL2BGenesisAllocJSON
case params.SnaefellsjokullNetworkID.Uint64():
chainConfig.ChainID = params.SnaefellsjokullNetworkID
allocJSON = taikoGenesis.SnaefellsjokullGenesisAllocJSON
case params.AskjaNetworkID.Uint64():
chainConfig.ChainID = params.AskjaNetworkID
allocJSON = taikoGenesis.AskjaGenesisAllocJSON
case params.GrimsvotnNetworkID.Uint64():
chainConfig.ChainID = params.GrimsvotnNetworkID
allocJSON = taikoGenesis.GrimsvotnGenesisAllocJSON
case params.EldfellNetworkID.Uint64():
chainConfig.ChainID = params.EldfellNetworkID
allocJSON = taikoGenesis.EldfellGenesisAllocJSON
case params.JolnirNetworkID.Uint64():
chainConfig.ChainID = params.JolnirNetworkID
allocJSON = taikoGenesis.JolnirGenesisAllocJSON
case params.KatlaNetworkID.Uint64():
chainConfig.ChainID = params.KatlaNetworkID
allocJSON = taikoGenesis.KatlaGenesisAllocJSON
case params.HeklaNetworkID.Uint64():
chainConfig.ChainID = params.HeklaNetworkID
allocJSON = taikoGenesis.HeklaGenesisAllocJSON
default:
chainConfig.ChainID = params.TaikoInternalL2ANetworkID
allocJSON = taikoGenesis.InternalL2AGenesisAllocJSON
}
var alloc GenesisAlloc
if err := alloc.UnmarshalJSON(allocJSON); err != nil {
log.Crit("unmarshal alloc json error", "error", err)
}
return &Genesis{
Config: chainConfig,
ExtraData: []byte{},
GasLimit: uint64(15_000_000),
Difficulty: common.Big0,
Alloc: alloc,
GasUsed: 0,
BaseFee: new(big.Int).SetUint64(10_000_000),
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,35 @@
package taiko_genesis
import (
_ "embed"
)
//go:embed internal_l2a.json
var InternalL2AGenesisAllocJSON []byte
//go:embed internal_l2b.json
var InternalL2BGenesisAllocJSON []byte
//go:embed snaefellsjokull.json
var SnaefellsjokullGenesisAllocJSON []byte
//go:embed askja.json
var AskjaGenesisAllocJSON []byte
//go:embed grimsvotn.json
var GrimsvotnGenesisAllocJSON []byte
//go:embed eldfell.json
var EldfellGenesisAllocJSON []byte
//go:embed jolnir.json
var JolnirGenesisAllocJSON []byte
//go:embed katla.json
var KatlaGenesisAllocJSON []byte
//go:embed hekla.json
var HeklaGenesisAllocJSON []byte
//go:embed mainnet.json
var MainnetGenesisAllocJSON []byte

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -18,8 +18,10 @@ package txpool
import ( import (
"crypto/sha256" "crypto/sha256"
"errors"
"fmt" "fmt"
"math/big" "math/big"
"os"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
@ -96,6 +98,10 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
if tx.GasFeeCapIntCmp(tx.GasTipCap()) < 0 { if tx.GasFeeCapIntCmp(tx.GasTipCap()) < 0 {
return core.ErrTipAboveFeeCap return core.ErrTipAboveFeeCap
} }
// CHANGE(taiko): ensure gasFeeCap fee cap larger than 0.1 GWei
if os.Getenv("TAIKO_TEST") == "" && tx.GasFeeCap().Cmp(new(big.Int).SetUint64(params.GWei/10)) < 0 {
return errors.New("max fee per gas is less than 0.1 gwei")
}
// Make sure the transaction is signed properly // Make sure the transaction is signed properly
if _, err := types.Sender(signer, tx); err != nil { if _, err := types.Sender(signer, tx); err != nil {
return ErrInvalidSender return ErrInvalidSender

View file

@ -0,0 +1,42 @@
package types
func (tx *Transaction) MarkAsAnchor() error {
return tx.inner.markAsAnchor()
}
func (tx *Transaction) IsAnchor() bool {
return tx.inner.isAnchor()
}
func (tx *DynamicFeeTx) isAnchor() bool {
return tx.isAnhcor
}
func (tx *LegacyTx) isAnchor() bool {
return false
}
func (tx *AccessListTx) isAnchor() bool {
return false
}
func (tx *BlobTx) isAnchor() bool {
return false
}
func (tx *DynamicFeeTx) markAsAnchor() error {
tx.isAnhcor = true
return nil
}
func (tx *LegacyTx) markAsAnchor() error {
return ErrInvalidTxType
}
func (tx *AccessListTx) markAsAnchor() error {
return ErrInvalidTxType
}
func (tx *BlobTx) markAsAnchor() error {
return ErrInvalidTxType
}

View file

@ -87,6 +87,10 @@ type TxData interface {
nonce() uint64 nonce() uint64
to() *common.Address to() *common.Address
// CHANGE(taiko): anchor transaction related.
isAnchor() bool
markAsAnchor() error
rawSignatureValues() (v, r, s *big.Int) rawSignatureValues() (v, r, s *big.Int)
setSignatureValues(chainID, v, r, s *big.Int) setSignatureValues(chainID, v, r, s *big.Int)
@ -589,3 +593,15 @@ func copyAddressPtr(a *common.Address) *common.Address {
cpy := *a cpy := *a
return &cpy return &cpy
} }
// CHANGE(taiko): SetBlobTxSidecar sets the sidecar of a transaction.
// The sidecar should match the blob-tx versioned hashes, or the transaction will be invalid.
// This allows tools to easily re-attach blob sidecars to signed transactions that omit the sidecar.
func (tx *Transaction) SetBlobTxSidecar(sidecar *BlobTxSidecar) error {
blobtx, ok := tx.inner.(*BlobTx)
if !ok {
return fmt.Errorf("not a blob tx, type = %d", tx.Type())
}
blobtx.Sidecar = sidecar
return nil
}

View file

@ -40,6 +40,9 @@ type DynamicFeeTx struct {
V *big.Int `json:"v" gencodec:"required"` V *big.Int `json:"v" gencodec:"required"`
R *big.Int `json:"r" gencodec:"required"` R *big.Int `json:"r" gencodec:"required"`
S *big.Int `json:"s" gencodec:"required"` S *big.Int `json:"s" gencodec:"required"`
// CHANGE(taiko): if this transaction is the first TaikoL2.anchor transaction in a block
isAnhcor bool
} }
// copy creates a deep copy of the transaction data and initializes all fields. // copy creates a deep copy of the transaction data and initializes all fields.
@ -58,6 +61,7 @@ func (tx *DynamicFeeTx) copy() TxData {
V: new(big.Int), V: new(big.Int),
R: new(big.Int), R: new(big.Int),
S: new(big.Int), S: new(big.Int),
isAnhcor: tx.isAnhcor,
} }
copy(cpy.AccessList, tx.AccessList) copy(cpy.AccessList, tx.AccessList)
if tx.Value != nil { if tx.Value != nil {

View file

@ -20,6 +20,7 @@ package catalyst
import ( import (
"errors" "errors"
"fmt" "fmt"
"math/big"
"sync" "sync"
"time" "time"
@ -318,6 +319,10 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
PayloadID: id, PayloadID: id,
} }
} }
// CHANGE(taiko): check whether --taiko flag is set.
isTaiko := api.eth.BlockChain().Config().Taiko
if rawdb.ReadCanonicalHash(api.eth.ChainDb(), block.NumberU64()) != update.HeadBlockHash { if rawdb.ReadCanonicalHash(api.eth.ChainDb(), block.NumberU64()) != update.HeadBlockHash {
// Block is not canonical, set head. // Block is not canonical, set head.
if latestValid, err := api.eth.BlockChain().SetCanonical(block); err != nil { if latestValid, err := api.eth.BlockChain().SetCanonical(block); err != nil {
@ -327,6 +332,10 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
// If the specified head matches with our local head, do nothing and keep // If the specified head matches with our local head, do nothing and keep
// generating the payload. It's a special corner case that a few slots are // generating the payload. It's a special corner case that a few slots are
// missing and we are requested to generate the payload in slot. // missing and we are requested to generate the payload in slot.
} else if isTaiko { // CHANGE(taiko): reorg is allowed in L2.
if latestValid, err := api.eth.BlockChain().SetCanonical(block); err != nil {
return engine.ForkChoiceResponse{PayloadStatus: engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: &latestValid}}, err
}
} else { } else {
// If the head block is already in our canonical chain, the beacon client is // If the head block is already in our canonical chain, the beacon client is
// probably resyncing. Ignore the update. // probably resyncing. Ignore the update.
@ -371,6 +380,56 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
// sealed by the beacon client. The payload will be requested later, and we // sealed by the beacon client. The payload will be requested later, and we
// will replace it arbitrarily many times in between. // will replace it arbitrarily many times in between.
if payloadAttributes != nil { if payloadAttributes != nil {
// CHANGE(taiko): create a L2 block by Taiko protocol.
if isTaiko {
// No need to check payloadAttribute here, because all its fields are
// marked as required.
block, err := api.eth.Miner().SealBlockWith(
update.HeadBlockHash,
payloadAttributes.Timestamp,
payloadAttributes.BlockMetadata,
payloadAttributes.BaseFeePerGas,
payloadAttributes.Withdrawals,
)
if err != nil {
log.Error("Failed to create sealing block", "err", err)
return valid(nil), engine.InvalidPayloadAttributes.With(err)
}
// Cache the mined block for later use.
args := &miner.BuildPayloadArgs{
Parent: block.ParentHash(),
Timestamp: block.Time(),
FeeRecipient: block.Coinbase(),
Random: block.MixDigest(),
Withdrawals: block.Withdrawals(),
Version: payloadVersion,
}
id := args.Id()
payload, err := api.eth.Miner().BuildPayload(args)
if err != nil {
log.Error("Failed to build payload", "err", err)
return valid(nil), engine.InvalidPayloadAttributes.With(err)
}
payload.SetFullBlock(block, common.Big0)
api.localBlocks.put(id, payload)
// L1Origin **MUST NOT** be nil, it's a required field in PayloadAttributesV1.
l1Origin := payloadAttributes.L1Origin
// Set the block hash before inserting the L1Origin into database.
l1Origin.L2BlockHash = block.Hash()
// Write L1Origin.
rawdb.WriteL1Origin(api.eth.ChainDb(), l1Origin.BlockID, l1Origin)
// Write the head L1Origin.
rawdb.WriteHeadL1Origin(api.eth.ChainDb(), l1Origin.BlockID)
return valid(&id), nil
}
args := &miner.BuildPayloadArgs{ args := &miner.BuildPayloadArgs{
Parent: update.HeadBlockHash, Parent: update.HeadBlockHash,
Timestamp: payloadAttributes.Timestamp, Timestamp: payloadAttributes.Timestamp,
@ -491,7 +550,8 @@ func (api *ConsensusAPI) NewPayloadV2(params engine.ExecutableData) (engine.Payl
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("can't use newPayloadV2 post-cancun")) return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("can't use newPayloadV2 post-cancun"))
} }
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) == forks.Shanghai { if api.eth.BlockChain().Config().LatestFork(params.Timestamp) == forks.Shanghai {
if params.Withdrawals == nil { if params.Withdrawals == nil &&
(api.eth.BlockChain().Config().Taiko && params.WithdrawalsHash == (common.Hash{})) {
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai")) return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
} }
} else { } else {
@ -551,10 +611,37 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe
defer api.newPayloadLock.Unlock() defer api.newPayloadLock.Unlock()
log.Trace("Engine API request received", "method", "NewPayload", "number", params.Number, "hash", params.BlockHash) log.Trace("Engine API request received", "method", "NewPayload", "number", params.Number, "hash", params.BlockHash)
block, err := engine.ExecutableDataToBlock(params, versionedHashes, beaconRoot) // CHANGE(taiko): allow passing the executable data with txHash instead of all transactions.
if err != nil { var (
log.Warn("Invalid NewPayload params", "params", params, "error", err) block *types.Block
return api.invalid(err, nil), nil err error
)
params.TaikoBlock = api.eth.BlockChain().Config().Taiko
if api.eth.BlockChain().Config().Taiko && params.Transactions == nil && params.Withdrawals == nil {
block = types.NewBlockWithHeader(&types.Header{
ParentHash: params.ParentHash,
UncleHash: types.EmptyUncleHash,
Coinbase: params.FeeRecipient,
Root: params.StateRoot,
TxHash: params.TxHash,
ReceiptHash: params.ReceiptsRoot,
Bloom: types.BytesToBloom(params.LogsBloom),
Difficulty: common.Big0,
Number: new(big.Int).SetUint64(params.Number),
GasLimit: params.GasLimit,
GasUsed: params.GasUsed,
Time: params.Timestamp,
BaseFee: params.BaseFeePerGas,
Extra: params.ExtraData,
MixDigest: params.Random,
WithdrawalsHash: &params.WithdrawalsHash,
})
} else {
block, err = engine.ExecutableDataToBlock(params, versionedHashes, beaconRoot)
if err != nil {
log.Debug("Invalid NewPayload params", "params", params, "error", err)
return engine.PayloadStatusV1{Status: engine.INVALID}, nil
}
} }
// Stash away the last update to warn the user if the beacon client goes offline // Stash away the last update to warn the user if the beacon client goes offline
api.lastNewPayloadLock.Lock() api.lastNewPayloadLock.Lock()
@ -597,9 +684,18 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe
log.Error("Ignoring pre-merge parent block", "number", params.Number, "hash", params.BlockHash, "td", ptd, "ttd", ttd) log.Error("Ignoring pre-merge parent block", "number", params.Number, "hash", params.BlockHash, "td", ptd, "ttd", ttd)
return engine.INVALID_TERMINAL_BLOCK, nil return engine.INVALID_TERMINAL_BLOCK, nil
} }
if block.Time() <= parent.Time() { // CHANGE(taiko): a block that has the same timestamp as its parents is
log.Warn("Invalid timestamp", "parent", block.Time(), "block", block.Time()) // allowed in Taiko protocol.
return api.invalid(errors.New("invalid timestamp"), parent.Header()), nil if api.eth.BlockChain().Config().Taiko {
if block.Time() < parent.Time() {
log.Warn("Invalid timestamp", "parent", block.Time(), "block", block.Time())
return api.invalid(errors.New("invalid timestamp"), parent.Header()), nil
}
} else {
if block.Time() <= parent.Time() {
log.Warn("Invalid timestamp", "parent", block.Time(), "block", block.Time())
return api.invalid(errors.New("invalid timestamp"), parent.Header()), nil
}
} }
// Another corner case: if the node is in snap sync mode, but the CL client // Another corner case: if the node is in snap sync mode, but the CL client
// tries to make it import a block. That should be denied as pushing something // tries to make it import a block. That should be denied as pushing something

View file

@ -26,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum/consensus/beacon" "github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/consensus/clique"
"github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/consensus/taiko"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/txpool/blobpool" "github.com/ethereum/go-ethereum/core/txpool/blobpool"
"github.com/ethereum/go-ethereum/core/txpool/legacypool" "github.com/ethereum/go-ethereum/core/txpool/legacypool"
@ -165,6 +166,10 @@ type Config struct {
// Clique is allowed for now to live standalone, but ethash is forbidden and can // Clique is allowed for now to live standalone, but ethash is forbidden and can
// only exist on already merged networks. // only exist on already merged networks.
func CreateConsensusEngine(config *params.ChainConfig, db ethdb.Database) (consensus.Engine, error) { func CreateConsensusEngine(config *params.ChainConfig, db ethdb.Database) (consensus.Engine, error) {
// CHANGE(taiko): use Taiko consensus engine when the --taiko flag is set.
if config.Taiko {
return taiko.New(config), nil
}
// If proof-of-authority is requested, set it up // If proof-of-authority is requested, set it up
if config.Clique != nil { if config.Clique != nil {
return beacon.New(clique.New(config.Clique, db)), nil return beacon.New(clique.New(config.Clique, db)), nil

View file

@ -239,6 +239,11 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block,
// Recompute transactions up to the target index. // Recompute transactions up to the target index.
signer := types.MakeSigner(eth.blockchain.Config(), block.Number(), block.Time()) signer := types.MakeSigner(eth.blockchain.Config(), block.Number(), block.Time())
for idx, tx := range block.Transactions() { for idx, tx := range block.Transactions() {
if idx == 0 && eth.config.Genesis.Config.Taiko {
if err := tx.MarkAsAnchor(); err != nil {
return nil, vm.BlockContext{}, nil, nil, err
}
}
// Assemble the transaction call message and return if the requested offset // Assemble the transaction call message and return if the requested offset
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee()) msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
txContext := core.NewEVMTxContext(msg) txContext := core.NewEVMTxContext(msg)

104
eth/taiko_api_backend.go Normal file
View file

@ -0,0 +1,104 @@
package eth
import (
"math/big"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/miner"
)
// TaikoAPIBackend handles L2 node related RPC calls.
type TaikoAPIBackend struct {
eth *Ethereum
}
// NewTaikoAPIBackend creates a new TaikoAPIBackend instance.
func NewTaikoAPIBackend(eth *Ethereum) *TaikoAPIBackend {
return &TaikoAPIBackend{
eth: eth,
}
}
// HeadL1Origin returns the latest L2 block's corresponding L1 origin.
func (s *TaikoAPIBackend) HeadL1Origin() (*rawdb.L1Origin, error) {
blockID, err := rawdb.ReadHeadL1Origin(s.eth.ChainDb())
if err != nil {
return nil, err
}
if blockID == nil {
return nil, ethereum.NotFound
}
l1Origin, err := rawdb.ReadL1Origin(s.eth.ChainDb(), blockID)
if err != nil {
return nil, err
}
if l1Origin == nil {
return nil, ethereum.NotFound
}
return l1Origin, nil
}
// L1OriginByID returns the L2 block's corresponding L1 origin.
func (s *TaikoAPIBackend) L1OriginByID(blockID *math.HexOrDecimal256) (*rawdb.L1Origin, error) {
l1Origin, err := rawdb.ReadL1Origin(s.eth.ChainDb(), (*big.Int)(blockID))
if err != nil {
return nil, err
}
if l1Origin == nil {
return nil, ethereum.NotFound
}
return l1Origin, nil
}
// GetSyncMode returns the node sync mode.
func (s *TaikoAPIBackend) GetSyncMode() (string, error) {
return s.eth.config.SyncMode.String(), nil
}
// TaikoAuthAPIBackend handles L2 node related authorized RPC calls.
type TaikoAuthAPIBackend struct {
eth *Ethereum
}
// NewTaikoAuthAPIBackend creates a new TaikoAuthAPIBackend instance.
func NewTaikoAuthAPIBackend(eth *Ethereum) *TaikoAuthAPIBackend {
return &TaikoAuthAPIBackend{eth}
}
// TxPoolContent retrieves the transaction pool content with the given upper limits.
func (a *TaikoAuthAPIBackend) TxPoolContent(
beneficiary common.Address,
baseFee *big.Int,
blockMaxGasLimit uint64,
maxBytesPerTxList uint64,
locals []string,
maxTransactionsLists uint64,
) ([]*miner.PreBuiltTxList, error) {
log.Debug(
"Fetching L2 pending transactions finished",
"baseFee", baseFee,
"blockMaxGasLimit", blockMaxGasLimit,
"maxBytesPerTxList", maxBytesPerTxList,
"maxTransactions", maxTransactionsLists,
"locals", locals,
)
return a.eth.Miner().BuildTransactionsLists(
beneficiary,
baseFee,
blockMaxGasLimit,
maxBytesPerTxList,
locals,
maxTransactionsLists,
)
}

View file

@ -270,6 +270,13 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
) )
// Trace all the transactions contained within // Trace all the transactions contained within
for i, tx := range task.block.Transactions() { for i, tx := range task.block.Transactions() {
if i == 0 && api.backend.ChainConfig().Taiko {
if err := tx.MarkAsAnchor(); err != nil {
log.Warn("Mark anchor transaction error", "error", err)
task.results[i] = &txTraceResult{TxHash: tx.Hash(), Error: err.Error()}
break
}
}
msg, _ := core.TransactionToMessage(tx, signer, task.block.BaseFee()) msg, _ := core.TransactionToMessage(tx, signer, task.block.BaseFee())
txctx := &Context{ txctx := &Context{
BlockHash: task.block.Hash(), BlockHash: task.block.Hash(),
@ -527,6 +534,11 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
deleteEmptyObjects = chainConfig.IsEIP158(block.Number()) deleteEmptyObjects = chainConfig.IsEIP158(block.Number())
) )
for i, tx := range block.Transactions() { for i, tx := range block.Transactions() {
if i == 0 && chainConfig.Taiko {
if err := tx.MarkAsAnchor(); err != nil {
return nil, err
}
}
if err := ctx.Err(); err != nil { if err := ctx.Err(); err != nil {
return nil, err return nil, err
} }
@ -604,6 +616,11 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
results = make([]*txTraceResult, len(txs)) results = make([]*txTraceResult, len(txs))
) )
for i, tx := range txs { for i, tx := range txs {
if i == 0 && api.backend.ChainConfig().Taiko {
if err := tx.MarkAsAnchor(); err != nil {
return nil, err
}
}
// Generate the next state snapshot fast without tracing // Generate the next state snapshot fast without tracing
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee()) msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
txctx := &Context{ txctx := &Context{
@ -757,6 +774,11 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
chainConfig, canon = overrideConfig(chainConfig, config.Overrides) chainConfig, canon = overrideConfig(chainConfig, config.Overrides)
} }
for i, tx := range block.Transactions() { for i, tx := range block.Transactions() {
if i == 0 && chainConfig.Taiko {
if err := tx.MarkAsAnchor(); err != nil {
return nil, err
}
}
// Prepare the transaction for un-traced execution // Prepare the transaction for un-traced execution
var ( var (
msg, _ = core.TransactionToMessage(tx, signer, block.BaseFee()) msg, _ = core.TransactionToMessage(tx, signer, block.BaseFee())

42
ethclient/taiko_api.go Normal file
View file

@ -0,0 +1,42 @@
package ethclient
import (
"context"
"math/big"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb"
)
// HeadL1Origin returns the latest L2 block's corresponding L1 origin.
func (ec *Client) HeadL1Origin(ctx context.Context) (*rawdb.L1Origin, error) {
var res *rawdb.L1Origin
if err := ec.c.CallContext(ctx, &res, "taiko_headL1Origin"); err != nil {
return nil, err
}
return res, nil
}
// L1OriginByID returns the L2 block's corresponding L1 origin.
func (ec *Client) L1OriginByID(ctx context.Context, blockID *big.Int) (*rawdb.L1Origin, error) {
var res *rawdb.L1Origin
if err := ec.c.CallContext(ctx, &res, "taiko_l1OriginByID", hexutil.EncodeBig(blockID)); err != nil {
return nil, err
}
return res, nil
}
// GetSyncMode returns the current sync mode of the L2 node.
func (ec *Client) GetSyncMode(ctx context.Context) (string, error) {
var res string
if err := ec.c.CallContext(ctx, &res, "taiko_getSyncMode"); err != nil {
return "", err
}
return res, nil
}

124
ethclient/taiko_api_test.go Normal file
View file

@ -0,0 +1,124 @@
package ethclient
import (
"context"
"crypto/rand"
"math/big"
"testing"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require"
)
func newTaikoAPITestClient(t *testing.T) (*Client, []*types.Block, ethdb.Database) {
// Generate test chain.
blocks := generateTestChain()
// Create node
n, err := node.New(&node.Config{})
require.Nil(t, err)
// Create Ethereum Service
config := &ethconfig.Config{Genesis: genesis}
ethservice, err := eth.New(n, config)
require.Nil(t, err)
n.RegisterAPIs([]rpc.API{
{
Namespace: "taiko",
Version: params.VersionWithMeta,
Service: eth.NewTaikoAPIBackend(ethservice),
Public: true,
},
})
// Start node
require.Nil(t, n.Start())
// Insert test blocks
_, err = ethservice.BlockChain().InsertChain(blocks[1:])
require.Nil(t, err)
return NewClient(n.Attach()), blocks, ethservice.ChainDb()
}
func TestHeadL1Origin(t *testing.T) {
ec, blocks, db := newTaikoAPITestClient(t)
headerHash := blocks[len(blocks)-1].Hash()
l1OriginFound, err := ec.HeadL1Origin(context.Background())
require.Equal(t, ethereum.NotFound.Error(), err.Error())
require.Nil(t, l1OriginFound)
testL1Origin := &rawdb.L1Origin{
BlockID: randomBigInt(),
L2BlockHash: headerHash,
L1BlockHeight: randomBigInt(),
L1BlockHash: randomHash(),
}
rawdb.WriteL1Origin(db, testL1Origin.BlockID, testL1Origin)
rawdb.WriteHeadL1Origin(db, testL1Origin.BlockID)
l1OriginFound, err = ec.HeadL1Origin(context.Background())
require.Nil(t, err)
require.Equal(t, testL1Origin, l1OriginFound)
}
func TestL1OriginByID(t *testing.T) {
ec, blocks, db := newTaikoAPITestClient(t)
headerHash := blocks[len(blocks)-1].Hash()
testL1Origin := &rawdb.L1Origin{
BlockID: randomBigInt(),
L2BlockHash: headerHash,
L1BlockHeight: randomBigInt(),
L1BlockHash: randomHash(),
}
l1OriginFound, err := ec.L1OriginByID(context.Background(), testL1Origin.BlockID)
require.Equal(t, ethereum.NotFound.Error(), err.Error())
require.Nil(t, l1OriginFound)
rawdb.WriteL1Origin(db, testL1Origin.BlockID, testL1Origin)
rawdb.WriteHeadL1Origin(db, testL1Origin.BlockID)
l1OriginFound, err = ec.L1OriginByID(context.Background(), testL1Origin.BlockID)
require.Nil(t, err)
require.Equal(t, testL1Origin, l1OriginFound)
}
// randomHash generates a random blob of data and returns it as a hash.
func randomHash() common.Hash {
var hash common.Hash
if n, err := rand.Read(hash[:]); n != common.HashLength || err != nil {
panic(err)
}
return hash
}
// randomBigInt generates a random big integer.
func randomBigInt() *big.Int {
randomBigInt, err := rand.Int(rand.Reader, common.Big256)
if err != nil {
log.Crit(err.Error())
}
return randomBigInt
}

2
go.mod
View file

@ -72,6 +72,7 @@ require (
golang.org/x/tools v0.15.0 golang.org/x/tools v0.15.0
gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
modernc.org/mathutil v1.6.0
) )
require ( require (
@ -132,6 +133,7 @@ require (
github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a // indirect github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a // indirect
github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect github.com/prometheus/procfs v0.7.3 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect

4
go.sum
View file

@ -531,6 +531,8 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7 h1:cZC+usqsYgHtlBaGulVnZ1hfKAi8iWtujBnRLQE698c= github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7 h1:cZC+usqsYgHtlBaGulVnZ1hfKAi8iWtujBnRLQE698c=
github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7/go.mod h1:IToEjHuttnUzwZI5KBSM/LOOW3qLbbrHOEfp3SbECGY= github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7/go.mod h1:IToEjHuttnUzwZI5KBSM/LOOW3qLbbrHOEfp3SbECGY=
github.com/prysmaticlabs/gohashtree v0.0.1-alpha.0.20220714111606-acbb2962fb48 h1:cSo6/vk8YpvkLbk9v3FO97cakNmUoxwi2KMP8hd5WIw= github.com/prysmaticlabs/gohashtree v0.0.1-alpha.0.20220714111606-acbb2962fb48 h1:cSo6/vk8YpvkLbk9v3FO97cakNmUoxwi2KMP8hd5WIw=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@ -973,6 +975,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View file

@ -76,6 +76,7 @@ type Payload struct {
stop chan struct{} stop chan struct{}
lock sync.Mutex lock sync.Mutex
cond *sync.Cond cond *sync.Cond
done chan struct{} // CHANGE(taiko): done channel to communicate we shouldnt write to `stop` channel.
} }
// newPayload initializes the payload object. // newPayload initializes the payload object.
@ -84,6 +85,7 @@ func newPayload(empty *types.Block, id engine.PayloadID) *Payload {
id: id, id: id,
empty: empty, empty: empty,
stop: make(chan struct{}), stop: make(chan struct{}),
done: make(chan struct{}, 1), // CHANGE(taiko): buffered channel to communicate done to taiko payload builder
} }
log.Info("Starting work on payload", "id", payload.id) log.Info("Starting work on payload", "id", payload.id)
payload.cond = sync.NewCond(&payload.lock) payload.cond = sync.NewCond(&payload.lock)
@ -133,6 +135,7 @@ func (payload *Payload) Resolve() *engine.ExecutionPayloadEnvelope {
select { select {
case <-payload.stop: case <-payload.stop:
default: default:
payload.done <- struct{}{} // CHANGE(taiko): signal to taiko payload builder to not write to `payload.stop` channel
close(payload.stop) close(payload.stop)
} }
if payload.full != nil { if payload.full != nil {
@ -171,6 +174,7 @@ func (payload *Payload) ResolveFull() *engine.ExecutionPayloadEnvelope {
select { select {
case <-payload.stop: case <-payload.stop:
default: default:
payload.done <- struct{}{} // CHANGE(taiko): signal to taiko payload builder to not write to `payload.stop` channel
close(payload.stop) close(payload.stop)
} }
return engine.BlockToExecutableData(payload.full, payload.fullFees, payload.sidecars) return engine.BlockToExecutableData(payload.full, payload.fullFees, payload.sidecars)
@ -226,6 +230,10 @@ func (w *worker) buildPayload(args *BuildPayloadArgs) (*Payload, error) {
for { for {
select { select {
case <-timer.C: case <-timer.C:
// CHANGE(taiko): do not update payload.
if w.chainConfig.Taiko {
continue
}
start := time.Now() start := time.Now()
r := w.getSealingBlock(fullParams) r := w.getSealingBlock(fullParams)
if r.err == nil { if r.err == nil {

47
miner/taiko_miner.go Normal file
View file

@ -0,0 +1,47 @@
package miner
import (
"math/big"
"github.com/ethereum/go-ethereum/beacon/engine"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
// PreBuiltTxList is a pre-built transaction list based on the latest chain state,
// with estimated gas used / bytes.
type PreBuiltTxList struct {
TxList types.Transactions
EstimatedGasUsed uint64
BytesLength uint64
}
// SealBlockWith mines and seals a block without changing the canonical chain.
func (miner *Miner) SealBlockWith(
parent common.Hash,
timestamp uint64,
blkMeta *engine.BlockMetadata,
baseFeePerGas *big.Int,
withdrawals types.Withdrawals,
) (*types.Block, error) {
return miner.worker.sealBlockWith(parent, timestamp, blkMeta, baseFeePerGas, withdrawals)
}
// BuildTransactionsLists builds multiple transactions lists which satisfy all the given limits.
func (miner *Miner) BuildTransactionsLists(
beneficiary common.Address,
baseFee *big.Int,
blockMaxGasLimit uint64,
maxBytesPerTxList uint64,
locals []string,
maxTransactionsLists uint64,
) ([]*PreBuiltTxList, error) {
return miner.worker.BuildTransactionsLists(
beneficiary,
baseFee,
blockMaxGasLimit,
maxBytesPerTxList,
locals,
maxTransactionsLists,
)
}

View file

@ -0,0 +1,53 @@
package miner
import (
"math/big"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
)
// SetFullBlock updates the full-block to the given block.
func (payload *Payload) SetFullBlock(block *types.Block, fees *big.Int) {
payload.lock.Lock()
defer payload.lock.Unlock()
go payload.afterSetFullBlock()
payload.full = block
payload.fullFees = fees
feesInEther := new(big.Float).Quo(new(big.Float).SetInt(fees), big.NewFloat(params.Ether))
log.Info("Updated payload", "id", payload.id, "number", block.NumberU64(), "hash", block.Hash(),
"txs", len(block.Transactions()), "gas", block.GasUsed(), "fees", feesInEther,
"root", block.Root())
payload.cond.Broadcast() // fire signal for notifying full block
}
func (payload *Payload) afterSetFullBlock() {
payload.lock.Lock()
defer payload.lock.Unlock()
select {
case <-payload.done:
log.Info("SetFullBlock payload.done received", "id", payload.id)
return
default:
}
select {
case _, ok := <-payload.stop:
if ok {
log.Info("SetFullBlock payload.stop received", "id", payload.id)
return
}
default:
}
select {
case payload.stop <- struct{}{}:
default:
}
}

View file

@ -0,0 +1,140 @@
package miner
import (
"math/big"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
"github.com/stretchr/testify/assert"
)
func newTestBlock() *types.Block {
tx1 := types.NewTransaction(1, common.BytesToAddress([]byte{0x11}), big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11})
txs := []*types.Transaction{tx1}
block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, txs, nil, nil, trie.NewStackTrie(nil))
return block
}
func TestSetFullBlock_AvoidPanic(t *testing.T) {
var (
db = rawdb.NewMemoryDatabase()
recipient = common.HexToAddress("0xdeadbeef")
)
w, b := newTestWorker(t, params.TestChainConfig, ethash.NewFaker(), db, 0)
defer w.close()
timestamp := uint64(time.Now().Unix())
args := &BuildPayloadArgs{
Parent: b.chain.CurrentBlock().Hash(),
Timestamp: timestamp,
Random: common.Hash{},
FeeRecipient: recipient,
}
payload, err := w.buildPayload(args)
if err != nil {
t.Fatalf("Failed to build payload %v", err)
}
fees := big.NewInt(1)
payload.done <- struct{}{}
close(payload.stop)
block := newTestBlock()
// expect not to panic sending to payload.stop
// now that done is closed
payload.SetFullBlock(block, fees)
}
func TestAfterSetFullBlock_Panic_DoneChannelNotSent(t *testing.T) {
var (
db = rawdb.NewMemoryDatabase()
recipient = common.HexToAddress("0xdeadbeef")
)
w, b := newTestWorker(t, params.TestChainConfig, ethash.NewFaker(), db, 0)
defer w.close()
timestamp := uint64(time.Now().Unix())
args := &BuildPayloadArgs{
Parent: b.chain.CurrentBlock().Hash(),
Timestamp: timestamp,
Random: common.Hash{},
FeeRecipient: recipient,
}
payload, err := w.buildPayload(args)
if err != nil {
t.Fatalf("Failed to build payload %v", err)
}
// dont send on done channel, but close stop channel.
// should panic when sent on.
close(payload.stop)
assert.Panics(t, func() {
payload.afterSetFullBlock()
})
}
func TestAfterSetFullBlock_AvoidPanic_DoneChannelSent(t *testing.T) {
var (
db = rawdb.NewMemoryDatabase()
recipient = common.HexToAddress("0xdeadbeef")
)
w, b := newTestWorker(t, params.TestChainConfig, ethash.NewFaker(), db, 0)
defer w.close()
timestamp := uint64(time.Now().Unix())
args := &BuildPayloadArgs{
Parent: b.chain.CurrentBlock().Hash(),
Timestamp: timestamp,
Random: common.Hash{},
FeeRecipient: recipient,
}
payload, err := w.buildPayload(args)
if err != nil {
t.Fatalf("Failed to build payload %v", err)
}
payload.done <- struct{}{}
close(payload.stop)
assert.NotPanics(t, func() {
payload.afterSetFullBlock()
})
}
func TestSetFullBlock(t *testing.T) {
var (
db = rawdb.NewMemoryDatabase()
recipient = common.HexToAddress("0xdeadbeef")
)
w, b := newTestWorker(t, params.TestChainConfig, ethash.NewFaker(), db, 0)
defer w.close()
timestamp := uint64(time.Now().Unix())
args := &BuildPayloadArgs{
Parent: b.chain.CurrentBlock().Hash(),
Timestamp: timestamp,
Random: common.Hash{},
FeeRecipient: recipient,
}
payload, err := w.buildPayload(args)
if err != nil {
t.Fatalf("Failed to build payload %v", err)
}
fees := big.NewInt(1)
block := newTestBlock()
payload.SetFullBlock(block, fees)
assert.Equal(t, block, payload.full)
assert.Equal(t, fees, payload.fullFees)
}

335
miner/taiko_worker.go Normal file
View file

@ -0,0 +1,335 @@
package miner
import (
"bytes"
"compress/zlib"
"errors"
"fmt"
"math/big"
"time"
"github.com/ethereum/go-ethereum/beacon/engine"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/holiman/uint256"
)
// BuildTransactionsLists builds multiple transactions lists which satisfy all the given conditions
// 1. All transactions should all be able to pay the given base fee.
// 2. The total gas used should not exceed the given blockMaxGasLimit
// 3. The total bytes used should not exceed the given maxBytesPerTxList
// 4. The total number of transactions lists should not exceed the given maxTransactionsLists
func (w *worker) BuildTransactionsLists(
beneficiary common.Address,
baseFee *big.Int,
blockMaxGasLimit uint64,
maxBytesPerTxList uint64,
localAccounts []string,
maxTransactionsLists uint64,
) ([]*PreBuiltTxList, error) {
var (
txsLists []*PreBuiltTxList
currentHead = w.chain.CurrentBlock()
)
if currentHead == nil {
return nil, fmt.Errorf("failed to find current head")
}
// Check if tx pool is empty at first.
if len(w.eth.TxPool().Pending(txpool.PendingFilter{BaseFee: uint256.MustFromBig(baseFee), OnlyPlainTxs: true})) == 0 {
return txsLists, nil
}
params := &generateParams{
timestamp: uint64(time.Now().Unix()),
forceTime: true,
parentHash: currentHead.Hash(),
coinbase: beneficiary,
random: currentHead.MixDigest,
noTxs: false,
baseFeePerGas: baseFee,
}
env, err := w.prepareWork(params)
if err != nil {
return nil, err
}
defer env.discard()
var (
signer = types.MakeSigner(w.chainConfig, new(big.Int).Add(currentHead.Number, common.Big1), currentHead.Time)
// Split the pending transactions into locals and remotes, then
// fill the block with all available pending transactions.
localTxs, remoteTxs = w.getPendingTxs(localAccounts, baseFee)
)
commitTxs := func() (*PreBuiltTxList, error) {
env.tcount = 0
env.txs = []*types.Transaction{}
env.gasPool = new(core.GasPool).AddGas(blockMaxGasLimit)
env.header.GasLimit = blockMaxGasLimit
var (
locals = make(map[common.Address][]*txpool.LazyTransaction)
remotes = make(map[common.Address][]*txpool.LazyTransaction)
)
for address, txs := range localTxs {
locals[address] = txs
}
for address, txs := range remoteTxs {
remotes[address] = txs
}
w.commitL2Transactions(
env,
newTransactionsByPriceAndNonce(signer, locals, baseFee),
newTransactionsByPriceAndNonce(signer, remotes, baseFee),
maxBytesPerTxList,
)
b, err := encodeAndComporeessTxList(env.txs)
if err != nil {
return nil, err
}
return &PreBuiltTxList{
TxList: env.txs,
EstimatedGasUsed: env.header.GasLimit - env.gasPool.Gas(),
BytesLength: uint64(len(b)),
}, nil
}
for i := 0; i < int(maxTransactionsLists); i++ {
res, err := commitTxs()
if err != nil {
return nil, err
}
if len(res.TxList) == 0 {
break
}
txsLists = append(txsLists, res)
}
return txsLists, nil
}
// sealBlockWith mines and seals a block with the given block metadata.
func (w *worker) sealBlockWith(
parent common.Hash,
timestamp uint64,
blkMeta *engine.BlockMetadata,
baseFeePerGas *big.Int,
withdrawals types.Withdrawals,
) (*types.Block, error) {
// Decode transactions bytes.
var txs types.Transactions
if err := rlp.DecodeBytes(blkMeta.TxList, &txs); err != nil {
return nil, fmt.Errorf("failed to decode txList: %w", err)
}
if len(txs) == 0 {
// A L2 block needs to have have at least one `V1TaikoL2.anchor` or
// `V1TaikoL2.invalidateBlock` transaction.
return nil, fmt.Errorf("too less transactions in the block")
}
params := &generateParams{
timestamp: timestamp,
forceTime: true,
parentHash: parent,
coinbase: blkMeta.Beneficiary,
random: blkMeta.MixHash,
withdrawals: withdrawals,
noTxs: false,
baseFeePerGas: baseFeePerGas,
}
// Set extraData
w.extra = blkMeta.ExtraData
env, err := w.prepareWork(params)
if err != nil {
return nil, err
}
defer env.discard()
env.header.GasLimit = blkMeta.GasLimit
// Commit transactions.
gasLimit := env.header.GasLimit
rules := w.chain.Config().Rules(env.header.Number, true, timestamp)
env.gasPool = new(core.GasPool).AddGas(gasLimit)
for i, tx := range txs {
if i == 0 {
if err := tx.MarkAsAnchor(); err != nil {
return nil, err
}
}
sender, err := types.LatestSignerForChainID(tx.ChainId()).Sender(tx)
if err != nil {
log.Info("Skip an invalid proposed transaction", "hash", tx.Hash(), "reason", err)
continue
}
env.state.Prepare(rules, sender, blkMeta.Beneficiary, tx.To(), vm.ActivePrecompiles(rules), tx.AccessList())
env.state.SetTxContext(tx.Hash(), env.tcount)
if _, err := w.commitTransaction(env, tx); err != nil {
log.Info("Skip an invalid proposed transaction", "hash", tx.Hash(), "reason", err)
continue
}
env.tcount++
}
block, err := w.engine.FinalizeAndAssemble(w.chain, env.header, env.state, env.txs, nil, env.receipts, withdrawals)
if err != nil {
return nil, err
}
results := make(chan *types.Block, 1)
if err := w.engine.Seal(w.chain, block, results, nil); err != nil {
return nil, err
}
block = <-results
return block, nil
}
// getPendingTxs fetches the pending transactions from tx pool.
func (w *worker) getPendingTxs(localAccounts []string, baseFee *big.Int) (
map[common.Address][]*txpool.LazyTransaction,
map[common.Address][]*txpool.LazyTransaction,
) {
pending := w.eth.TxPool().Pending(txpool.PendingFilter{OnlyPlainTxs: true, BaseFee: uint256.MustFromBig(baseFee)})
localTxs, remoteTxs := make(map[common.Address][]*txpool.LazyTransaction), pending
for _, local := range localAccounts {
account := common.HexToAddress(local)
if txs := remoteTxs[account]; len(txs) > 0 {
delete(remoteTxs, account)
localTxs[account] = txs
}
}
return localTxs, remoteTxs
}
// commitL2Transactions tries to commit the transactions into the given state.
func (w *worker) commitL2Transactions(
env *environment,
txsLocal *transactionsByPriceAndNonce,
txsRemote *transactionsByPriceAndNonce,
maxBytesPerTxList uint64,
) {
var (
txs = txsLocal
isLocal = true
)
for {
// If we don't have enough gas for any further transactions then we're done.
if env.gasPool.Gas() < params.TxGas {
log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", params.TxGas)
break
}
// Retrieve the next transaction and abort if all done.
ltx, _ := txs.Peek()
if ltx == nil {
if isLocal {
txs = txsRemote
isLocal = false
continue
}
break
}
tx := ltx.Resolve()
if tx == nil {
log.Trace("Ignoring evicted transaction")
txs.Pop()
continue
}
// Error may be ignored here. The error has already been checked
// during transaction acceptance is the transaction pool.
from, _ := types.Sender(env.signer, tx)
b, err := encodeAndComporeessTxList(append(env.txs, tx))
if err != nil {
log.Trace("Failed to rlp encode and compress the pending transaction %s: %w", tx.Hash(), err)
txs.Pop()
continue
}
if len(b) >= int(maxBytesPerTxList) {
break
}
// Check whether the tx is replay protected. If we're not in the EIP155 hf
// phase, start ignoring the sender until we do.
if tx.Protected() && !w.chainConfig.IsEIP155(env.header.Number) {
log.Trace("Ignoring reply protected transaction", "hash", tx.Hash(), "eip155", w.chainConfig.EIP155Block)
txs.Pop()
continue
}
// Start executing the transaction
env.state.SetTxContext(tx.Hash(), env.tcount)
_, err = w.commitTransaction(env, tx)
switch {
case errors.Is(err, core.ErrNonceTooLow):
// New head notification data race between the transaction pool and miner, shift
log.Trace("Skipping transaction with low nonce", "hash", ltx.Hash, "sender", from, "nonce", tx.Nonce())
txs.Shift()
case errors.Is(err, nil):
// Everything ok, collect the logs and shift in the next transaction from the same account
env.tcount++
txs.Shift()
default:
// Transaction is regarded as invalid, drop all consecutive transactions from
// the same sender because of `nonce-too-high` clause.
log.Trace("Transaction failed, account skipped", "hash", ltx.Hash, "err", err)
txs.Pop()
}
}
}
// encodeAndComporeessTxList encodes and compresses the given transactions list.
func encodeAndComporeessTxList(txs types.Transactions) ([]byte, error) {
b, err := rlp.EncodeToBytes(txs)
if err != nil {
return nil, err
}
return compress(b)
}
// compress compresses the given txList bytes using zlib.
func compress(txListBytes []byte) ([]byte, error) {
var b bytes.Buffer
w := zlib.NewWriter(&b)
defer w.Close()
if _, err := w.Write(txListBytes); err != nil {
return nil, err
}
if err := w.Flush(); err != nil {
return nil, err
}
return b.Bytes(), nil
}

View file

@ -925,14 +925,15 @@ func (w *worker) commitTransactions(env *environment, plainTxs, blobTxs *transac
// generateParams wraps various of settings for generating sealing task. // generateParams wraps various of settings for generating sealing task.
type generateParams struct { type generateParams struct {
timestamp uint64 // The timestamp for sealing task timestamp uint64 // The timestamp for sealing task
forceTime bool // Flag whether the given timestamp is immutable or not forceTime bool // Flag whether the given timestamp is immutable or not
parentHash common.Hash // Parent block hash, empty means the latest chain head parentHash common.Hash // Parent block hash, empty means the latest chain head
coinbase common.Address // The fee recipient address for including transaction coinbase common.Address // The fee recipient address for including transaction
random common.Hash // The randomness generated by beacon chain, empty before the merge random common.Hash // The randomness generated by beacon chain, empty before the merge
withdrawals types.Withdrawals // List of withdrawals to include in block. withdrawals types.Withdrawals // List of withdrawals to include in block.
beaconRoot *common.Hash // The beacon root (cancun field). beaconRoot *common.Hash // The beacon root (cancun field).
noTxs bool // Flag whether an empty block without any transaction is expected noTxs bool // Flag whether an empty block without any transaction is expected
baseFeePerGas *big.Int // CHANGE(taiko): The base fee per gas for the next block
} }
// prepareWork constructs the sealing task according to the given parameters, // prepareWork constructs the sealing task according to the given parameters,
@ -955,10 +956,17 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) {
// to parent+1 if the mutation is allowed. // to parent+1 if the mutation is allowed.
timestamp := genParams.timestamp timestamp := genParams.timestamp
if parent.Time >= timestamp { if parent.Time >= timestamp {
if genParams.forceTime { // CHANGE(taiko): block.timestamp == parent.timestamp is allowed in Taiko protocol.
return nil, fmt.Errorf("invalid timestamp, parent %d given %d", parent.Time, timestamp) if !w.chainConfig.Taiko {
if genParams.forceTime {
return nil, fmt.Errorf("invalid timestamp, parent %d given %d", parent.Time, timestamp)
}
timestamp = parent.Time + 1
} else {
if parent.Time > timestamp {
return nil, fmt.Errorf("invalid timestamp, parent %d given %d", parent.Time, timestamp)
}
} }
timestamp = parent.Time + 1
} }
// Construct the sealing block header. // Construct the sealing block header.
header := &types.Header{ header := &types.Header{
@ -978,10 +986,14 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) {
} }
// Set baseFee and GasLimit if we are on an EIP-1559 chain // Set baseFee and GasLimit if we are on an EIP-1559 chain
if w.chainConfig.IsLondon(header.Number) { if w.chainConfig.IsLondon(header.Number) {
header.BaseFee = eip1559.CalcBaseFee(w.chainConfig, parent) if w.chainConfig.Taiko && genParams.baseFeePerGas != nil {
if !w.chainConfig.IsLondon(parent.Number) { header.BaseFee = genParams.baseFeePerGas
parentGasLimit := parent.GasLimit * w.chainConfig.ElasticityMultiplier() } else {
header.GasLimit = core.CalcGasLimit(parentGasLimit, w.config.GasCeil) header.BaseFee = eip1559.CalcBaseFee(w.chainConfig, parent)
if !w.chainConfig.IsLondon(parent.Number) {
parentGasLimit := parent.GasLimit * w.chainConfig.ElasticityMultiplier()
header.GasLimit = core.CalcGasLimit(parentGasLimit, w.config.GasCeil)
}
} }
} }
// Apply EIP-4844, EIP-4788. // Apply EIP-4844, EIP-4788.

View file

@ -49,7 +49,8 @@ var (
DefaultAuthVhosts = []string{"localhost"} // Default virtual hosts for the authenticated apis DefaultAuthVhosts = []string{"localhost"} // Default virtual hosts for the authenticated apis
DefaultAuthOrigins = []string{"localhost"} // Default origins for the authenticated apis DefaultAuthOrigins = []string{"localhost"} // Default origins for the authenticated apis
DefaultAuthPrefix = "" // Default prefix for the authenticated apis DefaultAuthPrefix = "" // Default prefix for the authenticated apis
DefaultAuthModules = []string{"eth", "engine"} // CHANGE(taiko): add `taikoAuth` namespace for authenticated APIs.
DefaultAuthModules = []string{"eth", "engine", rpc.TaikoAuth}
) )
// DefaultConfig contains reasonable default settings. // DefaultConfig contains reasonable default settings.

View file

@ -62,7 +62,8 @@ func checkModuleAvailability(modules []string, apis []rpc.API) (bad, available [
} }
for _, name := range modules { for _, name := range modules {
if _, ok := availableSet[name]; !ok { if _, ok := availableSet[name]; !ok {
if name != rpc.MetadataApi && name != rpc.EngineApi { // CHANGE(taiko): add `taikoAuth` namespace for authenticated APIs.
if name != rpc.MetadataApi && name != rpc.EngineApi && name != rpc.TaikoAuth {
bad = append(bad, name) bad = append(bad, name)
} }
} }

View file

@ -315,6 +315,17 @@ var NetworkNames = map[string]string{
GoerliChainConfig.ChainID.String(): "goerli", GoerliChainConfig.ChainID.String(): "goerli",
SepoliaChainConfig.ChainID.String(): "sepolia", SepoliaChainConfig.ChainID.String(): "sepolia",
HoleskyChainConfig.ChainID.String(): "holesky", HoleskyChainConfig.ChainID.String(): "holesky",
// CHANGE(taiko): add Taiko network name.
TaikoMainnetNetworkID.String(): "Taiko",
TaikoInternalL2ANetworkID.String(): "Taiko Internal L2A Devnet",
TaikoInternalL2BNetworkID.String(): "Taiko Internal L2B Devnet",
SnaefellsjokullNetworkID.String(): "Taiko Alpha-1 (Snæfellsjökull)",
AskjaNetworkID.String(): "Taiko Alpha-2 (Askja)",
GrimsvotnNetworkID.String(): "Taiko Alpha-3 L2 (Grimsvotn)",
EldfellNetworkID.String(): "Taiko Alpha-4 L3 (Eldfell)",
JolnirNetworkID.String(): "Taiko Alpha-5 L2 (Jolnir)",
KatlaNetworkID.String(): "Taiko Alpha-6 L2 (Katla)",
HeklaNetworkID.String(): "Taiko Alpha-7 L2 (Hekla)",
} }
// ChainConfig is the core config which determines the blockchain settings. // ChainConfig is the core config which determines the blockchain settings.
@ -365,6 +376,9 @@ type ChainConfig struct {
// Various consensus engines // Various consensus engines
Ethash *EthashConfig `json:"ethash,omitempty"` Ethash *EthashConfig `json:"ethash,omitempty"`
Clique *CliqueConfig `json:"clique,omitempty"` Clique *CliqueConfig `json:"clique,omitempty"`
// CHANGE(taiko): Taiko network flag.
Taiko bool `json:"taiko"`
} }
// EthashConfig is the consensus engine configs for proof-of-work based sealing. // EthashConfig is the consensus engine configs for proof-of-work based sealing.
@ -397,6 +411,9 @@ func (c *ChainConfig) Description() string {
} }
banner += fmt.Sprintf("Chain ID: %v (%s)\n", c.ChainID, network) banner += fmt.Sprintf("Chain ID: %v (%s)\n", c.ChainID, network)
switch { switch {
// CHANGE(taiko): print Taiko consensus engine in banner.
case c.Taiko:
banner += "Consensus: Taiko\n"
case c.Ethash != nil: case c.Ethash != nil:
if c.TerminalTotalDifficulty == nil { if c.TerminalTotalDifficulty == nil {
banner += "Consensus: Ethash (proof-of-work)\n" banner += "Consensus: Ethash (proof-of-work)\n"

View file

@ -173,6 +173,8 @@ const (
BlobTxTargetBlobGasPerBlock = 3 * BlobTxBlobGasPerBlob // Target consumable blob gas for data blobs per block (for 1559-like pricing) BlobTxTargetBlobGasPerBlock = 3 * BlobTxBlobGasPerBlob // Target consumable blob gas for data blobs per block (for 1559-like pricing)
MaxBlobGasPerBlock = 6 * BlobTxBlobGasPerBlob // Maximum consumable blob gas for data blobs per block MaxBlobGasPerBlock = 6 * BlobTxBlobGasPerBlob // Maximum consumable blob gas for data blobs per block
BlobTxHashVersion = 0x01 // CHANGE(taiko): Version byte of the commitment hash
) )
// Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations // Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations

68
params/taiko_config.go Normal file
View file

@ -0,0 +1,68 @@
package params
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
)
func u64(val uint64) *uint64 { return &val }
// Network IDs
var (
TaikoMainnetNetworkID = big.NewInt(167000)
TaikoInternalL2ANetworkID = big.NewInt(167001)
TaikoInternalL2BNetworkID = big.NewInt(167002)
SnaefellsjokullNetworkID = big.NewInt(167003)
AskjaNetworkID = big.NewInt(167004)
GrimsvotnNetworkID = big.NewInt(167005)
EldfellNetworkID = big.NewInt(167006)
JolnirNetworkID = big.NewInt(167007)
KatlaNetworkID = big.NewInt(167008)
HeklaNetworkID = big.NewInt(167009)
)
var networkIDToChainConfig = map[*big.Int]*ChainConfig{
TaikoMainnetNetworkID: TaikoChainConfig,
TaikoInternalL2ANetworkID: TaikoChainConfig,
TaikoInternalL2BNetworkID: TaikoChainConfig,
SnaefellsjokullNetworkID: TaikoChainConfig,
AskjaNetworkID: TaikoChainConfig,
GrimsvotnNetworkID: TaikoChainConfig,
EldfellNetworkID: TaikoChainConfig,
JolnirNetworkID: TaikoChainConfig,
KatlaNetworkID: TaikoChainConfig,
HeklaNetworkID: TaikoChainConfig,
MainnetChainConfig.ChainID: MainnetChainConfig,
SepoliaChainConfig.ChainID: SepoliaChainConfig,
GoerliChainConfig.ChainID: GoerliChainConfig,
TestChainConfig.ChainID: TestChainConfig,
NonActivatedConfig.ChainID: NonActivatedConfig,
}
func NetworkIDToChainConfigOrDefault(networkID *big.Int) *ChainConfig {
if config, ok := networkIDToChainConfig[networkID]; ok {
return config
}
return AllEthashProtocolChanges
}
var TaikoChainConfig = &ChainConfig{
ChainID: TaikoInternalL2ANetworkID, // Use Internal Devnet network ID by default.
HomesteadBlock: common.Big0,
EIP150Block: common.Big0,
EIP155Block: common.Big0,
EIP158Block: common.Big0,
ByzantiumBlock: common.Big0,
ConstantinopleBlock: common.Big0,
PetersburgBlock: common.Big0,
IstanbulBlock: common.Big0,
BerlinBlock: common.Big0,
LondonBlock: common.Big0,
ShanghaiTime: u64(0),
MergeNetsplitBlock: nil,
TerminalTotalDifficulty: common.Big0,
TerminalTotalDifficultyPassed: true,
Taiko: true,
}

View file

@ -0,0 +1,93 @@
package params
import (
"math/big"
"testing"
)
func TestNetworkIDToChainConfigOrDefault(t *testing.T) {
tests := []struct {
name string
networkID *big.Int
wantChainConfig *ChainConfig
}{
{
"taikoMainnetNetworkID",
TaikoMainnetNetworkID,
TaikoChainConfig,
},
{
"taikoInternalL2ANetworkId",
TaikoInternalL2ANetworkID,
TaikoChainConfig,
},
{
"taikoInternalL2BNetworkId",
TaikoInternalL2BNetworkID,
TaikoChainConfig,
},
{
"snaefoll",
SnaefellsjokullNetworkID,
TaikoChainConfig,
},
{
"askja",
AskjaNetworkID,
TaikoChainConfig,
},
{
"grimsvotn",
GrimsvotnNetworkID,
TaikoChainConfig,
},
{
"eldfellNetworkID",
EldfellNetworkID,
TaikoChainConfig,
},
{
"jolnirNetworkID",
JolnirNetworkID,
TaikoChainConfig,
},
{
"katlaNetworkID",
KatlaNetworkID,
TaikoChainConfig,
},
{
"heklaNetworkID",
HeklaNetworkID,
TaikoChainConfig,
},
{
"mainnet",
MainnetChainConfig.ChainID,
MainnetChainConfig,
},
{
"sepolia",
SepoliaChainConfig.ChainID,
SepoliaChainConfig,
},
{
"goerli",
GoerliChainConfig.ChainID,
GoerliChainConfig,
},
{
"doesntExist",
big.NewInt(89390218390),
AllEthashProtocolChanges,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if config := NetworkIDToChainConfigOrDefault(tt.networkID); config != tt.wantChainConfig {
t.Fatalf("expected %v, got %v", config, tt.wantChainConfig)
}
})
}
}

View file

@ -28,6 +28,9 @@ import (
const MetadataApi = "rpc" const MetadataApi = "rpc"
const EngineApi = "engine" const EngineApi = "engine"
// CHANGE(taiko): add `taikoAuth` namespace for authenticated APIs
const TaikoAuth = "taikoAuth"
// CodecOption specifies which type of messages a codec supports. // CodecOption specifies which type of messages a codec supports.
// //
// Deprecated: this option is no longer honored by Server. // Deprecated: this option is no longer honored by Server.