From 665dbf2baecc5719fb330e2746453194ef6f92dc Mon Sep 17 00:00:00 2001 From: Ahmad Bitar Date: Tue, 5 Nov 2024 09:39:58 +0300 Subject: [PATCH 1/4] feat: add synthetic block generation for L2 chain syncer --- .../driver/chain_syncer/chain_syncer.go | 34 +++++ packages/taiko-client/driver/config.go | 7 ++ packages/taiko-client/driver/driver.go | 22 +++- .../driver/synthetic_block_generator.go | 116 ++++++++++++++++++ 4 files changed, 174 insertions(+), 5 deletions(-) create mode 100644 packages/taiko-client/driver/synthetic_block_generator.go diff --git a/packages/taiko-client/driver/chain_syncer/chain_syncer.go b/packages/taiko-client/driver/chain_syncer/chain_syncer.go index 6e17215d73d..75b930b2eae 100644 --- a/packages/taiko-client/driver/chain_syncer/chain_syncer.go +++ b/packages/taiko-client/driver/chain_syncer/chain_syncer.go @@ -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" @@ -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. @@ -84,6 +92,28 @@ func New( // Sync performs a sync operation to L2 execution engine's local chain. func (s *L2ChainSyncer) Sync() error { + if s.blockGenerator != nil { + // Generate synthetic blocks + parent, err := s.rpc.L2.HeaderByNumber(s.ctx, nil) + if err != nil { + return err + } + + execData := s.blockGenerator.GenerateBlock(parent) + + // Insert block directly via Engine API + status, err := s.rpc.L2Engine.NewPayload(s.ctx, execData) + if err != nil { + return fmt.Errorf("failed to insert synthetic block: %w", err) + } + + if status.Status != "VALID" { + return fmt.Errorf("invalid synthetic block: %s", status.Status) + } + + return nil + } + blockIDToSync, needNewBeaconSyncTriggered, err := s.needNewBeaconSyncTriggered() if err != nil { return err @@ -219,3 +249,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 +} diff --git a/packages/taiko-client/driver/config.go b/packages/taiko-client/driver/config.go index 3eb3b95e236..a5b609543f6 100644 --- a/packages/taiko-client/driver/config.go +++ b/packages/taiko-client/driver/config.go @@ -1,6 +1,7 @@ package driver import ( + "crypto/ecdsa" "errors" "fmt" "net/url" @@ -23,6 +24,12 @@ type Config struct { MaxExponent uint64 BlobServerEndpoint *url.URL SocialScanEndpoint *url.URL + SyntheticBlocks struct { + Enabled bool + BlockTime time.Duration + NumAccounts int + InitialKey *ecdsa.PrivateKey + } } // NewConfigFromCliContext creates a new config instance from diff --git a/packages/taiko-client/driver/driver.go b/packages/taiko-client/driver/driver.go index 51967df1d93..28875d5aae8 100644 --- a/packages/taiko-client/driver/driver.go +++ b/packages/taiko-client/driver/driver.go @@ -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. @@ -89,6 +90,17 @@ 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, + ) + + // Pass generator to chain syncer + d.l2ChainSyncer.SetBlockGenerator(d.syntheticBlockGen) + } + return nil } diff --git a/packages/taiko-client/driver/synthetic_block_generator.go b/packages/taiko-client/driver/synthetic_block_generator.go new file mode 100644 index 00000000000..3d6089a5b9e --- /dev/null +++ b/packages/taiko-client/driver/synthetic_block_generator.go @@ -0,0 +1,116 @@ +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" +) + +type SyntheticBlockGenerator struct { + blockTime time.Duration + lastBlock *types.Header + accounts []*ecdsa.PrivateKey // Store multiple private keys + initialKey *ecdsa.PrivateKey // Store initial key +} + +func NewSyntheticBlockGenerator(blockTime time.Duration, numAccounts int, initialKey *ecdsa.PrivateKey) *SyntheticBlockGenerator { + accounts := make([]*ecdsa.PrivateKey, numAccounts) + accounts[0] = initialKey // Use provided initial key + + // Generate remaining accounts + for i := 1; i < numAccounts; i++ { + key, _ := crypto.GenerateKey() + accounts[i] = key + } + return &SyntheticBlockGenerator{ + blockTime: blockTime, + accounts: accounts, + initialKey: initialKey, + } +} + +// 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) + 21000, // gas limit (standard transfer) + big.NewInt(1000000000), // gas price (1 gwei) + 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 + 21000, // gas limit + big.NewInt(1000000000), // gas price (1 gwei) + nil, // data + ) + signedTx, _ := types.SignTx(tx, types.NewEIP155Signer(big.NewInt(1)), fromKey) + txBytes, _ := signedTx.MarshalBinary() + return txBytes +} + +func (g *SyntheticBlockGenerator) generateTransactions() [][]byte { + var transactions [][]byte + transferAmount := big.NewInt(1e18) // 1 ETH + + for i, account := range g.accounts { + // Generate 126 self-transfers (nonce 0-125) + for nonce := uint64(0); nonce < 126; nonce++ { + transactions = append(transactions, createSelfTransferTx(nonce, account)) + } + + // Transfer to next account with nonce 126 + nextIdx := (i + 1) % len(g.accounts) + transactions = append(transactions, createTransferToNextTx(126, account, g.accounts[nextIdx], 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: 30000000, + GasUsed: 21000 * uint64(len(transactions)), // Update gas used (21000 per transaction) + Timestamp: timestamp, + ExtraData: []byte{}, + BaseFeePerGas: big.NewInt(1000000000), // 1 gwei + BlockHash: common.Hash{}, + Transactions: transactions, + } +} + +// Usage example: +// initialKey, _ := crypto.HexToECDSA("your_private_key_hex") +// generator := NewSyntheticBlockGenerator(time.Second, 8, initialKey) From f0c584b2b104495079191dcc004a54541e3b2327 Mon Sep 17 00:00:00 2001 From: Ahmad Bitar Date: Tue, 5 Nov 2024 09:43:36 +0300 Subject: [PATCH 2/4] 1 wei instead of gwei --- .../driver/synthetic_block_generator.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/taiko-client/driver/synthetic_block_generator.go b/packages/taiko-client/driver/synthetic_block_generator.go index 3d6089a5b9e..fe3d37a2fdf 100644 --- a/packages/taiko-client/driver/synthetic_block_generator.go +++ b/packages/taiko-client/driver/synthetic_block_generator.go @@ -55,12 +55,12 @@ func createSelfTransferTx(nonce uint64, privateKey *ecdsa.PrivateKey) []byte { 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 - 21000, // gas limit - big.NewInt(1000000000), // gas price (1 gwei) - nil, // data + nonce, // nonce (will be 127) + toAddr, // to (next account) + value, // transfer amount + 21000, // gas limit + big.NewInt(1), // gas price (1 wei) + nil, // data ) signedTx, _ := types.SignTx(tx, types.NewEIP155Signer(big.NewInt(1)), fromKey) txBytes, _ := signedTx.MarshalBinary() @@ -105,7 +105,7 @@ func (g *SyntheticBlockGenerator) GenerateBlock(parent *types.Header) *engine.Ex GasUsed: 21000 * uint64(len(transactions)), // Update gas used (21000 per transaction) Timestamp: timestamp, ExtraData: []byte{}, - BaseFeePerGas: big.NewInt(1000000000), // 1 gwei + BaseFeePerGas: big.NewInt(1), // 1 wei BlockHash: common.Hash{}, Transactions: transactions, } From 0d277e20d3afdf6ac2dadd89efaf6d8cf6409cd5 Mon Sep 17 00:00:00 2001 From: Ahmad Bitar Date: Tue, 5 Nov 2024 09:55:41 +0300 Subject: [PATCH 3/4] repeat pushing --- .../driver/chain_syncer/chain_syncer.go | 49 ++++++++++++------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/packages/taiko-client/driver/chain_syncer/chain_syncer.go b/packages/taiko-client/driver/chain_syncer/chain_syncer.go index 75b930b2eae..4f64b8f1fa3 100644 --- a/packages/taiko-client/driver/chain_syncer/chain_syncer.go +++ b/packages/taiko-client/driver/chain_syncer/chain_syncer.go @@ -93,23 +93,38 @@ func New( // Sync performs a sync operation to L2 execution engine's local chain. func (s *L2ChainSyncer) Sync() error { if s.blockGenerator != nil { - // Generate synthetic blocks - parent, err := s.rpc.L2.HeaderByNumber(s.ctx, nil) - if err != nil { - return err - } - - execData := s.blockGenerator.GenerateBlock(parent) - - // Insert block directly via Engine API - status, err := s.rpc.L2Engine.NewPayload(s.ctx, execData) - if err != nil { - return fmt.Errorf("failed to insert synthetic block: %w", err) - } - - if status.Status != "VALID" { - return fmt.Errorf("invalid synthetic block: %s", status.Status) - } + 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 } From b64b0e588c868ef63371c01d47c132d5097d2cee Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Tue, 5 Nov 2024 08:29:53 +0000 Subject: [PATCH 4/4] Better cycling --- packages/taiko-client/driver/config.go | 9 ++- packages/taiko-client/driver/driver.go | 1 + .../driver/synthetic_block_generator.go | 75 +++++++++++++------ 3 files changed, 57 insertions(+), 28 deletions(-) diff --git a/packages/taiko-client/driver/config.go b/packages/taiko-client/driver/config.go index a5b609543f6..19a3ccaf590 100644 --- a/packages/taiko-client/driver/config.go +++ b/packages/taiko-client/driver/config.go @@ -25,10 +25,11 @@ type Config struct { BlobServerEndpoint *url.URL SocialScanEndpoint *url.URL SyntheticBlocks struct { - Enabled bool - BlockTime time.Duration - NumAccounts int - InitialKey *ecdsa.PrivateKey + Enabled bool + BlockTime time.Duration + NumAccounts int + InitialKey *ecdsa.PrivateKey + InitialNonce uint64 } } diff --git a/packages/taiko-client/driver/driver.go b/packages/taiko-client/driver/driver.go index 28875d5aae8..a53c6470d63 100644 --- a/packages/taiko-client/driver/driver.go +++ b/packages/taiko-client/driver/driver.go @@ -95,6 +95,7 @@ func (d *Driver) InitFromConfig(ctx context.Context, cfg *Config) (err error) { cfg.SyntheticBlocks.BlockTime, cfg.SyntheticBlocks.NumAccounts, cfg.SyntheticBlocks.InitialKey, + cfg.SyntheticBlocks.InitialNonce, ) // Pass generator to chain syncer diff --git a/packages/taiko-client/driver/synthetic_block_generator.go b/packages/taiko-client/driver/synthetic_block_generator.go index fe3d37a2fdf..500e57ee5f9 100644 --- a/packages/taiko-client/driver/synthetic_block_generator.go +++ b/packages/taiko-client/driver/synthetic_block_generator.go @@ -11,26 +11,26 @@ import ( "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) *SyntheticBlockGenerator { +func NewSyntheticBlockGenerator(blockTime time.Duration, numAccounts int, initialKey *ecdsa.PrivateKey, initialNonce uint64) *SyntheticBlockGenerator { accounts := make([]*ecdsa.PrivateKey, numAccounts) - accounts[0] = initialKey // Use provided initial key - // Generate remaining accounts - for i := 1; i < numAccounts; i++ { - key, _ := crypto.GenerateKey() - accounts[i] = key - } return &SyntheticBlockGenerator{ blockTime: blockTime, accounts: accounts, initialKey: initialKey, + nonce: initialNonce, } } @@ -38,12 +38,12 @@ func NewSyntheticBlockGenerator(blockTime time.Duration, numAccounts int, initia 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) - 21000, // gas limit (standard transfer) - big.NewInt(1000000000), // gas price (1 gwei) - nil, // data + 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) @@ -58,7 +58,7 @@ func createTransferToNextTx(nonce uint64, fromKey *ecdsa.PrivateKey, toKey *ecds nonce, // nonce (will be 127) toAddr, // to (next account) value, // transfer amount - 21000, // gas limit + txGasLimit, // gas limit big.NewInt(1), // gas price (1 wei) nil, // data ) @@ -68,20 +68,47 @@ func createTransferToNextTx(nonce uint64, fromKey *ecdsa.PrivateKey, toKey *ecds } func (g *SyntheticBlockGenerator) generateTransactions() [][]byte { - var transactions [][]byte - transferAmount := big.NewInt(1e18) // 1 ETH + // Generate accounts + for i := 0; i < len(g.accounts); i++ { + key, _ := crypto.GenerateKey() + g.accounts[i] = key + } - for i, account := range g.accounts { - // Generate 126 self-transfers (nonce 0-125) - for nonce := uint64(0); nonce < 126; nonce++ { - transactions = append(transactions, createSelfTransferTx(nonce, account)) + 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)) } - // Transfer to next account with nonce 126 + 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(126, account, g.accounts[nextIdx], transferAmount)) + 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 } @@ -101,8 +128,8 @@ func (g *SyntheticBlockGenerator) GenerateBlock(parent *types.Header) *engine.Ex LogsBloom: types.Bloom{}.Bytes(), Random: common.Hash{}, Number: new(big.Int).Add(parent.Number, common.Big1).Uint64(), - GasLimit: 30000000, - GasUsed: 21000 * uint64(len(transactions)), // Update gas used (21000 per transaction) + GasLimit: blockGasLimit, + GasUsed: txGasLimit * uint64(len(transactions)), // Update gas used (21000 per transaction) Timestamp: timestamp, ExtraData: []byte{}, BaseFeePerGas: big.NewInt(1), // 1 wei