From b68dcb712300706e8a83cdaae86e9892c9eeb19f Mon Sep 17 00:00:00 2001 From: Morty Date: Mon, 16 Dec 2024 03:33:05 +0800 Subject: [PATCH 01/36] feat: add system config consensus to deprecate poa --- cmd/faucet/faucet.go | 2 +- cmd/utils/flags.go | 20 +- consensus/system_contract/api.go | 18 + consensus/system_contract/consensus.go | 371 +++++++++++++++++++ consensus/system_contract/system_contract.go | 98 +++++ eth/backend.go | 10 +- eth/ethconfig/config.go | 8 +- les/api_test.go | 2 +- les/client.go | 5 +- mobile/geth.go | 2 +- params/config.go | 20 +- rollup/rollup_sync_service/l1client_test.go | 5 + rollup/sync_service/types.go | 1 + 13 files changed, 543 insertions(+), 19 deletions(-) create mode 100644 consensus/system_contract/api.go create mode 100644 consensus/system_contract/consensus.go create mode 100644 consensus/system_contract/system_contract.go diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index 6290347b4e3e..970919215d09 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -254,7 +254,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*enode.Node, network ui cfg.Genesis = genesis utils.SetDNSDiscoveryDefaults(&cfg, genesis.ToBlock(nil).Hash()) - lesBackend, err := les.New(stack, &cfg) + lesBackend, err := les.New(stack, &cfg, nil) if err != nil { return nil, fmt.Errorf("Failed to register the Ethereum service: %w", err) } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index bccd6017b36e..5b9d814879f8 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1981,16 +1981,6 @@ func SetDNSDiscoveryDefaults(cfg *ethconfig.Config, genesis common.Hash) { // The second return value is the full node instance, which may be nil if the // node is running as a light client. func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend, *eth.Ethereum) { - if cfg.SyncMode == downloader.LightSync { - backend, err := les.New(stack, cfg) - if err != nil { - Fatalf("Failed to register the Ethereum service: %v", err) - } - scrollTracerWrapper := tracing.NewTracerWrapper() - stack.RegisterAPIs(tracers.APIs(backend.ApiBackend, scrollTracerWrapper)) - return backend.ApiBackend, nil - } - // initialize L1 client for sync service // note: we need to do this here to avoid circular dependency l1EndpointUrl := stack.Config().L1Endpoint @@ -2006,6 +1996,16 @@ func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend log.Info("Initialized L1 client", "endpoint", l1EndpointUrl) } + if cfg.SyncMode == downloader.LightSync { + backend, err := les.New(stack, cfg, l1Client) + if err != nil { + Fatalf("Failed to register the Ethereum service: %v", err) + } + scrollTracerWrapper := tracing.NewTracerWrapper() + stack.RegisterAPIs(tracers.APIs(backend.ApiBackend, scrollTracerWrapper)) + return backend.ApiBackend, nil + } + backend, err := eth.New(stack, cfg, l1Client) if err != nil { Fatalf("Failed to register the Ethereum service: %v", err) diff --git a/consensus/system_contract/api.go b/consensus/system_contract/api.go new file mode 100644 index 000000000000..9e201766ebbe --- /dev/null +++ b/consensus/system_contract/api.go @@ -0,0 +1,18 @@ +package system_contract + +import ( + "github.com/scroll-tech/go-ethereum/common" + "github.com/scroll-tech/go-ethereum/consensus" + "github.com/scroll-tech/go-ethereum/rpc" +) + +// API is a user facing RPC API to allow controlling the signer and voting +// mechanisms of the proof-of-authority scheme. +type API struct { + chain consensus.ChainHeaderReader +} + +// GetSigners retrieves the list of authorized signers at the specified block. +func (api *API) GetSigners(number *rpc.BlockNumber) ([]common.Address, error) { + return nil, nil +} \ No newline at end of file diff --git a/consensus/system_contract/consensus.go b/consensus/system_contract/consensus.go new file mode 100644 index 000000000000..878012f42827 --- /dev/null +++ b/consensus/system_contract/consensus.go @@ -0,0 +1,371 @@ +package system_contract + +import ( + "bytes" + "errors" + "fmt" + "io" + "math/big" + "time" + + "github.com/scroll-tech/go-ethereum/accounts" + "github.com/scroll-tech/go-ethereum/common" + "github.com/scroll-tech/go-ethereum/consensus" + "github.com/scroll-tech/go-ethereum/consensus/misc" + "github.com/scroll-tech/go-ethereum/core/state" + "github.com/scroll-tech/go-ethereum/core/types" + "github.com/scroll-tech/go-ethereum/crypto" + "github.com/scroll-tech/go-ethereum/log" + "github.com/scroll-tech/go-ethereum/rlp" + "github.com/scroll-tech/go-ethereum/rpc" + "github.com/scroll-tech/go-ethereum/trie" + "golang.org/x/crypto/sha3" +) + +var ( + extraSeal = crypto.SignatureLength // Fixed number of extra-data suffix bytes reserved for signer seal + uncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW. +) + +// Various error messages to mark blocks invalid. These should be private to +// prevent engine specific errors from being referenced in the remainder of the +// codebase, inherently breaking if the engine is swapped out. Please put common +// error types into the consensus package. +var ( + // errUnknownBlock is returned when the list of signers is requested for a block + // that is not part of the local blockchain. + errUnknownBlock = errors.New("unknown block") + // errCoinbaseNotEmpty is returned if a coinbase value is non-zero + errInvalidCoinbase = errors.New("coinbase not empty nor zero") + // errNonceNotEmpty is returned if a nonce value is non-zero + errInvalidNonce = errors.New("nonce not empty nor zero") + // errMissingSignature is returned if a block's extra-data section doesn't seem + // to contain a 65 byte secp256k1 signature. + errMissingSignature = errors.New("extra-data 65 byte signature missing") + // errInvalidMixDigest is returned if a block's mix digest is non-zero. + errInvalidMixDigest = errors.New("non-zero mix digest") + // errInvalidUncleHash is returned if a block contains an non-empty uncle list. + errInvalidUncleHash = errors.New("non empty uncle hash") + // errInvalidDifficulty is returned if a difficulty value is non-zero + errInvalidDifficulty = errors.New("non-one difficulty") + // errInvalidTimestamp is returned if the timestamp of a block is lower than + // the previous block's timestamp + the minimum block period. + errInvalidTimestamp = errors.New("invalid timestamp") + // errUnauthorizedSigner is returned if a header is signed by a non-authorized entity. + errUnauthorizedSigner = errors.New("unauthorized signer") +) + +// SignerFn hashes and signs the data to be signed by a backing account. +type SignerFn func(signer accounts.Account, mimeType string, message []byte) ([]byte, error) + +// Author implements consensus.Engine, returning the Ethereum address recovered +// from the signature in the header's extra-data section. +func (s *SystemContract) Author(header *types.Header) (common.Address, error) { + return ecrecover(header) +} + +// VerifyHeader checks whether a header conforms to the consensus rules of a +// given engine. +func (s *SystemContract) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error { + return s.verifyHeader(chain, header, nil) +} + +// 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 (s *SystemContract) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { + abort := make(chan struct{}) + results := make(chan error, len(headers)) + + go func() { + for i, header := range headers { + err := s.verifyHeader(chain, header, headers[:i]) + + select { + case <-abort: + return + case results <- err: + } + } + }() + return abort, results +} + +// verifyHeader checks whether a header conforms to the consensus rules.The +// caller may optionally pass in a batch of parents (ascending order) to avoid +// looking those up from the database. This is useful for concurrently verifying +// a batch of new headers. +func (s *SystemContract) verifyHeader(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) error { + if header.Number == nil { + return errUnknownBlock + } + + // Don't waste time checking blocks from the future + if header.Time > uint64(time.Now().Unix()) { + return consensus.ErrFutureBlock + } + // Ensure that the nonce is zero + if header.Nonce != (types.BlockNonce{}) { + return errInvalidNonce + } + // Check that the extra-data contains signature + if len(header.Extra) != extraSeal { + return errMissingSignature + } + // Ensure that the mix digest is zero + if header.MixDigest != (common.Hash{}) { + return errInvalidMixDigest + } + // Ensure that the block doesn't contain any uncles which are meaningless in PoA + if header.UncleHash != uncleHash { + return errInvalidUncleHash + } + // Ensure that the difficulty is zero + if header.Difficulty.Cmp(common.Big0) != 1 { + return errInvalidDifficulty + } + // Verify that the gas limit is <= 2^63-1 + cap := uint64(0x7fffffffffffffff) + if header.GasLimit > cap { + return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, cap) + } + // All basic checks passed, verify cascading fields + return s.verifyCascadingFields(chain, header, parents) +} + +// verifyCascadingFields verifies all the header fields that are not standalone, +// rather depend on a batch of previous headers. The caller may optionally pass +// in a batch of parents (ascending order) to avoid looking those up from the +// database. This is useful for concurrently verifying a batch of new headers. +func (s *SystemContract) verifyCascadingFields(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) error { + // The genesis block is the always valid dead-end + number := header.Number.Uint64() + if number == 0 { + return nil + } + // Ensure that the block's timestamp isn't too close to its parent + var parent *types.Header + if len(parents) > 0 { + parent = parents[len(parents)-1] + } else { + parent = chain.GetHeader(header.ParentHash, number-1) + } + if parent == nil || parent.Number.Uint64() != number-1 || parent.Hash() != header.ParentHash { + return consensus.ErrUnknownAncestor + } + if header.Time < parent.Time { + return errInvalidTimestamp + } + // Verify that the gasUsed is <= gasLimit + if header.GasUsed > header.GasLimit { + return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit) + } + if !chain.Config().IsCurie(header.Number) { + // Verify BaseFee not present before EIP-1559 fork. + if header.BaseFee != nil { + return fmt.Errorf("invalid baseFee before fork: have %d, want ", header.BaseFee) + } + if err := misc.VerifyGaslimit(parent.GasLimit, header.GasLimit); err != nil { + return err + } + } else if err := misc.VerifyEip1559Header(chain.Config(), parent, header); err != nil { + // Verify the header's EIP-1559 attributes. + return err + } + + signer, err := ecrecover(header) + if err != nil { + return err + } + + s.lock.Lock() + defer s.lock.Unlock() + + if signer != s.signerAddressL1 { + return errUnauthorizedSigner + } + return nil +} + +// VerifyUncles implements consensus.Engine, always returning an error for any +// uncles as this consensus mechanism doesn't permit uncles. +func (s *SystemContract) VerifyUncles(chain consensus.ChainReader, block *types.Block) error { + if len(block.Uncles()) > 0 { + return errors.New("uncles not allowed") + } + return nil +} + +// Prepare initializes the consensus fields of a block header according to the +// rules of a particular engine. Update only timestamp and prepare ExtraData for Signature +func (s *SystemContract) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error { + header.Extra = make([]byte, extraSeal) + // Ensure the timestamp has the correct delay + parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) + if parent == nil { + return consensus.ErrUnknownAncestor + } + header.Time = parent.Time + s.config.Period + // If RelaxedPeriod is enabled, always set the header timestamp to now (ie the time we start building it) as + // we don't know when it will be sealed + if s.config.RelaxedPeriod || header.Time < uint64(time.Now().Unix()) { + header.Time = uint64(time.Now().Unix()) + } + header.Difficulty = big.NewInt(1) + return nil +} + +// Finalize implements consensus.Engine. There is no post-transaction +// consensus rules in clique, therefore no rules here +func (s *SystemContract) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) { + // No block rewards in PoA, so the state remains as is +} + +// FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set, +// nor block rewards given, and returns the final block. +func (s *SystemContract) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { + // Finalize block + s.Finalize(chain, header, state, txs, uncles) + + // Assign the final state root to header. + header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) + + // Assemble and return the final block for sealing. + return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil)), nil +} + +// Seal implements consensus.Engine, attempting to create a sealed block using +// the local signing credentials. +func (s *SystemContract) 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 errUnknownBlock + } + // For 0-period chains, refuse to seal empty blocks (no reward but would spin sealing) + if s.config.Period == 0 && len(block.Transactions()) == 0 { + return errors.New("sealing paused while waiting for transactions") + } + // Don't hold the signer fields for the entire sealing procedure + s.lock.RLock() + signer, signFn := s.signer, s.signFn + s.lock.RUnlock() + + // Bail out if we're unauthorized to sign a block + // todo + + // Sweet, the protocol permits us to sign the block, wait for our time + delay := time.Unix(int64(header.Time), 0).Sub(time.Now()) // nolint: gosimple + + // Sign all the things! + sighash, err := signFn(accounts.Account{Address: signer}, accounts.MimetypeClique, SystemContractRLP(header)) + if err != nil { + return err + } + copy(header.Extra[0:], sighash) + // Wait until sealing is terminated or delay timeout. + log.Trace("Waiting for slot to sign and propagate", "delay", common.PrettyDuration(delay)) + go func() { + defer close(results) + + select { + case <-stop: + return + case <-time.After(delay): + } + + select { + case results <- block.WithSeal(header): + case <-time.After(time.Second): + log.Warn("Sealing result is not read by miner", "sealhash", SealHash(header)) + } + }() + + return nil +} + +// SealHash returns the hash of a block prior to it being sealed. +func (s *SystemContract) SealHash(header *types.Header) (hash common.Hash) { + return SealHash(header) +} + +// SealHash returns the hash of a block prior to it being sealed. +func SealHash(header *types.Header) (hash common.Hash) { + hasher := sha3.NewLegacyKeccak256() + encodeSigHeader(hasher, header) + hasher.(crypto.KeccakState).Read(hash[:]) + return hash +} + + +// ecrecover extracts the Ethereum account address from a signed header. +func ecrecover(header *types.Header) (common.Address, error) { + signature := header.Extra[0:] + + // Recover the public key and the Ethereum address + pubkey, err := crypto.Ecrecover(SealHash(header).Bytes(), signature) + if err != nil { + return common.Address{}, err + } + var signer common.Address + copy(signer[:], crypto.Keccak256(pubkey[1:])[12:]) + + return signer, nil +} + +// SystemContractRLP returns the rlp bytes which needs to be signed for the system contract +// sealing. The RLP to sign consists of the entire header apart from the ExtraData +// +// Note, the method requires the extra data to be at least 65 bytes, otherwise it +// panics. This is done to avoid accidentally using both forms (signature present +// or not), which could be abused to produce different hashes for the same header. +func SystemContractRLP(header *types.Header) []byte { + b := new(bytes.Buffer) + encodeSigHeader(b, header) + return b.Bytes() +} + +// CalcDifficulty implements consensus.Engine. There is no difficulty rules here +func (s *SystemContract) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int { + return nil +} + +// APIs implements consensus.Engine, returning the user facing RPC API to allow +// controlling the signer voting. +func (s *SystemContract) APIs(chain consensus.ChainHeaderReader) []rpc.API { + return []rpc.API{{ + Namespace: "system_contract", + Service: &API{}, + }} +} + +func encodeSigHeader(w io.Writer, header *types.Header) { + enc := []interface{}{ + header.ParentHash, + header.UncleHash, + header.Coinbase, + header.Root, + header.TxHash, + header.ReceiptHash, + header.Bloom, + header.Difficulty, + header.Number, + header.GasLimit, + header.GasUsed, + header.Time, + header.MixDigest, + header.Nonce, + } + if header.BaseFee != nil { + enc = append(enc, header.BaseFee) + } + if header.WithdrawalsHash != nil { + panic("unexpected withdrawal hash value in clique") + } + if err := rlp.Encode(w, enc); err != nil { + panic("can't encode: " + err.Error()) + } +} \ No newline at end of file diff --git a/consensus/system_contract/system_contract.go b/consensus/system_contract/system_contract.go new file mode 100644 index 000000000000..f77a80eb2d4c --- /dev/null +++ b/consensus/system_contract/system_contract.go @@ -0,0 +1,98 @@ +package system_contract + +import ( + "context" + "math/big" + "sync" + "time" + + "github.com/scroll-tech/go-ethereum/common" + "github.com/scroll-tech/go-ethereum/log" + "github.com/scroll-tech/go-ethereum/params" + "github.com/scroll-tech/go-ethereum/rollup/sync_service" +) + +const ( + defaultSyncInterval = 10 +) + +// SystemContract +type SystemContract struct { + config *params.SystemContractConfig // Consensus engine configuration parameters + client sync_service.EthClient + + signerAddressL1 common.Address // Address of the signer stored in L1 System Contract + + signer common.Address // Ethereum address of the signing key + signFn SignerFn // Signer function to authorize hashes with + lock sync.RWMutex // Protects the signer and proposals fields + + ctx context.Context + cancel context.CancelFunc +} + +// New creates a SystemContract consensus engine with the initial +// signers set to the ones provided by the user. +func New(ctx context.Context, config *params.SystemContractConfig, client sync_service.EthClient) *SystemContract { + blockNumber := big.NewInt(-1) // todo: get block number from L1BlocksContract (l1 block hash relay) or other source (depending on exact design) + ctx, cancel := context.WithCancel(ctx) + address, err := client.StorageAt(ctx, config.SystemContractAddress, config.SystemContractSlot, blockNumber) + if err != nil { + log.Error("failed to get signer address from L1 System Contract", "err", err) + } + systemContract := &SystemContract{ + config: config, + client: client, + signerAddressL1: common.BytesToAddress(address), + + ctx: ctx, + cancel: cancel, + } + systemContract.Start() + return systemContract +} + +// Authorize injects a private key into the consensus engine to mint new blocks +// with. +func (s *SystemContract) Authorize(signer common.Address, signFn SignerFn) { + s.lock.Lock() + defer s.lock.Unlock() + + s.signer = signer + s.signFn = signFn +} + +func (s *SystemContract) Start() { + log.Info("starting SystemContract") + go func() { + syncTicker := time.NewTicker(defaultSyncInterval) + defer syncTicker.Stop() + for { + select { + case <-s.ctx.Done(): + return + default: + } + select { + case <-s.ctx.Done(): + return + case <-syncTicker.C: + blockNumber := big.NewInt(-1) // todo: get block number from L1BlocksContract (l1 block hash relay) or other source (depending on exact design) + + address, err := s.client.StorageAt(s.ctx, s.config.SystemContractAddress, s.config.SystemContractSlot, blockNumber) + if err != nil { + log.Error("failed to get signer address from L1 System Contract", "err", err) + } + s.lock.Lock() + s.signerAddressL1 = common.BytesToAddress(address) + s.lock.Unlock() + } + } + }() +} + +// Close implements consensus.Engine. +func (s *SystemContract) Close() error { + s.cancel() + return nil +} \ No newline at end of file diff --git a/eth/backend.go b/eth/backend.go index 2b6c663d2744..acbec39418af 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -32,6 +32,7 @@ import ( "github.com/scroll-tech/go-ethereum/common/hexutil" "github.com/scroll-tech/go-ethereum/consensus" "github.com/scroll-tech/go-ethereum/consensus/clique" + "github.com/scroll-tech/go-ethereum/consensus/system_contract" "github.com/scroll-tech/go-ethereum/core" "github.com/scroll-tech/go-ethereum/core/bloombits" "github.com/scroll-tech/go-ethereum/core/rawdb" @@ -155,7 +156,7 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client sync_service.EthCl chainDb: chainDb, eventMux: stack.EventMux(), accountManager: stack.AccountManager(), - engine: ethconfig.CreateConsensusEngine(stack, chainConfig, ðashConfig, config.Miner.Notify, config.Miner.Noverify, chainDb), + engine: ethconfig.CreateConsensusEngine(stack, chainConfig, ðashConfig, config.Miner.Notify, config.Miner.Noverify, chainDb, l1Client), closeBloomHandler: make(chan struct{}), networkID: config.NetworkId, gasPrice: config.Miner.GasPrice, @@ -529,6 +530,13 @@ func (s *Ethereum) StartMining(threads int) error { return fmt.Errorf("signer missing: %v", err) } clique.Authorize(eb, wallet.SignData) + } else if systemContract, ok := s.engine.(*system_contract.SystemContract); ok { + wallet, err := s.accountManager.Find(accounts.Account{Address: eb}) + if wallet == nil || err != nil { + log.Error("Etherbase account unavailable locally", "err", err) + return fmt.Errorf("signer missing: %v", err) + } + systemContract.Authorize(eb, wallet.SignData) } // If mining is started, we can disable the transaction rejection mechanism // introduced to speed sync times. diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index e8c7a5aa178c..129118e0df8e 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -18,6 +18,7 @@ package ethconfig import ( + "context" "math/big" "os" "os/user" @@ -29,6 +30,7 @@ import ( "github.com/scroll-tech/go-ethereum/consensus" "github.com/scroll-tech/go-ethereum/consensus/clique" "github.com/scroll-tech/go-ethereum/consensus/ethash" + "github.com/scroll-tech/go-ethereum/consensus/system_contract" "github.com/scroll-tech/go-ethereum/core" "github.com/scroll-tech/go-ethereum/eth/downloader" "github.com/scroll-tech/go-ethereum/eth/gasprice" @@ -38,6 +40,7 @@ import ( "github.com/scroll-tech/go-ethereum/node" "github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/rollup/da_syncer" + "github.com/scroll-tech/go-ethereum/rollup/sync_service" ) // FullNodeGPO contains default gasprice oracle settings for full node. @@ -228,11 +231,14 @@ type Config struct { } // CreateConsensusEngine creates a consensus engine for the given chain configuration. -func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database) consensus.Engine { +func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database, l1Client sync_service.EthClient) consensus.Engine { // If proof-of-authority is requested, set it up if chainConfig.Clique != nil { return clique.New(chainConfig.Clique, db) } + if chainConfig.SystemContract != nil { + return system_contract.New(context.Background(), chainConfig.SystemContract, l1Client) + } // Otherwise assume proof-of-work switch config.PowMode { case ethash.ModeFake: diff --git a/les/api_test.go b/les/api_test.go index ad47ff02d6a1..28634c7c86d0 100644 --- a/les/api_test.go +++ b/les/api_test.go @@ -498,7 +498,7 @@ func newLesClientService(ctx *adapters.ServiceContext, stack *node.Node) (node.L config := ethconfig.Defaults config.SyncMode = (ethdownloader.SyncMode)(downloader.LightSync) config.Ethash.PowMode = ethash.ModeFake - return New(stack, &config) + return New(stack, &config, nil) } func newLesServerService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { diff --git a/les/client.go b/les/client.go index 311f78a658a5..2918d4d68f9d 100644 --- a/les/client.go +++ b/les/client.go @@ -46,6 +46,7 @@ import ( "github.com/scroll-tech/go-ethereum/p2p/enr" "github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/rlp" + "github.com/scroll-tech/go-ethereum/rollup/sync_service" "github.com/scroll-tech/go-ethereum/rpc" ) @@ -79,7 +80,7 @@ type LightEthereum struct { } // New creates an instance of the light client. -func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { +func New(stack *node.Node, config *ethconfig.Config, l1Client sync_service.EthClient) (*LightEthereum, error) { chainDb, err := stack.OpenDatabase("lightchaindata", config.DatabaseCache, config.DatabaseHandles, "eth/db/chaindata/", false) if err != nil { return nil, err @@ -109,7 +110,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { eventMux: stack.EventMux(), reqDist: newRequestDistributor(peers, &mclock.System{}), accountManager: stack.AccountManager(), - engine: ethconfig.CreateConsensusEngine(stack, chainConfig, &config.Ethash, nil, false, chainDb), + engine: ethconfig.CreateConsensusEngine(stack, chainConfig, &config.Ethash, nil, false, chainDb, l1Client), bloomRequests: make(chan chan *bloombits.Retrieval), bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), p2pServer: stack.Server(), diff --git a/mobile/geth.go b/mobile/geth.go index 6b1dfebb1bf8..d1c252adfc4b 100644 --- a/mobile/geth.go +++ b/mobile/geth.go @@ -194,7 +194,7 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { ethConf.SyncMode = downloader.LightSync ethConf.NetworkId = uint64(config.EthereumNetworkID) ethConf.DatabaseCache = config.EthereumDatabaseCache - lesBackend, err := les.New(rawStack, ðConf) + lesBackend, err := les.New(rawStack, ðConf, nil) if err != nil { return nil, fmt.Errorf("ethereum init: %v", err) } diff --git a/params/config.go b/params/config.go index eb2213fdd360..dde4856781c3 100644 --- a/params/config.go +++ b/params/config.go @@ -639,8 +639,9 @@ type ChainConfig struct { TerminalTotalDifficulty *big.Int `json:"terminalTotalDifficulty,omitempty"` // Various consensus engines - Ethash *EthashConfig `json:"ethash,omitempty"` - Clique *CliqueConfig `json:"clique,omitempty"` + Ethash *EthashConfig `json:"ethash,omitempty"` + Clique *CliqueConfig `json:"clique,omitempty"` + SystemContract *SystemContractConfig `json:"systemContract,omitempty"` // Scroll genesis extension: enable scroll rollup-related traces & state transition Scroll ScrollConfig `json:"scroll,omitempty"` @@ -745,6 +746,21 @@ func (c *CliqueConfig) String() string { return "clique" } +// SystemContractConfig is the consensus engine configs for rollup sequencer sealing. +type SystemContractConfig struct { + Period uint64 `json:"period"` // Number of seconds between blocks to enforce + + SystemContractAddress common.Address `json:"system_contract_address"` // address of system contract on L1 + SystemContractSlot common.Hash `json:"system_contract_slot"` // slot of signer address in system contract on L1 + + RelaxedPeriod bool `json:"relaxed_period"` // Relaxes the period to be just an upper bound +} + +// String implements the stringer interface, returning the consensus engine details. +func (c *SystemContractConfig) String() string { + return "system_contract" +} + // String implements the fmt.Stringer interface. func (c *ChainConfig) String() string { var engine interface{} diff --git a/rollup/rollup_sync_service/l1client_test.go b/rollup/rollup_sync_service/l1client_test.go index 394f455b80c5..8a42bfb3068e 100644 --- a/rollup/rollup_sync_service/l1client_test.go +++ b/rollup/rollup_sync_service/l1client_test.go @@ -72,3 +72,8 @@ func (m *mockEthClient) TransactionByHash(ctx context.Context, txHash common.Has func (m *mockEthClient) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { return nil, nil } + +func (m *mockEthClient) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { + return nil, nil +} + diff --git a/rollup/sync_service/types.go b/rollup/sync_service/types.go index 3429ec1bb778..08b4dda2ecf0 100644 --- a/rollup/sync_service/types.go +++ b/rollup/sync_service/types.go @@ -19,4 +19,5 @@ type EthClient interface { SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) TransactionByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, isPending bool, err error) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) + StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) } From 8e4deb156ae2c87d62463c717f651c4abb329fbb Mon Sep 17 00:00:00 2001 From: Morty Date: Tue, 17 Dec 2024 02:32:19 +0800 Subject: [PATCH 02/36] fix: check extra field lens --- consensus/system_contract/consensus.go | 4 ++-- consensus/system_contract/system_contract.go | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/consensus/system_contract/consensus.go b/consensus/system_contract/consensus.go index 878012f42827..bffb2c17d1b3 100644 --- a/consensus/system_contract/consensus.go +++ b/consensus/system_contract/consensus.go @@ -110,7 +110,7 @@ func (s *SystemContract) verifyHeader(chain consensus.ChainHeaderReader, header return errInvalidNonce } // Check that the extra-data contains signature - if len(header.Extra) != extraSeal { + if header.Number != big.NewInt(0) && len(header.Extra) != extraSeal { return errMissingSignature } // Ensure that the mix digest is zero @@ -121,7 +121,7 @@ func (s *SystemContract) verifyHeader(chain consensus.ChainHeaderReader, header if header.UncleHash != uncleHash { return errInvalidUncleHash } - // Ensure that the difficulty is zero + // Ensure that the difficulty is one if header.Difficulty.Cmp(common.Big0) != 1 { return errInvalidDifficulty } diff --git a/consensus/system_contract/system_contract.go b/consensus/system_contract/system_contract.go index f77a80eb2d4c..c7919fc466af 100644 --- a/consensus/system_contract/system_contract.go +++ b/consensus/system_contract/system_contract.go @@ -78,7 +78,6 @@ func (s *SystemContract) Start() { return case <-syncTicker.C: blockNumber := big.NewInt(-1) // todo: get block number from L1BlocksContract (l1 block hash relay) or other source (depending on exact design) - address, err := s.client.StorageAt(s.ctx, s.config.SystemContractAddress, s.config.SystemContractSlot, blockNumber) if err != nil { log.Error("failed to get signer address from L1 System Contract", "err", err) From a77524ffe650a95287776b4c2e921b57ddc20c85 Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Thu, 16 Jan 2025 14:20:49 +0100 Subject: [PATCH 03/36] Finish implementation with one signer only --- consensus/system_contract/consensus.go | 8 +++++--- consensus/system_contract/system_contract.go | 11 ++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/consensus/system_contract/consensus.go b/consensus/system_contract/consensus.go index bffb2c17d1b3..bf22467f89f4 100644 --- a/consensus/system_contract/consensus.go +++ b/consensus/system_contract/consensus.go @@ -252,10 +252,13 @@ func (s *SystemContract) Seal(chain consensus.ChainHeaderReader, block *types.Bl // Don't hold the signer fields for the entire sealing procedure s.lock.RLock() signer, signFn := s.signer, s.signFn + signerAddressL1 := s.signerAddressL1 s.lock.RUnlock() // Bail out if we're unauthorized to sign a block - // todo + if signer != signerAddressL1 { + return errors.New("local node is not authorized to sign this block") + } // Sweet, the protocol permits us to sign the block, wait for our time delay := time.Unix(int64(header.Time), 0).Sub(time.Now()) // nolint: gosimple @@ -300,7 +303,6 @@ func SealHash(header *types.Header) (hash common.Hash) { return hash } - // ecrecover extracts the Ethereum account address from a signed header. func ecrecover(header *types.Header) (common.Address, error) { signature := header.Extra[0:] @@ -368,4 +370,4 @@ func encodeSigHeader(w io.Writer, header *types.Header) { if err := rlp.Encode(w, enc); err != nil { panic("can't encode: " + err.Error()) } -} \ No newline at end of file +} diff --git a/consensus/system_contract/system_contract.go b/consensus/system_contract/system_contract.go index c7919fc466af..124ab5cd87f5 100644 --- a/consensus/system_contract/system_contract.go +++ b/consensus/system_contract/system_contract.go @@ -2,7 +2,6 @@ package system_contract import ( "context" - "math/big" "sync" "time" @@ -13,7 +12,7 @@ import ( ) const ( - defaultSyncInterval = 10 + defaultSyncInterval = 10 * time.Second ) // SystemContract @@ -34,9 +33,8 @@ type SystemContract struct { // New creates a SystemContract consensus engine with the initial // signers set to the ones provided by the user. func New(ctx context.Context, config *params.SystemContractConfig, client sync_service.EthClient) *SystemContract { - blockNumber := big.NewInt(-1) // todo: get block number from L1BlocksContract (l1 block hash relay) or other source (depending on exact design) ctx, cancel := context.WithCancel(ctx) - address, err := client.StorageAt(ctx, config.SystemContractAddress, config.SystemContractSlot, blockNumber) + address, err := client.StorageAt(ctx, config.SystemContractAddress, config.SystemContractSlot, nil) if err != nil { log.Error("failed to get signer address from L1 System Contract", "err", err) } @@ -77,8 +75,7 @@ func (s *SystemContract) Start() { case <-s.ctx.Done(): return case <-syncTicker.C: - blockNumber := big.NewInt(-1) // todo: get block number from L1BlocksContract (l1 block hash relay) or other source (depending on exact design) - address, err := s.client.StorageAt(s.ctx, s.config.SystemContractAddress, s.config.SystemContractSlot, blockNumber) + address, err := s.client.StorageAt(s.ctx, s.config.SystemContractAddress, s.config.SystemContractSlot, nil) if err != nil { log.Error("failed to get signer address from L1 System Contract", "err", err) } @@ -94,4 +91,4 @@ func (s *SystemContract) Start() { func (s *SystemContract) Close() error { s.cancel() return nil -} \ No newline at end of file +} From 4047f24769804d5f17347d8e91f5379b39b5ad91 Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Fri, 17 Jan 2025 19:11:05 +0100 Subject: [PATCH 04/36] Implement unit tests --- .../system_contract/system_contract_test.go | 232 ++++++++++++++++++ go.mod | 2 +- go.sum | 2 + rollup/rollup_sync_service/l1client_test.go | 1 - 4 files changed, 235 insertions(+), 2 deletions(-) create mode 100644 consensus/system_contract/system_contract_test.go diff --git a/consensus/system_contract/system_contract_test.go b/consensus/system_contract/system_contract_test.go new file mode 100644 index 000000000000..db1ff9103220 --- /dev/null +++ b/consensus/system_contract/system_contract_test.go @@ -0,0 +1,232 @@ +package system_contract + +import ( + "context" + "github.com/scroll-tech/go-ethereum" + "github.com/scroll-tech/go-ethereum/accounts" + "github.com/scroll-tech/go-ethereum/common" + "github.com/scroll-tech/go-ethereum/core/types" + "github.com/scroll-tech/go-ethereum/log" + "github.com/scroll-tech/go-ethereum/params" + "github.com/scroll-tech/go-ethereum/rollup/sync_service" + "github.com/scroll-tech/go-ethereum/trie" + "github.com/stretchr/testify/require" + "math/big" + "sync" + "testing" + "time" +) + +var _ sync_service.EthClient = &fakeEthClient{} + +func TestSystemContract_FetchSigner(t *testing.T) { + log.Root().SetHandler(log.DiscardHandler()) + + expectedSigner := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + + fakeClient := &fakeEthClient{value: expectedSigner} + + config := ¶ms.SystemContractConfig{ + SystemContractAddress: common.HexToAddress("0xFAKE"), + // The slot number can be arbitrary – fake client doesn't use it. + Period: 10, + RelaxedPeriod: false, + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + sys := New(ctx, config, fakeClient) + defer sys.Close() + + // Since the SystemContract's Start() routine fetches and updates s.signerAddressL1 + // in a separate goroutine, wait a bit for that to complete. + time.Sleep(2 * time.Second) + + // Acquire a read lock to safely read the value. + sys.lock.RLock() + actualSigner := sys.signerAddressL1 + sys.lock.RUnlock() + + // Verify that the fetched signer equals the expectedSigner from our fake client. + require.Equal(t, expectedSigner, actualSigner, "The SystemContract should update signerAddressL1 to the value provided by the client") +} + +func TestSystemContract_AuthorizeCheck(t *testing.T) { + // This test verifies that if the local signer does not match the authorized signer, + // then the Seal() function returns an error. + + expectedSigner := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") + + fakeClient := &fakeEthClient{value: expectedSigner} + config := ¶ms.SystemContractConfig{ + SystemContractAddress: common.HexToAddress("0xFAKE"), + Period: 10, + RelaxedPeriod: false, + } + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + sys := New(ctx, config, fakeClient) + defer sys.Close() + + // Wait to ensure that the background routine has updated signerAddressL1. + time.Sleep(2 * time.Second) + + // Authorize with a different signer than expected. + differentSigner := common.HexToAddress("0xABCDEFabcdefABCDEFabcdefabcdefABCDEFABCD") + sys.Authorize(differentSigner, func(acc accounts.Account, mimeType string, message []byte) ([]byte, error) { + // For testing, return a dummy signature + return []byte("dummy_sig"), nil + }) + + // Create a dummy block header. + // We only need the block number and extra data length for this test. + header := &types.Header{ + Number: big.NewInt(100), + // We use an extra slice with length equal to extraSeal + Extra: make([]byte, extraSeal), + } + + // Call Seal() and expect an error since local signer != authorized signer. + results := make(chan *types.Block) + stop := make(chan struct{}) + err := sys.Seal(nil, (&types.Block{}).WithSeal(header), results, stop) + + require.Error(t, err, "Seal should return an error when the local signer is not authorized") +} + +// TestSystemContract_SignsAfterUpdate simulates: +// 1. Initially, the SystemContract authorized signer (from StorageAt) is not the signer of the Block. +// 2. Later, after updating the fake client to the correct signer, the background +// poll updates the SystemContract. +// 3. Once updated, if the local signing key is set to match, Seal() should succeed. +func TestSystemContract_SignsAfterUpdate(t *testing.T) { + // Silence logging during tests. + log.Root().SetHandler(log.DiscardHandler()) + + // Define two addresses: one "wrong" and one "correct". + oldSigner := common.HexToAddress("0x1111111111111111111111111111111111111111") + updatedSigner := common.HexToAddress("0x2222222222222222222222222222222222222222") + + // Create a fake client that starts by returning the wrong signer. + fakeClient := &fakeEthClient{ + value: oldSigner, + } + + config := ¶ms.SystemContractConfig{ + SystemContractAddress: common.HexToAddress("0xFAKE"), // Dummy value + Period: 10, // arbitrary non-zero value + RelaxedPeriod: false, + } + + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + + sys := New(ctx, config, fakeClient) + defer sys.Close() + + // Wait for the background routine to poll at least once. + time.Sleep(2 * time.Second) + + // Verify that initially the fetched signer equals oldSigner. + sys.lock.RLock() + initialSigner := sys.signerAddressL1 + sys.lock.RUnlock() + require.Equal(t, oldSigner, initialSigner, "Initial signerAddressL1 should be oldSigner") + + // Now, simulate an update: change the fake client's returned value to updatedSigner. + fakeClient.mu.Lock() + fakeClient.value = updatedSigner + fakeClient.mu.Unlock() + + // Wait enough for the background polling routine to fetch the new value. + time.Sleep(1 + time.Second + defaultSyncInterval) + + // Verify that system contract's signerAddressL1 is now updated to updatedSigner. + sys.lock.RLock() + newSigner := sys.signerAddressL1 + sys.lock.RUnlock() + require.Equal(t, newSigner, updatedSigner, "SignerAddressL1 should update to updatedSigner after polling") + + // Now simulate authorizing with the correct local signer. + sys.Authorize(updatedSigner, func(acc accounts.Account, mimeType string, message []byte) ([]byte, error) { + // For testing, return a dummy signature. + return []byte("dummy_signature"), nil + }) + + // Create a dummy header for sealing. + header := &types.Header{ + Number: big.NewInt(100), + Extra: make([]byte, extraSeal), + } + + // Construct a new block from the header using NewBlock constructor. + block := types.NewBlock(header, nil, nil, nil, trie.NewStackTrie(nil)) + + results := make(chan *types.Block) + stop := make(chan struct{}) + + // Call Seal. It should succeed (i.e. return no error) because local signer now equals authorized signer. + err := sys.Seal(nil, block, results, stop) + require.NoError(t, err, "Seal should succeed when the local signer is authorized after update") + + // Wait for the result from Seal's goroutine. + select { + case sealedBlock := <-results: + require.NotNil(t, sealedBlock, "Seal should eventually return a sealed block") + // Optionally, you may log or further inspect sealedBlock here. + case <-time.After(15 * time.Second): + t.Fatal("Timed out waiting for Seal to return a sealed block") + } +} + +// fakeEthClient implements a minimal version of sync_service.EthClient for testing purposes. +type fakeEthClient struct { + mu sync.Mutex + // value is the fixed value to return from StorageAt. + // We'll assume StorageAt returns a 32-byte value representing an Ethereum address. + value common.Address +} + +// BlockNumber returns 0. +func (f *fakeEthClient) BlockNumber(ctx context.Context) (uint64, error) { + return 0, nil +} + +// ChainID returns a zero-value chain ID. +func (f *fakeEthClient) ChainID(ctx context.Context) (*big.Int, error) { + return big.NewInt(0), nil +} + +// FilterLogs returns an empty slice of logs. +func (f *fakeEthClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { + return []types.Log{}, nil +} + +// HeaderByNumber returns nil. +func (f *fakeEthClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + return nil, nil +} + +// SubscribeFilterLogs returns a nil subscription. +func (f *fakeEthClient) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { + return nil, nil +} + +// TransactionByHash returns (nil, false, nil). +func (f *fakeEthClient) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) { + return nil, false, nil +} + +// BlockByHash returns nil. +func (f *fakeEthClient) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + return nil, nil +} + +// StorageAt returns the byte representation of f.value. +func (f *fakeEthClient) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { + f.mu.Lock() + defer f.mu.Unlock() + return f.value.Bytes(), nil +} diff --git a/go.mod b/go.mod index db0e122a1d37..3ffa21e58fdc 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/edsrzf/mmap-go v1.0.0 github.com/ethereum/c-kzg-4844/bindings/go v0.0.0-20230126171313-363c7d7593b4 github.com/fatih/color v1.7.0 - github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 + github.com/fjl/memsize v0.0.2 github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff github.com/go-stack/stack v1.8.1 github.com/golang/protobuf v1.4.3 diff --git a/go.sum b/go.sum index 47f631beb92e..2f6680439bd9 100644 --- a/go.sum +++ b/go.sum @@ -136,6 +136,8 @@ github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA= +github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= diff --git a/rollup/rollup_sync_service/l1client_test.go b/rollup/rollup_sync_service/l1client_test.go index 8a42bfb3068e..397e25a29ade 100644 --- a/rollup/rollup_sync_service/l1client_test.go +++ b/rollup/rollup_sync_service/l1client_test.go @@ -76,4 +76,3 @@ func (m *mockEthClient) BlockByHash(ctx context.Context, hash common.Hash) (*typ func (m *mockEthClient) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { return nil, nil } - From b68d2d628530d4dfd3c26a101ac01edb315de63c Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Tue, 21 Jan 2025 13:20:32 +0100 Subject: [PATCH 05/36] Only check signature if block not requested --- consensus/system_contract/api.go | 4 +- consensus/system_contract/consensus.go | 20 ++++++---- consensus/system_contract/system_contract.go | 12 ++++-- core/types/block.go | 5 +++ eth/downloader/downloader.go | 40 ++++++++++---------- eth/downloader/queue.go | 19 ++++++++-- eth/downloader/resultstore.go | 9 +++-- les/downloader/downloader.go | 40 ++++++++++---------- les/downloader/queue.go | 19 ++++++++-- les/downloader/resultstore.go | 9 +++-- 10 files changed, 112 insertions(+), 65 deletions(-) diff --git a/consensus/system_contract/api.go b/consensus/system_contract/api.go index 9e201766ebbe..c975347f1b99 100644 --- a/consensus/system_contract/api.go +++ b/consensus/system_contract/api.go @@ -9,10 +9,10 @@ import ( // API is a user facing RPC API to allow controlling the signer and voting // mechanisms of the proof-of-authority scheme. type API struct { - chain consensus.ChainHeaderReader + chain consensus.ChainHeaderReader } // GetSigners retrieves the list of authorized signers at the specified block. func (api *API) GetSigners(number *rpc.BlockNumber) ([]common.Address, error) { return nil, nil -} \ No newline at end of file +} diff --git a/consensus/system_contract/consensus.go b/consensus/system_contract/consensus.go index bf22467f89f4..a78cda3a27fa 100644 --- a/consensus/system_contract/consensus.go +++ b/consensus/system_contract/consensus.go @@ -174,17 +174,21 @@ func (s *SystemContract) verifyCascadingFields(chain consensus.ChainHeaderReader return err } - signer, err := ecrecover(header) - if err != nil { - return err - } + // only if block header has NOT been requested, then verify the signature against the current signer + if !header.Requested { + signer, err := ecrecover(header) + if err != nil { + return err + } - s.lock.Lock() - defer s.lock.Unlock() + s.lock.RLock() + defer s.lock.RUnlock() - if signer != s.signerAddressL1 { - return errUnauthorizedSigner + if signer != s.signerAddressL1 { + return errUnauthorizedSigner + } } + return nil } diff --git a/consensus/system_contract/system_contract.go b/consensus/system_contract/system_contract.go index 124ab5cd87f5..764e4136e71e 100644 --- a/consensus/system_contract/system_contract.go +++ b/consensus/system_contract/system_contract.go @@ -79,9 +79,15 @@ func (s *SystemContract) Start() { if err != nil { log.Error("failed to get signer address from L1 System Contract", "err", err) } - s.lock.Lock() - s.signerAddressL1 = common.BytesToAddress(address) - s.lock.Unlock() + bAddress := common.BytesToAddress(address) + s.lock.RLock() + addressChanged := s.signerAddressL1 != bAddress + s.lock.RUnlock() + if addressChanged { + s.lock.Lock() + s.signerAddressL1 = bAddress + s.lock.Unlock() + } } } }() diff --git a/core/types/block.go b/core/types/block.go index bee3dee5168c..8810a464c882 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -101,6 +101,11 @@ type Header struct { // ParentBeaconRoot was added by EIP-4788 and is ignored in legacy headers. // Included for Ethereum compatibility in Scroll SDK ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` + + //Hacky: used internally to mark the header as requested by the downloader at the deliver queue + // The tag "rlp:\"-\"" ensures it's excluded from the RLP encoding (and thus, from the hash computation). + + Requested bool `json:"requested" rlp:"-"` } // field type overrides for gencodec diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 839ebe733885..6afb3a852744 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -702,9 +702,11 @@ func (d *Downloader) fetchHead(p *peerConnection) (head *types.Header, pivot *ty // calculateRequestSpan calculates what headers to request from a peer when trying to determine the // common ancestor. // It returns parameters to be used for peer.RequestHeadersByNumber: -// from - starting block number -// count - number of headers to request -// skip - number of headers to skip +// +// from - starting block number +// count - number of headers to request +// skip - number of headers to skip +// // and also returns 'max', the last block which is expected to be returned by the remote peers, // given the (from,count,skip) func calculateRequestSpan(remoteHeight, localHeight uint64) (int64, int, int, uint64) { @@ -1319,22 +1321,22 @@ func (d *Downloader) fetchReceipts(from uint64) error { // various callbacks to handle the slight differences between processing them. // // The instrumentation parameters: -// - errCancel: error type to return if the fetch operation is cancelled (mostly makes logging nicer) -// - deliveryCh: channel from which to retrieve downloaded data packets (merged from all concurrent peers) -// - deliver: processing callback to deliver data packets into type specific download queues (usually within `queue`) -// - wakeCh: notification channel for waking the fetcher when new tasks are available (or sync completed) -// - expire: task callback method to abort requests that took too long and return the faulty peers (traffic shaping) -// - pending: task callback for the number of requests still needing download (detect completion/non-completability) -// - inFlight: task callback for the number of in-progress requests (wait for all active downloads to finish) -// - throttle: task callback to check if the processing queue is full and activate throttling (bound memory use) -// - reserve: task callback to reserve new download tasks to a particular peer (also signals partial completions) -// - fetchHook: tester callback to notify of new tasks being initiated (allows testing the scheduling logic) -// - fetch: network callback to actually send a particular download request to a physical remote peer -// - cancel: task callback to abort an in-flight download request and allow rescheduling it (in case of lost peer) -// - capacity: network callback to retrieve the estimated type-specific bandwidth capacity of a peer (traffic shaping) -// - idle: network callback to retrieve the currently (type specific) idle peers that can be assigned tasks -// - setIdle: network callback to set a peer back to idle and update its estimated capacity (traffic shaping) -// - kind: textual label of the type being downloaded to display in log messages +// - errCancel: error type to return if the fetch operation is cancelled (mostly makes logging nicer) +// - deliveryCh: channel from which to retrieve downloaded data packets (merged from all concurrent peers) +// - deliver: processing callback to deliver data packets into type specific download queues (usually within `queue`) +// - wakeCh: notification channel for waking the fetcher when new tasks are available (or sync completed) +// - expire: task callback method to abort requests that took too long and return the faulty peers (traffic shaping) +// - pending: task callback for the number of requests still needing download (detect completion/non-completability) +// - inFlight: task callback for the number of in-progress requests (wait for all active downloads to finish) +// - throttle: task callback to check if the processing queue is full and activate throttling (bound memory use) +// - reserve: task callback to reserve new download tasks to a particular peer (also signals partial completions) +// - fetchHook: tester callback to notify of new tasks being initiated (allows testing the scheduling logic) +// - fetch: network callback to actually send a particular download request to a physical remote peer +// - cancel: task callback to abort an in-flight download request and allow rescheduling it (in case of lost peer) +// - capacity: network callback to retrieve the estimated type-specific bandwidth capacity of a peer (traffic shaping) +// - idle: network callback to retrieve the currently (type specific) idle peers that can be assigned tasks +// - setIdle: network callback to set a peer back to idle and update its estimated capacity (traffic shaping) +// - kind: textual label of the type being downloaded to display in log messages func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack) (int, error), wakeCh chan bool, expire func() map[string]int, pending func() int, inFlight func() bool, reserve func(*peerConnection, int) (*fetchRequest, bool, bool), fetchHook func([]*types.Header), fetch func(*peerConnection, *fetchRequest) error, cancel func(*fetchRequest), capacity func(*peerConnection) int, diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index 158608ccd7f9..37f44e2d9f37 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -477,9 +477,10 @@ func (q *queue) ReserveReceipts(p *peerConnection, count int) (*fetchRequest, bo // to access the queue, so they already need a lock anyway. // // Returns: -// item - the fetchRequest -// progress - whether any progress was made -// throttle - if the caller should throttle for a while +// +// item - the fetchRequest +// progress - whether any progress was made +// throttle - if the caller should throttle for a while func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common.Hash]*types.Header, taskQueue *prque.Prque, pendPool map[string]*fetchRequest, kind uint) (*fetchRequest, bool, bool) { // Short circuit if the pool has been depleted, or if the peer's already @@ -705,6 +706,13 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh headerReqTimer.UpdateSince(request.Time) delete(q.headerPendPool, id) + // Hacky: mark that the header was explicitly requested + // This extra field is used by the custom consensus to + // verify against current signerAddressL1 if the header was not requested + for _, header := range headers { + header.Requested = true + } + // Ensure headers can be mapped onto the skeleton chain target := q.headerTaskPool[request.From].Hash() @@ -842,6 +850,11 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, reqTimer.UpdateSince(request.Time) delete(pendPool, id) + // Hacky: mark that the header was explicitly requested + for _, header := range request.Headers { + header.Requested = true + } + // If no data items were retrieved, mark them as unavailable for the origin peer if results == 0 { for _, header := range request.Headers { diff --git a/eth/downloader/resultstore.go b/eth/downloader/resultstore.go index 8075f507db3a..4a40e0ebd39e 100644 --- a/eth/downloader/resultstore.go +++ b/eth/downloader/resultstore.go @@ -71,10 +71,11 @@ func (r *resultStore) SetThrottleThreshold(threshold uint64) uint64 { // wants to reserve headers for fetching. // // It returns the following: -// stale - if true, this item is already passed, and should not be requested again -// throttled - if true, the store is at capacity, this particular header is not prio now -// item - the result to store data into -// err - any error that occurred +// +// stale - if true, this item is already passed, and should not be requested again +// throttled - if true, the store is at capacity, this particular header is not prio now +// item - the result to store data into +// err - any error that occurred func (r *resultStore) AddFetch(header *types.Header, fastSync bool) (stale, throttled bool, item *fetchResult, err error) { r.lock.Lock() defer r.lock.Unlock() diff --git a/les/downloader/downloader.go b/les/downloader/downloader.go index d02d91b2ae91..7ff6eda9ac34 100644 --- a/les/downloader/downloader.go +++ b/les/downloader/downloader.go @@ -705,9 +705,11 @@ func (d *Downloader) fetchHead(p *peerConnection) (head *types.Header, pivot *ty // calculateRequestSpan calculates what headers to request from a peer when trying to determine the // common ancestor. // It returns parameters to be used for peer.RequestHeadersByNumber: -// from - starting block number -// count - number of headers to request -// skip - number of headers to skip +// +// from - starting block number +// count - number of headers to request +// skip - number of headers to skip +// // and also returns 'max', the last block which is expected to be returned by the remote peers, // given the (from,count,skip) func calculateRequestSpan(remoteHeight, localHeight uint64) (int64, int, int, uint64) { @@ -1322,22 +1324,22 @@ func (d *Downloader) fetchReceipts(from uint64) error { // various callbacks to handle the slight differences between processing them. // // The instrumentation parameters: -// - errCancel: error type to return if the fetch operation is cancelled (mostly makes logging nicer) -// - deliveryCh: channel from which to retrieve downloaded data packets (merged from all concurrent peers) -// - deliver: processing callback to deliver data packets into type specific download queues (usually within `queue`) -// - wakeCh: notification channel for waking the fetcher when new tasks are available (or sync completed) -// - expire: task callback method to abort requests that took too long and return the faulty peers (traffic shaping) -// - pending: task callback for the number of requests still needing download (detect completion/non-completability) -// - inFlight: task callback for the number of in-progress requests (wait for all active downloads to finish) -// - throttle: task callback to check if the processing queue is full and activate throttling (bound memory use) -// - reserve: task callback to reserve new download tasks to a particular peer (also signals partial completions) -// - fetchHook: tester callback to notify of new tasks being initiated (allows testing the scheduling logic) -// - fetch: network callback to actually send a particular download request to a physical remote peer -// - cancel: task callback to abort an in-flight download request and allow rescheduling it (in case of lost peer) -// - capacity: network callback to retrieve the estimated type-specific bandwidth capacity of a peer (traffic shaping) -// - idle: network callback to retrieve the currently (type specific) idle peers that can be assigned tasks -// - setIdle: network callback to set a peer back to idle and update its estimated capacity (traffic shaping) -// - kind: textual label of the type being downloaded to display in log messages +// - errCancel: error type to return if the fetch operation is cancelled (mostly makes logging nicer) +// - deliveryCh: channel from which to retrieve downloaded data packets (merged from all concurrent peers) +// - deliver: processing callback to deliver data packets into type specific download queues (usually within `queue`) +// - wakeCh: notification channel for waking the fetcher when new tasks are available (or sync completed) +// - expire: task callback method to abort requests that took too long and return the faulty peers (traffic shaping) +// - pending: task callback for the number of requests still needing download (detect completion/non-completability) +// - inFlight: task callback for the number of in-progress requests (wait for all active downloads to finish) +// - throttle: task callback to check if the processing queue is full and activate throttling (bound memory use) +// - reserve: task callback to reserve new download tasks to a particular peer (also signals partial completions) +// - fetchHook: tester callback to notify of new tasks being initiated (allows testing the scheduling logic) +// - fetch: network callback to actually send a particular download request to a physical remote peer +// - cancel: task callback to abort an in-flight download request and allow rescheduling it (in case of lost peer) +// - capacity: network callback to retrieve the estimated type-specific bandwidth capacity of a peer (traffic shaping) +// - idle: network callback to retrieve the currently (type specific) idle peers that can be assigned tasks +// - setIdle: network callback to set a peer back to idle and update its estimated capacity (traffic shaping) +// - kind: textual label of the type being downloaded to display in log messages func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack) (int, error), wakeCh chan bool, expire func() map[string]int, pending func() int, inFlight func() bool, reserve func(*peerConnection, int) (*fetchRequest, bool, bool), fetchHook func([]*types.Header), fetch func(*peerConnection, *fetchRequest) error, cancel func(*fetchRequest), capacity func(*peerConnection) int, diff --git a/les/downloader/queue.go b/les/downloader/queue.go index 158608ccd7f9..37f44e2d9f37 100644 --- a/les/downloader/queue.go +++ b/les/downloader/queue.go @@ -477,9 +477,10 @@ func (q *queue) ReserveReceipts(p *peerConnection, count int) (*fetchRequest, bo // to access the queue, so they already need a lock anyway. // // Returns: -// item - the fetchRequest -// progress - whether any progress was made -// throttle - if the caller should throttle for a while +// +// item - the fetchRequest +// progress - whether any progress was made +// throttle - if the caller should throttle for a while func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common.Hash]*types.Header, taskQueue *prque.Prque, pendPool map[string]*fetchRequest, kind uint) (*fetchRequest, bool, bool) { // Short circuit if the pool has been depleted, or if the peer's already @@ -705,6 +706,13 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh headerReqTimer.UpdateSince(request.Time) delete(q.headerPendPool, id) + // Hacky: mark that the header was explicitly requested + // This extra field is used by the custom consensus to + // verify against current signerAddressL1 if the header was not requested + for _, header := range headers { + header.Requested = true + } + // Ensure headers can be mapped onto the skeleton chain target := q.headerTaskPool[request.From].Hash() @@ -842,6 +850,11 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, reqTimer.UpdateSince(request.Time) delete(pendPool, id) + // Hacky: mark that the header was explicitly requested + for _, header := range request.Headers { + header.Requested = true + } + // If no data items were retrieved, mark them as unavailable for the origin peer if results == 0 { for _, header := range request.Headers { diff --git a/les/downloader/resultstore.go b/les/downloader/resultstore.go index 8075f507db3a..4a40e0ebd39e 100644 --- a/les/downloader/resultstore.go +++ b/les/downloader/resultstore.go @@ -71,10 +71,11 @@ func (r *resultStore) SetThrottleThreshold(threshold uint64) uint64 { // wants to reserve headers for fetching. // // It returns the following: -// stale - if true, this item is already passed, and should not be requested again -// throttled - if true, the store is at capacity, this particular header is not prio now -// item - the result to store data into -// err - any error that occurred +// +// stale - if true, this item is already passed, and should not be requested again +// throttled - if true, the store is at capacity, this particular header is not prio now +// item - the result to store data into +// err - any error that occurred func (r *resultStore) AddFetch(header *types.Header, fastSync bool) (stale, throttled bool, item *fetchResult, err error) { r.lock.Lock() defer r.lock.Unlock() From ad96fa40d4f6a23ab587544a6c88577701bcf3c2 Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Wed, 22 Jan 2025 10:06:13 +0100 Subject: [PATCH 06/36] Remove Extra from rlp encoding (and hashing) --- core/types/block.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/types/block.go b/core/types/block.go index 8810a464c882..d7296e3a806e 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -79,7 +79,7 @@ type Header struct { GasLimit uint64 `json:"gasLimit" gencodec:"required"` GasUsed uint64 `json:"gasUsed" gencodec:"required"` Time uint64 `json:"timestamp" gencodec:"required"` - Extra []byte `json:"extraData" gencodec:"required"` + Extra []byte `json:"extraData" gencodec:"required" rlp:"-"` MixDigest common.Hash `json:"mixHash"` Nonce BlockNonce `json:"nonce"` @@ -105,7 +105,7 @@ type Header struct { //Hacky: used internally to mark the header as requested by the downloader at the deliver queue // The tag "rlp:\"-\"" ensures it's excluded from the RLP encoding (and thus, from the hash computation). - Requested bool `json:"requested" rlp:"-"` + Requested bool `rlp:"-"` } // field type overrides for gencodec From ec2e3d07ce71d75a460b2c3c86c4ae62ffb722e5 Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Wed, 22 Jan 2025 11:01:08 +0100 Subject: [PATCH 07/36] Implement UpgradableEngine as middleware for Clique/SystemContract --- consensus/consensus_wrapper/consensus.go | 198 +++++++++++++++++++++++ eth/ethconfig/config.go | 21 ++- params/config.go | 1 + 3 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 consensus/consensus_wrapper/consensus.go diff --git a/consensus/consensus_wrapper/consensus.go b/consensus/consensus_wrapper/consensus.go new file mode 100644 index 000000000000..588e7e134317 --- /dev/null +++ b/consensus/consensus_wrapper/consensus.go @@ -0,0 +1,198 @@ +package consensus_wrapper + +import ( + "github.com/scroll-tech/go-ethereum/common" + "github.com/scroll-tech/go-ethereum/consensus" + "github.com/scroll-tech/go-ethereum/core/state" + "github.com/scroll-tech/go-ethereum/core/types" + "github.com/scroll-tech/go-ethereum/rpc" + "math/big" + "sync" +) + +// UpgradableEngine implements consensus.Engine and acts as a middleware to dispatch +// calls to either Clique or SystemContract consensus based on block height. +type UpgradableEngine struct { + // forkBlock is the block number at which the switchover to SystemContract occurs. + forkBlock *big.Int + + // clique is the original Clique consensus engine. + clique consensus.Engine + + // system is the new SystemContract consensus engine. + system consensus.Engine +} + +// NewUpgradableEngine constructs a new upgradable consensus middleware. +func NewUpgradableEngine(forkBlock *big.Int, clique consensus.Engine, system consensus.Engine) *UpgradableEngine { + return &UpgradableEngine{ + forkBlock: forkBlock, + clique: clique, + system: system, + } +} + +// chooseEngine returns the appropriate consensus engine based on the header's number. +func (ue *UpgradableEngine) chooseEngine(header *types.Header) consensus.Engine { + if header.Number == nil { + // Fallback: treat as pre-fork if header number is unknown. + return ue.clique + } + // If block number is >= forkBlock, use the new SystemContract consensus, else use Clique. + if header.Number.Cmp(ue.forkBlock) >= 0 { + return ue.system + } + return ue.clique +} + +// -------------------- +// Methods to implement consensus.Engine + +// Author returns the author of the block based on the header. +func (ue *UpgradableEngine) Author(header *types.Header) (common.Address, error) { + return ue.chooseEngine(header).Author(header) +} + +// VerifyHeader checks whether a header conforms to the consensus rules of the engine. +func (ue *UpgradableEngine) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error { + return ue.chooseEngine(header).VerifyHeader(chain, header, seal) +} + +// VerifyHeaders verifies a batch of headers concurrently. In our use-case, +// headers can only be all system, all clique, or start with clique and then switch once to system. +func (ue *UpgradableEngine) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { + abort := make(chan struct{}) + out := make(chan error) + + // If there are no headers, return a closed error channel. + if len(headers) == 0 { + close(out) + return nil, out + } + + // Choose engine for the first and last header. + firstEngine := ue.chooseEngine(headers[0]) + lastEngine := ue.chooseEngine(headers[len(headers)-1]) + + // If the first header is system, then all headers must be system. + if firstEngine == ue.system { + return firstEngine.VerifyHeaders(chain, headers, seals) + } + + // If first and last headers are both clique, then all headers are clique. + if firstEngine == lastEngine { + return firstEngine.VerifyHeaders(chain, headers, seals) + } + + // Otherwise, headers start as clique then switch to system. Since we assume + // a single switchover, find the first header that uses system. + splitIndex := 0 + for i, header := range headers { + if ue.chooseEngine(header) == ue.system { + splitIndex = i + break + } + } + // It's expected that splitIndex is > 0. + cliqueHeaders := headers[:splitIndex] + cliqueSeals := seals[:splitIndex] + systemHeaders := headers[splitIndex:] + systemSeals := seals[splitIndex:] + + // Create a wait group to merge results. + var wg sync.WaitGroup + wg.Add(2) + + // Launch concurrent verifications. + go func() { + defer wg.Done() + _, cliqueResults := ue.clique.VerifyHeaders(chain, cliqueHeaders, cliqueSeals) + for err := range cliqueResults { + select { + case <-abort: + return + case out <- err: + } + } + }() + + go func() { + defer wg.Done() + _, systemResults := ue.system.VerifyHeaders(chain, systemHeaders, systemSeals) + for err := range systemResults { + select { + case <-abort: + return + case out <- err: + } + } + }() + + // Close the out channel when both verifications are complete. + go func() { + wg.Wait() + close(out) + }() + + return abort, out +} + +// Prepare prepares a block header for sealing. +func (ue *UpgradableEngine) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error { + return ue.chooseEngine(header).Prepare(chain, header) +} + +// Seal instructs the engine to start sealing a block. +func (ue *UpgradableEngine) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error { + return ue.chooseEngine(block.Header()).Seal(chain, block, results, stop) +} + +// CalcDifficulty calculates the block difficulty if applicable. +func (ue *UpgradableEngine) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int { + return ue.chooseEngine(parent).CalcDifficulty(chain, time, parent) +} + +// Finalize finalizes the block, applying any post-transaction rules. +func (ue *UpgradableEngine) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) { + ue.chooseEngine(header).Finalize(chain, header, state, txs, uncles) +} + +// FinalizeAndAssemble finalizes and assembles a new block. +func (ue *UpgradableEngine) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { + return ue.chooseEngine(header).FinalizeAndAssemble(chain, header, state, txs, uncles, receipts) +} + +// VerifyUncles verifies that no uncles are attached to the block. +func (ue *UpgradableEngine) VerifyUncles(chain consensus.ChainReader, block *types.Block) error { + return ue.chooseEngine(block.Header()).VerifyUncles(chain, block) +} + +// APIs returns any RPC APIs exposed by the consensus engine. +func (ue *UpgradableEngine) APIs(chain consensus.ChainHeaderReader) []rpc.API { + // Determine the current chain head. + head := chain.CurrentHeader() + if head == nil { + // Fallback: return the clique APIs (or an empty slice) if we don't have a header. + return ue.clique.APIs(chain) + } + + // Choose engine based on whether the chain head is before or after the fork block. + if head.Number.Cmp(ue.forkBlock) >= 0 { + return ue.system.APIs(chain) + } + return ue.clique.APIs(chain) +} + +// Close terminates the consensus engine. +func (ue *UpgradableEngine) Close() error { + // Always close both engines. + if err := ue.clique.Close(); err != nil { + return err + } + return ue.system.Close() +} + +// SealHash returns the hash of a block prior to it being sealed. +func (ue *UpgradableEngine) SealHash(header *types.Header) common.Hash { + return ue.chooseEngine(header).SealHash(header) +} diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 129118e0df8e..7b53b481065d 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -19,6 +19,7 @@ package ethconfig import ( "context" + "github.com/scroll-tech/go-ethereum/consensus/consensus_wrapper" "math/big" "os" "os/user" @@ -232,13 +233,31 @@ type Config struct { // CreateConsensusEngine creates a consensus engine for the given chain configuration. func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database, l1Client sync_service.EthClient) consensus.Engine { - // If proof-of-authority is requested, set it up + // Case 1: Both SystemContract and Clique are defined: create an upgradable engine. + if chainConfig.SystemContract != nil && chainConfig.Clique != nil { + // Create the Clique engine. + cliqueEngine := clique.New(chainConfig.Clique, db) + // Create the SystemContract engine. + sysEngine := system_contract.New(context.Background(), chainConfig.SystemContract, l1Client) + + // Determine the fork block at which the switch occurs. + var forkBlock *big.Int + if chainConfig.EuclidBlock != nil { + forkBlock = chainConfig.EuclidBlock + } + return consensus_wrapper.NewUpgradableEngine(forkBlock, cliqueEngine, sysEngine) + } + + // Case 2: Only the Clique engine is defined. if chainConfig.Clique != nil { return clique.New(chainConfig.Clique, db) } + + // Case 3: Only the SystemContract engine is defined. if chainConfig.SystemContract != nil { return system_contract.New(context.Background(), chainConfig.SystemContract, l1Client) } + // Otherwise assume proof-of-work switch config.PowMode { case ethash.ModeFake: diff --git a/params/config.go b/params/config.go index dde4856781c3..9baaab88f489 100644 --- a/params/config.go +++ b/params/config.go @@ -633,6 +633,7 @@ type ChainConfig struct { CurieBlock *big.Int `json:"curieBlock,omitempty"` // Curie switch block (nil = no fork, 0 = already on curie) DarwinTime *uint64 `json:"darwinTime,omitempty"` // Darwin switch time (nil = no fork, 0 = already on darwin) DarwinV2Time *uint64 `json:"darwinv2Time,omitempty"` // DarwinV2 switch time (nil = no fork, 0 = already on darwinv2) + EuclidBlock *big.Int `json:"euclidBlock,omitempty"` // Euclid switch block (nil = no fork, 0 = already on euclid) // TerminalTotalDifficulty is the amount of total difficulty reached by // the network that triggers the consensus upgrade. From 20185f28dc13feefe0c8c90354797eabbfbbdce5 Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Thu, 23 Jan 2025 10:23:51 +0100 Subject: [PATCH 08/36] Use isEuclid, add json tag to Requested, make go idiomatic --- .../consensus.go | 21 +++++++------------ core/types/block.go | 2 +- eth/ethconfig/config.go | 15 +++++-------- params/config.go | 7 ++++++- 4 files changed, 20 insertions(+), 25 deletions(-) rename consensus/{consensus_wrapper => wrapper}/consensus.go (92%) diff --git a/consensus/consensus_wrapper/consensus.go b/consensus/wrapper/consensus.go similarity index 92% rename from consensus/consensus_wrapper/consensus.go rename to consensus/wrapper/consensus.go index 588e7e134317..eccbf0d67698 100644 --- a/consensus/consensus_wrapper/consensus.go +++ b/consensus/wrapper/consensus.go @@ -1,4 +1,4 @@ -package consensus_wrapper +package wrapper import ( "github.com/scroll-tech/go-ethereum/common" @@ -14,7 +14,7 @@ import ( // calls to either Clique or SystemContract consensus based on block height. type UpgradableEngine struct { // forkBlock is the block number at which the switchover to SystemContract occurs. - forkBlock *big.Int + isUpgraded func(uint64) bool // clique is the original Clique consensus engine. clique consensus.Engine @@ -24,22 +24,17 @@ type UpgradableEngine struct { } // NewUpgradableEngine constructs a new upgradable consensus middleware. -func NewUpgradableEngine(forkBlock *big.Int, clique consensus.Engine, system consensus.Engine) *UpgradableEngine { +func NewUpgradableEngine(isUpgraded func(uint64) bool, clique consensus.Engine, system consensus.Engine) *UpgradableEngine { return &UpgradableEngine{ - forkBlock: forkBlock, - clique: clique, - system: system, + isUpgraded: isUpgraded, + clique: clique, + system: system, } } // chooseEngine returns the appropriate consensus engine based on the header's number. func (ue *UpgradableEngine) chooseEngine(header *types.Header) consensus.Engine { - if header.Number == nil { - // Fallback: treat as pre-fork if header number is unknown. - return ue.clique - } - // If block number is >= forkBlock, use the new SystemContract consensus, else use Clique. - if header.Number.Cmp(ue.forkBlock) >= 0 { + if ue.isUpgraded(header.Time) { return ue.system } return ue.clique @@ -177,7 +172,7 @@ func (ue *UpgradableEngine) APIs(chain consensus.ChainHeaderReader) []rpc.API { } // Choose engine based on whether the chain head is before or after the fork block. - if head.Number.Cmp(ue.forkBlock) >= 0 { + if ue.isUpgraded(head.Time) { return ue.system.APIs(chain) } return ue.clique.APIs(chain) diff --git a/core/types/block.go b/core/types/block.go index d7296e3a806e..d49d882b8a59 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -105,7 +105,7 @@ type Header struct { //Hacky: used internally to mark the header as requested by the downloader at the deliver queue // The tag "rlp:\"-\"" ensures it's excluded from the RLP encoding (and thus, from the hash computation). - Requested bool `rlp:"-"` + Requested bool `json:"-" rlp:"-"` } // field type overrides for gencodec diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 7b53b481065d..7c0e45b9cacb 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -19,7 +19,7 @@ package ethconfig import ( "context" - "github.com/scroll-tech/go-ethereum/consensus/consensus_wrapper" + "github.com/scroll-tech/go-ethereum/consensus/wrapper" "math/big" "os" "os/user" @@ -240,20 +240,15 @@ func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, co // Create the SystemContract engine. sysEngine := system_contract.New(context.Background(), chainConfig.SystemContract, l1Client) - // Determine the fork block at which the switch occurs. - var forkBlock *big.Int - if chainConfig.EuclidBlock != nil { - forkBlock = chainConfig.EuclidBlock - } - return consensus_wrapper.NewUpgradableEngine(forkBlock, cliqueEngine, sysEngine) + return wrapper.NewUpgradableEngine(chainConfig.IsEuclid, cliqueEngine, sysEngine) } - // Case 2: Only the Clique engine is defined. + //// Case 2: Only the Clique engine is defined. if chainConfig.Clique != nil { return clique.New(chainConfig.Clique, db) } - - // Case 3: Only the SystemContract engine is defined. + // + //// Case 3: Only the SystemContract engine is defined. if chainConfig.SystemContract != nil { return system_contract.New(context.Background(), chainConfig.SystemContract, l1Client) } diff --git a/params/config.go b/params/config.go index 9baaab88f489..a6672f42cf36 100644 --- a/params/config.go +++ b/params/config.go @@ -633,7 +633,7 @@ type ChainConfig struct { CurieBlock *big.Int `json:"curieBlock,omitempty"` // Curie switch block (nil = no fork, 0 = already on curie) DarwinTime *uint64 `json:"darwinTime,omitempty"` // Darwin switch time (nil = no fork, 0 = already on darwin) DarwinV2Time *uint64 `json:"darwinv2Time,omitempty"` // DarwinV2 switch time (nil = no fork, 0 = already on darwinv2) - EuclidBlock *big.Int `json:"euclidBlock,omitempty"` // Euclid switch block (nil = no fork, 0 = already on euclid) + EuclidTime *uint64 `json:"euclidTime,omitempty"` // Euclid switch time (nil = no fork, 0 = already on euclid) // TerminalTotalDifficulty is the amount of total difficulty reached by // the network that triggers the consensus upgrade. @@ -905,6 +905,11 @@ func (c *ChainConfig) IsDarwinV2(now uint64) bool { return isForkedTime(now, c.DarwinV2Time) } +// IsEuclid returns whether num is either equal to the Darwin fork block or greater. +func (c *ChainConfig) IsEuclid(now uint64) bool { + return isForkedTime(now, c.EuclidTime) +} + // IsTerminalPoWBlock returns whether the given block is the last block of PoW stage. func (c *ChainConfig) IsTerminalPoWBlock(parentTotalDiff *big.Int, totalDiff *big.Int) bool { if c.TerminalTotalDifficulty == nil { From 4d00f42fd10ded8f60d17affa2c280c7510b3775 Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Thu, 6 Feb 2025 15:40:19 -0300 Subject: [PATCH 09/36] Changes post integration test --- consensus/system_contract/consensus.go | 19 ++++++++++++------- consensus/system_contract/system_contract.go | 4 ++++ consensus/wrapper/consensus.go | 15 ++++++++++++++- core/types/block.go | 5 +---- eth/backend.go | 11 ++++++++++- eth/ethconfig/config.go | 9 +++------ miner/scroll_worker.go | 9 +++++++-- miner/scroll_worker_test.go | 13 +++++++++++++ 8 files changed, 64 insertions(+), 21 deletions(-) diff --git a/consensus/system_contract/consensus.go b/consensus/system_contract/consensus.go index a78cda3a27fa..0f90daa358b4 100644 --- a/consensus/system_contract/consensus.go +++ b/consensus/system_contract/consensus.go @@ -51,10 +51,11 @@ var ( // errInvalidTimestamp is returned if the timestamp of a block is lower than // the previous block's timestamp + the minimum block period. errInvalidTimestamp = errors.New("invalid timestamp") - // errUnauthorizedSigner is returned if a header is signed by a non-authorized entity. - errUnauthorizedSigner = errors.New("unauthorized signer") ) +// ErrUnauthorizedSigner is returned if a header is signed by a non-authorized entity. +var ErrUnauthorizedSigner = errors.New("unauthorized signer") + // SignerFn hashes and signs the data to be signed by a backing account. type SignerFn func(signer accounts.Account, mimeType string, message []byte) ([]byte, error) @@ -81,7 +82,9 @@ func (s *SystemContract) VerifyHeaders(chain consensus.ChainHeaderReader, header go func() { for i, header := range headers { err := s.verifyHeader(chain, header, headers[:i]) - + if err != nil { + log.Error("Error verifying headers", "err", err) + } select { case <-abort: return @@ -185,7 +188,8 @@ func (s *SystemContract) verifyCascadingFields(chain consensus.ChainHeaderReader defer s.lock.RUnlock() if signer != s.signerAddressL1 { - return errUnauthorizedSigner + log.Error("Unauthorized signer", "Got", signer, "Expected:", s.signerAddressL1) + return ErrUnauthorizedSigner } } @@ -243,25 +247,26 @@ func (s *SystemContract) FinalizeAndAssemble(chain consensus.ChainHeaderReader, // the local signing credentials. func (s *SystemContract) 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 errUnknownBlock } + // For 0-period chains, refuse to seal empty blocks (no reward but would spin sealing) if s.config.Period == 0 && len(block.Transactions()) == 0 { return errors.New("sealing paused while waiting for transactions") } + // Don't hold the signer fields for the entire sealing procedure s.lock.RLock() signer, signFn := s.signer, s.signFn signerAddressL1 := s.signerAddressL1 s.lock.RUnlock() - // Bail out if we're unauthorized to sign a block + // Bail out if we are unauthorized to sign a block if signer != signerAddressL1 { - return errors.New("local node is not authorized to sign this block") + return ErrUnauthorizedSigner } // Sweet, the protocol permits us to sign the block, wait for our time diff --git a/consensus/system_contract/system_contract.go b/consensus/system_contract/system_contract.go index 764e4136e71e..efa3f2e3495a 100644 --- a/consensus/system_contract/system_contract.go +++ b/consensus/system_contract/system_contract.go @@ -35,6 +35,8 @@ type SystemContract struct { func New(ctx context.Context, config *params.SystemContractConfig, client sync_service.EthClient) *SystemContract { ctx, cancel := context.WithCancel(ctx) address, err := client.StorageAt(ctx, config.SystemContractAddress, config.SystemContractSlot, nil) + //ATT just did this for testing! + //address = common.Hex2Bytes("0x756EA06BDEe36de11F22DCca45a31d8a178eF3c6") if err != nil { log.Error("failed to get signer address from L1 System Contract", "err", err) } @@ -53,6 +55,7 @@ func New(ctx context.Context, config *params.SystemContractConfig, client sync_s // Authorize injects a private key into the consensus engine to mint new blocks // with. func (s *SystemContract) Authorize(signer common.Address, signFn SignerFn) { + log.Info("Authorizing system contract", "signer", signer.Hex()) s.lock.Lock() defer s.lock.Unlock() @@ -80,6 +83,7 @@ func (s *SystemContract) Start() { log.Error("failed to get signer address from L1 System Contract", "err", err) } bAddress := common.BytesToAddress(address) + log.Info("Read address from system contract", "address", bAddress.Hex()) s.lock.RLock() addressChanged := s.signerAddressL1 != bAddress s.lock.RUnlock() diff --git a/consensus/wrapper/consensus.go b/consensus/wrapper/consensus.go index eccbf0d67698..49792b626e82 100644 --- a/consensus/wrapper/consensus.go +++ b/consensus/wrapper/consensus.go @@ -3,6 +3,8 @@ package wrapper import ( "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/consensus" + "github.com/scroll-tech/go-ethereum/consensus/clique" + "github.com/scroll-tech/go-ethereum/consensus/system_contract" "github.com/scroll-tech/go-ethereum/core/state" "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/rpc" @@ -11,7 +13,7 @@ import ( ) // UpgradableEngine implements consensus.Engine and acts as a middleware to dispatch -// calls to either Clique or SystemContract consensus based on block height. +// calls to either Clique or SystemContract consensus. type UpgradableEngine struct { // forkBlock is the block number at which the switchover to SystemContract occurs. isUpgraded func(uint64) bool @@ -191,3 +193,14 @@ func (ue *UpgradableEngine) Close() error { func (ue *UpgradableEngine) SealHash(header *types.Header) common.Hash { return ue.chooseEngine(header).SealHash(header) } + +// Authorize injects a private key into the consensus engine to mint new blocks +// with. +func (ue *UpgradableEngine) Authorize(signer common.Address, signFn clique.SignerFn, signFn2 system_contract.SignerFn) { + if cliqueEngine, ok := ue.clique.(*clique.Clique); ok { + cliqueEngine.Authorize(signer, signFn) + } + if sysContractEngine, ok := ue.system.(*system_contract.SystemContract); ok { + sysContractEngine.Authorize(signer, signFn2) + } +} diff --git a/core/types/block.go b/core/types/block.go index d49d882b8a59..26ce365b0910 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -79,7 +79,7 @@ type Header struct { GasLimit uint64 `json:"gasLimit" gencodec:"required"` GasUsed uint64 `json:"gasUsed" gencodec:"required"` Time uint64 `json:"timestamp" gencodec:"required"` - Extra []byte `json:"extraData" gencodec:"required" rlp:"-"` + Extra []byte `json:"extraData" gencodec:"required"` MixDigest common.Hash `json:"mixHash"` Nonce BlockNonce `json:"nonce"` @@ -103,8 +103,6 @@ type Header struct { ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` //Hacky: used internally to mark the header as requested by the downloader at the deliver queue - // The tag "rlp:\"-\"" ensures it's excluded from the RLP encoding (and thus, from the hash computation). - Requested bool `json:"-" rlp:"-"` } @@ -383,7 +381,6 @@ func CalcUncleHash(uncles []*Header) common.Hash { // the sealed one. func (b *Block) WithSeal(header *Header) *Block { cpy := *header - return &Block{ header: &cpy, transactions: b.transactions, diff --git a/eth/backend.go b/eth/backend.go index acbec39418af..b8d5d68ec713 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -21,6 +21,7 @@ import ( "context" "errors" "fmt" + "github.com/scroll-tech/go-ethereum/consensus/wrapper" "math/big" "runtime" "sync" @@ -151,6 +152,7 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client sync_service.EthCl if err := pruner.RecoverPruning(stack.ResolvePath(""), chainDb, stack.ResolvePath(config.TrieCleanCacheJournal)); err != nil { log.Error("Failed to recover state", "error", err) } + eth := &Ethereum{ config: config, chainDb: chainDb, @@ -523,7 +525,14 @@ func (s *Ethereum) StartMining(threads int) error { log.Error("Cannot start mining without etherbase", "err", err) return fmt.Errorf("etherbase missing: %v", err) } - if clique, ok := s.engine.(*clique.Clique); ok { + if wrapper, ok := s.engine.(*wrapper.UpgradableEngine); ok { + wallet, err := s.accountManager.Find(accounts.Account{Address: eb}) + if wallet == nil || err != nil { + log.Error("Etherbase account unavailable locally", "err", err) + return fmt.Errorf("signer missing: %v", err) + } + wrapper.Authorize(eb, wallet.SignData, wallet.SignData) + } else if clique, ok := s.engine.(*clique.Clique); ok { wallet, err := s.accountManager.Find(accounts.Account{Address: eb}) if wallet == nil || err != nil { log.Error("Etherbase account unavailable locally", "err", err) diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 7c0e45b9cacb..69401e90a704 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -235,20 +235,17 @@ type Config struct { func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database, l1Client sync_service.EthClient) consensus.Engine { // Case 1: Both SystemContract and Clique are defined: create an upgradable engine. if chainConfig.SystemContract != nil && chainConfig.Clique != nil { - // Create the Clique engine. cliqueEngine := clique.New(chainConfig.Clique, db) - // Create the SystemContract engine. sysEngine := system_contract.New(context.Background(), chainConfig.SystemContract, l1Client) - return wrapper.NewUpgradableEngine(chainConfig.IsEuclid, cliqueEngine, sysEngine) } - //// Case 2: Only the Clique engine is defined. + // Case 2: Only the Clique engine is defined. if chainConfig.Clique != nil { return clique.New(chainConfig.Clique, db) } - // - //// Case 3: Only the SystemContract engine is defined. + + // Case 3: Only the SystemContract engine is defined. if chainConfig.SystemContract != nil { return system_contract.New(context.Background(), chainConfig.SystemContract, l1Client) } diff --git a/miner/scroll_worker.go b/miner/scroll_worker.go index e152878d40e6..e2570cd13341 100644 --- a/miner/scroll_worker.go +++ b/miner/scroll_worker.go @@ -19,6 +19,7 @@ package miner import ( "errors" "fmt" + "github.com/scroll-tech/go-ethereum/consensus/system_contract" "math" "math/big" "sync" @@ -358,8 +359,11 @@ func (w *worker) mainLoop() { } var retryableCommitError *retryableCommitError - if errors.As(err, &retryableCommitError) { + if errors.As(err, &retryableCommitError) || errors.Is(err, system_contract.ErrUnauthorizedSigner) { log.Warn("failed to commit to a block, retrying", "err", err) + if errors.Is(err, system_contract.ErrUnauthorizedSigner) { + time.Sleep(5 * time.Second) // half the time it takes for the system contract consensus to read and update the address locally. + } if _, err = w.tryCommitNewWork(time.Now(), w.current.header.ParentHash, w.current.reorging, w.current.reorgReason); err != nil { continue } @@ -367,7 +371,6 @@ func (w *worker) mainLoop() { log.Error("failed to mine block", "err", err) w.current = nil } - idleStart := time.Now() select { case <-w.startCh: @@ -481,6 +484,7 @@ func (w *worker) newWork(now time.Time, parentHash common.Hash, reorging bool, r if err := w.engine.Prepare(w.chain, header); err != nil { return fmt.Errorf("failed to prepare header for mining: %w", err) } + prepareTimer.UpdateSince(prepareStart) var nextL1MsgIndex uint64 @@ -523,6 +527,7 @@ func (w *worker) newWork(now time.Time, parentHash common.Hash, reorging bool, r // tryCommitNewWork func (w *worker) tryCommitNewWork(now time.Time, parent common.Hash, reorging bool, reorgReason error) (common.Hash, error) { err := w.newWork(now, parent, reorging, reorgReason) + if err != nil { return common.Hash{}, fmt.Errorf("failed creating new work: %w", err) } diff --git a/miner/scroll_worker_test.go b/miner/scroll_worker_test.go index 70ec4a9582d3..0ebadd686753 100644 --- a/miner/scroll_worker_test.go +++ b/miner/scroll_worker_test.go @@ -17,9 +17,12 @@ package miner import ( + "github.com/scroll-tech/go-ethereum/consensus/wrapper" + "github.com/scroll-tech/go-ethereum/log" "math" "math/big" "math/rand" + "reflect" "testing" "time" @@ -128,7 +131,17 @@ func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}, } + log.Info("Test engine type: ", "type:", reflect.TypeOf(engine)) + switch e := engine.(type) { + case *wrapper.UpgradableEngine: + log.Info("Upgradable Engine type, fall over to Clique for now", ":", "NVM") + gspec.ExtraData = make([]byte, 32+common.AddressLength+crypto.SignatureLength) + gspec.Timestamp = uint64(time.Now().Unix()) + copy(gspec.ExtraData[32:32+common.AddressLength], testBankAddress.Bytes()) + e.Authorize(testBankAddress, func(account accounts.Account, s string, data []byte) ([]byte, error) { + return crypto.Sign(crypto.Keccak256(data), testBankKey) + }) case *clique.Clique: gspec.ExtraData = make([]byte, 32+common.AddressLength+crypto.SignatureLength) gspec.Timestamp = uint64(time.Now().Unix()) From 80e06b856ad9bec7436b69b839a6377b39811cdd Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Thu, 6 Feb 2025 15:50:22 -0300 Subject: [PATCH 10/36] Remove comment --- consensus/system_contract/system_contract.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/consensus/system_contract/system_contract.go b/consensus/system_contract/system_contract.go index efa3f2e3495a..b09612c6aae7 100644 --- a/consensus/system_contract/system_contract.go +++ b/consensus/system_contract/system_contract.go @@ -35,8 +35,6 @@ type SystemContract struct { func New(ctx context.Context, config *params.SystemContractConfig, client sync_service.EthClient) *SystemContract { ctx, cancel := context.WithCancel(ctx) address, err := client.StorageAt(ctx, config.SystemContractAddress, config.SystemContractSlot, nil) - //ATT just did this for testing! - //address = common.Hex2Bytes("0x756EA06BDEe36de11F22DCca45a31d8a178eF3c6") if err != nil { log.Error("failed to get signer address from L1 System Contract", "err", err) } From d4c725e0d99f67ee80ee9056667bed4f8acb9a2b Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Fri, 7 Feb 2025 13:54:11 -0300 Subject: [PATCH 11/36] Address comments --- consensus/system_contract/consensus.go | 4 ++-- eth/ethconfig/config.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/consensus/system_contract/consensus.go b/consensus/system_contract/consensus.go index 0f90daa358b4..6416d5dbfa63 100644 --- a/consensus/system_contract/consensus.go +++ b/consensus/system_contract/consensus.go @@ -113,7 +113,7 @@ func (s *SystemContract) verifyHeader(chain consensus.ChainHeaderReader, header return errInvalidNonce } // Check that the extra-data contains signature - if header.Number != big.NewInt(0) && len(header.Extra) != extraSeal { + if header.Number.Cmp(big.NewInt(0)) != 0 && len(header.Extra) != extraSeal { return errMissingSignature } // Ensure that the mix digest is zero @@ -125,7 +125,7 @@ func (s *SystemContract) verifyHeader(chain consensus.ChainHeaderReader, header return errInvalidUncleHash } // Ensure that the difficulty is one - if header.Difficulty.Cmp(common.Big0) != 1 { + if header.Difficulty.Cmp(common.Big1) != 0 { return errInvalidDifficulty } // Verify that the gas limit is <= 2^63-1 diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 69401e90a704..36eed92482f4 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -237,7 +237,7 @@ func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, co if chainConfig.SystemContract != nil && chainConfig.Clique != nil { cliqueEngine := clique.New(chainConfig.Clique, db) sysEngine := system_contract.New(context.Background(), chainConfig.SystemContract, l1Client) - return wrapper.NewUpgradableEngine(chainConfig.IsEuclid, cliqueEngine, sysEngine) + return wrapper.NewUpgradableEngine(chainConfig.IsEuclidV2, cliqueEngine, sysEngine) } // Case 2: Only the Clique engine is defined. From 8f961e713a4db1c83a51a5cf0176269b800b5f3b Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Mon, 10 Feb 2025 11:39:08 -0300 Subject: [PATCH 12/36] Merge --- rollup/l1/types.go | 1 + 1 file changed, 1 insertion(+) diff --git a/rollup/l1/types.go b/rollup/l1/types.go index 8c030815ec28..744ff0575a00 100644 --- a/rollup/l1/types.go +++ b/rollup/l1/types.go @@ -19,4 +19,5 @@ type Client interface { TransactionByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, isPending bool, err error) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) + StorageAt(ctx context.Context, contract common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) } From ebd8e617d6168762eddd59d1abcacd2be35c589c Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Thu, 13 Feb 2025 12:57:06 -0300 Subject: [PATCH 13/36] New field BlockSignature (not used in hashing/JSON) --- consensus/system_contract/consensus.go | 13 ++++++++----- core/types/block.go | 11 ++++++++++- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/consensus/system_contract/consensus.go b/consensus/system_contract/consensus.go index 6416d5dbfa63..a7ccfc1200d4 100644 --- a/consensus/system_contract/consensus.go +++ b/consensus/system_contract/consensus.go @@ -51,6 +51,8 @@ var ( // errInvalidTimestamp is returned if the timestamp of a block is lower than // the previous block's timestamp + the minimum block period. errInvalidTimestamp = errors.New("invalid timestamp") + // errInvalidExtra is returned if the extra data is not empty + errInvalidExtra = errors.New("invalid extra") ) // ErrUnauthorizedSigner is returned if a header is signed by a non-authorized entity. @@ -113,7 +115,7 @@ func (s *SystemContract) verifyHeader(chain consensus.ChainHeaderReader, header return errInvalidNonce } // Check that the extra-data contains signature - if header.Number.Cmp(big.NewInt(0)) != 0 && len(header.Extra) != extraSeal { + if header.Number.Cmp(big.NewInt(0)) != 0 && len(header.BlockSignature) != extraSeal { return errMissingSignature } // Ensure that the mix digest is zero @@ -133,7 +135,7 @@ func (s *SystemContract) verifyHeader(chain consensus.ChainHeaderReader, header if header.GasLimit > cap { return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, cap) } - // All basic checks passed, verify cascading fields + //// All basic checks passed, verify cascading fields return s.verifyCascadingFields(chain, header, parents) } @@ -208,7 +210,8 @@ func (s *SystemContract) VerifyUncles(chain consensus.ChainReader, block *types. // Prepare initializes the consensus fields of a block header according to the // rules of a particular engine. Update only timestamp and prepare ExtraData for Signature func (s *SystemContract) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error { - header.Extra = make([]byte, extraSeal) + header.BlockSignature = make([]byte, extraSeal) + // Ensure the timestamp has the correct delay parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) if parent == nil { @@ -277,7 +280,7 @@ func (s *SystemContract) Seal(chain consensus.ChainHeaderReader, block *types.Bl if err != nil { return err } - copy(header.Extra[0:], sighash) + copy(header.BlockSignature[0:], sighash) // Wait until sealing is terminated or delay timeout. log.Trace("Waiting for slot to sign and propagate", "delay", common.PrettyDuration(delay)) go func() { @@ -314,7 +317,7 @@ func SealHash(header *types.Header) (hash common.Hash) { // ecrecover extracts the Ethereum account address from a signed header. func ecrecover(header *types.Header) (common.Address, error) { - signature := header.Extra[0:] + signature := header.BlockSignature[0:] // Recover the public key and the Ethereum address pubkey, err := crypto.Ecrecover(SealHash(header).Bytes(), signature) diff --git a/core/types/block.go b/core/types/block.go index 26ce365b0910..04427953d6bc 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -83,6 +83,9 @@ type Header struct { MixDigest common.Hash `json:"mixHash"` Nonce BlockNonce `json:"nonce"` + // BlockSignature was added by EuclidV2 to make Extra empty and is ignored during hashing + BlockSignature []byte `json:"-" rlp:"optional"` + // BaseFee was added by EIP-1559 and is ignored in legacy headers. BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"` @@ -121,7 +124,9 @@ type headerMarshaling struct { // Hash returns the block hash of the header, which is simply the keccak256 hash of its // RLP encoding. func (h *Header) Hash() common.Hash { - return rlpHash(h) + hCopy := CopyHeader(h) + hCopy.BlockSignature = nil + return rlpHash(hCopy) } var headerSize = common.StorageSize(reflect.TypeOf(Header{}).Size()) @@ -265,6 +270,10 @@ func CopyHeader(h *Header) *Header { cpy.Extra = make([]byte, len(h.Extra)) copy(cpy.Extra, h.Extra) } + if len(h.BlockSignature) > 0 { + cpy.BlockSignature = make([]byte, len(h.BlockSignature)) + copy(cpy.BlockSignature, h.BlockSignature) + } return &cpy } From c3f5b4654f3841171b649fcf5ed1dc4de917de3f Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Thu, 13 Feb 2025 12:58:33 -0300 Subject: [PATCH 14/36] Enforce .Extra to be an empty slice --- consensus/system_contract/consensus.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/consensus/system_contract/consensus.go b/consensus/system_contract/consensus.go index a7ccfc1200d4..b1bef6f20a44 100644 --- a/consensus/system_contract/consensus.go +++ b/consensus/system_contract/consensus.go @@ -135,6 +135,9 @@ func (s *SystemContract) verifyHeader(chain consensus.ChainHeaderReader, header if header.GasLimit > cap { return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, cap) } + if len(header.Extra) > 0 { + return errInvalidExtra + } //// All basic checks passed, verify cascading fields return s.verifyCascadingFields(chain, header, parents) } @@ -211,7 +214,7 @@ func (s *SystemContract) VerifyUncles(chain consensus.ChainReader, block *types. // rules of a particular engine. Update only timestamp and prepare ExtraData for Signature func (s *SystemContract) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error { header.BlockSignature = make([]byte, extraSeal) - + header.Extra = []byte{} // Ensure the timestamp has the correct delay parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) if parent == nil { From 3190585f9a37f5ebc5e323daf27d0a4fd19a6188 Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Thu, 13 Feb 2025 13:09:23 -0300 Subject: [PATCH 15/36] replace header.Requested for header.IsNewBlock --- consensus/system_contract/consensus.go | 4 ++-- core/types/block.go | 2 +- eth/downloader/queue.go | 12 ------------ les/downloader/queue.go | 12 ------------ 4 files changed, 3 insertions(+), 27 deletions(-) diff --git a/consensus/system_contract/consensus.go b/consensus/system_contract/consensus.go index b1bef6f20a44..98ac343edbd8 100644 --- a/consensus/system_contract/consensus.go +++ b/consensus/system_contract/consensus.go @@ -182,8 +182,8 @@ func (s *SystemContract) verifyCascadingFields(chain consensus.ChainHeaderReader return err } - // only if block header has NOT been requested, then verify the signature against the current signer - if !header.Requested { + // only if block header comes from a new block msg, then verify the signature against the current signer + if header.IsNewBlock { signer, err := ecrecover(header) if err != nil { return err diff --git a/core/types/block.go b/core/types/block.go index 04427953d6bc..da1342147d3b 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -106,7 +106,7 @@ type Header struct { ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` //Hacky: used internally to mark the header as requested by the downloader at the deliver queue - Requested bool `json:"-" rlp:"-"` + IsNewBlock bool `json:"-" rlp:"-"` } // field type overrides for gencodec diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index 37f44e2d9f37..c6fa78729a0e 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -706,13 +706,6 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh headerReqTimer.UpdateSince(request.Time) delete(q.headerPendPool, id) - // Hacky: mark that the header was explicitly requested - // This extra field is used by the custom consensus to - // verify against current signerAddressL1 if the header was not requested - for _, header := range headers { - header.Requested = true - } - // Ensure headers can be mapped onto the skeleton chain target := q.headerTaskPool[request.From].Hash() @@ -850,11 +843,6 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, reqTimer.UpdateSince(request.Time) delete(pendPool, id) - // Hacky: mark that the header was explicitly requested - for _, header := range request.Headers { - header.Requested = true - } - // If no data items were retrieved, mark them as unavailable for the origin peer if results == 0 { for _, header := range request.Headers { diff --git a/les/downloader/queue.go b/les/downloader/queue.go index 37f44e2d9f37..c6fa78729a0e 100644 --- a/les/downloader/queue.go +++ b/les/downloader/queue.go @@ -706,13 +706,6 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh headerReqTimer.UpdateSince(request.Time) delete(q.headerPendPool, id) - // Hacky: mark that the header was explicitly requested - // This extra field is used by the custom consensus to - // verify against current signerAddressL1 if the header was not requested - for _, header := range headers { - header.Requested = true - } - // Ensure headers can be mapped onto the skeleton chain target := q.headerTaskPool[request.From].Hash() @@ -850,11 +843,6 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, reqTimer.UpdateSince(request.Time) delete(pendPool, id) - // Hacky: mark that the header was explicitly requested - for _, header := range request.Headers { - header.Requested = true - } - // If no data items were retrieved, mark them as unavailable for the origin peer if results == 0 { for _, header := range request.Headers { From 38135a3d04db58a0e5268cd587cc749bac39a925 Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Thu, 13 Feb 2025 13:22:08 -0300 Subject: [PATCH 16/36] mark new block as IsNewBlock --- consensus/system_contract/consensus.go | 1 + 1 file changed, 1 insertion(+) diff --git a/consensus/system_contract/consensus.go b/consensus/system_contract/consensus.go index 98ac343edbd8..5f1c02ebe752 100644 --- a/consensus/system_contract/consensus.go +++ b/consensus/system_contract/consensus.go @@ -215,6 +215,7 @@ func (s *SystemContract) VerifyUncles(chain consensus.ChainReader, block *types. func (s *SystemContract) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error { header.BlockSignature = make([]byte, extraSeal) header.Extra = []byte{} + header.IsNewBlock = true // Ensure the timestamp has the correct delay parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) if parent == nil { From d59e79b16623469d0f5933994778d1ad78714f33 Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Mon, 17 Feb 2025 11:38:46 -0300 Subject: [PATCH 17/36] Make new RLP optional fields always default for legacy/reth compatibility --- consensus/system_contract/consensus.go | 1 + core/types/block.go | 39 ++++++++++++++++++++++++++ eth/protocols/eth/handlers.go | 12 ++++++++ eth/protocols/eth/peer.go | 11 ++++++++ 4 files changed, 63 insertions(+) diff --git a/consensus/system_contract/consensus.go b/consensus/system_contract/consensus.go index 5f1c02ebe752..3997bc4f6065 100644 --- a/consensus/system_contract/consensus.go +++ b/consensus/system_contract/consensus.go @@ -214,6 +214,7 @@ func (s *SystemContract) VerifyUncles(chain consensus.ChainReader, block *types. // rules of a particular engine. Update only timestamp and prepare ExtraData for Signature func (s *SystemContract) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error { header.BlockSignature = make([]byte, extraSeal) + header.IsEuclidV2 = true header.Extra = []byte{} header.IsNewBlock = true // Ensure the timestamp has the correct delay diff --git a/core/types/block.go b/core/types/block.go index da1342147d3b..9d1b754f7fca 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -86,6 +86,9 @@ type Header struct { // BlockSignature was added by EuclidV2 to make Extra empty and is ignored during hashing BlockSignature []byte `json:"-" rlp:"optional"` + // IsEuclidV2 was added by EuclidV2 to make Extra empty and is ignored during hashing + IsEuclidV2 bool `json:"-" rlp:"optional"` + // BaseFee was added by EIP-1559 and is ignored in legacy headers. BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"` @@ -126,6 +129,10 @@ type headerMarshaling struct { func (h *Header) Hash() common.Hash { hCopy := CopyHeader(h) hCopy.BlockSignature = nil + if hCopy.IsEuclidV2 { + hCopy.IsEuclidV2 = false + hCopy.Extra = []byte{} + } return rlpHash(hCopy) } @@ -172,6 +179,22 @@ func (h *Header) EmptyReceipts() bool { return h.ReceiptHash == EmptyRootHash } +func (h *Header) PrepareForNetwork() { + if h.IsEuclidV2 { + h.IsEuclidV2 = false + h.Extra = h.BlockSignature + h.BlockSignature = nil + } +} + +func (h *Header) PrepareFromNetwork(isEuclidV2 bool) { + if isEuclidV2 { + h.IsEuclidV2 = true + h.BlockSignature = h.Extra + h.Extra = []byte{} + } +} + // Body is a simple (mutable, non-safe) data container for storing and moving // a block's data contents (transactions and uncles) together. type Body struct { @@ -471,6 +494,22 @@ func (b *Block) CountL2Tx() int { return count } +func (b *Block) CopyBlockDeepWithHeader(header *Header) *Block { + return &Block{ + header: CopyHeader(header), + uncles: b.uncles, // slice reference (the slice header is copied but the underlying array is shared) + transactions: b.transactions, // reference copy + // caches (atomic.Value fields) are reused as-is; if necessary, you might want to load and store their values + hash: b.hash, + size: b.size, + l1MsgCount: b.l1MsgCount, + // Other fields are copied by value (td is a pointer so it's shared) + td: b.td, + ReceivedAt: b.ReceivedAt, + ReceivedFrom: b.ReceivedFrom, + } +} + type Blocks []*Block type BlockWithRowConsumption struct { diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go index 367d9a4dbc75..5c6791aa6e6c 100644 --- a/eth/protocols/eth/handlers.go +++ b/eth/protocols/eth/handlers.go @@ -266,6 +266,11 @@ func handleNewBlock(backend Backend, msg Decoder, peer *Peer) error { if err := msg.Decode(ann); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } + + hCopy := ann.Block.Header() + hCopy.PrepareFromNetwork(backend.Chain().Config().IsEuclidV2(hCopy.Time)) + ann.Block = ann.Block.CopyBlockDeepWithHeader(hCopy) + if err := ann.sanityCheck(); err != nil { return err } @@ -294,6 +299,13 @@ func handleBlockHeaders66(backend Backend, msg Decoder, peer *Peer) error { } requestTracker.Fulfil(peer.id, peer.version, BlockHeadersMsg, res.RequestId) + headersCopy := make([]*types.Header, 0, len(res.BlockHeadersPacket)) + for _, header := range res.BlockHeadersPacket { + hCopy := types.CopyHeader(header) + hCopy.PrepareFromNetwork(backend.Chain().Config().IsEuclidV2(header.Time)) + headersCopy = append(headersCopy, hCopy) + } + res.BlockHeadersPacket = headersCopy return backend.Handle(peer, &res.BlockHeadersPacket) } diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go index 8a012868ac8f..d7ab78922723 100644 --- a/eth/protocols/eth/peer.go +++ b/eth/protocols/eth/peer.go @@ -277,6 +277,9 @@ func (p *Peer) AsyncSendNewBlockHash(block *types.Block) { func (p *Peer) SendNewBlock(block *types.Block, td *big.Int) error { // Mark all the block hash as known, but ensure we don't overflow our limits p.knownBlocks.Add(block.Hash()) + hCopy := block.Header() + hCopy.PrepareForNetwork() + block = block.CopyBlockDeepWithHeader(hCopy) return p2p.Send(p.rw, NewBlockMsg, &NewBlockPacket{ Block: block, TD: td, @@ -297,6 +300,14 @@ func (p *Peer) AsyncSendNewBlock(block *types.Block, td *big.Int) { // ReplyBlockHeaders is the eth/66 version of SendBlockHeaders. func (p *Peer) ReplyBlockHeaders(id uint64, headers []*types.Header) error { + headersCopy := make([]*types.Header, 0, len(headers)) + for _, header := range headers { + hCopy := types.CopyHeader(header) + hCopy.PrepareForNetwork() + headersCopy = append(headersCopy, hCopy) + } + headers = headersCopy + return p2p.Send(p.rw, BlockHeadersMsg, BlockHeadersPacket66{ RequestId: id, BlockHeadersPacket: headers, From b077f7158996c1a719aca58dd9b432afea31af0e Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Mon, 17 Feb 2025 12:32:29 -0300 Subject: [PATCH 18/36] Placing new fields as last non-zero rlp:optional values used by Scroll --- core/types/block.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/types/block.go b/core/types/block.go index 9d1b754f7fca..78cd6ffc85d1 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -83,15 +83,15 @@ type Header struct { MixDigest common.Hash `json:"mixHash"` Nonce BlockNonce `json:"nonce"` + // BaseFee was added by EIP-1559 and is ignored in legacy headers. + BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"` + // BlockSignature was added by EuclidV2 to make Extra empty and is ignored during hashing BlockSignature []byte `json:"-" rlp:"optional"` // IsEuclidV2 was added by EuclidV2 to make Extra empty and is ignored during hashing IsEuclidV2 bool `json:"-" rlp:"optional"` - // BaseFee was added by EIP-1559 and is ignored in legacy headers. - BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"` - // WithdrawalsHash was added by EIP-4895 and is ignored in legacy headers. // Included for Ethereum compatibility in Scroll SDK WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` From 6b2a63bad7918760d10e229ff8711bd981d6d7f8 Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Tue, 18 Feb 2025 11:37:19 -0300 Subject: [PATCH 19/36] Penalize nodes that send non-zero Euclid V2 header field values --- core/types/block.go | 8 ++++++++ eth/protocols/eth/handlers.go | 11 ++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/core/types/block.go b/core/types/block.go index 78cd6ffc85d1..507c317f33b2 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -168,6 +168,14 @@ func (h *Header) SanityCheck() error { return nil } +// NetworkCompatibleEuclidV2Fields Enforces that both IsEuclidV2 and BlockSignature are empty when received over the network +func (h *Header) NetworkCompatibleEuclidV2Fields() error { + if h.IsEuclidV2 || h.BlockSignature != nil { + return fmt.Errorf("header contains disallowed Euclid V2 fields (only used locally)") + } + return nil +} + // EmptyBody returns true if there is no additional 'body' to complete the header // that is: no transactions and no uncles. func (h *Header) EmptyBody() bool { diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go index 5c6791aa6e6c..c289d6ee8d29 100644 --- a/eth/protocols/eth/handlers.go +++ b/eth/protocols/eth/handlers.go @@ -19,11 +19,11 @@ package eth import ( "encoding/json" "fmt" - "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/log" "github.com/scroll-tech/go-ethereum/metrics" + "github.com/scroll-tech/go-ethereum/p2p" "github.com/scroll-tech/go-ethereum/rlp" "github.com/scroll-tech/go-ethereum/trie" ) @@ -268,6 +268,11 @@ func handleNewBlock(backend Backend, msg Decoder, peer *Peer) error { } hCopy := ann.Block.Header() + if err := hCopy.NetworkCompatibleEuclidV2Fields(); err != nil { + peer.Peer.Disconnect(p2p.DiscUselessPeer) + return err + } + hCopy.PrepareFromNetwork(backend.Chain().Config().IsEuclidV2(hCopy.Time)) ann.Block = ann.Block.CopyBlockDeepWithHeader(hCopy) @@ -302,6 +307,10 @@ func handleBlockHeaders66(backend Backend, msg Decoder, peer *Peer) error { headersCopy := make([]*types.Header, 0, len(res.BlockHeadersPacket)) for _, header := range res.BlockHeadersPacket { hCopy := types.CopyHeader(header) + if err := hCopy.NetworkCompatibleEuclidV2Fields(); err != nil { + peer.Peer.Disconnect(p2p.DiscUselessPeer) + return err + } hCopy.PrepareFromNetwork(backend.Chain().Config().IsEuclidV2(header.Time)) headersCopy = append(headersCopy, hCopy) } From 3fa378d753fae96bae92034bd5fc57ea9c179dd4 Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Thu, 20 Feb 2025 11:49:47 -0300 Subject: [PATCH 20/36] Bring back .Requested to downloader instead of .IsNewBlock --- core/types/block.go | 2 +- eth/downloader/queue.go | 10 ++++++++++ les/downloader/queue.go | 10 ++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/core/types/block.go b/core/types/block.go index 507c317f33b2..bba706827b20 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -109,7 +109,7 @@ type Header struct { ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` //Hacky: used internally to mark the header as requested by the downloader at the deliver queue - IsNewBlock bool `json:"-" rlp:"-"` + Requested bool `json:"-" rlp:"-"` } // field type overrides for gencodec diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index c6fa78729a0e..d27d1b5b8e80 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -706,6 +706,11 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh headerReqTimer.UpdateSince(request.Time) delete(q.headerPendPool, id) + // Hacky: mark that the header was explicitly requested + for _, header := range headers { + header.Requested = true + } + // Ensure headers can be mapped onto the skeleton chain target := q.headerTaskPool[request.From].Hash() @@ -843,6 +848,11 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, reqTimer.UpdateSince(request.Time) delete(pendPool, id) + // Hacky: mark that the header was explicitly requested + for _, header := range request.Headers { + header.Requested = true + } + // If no data items were retrieved, mark them as unavailable for the origin peer if results == 0 { for _, header := range request.Headers { diff --git a/les/downloader/queue.go b/les/downloader/queue.go index c6fa78729a0e..d27d1b5b8e80 100644 --- a/les/downloader/queue.go +++ b/les/downloader/queue.go @@ -706,6 +706,11 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh headerReqTimer.UpdateSince(request.Time) delete(q.headerPendPool, id) + // Hacky: mark that the header was explicitly requested + for _, header := range headers { + header.Requested = true + } + // Ensure headers can be mapped onto the skeleton chain target := q.headerTaskPool[request.From].Hash() @@ -843,6 +848,11 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, reqTimer.UpdateSince(request.Time) delete(pendPool, id) + // Hacky: mark that the header was explicitly requested + for _, header := range request.Headers { + header.Requested = true + } + // If no data items were retrieved, mark them as unavailable for the origin peer if results == 0 { for _, header := range request.Headers { From 66380577c2e42d50f67cad8f7476a4118ebbb367 Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Mon, 24 Feb 2025 16:02:08 -0300 Subject: [PATCH 21/36] Replace IsNewBlock for Requested --- consensus/system_contract/consensus.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/consensus/system_contract/consensus.go b/consensus/system_contract/consensus.go index 3997bc4f6065..635f9f1120e9 100644 --- a/consensus/system_contract/consensus.go +++ b/consensus/system_contract/consensus.go @@ -114,8 +114,8 @@ func (s *SystemContract) verifyHeader(chain consensus.ChainHeaderReader, header if header.Nonce != (types.BlockNonce{}) { return errInvalidNonce } - // Check that the extra-data contains signature - if header.Number.Cmp(big.NewInt(0)) != 0 && len(header.BlockSignature) != extraSeal { + // Check that the BlockSignature contains signature if block is not requested + if header.Number.Cmp(big.NewInt(0)) != 0 && len(header.BlockSignature) != extraSeal && !header.Requested { return errMissingSignature } // Ensure that the mix digest is zero @@ -182,8 +182,8 @@ func (s *SystemContract) verifyCascadingFields(chain consensus.ChainHeaderReader return err } - // only if block header comes from a new block msg, then verify the signature against the current signer - if header.IsNewBlock { + // only if block header has NOT been requested, then verify the signature against the current signer + if !header.Requested { signer, err := ecrecover(header) if err != nil { return err @@ -216,7 +216,6 @@ func (s *SystemContract) Prepare(chain consensus.ChainHeaderReader, header *type header.BlockSignature = make([]byte, extraSeal) header.IsEuclidV2 = true header.Extra = []byte{} - header.IsNewBlock = true // Ensure the timestamp has the correct delay parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) if parent == nil { From cd6344dd03bc591bd96a7deb6461f603f65bf387 Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Mon, 24 Feb 2025 17:46:49 -0300 Subject: [PATCH 22/36] Address comments --- consensus/system_contract/consensus.go | 6 +-- consensus/system_contract/system_contract.go | 54 +++++++++---------- .../system_contract/system_contract_test.go | 8 +-- core/types/block.go | 4 +- eth/backend.go | 1 - eth/downloader/downloader.go | 42 +++++++-------- eth/downloader/queue.go | 1 - eth/downloader/resultstore.go | 11 ++-- 8 files changed, 60 insertions(+), 67 deletions(-) diff --git a/consensus/system_contract/consensus.go b/consensus/system_contract/consensus.go index 635f9f1120e9..81e27381639f 100644 --- a/consensus/system_contract/consensus.go +++ b/consensus/system_contract/consensus.go @@ -215,7 +215,7 @@ func (s *SystemContract) VerifyUncles(chain consensus.ChainReader, block *types. func (s *SystemContract) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error { header.BlockSignature = make([]byte, extraSeal) header.IsEuclidV2 = true - header.Extra = []byte{} + header.Extra = nil // Ensure the timestamp has the correct delay parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) if parent == nil { @@ -232,7 +232,7 @@ func (s *SystemContract) Prepare(chain consensus.ChainHeaderReader, header *type } // Finalize implements consensus.Engine. There is no post-transaction -// consensus rules in clique, therefore no rules here +// No rules here func (s *SystemContract) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) { // No block rewards in PoA, so the state remains as is } @@ -381,7 +381,7 @@ func encodeSigHeader(w io.Writer, header *types.Header) { enc = append(enc, header.BaseFee) } if header.WithdrawalsHash != nil { - panic("unexpected withdrawal hash value in clique") + panic("unexpected withdrawal hash value") } if err := rlp.Encode(w, enc); err != nil { panic("can't encode: " + err.Error()) diff --git a/consensus/system_contract/system_contract.go b/consensus/system_contract/system_contract.go index b09612c6aae7..471a7fa24b38 100644 --- a/consensus/system_contract/system_contract.go +++ b/consensus/system_contract/system_contract.go @@ -46,7 +46,7 @@ func New(ctx context.Context, config *params.SystemContractConfig, client sync_s ctx: ctx, cancel: cancel, } - systemContract.Start() + go systemContract.Start() return systemContract } @@ -63,36 +63,34 @@ func (s *SystemContract) Authorize(signer common.Address, signFn SignerFn) { func (s *SystemContract) Start() { log.Info("starting SystemContract") - go func() { - syncTicker := time.NewTicker(defaultSyncInterval) - defer syncTicker.Stop() - for { - select { - case <-s.ctx.Done(): - return - default: + syncTicker := time.NewTicker(defaultSyncInterval) + defer syncTicker.Stop() + for { + select { + case <-s.ctx.Done(): + return + default: + } + select { + case <-s.ctx.Done(): + return + case <-syncTicker.C: + address, err := s.client.StorageAt(s.ctx, s.config.SystemContractAddress, s.config.SystemContractSlot, nil) + if err != nil { + log.Error("failed to get signer address from L1 System Contract", "err", err) } - select { - case <-s.ctx.Done(): - return - case <-syncTicker.C: - address, err := s.client.StorageAt(s.ctx, s.config.SystemContractAddress, s.config.SystemContractSlot, nil) - if err != nil { - log.Error("failed to get signer address from L1 System Contract", "err", err) - } - bAddress := common.BytesToAddress(address) - log.Info("Read address from system contract", "address", bAddress.Hex()) - s.lock.RLock() - addressChanged := s.signerAddressL1 != bAddress - s.lock.RUnlock() - if addressChanged { - s.lock.Lock() - s.signerAddressL1 = bAddress - s.lock.Unlock() - } + bAddress := common.BytesToAddress(address) + log.Info("Read address from system contract", "address", bAddress.Hex()) + s.lock.RLock() + addressChanged := s.signerAddressL1 != bAddress + s.lock.RUnlock() + if addressChanged { + s.lock.Lock() + s.signerAddressL1 = bAddress + s.lock.Unlock() } } - }() + } } // Close implements consensus.Engine. diff --git a/consensus/system_contract/system_contract_test.go b/consensus/system_contract/system_contract_test.go index db1ff9103220..aab573fa4a00 100644 --- a/consensus/system_contract/system_contract_test.go +++ b/consensus/system_contract/system_contract_test.go @@ -81,11 +81,11 @@ func TestSystemContract_AuthorizeCheck(t *testing.T) { }) // Create a dummy block header. - // We only need the block number and extra data length for this test. + // We only need the block number and blocksignature data length for this test. header := &types.Header{ Number: big.NewInt(100), // We use an extra slice with length equal to extraSeal - Extra: make([]byte, extraSeal), + BlockSignature: make([]byte, extraSeal), } // Call Seal() and expect an error since local signer != authorized signer. @@ -157,8 +157,8 @@ func TestSystemContract_SignsAfterUpdate(t *testing.T) { // Create a dummy header for sealing. header := &types.Header{ - Number: big.NewInt(100), - Extra: make([]byte, extraSeal), + Number: big.NewInt(100), + BlockSignature: make([]byte, extraSeal), } // Construct a new block from the header using NewBlock constructor. diff --git a/core/types/block.go b/core/types/block.go index bba706827b20..e1a1bbeeed61 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -131,7 +131,7 @@ func (h *Header) Hash() common.Hash { hCopy.BlockSignature = nil if hCopy.IsEuclidV2 { hCopy.IsEuclidV2 = false - hCopy.Extra = []byte{} + hCopy.Extra = nil } return rlpHash(hCopy) } @@ -199,7 +199,7 @@ func (h *Header) PrepareFromNetwork(isEuclidV2 bool) { if isEuclidV2 { h.IsEuclidV2 = true h.BlockSignature = h.Extra - h.Extra = []byte{} + h.Extra = nil } } diff --git a/eth/backend.go b/eth/backend.go index dad0ff03c4b7..978ecbdd33f7 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -277,7 +277,6 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client l1.Client) (*Ether } eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock, config.EnableDASyncing) - eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil} if eth.APIBackend.allowUnprotectedTxs { diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 6afb3a852744..b402c4af0261 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -702,11 +702,9 @@ func (d *Downloader) fetchHead(p *peerConnection) (head *types.Header, pivot *ty // calculateRequestSpan calculates what headers to request from a peer when trying to determine the // common ancestor. // It returns parameters to be used for peer.RequestHeadersByNumber: -// -// from - starting block number -// count - number of headers to request -// skip - number of headers to skip -// +// from - starting block number +// count - number of headers to request +// skip - number of headers to skip // and also returns 'max', the last block which is expected to be returned by the remote peers, // given the (from,count,skip) func calculateRequestSpan(remoteHeight, localHeight uint64) (int64, int, int, uint64) { @@ -1321,22 +1319,22 @@ func (d *Downloader) fetchReceipts(from uint64) error { // various callbacks to handle the slight differences between processing them. // // The instrumentation parameters: -// - errCancel: error type to return if the fetch operation is cancelled (mostly makes logging nicer) -// - deliveryCh: channel from which to retrieve downloaded data packets (merged from all concurrent peers) -// - deliver: processing callback to deliver data packets into type specific download queues (usually within `queue`) -// - wakeCh: notification channel for waking the fetcher when new tasks are available (or sync completed) -// - expire: task callback method to abort requests that took too long and return the faulty peers (traffic shaping) -// - pending: task callback for the number of requests still needing download (detect completion/non-completability) -// - inFlight: task callback for the number of in-progress requests (wait for all active downloads to finish) -// - throttle: task callback to check if the processing queue is full and activate throttling (bound memory use) -// - reserve: task callback to reserve new download tasks to a particular peer (also signals partial completions) -// - fetchHook: tester callback to notify of new tasks being initiated (allows testing the scheduling logic) -// - fetch: network callback to actually send a particular download request to a physical remote peer -// - cancel: task callback to abort an in-flight download request and allow rescheduling it (in case of lost peer) -// - capacity: network callback to retrieve the estimated type-specific bandwidth capacity of a peer (traffic shaping) -// - idle: network callback to retrieve the currently (type specific) idle peers that can be assigned tasks -// - setIdle: network callback to set a peer back to idle and update its estimated capacity (traffic shaping) -// - kind: textual label of the type being downloaded to display in log messages +// - errCancel: error type to return if the fetch operation is cancelled (mostly makes logging nicer) +// - deliveryCh: channel from which to retrieve downloaded data packets (merged from all concurrent peers) +// - deliver: processing callback to deliver data packets into type specific download queues (usually within `queue`) +// - wakeCh: notification channel for waking the fetcher when new tasks are available (or sync completed) +// - expire: task callback method to abort requests that took too long and return the faulty peers (traffic shaping) +// - pending: task callback for the number of requests still needing download (detect completion/non-completability) +// - inFlight: task callback for the number of in-progress requests (wait for all active downloads to finish) +// - throttle: task callback to check if the processing queue is full and activate throttling (bound memory use) +// - reserve: task callback to reserve new download tasks to a particular peer (also signals partial completions) +// - fetchHook: tester callback to notify of new tasks being initiated (allows testing the scheduling logic) +// - fetch: network callback to actually send a particular download request to a physical remote peer +// - cancel: task callback to abort an in-flight download request and allow rescheduling it (in case of lost peer) +// - capacity: network callback to retrieve the estimated type-specific bandwidth capacity of a peer (traffic shaping) +// - idle: network callback to retrieve the currently (type specific) idle peers that can be assigned tasks +// - setIdle: network callback to set a peer back to idle and update its estimated capacity (traffic shaping) +// - kind: textual label of the type being downloaded to display in log messages func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack) (int, error), wakeCh chan bool, expire func() map[string]int, pending func() int, inFlight func() bool, reserve func(*peerConnection, int) (*fetchRequest, bool, bool), fetchHook func([]*types.Header), fetch func(*peerConnection, *fetchRequest) error, cancel func(*fetchRequest), capacity func(*peerConnection) int, @@ -2010,4 +2008,4 @@ func (d *Downloader) deliver(destCh chan dataPack, packet dataPack, inMeter, dro case <-cancel: return errNoSyncActive } -} +} \ No newline at end of file diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index d27d1b5b8e80..65fd5db91b25 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -477,7 +477,6 @@ func (q *queue) ReserveReceipts(p *peerConnection, count int) (*fetchRequest, bo // to access the queue, so they already need a lock anyway. // // Returns: -// // item - the fetchRequest // progress - whether any progress was made // throttle - if the caller should throttle for a while diff --git a/eth/downloader/resultstore.go b/eth/downloader/resultstore.go index 4a40e0ebd39e..8b45f3ebe760 100644 --- a/eth/downloader/resultstore.go +++ b/eth/downloader/resultstore.go @@ -71,11 +71,10 @@ func (r *resultStore) SetThrottleThreshold(threshold uint64) uint64 { // wants to reserve headers for fetching. // // It returns the following: -// -// stale - if true, this item is already passed, and should not be requested again -// throttled - if true, the store is at capacity, this particular header is not prio now -// item - the result to store data into -// err - any error that occurred +// stale - if true, this item is already passed, and should not be requested again +// throttled - if true, the store is at capacity, this particular header is not prio now +// item - the result to store data into +// err - any error that occurred func (r *resultStore) AddFetch(header *types.Header, fastSync bool) (stale, throttled bool, item *fetchResult, err error) { r.lock.Lock() defer r.lock.Unlock() @@ -192,4 +191,4 @@ func (r *resultStore) Prepare(offset uint64) { if r.resultOffset < offset { r.resultOffset = offset } -} +} \ No newline at end of file From f6bcdd0770005a392736d32eb066d02ad440fdcb Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Mon, 24 Feb 2025 17:51:20 -0300 Subject: [PATCH 23/36] merge --- miner/scroll_worker_test.go | 131 ++++++++++++++++++-- rollup/rollup_sync_service/l1client_test.go | 78 ------------ 2 files changed, 118 insertions(+), 91 deletions(-) delete mode 100644 rollup/rollup_sync_service/l1client_test.go diff --git a/miner/scroll_worker_test.go b/miner/scroll_worker_test.go index 07188fa85248..db18e8673856 100644 --- a/miner/scroll_worker_test.go +++ b/miner/scroll_worker_test.go @@ -17,15 +17,14 @@ package miner import ( - "github.com/scroll-tech/go-ethereum/consensus/wrapper" - "github.com/scroll-tech/go-ethereum/log" + "fmt" "math" "math/big" "math/rand" - "reflect" "testing" "time" + "github.com/agiledragon/gomonkey/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -131,17 +130,7 @@ func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}, } - log.Info("Test engine type: ", "type:", reflect.TypeOf(engine)) - switch e := engine.(type) { - case *wrapper.UpgradableEngine: - log.Info("Upgradable Engine type, fall over to Clique for now", ":", "NVM") - gspec.ExtraData = make([]byte, 32+common.AddressLength+crypto.SignatureLength) - gspec.Timestamp = uint64(time.Now().Unix()) - copy(gspec.ExtraData[32:32+common.AddressLength], testBankAddress.Bytes()) - e.Authorize(testBankAddress, func(account accounts.Account, s string, data []byte) ([]byte, error) { - return crypto.Sign(crypto.Keccak256(data), testBankKey) - }) case *clique.Clique: gspec.ExtraData = make([]byte, 32+common.AddressLength+crypto.SignatureLength) gspec.Timestamp = uint64(time.Now().Unix()) @@ -1250,3 +1239,119 @@ func TestRestartHeadCCC(t *testing.T) { // head should be rechecked by CCC require.NotNil(t, rawdb.ReadBlockRowConsumption(db, headHash)) } + +func newUint64(val uint64) *uint64 { return &val } + +// TestEuclidV2MessageQueue tests L1 messages are correctly processed and included in the block during the +// transition from Euclid to EuclidV2 hard fork. +// - Before EuclidV2 only L1 messages V1 can be included. +// - During the hard fork, we need to ensure that blocks are backdated and all L1 messages V1 are included before the hard fork time. +// - After EuclidV2 only L1 messages V2 can be included. +func TestEuclidV2HardForkMessageQueue(t *testing.T) { + // patch time.Now() to be able to simulate hard fork time + patches := gomonkey.NewPatches() + defer patches.Reset() + + // EuclidV2 hard fork time, leave a big gap so that we can test before and after the hard fork time + euclidV2Time := uint64(10000) + + var timeCount int64 + patches.ApplyFunc(time.Now, func() time.Time { + timeCount++ + return time.Unix(timeCount, 0) + }) + + var ( + engine consensus.Engine + chainConfig *params.ChainConfig + db = rawdb.NewMemoryDatabase() + ) + msgs := []types.L1MessageTx{ + {QueueIndex: 0, Gas: 21016, To: &common.Address{3}, Data: []byte{0x01}, Sender: common.Address{4}}, + {QueueIndex: 1, Gas: 21016, To: &common.Address{1}, Data: []byte{0x01}, Sender: common.Address{2}}, + {QueueIndex: 2, Gas: 21016, To: &common.Address{1}, Data: []byte{0x01}, Sender: common.Address{2}}, + {QueueIndex: 3, Gas: 21016, To: &common.Address{1}, Data: []byte{0x01}, Sender: common.Address{2}}, + {QueueIndex: 4, Gas: 21016, To: &common.Address{1}, Data: []byte{0x01}, Sender: common.Address{2}}, + {QueueIndex: 5, Gas: 21016, To: &common.Address{1}, Data: []byte{0x01}, Sender: common.Address{2}}, + } + rawdb.WriteL1Messages(db, msgs) + rawdb.WriteL1MessageV2StartIndex(db, 4) + + chainConfig = params.AllCliqueProtocolChanges.Clone() + chainConfig.Clique = ¶ms.CliqueConfig{Period: 1, Epoch: 30000} + engine = clique.New(chainConfig.Clique, db) + + chainConfig.Scroll.L1Config = ¶ms.L1Config{ + NumL1MessagesPerBlock: 1, + } + chainConfig.Scroll.FeeVaultAddress = &common.Address{} + chainConfig.Scroll.UseZktrie = false + + chainConfig.EuclidTime = newUint64(0) + chainConfig.EuclidV2Time = newUint64(euclidV2Time) + w, b := newTestWorker(t, chainConfig, engine, db, 0) + defer w.close() + + // This test chain imports the mined blocks. + b.genesis.MustCommit(db) + chain, _ := core.NewBlockChain(db, nil, b.chain.Config(), engine, vm.Config{ + Debug: true, + Tracer: vm.NewStructLogger(&vm.LogConfig{EnableMemory: true, EnableReturnData: true})}, nil, nil) + defer chain.Stop() + + // Wait for mined blocks. + sub := w.mux.Subscribe(core.NewMinedBlockEvent{}) + defer sub.Unsubscribe() + + // Start mining! + w.start() + + var block1Time uint64 + for i := 0; i < 6; i++ { + select { + case ev := <-sub.Chan(): + // After we received the first block, we activate EuclidV2 + if i == 0 { + timeCount = int64(euclidV2Time) + } + + block := ev.Data.(core.NewMinedBlockEvent).Block + fmt.Println("block", block.NumberU64(), block.Time()) + _, err := chain.InsertChain([]*types.Block{block}) + require.NoError(t, err, "failed to insert new mined block %d", block.NumberU64()) + require.Equal(t, 1, len(block.Transactions())) + + queueIndex := block.Transactions()[0].AsL1MessageTx().QueueIndex + require.Equal(t, uint64(i), queueIndex) + + switch i { + case 0: + block1Time = block.Time() + case 1, 2, 3: + // pre EuclidV2, we should include 1 L1 message V1 per block. + // we expect backdated blocks (same time as parent) and all L1 messages V1 to be included before the hard fork time. + + if i == 1 { + require.GreaterOrEqual(t, block.Time(), block1Time, "block %d", block.NumberU64()) + block1Time = block.Time() // due to concurrent mining it might be that block2 is mined before the hard fork time is set + } + // make sure the block is backdated + require.Equal(t, block1Time, block.Time(), "block %d", block.NumberU64()) + // since the block contains L1 message V1 it needs to be included before the hard fork time + require.Less(t, block.Time(), euclidV2Time, "block %d", block.NumberU64()) + case 4, 5: + // after EuclidV2 and when all L1 messages are consumed, we should include 1 L1 message V2 per block + + require.GreaterOrEqual(t, block.Time(), euclidV2Time, "block %d", block.NumberU64()) + } + + // make sure DB is updated correctly + queueIndexNotInDB := rawdb.ReadFirstQueueIndexNotInL2Block(db, block.Hash()) + require.NotNil(t, queueIndexNotInDB) + require.Equal(t, uint64(i+1), *queueIndexNotInDB) + + case <-time.After(3 * time.Second): + t.Fatalf("timeout") + } + } +} diff --git a/rollup/rollup_sync_service/l1client_test.go b/rollup/rollup_sync_service/l1client_test.go deleted file mode 100644 index 397e25a29ade..000000000000 --- a/rollup/rollup_sync_service/l1client_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package rollup_sync_service - -import ( - "context" - "math/big" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/scroll-tech/go-ethereum" - "github.com/scroll-tech/go-ethereum/common" - "github.com/scroll-tech/go-ethereum/core/types" - "github.com/scroll-tech/go-ethereum/rlp" -) - -func TestL1Client(t *testing.T) { - ctx := context.Background() - mockClient := &mockEthClient{} - - scrollChainABI, err := ScrollChainMetaData.GetAbi() - if err != nil { - t.Fatal("failed to get scroll chain abi", "err", err) - } - scrollChainAddress := common.HexToAddress("0x0123456789abcdef") - l1Client, err := NewL1Client(ctx, mockClient, 11155111, scrollChainAddress, scrollChainABI) - require.NoError(t, err, "Failed to initialize L1Client") - - blockNumber, err := l1Client.GetLatestFinalizedBlockNumber() - assert.NoError(t, err, "Error getting latest confirmed block number") - assert.Equal(t, uint64(36), blockNumber, "Unexpected block number") - - logs, err := l1Client.FetchRollupEventsInRange(0, blockNumber) - assert.NoError(t, err, "Error fetching rollup events in range") - assert.Empty(t, logs, "Expected no logs from FetchRollupEventsInRange") -} - -type mockEthClient struct { - txRLP []byte -} - -func (m *mockEthClient) BlockNumber(ctx context.Context) (uint64, error) { - return 11155111, nil -} - -func (m *mockEthClient) ChainID(ctx context.Context) (*big.Int, error) { - return big.NewInt(11155111), nil -} - -func (m *mockEthClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { - return []types.Log{}, nil -} - -func (m *mockEthClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { - return &types.Header{ - Number: big.NewInt(100 - 64), - }, nil -} - -func (m *mockEthClient) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { - return nil, nil -} - -func (m *mockEthClient) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) { - var tx types.Transaction - if err := rlp.DecodeBytes(m.txRLP, &tx); err != nil { - return nil, false, err - } - return &tx, false, nil -} - -func (m *mockEthClient) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { - return nil, nil -} - -func (m *mockEthClient) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { - return nil, nil -} From 80eba61389d5b63cc24e0a9a6009ee12e8ce8048 Mon Sep 17 00:00:00 2001 From: jonastheis <4181434+jonastheis@users.noreply.github.com> Date: Tue, 25 Feb 2025 12:16:54 +0800 Subject: [PATCH 24/36] prevent timing issues in tests --- consensus/system_contract/system_contract.go | 71 +++++++++++-------- .../system_contract/system_contract_test.go | 39 ++++------ eth/ethconfig/config.go | 8 ++- 3 files changed, 64 insertions(+), 54 deletions(-) diff --git a/consensus/system_contract/system_contract.go b/consensus/system_contract/system_contract.go index 471a7fa24b38..f1a0afdecb0f 100644 --- a/consensus/system_contract/system_contract.go +++ b/consensus/system_contract/system_contract.go @@ -38,7 +38,8 @@ func New(ctx context.Context, config *params.SystemContractConfig, client sync_s if err != nil { log.Error("failed to get signer address from L1 System Contract", "err", err) } - systemContract := &SystemContract{ + + return &SystemContract{ config: config, client: client, signerAddressL1: common.BytesToAddress(address), @@ -46,8 +47,6 @@ func New(ctx context.Context, config *params.SystemContractConfig, client sync_s ctx: ctx, cancel: cancel, } - go systemContract.Start() - return systemContract } // Authorize injects a private key into the consensus engine to mint new blocks @@ -62,34 +61,43 @@ func (s *SystemContract) Authorize(signer common.Address, signFn SignerFn) { } func (s *SystemContract) Start() { - log.Info("starting SystemContract") - syncTicker := time.NewTicker(defaultSyncInterval) - defer syncTicker.Stop() - for { - select { - case <-s.ctx.Done(): - return - default: - } - select { - case <-s.ctx.Done(): - return - case <-syncTicker.C: - address, err := s.client.StorageAt(s.ctx, s.config.SystemContractAddress, s.config.SystemContractSlot, nil) - if err != nil { - log.Error("failed to get signer address from L1 System Contract", "err", err) + go func() { + log.Info("starting SystemContract") + syncTicker := time.NewTicker(defaultSyncInterval) + defer syncTicker.Stop() + for { + select { + case <-s.ctx.Done(): + return + default: } - bAddress := common.BytesToAddress(address) - log.Info("Read address from system contract", "address", bAddress.Hex()) - s.lock.RLock() - addressChanged := s.signerAddressL1 != bAddress - s.lock.RUnlock() - if addressChanged { - s.lock.Lock() - s.signerAddressL1 = bAddress - s.lock.Unlock() + select { + case <-s.ctx.Done(): + return + case <-syncTicker.C: + s.fetchAddressFromL1() } } + }() +} + +func (s *SystemContract) fetchAddressFromL1() { + address, err := s.client.StorageAt(s.ctx, s.config.SystemContractAddress, s.config.SystemContractSlot, nil) + if err != nil { + log.Error("failed to get signer address from L1 System Contract", "err", err) + } + bAddress := common.BytesToAddress(address) + + log.Info("Read address from system contract", "address", bAddress.Hex()) + + s.lock.RLock() + addressChanged := s.signerAddressL1 != bAddress + s.lock.RUnlock() + + if addressChanged { + s.lock.Lock() + s.signerAddressL1 = bAddress + s.lock.Unlock() } } @@ -98,3 +106,10 @@ func (s *SystemContract) Close() error { s.cancel() return nil } + +func (s *SystemContract) currentSignerAddressL1() common.Address { + s.lock.RLock() + defer s.lock.RUnlock() + + return s.signerAddressL1 +} diff --git a/consensus/system_contract/system_contract_test.go b/consensus/system_contract/system_contract_test.go index aab573fa4a00..65dc6cf5013c 100644 --- a/consensus/system_contract/system_contract_test.go +++ b/consensus/system_contract/system_contract_test.go @@ -2,6 +2,13 @@ package system_contract import ( "context" + "math/big" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/scroll-tech/go-ethereum" "github.com/scroll-tech/go-ethereum/accounts" "github.com/scroll-tech/go-ethereum/common" @@ -10,11 +17,6 @@ import ( "github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/rollup/sync_service" "github.com/scroll-tech/go-ethereum/trie" - "github.com/stretchr/testify/require" - "math/big" - "sync" - "testing" - "time" ) var _ sync_service.EthClient = &fakeEthClient{} @@ -39,14 +41,9 @@ func TestSystemContract_FetchSigner(t *testing.T) { sys := New(ctx, config, fakeClient) defer sys.Close() - // Since the SystemContract's Start() routine fetches and updates s.signerAddressL1 - // in a separate goroutine, wait a bit for that to complete. - time.Sleep(2 * time.Second) + sys.fetchAddressFromL1() - // Acquire a read lock to safely read the value. - sys.lock.RLock() - actualSigner := sys.signerAddressL1 - sys.lock.RUnlock() + actualSigner := sys.currentSignerAddressL1() // Verify that the fetched signer equals the expectedSigner from our fake client. require.Equal(t, expectedSigner, actualSigner, "The SystemContract should update signerAddressL1 to the value provided by the client") @@ -70,8 +67,7 @@ func TestSystemContract_AuthorizeCheck(t *testing.T) { sys := New(ctx, config, fakeClient) defer sys.Close() - // Wait to ensure that the background routine has updated signerAddressL1. - time.Sleep(2 * time.Second) + sys.fetchAddressFromL1() // Authorize with a different signer than expected. differentSigner := common.HexToAddress("0xABCDEFabcdefABCDEFabcdefabcdefABCDEFABCD") @@ -126,13 +122,10 @@ func TestSystemContract_SignsAfterUpdate(t *testing.T) { sys := New(ctx, config, fakeClient) defer sys.Close() - // Wait for the background routine to poll at least once. - time.Sleep(2 * time.Second) + sys.fetchAddressFromL1() // Verify that initially the fetched signer equals oldSigner. - sys.lock.RLock() - initialSigner := sys.signerAddressL1 - sys.lock.RUnlock() + initialSigner := sys.currentSignerAddressL1() require.Equal(t, oldSigner, initialSigner, "Initial signerAddressL1 should be oldSigner") // Now, simulate an update: change the fake client's returned value to updatedSigner. @@ -140,13 +133,11 @@ func TestSystemContract_SignsAfterUpdate(t *testing.T) { fakeClient.value = updatedSigner fakeClient.mu.Unlock() - // Wait enough for the background polling routine to fetch the new value. - time.Sleep(1 + time.Second + defaultSyncInterval) + // fetch new value from L1 (simulating a background poll) + sys.fetchAddressFromL1() // Verify that system contract's signerAddressL1 is now updated to updatedSigner. - sys.lock.RLock() - newSigner := sys.signerAddressL1 - sys.lock.RUnlock() + newSigner := sys.currentSignerAddressL1() require.Equal(t, newSigner, updatedSigner, "SignerAddressL1 should update to updatedSigner after polling") // Now simulate authorizing with the correct local signer. diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 36eed92482f4..94fa46c34c31 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -19,7 +19,6 @@ package ethconfig import ( "context" - "github.com/scroll-tech/go-ethereum/consensus/wrapper" "math/big" "os" "os/user" @@ -27,6 +26,8 @@ import ( "runtime" "time" + "github.com/scroll-tech/go-ethereum/consensus/wrapper" + "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/consensus" "github.com/scroll-tech/go-ethereum/consensus/clique" @@ -237,6 +238,7 @@ func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, co if chainConfig.SystemContract != nil && chainConfig.Clique != nil { cliqueEngine := clique.New(chainConfig.Clique, db) sysEngine := system_contract.New(context.Background(), chainConfig.SystemContract, l1Client) + sysEngine.Start() return wrapper.NewUpgradableEngine(chainConfig.IsEuclidV2, cliqueEngine, sysEngine) } @@ -247,7 +249,9 @@ func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, co // Case 3: Only the SystemContract engine is defined. if chainConfig.SystemContract != nil { - return system_contract.New(context.Background(), chainConfig.SystemContract, l1Client) + sysEngine := system_contract.New(context.Background(), chainConfig.SystemContract, l1Client) + sysEngine.Start() + return sysEngine } // Otherwise assume proof-of-work From b2fb9a2af5d773132fc34846e603d45cd17b7314 Mon Sep 17 00:00:00 2001 From: jonastheis <4181434+jonastheis@users.noreply.github.com> Date: Tue, 25 Feb 2025 12:22:35 +0800 Subject: [PATCH 25/36] fix ci --- consensus/system_contract/consensus.go | 5 ++--- eth/backend.go | 4 +++- go.sum | 2 -- rollup/l1/types.go | 4 ++++ 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/consensus/system_contract/consensus.go b/consensus/system_contract/consensus.go index 81e27381639f..33cfff3b99e9 100644 --- a/consensus/system_contract/consensus.go +++ b/consensus/system_contract/consensus.go @@ -8,6 +8,8 @@ import ( "math/big" "time" + "golang.org/x/crypto/sha3" + "github.com/scroll-tech/go-ethereum/accounts" "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/consensus" @@ -19,7 +21,6 @@ import ( "github.com/scroll-tech/go-ethereum/rlp" "github.com/scroll-tech/go-ethereum/rpc" "github.com/scroll-tech/go-ethereum/trie" - "golang.org/x/crypto/sha3" ) var ( @@ -35,8 +36,6 @@ var ( // errUnknownBlock is returned when the list of signers is requested for a block // that is not part of the local blockchain. errUnknownBlock = errors.New("unknown block") - // errCoinbaseNotEmpty is returned if a coinbase value is non-zero - errInvalidCoinbase = errors.New("coinbase not empty nor zero") // errNonceNotEmpty is returned if a nonce value is non-zero errInvalidNonce = errors.New("nonce not empty nor zero") // errMissingSignature is returned if a block's extra-data section doesn't seem diff --git a/eth/backend.go b/eth/backend.go index 5a419d69373b..7635b8e26d74 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -21,7 +21,6 @@ import ( "context" "errors" "fmt" - "github.com/scroll-tech/go-ethereum/consensus/wrapper" "math/big" "runtime" "sync" @@ -34,6 +33,7 @@ import ( "github.com/scroll-tech/go-ethereum/consensus" "github.com/scroll-tech/go-ethereum/consensus/clique" "github.com/scroll-tech/go-ethereum/consensus/system_contract" + "github.com/scroll-tech/go-ethereum/consensus/wrapper" "github.com/scroll-tech/go-ethereum/core" "github.com/scroll-tech/go-ethereum/core/bloombits" "github.com/scroll-tech/go-ethereum/core/rawdb" @@ -277,6 +277,8 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client l1.Client) (*Ether } eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock, config.EnableDASyncing) + // Some of the extraData is used with Clique consensus (before EuclidV2). After EuclidV2 we use SystemContract consensus where this is overridden when creating a block. + eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil} if eth.APIBackend.allowUnprotectedTxs { diff --git a/go.sum b/go.sum index cb85928084c6..6e8606ed54b2 100644 --- a/go.sum +++ b/go.sum @@ -134,8 +134,6 @@ github.com/ethereum/c-kzg-4844/bindings/go v0.0.0-20230126171313-363c7d7593b4 h1 github.com/ethereum/c-kzg-4844/bindings/go v0.0.0-20230126171313-363c7d7593b4/go.mod h1:y4GA2JbAUama1S4QwYjC2hefgGLU8Ul0GMtL/ADMF1c= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= -github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA= github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= diff --git a/rollup/l1/types.go b/rollup/l1/types.go index a103bb810d51..209d3956da97 100644 --- a/rollup/l1/types.go +++ b/rollup/l1/types.go @@ -59,3 +59,7 @@ func (m *MockNopClient) BlockByHash(ctx context.Context, hash common.Hash) (*typ func (m *MockNopClient) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { return nil, nil } + +func (m *MockNopClient) StorageAt(ctx context.Context, contract common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { + return nil, nil +} From 396db648cc7cc8a310a7a2956c2937b060a525b7 Mon Sep 17 00:00:00 2001 From: jonastheis Date: Tue, 25 Feb 2025 04:24:37 +0000 Subject: [PATCH 26/36] =?UTF-8?q?chore:=20auto=20version=20bump=E2=80=89[b?= =?UTF-8?q?ot]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- params/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/params/version.go b/params/version.go index b85c4244adcf..607d43ac66f3 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 9 // Patch version component of the current release + VersionPatch = 10 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From cc5c7dc0be1db0185f2a492ec12e4018a638248f Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Tue, 25 Feb 2025 08:07:50 +0000 Subject: [PATCH 27/36] Update consensus/system_contract/consensus.go Co-authored-by: Morty <70688412+yiweichi@users.noreply.github.com> --- consensus/system_contract/consensus.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/system_contract/consensus.go b/consensus/system_contract/consensus.go index 33cfff3b99e9..39c0a0e47b03 100644 --- a/consensus/system_contract/consensus.go +++ b/consensus/system_contract/consensus.go @@ -61,7 +61,7 @@ var ErrUnauthorizedSigner = errors.New("unauthorized signer") type SignerFn func(signer accounts.Account, mimeType string, message []byte) ([]byte, error) // Author implements consensus.Engine, returning the Ethereum address recovered -// from the signature in the header's extra-data section. +// from the signature in the header's block-signature section. func (s *SystemContract) Author(header *types.Header) (common.Address, error) { return ecrecover(header) } From b914e00da6efe0c9f6d5b3d64f5c3fac5f5b1f27 Mon Sep 17 00:00:00 2001 From: ranchalp Date: Tue, 25 Feb 2025 18:08:39 +0000 Subject: [PATCH 28/36] =?UTF-8?q?chore:=20auto=20version=20bump=E2=80=89[b?= =?UTF-8?q?ot]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- params/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/params/version.go b/params/version.go index f904740d6565..b5ef4640b6e3 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 13 // Patch version component of the current release + VersionPatch = 14 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From 0b007896ed493033b43357f197ae4366a8d163ea Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Tue, 25 Feb 2025 18:10:11 +0000 Subject: [PATCH 29/36] Update consensus/system_contract/consensus.go Co-authored-by: Jonas Theis <4181434+jonastheis@users.noreply.github.com> --- consensus/system_contract/consensus.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/system_contract/consensus.go b/consensus/system_contract/consensus.go index 39c0a0e47b03..ffe944ba924f 100644 --- a/consensus/system_contract/consensus.go +++ b/consensus/system_contract/consensus.go @@ -114,7 +114,7 @@ func (s *SystemContract) verifyHeader(chain consensus.ChainHeaderReader, header return errInvalidNonce } // Check that the BlockSignature contains signature if block is not requested - if header.Number.Cmp(big.NewInt(0)) != 0 && len(header.BlockSignature) != extraSeal && !header.Requested { +if !header.Requested && header.Number.Cmp(big.NewInt(0)) != 0 && len(header.BlockSignature) != extraSeal { return errMissingSignature } // Ensure that the mix digest is zero From d1e479099e6009157eda6cc8c1e745c59c592879 Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Tue, 25 Feb 2025 15:14:45 -0300 Subject: [PATCH 30/36] Remove whitespaces and merge version number --- core/types/block.go | 1 + eth/backend.go | 3 +-- eth/downloader/queue.go | 6 +++--- eth/protocols/eth/handlers.go | 1 + les/downloader/downloader.go | 40 +++++++++++++++++------------------ les/downloader/queue.go | 7 +++--- les/downloader/resultstore.go | 9 ++++---- miner/scroll_worker.go | 2 +- params/version.go | 2 +- 9 files changed, 34 insertions(+), 37 deletions(-) diff --git a/core/types/block.go b/core/types/block.go index e1a1bbeeed61..644291c52992 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -421,6 +421,7 @@ func CalcUncleHash(uncles []*Header) common.Hash { // the sealed one. func (b *Block) WithSeal(header *Header) *Block { cpy := *header + return &Block{ header: &cpy, transactions: b.transactions, diff --git a/eth/backend.go b/eth/backend.go index e5f937f45cd9..205cf605d539 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -153,7 +153,6 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client l1.Client) (*Ether if err := pruner.RecoverPruning(stack.ResolvePath(""), chainDb, stack.ResolvePath(config.TrieCleanCacheJournal)); err != nil { log.Error("Failed to recover state", "error", err) } - eth := &Ethereum{ config: config, chainDb: chainDb, @@ -279,7 +278,7 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client l1.Client) (*Ether }); err != nil { return nil, err } - + config.Miner.SigningDisabled = config.DA.ProduceBlocks eth.miner = miner.New(eth, &config.Miner, eth.blockchain.Config(), eth.EventMux(), eth.engine, eth.isLocalBlock, config.EnableDASyncing && !config.DA.ProduceBlocks) // Some of the extraData is used with Clique consensus (before EuclidV2). After EuclidV2 we use SystemContract consensus where this is overridden when creating a block. diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index 65fd5db91b25..dbb879d940c2 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -477,9 +477,9 @@ func (q *queue) ReserveReceipts(p *peerConnection, count int) (*fetchRequest, bo // to access the queue, so they already need a lock anyway. // // Returns: -// item - the fetchRequest -// progress - whether any progress was made -// throttle - if the caller should throttle for a while +// item - the fetchRequest +// progress - whether any progress was made +// throttle - if the caller should throttle for a while func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common.Hash]*types.Header, taskQueue *prque.Prque, pendPool map[string]*fetchRequest, kind uint) (*fetchRequest, bool, bool) { // Short circuit if the pool has been depleted, or if the peer's already diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go index c289d6ee8d29..766bc1370fff 100644 --- a/eth/protocols/eth/handlers.go +++ b/eth/protocols/eth/handlers.go @@ -19,6 +19,7 @@ package eth import ( "encoding/json" "fmt" + "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/log" diff --git a/les/downloader/downloader.go b/les/downloader/downloader.go index 7ff6eda9ac34..d02d91b2ae91 100644 --- a/les/downloader/downloader.go +++ b/les/downloader/downloader.go @@ -705,11 +705,9 @@ func (d *Downloader) fetchHead(p *peerConnection) (head *types.Header, pivot *ty // calculateRequestSpan calculates what headers to request from a peer when trying to determine the // common ancestor. // It returns parameters to be used for peer.RequestHeadersByNumber: -// -// from - starting block number -// count - number of headers to request -// skip - number of headers to skip -// +// from - starting block number +// count - number of headers to request +// skip - number of headers to skip // and also returns 'max', the last block which is expected to be returned by the remote peers, // given the (from,count,skip) func calculateRequestSpan(remoteHeight, localHeight uint64) (int64, int, int, uint64) { @@ -1324,22 +1322,22 @@ func (d *Downloader) fetchReceipts(from uint64) error { // various callbacks to handle the slight differences between processing them. // // The instrumentation parameters: -// - errCancel: error type to return if the fetch operation is cancelled (mostly makes logging nicer) -// - deliveryCh: channel from which to retrieve downloaded data packets (merged from all concurrent peers) -// - deliver: processing callback to deliver data packets into type specific download queues (usually within `queue`) -// - wakeCh: notification channel for waking the fetcher when new tasks are available (or sync completed) -// - expire: task callback method to abort requests that took too long and return the faulty peers (traffic shaping) -// - pending: task callback for the number of requests still needing download (detect completion/non-completability) -// - inFlight: task callback for the number of in-progress requests (wait for all active downloads to finish) -// - throttle: task callback to check if the processing queue is full and activate throttling (bound memory use) -// - reserve: task callback to reserve new download tasks to a particular peer (also signals partial completions) -// - fetchHook: tester callback to notify of new tasks being initiated (allows testing the scheduling logic) -// - fetch: network callback to actually send a particular download request to a physical remote peer -// - cancel: task callback to abort an in-flight download request and allow rescheduling it (in case of lost peer) -// - capacity: network callback to retrieve the estimated type-specific bandwidth capacity of a peer (traffic shaping) -// - idle: network callback to retrieve the currently (type specific) idle peers that can be assigned tasks -// - setIdle: network callback to set a peer back to idle and update its estimated capacity (traffic shaping) -// - kind: textual label of the type being downloaded to display in log messages +// - errCancel: error type to return if the fetch operation is cancelled (mostly makes logging nicer) +// - deliveryCh: channel from which to retrieve downloaded data packets (merged from all concurrent peers) +// - deliver: processing callback to deliver data packets into type specific download queues (usually within `queue`) +// - wakeCh: notification channel for waking the fetcher when new tasks are available (or sync completed) +// - expire: task callback method to abort requests that took too long and return the faulty peers (traffic shaping) +// - pending: task callback for the number of requests still needing download (detect completion/non-completability) +// - inFlight: task callback for the number of in-progress requests (wait for all active downloads to finish) +// - throttle: task callback to check if the processing queue is full and activate throttling (bound memory use) +// - reserve: task callback to reserve new download tasks to a particular peer (also signals partial completions) +// - fetchHook: tester callback to notify of new tasks being initiated (allows testing the scheduling logic) +// - fetch: network callback to actually send a particular download request to a physical remote peer +// - cancel: task callback to abort an in-flight download request and allow rescheduling it (in case of lost peer) +// - capacity: network callback to retrieve the estimated type-specific bandwidth capacity of a peer (traffic shaping) +// - idle: network callback to retrieve the currently (type specific) idle peers that can be assigned tasks +// - setIdle: network callback to set a peer back to idle and update its estimated capacity (traffic shaping) +// - kind: textual label of the type being downloaded to display in log messages func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack) (int, error), wakeCh chan bool, expire func() map[string]int, pending func() int, inFlight func() bool, reserve func(*peerConnection, int) (*fetchRequest, bool, bool), fetchHook func([]*types.Header), fetch func(*peerConnection, *fetchRequest) error, cancel func(*fetchRequest), capacity func(*peerConnection) int, diff --git a/les/downloader/queue.go b/les/downloader/queue.go index d27d1b5b8e80..dbb879d940c2 100644 --- a/les/downloader/queue.go +++ b/les/downloader/queue.go @@ -477,10 +477,9 @@ func (q *queue) ReserveReceipts(p *peerConnection, count int) (*fetchRequest, bo // to access the queue, so they already need a lock anyway. // // Returns: -// -// item - the fetchRequest -// progress - whether any progress was made -// throttle - if the caller should throttle for a while +// item - the fetchRequest +// progress - whether any progress was made +// throttle - if the caller should throttle for a while func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common.Hash]*types.Header, taskQueue *prque.Prque, pendPool map[string]*fetchRequest, kind uint) (*fetchRequest, bool, bool) { // Short circuit if the pool has been depleted, or if the peer's already diff --git a/les/downloader/resultstore.go b/les/downloader/resultstore.go index 4a40e0ebd39e..8075f507db3a 100644 --- a/les/downloader/resultstore.go +++ b/les/downloader/resultstore.go @@ -71,11 +71,10 @@ func (r *resultStore) SetThrottleThreshold(threshold uint64) uint64 { // wants to reserve headers for fetching. // // It returns the following: -// -// stale - if true, this item is already passed, and should not be requested again -// throttled - if true, the store is at capacity, this particular header is not prio now -// item - the result to store data into -// err - any error that occurred +// stale - if true, this item is already passed, and should not be requested again +// throttled - if true, the store is at capacity, this particular header is not prio now +// item - the result to store data into +// err - any error that occurred func (r *resultStore) AddFetch(header *types.Header, fastSync bool) (stale, throttled bool, item *fetchResult, err error) { r.lock.Lock() defer r.lock.Unlock() diff --git a/miner/scroll_worker.go b/miner/scroll_worker.go index 1c3bf4f7c308..89ee11be0771 100644 --- a/miner/scroll_worker.go +++ b/miner/scroll_worker.go @@ -371,6 +371,7 @@ func (w *worker) mainLoop() { log.Error("failed to mine block", "err", err) w.current = nil } + idleStart := time.Now() select { case <-w.startCh: @@ -560,7 +561,6 @@ func (w *worker) newWork(now time.Time, parentHash common.Hash, reorging bool, r // tryCommitNewWork func (w *worker) tryCommitNewWork(now time.Time, parent common.Hash, reorging bool, reorgReason error) (common.Hash, error) { err := w.newWork(now, parent, reorging, reorgReason) - if err != nil { return common.Hash{}, fmt.Errorf("failed creating new work: %w", err) } diff --git a/params/version.go b/params/version.go index b5ef4640b6e3..f904740d6565 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 14 // Patch version component of the current release + VersionPatch = 13 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From 3839bf78259efc8baba433a9d4379100c2642978 Mon Sep 17 00:00:00 2001 From: ranchalp Date: Tue, 25 Feb 2025 18:33:14 +0000 Subject: [PATCH 31/36] =?UTF-8?q?chore:=20auto=20version=20bump=E2=80=89[b?= =?UTF-8?q?ot]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- params/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/params/version.go b/params/version.go index f904740d6565..b5ef4640b6e3 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 13 // Patch version component of the current release + VersionPatch = 14 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) From 3c2347eb7cd984f88f52bb50ba7d0c1172038107 Mon Sep 17 00:00:00 2001 From: jonastheis <4181434+jonastheis@users.noreply.github.com> Date: Wed, 26 Feb 2025 06:28:02 +0800 Subject: [PATCH 32/36] validate that the read address from L1 is not empty and improved error handling when fetching address --- consensus/system_contract/system_contract.go | 34 ++++++++++++------- .../system_contract/system_contract_test.go | 8 ++--- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/consensus/system_contract/system_contract.go b/consensus/system_contract/system_contract.go index f1a0afdecb0f..ebd753661788 100644 --- a/consensus/system_contract/system_contract.go +++ b/consensus/system_contract/system_contract.go @@ -2,6 +2,7 @@ package system_contract import ( "context" + "fmt" "sync" "time" @@ -34,19 +35,19 @@ type SystemContract struct { // signers set to the ones provided by the user. func New(ctx context.Context, config *params.SystemContractConfig, client sync_service.EthClient) *SystemContract { ctx, cancel := context.WithCancel(ctx) - address, err := client.StorageAt(ctx, config.SystemContractAddress, config.SystemContractSlot, nil) - if err != nil { - log.Error("failed to get signer address from L1 System Contract", "err", err) - } - return &SystemContract{ - config: config, - client: client, - signerAddressL1: common.BytesToAddress(address), + s := &SystemContract{ + config: config, + client: client, ctx: ctx, cancel: cancel, } + + if err := s.fetchAddressFromL1(); err != nil { + log.Error("failed to fetch signer address from L1", "err", err) + } + return s } // Authorize injects a private key into the consensus engine to mint new blocks @@ -75,20 +76,27 @@ func (s *SystemContract) Start() { case <-s.ctx.Done(): return case <-syncTicker.C: - s.fetchAddressFromL1() + if err := s.fetchAddressFromL1(); err != nil { + log.Error("failed to fetch signer address from L1", "err", err) + } } } }() } -func (s *SystemContract) fetchAddressFromL1() { +func (s *SystemContract) fetchAddressFromL1() error { address, err := s.client.StorageAt(s.ctx, s.config.SystemContractAddress, s.config.SystemContractSlot, nil) if err != nil { - log.Error("failed to get signer address from L1 System Contract", "err", err) + return fmt.Errorf("failed to get signer address from L1 System Contract: %w", err) } bAddress := common.BytesToAddress(address) - log.Info("Read address from system contract", "address", bAddress.Hex()) + // Validate the address is not empty + if bAddress == (common.Address{}) { + return fmt.Errorf("retrieved empty signer address from L1 System Contract") + } + + log.Debug("Read address from system contract", "address", bAddress.Hex()) s.lock.RLock() addressChanged := s.signerAddressL1 != bAddress @@ -99,6 +107,8 @@ func (s *SystemContract) fetchAddressFromL1() { s.signerAddressL1 = bAddress s.lock.Unlock() } + + return nil } // Close implements consensus.Engine. diff --git a/consensus/system_contract/system_contract_test.go b/consensus/system_contract/system_contract_test.go index 65dc6cf5013c..ddd6a0fc8d66 100644 --- a/consensus/system_contract/system_contract_test.go +++ b/consensus/system_contract/system_contract_test.go @@ -41,7 +41,7 @@ func TestSystemContract_FetchSigner(t *testing.T) { sys := New(ctx, config, fakeClient) defer sys.Close() - sys.fetchAddressFromL1() + require.NoError(t, sys.fetchAddressFromL1()) actualSigner := sys.currentSignerAddressL1() @@ -67,7 +67,7 @@ func TestSystemContract_AuthorizeCheck(t *testing.T) { sys := New(ctx, config, fakeClient) defer sys.Close() - sys.fetchAddressFromL1() + require.NoError(t, sys.fetchAddressFromL1()) // Authorize with a different signer than expected. differentSigner := common.HexToAddress("0xABCDEFabcdefABCDEFabcdefabcdefABCDEFABCD") @@ -122,7 +122,7 @@ func TestSystemContract_SignsAfterUpdate(t *testing.T) { sys := New(ctx, config, fakeClient) defer sys.Close() - sys.fetchAddressFromL1() + require.NoError(t, sys.fetchAddressFromL1()) // Verify that initially the fetched signer equals oldSigner. initialSigner := sys.currentSignerAddressL1() @@ -134,7 +134,7 @@ func TestSystemContract_SignsAfterUpdate(t *testing.T) { fakeClient.mu.Unlock() // fetch new value from L1 (simulating a background poll) - sys.fetchAddressFromL1() + require.NoError(t, sys.fetchAddressFromL1()) // Verify that system contract's signerAddressL1 is now updated to updatedSigner. newSigner := sys.currentSignerAddressL1() From 07559cb9f8cd090ff840f3d8d7454e8c4006ef36 Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Tue, 25 Feb 2025 23:52:28 -0300 Subject: [PATCH 33/36] Fix indentation issue --- eth/backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/backend.go b/eth/backend.go index 205cf605d539..d6902f089d80 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -281,7 +281,7 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client l1.Client) (*Ether config.Miner.SigningDisabled = config.DA.ProduceBlocks eth.miner = miner.New(eth, &config.Miner, eth.blockchain.Config(), eth.EventMux(), eth.engine, eth.isLocalBlock, config.EnableDASyncing && !config.DA.ProduceBlocks) - // Some of the extraData is used with Clique consensus (before EuclidV2). After EuclidV2 we use SystemContract consensus where this is overridden when creating a block. + // Some of the extraData is used with Clique consensus (before EuclidV2). After EuclidV2 we use SystemContract consensus where this is overridden when creating a block. eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData)) eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil} From 83bf259e7a125c3404a4981eea0bd4d5b511dfd2 Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Wed, 26 Feb 2025 00:06:25 -0300 Subject: [PATCH 34/36] Fix test --- eth/handler_eth_test.go | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/eth/handler_eth_test.go b/eth/handler_eth_test.go index d24705cec0df..2c46eb8692ed 100644 --- a/eth/handler_eth_test.go +++ b/eth/handler_eth_test.go @@ -18,6 +18,7 @@ package eth import ( "fmt" + "github.com/scroll-tech/go-ethereum/crypto" "math/big" "math/rand" "sync/atomic" @@ -48,7 +49,26 @@ type testEthHandler struct { txBroadcasts event.Feed } -func (h *testEthHandler) Chain() *core.BlockChain { panic("no backing chain") } +func (h *testEthHandler) Chain() *core.BlockChain { + chainConfig := ¶ms.ChainConfig{} + engine := ethash.NewFaker() + chaindb := rawdb.NewMemoryDatabase() + + // Import the canonical chain + cacheConfig := &core.CacheConfig{} + key1, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + addr1 := crypto.PubkeyToAddress(key1.PublicKey) + genesis := core.GenesisBlockForTesting(chaindb, addr1, big.NewInt(1000000)) + rawdb.WriteBlock(chaindb, genesis) + rawdb.WriteCanonicalHash(chaindb, genesis.Hash(), genesis.NumberU64()) + rawdb.WriteHeadBlockHash(chaindb, genesis.Hash()) + + chain, err := core.NewBlockChain(chaindb, cacheConfig, chainConfig, engine, vm.Config{}, nil, nil) + if err != nil { + panic(fmt.Sprintf("failed to create chain: %v", err)) + } + return chain +} func (h *testEthHandler) StateBloom() *trie.SyncBloom { panic("no backing state bloom") } func (h *testEthHandler) TxPool() eth.TxPool { panic("no backing tx pool") } func (h *testEthHandler) AcceptTxs() bool { return true } @@ -505,8 +525,7 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo var response *types.Header if checkpoint { number := (uint64(rand.Intn(500))+1)*params.CHTFrequency - 1 - response = &types.Header{Number: big.NewInt(int64(number)), Extra: []byte("valid")} - + response = &types.Header{Number: big.NewInt(int64(number))} handler.handler.checkpointNumber = number handler.handler.checkpointHash = response.Hash() } @@ -554,6 +573,7 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo query.Origin.Number, query.Amount, query.Skip, query.Reverse, response.Number.Uint64(), 1, 0, false) } + // Create a block to reply to the challenge if no timeout is simulated. if !timeout { if empty { @@ -565,7 +585,8 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo t.Fatalf("failed to answer challenge: %v", err) } } else { - if err := remote.ReplyBlockHeaders(request.RequestId, []*types.Header{{Number: response.Number}}); err != nil { + number := new(big.Int).Add(response.Number, big.NewInt(1)) // mismatching headers to same request + if err := remote.ReplyBlockHeaders(request.RequestId, []*types.Header{{Number: number}}); err != nil { t.Fatalf("failed to answer challenge: %v", err) } } @@ -573,7 +594,6 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo } // Wait until the test timeout passes to ensure proper cleanup time.Sleep(syncChallengeTimeout + 300*time.Millisecond) - // Verify that the remote peer is maintained or dropped. if drop { <-handlerDone From 31511059ea806fe45e3e438627fc6a587b1d9595 Mon Sep 17 00:00:00 2001 From: Alejandro Ranchal-Pedrosa Date: Wed, 26 Feb 2025 00:11:51 -0300 Subject: [PATCH 35/36] goimports fix --- eth/downloader/downloader.go | 42 ++++++++++++----------- eth/downloader/queue.go | 7 ++-- eth/downloader/resultstore.go | 11 +++--- eth/handler_eth_test.go | 3 +- eth/protocols/snap/sync_test.go | 3 +- eth/tracers/js/internal/tracers/assets.go | 12 ++++--- eth/tracers/native/4byte.go | 17 ++++----- 7 files changed, 52 insertions(+), 43 deletions(-) diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index b402c4af0261..6afb3a852744 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -702,9 +702,11 @@ func (d *Downloader) fetchHead(p *peerConnection) (head *types.Header, pivot *ty // calculateRequestSpan calculates what headers to request from a peer when trying to determine the // common ancestor. // It returns parameters to be used for peer.RequestHeadersByNumber: -// from - starting block number -// count - number of headers to request -// skip - number of headers to skip +// +// from - starting block number +// count - number of headers to request +// skip - number of headers to skip +// // and also returns 'max', the last block which is expected to be returned by the remote peers, // given the (from,count,skip) func calculateRequestSpan(remoteHeight, localHeight uint64) (int64, int, int, uint64) { @@ -1319,22 +1321,22 @@ func (d *Downloader) fetchReceipts(from uint64) error { // various callbacks to handle the slight differences between processing them. // // The instrumentation parameters: -// - errCancel: error type to return if the fetch operation is cancelled (mostly makes logging nicer) -// - deliveryCh: channel from which to retrieve downloaded data packets (merged from all concurrent peers) -// - deliver: processing callback to deliver data packets into type specific download queues (usually within `queue`) -// - wakeCh: notification channel for waking the fetcher when new tasks are available (or sync completed) -// - expire: task callback method to abort requests that took too long and return the faulty peers (traffic shaping) -// - pending: task callback for the number of requests still needing download (detect completion/non-completability) -// - inFlight: task callback for the number of in-progress requests (wait for all active downloads to finish) -// - throttle: task callback to check if the processing queue is full and activate throttling (bound memory use) -// - reserve: task callback to reserve new download tasks to a particular peer (also signals partial completions) -// - fetchHook: tester callback to notify of new tasks being initiated (allows testing the scheduling logic) -// - fetch: network callback to actually send a particular download request to a physical remote peer -// - cancel: task callback to abort an in-flight download request and allow rescheduling it (in case of lost peer) -// - capacity: network callback to retrieve the estimated type-specific bandwidth capacity of a peer (traffic shaping) -// - idle: network callback to retrieve the currently (type specific) idle peers that can be assigned tasks -// - setIdle: network callback to set a peer back to idle and update its estimated capacity (traffic shaping) -// - kind: textual label of the type being downloaded to display in log messages +// - errCancel: error type to return if the fetch operation is cancelled (mostly makes logging nicer) +// - deliveryCh: channel from which to retrieve downloaded data packets (merged from all concurrent peers) +// - deliver: processing callback to deliver data packets into type specific download queues (usually within `queue`) +// - wakeCh: notification channel for waking the fetcher when new tasks are available (or sync completed) +// - expire: task callback method to abort requests that took too long and return the faulty peers (traffic shaping) +// - pending: task callback for the number of requests still needing download (detect completion/non-completability) +// - inFlight: task callback for the number of in-progress requests (wait for all active downloads to finish) +// - throttle: task callback to check if the processing queue is full and activate throttling (bound memory use) +// - reserve: task callback to reserve new download tasks to a particular peer (also signals partial completions) +// - fetchHook: tester callback to notify of new tasks being initiated (allows testing the scheduling logic) +// - fetch: network callback to actually send a particular download request to a physical remote peer +// - cancel: task callback to abort an in-flight download request and allow rescheduling it (in case of lost peer) +// - capacity: network callback to retrieve the estimated type-specific bandwidth capacity of a peer (traffic shaping) +// - idle: network callback to retrieve the currently (type specific) idle peers that can be assigned tasks +// - setIdle: network callback to set a peer back to idle and update its estimated capacity (traffic shaping) +// - kind: textual label of the type being downloaded to display in log messages func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack) (int, error), wakeCh chan bool, expire func() map[string]int, pending func() int, inFlight func() bool, reserve func(*peerConnection, int) (*fetchRequest, bool, bool), fetchHook func([]*types.Header), fetch func(*peerConnection, *fetchRequest) error, cancel func(*fetchRequest), capacity func(*peerConnection) int, @@ -2008,4 +2010,4 @@ func (d *Downloader) deliver(destCh chan dataPack, packet dataPack, inMeter, dro case <-cancel: return errNoSyncActive } -} \ No newline at end of file +} diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index dbb879d940c2..d27d1b5b8e80 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -477,9 +477,10 @@ func (q *queue) ReserveReceipts(p *peerConnection, count int) (*fetchRequest, bo // to access the queue, so they already need a lock anyway. // // Returns: -// item - the fetchRequest -// progress - whether any progress was made -// throttle - if the caller should throttle for a while +// +// item - the fetchRequest +// progress - whether any progress was made +// throttle - if the caller should throttle for a while func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common.Hash]*types.Header, taskQueue *prque.Prque, pendPool map[string]*fetchRequest, kind uint) (*fetchRequest, bool, bool) { // Short circuit if the pool has been depleted, or if the peer's already diff --git a/eth/downloader/resultstore.go b/eth/downloader/resultstore.go index 8b45f3ebe760..4a40e0ebd39e 100644 --- a/eth/downloader/resultstore.go +++ b/eth/downloader/resultstore.go @@ -71,10 +71,11 @@ func (r *resultStore) SetThrottleThreshold(threshold uint64) uint64 { // wants to reserve headers for fetching. // // It returns the following: -// stale - if true, this item is already passed, and should not be requested again -// throttled - if true, the store is at capacity, this particular header is not prio now -// item - the result to store data into -// err - any error that occurred +// +// stale - if true, this item is already passed, and should not be requested again +// throttled - if true, the store is at capacity, this particular header is not prio now +// item - the result to store data into +// err - any error that occurred func (r *resultStore) AddFetch(header *types.Header, fastSync bool) (stale, throttled bool, item *fetchResult, err error) { r.lock.Lock() defer r.lock.Unlock() @@ -191,4 +192,4 @@ func (r *resultStore) Prepare(offset uint64) { if r.resultOffset < offset { r.resultOffset = offset } -} \ No newline at end of file +} diff --git a/eth/handler_eth_test.go b/eth/handler_eth_test.go index 2c46eb8692ed..f5e18d2ad1c1 100644 --- a/eth/handler_eth_test.go +++ b/eth/handler_eth_test.go @@ -18,13 +18,14 @@ package eth import ( "fmt" - "github.com/scroll-tech/go-ethereum/crypto" "math/big" "math/rand" "sync/atomic" "testing" "time" + "github.com/scroll-tech/go-ethereum/crypto" + "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/consensus/ethash" "github.com/scroll-tech/go-ethereum/core" diff --git a/eth/protocols/snap/sync_test.go b/eth/protocols/snap/sync_test.go index db898241ee98..2aa668542d6f 100644 --- a/eth/protocols/snap/sync_test.go +++ b/eth/protocols/snap/sync_test.go @@ -369,7 +369,8 @@ func createStorageRequestResponse(t *testPeer, root common.Hash, accounts []comm return hashes, slots, proofs } -// the createStorageRequestResponseAlwaysProve tests a cornercase, where it always +// the createStorageRequestResponseAlwaysProve tests a cornercase, where it always +// // supplies the proof for the last account, even if it is 'complete'.h func createStorageRequestResponseAlwaysProve(t *testPeer, root common.Hash, accounts []common.Hash, bOrigin, bLimit []byte, max uint64) (hashes [][]common.Hash, slots [][][]byte, proofs [][]byte) { var size uint64 diff --git a/eth/tracers/js/internal/tracers/assets.go b/eth/tracers/js/internal/tracers/assets.go index caeccb7f3655..15e7757a7d39 100644 --- a/eth/tracers/js/internal/tracers/assets.go +++ b/eth/tracers/js/internal/tracers/assets.go @@ -388,11 +388,13 @@ const AssetDebug = false // directory embedded in the file by go-bindata. // For example if you run go-bindata on data/... and data contains the // following hierarchy: -// data/ -// foo.txt -// img/ -// a.png -// b.png +// +// data/ +// foo.txt +// img/ +// a.png +// b.png +// // then AssetDir("data") would return []string{"foo.txt", "img"}, // AssetDir("data/img") would return []string{"a.png", "b.png"}, // AssetDir("foo.txt") and AssetDir("notexist") would return an error, and diff --git a/eth/tracers/native/4byte.go b/eth/tracers/native/4byte.go index a62da6313766..bad90f828e28 100644 --- a/eth/tracers/native/4byte.go +++ b/eth/tracers/native/4byte.go @@ -38,14 +38,15 @@ func init() { // a reversed signature can be matched against the size of the data. // // Example: -// > debug.traceTransaction( "0x214e597e35da083692f5386141e69f47e973b2c56e7a8073b1ea08fd7571e9de", {tracer: "4byteTracer"}) -// { -// 0x27dc297e-128: 1, -// 0x38cc4831-0: 2, -// 0x524f3889-96: 1, -// 0xadf59f99-288: 1, -// 0xc281d19e-0: 1 -// } +// +// > debug.traceTransaction( "0x214e597e35da083692f5386141e69f47e973b2c56e7a8073b1ea08fd7571e9de", {tracer: "4byteTracer"}) +// { +// 0x27dc297e-128: 1, +// 0x38cc4831-0: 2, +// 0x524f3889-96: 1, +// 0xadf59f99-288: 1, +// 0xc281d19e-0: 1 +// } type fourByteTracer struct { env *vm.EVM ids map[string]int // ids aggregates the 4byte ids found From 297186b35790dc3c806487eff131f8e1c625d4b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Wed, 26 Feb 2025 11:09:03 +0100 Subject: [PATCH 36/36] goimports --- consensus/system_contract/consensus.go | 2 +- consensus/wrapper/consensus.go | 5 +++-- miner/scroll_worker.go | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/consensus/system_contract/consensus.go b/consensus/system_contract/consensus.go index ffe944ba924f..456cf0d7ef42 100644 --- a/consensus/system_contract/consensus.go +++ b/consensus/system_contract/consensus.go @@ -114,7 +114,7 @@ func (s *SystemContract) verifyHeader(chain consensus.ChainHeaderReader, header return errInvalidNonce } // Check that the BlockSignature contains signature if block is not requested -if !header.Requested && header.Number.Cmp(big.NewInt(0)) != 0 && len(header.BlockSignature) != extraSeal { + if !header.Requested && header.Number.Cmp(big.NewInt(0)) != 0 && len(header.BlockSignature) != extraSeal { return errMissingSignature } // Ensure that the mix digest is zero diff --git a/consensus/wrapper/consensus.go b/consensus/wrapper/consensus.go index 49792b626e82..040de0ef467f 100644 --- a/consensus/wrapper/consensus.go +++ b/consensus/wrapper/consensus.go @@ -1,6 +1,9 @@ package wrapper import ( + "math/big" + "sync" + "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/consensus" "github.com/scroll-tech/go-ethereum/consensus/clique" @@ -8,8 +11,6 @@ import ( "github.com/scroll-tech/go-ethereum/core/state" "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/rpc" - "math/big" - "sync" ) // UpgradableEngine implements consensus.Engine and acts as a middleware to dispatch diff --git a/miner/scroll_worker.go b/miner/scroll_worker.go index 89ee11be0771..4ae5413f60e2 100644 --- a/miner/scroll_worker.go +++ b/miner/scroll_worker.go @@ -19,13 +19,14 @@ package miner import ( "errors" "fmt" - "github.com/scroll-tech/go-ethereum/consensus/system_contract" "math" "math/big" "sync" "sync/atomic" "time" + "github.com/scroll-tech/go-ethereum/consensus/system_contract" + "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/consensus" "github.com/scroll-tech/go-ethereum/consensus/misc"