mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-27 23:26:44 +00:00
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:
parent
5ef74219ab
commit
a2cbf904ea
26 changed files with 566 additions and 83 deletions
8
.github/workflows/docker.yml
vendored
8
.github/workflows/docker.yml
vendored
|
|
@ -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 }}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 := ð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()
|
||||
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, ðconfig.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())
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -33,3 +33,6 @@ var HeklaGenesisAllocJSON []byte
|
|||
|
||||
//go:embed mainnet.json
|
||||
var MainnetGenesisAllocJSON []byte
|
||||
|
||||
//go:embed preconf_devnet.json
|
||||
var PreconfDevnetGenesisAllocJSON []byte
|
||||
|
|
|
|||
217
core/taiko_genesis/preconf_devnet.json
Normal file
217
core/taiko_genesis/preconf_devnet.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
94
internal/ethapi/taiko_preconf.go
Normal file
94
internal/ethapi/taiko_preconf.go
Normal 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
|
||||
}
|
||||
|
|
@ -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 ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue