mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-07 20:43:48 +00:00
feat(all): changes based on Taiko protocol
This commit is contained in:
parent
c5ba367eb6
commit
7e1b8b65a3
59 changed files with 4758 additions and 73 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.21'
|
||||
|
||||
- name: Lint
|
||||
run: make lint
|
||||
|
||||
- name: Test
|
||||
env:
|
||||
TAIKO_TEST: true
|
||||
run: make test
|
||||
22
.github/workflows/close-inactive-issues.yml
vendored
Normal file
22
.github/workflows/close-inactive-issues.yml
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
name: Close inactive issues
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 1 * * *"
|
||||
|
||||
jobs:
|
||||
close-issues:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v5
|
||||
with:
|
||||
days-before-issue-stale: 30
|
||||
days-before-issue-close: 7
|
||||
stale-issue-label: "stale"
|
||||
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
|
||||
close-issue-message: "This issue was closed because it has been inactive for a week since being marked as stale."
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
47
.github/workflows/docker.yml
vendored
Normal file
47
.github/workflows/docker.yml
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
name: "Push docker image to GAR"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [taiko]
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
push-docker-image:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Login to GAR
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: us-docker.pkg.dev
|
||||
username: _json_key
|
||||
password: ${{ secrets.GAR_JSON_KEY }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
us-docker.pkg.dev/evmchain/images/taiko-geth
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=ref,event=tag
|
||||
type=sha
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
14
.github/workflows/lint-pr.yml
vendored
Normal file
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.15](https://github.com/ethereum/go-ethereum/releases/tag/v1.13.15).
|
||||
|
||||
## Tracing changes
|
||||
|
||||
All source code changes by [taiko](https://taiko.xyz) team are commented with `"CHANGE(taiko): ...."`, and the filenames of all additional files have the prefix `"taiko_"` (`taiko_*.go`).
|
||||
|
||||
## Go Ethereum
|
||||
|
||||
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"
|
||||
)
|
||||
|
|
@ -46,6 +47,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.
|
||||
|
|
@ -53,6 +59,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.
|
||||
|
|
@ -70,10 +99,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.
|
||||
|
|
|
|||
|
|
@ -179,6 +179,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
|
||||
|
|
|
|||
|
|
@ -248,6 +248,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 {
|
||||
|
|
|
|||
|
|
@ -1730,6 +1730,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
|
||||
|
|
|
|||
41
cmd/utils/taiko_flags.go
Normal file
41
cmd/utils/taiko_flags.go
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
TaikoFlag = cli.BoolFlag{
|
||||
Name: "taiko",
|
||||
Usage: "Taiko network",
|
||||
}
|
||||
)
|
||||
|
||||
// RegisterTaikoAPIs initializes and registers the Taiko RPC APIs.
|
||||
func RegisterTaikoAPIs(stack *node.Node, cfg *ethconfig.Config, backend *eth.Ethereum) {
|
||||
if os.Getenv("TAIKO_TEST") != "" {
|
||||
return
|
||||
}
|
||||
// Add methods under "taiko_" RPC namespace to the available APIs list
|
||||
stack.RegisterAPIs([]rpc.API{
|
||||
{
|
||||
Namespace: "taiko",
|
||||
Version: params.VersionWithMeta,
|
||||
Service: eth.NewTaikoAPIBackend(backend),
|
||||
Public: true,
|
||||
},
|
||||
{
|
||||
Namespace: "taikoAuth",
|
||||
Version: params.VersionWithMeta,
|
||||
Service: eth.NewTaikoAuthAPIBackend(backend),
|
||||
Authenticated: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
358
consensus/taiko/consensus.go
Normal file
358
consensus/taiko/consensus.go
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
package taiko
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrOlderBlockTime = errors.New("timestamp older than parent")
|
||||
ErrUnclesNotEmpty = errors.New("uncles not empty")
|
||||
ErrEmptyBasefee = errors.New("empty base fee")
|
||||
ErrEmptyWithdrawalsHash = errors.New("withdrawals hash missing")
|
||||
ErrAnchorTxNotFound = errors.New("anchor transaction not found")
|
||||
|
||||
GoldenTouchAccount = common.HexToAddress("0x0000777735367b36bC9B61C50022d9D0700dB4Ec")
|
||||
TaikoL2AddressSuffix = "10001"
|
||||
AnchorSelector = crypto.Keccak256([]byte("anchor(bytes32,bytes32,uint64,uint32)"))[:4]
|
||||
AnchorGasLimit = uint64(250_000)
|
||||
)
|
||||
|
||||
// Taiko is a consensus engine used by L2 rollup.
|
||||
type Taiko struct {
|
||||
chainConfig *params.ChainConfig
|
||||
taikoL2Address common.Address
|
||||
}
|
||||
|
||||
var _ = new(Taiko)
|
||||
|
||||
func New(chainConfig *params.ChainConfig) *Taiko {
|
||||
taikoL2AddressPrefix := strings.TrimPrefix(chainConfig.ChainID.String(), "0")
|
||||
|
||||
return &Taiko{
|
||||
chainConfig: chainConfig,
|
||||
taikoL2Address: common.HexToAddress(
|
||||
"0x" +
|
||||
taikoL2AddressPrefix +
|
||||
strings.Repeat("0", common.AddressLength*2-len(taikoL2AddressPrefix)-len(TaikoL2AddressSuffix)) +
|
||||
TaikoL2AddressSuffix,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// check all method stubs for interface `Engine` without affect performance.
|
||||
var _ consensus.Engine = (*Taiko)(nil)
|
||||
|
||||
// Author retrieves the Ethereum address of the account that minted the given
|
||||
// block, who proposes the block (not the prover).
|
||||
func (t *Taiko) Author(header *types.Header) (common.Address, error) {
|
||||
return header.Coinbase, nil
|
||||
}
|
||||
|
||||
// VerifyHeader checks whether a header conforms to the consensus rules of a
|
||||
// given engine. Verifying the seal may be done optionally here, or explicitly
|
||||
// via the VerifySeal method.
|
||||
func (t *Taiko) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header) error {
|
||||
// Short circuit if the header is known, or its parent not
|
||||
number := header.Number.Uint64()
|
||||
if chain.GetHeader(header.Hash(), number) != nil {
|
||||
return nil
|
||||
}
|
||||
parent := chain.GetHeader(header.ParentHash, number-1)
|
||||
if parent == nil {
|
||||
return consensus.ErrUnknownAncestor
|
||||
}
|
||||
// Sanity checks passed, do a proper verification
|
||||
return t.verifyHeader(chain, header, parent, time.Now().Unix())
|
||||
}
|
||||
|
||||
// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers
|
||||
// concurrently. The method returns a quit channel to abort the operations and
|
||||
// a results channel to retrieve the async verifications (the order is that of
|
||||
// the input slice).
|
||||
func (t *Taiko) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header) (chan<- struct{}, <-chan error) {
|
||||
// Spawn as many workers as allowed threads
|
||||
workers := runtime.GOMAXPROCS(0)
|
||||
if len(headers) < workers {
|
||||
workers = len(headers)
|
||||
}
|
||||
|
||||
// Create a task channel and spawn the verifiers
|
||||
var (
|
||||
inputs = make(chan int)
|
||||
done = make(chan int, workers)
|
||||
errors = make([]error, len(headers))
|
||||
abort = make(chan struct{})
|
||||
unixNow = time.Now().Unix()
|
||||
)
|
||||
for i := 0; i < workers; i++ {
|
||||
go func() {
|
||||
for index := range inputs {
|
||||
errors[index] = t.verifyHeaderWorker(chain, headers, index, unixNow)
|
||||
done <- index
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
errorsOut := make(chan error, len(headers))
|
||||
go func() {
|
||||
defer close(inputs)
|
||||
var (
|
||||
in, out = 0, 0
|
||||
checked = make([]bool, len(headers))
|
||||
inputs = inputs
|
||||
)
|
||||
for {
|
||||
select {
|
||||
case inputs <- in:
|
||||
if in++; in == len(headers) {
|
||||
// Reached end of headers. Stop sending to workers.
|
||||
inputs = nil
|
||||
}
|
||||
case index := <-done:
|
||||
for checked[index] = true; checked[out]; out++ {
|
||||
errorsOut <- errors[out]
|
||||
if out == len(headers)-1 {
|
||||
return
|
||||
}
|
||||
}
|
||||
case <-abort:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
return abort, errorsOut
|
||||
}
|
||||
|
||||
func (t *Taiko) verifyHeader(chain consensus.ChainHeaderReader, header, parent *types.Header, unixNow int64) error {
|
||||
if header.Time > uint64(unixNow) {
|
||||
return consensus.ErrFutureBlock
|
||||
}
|
||||
|
||||
// Ensure that the header's extra-data section is of a reasonable size (<= 32 bytes)
|
||||
if uint64(len(header.Extra)) > params.MaximumExtraDataSize {
|
||||
return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize)
|
||||
}
|
||||
|
||||
// Timestamp should later than or equal to parent (when many L2 blocks included in one L1 block)
|
||||
if header.Time < parent.Time {
|
||||
return ErrOlderBlockTime
|
||||
}
|
||||
|
||||
// Verify that the block number is parent's +1
|
||||
if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 {
|
||||
return consensus.ErrInvalidNumber
|
||||
}
|
||||
|
||||
// Difficulty should always be zero
|
||||
if header.Difficulty != nil && header.Difficulty.Cmp(common.Big0) != 0 {
|
||||
return fmt.Errorf("invalid difficulty: have %v, want %v", header.Difficulty, common.Big0)
|
||||
}
|
||||
|
||||
// Verify that the gas limit is <= 2^63-1
|
||||
if header.GasLimit > params.MaxGasLimit {
|
||||
return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit)
|
||||
}
|
||||
|
||||
// Verify that the gasUsed is <= gasLimit
|
||||
if header.GasUsed > header.GasLimit {
|
||||
return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit)
|
||||
}
|
||||
|
||||
// Uncles should be empty
|
||||
if header.UncleHash != types.CalcUncleHash(nil) {
|
||||
return ErrUnclesNotEmpty
|
||||
}
|
||||
|
||||
// BaseFee should not be empty
|
||||
if header.BaseFee == nil {
|
||||
return ErrEmptyBasefee
|
||||
}
|
||||
|
||||
// WithdrawalsHash should not be empty
|
||||
if header.WithdrawalsHash == nil {
|
||||
return ErrEmptyWithdrawalsHash
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Taiko) verifyHeaderWorker(chain consensus.ChainHeaderReader, headers []*types.Header, index int, unixNow int64) error {
|
||||
var parent *types.Header
|
||||
if index == 0 {
|
||||
parent = chain.GetHeader(headers[0].ParentHash, headers[0].Number.Uint64()-1)
|
||||
} else if headers[index-1].Hash() == headers[index].ParentHash {
|
||||
parent = headers[index-1]
|
||||
}
|
||||
if parent == nil {
|
||||
return consensus.ErrUnknownAncestor
|
||||
}
|
||||
|
||||
return t.verifyHeader(chain, headers[index], parent, unixNow)
|
||||
}
|
||||
|
||||
// VerifyUncles verifies that the given block's uncles conform to the consensus
|
||||
// rules of a given engine.
|
||||
//
|
||||
// always returning an error for any uncles as this consensus mechanism doesn't permit uncles.
|
||||
func (t *Taiko) VerifyUncles(chain consensus.ChainReader, block *types.Block) error {
|
||||
if len(block.Uncles()) > 0 {
|
||||
return ErrUnclesNotEmpty
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Prepare initializes the consensus fields of a block header according to the
|
||||
// rules of a particular engine. The changes are executed inline.
|
||||
func (t *Taiko) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
|
||||
parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1)
|
||||
if parent == nil {
|
||||
return consensus.ErrUnknownAncestor
|
||||
}
|
||||
header.Difficulty = common.Big0
|
||||
return nil
|
||||
}
|
||||
|
||||
// Finalize runs any post-transaction state modifications (e.g. block rewards)
|
||||
// but does not assemble the block.
|
||||
//
|
||||
// Note: The block header and state database might be updated to reflect any
|
||||
// consensus rules that happen at finalization (e.g. block rewards).
|
||||
func (t *Taiko) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) {
|
||||
// no block rewards in l2
|
||||
header.UncleHash = types.CalcUncleHash(nil)
|
||||
header.Difficulty = common.Big0
|
||||
// Withdrawals processing.
|
||||
for _, w := range withdrawals {
|
||||
state.AddBalance(w.Address, uint256.MustFromBig(new(big.Int).SetUint64(w.Amount)))
|
||||
}
|
||||
header.Root = state.IntermediateRoot(true)
|
||||
}
|
||||
|
||||
// FinalizeAndAssemble runs any post-transaction state modifications (e.g. block
|
||||
// rewards) and assembles the final block.
|
||||
//
|
||||
// Note: The block header and state database might be updated to reflect any
|
||||
// consensus rules that happen at finalization (e.g. block rewards).
|
||||
func (t *Taiko) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt, withdrawals []*types.Withdrawal) (*types.Block, error) {
|
||||
if withdrawals == nil {
|
||||
withdrawals = make([]*types.Withdrawal, 0)
|
||||
}
|
||||
|
||||
// Verify anchor transaction
|
||||
if len(txs) != 0 { // Transactions list might be empty when building empty payload.
|
||||
isAnchor, err := t.ValidateAnchorTx(txs[0], header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isAnchor {
|
||||
return nil, ErrAnchorTxNotFound
|
||||
}
|
||||
}
|
||||
|
||||
// Finalize block
|
||||
t.Finalize(chain, header, state, txs, uncles, withdrawals)
|
||||
return types.NewBlockWithWithdrawals(
|
||||
header, txs, nil /* ignore uncles */, receipts, withdrawals, trie.NewStackTrie(nil),
|
||||
), nil
|
||||
}
|
||||
|
||||
// Seal generates a new sealing request for the given input block and pushes
|
||||
// the result into the given channel.
|
||||
//
|
||||
// Note, the method returns immediately and will send the result async. More
|
||||
// than one result may also be returned depending on the consensus algorithm.
|
||||
func (t *Taiko) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
|
||||
header := block.Header()
|
||||
|
||||
// Sealing the genesis block is not supported
|
||||
number := header.Number.Uint64()
|
||||
if number == 0 {
|
||||
return consensus.ErrInvalidNumber
|
||||
}
|
||||
|
||||
select {
|
||||
case results <- block.WithSeal(header):
|
||||
case <-stop:
|
||||
return nil
|
||||
default:
|
||||
log.Warn("Sealing result is not read by miner", "sealHash", t.SealHash(header))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SealHash returns the hash of a block prior to it being sealed.
|
||||
func (t *Taiko) SealHash(header *types.Header) common.Hash {
|
||||
// Keccak(rlp(header))
|
||||
return header.Hash()
|
||||
}
|
||||
|
||||
// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty
|
||||
// that a new block should have.
|
||||
func (t *Taiko) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int {
|
||||
return common.Big0
|
||||
}
|
||||
|
||||
// ValidateAnchorTx checks if the given transaction is a valid TaikoL2.anchor transaction.
|
||||
func (t *Taiko) ValidateAnchorTx(tx *types.Transaction, header *types.Header) (bool, error) {
|
||||
if tx.Type() != types.DynamicFeeTxType {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if tx.To() == nil || *tx.To() != t.taikoL2Address {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if !bytes.HasPrefix(tx.Data(), AnchorSelector) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if tx.Value().Cmp(common.Big0) != 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if tx.Gas() != AnchorGasLimit {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if tx.GasFeeCap().Cmp(header.BaseFee) != 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
s := types.MakeSigner(t.chainConfig, header.Number, header.Time)
|
||||
|
||||
addr, err := s.Sender(tx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return strings.EqualFold(addr.String(), GoldenTouchAccount.String()), nil
|
||||
}
|
||||
|
||||
// APIs returns the RPC APIs this consensus engine provides.
|
||||
func (t *Taiko) APIs(chain consensus.ChainHeaderReader) []rpc.API {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close terminates any background threads maintained by the consensus engine.
|
||||
func (t *Taiko) Close() error {
|
||||
return nil
|
||||
}
|
||||
237
consensus/taiko/consensus_test.go
Normal file
237
consensus/taiko/consensus_test.go
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
package taiko_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/taiko"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
testL2RollupAddress = common.HexToAddress("0x79fcdef22feed20eddacbb2587640e45491b757f")
|
||||
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
goldenTouchKey, _ = crypto.HexToECDSA("92954368afd3caa1f3ce3ead0069c1af414054aefe1ef9aeacc1bf426222ce38")
|
||||
testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
|
||||
|
||||
genesis *core.Genesis
|
||||
txs []*types.Transaction
|
||||
testEngine *taiko.Taiko
|
||||
)
|
||||
|
||||
func init() {
|
||||
config := params.TestChainConfig
|
||||
config.GrayGlacierBlock = nil
|
||||
config.ArrowGlacierBlock = nil
|
||||
config.Ethash = nil
|
||||
config.Taiko = true
|
||||
testEngine = taiko.New(config)
|
||||
|
||||
taikoL2AddressPrefix := strings.TrimPrefix(config.ChainID.String(), "0")
|
||||
|
||||
taikoL2Address := common.HexToAddress(
|
||||
"0x" +
|
||||
taikoL2AddressPrefix +
|
||||
strings.Repeat("0", common.AddressLength*2-len(taikoL2AddressPrefix)-len(taiko.TaikoL2AddressSuffix)) +
|
||||
taiko.TaikoL2AddressSuffix,
|
||||
)
|
||||
|
||||
genesis = &core.Genesis{
|
||||
Config: config,
|
||||
Alloc: types.GenesisAlloc{testAddr: {Balance: big.NewInt(2e15)}},
|
||||
ExtraData: []byte("test genesis"),
|
||||
Timestamp: 9000,
|
||||
Difficulty: common.Big0,
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
}
|
||||
|
||||
txs = []*types.Transaction{
|
||||
types.MustSignNewTx(goldenTouchKey, types.LatestSigner(genesis.Config), &types.DynamicFeeTx{
|
||||
Nonce: 0,
|
||||
GasTipCap: common.Big0,
|
||||
GasFeeCap: new(big.Int).SetUint64(875_000_000),
|
||||
Data: taiko.AnchorSelector,
|
||||
Gas: taiko.AnchorGasLimit,
|
||||
To: &taikoL2Address,
|
||||
}),
|
||||
types.MustSignNewTx(testKey, types.LatestSigner(genesis.Config), &types.LegacyTx{
|
||||
Nonce: 0,
|
||||
Value: big.NewInt(12),
|
||||
GasPrice: big.NewInt(params.InitialBaseFee),
|
||||
Gas: params.TxGas,
|
||||
To: &common.Address{2},
|
||||
}),
|
||||
types.MustSignNewTx(testKey, types.LatestSigner(genesis.Config), &types.LegacyTx{
|
||||
Nonce: 1,
|
||||
Value: big.NewInt(8),
|
||||
GasPrice: big.NewInt(params.InitialBaseFee),
|
||||
Gas: params.TxGas,
|
||||
To: &common.Address{2},
|
||||
}),
|
||||
// prepareBlockTx
|
||||
types.MustSignNewTx(testKey, types.LatestSigner(genesis.Config), &types.LegacyTx{
|
||||
Nonce: 2,
|
||||
Value: big.NewInt(8),
|
||||
GasPrice: big.NewInt(params.InitialBaseFee),
|
||||
Gas: params.TxGas,
|
||||
To: &testL2RollupAddress,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func newTestBackend(t *testing.T) (*eth.Ethereum, []*types.Block) {
|
||||
// Generate test chain.
|
||||
blocks := generateTestChain()
|
||||
|
||||
// Create node
|
||||
n, err := node.New(&node.Config{})
|
||||
if err != nil {
|
||||
t.Fatalf("can't create new node: %v", err)
|
||||
}
|
||||
|
||||
// Create Ethereum Service
|
||||
config := ð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, triedb.NewDatabase(db, triedb.HashDefaults))
|
||||
|
||||
blocks, _ := core.GenerateChain(genesis.Config, gblock, testEngine, db, 1, generate)
|
||||
|
||||
blocks = append([]*types.Block{gblock}, blocks...)
|
||||
return blocks
|
||||
}
|
||||
|
||||
func TestVerifyHeader(t *testing.T) {
|
||||
ethService, blocks := newTestBackend(t)
|
||||
|
||||
for _, b := range blocks {
|
||||
err := testEngine.VerifyHeader(ethService.BlockChain(), b.Header())
|
||||
assert.NoErrorf(t, err, "VerifyHeader error: %s", err)
|
||||
}
|
||||
|
||||
err := testEngine.VerifyHeader(ethService.BlockChain(), &types.Header{
|
||||
Number: common.Big1,
|
||||
Time: uint64(time.Now().Unix()),
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
WithdrawalsHash: &types.EmptyWithdrawalsHash,
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
})
|
||||
assert.ErrorIs(t, err, consensus.ErrUnknownAncestor, "VerifyHeader should throw ErrUnknownAncestor when parentHash is unknown")
|
||||
|
||||
err = testEngine.VerifyHeader(ethService.BlockChain(), &types.Header{
|
||||
ParentHash: blocks[len(blocks)-1].Hash(),
|
||||
Number: common.Big0,
|
||||
Time: uint64(time.Now().Unix()),
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
WithdrawalsHash: &types.EmptyWithdrawalsHash,
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
})
|
||||
assert.ErrorIs(t, err, consensus.ErrInvalidNumber, "VerifyHeader should throw ErrInvalidNumber when the block number is wrong")
|
||||
|
||||
err = testEngine.VerifyHeader(ethService.BlockChain(), &types.Header{
|
||||
ParentHash: blocks[len(blocks)-1].Hash(),
|
||||
Number: new(big.Int).SetInt64(int64(len(blocks))),
|
||||
Time: uint64(time.Now().Unix()),
|
||||
Extra: bytes.Repeat([]byte{1}, int(params.MaximumExtraDataSize+1)),
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
WithdrawalsHash: &types.EmptyWithdrawalsHash,
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
})
|
||||
assert.ErrorContains(t, err, "extra-data too long", "VerifyHeader should throw ErrExtraDataTooLong when the block has too much extra data")
|
||||
|
||||
err = testEngine.VerifyHeader(ethService.BlockChain(), &types.Header{
|
||||
ParentHash: blocks[len(blocks)-1].Hash(),
|
||||
Number: new(big.Int).SetInt64(int64(len(blocks))),
|
||||
Time: uint64(time.Now().Unix()),
|
||||
Difficulty: common.Big1,
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
WithdrawalsHash: &types.EmptyWithdrawalsHash,
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
})
|
||||
assert.ErrorContains(t, err, "invalid difficulty", "VerifyHeader should throw ErrInvalidDifficulty when difficulty is not 0")
|
||||
|
||||
err = testEngine.VerifyHeader(ethService.BlockChain(), &types.Header{
|
||||
ParentHash: blocks[len(blocks)-1].Hash(),
|
||||
Number: new(big.Int).SetInt64(int64(len(blocks))),
|
||||
Time: uint64(time.Now().Unix()),
|
||||
GasLimit: params.MaxGasLimit + 1,
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
WithdrawalsHash: &types.EmptyWithdrawalsHash,
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
})
|
||||
assert.ErrorContains(t, err, "invalid gasLimit", "VerifyHeader should throw ErrInvalidGasLimit when gasLimit is higher than the limit")
|
||||
|
||||
err = testEngine.VerifyHeader(ethService.BlockChain(), &types.Header{
|
||||
ParentHash: blocks[len(blocks)-1].Hash(),
|
||||
Number: new(big.Int).SetInt64(int64(len(blocks))),
|
||||
Time: uint64(time.Now().Unix()),
|
||||
GasLimit: params.MaxGasLimit,
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
})
|
||||
assert.ErrorContains(t, err, "withdrawals hash missing", "VerifyHeader should throw ErrWithdrawalsHashMissing withdrawalshash is nil")
|
||||
|
||||
err = testEngine.VerifyHeader(ethService.BlockChain(), &types.Header{
|
||||
ParentHash: blocks[len(blocks)-1].Hash(),
|
||||
Number: new(big.Int).SetInt64(int64(len(blocks))),
|
||||
Time: uint64(time.Now().Unix()),
|
||||
GasLimit: params.MaxGasLimit,
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
WithdrawalsHash: &types.EmptyWithdrawalsHash,
|
||||
})
|
||||
assert.ErrorContains(t, err, "uncles not empty", "VerifyHeader should throw ErrUnclesNotEmpty if uncles is not the empty hash")
|
||||
}
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
cmath "github.com/ethereum/go-ethereum/common/math"
|
||||
|
|
@ -145,6 +146,9 @@ type Message struct {
|
|||
// account nonce in state. It also disables checking that the sender is an EOA.
|
||||
// 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.
|
||||
|
|
@ -162,6 +166,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 {
|
||||
|
|
@ -257,6 +262,10 @@ func (st *StateTransition) buyGas() error {
|
|||
if overflow {
|
||||
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
|
||||
}
|
||||
if st.msg.IsAnchor {
|
||||
balanceCheckU256 = common.U2560
|
||||
mgval = common.Big0
|
||||
}
|
||||
if have, want := st.state.GetBalance(st.msg.From), balanceCheckU256; have.Cmp(want) < 0 {
|
||||
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
|
||||
}
|
||||
|
|
@ -297,7 +306,8 @@ func (st *StateTransition) preCheck() error {
|
|||
// Make sure that transaction gasFeeCap is greater than the baseFee (post london)
|
||||
if st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) {
|
||||
// Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call)
|
||||
skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0
|
||||
skipCheck := (st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0) ||
|
||||
st.msg.IsAnchor
|
||||
if !skipCheck {
|
||||
if l := msg.GasFeeCap.BitLen(); l > 256 {
|
||||
return fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh,
|
||||
|
|
@ -457,6 +467,13 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
|||
fee := new(uint256.Int).SetUint64(st.gasUsed())
|
||||
fee.Mul(fee, effectiveTipU256)
|
||||
st.state.AddBalance(st.evm.Context.Coinbase, fee)
|
||||
// CHANGE(taiko): basefee is not burnt, but sent to a treasury instead.
|
||||
if st.evm.ChainConfig().Taiko && st.evm.Context.BaseFee != nil && !st.msg.IsAnchor {
|
||||
st.state.AddBalance(
|
||||
st.getTreasuryAddress(),
|
||||
uint256.MustFromBig(new(big.Int).Mul(st.evm.Context.BaseFee, new(big.Int).SetUint64(st.gasUsed()))),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return &ExecutionResult{
|
||||
|
|
@ -475,10 +492,13 @@ func (st *StateTransition) refundGas(refundQuotient uint64) uint64 {
|
|||
}
|
||||
st.gasRemaining += refund
|
||||
|
||||
// Return ETH for remaining gas, exchanged at the original rate.
|
||||
remaining := uint256.NewInt(st.gasRemaining)
|
||||
remaining = remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice))
|
||||
st.state.AddBalance(st.msg.From, remaining)
|
||||
// Do not change the balance in anchor transactions.
|
||||
if !st.msg.IsAnchor {
|
||||
// Return ETH for remaining gas, exchanged at the original rate.
|
||||
remaining := uint256.NewInt(st.gasRemaining)
|
||||
remaining = remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice))
|
||||
st.state.AddBalance(st.msg.From, remaining)
|
||||
}
|
||||
|
||||
// Also return remaining gas to the block gas counter so it is
|
||||
// available for the next transaction.
|
||||
|
|
@ -496,3 +516,17 @@ func (st *StateTransition) gasUsed() uint64 {
|
|||
func (st *StateTransition) blobGasUsed() uint64 {
|
||||
return uint64(len(st.msg.BlobHashes) * params.BlobTxBlobGasPerBlob)
|
||||
}
|
||||
|
||||
// CHANGE(taiko): returns the treasury address based on chain ID.
|
||||
func (st *StateTransition) getTreasuryAddress() common.Address {
|
||||
var (
|
||||
prefix = st.evm.ChainConfig().ChainID.String()
|
||||
suffix = "10001"
|
||||
)
|
||||
return common.HexToAddress(
|
||||
"0x" +
|
||||
prefix +
|
||||
strings.Repeat("0", common.AddressLength*2-len(prefix)-len(suffix)) +
|
||||
suffix,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
67
core/taiko_genesis.go
Normal file
67
core/taiko_genesis.go
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
taikoGenesis "github.com/ethereum/go-ethereum/core/taiko_genesis"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// TaikoGenesisBlock returns the Taiko network genesis block configs.
|
||||
func TaikoGenesisBlock(networkID uint64) *Genesis {
|
||||
chainConfig := params.TaikoChainConfig
|
||||
|
||||
var allocJSON []byte
|
||||
switch networkID {
|
||||
case params.TaikoMainnetNetworkID.Uint64():
|
||||
chainConfig.ChainID = params.TaikoMainnetNetworkID
|
||||
allocJSON = taikoGenesis.MainnetGenesisAllocJSON
|
||||
case params.TaikoInternalL2ANetworkID.Uint64():
|
||||
chainConfig.ChainID = params.TaikoInternalL2ANetworkID
|
||||
allocJSON = taikoGenesis.InternalL2AGenesisAllocJSON
|
||||
case params.TaikoInternalL2BNetworkID.Uint64():
|
||||
chainConfig.ChainID = params.TaikoInternalL2BNetworkID
|
||||
allocJSON = taikoGenesis.InternalL2BGenesisAllocJSON
|
||||
case params.SnaefellsjokullNetworkID.Uint64():
|
||||
chainConfig.ChainID = params.SnaefellsjokullNetworkID
|
||||
allocJSON = taikoGenesis.SnaefellsjokullGenesisAllocJSON
|
||||
case params.AskjaNetworkID.Uint64():
|
||||
chainConfig.ChainID = params.AskjaNetworkID
|
||||
allocJSON = taikoGenesis.AskjaGenesisAllocJSON
|
||||
case params.GrimsvotnNetworkID.Uint64():
|
||||
chainConfig.ChainID = params.GrimsvotnNetworkID
|
||||
allocJSON = taikoGenesis.GrimsvotnGenesisAllocJSON
|
||||
case params.EldfellNetworkID.Uint64():
|
||||
chainConfig.ChainID = params.EldfellNetworkID
|
||||
allocJSON = taikoGenesis.EldfellGenesisAllocJSON
|
||||
case params.JolnirNetworkID.Uint64():
|
||||
chainConfig.ChainID = params.JolnirNetworkID
|
||||
allocJSON = taikoGenesis.JolnirGenesisAllocJSON
|
||||
case params.KatlaNetworkID.Uint64():
|
||||
chainConfig.ChainID = params.KatlaNetworkID
|
||||
allocJSON = taikoGenesis.KatlaGenesisAllocJSON
|
||||
case params.HeklaNetworkID.Uint64():
|
||||
chainConfig.ChainID = params.HeklaNetworkID
|
||||
allocJSON = taikoGenesis.HeklaGenesisAllocJSON
|
||||
default:
|
||||
chainConfig.ChainID = params.TaikoInternalL2ANetworkID
|
||||
allocJSON = taikoGenesis.InternalL2AGenesisAllocJSON
|
||||
}
|
||||
|
||||
var alloc GenesisAlloc
|
||||
if err := alloc.UnmarshalJSON(allocJSON); err != nil {
|
||||
log.Crit("unmarshal alloc json error", "error", err)
|
||||
}
|
||||
|
||||
return &Genesis{
|
||||
Config: chainConfig,
|
||||
ExtraData: []byte{},
|
||||
GasLimit: uint64(15_000_000),
|
||||
Difficulty: common.Big0,
|
||||
Alloc: alloc,
|
||||
GasUsed: 0,
|
||||
BaseFee: new(big.Int).SetUint64(10_000_000),
|
||||
}
|
||||
}
|
||||
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
35
core/taiko_genesis/genesis_alloc.go
Normal file
35
core/taiko_genesis/genesis_alloc.go
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
package taiko_genesis
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed internal_l2a.json
|
||||
var InternalL2AGenesisAllocJSON []byte
|
||||
|
||||
//go:embed internal_l2b.json
|
||||
var InternalL2BGenesisAllocJSON []byte
|
||||
|
||||
//go:embed snaefellsjokull.json
|
||||
var SnaefellsjokullGenesisAllocJSON []byte
|
||||
|
||||
//go:embed askja.json
|
||||
var AskjaGenesisAllocJSON []byte
|
||||
|
||||
//go:embed grimsvotn.json
|
||||
var GrimsvotnGenesisAllocJSON []byte
|
||||
|
||||
//go:embed eldfell.json
|
||||
var EldfellGenesisAllocJSON []byte
|
||||
|
||||
//go:embed jolnir.json
|
||||
var JolnirGenesisAllocJSON []byte
|
||||
|
||||
//go:embed katla.json
|
||||
var KatlaGenesisAllocJSON []byte
|
||||
|
||||
//go:embed hekla.json
|
||||
var HeklaGenesisAllocJSON []byte
|
||||
|
||||
//go:embed mainnet.json
|
||||
var MainnetGenesisAllocJSON []byte
|
||||
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
217
core/taiko_genesis/hekla.json
Normal file
217
core/taiko_genesis/hekla.json
Normal file
File diff suppressed because one or more lines are too long
369
core/taiko_genesis/internal_l2a.json
Normal file
369
core/taiko_genesis/internal_l2a.json
Normal file
File diff suppressed because one or more lines are too long
369
core/taiko_genesis/internal_l2b.json
Normal file
369
core/taiko_genesis/internal_l2b.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
206
core/taiko_genesis/katla.json
Normal file
206
core/taiko_genesis/katla.json
Normal file
File diff suppressed because one or more lines are too long
205
core/taiko_genesis/mainnet.json
Normal file
205
core/taiko_genesis/mainnet.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"
|
||||
|
|
@ -96,6 +98,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 larger than 0.1 GWei
|
||||
if os.Getenv("TAIKO_TEST") == "" && tx.GasFeeCap().Cmp(new(big.Int).SetUint64(params.GWei/10)) < 0 {
|
||||
return errors.New("max fee per gas is less than 0.1 gwei")
|
||||
}
|
||||
// Make sure the transaction is signed properly
|
||||
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
|
||||
}
|
||||
|
|
@ -87,6 +87,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)
|
||||
|
||||
|
|
@ -589,3 +593,15 @@ func copyAddressPtr(a *common.Address) *common.Address {
|
|||
cpy := *a
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// CHANGE(taiko): SetBlobTxSidecar sets the sidecar of a transaction.
|
||||
// The sidecar should match the blob-tx versioned hashes, or the transaction will be invalid.
|
||||
// This allows tools to easily re-attach blob sidecars to signed transactions that omit the sidecar.
|
||||
func (tx *Transaction) SetBlobTxSidecar(sidecar *BlobTxSidecar) error {
|
||||
blobtx, ok := tx.inner.(*BlobTx)
|
||||
if !ok {
|
||||
return fmt.Errorf("not a blob tx, type = %d", tx.Type())
|
||||
}
|
||||
blobtx.Sidecar = sidecar
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ package catalyst
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
|
@ -318,6 +319,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 {
|
||||
|
|
@ -327,6 +332,10 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
|
|||
// If the specified head matches with our local head, do nothing and keep
|
||||
// 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.
|
||||
|
|
@ -371,6 +380,56 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
|
|||
// sealed by the beacon client. The payload will be requested later, and we
|
||||
// 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(),
|
||||
Version: payloadVersion,
|
||||
}
|
||||
id := args.Id()
|
||||
payload, err := api.eth.Miner().BuildPayload(args)
|
||||
if err != nil {
|
||||
log.Error("Failed to build payload", "err", err)
|
||||
return valid(nil), engine.InvalidPayloadAttributes.With(err)
|
||||
}
|
||||
|
||||
payload.SetFullBlock(block, common.Big0)
|
||||
|
||||
api.localBlocks.put(id, payload)
|
||||
|
||||
// L1Origin **MUST NOT** be nil, it's a required field in PayloadAttributesV1.
|
||||
l1Origin := payloadAttributes.L1Origin
|
||||
|
||||
// Set the block hash before inserting the L1Origin into database.
|
||||
l1Origin.L2BlockHash = block.Hash()
|
||||
|
||||
// Write L1Origin.
|
||||
rawdb.WriteL1Origin(api.eth.ChainDb(), l1Origin.BlockID, l1Origin)
|
||||
// Write the head L1Origin.
|
||||
rawdb.WriteHeadL1Origin(api.eth.ChainDb(), l1Origin.BlockID)
|
||||
|
||||
return valid(&id), nil
|
||||
}
|
||||
|
||||
args := &miner.BuildPayloadArgs{
|
||||
Parent: update.HeadBlockHash,
|
||||
Timestamp: payloadAttributes.Timestamp,
|
||||
|
|
@ -491,7 +550,8 @@ func (api *ConsensusAPI) NewPayloadV2(params engine.ExecutableData) (engine.Payl
|
|||
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("can't use newPayloadV2 post-cancun"))
|
||||
}
|
||||
if api.eth.BlockChain().Config().LatestFork(params.Timestamp) == forks.Shanghai {
|
||||
if params.Withdrawals == nil {
|
||||
if params.Withdrawals == nil &&
|
||||
(api.eth.BlockChain().Config().Taiko && params.WithdrawalsHash == (common.Hash{})) {
|
||||
return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai"))
|
||||
}
|
||||
} else {
|
||||
|
|
@ -551,10 +611,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()
|
||||
|
|
@ -597,9 +684,18 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe
|
|||
log.Error("Ignoring pre-merge parent block", "number", params.Number, "hash", params.BlockHash, "td", ptd, "ttd", ttd)
|
||||
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"
|
||||
|
|
@ -165,6 +166,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
|
||||
|
|
|
|||
|
|
@ -239,6 +239,11 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block,
|
|||
// Recompute transactions up to the target index.
|
||||
signer := types.MakeSigner(eth.blockchain.Config(), block.Number(), block.Time())
|
||||
for idx, tx := range block.Transactions() {
|
||||
if idx == 0 && eth.config.Genesis.Config.Taiko {
|
||||
if err := tx.MarkAsAnchor(); err != nil {
|
||||
return nil, vm.BlockContext{}, nil, nil, err
|
||||
}
|
||||
}
|
||||
// Assemble the transaction call message and return if the requested offset
|
||||
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
||||
txContext := core.NewEVMTxContext(msg)
|
||||
|
|
|
|||
104
eth/taiko_api_backend.go
Normal file
104
eth/taiko_api_backend.go
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
package eth
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/miner"
|
||||
)
|
||||
|
||||
// TaikoAPIBackend handles L2 node related RPC calls.
|
||||
type TaikoAPIBackend struct {
|
||||
eth *Ethereum
|
||||
}
|
||||
|
||||
// NewTaikoAPIBackend creates a new TaikoAPIBackend instance.
|
||||
func NewTaikoAPIBackend(eth *Ethereum) *TaikoAPIBackend {
|
||||
return &TaikoAPIBackend{
|
||||
eth: eth,
|
||||
}
|
||||
}
|
||||
|
||||
// HeadL1Origin returns the latest L2 block's corresponding L1 origin.
|
||||
func (s *TaikoAPIBackend) HeadL1Origin() (*rawdb.L1Origin, error) {
|
||||
blockID, err := rawdb.ReadHeadL1Origin(s.eth.ChainDb())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if blockID == nil {
|
||||
return nil, ethereum.NotFound
|
||||
}
|
||||
|
||||
l1Origin, err := rawdb.ReadL1Origin(s.eth.ChainDb(), blockID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if l1Origin == nil {
|
||||
return nil, ethereum.NotFound
|
||||
}
|
||||
|
||||
return l1Origin, nil
|
||||
}
|
||||
|
||||
// L1OriginByID returns the L2 block's corresponding L1 origin.
|
||||
func (s *TaikoAPIBackend) L1OriginByID(blockID *math.HexOrDecimal256) (*rawdb.L1Origin, error) {
|
||||
l1Origin, err := rawdb.ReadL1Origin(s.eth.ChainDb(), (*big.Int)(blockID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if l1Origin == nil {
|
||||
return nil, ethereum.NotFound
|
||||
}
|
||||
|
||||
return l1Origin, nil
|
||||
}
|
||||
|
||||
// GetSyncMode returns the node sync mode.
|
||||
func (s *TaikoAPIBackend) GetSyncMode() (string, error) {
|
||||
return s.eth.config.SyncMode.String(), nil
|
||||
}
|
||||
|
||||
// TaikoAuthAPIBackend handles L2 node related authorized RPC calls.
|
||||
type TaikoAuthAPIBackend struct {
|
||||
eth *Ethereum
|
||||
}
|
||||
|
||||
// NewTaikoAuthAPIBackend creates a new TaikoAuthAPIBackend instance.
|
||||
func NewTaikoAuthAPIBackend(eth *Ethereum) *TaikoAuthAPIBackend {
|
||||
return &TaikoAuthAPIBackend{eth}
|
||||
}
|
||||
|
||||
// TxPoolContent retrieves the transaction pool content with the given upper limits.
|
||||
func (a *TaikoAuthAPIBackend) TxPoolContent(
|
||||
beneficiary common.Address,
|
||||
baseFee *big.Int,
|
||||
blockMaxGasLimit uint64,
|
||||
maxBytesPerTxList uint64,
|
||||
locals []string,
|
||||
maxTransactionsLists uint64,
|
||||
) ([]*miner.PreBuiltTxList, error) {
|
||||
log.Debug(
|
||||
"Fetching L2 pending transactions finished",
|
||||
"baseFee", baseFee,
|
||||
"blockMaxGasLimit", blockMaxGasLimit,
|
||||
"maxBytesPerTxList", maxBytesPerTxList,
|
||||
"maxTransactions", maxTransactionsLists,
|
||||
"locals", locals,
|
||||
)
|
||||
|
||||
return a.eth.Miner().BuildTransactionsLists(
|
||||
beneficiary,
|
||||
baseFee,
|
||||
blockMaxGasLimit,
|
||||
maxBytesPerTxList,
|
||||
locals,
|
||||
maxTransactionsLists,
|
||||
)
|
||||
}
|
||||
|
|
@ -270,6 +270,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(),
|
||||
|
|
@ -527,6 +534,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
|
||||
}
|
||||
|
|
@ -604,6 +616,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{
|
||||
|
|
@ -757,6 +774,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())
|
||||
|
|
|
|||
42
ethclient/taiko_api.go
Normal file
42
ethclient/taiko_api.go
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
package ethclient
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
)
|
||||
|
||||
// HeadL1Origin returns the latest L2 block's corresponding L1 origin.
|
||||
func (ec *Client) HeadL1Origin(ctx context.Context) (*rawdb.L1Origin, error) {
|
||||
var res *rawdb.L1Origin
|
||||
|
||||
if err := ec.c.CallContext(ctx, &res, "taiko_headL1Origin"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// L1OriginByID returns the L2 block's corresponding L1 origin.
|
||||
func (ec *Client) L1OriginByID(ctx context.Context, blockID *big.Int) (*rawdb.L1Origin, error) {
|
||||
var res *rawdb.L1Origin
|
||||
|
||||
if err := ec.c.CallContext(ctx, &res, "taiko_l1OriginByID", hexutil.EncodeBig(blockID)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetSyncMode returns the current sync mode of the L2 node.
|
||||
func (ec *Client) GetSyncMode(ctx context.Context) (string, error) {
|
||||
var res string
|
||||
|
||||
if err := ec.c.CallContext(ctx, &res, "taiko_getSyncMode"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
124
ethclient/taiko_api_test.go
Normal file
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
|
||||
}
|
||||
2
go.mod
2
go.mod
|
|
@ -72,6 +72,7 @@ require (
|
|||
golang.org/x/tools v0.15.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/mathutil v1.6.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
|
@ -132,6 +133,7 @@ require (
|
|||
github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -531,6 +531,8 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
|
|||
github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7 h1:cZC+usqsYgHtlBaGulVnZ1hfKAi8iWtujBnRLQE698c=
|
||||
github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7/go.mod h1:IToEjHuttnUzwZI5KBSM/LOOW3qLbbrHOEfp3SbECGY=
|
||||
github.com/prysmaticlabs/gohashtree v0.0.1-alpha.0.20220714111606-acbb2962fb48 h1:cSo6/vk8YpvkLbk9v3FO97cakNmUoxwi2KMP8hd5WIw=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
|
|
@ -973,6 +975,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
|||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
|
|
|||
|
|
@ -76,6 +76,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.
|
||||
|
|
@ -84,6 +85,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)
|
||||
|
|
@ -133,6 +135,7 @@ func (payload *Payload) Resolve() *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)
|
||||
}
|
||||
if payload.full != nil {
|
||||
|
|
@ -171,6 +174,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)
|
||||
|
|
@ -226,6 +230,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 {
|
||||
|
|
|
|||
47
miner/taiko_miner.go
Normal file
47
miner/taiko_miner.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package miner
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/beacon/engine"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
// PreBuiltTxList is a pre-built transaction list based on the latest chain state,
|
||||
// with estimated gas used / bytes.
|
||||
type PreBuiltTxList struct {
|
||||
TxList types.Transactions
|
||||
EstimatedGasUsed uint64
|
||||
BytesLength uint64
|
||||
}
|
||||
|
||||
// SealBlockWith mines and seals a block without changing the canonical chain.
|
||||
func (miner *Miner) SealBlockWith(
|
||||
parent common.Hash,
|
||||
timestamp uint64,
|
||||
blkMeta *engine.BlockMetadata,
|
||||
baseFeePerGas *big.Int,
|
||||
withdrawals types.Withdrawals,
|
||||
) (*types.Block, error) {
|
||||
return miner.worker.sealBlockWith(parent, timestamp, blkMeta, baseFeePerGas, withdrawals)
|
||||
}
|
||||
|
||||
// BuildTransactionsLists builds multiple transactions lists which satisfy all the given limits.
|
||||
func (miner *Miner) BuildTransactionsLists(
|
||||
beneficiary common.Address,
|
||||
baseFee *big.Int,
|
||||
blockMaxGasLimit uint64,
|
||||
maxBytesPerTxList uint64,
|
||||
locals []string,
|
||||
maxTransactionsLists uint64,
|
||||
) ([]*PreBuiltTxList, error) {
|
||||
return miner.worker.BuildTransactionsLists(
|
||||
beneficiary,
|
||||
baseFee,
|
||||
blockMaxGasLimit,
|
||||
maxBytesPerTxList,
|
||||
locals,
|
||||
maxTransactionsLists,
|
||||
)
|
||||
}
|
||||
53
miner/taiko_payload_building.go
Normal file
53
miner/taiko_payload_building.go
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package miner
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// SetFullBlock updates the full-block to the given block.
|
||||
func (payload *Payload) SetFullBlock(block *types.Block, fees *big.Int) {
|
||||
payload.lock.Lock()
|
||||
defer payload.lock.Unlock()
|
||||
|
||||
go payload.afterSetFullBlock()
|
||||
|
||||
payload.full = block
|
||||
payload.fullFees = fees
|
||||
|
||||
feesInEther := new(big.Float).Quo(new(big.Float).SetInt(fees), big.NewFloat(params.Ether))
|
||||
log.Info("Updated payload", "id", payload.id, "number", block.NumberU64(), "hash", block.Hash(),
|
||||
"txs", len(block.Transactions()), "gas", block.GasUsed(), "fees", feesInEther,
|
||||
"root", block.Root())
|
||||
|
||||
payload.cond.Broadcast() // fire signal for notifying full block
|
||||
}
|
||||
|
||||
func (payload *Payload) afterSetFullBlock() {
|
||||
payload.lock.Lock()
|
||||
defer payload.lock.Unlock()
|
||||
|
||||
select {
|
||||
case <-payload.done:
|
||||
log.Info("SetFullBlock payload.done received", "id", payload.id)
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
select {
|
||||
case _, ok := <-payload.stop:
|
||||
if ok {
|
||||
log.Info("SetFullBlock payload.stop received", "id", payload.id)
|
||||
return
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
select {
|
||||
case payload.stop <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
335
miner/taiko_worker.go
Normal file
335
miner/taiko_worker.go
Normal file
|
|
@ -0,0 +1,335 @@
|
|||
package miner
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/beacon/engine"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/txpool"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// BuildTransactionsLists builds multiple transactions lists which satisfy all the given conditions
|
||||
// 1. All transactions should all be able to pay the given base fee.
|
||||
// 2. The total gas used should not exceed the given blockMaxGasLimit
|
||||
// 3. The total bytes used should not exceed the given maxBytesPerTxList
|
||||
// 4. The total number of transactions lists should not exceed the given maxTransactionsLists
|
||||
func (w *worker) BuildTransactionsLists(
|
||||
beneficiary common.Address,
|
||||
baseFee *big.Int,
|
||||
blockMaxGasLimit uint64,
|
||||
maxBytesPerTxList uint64,
|
||||
localAccounts []string,
|
||||
maxTransactionsLists uint64,
|
||||
) ([]*PreBuiltTxList, error) {
|
||||
var (
|
||||
txsLists []*PreBuiltTxList
|
||||
currentHead = w.chain.CurrentBlock()
|
||||
)
|
||||
|
||||
if currentHead == nil {
|
||||
return nil, fmt.Errorf("failed to find current head")
|
||||
}
|
||||
|
||||
// Check if tx pool is empty at first.
|
||||
if len(w.eth.TxPool().Pending(txpool.PendingFilter{BaseFee: uint256.MustFromBig(baseFee), OnlyPlainTxs: true})) == 0 {
|
||||
return txsLists, nil
|
||||
}
|
||||
|
||||
params := &generateParams{
|
||||
timestamp: uint64(time.Now().Unix()),
|
||||
forceTime: true,
|
||||
parentHash: currentHead.Hash(),
|
||||
coinbase: beneficiary,
|
||||
random: currentHead.MixDigest,
|
||||
noTxs: false,
|
||||
baseFeePerGas: baseFee,
|
||||
}
|
||||
|
||||
env, err := w.prepareWork(params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer env.discard()
|
||||
|
||||
var (
|
||||
signer = types.MakeSigner(w.chainConfig, new(big.Int).Add(currentHead.Number, common.Big1), currentHead.Time)
|
||||
// Split the pending transactions into locals and remotes, then
|
||||
// fill the block with all available pending transactions.
|
||||
localTxs, remoteTxs = w.getPendingTxs(localAccounts, baseFee)
|
||||
)
|
||||
|
||||
commitTxs := func() (*PreBuiltTxList, error) {
|
||||
env.tcount = 0
|
||||
env.txs = []*types.Transaction{}
|
||||
env.gasPool = new(core.GasPool).AddGas(blockMaxGasLimit)
|
||||
env.header.GasLimit = blockMaxGasLimit
|
||||
|
||||
var (
|
||||
locals = make(map[common.Address][]*txpool.LazyTransaction)
|
||||
remotes = make(map[common.Address][]*txpool.LazyTransaction)
|
||||
)
|
||||
|
||||
for address, txs := range localTxs {
|
||||
locals[address] = txs
|
||||
}
|
||||
for address, txs := range remoteTxs {
|
||||
remotes[address] = txs
|
||||
}
|
||||
|
||||
w.commitL2Transactions(
|
||||
env,
|
||||
newTransactionsByPriceAndNonce(signer, locals, baseFee),
|
||||
newTransactionsByPriceAndNonce(signer, remotes, baseFee),
|
||||
maxBytesPerTxList,
|
||||
)
|
||||
|
||||
b, err := encodeAndComporeessTxList(env.txs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &PreBuiltTxList{
|
||||
TxList: env.txs,
|
||||
EstimatedGasUsed: env.header.GasLimit - env.gasPool.Gas(),
|
||||
BytesLength: uint64(len(b)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
for i := 0; i < int(maxTransactionsLists); i++ {
|
||||
res, err := commitTxs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(res.TxList) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
txsLists = append(txsLists, res)
|
||||
}
|
||||
|
||||
return txsLists, nil
|
||||
}
|
||||
|
||||
// sealBlockWith mines and seals a block with the given block metadata.
|
||||
func (w *worker) sealBlockWith(
|
||||
parent common.Hash,
|
||||
timestamp uint64,
|
||||
blkMeta *engine.BlockMetadata,
|
||||
baseFeePerGas *big.Int,
|
||||
withdrawals types.Withdrawals,
|
||||
) (*types.Block, error) {
|
||||
// Decode transactions bytes.
|
||||
var txs types.Transactions
|
||||
if err := rlp.DecodeBytes(blkMeta.TxList, &txs); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode txList: %w", err)
|
||||
}
|
||||
|
||||
if len(txs) == 0 {
|
||||
// A L2 block needs to have have at least one `V1TaikoL2.anchor` or
|
||||
// `V1TaikoL2.invalidateBlock` transaction.
|
||||
return nil, fmt.Errorf("too less transactions in the block")
|
||||
}
|
||||
|
||||
params := &generateParams{
|
||||
timestamp: timestamp,
|
||||
forceTime: true,
|
||||
parentHash: parent,
|
||||
coinbase: blkMeta.Beneficiary,
|
||||
random: blkMeta.MixHash,
|
||||
withdrawals: withdrawals,
|
||||
noTxs: false,
|
||||
baseFeePerGas: baseFeePerGas,
|
||||
}
|
||||
|
||||
// Set extraData
|
||||
w.extra = blkMeta.ExtraData
|
||||
|
||||
env, err := w.prepareWork(params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer env.discard()
|
||||
|
||||
env.header.GasLimit = blkMeta.GasLimit
|
||||
|
||||
// Commit transactions.
|
||||
gasLimit := env.header.GasLimit
|
||||
rules := w.chain.Config().Rules(env.header.Number, true, timestamp)
|
||||
|
||||
env.gasPool = new(core.GasPool).AddGas(gasLimit)
|
||||
|
||||
for i, tx := range txs {
|
||||
if i == 0 {
|
||||
if err := tx.MarkAsAnchor(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
sender, err := types.LatestSignerForChainID(tx.ChainId()).Sender(tx)
|
||||
if err != nil {
|
||||
log.Info("Skip an invalid proposed transaction", "hash", tx.Hash(), "reason", err)
|
||||
continue
|
||||
}
|
||||
|
||||
env.state.Prepare(rules, sender, blkMeta.Beneficiary, tx.To(), vm.ActivePrecompiles(rules), tx.AccessList())
|
||||
env.state.SetTxContext(tx.Hash(), env.tcount)
|
||||
if _, err := w.commitTransaction(env, tx); err != nil {
|
||||
log.Info("Skip an invalid proposed transaction", "hash", tx.Hash(), "reason", err)
|
||||
continue
|
||||
}
|
||||
env.tcount++
|
||||
}
|
||||
|
||||
block, err := w.engine.FinalizeAndAssemble(w.chain, env.header, env.state, env.txs, nil, env.receipts, withdrawals)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results := make(chan *types.Block, 1)
|
||||
if err := w.engine.Seal(w.chain, block, results, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
block = <-results
|
||||
|
||||
return block, nil
|
||||
}
|
||||
|
||||
// getPendingTxs fetches the pending transactions from tx pool.
|
||||
func (w *worker) getPendingTxs(localAccounts []string, baseFee *big.Int) (
|
||||
map[common.Address][]*txpool.LazyTransaction,
|
||||
map[common.Address][]*txpool.LazyTransaction,
|
||||
) {
|
||||
pending := w.eth.TxPool().Pending(txpool.PendingFilter{OnlyPlainTxs: true, BaseFee: uint256.MustFromBig(baseFee)})
|
||||
localTxs, remoteTxs := make(map[common.Address][]*txpool.LazyTransaction), pending
|
||||
|
||||
for _, local := range localAccounts {
|
||||
account := common.HexToAddress(local)
|
||||
if txs := remoteTxs[account]; len(txs) > 0 {
|
||||
delete(remoteTxs, account)
|
||||
localTxs[account] = txs
|
||||
}
|
||||
}
|
||||
|
||||
return localTxs, remoteTxs
|
||||
}
|
||||
|
||||
// commitL2Transactions tries to commit the transactions into the given state.
|
||||
func (w *worker) commitL2Transactions(
|
||||
env *environment,
|
||||
txsLocal *transactionsByPriceAndNonce,
|
||||
txsRemote *transactionsByPriceAndNonce,
|
||||
maxBytesPerTxList uint64,
|
||||
) {
|
||||
var (
|
||||
txs = txsLocal
|
||||
isLocal = true
|
||||
)
|
||||
|
||||
for {
|
||||
// If we don't have enough gas for any further transactions then we're done.
|
||||
if env.gasPool.Gas() < params.TxGas {
|
||||
log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", params.TxGas)
|
||||
break
|
||||
}
|
||||
|
||||
// Retrieve the next transaction and abort if all done.
|
||||
ltx, _ := txs.Peek()
|
||||
if ltx == nil {
|
||||
if isLocal {
|
||||
txs = txsRemote
|
||||
isLocal = false
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
tx := ltx.Resolve()
|
||||
if tx == nil {
|
||||
log.Trace("Ignoring evicted transaction")
|
||||
|
||||
txs.Pop()
|
||||
continue
|
||||
}
|
||||
// Error may be ignored here. The error has already been checked
|
||||
// during transaction acceptance is the transaction pool.
|
||||
from, _ := types.Sender(env.signer, tx)
|
||||
|
||||
b, err := encodeAndComporeessTxList(append(env.txs, tx))
|
||||
if err != nil {
|
||||
log.Trace("Failed to rlp encode and compress the pending transaction %s: %w", tx.Hash(), err)
|
||||
txs.Pop()
|
||||
continue
|
||||
}
|
||||
if len(b) >= int(maxBytesPerTxList) {
|
||||
break
|
||||
}
|
||||
|
||||
// Check whether the tx is replay protected. If we're not in the EIP155 hf
|
||||
// phase, start ignoring the sender until we do.
|
||||
if tx.Protected() && !w.chainConfig.IsEIP155(env.header.Number) {
|
||||
log.Trace("Ignoring reply protected transaction", "hash", tx.Hash(), "eip155", w.chainConfig.EIP155Block)
|
||||
|
||||
txs.Pop()
|
||||
continue
|
||||
}
|
||||
// Start executing the transaction
|
||||
env.state.SetTxContext(tx.Hash(), env.tcount)
|
||||
|
||||
_, err = w.commitTransaction(env, tx)
|
||||
switch {
|
||||
case errors.Is(err, core.ErrNonceTooLow):
|
||||
// New head notification data race between the transaction pool and miner, shift
|
||||
log.Trace("Skipping transaction with low nonce", "hash", ltx.Hash, "sender", from, "nonce", tx.Nonce())
|
||||
txs.Shift()
|
||||
|
||||
case errors.Is(err, nil):
|
||||
// Everything ok, collect the logs and shift in the next transaction from the same account
|
||||
env.tcount++
|
||||
txs.Shift()
|
||||
|
||||
default:
|
||||
// Transaction is regarded as invalid, drop all consecutive transactions from
|
||||
// the same sender because of `nonce-too-high` clause.
|
||||
log.Trace("Transaction failed, account skipped", "hash", ltx.Hash, "err", err)
|
||||
txs.Pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// encodeAndComporeessTxList encodes and compresses the given transactions list.
|
||||
func encodeAndComporeessTxList(txs types.Transactions) ([]byte, error) {
|
||||
b, err := rlp.EncodeToBytes(txs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return compress(b)
|
||||
}
|
||||
|
||||
// compress compresses the given txList bytes using zlib.
|
||||
func compress(txListBytes []byte) ([]byte, error) {
|
||||
var b bytes.Buffer
|
||||
w := zlib.NewWriter(&b)
|
||||
defer w.Close()
|
||||
|
||||
if _, err := w.Write(txListBytes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := w.Flush(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
|
@ -925,14 +925,15 @@ func (w *worker) commitTransactions(env *environment, plainTxs, blobTxs *transac
|
|||
|
||||
// generateParams wraps various of settings for generating sealing task.
|
||||
type generateParams struct {
|
||||
timestamp uint64 // The timestamp 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 timestamp 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,
|
||||
|
|
@ -955,10 +956,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{
|
||||
|
|
@ -978,10 +986,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.
|
||||
|
|
|
|||
|
|
@ -49,7 +49,8 @@ var (
|
|||
DefaultAuthVhosts = []string{"localhost"} // Default virtual hosts for the authenticated apis
|
||||
DefaultAuthOrigins = []string{"localhost"} // Default origins for the authenticated apis
|
||||
DefaultAuthPrefix = "" // Default prefix for the authenticated apis
|
||||
DefaultAuthModules = []string{"eth", "engine"}
|
||||
// CHANGE(taiko): add `taikoAuth` namespace for authenticated APIs.
|
||||
DefaultAuthModules = []string{"eth", "engine", rpc.TaikoAuth}
|
||||
)
|
||||
|
||||
// DefaultConfig contains reasonable default settings.
|
||||
|
|
|
|||
|
|
@ -62,7 +62,8 @@ func checkModuleAvailability(modules []string, apis []rpc.API) (bad, available [
|
|||
}
|
||||
for _, name := range modules {
|
||||
if _, ok := availableSet[name]; !ok {
|
||||
if name != rpc.MetadataApi && name != rpc.EngineApi {
|
||||
// CHANGE(taiko): add `taikoAuth` namespace for authenticated APIs.
|
||||
if name != rpc.MetadataApi && name != rpc.EngineApi && name != rpc.TaikoAuth {
|
||||
bad = append(bad, name)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -315,6 +315,17 @@ var NetworkNames = map[string]string{
|
|||
GoerliChainConfig.ChainID.String(): "goerli",
|
||||
SepoliaChainConfig.ChainID.String(): "sepolia",
|
||||
HoleskyChainConfig.ChainID.String(): "holesky",
|
||||
// CHANGE(taiko): add Taiko network name.
|
||||
TaikoMainnetNetworkID.String(): "Taiko",
|
||||
TaikoInternalL2ANetworkID.String(): "Taiko Internal L2A Devnet",
|
||||
TaikoInternalL2BNetworkID.String(): "Taiko Internal L2B Devnet",
|
||||
SnaefellsjokullNetworkID.String(): "Taiko Alpha-1 (Snæfellsjökull)",
|
||||
AskjaNetworkID.String(): "Taiko Alpha-2 (Askja)",
|
||||
GrimsvotnNetworkID.String(): "Taiko Alpha-3 L2 (Grimsvotn)",
|
||||
EldfellNetworkID.String(): "Taiko Alpha-4 L3 (Eldfell)",
|
||||
JolnirNetworkID.String(): "Taiko Alpha-5 L2 (Jolnir)",
|
||||
KatlaNetworkID.String(): "Taiko Alpha-6 L2 (Katla)",
|
||||
HeklaNetworkID.String(): "Taiko Alpha-7 L2 (Hekla)",
|
||||
}
|
||||
|
||||
// ChainConfig is the core config which determines the blockchain settings.
|
||||
|
|
@ -365,6 +376,9 @@ type ChainConfig struct {
|
|||
// Various consensus engines
|
||||
Ethash *EthashConfig `json:"ethash,omitempty"`
|
||||
Clique *CliqueConfig `json:"clique,omitempty"`
|
||||
|
||||
// CHANGE(taiko): Taiko network flag.
|
||||
Taiko bool `json:"taiko"`
|
||||
}
|
||||
|
||||
// EthashConfig is the consensus engine configs for proof-of-work based sealing.
|
||||
|
|
@ -397,6 +411,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"
|
||||
|
|
|
|||
|
|
@ -173,6 +173,8 @@ const (
|
|||
|
||||
BlobTxTargetBlobGasPerBlock = 3 * BlobTxBlobGasPerBlob // Target consumable blob gas for data blobs per block (for 1559-like pricing)
|
||||
MaxBlobGasPerBlock = 6 * BlobTxBlobGasPerBlob // Maximum consumable blob gas for data blobs per block
|
||||
|
||||
BlobTxHashVersion = 0x01 // CHANGE(taiko): Version byte of the commitment hash
|
||||
)
|
||||
|
||||
// Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations
|
||||
|
|
|
|||
68
params/taiko_config.go
Normal file
68
params/taiko_config.go
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
package params
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
func u64(val uint64) *uint64 { return &val }
|
||||
|
||||
// Network IDs
|
||||
var (
|
||||
TaikoMainnetNetworkID = big.NewInt(167000)
|
||||
TaikoInternalL2ANetworkID = big.NewInt(167001)
|
||||
TaikoInternalL2BNetworkID = big.NewInt(167002)
|
||||
SnaefellsjokullNetworkID = big.NewInt(167003)
|
||||
AskjaNetworkID = big.NewInt(167004)
|
||||
GrimsvotnNetworkID = big.NewInt(167005)
|
||||
EldfellNetworkID = big.NewInt(167006)
|
||||
JolnirNetworkID = big.NewInt(167007)
|
||||
KatlaNetworkID = big.NewInt(167008)
|
||||
HeklaNetworkID = big.NewInt(167009)
|
||||
)
|
||||
|
||||
var networkIDToChainConfig = map[*big.Int]*ChainConfig{
|
||||
TaikoMainnetNetworkID: TaikoChainConfig,
|
||||
TaikoInternalL2ANetworkID: TaikoChainConfig,
|
||||
TaikoInternalL2BNetworkID: TaikoChainConfig,
|
||||
SnaefellsjokullNetworkID: TaikoChainConfig,
|
||||
AskjaNetworkID: TaikoChainConfig,
|
||||
GrimsvotnNetworkID: TaikoChainConfig,
|
||||
EldfellNetworkID: TaikoChainConfig,
|
||||
JolnirNetworkID: TaikoChainConfig,
|
||||
KatlaNetworkID: TaikoChainConfig,
|
||||
HeklaNetworkID: TaikoChainConfig,
|
||||
MainnetChainConfig.ChainID: MainnetChainConfig,
|
||||
SepoliaChainConfig.ChainID: SepoliaChainConfig,
|
||||
GoerliChainConfig.ChainID: GoerliChainConfig,
|
||||
TestChainConfig.ChainID: TestChainConfig,
|
||||
NonActivatedConfig.ChainID: NonActivatedConfig,
|
||||
}
|
||||
|
||||
func NetworkIDToChainConfigOrDefault(networkID *big.Int) *ChainConfig {
|
||||
if config, ok := networkIDToChainConfig[networkID]; ok {
|
||||
return config
|
||||
}
|
||||
|
||||
return AllEthashProtocolChanges
|
||||
}
|
||||
|
||||
var TaikoChainConfig = &ChainConfig{
|
||||
ChainID: TaikoInternalL2ANetworkID, // Use Internal Devnet network ID by default.
|
||||
HomesteadBlock: common.Big0,
|
||||
EIP150Block: common.Big0,
|
||||
EIP155Block: common.Big0,
|
||||
EIP158Block: common.Big0,
|
||||
ByzantiumBlock: common.Big0,
|
||||
ConstantinopleBlock: common.Big0,
|
||||
PetersburgBlock: common.Big0,
|
||||
IstanbulBlock: common.Big0,
|
||||
BerlinBlock: common.Big0,
|
||||
LondonBlock: common.Big0,
|
||||
ShanghaiTime: u64(0),
|
||||
MergeNetsplitBlock: nil,
|
||||
TerminalTotalDifficulty: common.Big0,
|
||||
TerminalTotalDifficultyPassed: true,
|
||||
Taiko: true,
|
||||
}
|
||||
93
params/taiko_config_test.go
Normal file
93
params/taiko_config_test.go
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
package params
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNetworkIDToChainConfigOrDefault(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
networkID *big.Int
|
||||
wantChainConfig *ChainConfig
|
||||
}{
|
||||
{
|
||||
"taikoMainnetNetworkID",
|
||||
TaikoMainnetNetworkID,
|
||||
TaikoChainConfig,
|
||||
},
|
||||
{
|
||||
"taikoInternalL2ANetworkId",
|
||||
TaikoInternalL2ANetworkID,
|
||||
TaikoChainConfig,
|
||||
},
|
||||
{
|
||||
"taikoInternalL2BNetworkId",
|
||||
TaikoInternalL2BNetworkID,
|
||||
TaikoChainConfig,
|
||||
},
|
||||
{
|
||||
"snaefoll",
|
||||
SnaefellsjokullNetworkID,
|
||||
TaikoChainConfig,
|
||||
},
|
||||
{
|
||||
"askja",
|
||||
AskjaNetworkID,
|
||||
TaikoChainConfig,
|
||||
},
|
||||
{
|
||||
"grimsvotn",
|
||||
GrimsvotnNetworkID,
|
||||
TaikoChainConfig,
|
||||
},
|
||||
{
|
||||
"eldfellNetworkID",
|
||||
EldfellNetworkID,
|
||||
TaikoChainConfig,
|
||||
},
|
||||
{
|
||||
"jolnirNetworkID",
|
||||
JolnirNetworkID,
|
||||
TaikoChainConfig,
|
||||
},
|
||||
{
|
||||
"katlaNetworkID",
|
||||
KatlaNetworkID,
|
||||
TaikoChainConfig,
|
||||
},
|
||||
{
|
||||
"heklaNetworkID",
|
||||
HeklaNetworkID,
|
||||
TaikoChainConfig,
|
||||
},
|
||||
{
|
||||
"mainnet",
|
||||
MainnetChainConfig.ChainID,
|
||||
MainnetChainConfig,
|
||||
},
|
||||
{
|
||||
"sepolia",
|
||||
SepoliaChainConfig.ChainID,
|
||||
SepoliaChainConfig,
|
||||
},
|
||||
{
|
||||
"goerli",
|
||||
GoerliChainConfig.ChainID,
|
||||
GoerliChainConfig,
|
||||
},
|
||||
{
|
||||
"doesntExist",
|
||||
big.NewInt(89390218390),
|
||||
AllEthashProtocolChanges,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if config := NetworkIDToChainConfigOrDefault(tt.networkID); config != tt.wantChainConfig {
|
||||
t.Fatalf("expected %v, got %v", config, tt.wantChainConfig)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -28,6 +28,9 @@ import (
|
|||
const MetadataApi = "rpc"
|
||||
const EngineApi = "engine"
|
||||
|
||||
// CHANGE(taiko): add `taikoAuth` namespace for authenticated APIs
|
||||
const TaikoAuth = "taikoAuth"
|
||||
|
||||
// CodecOption specifies which type of messages a codec supports.
|
||||
//
|
||||
// Deprecated: this option is no longer honored by Server.
|
||||
|
|
|
|||
Loading…
Reference in a new issue