mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
feat(all): changes based on Taiko protocol
This commit is contained in:
parent
3f907d6a6f
commit
733d07cdaa
49 changed files with 3684 additions and 67 deletions
33
.github/workflows/ci.yml
vendored
Normal file
33
.github/workflows/ci.yml
vendored
Normal 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.20'
|
||||
|
||||
- name: Lint
|
||||
run: make lint
|
||||
|
||||
- name: Test
|
||||
env:
|
||||
TAIKO_TEST: true
|
||||
run: make test
|
||||
47
.github/workflows/docker.yml
vendored
Normal file
47
.github/workflows/docker.yml
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
name: "Push docker image to GCR"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [taiko]
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
push-docker-image:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Login to GCR
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: gcr.io
|
||||
username: _json_key
|
||||
password: ${{ secrets.GCR_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: |
|
||||
gcr.io/evmchain/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
14
.github/workflows/lint-pr.yml
vendored
Normal 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
36
.github/workflows/openai-review.yml
vendored
Normal 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 }}
|
||||
14
README.md
14
README.md
|
|
@ -1,3 +1,13 @@
|
|||
# taiko-geth
|
||||
|
||||
[](https://github.com/taikoxyz/taiko-geth/actions/workflows/ci.yml)
|
||||
|
||||
The codebase is based on [go-ethereum v1.13.4](https://github.com/ethereum/go-ethereum/releases/tag/v1.13.4).
|
||||
|
||||
## 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
|
||||
|
||||
Official Golang execution layer implementation of the Ethereum protocol.
|
||||
|
|
@ -83,7 +93,7 @@ This command will:
|
|||
causing it to download more data in exchange for avoiding processing the entire history
|
||||
of the Ethereum network, which is very CPU intensive.
|
||||
* Start the built-in interactive [JavaScript console](https://geth.ethereum.org/docs/interacting-with-geth/javascript-console),
|
||||
(via the trailing `console` subcommand) through which you can interact using [`web3` methods](https://github.com/ChainSafe/web3.js/blob/0.20.7/DOCUMENTATION.md)
|
||||
(via the trailing `console` subcommand) through which you can interact using [`web3` methods](https://github.com/ChainSafe/web3.js/blob/0.20.7/DOCUMENTATION.md)
|
||||
(note: the `web3` version bundled within `geth` is very old, and not up to date with official docs),
|
||||
as well as `geth`'s own [management APIs](https://geth.ethereum.org/docs/interacting-with-geth/rpc).
|
||||
This tool is optional and if you leave it out you can always attach it to an already running
|
||||
|
|
@ -344,7 +354,7 @@ testing procedures.
|
|||
### Contributing to geth.ethereum.org
|
||||
|
||||
For contributions to the [go-ethereum website](https://geth.ethereum.org), please checkout and raise pull requests against the `website` branch.
|
||||
For more detailed instructions please see the `website` branch [README](https://github.com/ethereum/go-ethereum/tree/website#readme) or the
|
||||
For more detailed instructions please see the `website` branch [README](https://github.com/ethereum/go-ethereum/tree/website#readme) or the
|
||||
[contributing](https://geth.ethereum.org/docs/developers/geth-developer/contributing) page of the website.
|
||||
|
||||
## License
|
||||
|
|
|
|||
82
beacon/engine/gen_blockmetadata.go
Normal file
82
beacon/engine/gen_blockmetadata.go
Normal 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
|
||||
}
|
||||
|
|
@ -5,9 +5,11 @@ package engine
|
|||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
|
|
@ -21,6 +23,9 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) {
|
|||
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
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
|
||||
enc.Timestamp = hexutil.Uint64(p.Timestamp)
|
||||
|
|
@ -28,6 +33,9 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) {
|
|||
enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient
|
||||
enc.Withdrawals = p.Withdrawals
|
||||
enc.BeaconRoot = p.BeaconRoot
|
||||
enc.BaseFeePerGas = p.BaseFeePerGas
|
||||
enc.BlockMetadata = p.BlockMetadata
|
||||
enc.L1Origin = p.L1Origin
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
|
|
@ -39,6 +47,9 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
|
|||
SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
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
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
|
|
@ -62,5 +73,17 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
|
|||
if dec.BeaconRoot != nil {
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,23 +17,26 @@ var _ = (*executableDataMarshaling)(nil)
|
|||
// MarshalJSON marshals as JSON.
|
||||
func (e ExecutableData) MarshalJSON() ([]byte, error) {
|
||||
type ExecutableData struct {
|
||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
|
||||
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"`
|
||||
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
||||
Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
|
||||
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
|
||||
FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"`
|
||||
StateRoot common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"`
|
||||
Random common.Hash `json:"prevRandao" gencodec:"required"`
|
||||
Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
|
||||
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions []hexutil.Bytes `json:"transactions"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||
TxHash common.Hash `json:"txHash"`
|
||||
WithdrawalsHash common.Hash `json:"withdrawalsHash"`
|
||||
TaikoBlock bool
|
||||
}
|
||||
var enc ExecutableData
|
||||
enc.ParentHash = e.ParentHash
|
||||
|
|
@ -58,29 +61,35 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
|
|||
enc.Withdrawals = e.Withdrawals
|
||||
enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed)
|
||||
enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas)
|
||||
enc.TxHash = e.TxHash
|
||||
enc.WithdrawalsHash = e.WithdrawalsHash
|
||||
enc.TaikoBlock = e.TaikoBlock
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (e *ExecutableData) UnmarshalJSON(input []byte) error {
|
||||
type ExecutableData struct {
|
||||
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
|
||||
FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"`
|
||||
StateRoot *common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"`
|
||||
Random *common.Hash `json:"prevRandao" gencodec:"required"`
|
||||
Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
|
||||
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||
ParentHash *common.Hash `json:"parentHash" gencodec:"required"`
|
||||
FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"`
|
||||
StateRoot *common.Hash `json:"stateRoot" gencodec:"required"`
|
||||
ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"`
|
||||
Random *common.Hash `json:"prevRandao" gencodec:"required"`
|
||||
Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"`
|
||||
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash *common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions []hexutil.Bytes `json:"transactions"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
|
||||
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
|
||||
TxHash *common.Hash `json:"txHash"`
|
||||
WithdrawalsHash *common.Hash `json:"withdrawalsHash"`
|
||||
TaikoBlock *bool
|
||||
}
|
||||
var dec ExecutableData
|
||||
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")
|
||||
}
|
||||
e.BlockHash = *dec.BlockHash
|
||||
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[k] = v
|
||||
if dec.Transactions != nil {
|
||||
e.Transactions = make([][]byte, len(dec.Transactions))
|
||||
for k, v := range dec.Transactions {
|
||||
e.Transactions[k] = v
|
||||
}
|
||||
}
|
||||
if dec.Withdrawals != nil {
|
||||
e.Withdrawals = dec.Withdrawals
|
||||
|
|
@ -154,5 +162,14 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
|
|||
if dec.ExcessBlobGas != nil {
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"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/trie"
|
||||
)
|
||||
|
|
@ -36,6 +37,11 @@ type PayloadAttributes struct {
|
|||
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
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.
|
||||
|
|
@ -43,6 +49,29 @@ type payloadAttributesMarshaling struct {
|
|||
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
|
||||
|
||||
// ExecutableData is the data necessary to execute an EL payload.
|
||||
|
|
@ -60,10 +89,14 @@ type ExecutableData struct {
|
|||
ExtraData []byte `json:"extraData" gencodec:"required"`
|
||||
BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"`
|
||||
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
|
||||
Transactions [][]byte `json:"transactions" gencodec:"required"`
|
||||
Transactions [][]byte `json:"transactions"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals"`
|
||||
BlobGasUsed *uint64 `json:"blobGasUsed"`
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -180,6 +180,9 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
|
|||
}
|
||||
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
|
||||
if eth != nil { // The 'eth' backend may be nil in light mode
|
||||
var protos []string
|
||||
|
|
|
|||
|
|
@ -243,6 +243,9 @@ func init() {
|
|||
debug.Flags,
|
||||
metricsFlags,
|
||||
)
|
||||
// CHANGE(taiko): append Taiko flags into the original GETH flags
|
||||
app.Flags = append(app.Flags, &utils.TaikoFlag)
|
||||
|
||||
flags.AutoEnvVars(app.Flags, "GETH")
|
||||
|
||||
app.Before = func(ctx *cli.Context) error {
|
||||
|
|
|
|||
|
|
@ -1792,6 +1792,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
|||
}
|
||||
// Override any default configs for hard coded networks.
|
||||
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):
|
||||
if !ctx.IsSet(NetworkIdFlag.Name) {
|
||||
cfg.NetworkId = 1
|
||||
|
|
|
|||
35
cmd/utils/taiko_flags.go
Normal file
35
cmd/utils/taiko_flags.go
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
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,
|
||||
},
|
||||
})
|
||||
}
|
||||
347
consensus/taiko/consensus.go
Normal file
347
consensus/taiko/consensus.go
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
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"
|
||||
)
|
||||
|
||||
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")
|
||||
AnchorSelector = crypto.Keccak256([]byte("anchor(bytes32,bytes32,uint64,uint32)"))[:4]
|
||||
AnchorGasLimit = uint64(1_000_000)
|
||||
TaikoL2Address = common.HexToAddress("0x1000777700000000000000000000000000000001")
|
||||
)
|
||||
|
||||
// Taiko is a consensus engine used by L2 rollup.
|
||||
type Taiko struct {
|
||||
chainConfig *params.ChainConfig
|
||||
}
|
||||
|
||||
var _ = new(Taiko)
|
||||
|
||||
func New(chainConfig *params.ChainConfig) *Taiko {
|
||||
crypto.Keccak256()
|
||||
return &Taiko{chainConfig}
|
||||
}
|
||||
|
||||
// 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, 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() != 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
|
||||
}
|
||||
227
consensus/taiko/consensus_test.go
Normal file
227
consensus/taiko/consensus_test.go
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
package taiko_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"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/trie"
|
||||
"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)
|
||||
|
||||
genesis = &core.Genesis{
|
||||
Config: config,
|
||||
Alloc: core.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: &taiko.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 := ðconfig.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, trie.NewDatabase(db, trie.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")
|
||||
}
|
||||
60
core/rawdb/gen_taiko_l1_origin.go
Normal file
60
core/rawdb/gen_taiko_l1_origin.go
Normal 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
|
||||
}
|
||||
92
core/rawdb/taiko_l1_origin.go
Normal file
92
core/rawdb/taiko_l1_origin.go
Normal 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
|
||||
}
|
||||
59
core/rawdb/taiko_l1_origin_test.go
Normal file
59
core/rawdb/taiko_l1_origin_test.go
Normal 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)
|
||||
}
|
||||
|
|
@ -81,6 +81,11 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
|
|||
}
|
||||
// Iterate over and process the individual 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)
|
||||
if err != nil {
|
||||
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ import (
|
|||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
var (
|
||||
treasury = common.HexToAddress("0xdf09A0afD09a63fb04ab3573922437e1e637dE8b")
|
||||
)
|
||||
|
||||
// ExecutionResult includes all output after executing given evm
|
||||
// message no matter the execution itself is successful or not.
|
||||
type ExecutionResult struct {
|
||||
|
|
@ -143,6 +147,9 @@ type Message struct {
|
|||
// 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.
|
||||
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.
|
||||
|
|
@ -160,6 +167,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
|
|||
SkipAccountChecks: false,
|
||||
BlobHashes: tx.BlobHashes(),
|
||||
BlobGasFeeCap: tx.BlobGasFeeCap(),
|
||||
IsAnchor: tx.IsAnchor(),
|
||||
}
|
||||
// If baseFee provided, set gasPrice to effectiveGasPrice.
|
||||
if baseFee != nil {
|
||||
|
|
@ -251,6 +259,11 @@ func (st *StateTransition) buyGas() error {
|
|||
mgval.Add(mgval, blobFee)
|
||||
}
|
||||
}
|
||||
// CHANGE(taiko): skip balance check for TaikoL2.anchor transaction.
|
||||
if st.msg.IsAnchor {
|
||||
balanceCheck = common.Big0
|
||||
mgval = common.Big0
|
||||
}
|
||||
if have, want := st.state.GetBalance(st.msg.From), balanceCheck; have.Cmp(want) < 0 {
|
||||
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
|
||||
}
|
||||
|
|
@ -291,7 +304,7 @@ func (st *StateTransition) preCheck() error {
|
|||
// Make sure that transaction gasFeeCap is greater than the baseFee (post london)
|
||||
if st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) {
|
||||
// Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call)
|
||||
if !st.evm.Config.NoBaseFee || msg.GasFeeCap.BitLen() > 0 || msg.GasTipCap.BitLen() > 0 {
|
||||
if (!st.evm.Config.NoBaseFee || msg.GasFeeCap.BitLen() > 0 || msg.GasTipCap.BitLen() > 0) && !st.msg.IsAnchor {
|
||||
if l := msg.GasFeeCap.BitLen(); l > 256 {
|
||||
return fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh,
|
||||
msg.From.Hex(), l)
|
||||
|
|
@ -435,6 +448,13 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
|||
fee := new(big.Int).SetUint64(st.gasUsed())
|
||||
fee.Mul(fee, effectiveTip)
|
||||
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(
|
||||
treasury,
|
||||
new(big.Int).Mul(st.evm.Context.BaseFee, new(big.Int).SetUint64(st.gasUsed())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return &ExecutionResult{
|
||||
|
|
|
|||
58
core/taiko_genesis.go
Normal file
58
core/taiko_genesis.go
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
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.TaikoInternalNetworkID.Uint64():
|
||||
chainConfig.ChainID = params.TaikoInternalNetworkID
|
||||
allocJSON = taikoGenesis.InternalGenesisAllocJSON
|
||||
case params.TaikoInternalL3NetworkID.Uint64():
|
||||
chainConfig.ChainID = params.TaikoInternalL3NetworkID
|
||||
allocJSON = taikoGenesis.InternalL3GenesisAllocJSON
|
||||
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
|
||||
default:
|
||||
chainConfig.ChainID = params.TaikoInternalNetworkID
|
||||
allocJSON = taikoGenesis.InternalGenesisAllocJSON
|
||||
}
|
||||
|
||||
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(8000000),
|
||||
Difficulty: common.Big0,
|
||||
Alloc: alloc,
|
||||
GasUsed: 0,
|
||||
BaseFee: new(big.Int).SetUint64(10000000),
|
||||
}
|
||||
}
|
||||
117
core/taiko_genesis/askja.json
Normal file
117
core/taiko_genesis/askja.json
Normal file
File diff suppressed because one or more lines are too long
164
core/taiko_genesis/eldfell.json
Normal file
164
core/taiko_genesis/eldfell.json
Normal file
File diff suppressed because one or more lines are too long
26
core/taiko_genesis/genesis_alloc.go
Normal file
26
core/taiko_genesis/genesis_alloc.go
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package taiko_genesis
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed internal.json
|
||||
var InternalGenesisAllocJSON []byte
|
||||
|
||||
//go:embed internal_l3.json
|
||||
var InternalL3GenesisAllocJSON []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
|
||||
164
core/taiko_genesis/grimsvotn.json
Normal file
164
core/taiko_genesis/grimsvotn.json
Normal file
File diff suppressed because one or more lines are too long
250
core/taiko_genesis/internal.json
Normal file
250
core/taiko_genesis/internal.json
Normal file
File diff suppressed because one or more lines are too long
250
core/taiko_genesis/internal_l3.json
Normal file
250
core/taiko_genesis/internal_l3.json
Normal file
File diff suppressed because one or more lines are too long
210
core/taiko_genesis/jolnir.json
Normal file
210
core/taiko_genesis/jolnir.json
Normal file
File diff suppressed because one or more lines are too long
98
core/taiko_genesis/snaefellsjokull.json
Normal file
98
core/taiko_genesis/snaefellsjokull.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -18,8 +18,10 @@ package txpool
|
|||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
|
|
@ -90,6 +92,10 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
if tx.GasFeeCapIntCmp(tx.GasTipCap()) < 0 {
|
||||
return core.ErrTipAboveFeeCap
|
||||
}
|
||||
// CHANGE(taiko): ensure gasFeeCap fee cap is not zero
|
||||
if os.Getenv("TAIKO_TEST") == "" && tx.GasFeeCap().Cmp(common.Big0) == 0 {
|
||||
return errors.New("max fee per gas is zero")
|
||||
}
|
||||
// Make sure the transaction is signed properly
|
||||
if _, err := types.Sender(signer, tx); err != nil {
|
||||
return ErrInvalidSender
|
||||
|
|
|
|||
42
core/types/taiko_transaction.go
Normal file
42
core/types/taiko_transaction.go
Normal 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
|
||||
}
|
||||
|
|
@ -83,6 +83,10 @@ type TxData interface {
|
|||
nonce() uint64
|
||||
to() *common.Address
|
||||
|
||||
// CHANGE(taiko): anchor transaction related.
|
||||
isAnchor() bool
|
||||
markAsAnchor() error
|
||||
|
||||
rawSignatureValues() (v, r, s *big.Int)
|
||||
setSignatureValues(chainID, v, r, s *big.Int)
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,9 @@ type DynamicFeeTx struct {
|
|||
V *big.Int `json:"v" gencodec:"required"`
|
||||
R *big.Int `json:"r" 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.
|
||||
|
|
@ -58,6 +61,7 @@ func (tx *DynamicFeeTx) copy() TxData {
|
|||
V: new(big.Int),
|
||||
R: new(big.Int),
|
||||
S: new(big.Int),
|
||||
isAnhcor: tx.isAnhcor,
|
||||
}
|
||||
copy(cpy.AccessList, tx.AccessList)
|
||||
if tx.Value != nil {
|
||||
|
|
|
|||
|
|
@ -311,6 +311,10 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
|
|||
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 {
|
||||
// Block is not canonical, set head.
|
||||
if latestValid, err := api.eth.BlockChain().SetCanonical(block); err != nil {
|
||||
|
|
@ -320,6 +324,10 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
|
|||
// 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
|
||||
// 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 {
|
||||
// If the head block is already in our canonical chain, the beacon client is
|
||||
// probably resyncing. Ignore the update.
|
||||
|
|
@ -364,6 +372,55 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
|
|||
// sealed by the beacon client. The payload will be requested later, and we
|
||||
// will replace it arbitrarily many times in between.
|
||||
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(),
|
||||
}
|
||||
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{
|
||||
Parent: update.HeadBlockHash,
|
||||
Timestamp: payloadAttributes.Timestamp,
|
||||
|
|
@ -458,7 +515,8 @@ func (api *ConsensusAPI) NewPayloadV1(params engine.ExecutableData) (engine.Payl
|
|||
// NewPayloadV2 creates an Eth1 block, inserts it in the chain, and returns the status of the chain.
|
||||
func (api *ConsensusAPI) NewPayloadV2(params engine.ExecutableData) (engine.PayloadStatusV1, error) {
|
||||
if api.eth.BlockChain().Config().IsShanghai(new(big.Int).SetUint64(params.Number), params.Timestamp) {
|
||||
if params.Withdrawals == nil {
|
||||
if params.Withdrawals == nil &&
|
||||
(api.eth.BlockChain().Config().Taiko && params.WithdrawalsHash == (common.Hash{})) { // CHANGE(taiko): allow only passing `WithdrawalsHash`
|
||||
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
|
||||
}
|
||||
} else if params.Withdrawals != nil {
|
||||
|
|
@ -510,10 +568,37 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe
|
|||
defer api.newPayloadLock.Unlock()
|
||||
|
||||
log.Trace("Engine API request received", "method", "NewPayload", "number", params.Number, "hash", params.BlockHash)
|
||||
block, err := engine.ExecutableDataToBlock(params, versionedHashes, beaconRoot)
|
||||
if err != nil {
|
||||
log.Warn("Invalid NewPayload params", "params", params, "error", err)
|
||||
return api.invalid(err, nil), nil
|
||||
// CHANGE(taiko): allow passing the executable data with txHash instead of all transactions.
|
||||
var (
|
||||
block *types.Block
|
||||
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: ¶ms.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
|
||||
api.lastNewPayloadLock.Lock()
|
||||
|
|
@ -556,9 +641,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)
|
||||
return engine.INVALID_TERMINAL_BLOCK, nil
|
||||
}
|
||||
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
|
||||
// CHANGE(taiko): a block that has the same timestamp as its parents is
|
||||
// allowed in Taiko protocol.
|
||||
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
|
||||
// tries to make it import a block. That should be denied as pushing something
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||
"github.com/ethereum/go-ethereum/consensus/clique"
|
||||
"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/txpool/blobpool"
|
||||
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
|
||||
|
|
@ -174,6 +175,10 @@ type Config struct {
|
|||
// Clique is allowed for now to live standalone, but ethash is forbidden and can
|
||||
// only exist on already merged networks.
|
||||
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 config.Clique != nil {
|
||||
return beacon.New(clique.New(config.Clique, db)), nil
|
||||
|
|
|
|||
91
eth/taiko_api_backend.go
Normal file
91
eth/taiko_api_backend.go
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
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/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// TxPoolContent retrieves the transaction pool content with the given upper limits.
|
||||
func (s *TaikoAPIBackend) TxPoolContent(
|
||||
beneficiary common.Address,
|
||||
baseFee *big.Int,
|
||||
maxTransactionsPerBlock uint64,
|
||||
blockMaxGasLimit uint64,
|
||||
maxBytesPerTxList uint64,
|
||||
locals []string,
|
||||
maxTransactionsLists uint64,
|
||||
) ([]types.Transactions, error) {
|
||||
log.Info(
|
||||
"Fetching L2 pending transactions finished",
|
||||
"maxTransactionsPerBlock", maxTransactionsPerBlock,
|
||||
"blockMaxGasLimit", blockMaxGasLimit,
|
||||
"maxBytesPerTxList", maxBytesPerTxList,
|
||||
"maxTransactions", maxTransactionsLists,
|
||||
"locals", locals,
|
||||
)
|
||||
|
||||
return s.eth.Miner().BuildTransactionsLists(
|
||||
beneficiary,
|
||||
baseFee,
|
||||
maxTransactionsPerBlock,
|
||||
blockMaxGasLimit,
|
||||
maxBytesPerTxList,
|
||||
locals,
|
||||
maxTransactionsLists,
|
||||
)
|
||||
}
|
||||
|
|
@ -269,6 +269,13 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
|
|||
)
|
||||
// Trace all the transactions contained within
|
||||
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())
|
||||
txctx := &Context{
|
||||
BlockHash: task.block.Hash(),
|
||||
|
|
@ -526,6 +533,11 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
|
|||
deleteEmptyObjects = chainConfig.IsEIP158(block.Number())
|
||||
)
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -603,6 +615,11 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
|
|||
results = make([]*txTraceResult, len(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
|
||||
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
||||
txctx := &Context{
|
||||
|
|
@ -756,6 +773,11 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
|
|||
chainConfig, canon = overrideConfig(chainConfig, config.Overrides)
|
||||
}
|
||||
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
|
||||
var (
|
||||
msg, _ = core.TransactionToMessage(tx, signer, block.BaseFee())
|
||||
|
|
|
|||
31
ethclient/taiko_api.go
Normal file
31
ethclient/taiko_api.go
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
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
|
||||
}
|
||||
124
ethclient/taiko_api_test.go
Normal file
124
ethclient/taiko_api_test.go
Normal 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 := ðconfig.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
|
||||
}
|
||||
|
|
@ -59,6 +59,11 @@ func (leth *LightEthereum) stateAtTransaction(ctx context.Context, block *types.
|
|||
// Recompute transactions up to the target index.
|
||||
signer := types.MakeSigner(leth.blockchain.Config(), block.Number(), block.Time())
|
||||
for idx, tx := range block.Transactions() {
|
||||
if idx == 0 && leth.chainConfig.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
|
||||
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
||||
txContext := core.NewEVMTxContext(msg)
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ type Payload struct {
|
|||
stop chan struct{}
|
||||
lock sync.Mutex
|
||||
cond *sync.Cond
|
||||
done chan struct{} // CHANGE(taiko): done channel to communicate we shouldnt write to `stop` channel.
|
||||
}
|
||||
|
||||
// newPayload initializes the payload object.
|
||||
|
|
@ -82,6 +83,7 @@ func newPayload(empty *types.Block, id engine.PayloadID) *Payload {
|
|||
id: id,
|
||||
empty: empty,
|
||||
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)
|
||||
payload.cond = sync.NewCond(&payload.lock)
|
||||
|
|
@ -169,6 +171,7 @@ func (payload *Payload) ResolveFull() *engine.ExecutionPayloadEnvelope {
|
|||
select {
|
||||
case <-payload.stop:
|
||||
default:
|
||||
payload.done <- struct{}{} // CHANGE(taiko): signal to taiko payload builder to not write to `payload.stop` channel
|
||||
close(payload.stop)
|
||||
}
|
||||
return engine.BlockToExecutableData(payload.full, payload.fullFees, payload.sidecars)
|
||||
|
|
@ -224,6 +227,10 @@ func (w *worker) buildPayload(args *BuildPayloadArgs) (*Payload, error) {
|
|||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
// CHANGE(taiko): do not update payload.
|
||||
if w.chainConfig.Taiko {
|
||||
continue
|
||||
}
|
||||
start := time.Now()
|
||||
r := w.getSealingBlock(fullParams)
|
||||
if r.err == nil {
|
||||
|
|
|
|||
41
miner/taiko_miner.go
Normal file
41
miner/taiko_miner.go
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
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"
|
||||
)
|
||||
|
||||
// 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,
|
||||
maxTransactionsPerBlock uint64,
|
||||
blockMaxGasLimit uint64,
|
||||
maxBytesPerTxList uint64,
|
||||
locals []string,
|
||||
maxTransactionsLists uint64,
|
||||
) ([]types.Transactions, error) {
|
||||
return miner.worker.BuildTransactionsLists(
|
||||
beneficiary,
|
||||
baseFee,
|
||||
maxTransactionsPerBlock,
|
||||
blockMaxGasLimit,
|
||||
maxBytesPerTxList,
|
||||
locals,
|
||||
maxTransactionsLists,
|
||||
)
|
||||
}
|
||||
44
miner/taiko_payload_building.go
Normal file
44
miner/taiko_payload_building.go
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
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 payload.stop <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
140
miner/taiko_payload_building_test.go
Normal file
140
miner/taiko_payload_building_test.go
Normal 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)
|
||||
}
|
||||
306
miner/taiko_worker.go
Normal file
306
miner/taiko_worker.go
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
package miner
|
||||
|
||||
import (
|
||||
"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"
|
||||
)
|
||||
|
||||
// BuildTransactionsLists builds multiple transactions lists which satisfy all the given limits.
|
||||
func (w *worker) BuildTransactionsLists(
|
||||
beneficiary common.Address,
|
||||
baseFee *big.Int,
|
||||
maxTransactionsPerBlock uint64,
|
||||
blockMaxGasLimit uint64,
|
||||
maxBytesPerTxList uint64,
|
||||
localAccounts []string,
|
||||
maxTransactionsLists uint64,
|
||||
) ([]types.Transactions, error) {
|
||||
var (
|
||||
txsLists []types.Transactions
|
||||
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(false)) == 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)
|
||||
)
|
||||
|
||||
commitTxs := func() (types.Transactions, error) {
|
||||
env.tcount = 0
|
||||
env.txs = []*types.Transaction{}
|
||||
env.gasPool = new(core.GasPool).AddGas(blockMaxGasLimit)
|
||||
env.header.GasLimit = blockMaxGasLimit
|
||||
|
||||
// Split the pending transactions into locals and remotes, then
|
||||
// fill the block with all available pending transactions.
|
||||
localTxs, remoteTxs := w.getPendingTxs(localAccounts)
|
||||
|
||||
w.commitL2Transactions(
|
||||
env,
|
||||
newTransactionsByPriceAndNonce(signer, localTxs, baseFee),
|
||||
newTransactionsByPriceAndNonce(signer, remoteTxs, baseFee),
|
||||
maxTransactionsPerBlock,
|
||||
maxBytesPerTxList,
|
||||
)
|
||||
|
||||
return env.txs, nil
|
||||
}
|
||||
|
||||
for i := 0; i < int(maxTransactionsLists); i++ {
|
||||
txs, err := commitTxs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(txs) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
txsLists = append(txsLists, txs)
|
||||
}
|
||||
|
||||
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) (
|
||||
map[common.Address][]*txpool.LazyTransaction,
|
||||
map[common.Address][]*txpool.LazyTransaction,
|
||||
) {
|
||||
pending := w.eth.TxPool().Pending(false)
|
||||
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,
|
||||
maxTransactionsPerBlock uint64,
|
||||
maxBytesPerTxList uint64,
|
||||
) {
|
||||
var (
|
||||
txs = txsLocal
|
||||
isLocal = true
|
||||
accTxListBytes int
|
||||
)
|
||||
|
||||
loop:
|
||||
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.Warn("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 := rlp.EncodeToBytes(tx)
|
||||
if err != nil {
|
||||
log.Trace("Failed to rlp encode the pending transaction %s: %w", tx.Hash(), err)
|
||||
txs.Pop()
|
||||
continue
|
||||
}
|
||||
if accTxListBytes+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.ErrGasLimitReached):
|
||||
// Pop the current out-of-gas transaction without shifting in the next from the account
|
||||
log.Trace("Gas limit exceeded for current block", "sender", from)
|
||||
txs.Pop()
|
||||
|
||||
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", "sender", from, "nonce", tx.Nonce())
|
||||
txs.Shift()
|
||||
|
||||
case errors.Is(err, core.ErrNonceTooHigh):
|
||||
// Reorg notification data race between the transaction pool and miner, skip account =
|
||||
log.Trace("Skipping account with hight nonce", "sender", from, "nonce", tx.Nonce())
|
||||
txs.Pop()
|
||||
|
||||
case errors.Is(err, nil):
|
||||
// Everything ok, shift in the next transaction from the same account
|
||||
env.tcount++
|
||||
txs.Shift()
|
||||
|
||||
if env.tcount >= int(maxTransactionsPerBlock) {
|
||||
break loop
|
||||
}
|
||||
accTxListBytes += len(b)
|
||||
|
||||
case errors.Is(err, types.ErrTxTypeNotSupported):
|
||||
// Pop the unsupported transaction without shifting in the next from the account
|
||||
log.Trace("Skipping unsupported transaction type", "sender", from, "type", tx.Type())
|
||||
txs.Pop()
|
||||
|
||||
default:
|
||||
// Strange error, discard the transaction and get the next in line (note, the
|
||||
// nonce-too-high clause will prevent us from executing in vain).
|
||||
log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
|
||||
txs.Shift()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -888,14 +888,15 @@ func (w *worker) commitTransactions(env *environment, txs *transactionsByPriceAn
|
|||
|
||||
// generateParams wraps various of settings for generating sealing task.
|
||||
type generateParams struct {
|
||||
timestamp uint64 // The timstamp for sealing task
|
||||
forceTime bool // Flag whether the given timestamp is immutable or not
|
||||
parentHash common.Hash // Parent block hash, empty means the latest chain head
|
||||
coinbase common.Address // The fee recipient address for including transaction
|
||||
random common.Hash // The randomness generated by beacon chain, empty before the merge
|
||||
withdrawals types.Withdrawals // List of withdrawals to include in block.
|
||||
beaconRoot *common.Hash // The beacon root (cancun field).
|
||||
noTxs bool // Flag whether an empty block without any transaction is expected
|
||||
timestamp uint64 // The timstamp for sealing task
|
||||
forceTime bool // Flag whether the given timestamp is immutable or not
|
||||
parentHash common.Hash // Parent block hash, empty means the latest chain head
|
||||
coinbase common.Address // The fee recipient address for including transaction
|
||||
random common.Hash // The randomness generated by beacon chain, empty before the merge
|
||||
withdrawals types.Withdrawals // List of withdrawals to include in block.
|
||||
beaconRoot *common.Hash // The beacon root (cancun field).
|
||||
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,
|
||||
|
|
@ -918,10 +919,17 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) {
|
|||
// to parent+1 if the mutation is allowed.
|
||||
timestamp := genParams.timestamp
|
||||
if parent.Time >= timestamp {
|
||||
if genParams.forceTime {
|
||||
return nil, fmt.Errorf("invalid timestamp, parent %d given %d", parent.Time, timestamp)
|
||||
// CHANGE(taiko): block.timestamp == parent.timestamp is allowed in Taiko protocol.
|
||||
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.
|
||||
header := &types.Header{
|
||||
|
|
@ -941,10 +949,14 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) {
|
|||
}
|
||||
// Set baseFee and GasLimit if we are on an EIP-1559 chain
|
||||
if w.chainConfig.IsLondon(header.Number) {
|
||||
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)
|
||||
if w.chainConfig.Taiko && genParams.baseFeePerGas != nil {
|
||||
header.BaseFee = genParams.baseFeePerGas
|
||||
} else {
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -281,6 +281,13 @@ var NetworkNames = map[string]string{
|
|||
GoerliChainConfig.ChainID.String(): "goerli",
|
||||
SepoliaChainConfig.ChainID.String(): "sepolia",
|
||||
HoleskyChainConfig.ChainID.String(): "holesky",
|
||||
// CHANGE(taiko): add Taiko network name.
|
||||
TaikoInternalNetworkID.String(): "Taiko Internal 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)",
|
||||
}
|
||||
|
||||
// ChainConfig is the core config which determines the blockchain settings.
|
||||
|
|
@ -332,6 +339,9 @@ type ChainConfig struct {
|
|||
Ethash *EthashConfig `json:"ethash,omitempty"`
|
||||
Clique *CliqueConfig `json:"clique,omitempty"`
|
||||
IsDevMode bool `json:"isDev,omitempty"`
|
||||
|
||||
// CHANGE(taiko): Taiko network flag.
|
||||
Taiko bool `json:"taiko"`
|
||||
}
|
||||
|
||||
// EthashConfig is the consensus engine configs for proof-of-work based sealing.
|
||||
|
|
@ -364,6 +374,9 @@ func (c *ChainConfig) Description() string {
|
|||
}
|
||||
banner += fmt.Sprintf("Chain ID: %v (%s)\n", c.ChainID, network)
|
||||
switch {
|
||||
// CHANGE(taiko): print Taiko consensus engine in banner.
|
||||
case c.Taiko:
|
||||
banner += "Consensus: Taiko\n"
|
||||
case c.Ethash != nil:
|
||||
if c.TerminalTotalDifficulty == nil {
|
||||
banner += "Consensus: Ethash (proof-of-work)\n"
|
||||
|
|
|
|||
62
params/taiko_config.go
Normal file
62
params/taiko_config.go
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
package params
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
func u64(val uint64) *uint64 { return &val }
|
||||
|
||||
// Network IDs
|
||||
var (
|
||||
TaikoInternalNetworkID = big.NewInt(167001)
|
||||
TaikoInternalL3NetworkID = big.NewInt(167002)
|
||||
SnaefellsjokullNetworkID = big.NewInt(167003)
|
||||
AskjaNetworkID = big.NewInt(167004)
|
||||
GrimsvotnNetworkID = big.NewInt(167005)
|
||||
EldfellNetworkID = big.NewInt(167006)
|
||||
JolnirNetworkID = big.NewInt(167007)
|
||||
)
|
||||
|
||||
var networkIDToChainConfig = map[*big.Int]*ChainConfig{
|
||||
TaikoInternalNetworkID: TaikoChainConfig,
|
||||
TaikoInternalL3NetworkID: TaikoChainConfig,
|
||||
SnaefellsjokullNetworkID: TaikoChainConfig,
|
||||
AskjaNetworkID: TaikoChainConfig,
|
||||
GrimsvotnNetworkID: TaikoChainConfig,
|
||||
EldfellNetworkID: TaikoChainConfig,
|
||||
JolnirNetworkID: 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: TaikoInternalNetworkID, // 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,
|
||||
}
|
||||
78
params/taiko_config_test.go
Normal file
78
params/taiko_config_test.go
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
package params
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNetworkIDToChainConfigOrDefault(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
networkID *big.Int
|
||||
wantChainConfig *ChainConfig
|
||||
}{
|
||||
{
|
||||
"taikoInternal",
|
||||
TaikoInternalNetworkID,
|
||||
TaikoChainConfig,
|
||||
},
|
||||
{
|
||||
"taikoInternalL3NetworkId",
|
||||
TaikoInternalL3NetworkID,
|
||||
TaikoChainConfig,
|
||||
},
|
||||
{
|
||||
"snaefoll",
|
||||
SnaefellsjokullNetworkID,
|
||||
TaikoChainConfig,
|
||||
},
|
||||
{
|
||||
"askja",
|
||||
AskjaNetworkID,
|
||||
TaikoChainConfig,
|
||||
},
|
||||
{
|
||||
"grimsvotn",
|
||||
GrimsvotnNetworkID,
|
||||
TaikoChainConfig,
|
||||
},
|
||||
{
|
||||
"eldfellNetworkID",
|
||||
EldfellNetworkID,
|
||||
TaikoChainConfig,
|
||||
},
|
||||
{
|
||||
"jolnirNetworkID",
|
||||
JolnirNetworkID,
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue