Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add synthetic block generation for L2 chain syncer #21

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions packages/taiko-client/driver/chain_syncer/chain_syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"time"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/beacon/engine"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/log"

Expand Down Expand Up @@ -36,6 +38,12 @@ type L2ChainSyncer struct {
// If this flag is activated, will try P2P beacon sync if current node is behind of the protocol's
// the latest verified block head
p2pSync bool

blockGenerator BlockGenerator
}

type BlockGenerator interface {
GenerateBlock(parent *types.Header) *engine.ExecutableData
}

// New creates a new chain syncer instance.
Expand Down Expand Up @@ -84,6 +92,43 @@ func New(

// Sync performs a sync operation to L2 execution engine's local chain.
func (s *L2ChainSyncer) Sync() error {
if s.blockGenerator != nil {
ticker := time.NewTicker(200 * time.Millisecond)
defer ticker.Stop()

go func() {
for {
select {
case <-s.ctx.Done():
return
case <-ticker.C:
// Generate synthetic blocks
parent, err := s.rpc.L2.HeaderByNumber(s.ctx, nil)
if err != nil {
log.Error("Failed to get parent header", "error", err)
continue
}

execData := s.blockGenerator.GenerateBlock(parent)

// Insert block directly via Engine API
status, err := s.rpc.L2Engine.NewPayload(s.ctx, execData)
if err != nil {
log.Error("Failed to insert synthetic block", "error", err)
continue
}

if status.Status != "VALID" {
log.Error("Invalid synthetic block", "status", status.Status)
continue
}
}
}
}()

return nil
}

blockIDToSync, needNewBeaconSyncTriggered, err := s.needNewBeaconSyncTriggered()
if err != nil {
return err
Expand Down Expand Up @@ -219,3 +264,7 @@ func (s *L2ChainSyncer) BeaconSyncer() *beaconsync.Syncer {
func (s *L2ChainSyncer) BlobSyncer() *blob.Syncer {
return s.blobSyncer
}

func (s *L2ChainSyncer) SetBlockGenerator(gen BlockGenerator) {
s.blockGenerator = gen
}
8 changes: 8 additions & 0 deletions packages/taiko-client/driver/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package driver

import (
"crypto/ecdsa"
"errors"
"fmt"
"net/url"
Expand All @@ -23,6 +24,13 @@ type Config struct {
MaxExponent uint64
BlobServerEndpoint *url.URL
SocialScanEndpoint *url.URL
SyntheticBlocks struct {
Enabled bool
BlockTime time.Duration
NumAccounts int
InitialKey *ecdsa.PrivateKey
InitialNonce uint64
}
}

// NewConfigFromCliContext creates a new config instance from
Expand Down
23 changes: 18 additions & 5 deletions packages/taiko-client/driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@ type Driver struct {
rpc *rpc.Client
l2ChainSyncer *chainSyncer.L2ChainSyncer
state *state.State
l1HeadCh chan *types.Header
l1HeadSub event.Subscription
ctx context.Context
wg sync.WaitGroup

l1HeadCh chan *types.Header
l1HeadSub event.Subscription

ctx context.Context
wg sync.WaitGroup
// Add synthetic block generator
syntheticBlockGen *SyntheticBlockGenerator
}

// InitFromCli initializes the given driver instance based on the command line flags.
Expand Down Expand Up @@ -89,6 +90,18 @@ func (d *Driver) InitFromConfig(ctx context.Context, cfg *Config) (err error) {

d.l1HeadSub = d.state.SubL1HeadsFeed(d.l1HeadCh)

if cfg.SyntheticBlocks.Enabled {
d.syntheticBlockGen = NewSyntheticBlockGenerator(
cfg.SyntheticBlocks.BlockTime,
cfg.SyntheticBlocks.NumAccounts,
cfg.SyntheticBlocks.InitialKey,
cfg.SyntheticBlocks.InitialNonce,
)

// Pass generator to chain syncer
d.l2ChainSyncer.SetBlockGenerator(d.syntheticBlockGen)
}

return nil
}

Expand Down
143 changes: 143 additions & 0 deletions packages/taiko-client/driver/synthetic_block_generator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package driver

import (
"crypto/ecdsa"
"math/big"
"time"

"github.com/ethereum/go-ethereum/beacon/engine"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
)

const blockGasLimit = 60000000
const blockGasTarget = blockGasLimit / 2
const txGasLimit = 21000

type SyntheticBlockGenerator struct {
blockTime time.Duration
lastBlock *types.Header
accounts []*ecdsa.PrivateKey // Store multiple private keys
initialKey *ecdsa.PrivateKey // Store initial key
nonce uint64
}

func NewSyntheticBlockGenerator(blockTime time.Duration, numAccounts int, initialKey *ecdsa.PrivateKey, initialNonce uint64) *SyntheticBlockGenerator {
accounts := make([]*ecdsa.PrivateKey, numAccounts)

return &SyntheticBlockGenerator{
blockTime: blockTime,
accounts: accounts,
initialKey: initialKey,
nonce: initialNonce,
}
}

// Helper function to create a self-transfer transaction
func createSelfTransferTx(nonce uint64, privateKey *ecdsa.PrivateKey) []byte {
account := crypto.PubkeyToAddress(privateKey.PublicKey)
tx := types.NewTransaction(
nonce, // nonce
account, // to (same as sender)
big.NewInt(0), // value (0 ETH)
txGasLimit, // gas limit (standard transfer)
big.NewInt(1), // gas price (1 wei)
nil, // data
)

signedTx, _ := types.SignTx(tx, types.NewEIP155Signer(big.NewInt(1)), privateKey)
txBytes, _ := signedTx.MarshalBinary()
return txBytes
}

// Helper function to create a transfer to next account
func createTransferToNextTx(nonce uint64, fromKey *ecdsa.PrivateKey, toKey *ecdsa.PrivateKey, value *big.Int) []byte {
toAddr := crypto.PubkeyToAddress(toKey.PublicKey)
tx := types.NewTransaction(
nonce, // nonce (will be 127)
toAddr, // to (next account)
value, // transfer amount
txGasLimit, // gas limit
big.NewInt(1), // gas price (1 wei)
nil, // data
)
signedTx, _ := types.SignTx(tx, types.NewEIP155Signer(big.NewInt(1)), fromKey)
txBytes, _ := signedTx.MarshalBinary()
return txBytes
}

func (g *SyntheticBlockGenerator) generateTransactions() [][]byte {
// Generate accounts
for i := 0; i < len(g.accounts); i++ {
key, _ := crypto.GenerateKey()
g.accounts[i] = key
}

var transactions [][]byte
transferAmount := big.NewInt(1e17) // 0.1 ETH

availableGas := blockGasTarget - txGasLimit*2

// initial funding transfer
lastAccount := g.accounts[0]
transactions = append(transactions, createTransferToNextTx(g.nonce, g.initialKey, lastAccount, transferAmount))
g.nonce++

lastNonce := uint64(0)
i := 0
for i, lastAccount = range g.accounts {
// Generate 126 self-transfers (nonce 0-126)
for ; lastNonce < 127; lastNonce++ {
if availableGas-txGasLimit < 0 {
break
}
transactions = append(transactions, createSelfTransferTx(lastNonce, lastAccount))
}

transferAmount.Sub(transferAmount, big.NewInt(int64(lastNonce+1)*21000))

// Transfer to next account with nonce 127
if availableGas-txGasLimit < 0 {
break
}
nextIdx := (i + 1) % len(g.accounts)
transactions = append(transactions, createTransferToNextTx(lastNonce, lastAccount, g.accounts[nextIdx], transferAmount))
lastNonce = 0
}

// Transfer remaining back to initial account
transactions = append(transactions, createTransferToNextTx(lastNonce, g.accounts[(i)%len(g.accounts)], lastAccount, transferAmount))

return transactions
}

func (g *SyntheticBlockGenerator) GenerateBlock(parent *types.Header) *engine.ExecutableData {
timestamp := uint64(time.Now().Unix())
if parent != nil && timestamp < parent.Time {
timestamp = parent.Time + uint64(g.blockTime.Seconds())
}

transactions := g.generateTransactions()

return &engine.ExecutableData{
ParentHash: parent.Hash(),
FeeRecipient: common.Address{},
StateRoot: common.Hash{}, // Empty state root
ReceiptsRoot: common.Hash{}, // Empty receipts root
LogsBloom: types.Bloom{}.Bytes(),
Random: common.Hash{},
Number: new(big.Int).Add(parent.Number, common.Big1).Uint64(),
GasLimit: blockGasLimit,
GasUsed: txGasLimit * uint64(len(transactions)), // Update gas used (21000 per transaction)
Timestamp: timestamp,
ExtraData: []byte{},
BaseFeePerGas: big.NewInt(1), // 1 wei
BlockHash: common.Hash{},
Transactions: transactions,
}
}

// Usage example:
// initialKey, _ := crypto.HexToECDSA("your_private_key_hex")
// generator := NewSyntheticBlockGenerator(time.Second, 8, initialKey)
Loading