feat(beacon): introduce soft blocks (#342)

* feat(beacon): introduce soft blocks

* feat: update api.go

* chore(ci): update CI

* feat: update L1Origin

* feat: update `verifyHeader`

* test: update tests

* feat: update consensus

* feat: update consensus

* feat: update genesis

* feat: remove timestamp check in prepareWork

* feat: merge changes in #281

* Update eth/catalyst/api.go

Co-authored-by: maskpp <maskpp266@gmail.com>

* Update internal/ethapi/taiko_preconf.go

Co-authored-by: maskpp <maskpp266@gmail.com>

* fix consensus test

* revert commit f1df58

* fix consensus test (#349)

* Update eth/catalyst/api.go

Co-authored-by: maskpp <maskpp266@gmail.com>

* feat: add back timestamp check in worker

* add genesis

* temp fix for old l1origin

* nil value

* feat: rename to `L1OriginLegacy`

* feat: change `common.Big0` as the default value for legacy l1Origin, to make `IsSoftblock` return `false`

* feat(beacon): change the reorg log level (#350)

* use debug log level to avoid logging too many logs when frequently soft block reorg.

* use debug log level to avoid logging too many logs when frequently soft block reorg.

* feat: check --taiko flag

---------

Co-authored-by: David <david@taiko.xyz>

* add rlp optional flag (#353)

* fix lint

* fix test case

* feat(l1Origin): remove the reverted l1Origins (#355)

* remove the reverted l1Origins

* feat: add more comments

---------

Co-authored-by: David <david@taiko.xyz>

* only forward txs

* chore: update ci

---------

Co-authored-by: maskpp <maskpp266@gmail.com>
Co-authored-by: Jeffery Walsh <cyberhorsey@gmail.com>
This commit is contained in:
David 2025-01-07 16:25:44 +08:00 committed by GitHub
parent 5ef74219ab
commit a2cbf904ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 566 additions and 83 deletions

View file

@ -2,7 +2,7 @@ name: "Push multi-arch docker image to GAR"
on:
push:
branches: [ taiko ]
branches: [taiko]
tags:
- "v*"
@ -19,9 +19,9 @@ jobs:
platform: linux/amd64
- runner: arc-runner-set-arm64
platform: linux/arm64
runs-on: ${{ matrix.runner }}
steps:
- name: Prepare Environment
run: |
@ -80,7 +80,7 @@ jobs:
with:
context: .
cache-from: type=gha
cache-to: type=gha,mode=max
cache-to: type=gha,mode=max
platforms: ${{ matrix.platform }}
push: true
tags: ${{ env.REGISTRY_IMAGE }}

View file

@ -192,6 +192,11 @@ func makeFullNode(ctx *cli.Context) *node.Node {
cfg.Eth.OverrideVerkle = &v
}
// CHANGE(taiko): set preconfirmation forwarding URL.
if ctx.IsSet(utils.PreconfirmationForwardingURLFlag.Name) {
cfg.Eth.PreconfirmationForwardingURL = ctx.String(utils.PreconfirmationForwardingURLFlag.Name)
}
backend, eth := utils.RegisterEthService(stack, &cfg.Eth)
// CHANGE(TAIKO): register Taiko RPC APIs.

View file

@ -261,7 +261,7 @@ func init() {
metricsFlags,
)
// CHANGE(taiko): append Taiko flags into the original GETH flags
app.Flags = append(app.Flags, &utils.TaikoFlag)
app.Flags = append(app.Flags, utils.TaikoFlag, utils.PreconfirmationForwardingURLFlag)
flags.AutoEnvVars(app.Flags, "GETH")

View file

@ -5,6 +5,7 @@ import (
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
@ -12,9 +13,15 @@ import (
)
var (
TaikoFlag = cli.BoolFlag{
Name: "taiko",
Usage: "Taiko network",
TaikoFlag = &cli.BoolFlag{
Name: "taiko",
Usage: "Taiko network",
Category: flags.TaikoCategory,
}
PreconfirmationForwardingURLFlag = &cli.StringFlag{
Name: "taiko.preconfirmationForwardingUrl",
Usage: "URL to forward RPC requests before confirmation",
Category: flags.TaikoCategory,
}
)

View file

@ -10,10 +10,12 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
@ -40,12 +42,13 @@ var (
// Taiko is a consensus engine used by L2 rollup.
type Taiko struct {
chainConfig *params.ChainConfig
chainDB ethdb.Database
taikoL2Address common.Address
}
var _ = new(Taiko)
func New(chainConfig *params.ChainConfig) *Taiko {
func New(chainConfig *params.ChainConfig, chainDB ethdb.Database) *Taiko {
taikoL2AddressPrefix := strings.TrimPrefix(chainConfig.ChainID.String(), "0")
return &Taiko{
@ -56,6 +59,7 @@ func New(chainConfig *params.ChainConfig) *Taiko {
strings.Repeat("0", common.AddressLength*2-len(taikoL2AddressPrefix)-len(TaikoL2AddressSuffix)) +
TaikoL2AddressSuffix,
),
chainDB: chainDB,
}
}
@ -82,7 +86,7 @@ func (t *Taiko) VerifyHeader(chain consensus.ChainHeaderReader, header *types.He
return consensus.ErrUnknownAncestor
}
// Sanity checks passed, do a proper verification
return t.verifyHeader(chain, header, parent, time.Now().Unix())
return t.verifyHeader(header, parent, time.Now().Unix())
}
// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers
@ -109,7 +113,7 @@ func (t *Taiko) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*type
if parent == nil {
err = consensus.ErrUnknownAncestor
} else {
err = t.verifyHeader(chain, header, parent, unixNow)
err = t.verifyHeader(header, parent, unixNow)
}
select {
case <-abort:
@ -121,11 +125,7 @@ func (t *Taiko) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*type
return abort, results
}
func (t *Taiko) verifyHeader(chain consensus.ChainHeaderReader, header, parent *types.Header, unixNow int64) error {
if header.Time > uint64(unixNow) {
return consensus.ErrFutureBlock
}
func (t *Taiko) verifyHeader(header, parent *types.Header, unixNow int64) error {
// 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)
@ -171,6 +171,16 @@ func (t *Taiko) verifyHeader(chain consensus.ChainHeaderReader, header, parent *
return ErrEmptyWithdrawalsHash
}
l1Origin, err := rawdb.ReadL1Origin(t.chainDB, header.Number)
if err != nil {
return err
}
// If the current block is not a soft block, then check the timestamp.
if l1Origin != nil && !l1Origin.IsSoftBlock() && header.Time > uint64(unixNow) {
return consensus.ErrFutureBlock
}
return nil
}

View file

@ -39,7 +39,7 @@ func init() {
config.ArrowGlacierBlock = nil
config.Ethash = nil
config.Taiko = true
testEngine = taiko.New(config)
testEngine = taiko.New(config, rawdb.NewMemoryDatabase())
taikoL2AddressPrefix := strings.TrimPrefix(config.ChainID.String(), "0")
@ -93,44 +93,7 @@ func init() {
}
}
func newTestBackend(t *testing.T) (*eth.Ethereum, []*types.Block) {
// Generate test chain.
blocks := generateTestChain()
// Create node
n, err := node.New(&node.Config{})
if err != nil {
t.Fatalf("can't create new node: %v", err)
}
// Create Ethereum Service
config := &ethconfig.Config{
Genesis: genesis,
}
ethservice, err := eth.New(n, config)
if err != nil {
t.Fatalf("can't create new ethereum service: %v", err)
}
// Import the test chain.
if err := n.Start(); err != nil {
t.Fatalf("can't start test node: %v", err)
}
if _, err := ethservice.BlockChain().InsertChain(blocks[1:]); err != nil {
t.Fatalf("can't import test blocks: %v", err)
}
if _, ok := ethservice.Engine().(*taiko.Taiko); !ok {
t.Fatalf("not use taiko engine")
}
return ethservice, blocks
}
func generateTestChain() []*types.Block {
db := rawdb.NewMemoryDatabase()
func generateTestChain(t *testing.T) ([]*types.Block, *eth.Ethereum) {
generate := func(i int, g *core.BlockGen) {
g.OffsetTime(5)
@ -147,16 +110,56 @@ func generateTestChain() []*types.Block {
}
}
// Create node
n, err := node.New(&node.Config{
DataDir: t.TempDir(),
})
if err != nil {
t.Fatalf("can't create new node: %v", err)
}
// Create Ethereum Service
ethService, err := eth.New(n, &ethconfig.Config{
Genesis: genesis,
})
if err != nil {
t.Fatalf("can't create new ethereum service: %v", err)
}
db := ethService.ChainDb()
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
// Insert L1Origins.
for _, block := range blocks {
rawdb.WriteL1Origin(db, block.Number(), &rawdb.L1Origin{
BlockID: block.Number(),
L1BlockHeight: block.Number(),
L1BlockHash: block.Hash(),
})
}
// 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 blocks, ethService
}
func TestVerifyHeader(t *testing.T) {
ethService, blocks := newTestBackend(t)
// Generate test chain.
blocks, ethService := generateTestChain(t)
for _, b := range blocks {
err := testEngine.VerifyHeader(ethService.BlockChain(), b.Header())

View file

@ -2269,7 +2269,12 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error {
} else {
// len(newChain) == 0 && len(oldChain) > 0
// rewind the canonical chain to a lower point.
log.Error("Impossible reorg, please file an issue", "oldnum", oldBlock.Number(), "oldhash", oldBlock.Hash(), "oldblocks", len(oldChain), "newnum", newBlock.Number(), "newhash", newBlock.Hash(), "newblocks", len(newChain))
// CHANGE(taiko): use debug log level to avoid logging too many logs when frequently soft block rollback.
if bc.chainConfig.Taiko {
log.Debug("Impossible reorg, please file an issue", "oldnum", oldBlock.Number(), "oldhash", oldBlock.Hash(), "oldblocks", len(oldChain), "newnum", newBlock.Number(), "newhash", newBlock.Hash(), "newblocks", len(newChain))
} else {
log.Error("Impossible reorg, please file an issue", "oldnum", oldBlock.Number(), "oldhash", oldBlock.Hash(), "oldblocks", len(oldChain), "newnum", newBlock.Number(), "newhash", newBlock.Hash(), "newblocks", len(newChain))
}
}
// Acquire the tx-lookup lock before mutation. This step is essential
// as the txlookups should be changed atomically, and all subsequent

View file

@ -18,14 +18,22 @@ 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"`
L1BlockHeight *math.HexOrDecimal256 `json:"l1BlockHeight"`
L1BlockHash common.Hash `json:"l1BlockHash"`
BatchID *big.Int `json:"batchID"`
EndOfBlock bool `json:"endOfBlock"`
EndOfPreconf bool `json:"endOfPreconf"`
Preconfer common.Address `json:"preconfer"`
}
var enc L1Origin
enc.BlockID = (*math.HexOrDecimal256)(l.BlockID)
enc.L2BlockHash = l.L2BlockHash
enc.L1BlockHeight = (*math.HexOrDecimal256)(l.L1BlockHeight)
enc.L1BlockHash = l.L1BlockHash
enc.BatchID = l.BatchID
enc.EndOfBlock = l.EndOfBlock
enc.EndOfPreconf = l.EndOfPreconf
enc.Preconfer = l.Preconfer
return json.Marshal(&enc)
}
@ -34,8 +42,12 @@ 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"`
L1BlockHeight *math.HexOrDecimal256 `json:"l1BlockHeight"`
L1BlockHash *common.Hash `json:"l1BlockHash"`
BatchID *big.Int `json:"batchID"`
EndOfBlock *bool `json:"endOfBlock"`
EndOfPreconf *bool `json:"endOfPreconf"`
Preconfer *common.Address `json:"preconfer"`
}
var dec L1Origin
if err := json.Unmarshal(input, &dec); err != nil {
@ -48,13 +60,23 @@ func (l *L1Origin) UnmarshalJSON(input []byte) error {
if dec.L2BlockHash != nil {
l.L2BlockHash = *dec.L2BlockHash
}
if dec.L1BlockHeight == nil {
return errors.New("missing required field 'l1BlockHeight' for L1Origin")
if dec.L1BlockHeight != nil {
l.L1BlockHeight = (*big.Int)(dec.L1BlockHeight)
}
l.L1BlockHeight = (*big.Int)(dec.L1BlockHeight)
if dec.L1BlockHash == nil {
return errors.New("missing required field 'l1BlockHash' for L1Origin")
if dec.L1BlockHash != nil {
l.L1BlockHash = *dec.L1BlockHash
}
if dec.BatchID != nil {
l.BatchID = dec.BatchID
}
if dec.EndOfBlock != nil {
l.EndOfBlock = *dec.EndOfBlock
}
if dec.EndOfPreconf != nil {
l.EndOfPreconf = *dec.EndOfPreconf
}
if dec.Preconfer != nil {
l.Preconfer = *dec.Preconfer
}
l.L1BlockHash = *dec.L1BlockHash
return nil
}

View file

@ -29,10 +29,22 @@ func l1OriginKey(blockID *big.Int) []byte {
// 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"`
L1BlockHash common.Hash `json:"l1BlockHash"`
BatchID *big.Int `json:"batchID" rlp:"optional"`
EndOfBlock bool `json:"endOfBlock" rlp:"optional"`
EndOfPreconf bool `json:"endOfPreconf" rlp:"optional"`
Preconfer common.Address `json:"preconfer" rlp:"optional"`
}
// L1OriginLegacy represents the legacy L1Origin structure of a L2 block.
type L1OriginLegacy 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"`
L1BlockHeight *big.Int `json:"l1BlockHeight"`
L1BlockHash common.Hash `json:"l1BlockHash"`
}
type l1OriginMarshaling struct {
@ -40,6 +52,11 @@ type l1OriginMarshaling struct {
L1BlockHeight *math.HexOrDecimal256
}
// IsSoftBlock returns true if the L1Origin is a softblock.
func (l *L1Origin) IsSoftBlock() bool {
return l.BatchID != nil
}
// WriteL1Origin stores a L1Origin into the database.
func WriteL1Origin(db ethdb.KeyValueWriter, blockID *big.Int, l1Origin *L1Origin) {
data, err := rlp.EncodeToBytes(l1Origin)
@ -52,16 +69,40 @@ func WriteL1Origin(db ethdb.KeyValueWriter, blockID *big.Int, l1Origin *L1Origin
}
}
// ReadL1Origin retrieves the given L2 block's L1Origin from database.
// DeleteL1Origin removes the L1Origin.
func DeleteL1Origin(db ethdb.KeyValueWriter, blockID *big.Int) {
_ = db.Delete(l1OriginKey(blockID))
}
// ReadL1Origin retrieves the L1Origin of the given blockID from the database.
func ReadL1Origin(db ethdb.KeyValueReader, blockID *big.Int) (*L1Origin, error) {
data, _ := db.Get(l1OriginKey(blockID))
if len(data) == 0 {
return nil, nil
}
// First try to decode the new version (with new fields).
l1Origin := new(L1Origin)
if err := rlp.Decode(bytes.NewReader(data), l1Origin); err != nil {
return nil, fmt.Errorf("invalid L1Origin RLP bytes: %w", err)
// If decoding the new version fails, try to decode the legacy version (without new fields).
l1OriginLegacy := new(L1OriginLegacy)
if err := rlp.Decode(bytes.NewReader(data), &l1OriginLegacy); err != nil {
return nil, fmt.Errorf("invalid legacy L1Origin RLP bytes: %w", err)
}
// If decoding legacy version succeeds, manually
// construct the new L1Origin with default values for the new fields.
l1Origin = &L1Origin{
BlockID: l1OriginLegacy.BlockID,
L2BlockHash: l1OriginLegacy.L2BlockHash,
L1BlockHeight: l1OriginLegacy.L1BlockHeight,
L1BlockHash: l1OriginLegacy.L1BlockHash,
// default value, as the new fields are not present in the legacy version.
BatchID: common.Big0,
EndOfBlock: false,
EndOfPreconf: false,
Preconfer: common.Address{},
}
}
return l1Origin, nil

View file

@ -42,6 +42,7 @@ func TestL1Origin(t *testing.T) {
l1Origin, err := ReadL1Origin(db, testL1Origin.BlockID)
require.Nil(t, err)
require.NotNil(t, l1Origin)
assert.Equal(t, testL1Origin.BatchID, l1Origin.BatchID)
assert.Equal(t, testL1Origin.BlockID, l1Origin.BlockID)
assert.Equal(t, testL1Origin.L2BlockHash, l1Origin.L2BlockHash)
assert.Equal(t, testL1Origin.L1BlockHeight, l1Origin.L1BlockHeight)

View file

@ -10,7 +10,8 @@ import (
)
var (
InternalDevnetOntakeBlock = new(big.Int).SetUint64(0)
InternalDevnetOntakeBlock = common.Big0
PreconfDevnetOntakeBlock = common.Big0
HeklaOntakeBlock = new(big.Int).SetUint64(840_512)
MainnetOntakeBlock = new(big.Int).SetUint64(538_304)
)
@ -54,6 +55,10 @@ func TaikoGenesisBlock(networkID uint64) *Genesis {
chainConfig.ChainID = params.HeklaNetworkID
chainConfig.OntakeBlock = HeklaOntakeBlock
allocJSON = taikoGenesis.HeklaGenesisAllocJSON
case params.PreconfDevnetNetworkID.Uint64():
chainConfig.ChainID = params.PreconfDevnetNetworkID
chainConfig.OntakeBlock = PreconfDevnetOntakeBlock
allocJSON = taikoGenesis.PreconfDevnetGenesisAllocJSON
default:
chainConfig.ChainID = params.TaikoInternalL2ANetworkID
chainConfig.OntakeBlock = InternalDevnetOntakeBlock

View file

@ -33,3 +33,6 @@ var HeklaGenesisAllocJSON []byte
//go:embed mainnet.json
var MainnetGenesisAllocJSON []byte
//go:embed preconf_devnet.json
var PreconfDevnetGenesisAllocJSON []byte

File diff suppressed because one or more lines are too long

View file

@ -44,10 +44,11 @@ import (
// EthAPIBackend implements ethapi.Backend and tracers.Backend for full nodes
type EthAPIBackend struct {
extRPCEnabled bool
allowUnprotectedTxs bool
eth *Ethereum
gpo *gasprice.Oracle
extRPCEnabled bool
allowUnprotectedTxs bool
eth *Ethereum
gpo *gasprice.Oracle
preconfirmationForwardingURL string // CHANGE(taiko): add preconfirmation forwarding URL
}
// ChainConfig returns the active chain configuration.
@ -431,3 +432,8 @@ func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, re
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {
return b.eth.stateAtTransaction(ctx, block, txIndex, reexec)
}
// GetPreconfirmationForwardingURL returns the URL to forward RPC requests before confirmation.
func (b *EthAPIBackend) GetPreconfirmationForwardingURL() string {
return b.preconfirmationForwardingURL
}

View file

@ -96,6 +96,8 @@ type Ethereum struct {
lock sync.RWMutex // Protects the variadic fields (e.g. gas price and etherbase)
shutdownTracker *shutdowncheck.ShutdownTracker // Tracks if and when the node has shutdown ungracefully
PreconfirmationForwardingURL string // CHANGE(taiko): add preconfirmation forwarding URL
}
// New creates a new Ethereum object (including the initialisation of the common Ethereum object),
@ -254,7 +256,8 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
eth.miner = miner.New(eth, config.Miner, eth.engine)
eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))
eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil}
// CHANGE(taiko): set up the pre-confirmation forwarding URL
eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil, config.PreconfirmationForwardingURL}
if eth.APIBackend.allowUnprotectedTxs {
log.Info("Unprotected transactions allowed")
}

View file

@ -400,6 +400,11 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
// 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.
// After inserting a new canonical head, we delete all L1Origin data of the pending soft blocks.
head := api.eth.BlockChain().CurrentBlock()
for number := head.Number.Uint64(); number > block.NumberU64(); number-- {
rawdb.DeleteL1Origin(api.eth.ChainDb(), new(big.Int).SetUint64(number))
}
if latestValid, err := api.eth.BlockChain().SetCanonical(block); err != nil {
return engine.ForkChoiceResponse{PayloadStatus: engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: &latestValid}}, err
}
@ -446,6 +451,17 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
if payloadAttributes != nil {
// CHANGE(taiko): create a L2 block by Taiko protocol.
if isTaiko {
// L1Origin is a required field in PayloadAttributesV1.
if payloadAttributes.L1Origin == nil {
return valid(nil), engine.InvalidPayloadAttributes.With(errors.New("L1Origin is nil"))
}
// If it's not a softblock creation request, then `L1BlockHash` and `L1BlockHeight` are required.
if payloadAttributes.L1Origin.BatchID == nil &&
(payloadAttributes.L1Origin.L1BlockHash == (common.Hash{}) ||
payloadAttributes.L1Origin.L1BlockHeight == nil) {
return valid(nil), engine.InvalidPayloadAttributes.With(errors.New("L1BlockHash or L1BlockHeight is nil"))
}
// No need to check payloadAttribute here, because all its fields are
// marked as required.
block, err := api.eth.Miner().SealBlockWith(
@ -488,8 +504,10 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
// Write L1Origin.
rawdb.WriteL1Origin(api.eth.ChainDb(), l1Origin.BlockID, l1Origin)
// Write the head L1Origin.
rawdb.WriteHeadL1Origin(api.eth.ChainDb(), l1Origin.BlockID)
// Write the head L1Origin, only when it's not a soft block.
if l1Origin.BatchID == nil {
rawdb.WriteHeadL1Origin(api.eth.ChainDb(), l1Origin.BlockID)
}
return valid(&id), nil
}

View file

@ -157,6 +157,9 @@ type Config struct {
// OverrideVerkle (TODO: remove after the fork)
OverrideVerkle *uint64 `toml:",omitempty"`
// CHANGE(taiko): add preconfirmation forwarding URL
PreconfirmationForwardingURL string
}
// CreateConsensusEngine creates a consensus engine for the given chain config.
@ -165,7 +168,7 @@ type Config struct {
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
return taiko.New(config, db), nil
}
// Geth v1.14.0 dropped support for non-merged networks in any consensus
// mode. If such a network is requested, reject startup.

View file

@ -69,6 +69,10 @@ func TestHeadL1Origin(t *testing.T) {
L2BlockHash: headerHash,
L1BlockHeight: randomBigInt(),
L1BlockHash: randomHash(),
BatchID: nil,
EndOfBlock: false,
EndOfPreconf: false,
Preconfer: common.Address{},
}
rawdb.WriteL1Origin(db, testL1Origin.BlockID, testL1Origin)
@ -78,6 +82,7 @@ func TestHeadL1Origin(t *testing.T) {
require.Nil(t, err)
require.Equal(t, testL1Origin, l1OriginFound)
require.False(t, l1OriginFound.IsSoftBlock())
}
func TestL1OriginByID(t *testing.T) {
@ -89,6 +94,10 @@ func TestL1OriginByID(t *testing.T) {
L2BlockHash: headerHash,
L1BlockHeight: randomBigInt(),
L1BlockHash: randomHash(),
BatchID: common.Big1,
EndOfBlock: false,
EndOfPreconf: false,
Preconfer: testAddr,
}
l1OriginFound, err := ec.L1OriginByID(context.Background(), testL1Origin.BlockID)
@ -102,6 +111,7 @@ func TestL1OriginByID(t *testing.T) {
require.Nil(t, err)
require.Equal(t, testL1Origin, l1OriginFound)
require.True(t, l1OriginFound.IsSoftBlock())
}
// randomHash generates a random blob of data and returns it as a hash.

View file

@ -1974,6 +1974,18 @@ func (api *TransactionAPI) FillTransaction(ctx context.Context, args Transaction
// SendRawTransaction will add the signed transaction to the transaction pool.
// The sender is responsible for signing the transaction and using the correct nonce.
func (api *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error) {
// CHANGE(taiko): Forward the request to the preconf node if specified.
if forwardURL := api.b.GetPreconfirmationForwardingURL(); forwardURL != "" {
log.Info("Forwarding SendRawTransaction request", "forwardURL", forwardURL)
// Forward the raw transaction to the specified URL
h, err := forward[string](forwardURL, "eth_sendRawTransaction", []interface{}{input.String()})
if err == nil && h != nil {
return common.HexToHash(*h), nil
} else {
return common.Hash{}, err
}
}
tx := new(types.Transaction)
if err := tx.UnmarshalBinary(input); err != nil {
return common.Hash{}, err

View file

@ -3400,3 +3400,8 @@ func testRPCResponseWithFile(t *testing.T, testid int, result interface{}, rpc s
func addressToHash(a common.Address) common.Hash {
return common.BytesToHash(a.Bytes())
}
// CHANGE(taiko): add preconfirmation forwarding URL
func (b testBackend) GetPreconfirmationForwardingURL() string {
return ""
}

View file

@ -96,6 +96,9 @@ type Backend interface {
SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription
BloomStatus() (uint64, uint64)
ServiceFilter(ctx context.Context, session *bloombits.MatcherSession)
// CHANGE(taiko): add preconfirmation forwarding URL
GetPreconfirmationForwardingURL() string
}
func GetAPIs(apiBackend Backend) []rpc.API {

View file

@ -0,0 +1,94 @@
package ethapi
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/ethereum/go-ethereum/log"
)
type rpcRequest struct {
Jsonrpc string `json:"jsonrpc"`
Method string `json:"method"`
Params []interface{} `json:"params"`
ID int `json:"id"`
}
type rpcResponse struct {
Jsonrpc string `json:"jsonrpc"`
ID int `json:"id"`
Result *json.RawMessage `json:"result"`
Error *struct {
Code int `json:"code"`
Message string `json:"message"`
Data string `json:"data"`
} `json:"error,omitempty"`
}
func forward[T any](forwardURL string, method string, params []interface{}) (*T, error) {
rpcReq := rpcRequest{
Jsonrpc: "2.0",
Method: method,
Params: params,
ID: 1,
}
jsonData, err := json.Marshal(rpcReq)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", forwardURL, bytes.NewBuffer(jsonData))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to forward transaction, status code: %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var rpcResp rpcResponse
// Unmarshal the response into the struct
if err := json.Unmarshal(body, &rpcResp); err != nil {
return nil, err
}
// Check for errors in the response
if rpcResp.Error != nil {
err := fmt.Errorf("RPC error %d: %s", rpcResp.Error.Code, rpcResp.Error.Message)
log.Error("forwarded request error", "err", err, "method", method, "params", params)
return nil, fmt.Errorf("RPC error %d: %s", rpcResp.Error.Code, rpcResp.Error.Message)
}
if rpcResp.Result == nil {
log.Warn("forwarded request result is nil", "method", method)
return nil, nil
}
// Unmarshal the Result into the desired type
var result T
if err := json.Unmarshal(*rpcResp.Result, &result); err != nil {
return nil, err
}
return &result, nil
}

View file

@ -405,3 +405,8 @@ func (b *backendMock) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent)
}
func (b *backendMock) Engine() consensus.Engine { return nil }
// CHANGE(taiko): add preconfirmation forwarding URL
func (b *backendMock) GetPreconfirmationForwardingURL() string {
return ""
}

View file

@ -37,6 +37,8 @@ const (
MiscCategory = "MISC"
TestingCategory = "TESTING"
DeprecatedCategory = "ALIASED (deprecated)"
// CHANGE(taiko): Add a new flag category for Taiko network
TaikoCategory = "TAIKO"
)
func init() {

View file

@ -300,6 +300,7 @@ var NetworkNames = map[string]string{
JolnirNetworkID.String(): "Taiko Alpha-5 L2 (Jolnir)",
KatlaNetworkID.String(): "Taiko Alpha-6 L2 (Katla)",
HeklaNetworkID.String(): "Taiko Alpha-7 L2 (Hekla)",
PreconfDevnetNetworkID.String(): "Taiko Preconfirmation Devnet",
}
// ChainConfig is the core config which determines the blockchain settings.

View file

@ -20,6 +20,7 @@ var (
JolnirNetworkID = big.NewInt(167007)
KatlaNetworkID = big.NewInt(167008)
HeklaNetworkID = big.NewInt(167009)
PreconfDevnetNetworkID = big.NewInt(167010)
)
var networkIDToChainConfig = map[*big.Int]*ChainConfig{
@ -33,6 +34,7 @@ var networkIDToChainConfig = map[*big.Int]*ChainConfig{
JolnirNetworkID: TaikoChainConfig,
KatlaNetworkID: TaikoChainConfig,
HeklaNetworkID: TaikoChainConfig,
PreconfDevnetNetworkID: TaikoChainConfig,
MainnetChainConfig.ChainID: MainnetChainConfig,
SepoliaChainConfig.ChainID: SepoliaChainConfig,
TestChainConfig.ChainID: TestChainConfig,