mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-23 21:26:42 +00:00
feat: add system config consensus to deprecate clique (#1102)
* feat: add system config consensus to deprecate poa * fix: check extra field lens * Finish implementation with one signer only * Implement unit tests * Only check signature if block not requested * Remove Extra from rlp encoding (and hashing) * Implement UpgradableEngine as middleware for Clique/SystemContract * Use isEuclid, add json tag to Requested, make go idiomatic * Changes post integration test * Remove comment * Address comments * Merge * New field BlockSignature (not used in hashing/JSON) * Enforce .Extra to be an empty slice * replace header.Requested for header.IsNewBlock * mark new block as IsNewBlock * Make new RLP optional fields always default for legacy/reth compatibility * Placing new fields as last non-zero rlp:optional values used by Scroll * Penalize nodes that send non-zero Euclid V2 header field values * Bring back .Requested to downloader instead of .IsNewBlock * Replace IsNewBlock for Requested * Address comments * merge * prevent timing issues in tests * fix ci * chore: auto version bump [bot] * Update consensus/system_contract/consensus.go Co-authored-by: Morty <70688412+yiweichi@users.noreply.github.com> * chore: auto version bump [bot] * Update consensus/system_contract/consensus.go Co-authored-by: Jonas Theis <4181434+jonastheis@users.noreply.github.com> * Remove whitespaces and merge version number * chore: auto version bump [bot] * validate that the read address from L1 is not empty and improved error handling when fetching address * Fix indentation issue * Fix test * goimports fix * goimports --------- Co-authored-by: Alejandro Ranchal-Pedrosa <a.ranchalpedrosa@gmail.com> Co-authored-by: jonastheis <4181434+jonastheis@users.noreply.github.com> Co-authored-by: jonastheis <jonastheis@users.noreply.github.com> Co-authored-by: ranchalp <ranchalp@users.noreply.github.com> Co-authored-by: Péter Garamvölgyi <peter@scroll.io>
This commit is contained in:
parent
bcfdb48dd9
commit
365b82953d
30 changed files with 1240 additions and 72 deletions
|
|
@ -254,7 +254,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*enode.Node, network ui
|
||||||
cfg.Genesis = genesis
|
cfg.Genesis = genesis
|
||||||
utils.SetDNSDiscoveryDefaults(&cfg, genesis.ToBlock(nil).Hash())
|
utils.SetDNSDiscoveryDefaults(&cfg, genesis.ToBlock(nil).Hash())
|
||||||
|
|
||||||
lesBackend, err := les.New(stack, &cfg)
|
lesBackend, err := les.New(stack, &cfg, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to register the Ethereum service: %w", err)
|
return nil, fmt.Errorf("Failed to register the Ethereum service: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2048,16 +2048,6 @@ func SetDNSDiscoveryDefaults(cfg *ethconfig.Config, genesis common.Hash) {
|
||||||
// The second return value is the full node instance, which may be nil if the
|
// The second return value is the full node instance, which may be nil if the
|
||||||
// node is running as a light client.
|
// node is running as a light client.
|
||||||
func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend, *eth.Ethereum) {
|
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
|
// initialize L1 client for sync service
|
||||||
// note: we need to do this here to avoid circular dependency
|
// note: we need to do this here to avoid circular dependency
|
||||||
l1EndpointUrl := stack.Config().L1Endpoint
|
l1EndpointUrl := stack.Config().L1Endpoint
|
||||||
|
|
@ -2073,6 +2063,16 @@ func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend
|
||||||
log.Info("Initialized L1 client", "endpoint", l1EndpointUrl)
|
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)
|
backend, err := eth.New(stack, cfg, l1Client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Fatalf("Failed to register the Ethereum service: %v", err)
|
Fatalf("Failed to register the Ethereum service: %v", err)
|
||||||
|
|
|
||||||
18
consensus/system_contract/api.go
Normal file
18
consensus/system_contract/api.go
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
388
consensus/system_contract/consensus.go
Normal file
388
consensus/system_contract/consensus.go
Normal file
|
|
@ -0,0 +1,388 @@
|
||||||
|
package system_contract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"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"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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")
|
||||||
|
// 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")
|
||||||
|
// 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.
|
||||||
|
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)
|
||||||
|
|
||||||
|
// Author implements consensus.Engine, returning the Ethereum address recovered
|
||||||
|
// from the signature in the header's block-signature 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])
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error verifying headers", "err", err)
|
||||||
|
}
|
||||||
|
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 BlockSignature contains signature if block is not requested
|
||||||
|
if !header.Requested && header.Number.Cmp(big.NewInt(0)) != 0 && len(header.BlockSignature) != 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 one
|
||||||
|
if header.Difficulty.Cmp(common.Big1) != 0 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if len(header.Extra) > 0 {
|
||||||
|
return errInvalidExtra
|
||||||
|
}
|
||||||
|
//// 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 <nil>", 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.RLock()
|
||||||
|
defer s.lock.RUnlock()
|
||||||
|
|
||||||
|
if signer != s.signerAddressL1 {
|
||||||
|
log.Error("Unauthorized signer", "Got", signer, "Expected:", 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.BlockSignature = make([]byte, extraSeal)
|
||||||
|
header.IsEuclidV2 = true
|
||||||
|
header.Extra = nil
|
||||||
|
// 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
|
||||||
|
// 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
|
||||||
|
signerAddressL1 := s.signerAddressL1
|
||||||
|
s.lock.RUnlock()
|
||||||
|
|
||||||
|
// Bail out if we are unauthorized to sign a block
|
||||||
|
if signer != signerAddressL1 {
|
||||||
|
return ErrUnauthorizedSigner
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.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() {
|
||||||
|
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.BlockSignature[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")
|
||||||
|
}
|
||||||
|
if err := rlp.Encode(w, enc); err != nil {
|
||||||
|
panic("can't encode: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
125
consensus/system_contract/system_contract.go
Normal file
125
consensus/system_contract/system_contract.go
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
package system_contract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"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 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
|
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
|
||||||
|
// 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()
|
||||||
|
|
||||||
|
s.signer = signer
|
||||||
|
s.signFn = signFn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SystemContract) Start() {
|
||||||
|
go func() {
|
||||||
|
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:
|
||||||
|
if err := s.fetchAddressFromL1(); err != nil {
|
||||||
|
log.Error("failed to fetch signer address from L1", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SystemContract) fetchAddressFromL1() error {
|
||||||
|
address, err := s.client.StorageAt(s.ctx, s.config.SystemContractAddress, s.config.SystemContractSlot, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get signer address from L1 System Contract: %w", err)
|
||||||
|
}
|
||||||
|
bAddress := common.BytesToAddress(address)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
s.lock.RUnlock()
|
||||||
|
|
||||||
|
if addressChanged {
|
||||||
|
s.lock.Lock()
|
||||||
|
s.signerAddressL1 = bAddress
|
||||||
|
s.lock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements consensus.Engine.
|
||||||
|
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
|
||||||
|
}
|
||||||
223
consensus/system_contract/system_contract_test.go
Normal file
223
consensus/system_contract/system_contract_test.go
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
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"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
require.NoError(t, sys.fetchAddressFromL1())
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
require.NoError(t, sys.fetchAddressFromL1())
|
||||||
|
|
||||||
|
// 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 blocksignature data length for this test.
|
||||||
|
header := &types.Header{
|
||||||
|
Number: big.NewInt(100),
|
||||||
|
// We use an extra slice with length equal to extraSeal
|
||||||
|
BlockSignature: 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()
|
||||||
|
|
||||||
|
require.NoError(t, sys.fetchAddressFromL1())
|
||||||
|
|
||||||
|
// Verify that initially the fetched signer equals oldSigner.
|
||||||
|
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.
|
||||||
|
fakeClient.mu.Lock()
|
||||||
|
fakeClient.value = updatedSigner
|
||||||
|
fakeClient.mu.Unlock()
|
||||||
|
|
||||||
|
// fetch new value from L1 (simulating a background poll)
|
||||||
|
require.NoError(t, sys.fetchAddressFromL1())
|
||||||
|
|
||||||
|
// Verify that system contract's signerAddressL1 is now updated to updatedSigner.
|
||||||
|
newSigner := sys.currentSignerAddressL1()
|
||||||
|
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),
|
||||||
|
BlockSignature: 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
|
||||||
|
}
|
||||||
207
consensus/wrapper/consensus.go
Normal file
207
consensus/wrapper/consensus.go
Normal file
|
|
@ -0,0 +1,207 @@
|
||||||
|
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"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UpgradableEngine implements consensus.Engine and acts as a middleware to dispatch
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// 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(isUpgraded func(uint64) bool, clique consensus.Engine, system consensus.Engine) *UpgradableEngine {
|
||||||
|
return &UpgradableEngine{
|
||||||
|
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 ue.isUpgraded(header.Time) {
|
||||||
|
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 ue.isUpgraded(head.Time) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -86,6 +86,12 @@ type Header struct {
|
||||||
// BaseFee was added by EIP-1559 and is ignored in legacy headers.
|
// BaseFee was added by EIP-1559 and is ignored in legacy headers.
|
||||||
BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"`
|
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"`
|
||||||
|
|
||||||
// WithdrawalsHash was added by EIP-4895 and is ignored in legacy headers.
|
// WithdrawalsHash was added by EIP-4895 and is ignored in legacy headers.
|
||||||
// Included for Ethereum compatibility in Scroll SDK
|
// Included for Ethereum compatibility in Scroll SDK
|
||||||
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
|
WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"`
|
||||||
|
|
@ -101,6 +107,9 @@ type Header struct {
|
||||||
// ParentBeaconRoot was added by EIP-4788 and is ignored in legacy headers.
|
// ParentBeaconRoot was added by EIP-4788 and is ignored in legacy headers.
|
||||||
// Included for Ethereum compatibility in Scroll SDK
|
// Included for Ethereum compatibility in Scroll SDK
|
||||||
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
|
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:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// field type overrides for gencodec
|
// field type overrides for gencodec
|
||||||
|
|
@ -118,7 +127,13 @@ type headerMarshaling struct {
|
||||||
// Hash returns the block hash of the header, which is simply the keccak256 hash of its
|
// Hash returns the block hash of the header, which is simply the keccak256 hash of its
|
||||||
// RLP encoding.
|
// RLP encoding.
|
||||||
func (h *Header) Hash() common.Hash {
|
func (h *Header) Hash() common.Hash {
|
||||||
return rlpHash(h)
|
hCopy := CopyHeader(h)
|
||||||
|
hCopy.BlockSignature = nil
|
||||||
|
if hCopy.IsEuclidV2 {
|
||||||
|
hCopy.IsEuclidV2 = false
|
||||||
|
hCopy.Extra = nil
|
||||||
|
}
|
||||||
|
return rlpHash(hCopy)
|
||||||
}
|
}
|
||||||
|
|
||||||
var headerSize = common.StorageSize(reflect.TypeOf(Header{}).Size())
|
var headerSize = common.StorageSize(reflect.TypeOf(Header{}).Size())
|
||||||
|
|
@ -153,6 +168,14 @@ func (h *Header) SanityCheck() error {
|
||||||
return nil
|
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
|
// EmptyBody returns true if there is no additional 'body' to complete the header
|
||||||
// that is: no transactions and no uncles.
|
// that is: no transactions and no uncles.
|
||||||
func (h *Header) EmptyBody() bool {
|
func (h *Header) EmptyBody() bool {
|
||||||
|
|
@ -164,6 +187,22 @@ func (h *Header) EmptyReceipts() bool {
|
||||||
return h.ReceiptHash == EmptyRootHash
|
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 = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Body is a simple (mutable, non-safe) data container for storing and moving
|
// Body is a simple (mutable, non-safe) data container for storing and moving
|
||||||
// a block's data contents (transactions and uncles) together.
|
// a block's data contents (transactions and uncles) together.
|
||||||
type Body struct {
|
type Body struct {
|
||||||
|
|
@ -262,6 +301,10 @@ func CopyHeader(h *Header) *Header {
|
||||||
cpy.Extra = make([]byte, len(h.Extra))
|
cpy.Extra = make([]byte, len(h.Extra))
|
||||||
copy(cpy.Extra, 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
|
return &cpy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -460,6 +503,22 @@ func (b *Block) CountL2Tx() int {
|
||||||
return count
|
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 Blocks []*Block
|
||||||
|
|
||||||
type BlockWithRowConsumption struct {
|
type BlockWithRowConsumption struct {
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ import (
|
||||||
"github.com/scroll-tech/go-ethereum/common/hexutil"
|
"github.com/scroll-tech/go-ethereum/common/hexutil"
|
||||||
"github.com/scroll-tech/go-ethereum/consensus"
|
"github.com/scroll-tech/go-ethereum/consensus"
|
||||||
"github.com/scroll-tech/go-ethereum/consensus/clique"
|
"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"
|
||||||
"github.com/scroll-tech/go-ethereum/core/bloombits"
|
"github.com/scroll-tech/go-ethereum/core/bloombits"
|
||||||
"github.com/scroll-tech/go-ethereum/core/rawdb"
|
"github.com/scroll-tech/go-ethereum/core/rawdb"
|
||||||
|
|
@ -156,7 +158,7 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client l1.Client) (*Ether
|
||||||
chainDb: chainDb,
|
chainDb: chainDb,
|
||||||
eventMux: stack.EventMux(),
|
eventMux: stack.EventMux(),
|
||||||
accountManager: stack.AccountManager(),
|
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{}),
|
closeBloomHandler: make(chan struct{}),
|
||||||
networkID: config.NetworkId,
|
networkID: config.NetworkId,
|
||||||
gasPrice: config.Miner.GasPrice,
|
gasPrice: config.Miner.GasPrice,
|
||||||
|
|
@ -279,6 +281,7 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client l1.Client) (*Ether
|
||||||
|
|
||||||
config.Miner.SigningDisabled = config.DA.ProduceBlocks
|
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)
|
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.
|
||||||
eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))
|
eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))
|
||||||
|
|
||||||
eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil}
|
eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil}
|
||||||
|
|
@ -528,13 +531,27 @@ func (s *Ethereum) StartMining(threads int) error {
|
||||||
log.Error("Cannot start mining without etherbase", "err", err)
|
log.Error("Cannot start mining without etherbase", "err", err)
|
||||||
return fmt.Errorf("etherbase missing: %v", 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})
|
wallet, err := s.accountManager.Find(accounts.Account{Address: eb})
|
||||||
if wallet == nil || err != nil {
|
if wallet == nil || err != nil {
|
||||||
log.Error("Etherbase account unavailable locally", "err", err)
|
log.Error("Etherbase account unavailable locally", "err", err)
|
||||||
return fmt.Errorf("signer missing: %v", err)
|
return fmt.Errorf("signer missing: %v", err)
|
||||||
}
|
}
|
||||||
clique.Authorize(eb, wallet.SignData)
|
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
|
// If mining is started, we can disable the transaction rejection mechanism
|
||||||
// introduced to speed sync times.
|
// introduced to speed sync times.
|
||||||
|
|
|
||||||
|
|
@ -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
|
// calculateRequestSpan calculates what headers to request from a peer when trying to determine the
|
||||||
// common ancestor.
|
// common ancestor.
|
||||||
// It returns parameters to be used for peer.RequestHeadersByNumber:
|
// It returns parameters to be used for peer.RequestHeadersByNumber:
|
||||||
|
//
|
||||||
// from - starting block number
|
// from - starting block number
|
||||||
// count - number of headers to request
|
// count - number of headers to request
|
||||||
// skip - number of headers to skip
|
// skip - number of headers to skip
|
||||||
|
//
|
||||||
// and also returns 'max', the last block which is expected to be returned by the remote peers,
|
// and also returns 'max', the last block which is expected to be returned by the remote peers,
|
||||||
// given the (from,count,skip)
|
// given the (from,count,skip)
|
||||||
func calculateRequestSpan(remoteHeight, localHeight uint64) (int64, int, int, uint64) {
|
func calculateRequestSpan(remoteHeight, localHeight uint64) (int64, int, int, uint64) {
|
||||||
|
|
|
||||||
|
|
@ -477,6 +477,7 @@ func (q *queue) ReserveReceipts(p *peerConnection, count int) (*fetchRequest, bo
|
||||||
// to access the queue, so they already need a lock anyway.
|
// to access the queue, so they already need a lock anyway.
|
||||||
//
|
//
|
||||||
// Returns:
|
// Returns:
|
||||||
|
//
|
||||||
// item - the fetchRequest
|
// item - the fetchRequest
|
||||||
// progress - whether any progress was made
|
// progress - whether any progress was made
|
||||||
// throttle - if the caller should throttle for a while
|
// throttle - if the caller should throttle for a while
|
||||||
|
|
@ -705,6 +706,11 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh
|
||||||
headerReqTimer.UpdateSince(request.Time)
|
headerReqTimer.UpdateSince(request.Time)
|
||||||
delete(q.headerPendPool, id)
|
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
|
// Ensure headers can be mapped onto the skeleton chain
|
||||||
target := q.headerTaskPool[request.From].Hash()
|
target := q.headerTaskPool[request.From].Hash()
|
||||||
|
|
||||||
|
|
@ -842,6 +848,11 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header,
|
||||||
reqTimer.UpdateSince(request.Time)
|
reqTimer.UpdateSince(request.Time)
|
||||||
delete(pendPool, id)
|
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 no data items were retrieved, mark them as unavailable for the origin peer
|
||||||
if results == 0 {
|
if results == 0 {
|
||||||
for _, header := range request.Headers {
|
for _, header := range request.Headers {
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@ func (r *resultStore) SetThrottleThreshold(threshold uint64) uint64 {
|
||||||
// wants to reserve headers for fetching.
|
// wants to reserve headers for fetching.
|
||||||
//
|
//
|
||||||
// It returns the following:
|
// It returns the following:
|
||||||
|
//
|
||||||
// stale - if true, this item is already passed, and should not be requested again
|
// 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
|
// throttled - if true, the store is at capacity, this particular header is not prio now
|
||||||
// item - the result to store data into
|
// item - the result to store data into
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
package ethconfig
|
package ethconfig
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
|
@ -25,10 +26,13 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/scroll-tech/go-ethereum/consensus/wrapper"
|
||||||
|
|
||||||
"github.com/scroll-tech/go-ethereum/common"
|
"github.com/scroll-tech/go-ethereum/common"
|
||||||
"github.com/scroll-tech/go-ethereum/consensus"
|
"github.com/scroll-tech/go-ethereum/consensus"
|
||||||
"github.com/scroll-tech/go-ethereum/consensus/clique"
|
"github.com/scroll-tech/go-ethereum/consensus/clique"
|
||||||
"github.com/scroll-tech/go-ethereum/consensus/ethash"
|
"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/core"
|
||||||
"github.com/scroll-tech/go-ethereum/eth/downloader"
|
"github.com/scroll-tech/go-ethereum/eth/downloader"
|
||||||
"github.com/scroll-tech/go-ethereum/eth/gasprice"
|
"github.com/scroll-tech/go-ethereum/eth/gasprice"
|
||||||
|
|
@ -38,6 +42,7 @@ import (
|
||||||
"github.com/scroll-tech/go-ethereum/node"
|
"github.com/scroll-tech/go-ethereum/node"
|
||||||
"github.com/scroll-tech/go-ethereum/params"
|
"github.com/scroll-tech/go-ethereum/params"
|
||||||
"github.com/scroll-tech/go-ethereum/rollup/da_syncer"
|
"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.
|
// FullNodeGPO contains default gasprice oracle settings for full node.
|
||||||
|
|
@ -228,11 +233,27 @@ type Config struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateConsensusEngine creates a consensus engine for the given chain configuration.
|
// 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
|
// Case 1: Both SystemContract and Clique are defined: create an upgradable engine.
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 2: Only the Clique engine is defined.
|
||||||
if chainConfig.Clique != nil {
|
if chainConfig.Clique != nil {
|
||||||
return clique.New(chainConfig.Clique, db)
|
return clique.New(chainConfig.Clique, db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Case 3: Only the SystemContract engine is defined.
|
||||||
|
if chainConfig.SystemContract != nil {
|
||||||
|
sysEngine := system_contract.New(context.Background(), chainConfig.SystemContract, l1Client)
|
||||||
|
sysEngine.Start()
|
||||||
|
return sysEngine
|
||||||
|
}
|
||||||
|
|
||||||
// Otherwise assume proof-of-work
|
// Otherwise assume proof-of-work
|
||||||
switch config.PowMode {
|
switch config.PowMode {
|
||||||
case ethash.ModeFake:
|
case ethash.ModeFake:
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/scroll-tech/go-ethereum/crypto"
|
||||||
|
|
||||||
"github.com/scroll-tech/go-ethereum/common"
|
"github.com/scroll-tech/go-ethereum/common"
|
||||||
"github.com/scroll-tech/go-ethereum/consensus/ethash"
|
"github.com/scroll-tech/go-ethereum/consensus/ethash"
|
||||||
"github.com/scroll-tech/go-ethereum/core"
|
"github.com/scroll-tech/go-ethereum/core"
|
||||||
|
|
@ -48,7 +50,26 @@ type testEthHandler struct {
|
||||||
txBroadcasts event.Feed
|
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) StateBloom() *trie.SyncBloom { panic("no backing state bloom") }
|
||||||
func (h *testEthHandler) TxPool() eth.TxPool { panic("no backing tx pool") }
|
func (h *testEthHandler) TxPool() eth.TxPool { panic("no backing tx pool") }
|
||||||
func (h *testEthHandler) AcceptTxs() bool { return true }
|
func (h *testEthHandler) AcceptTxs() bool { return true }
|
||||||
|
|
@ -505,8 +526,7 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo
|
||||||
var response *types.Header
|
var response *types.Header
|
||||||
if checkpoint {
|
if checkpoint {
|
||||||
number := (uint64(rand.Intn(500))+1)*params.CHTFrequency - 1
|
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.checkpointNumber = number
|
||||||
handler.handler.checkpointHash = response.Hash()
|
handler.handler.checkpointHash = response.Hash()
|
||||||
}
|
}
|
||||||
|
|
@ -554,6 +574,7 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo
|
||||||
query.Origin.Number, query.Amount, query.Skip, query.Reverse,
|
query.Origin.Number, query.Amount, query.Skip, query.Reverse,
|
||||||
response.Number.Uint64(), 1, 0, false)
|
response.Number.Uint64(), 1, 0, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a block to reply to the challenge if no timeout is simulated.
|
// Create a block to reply to the challenge if no timeout is simulated.
|
||||||
if !timeout {
|
if !timeout {
|
||||||
if empty {
|
if empty {
|
||||||
|
|
@ -565,7 +586,8 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo
|
||||||
t.Fatalf("failed to answer challenge: %v", err)
|
t.Fatalf("failed to answer challenge: %v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} 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)
|
t.Fatalf("failed to answer challenge: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -573,7 +595,6 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo
|
||||||
}
|
}
|
||||||
// Wait until the test timeout passes to ensure proper cleanup
|
// Wait until the test timeout passes to ensure proper cleanup
|
||||||
time.Sleep(syncChallengeTimeout + 300*time.Millisecond)
|
time.Sleep(syncChallengeTimeout + 300*time.Millisecond)
|
||||||
|
|
||||||
// Verify that the remote peer is maintained or dropped.
|
// Verify that the remote peer is maintained or dropped.
|
||||||
if drop {
|
if drop {
|
||||||
<-handlerDone
|
<-handlerDone
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/scroll-tech/go-ethereum/core/types"
|
"github.com/scroll-tech/go-ethereum/core/types"
|
||||||
"github.com/scroll-tech/go-ethereum/log"
|
"github.com/scroll-tech/go-ethereum/log"
|
||||||
"github.com/scroll-tech/go-ethereum/metrics"
|
"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/rlp"
|
||||||
"github.com/scroll-tech/go-ethereum/trie"
|
"github.com/scroll-tech/go-ethereum/trie"
|
||||||
)
|
)
|
||||||
|
|
@ -266,6 +267,16 @@ func handleNewBlock(backend Backend, msg Decoder, peer *Peer) error {
|
||||||
if err := msg.Decode(ann); err != nil {
|
if err := msg.Decode(ann); err != nil {
|
||||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
if err := ann.sanityCheck(); err != nil {
|
if err := ann.sanityCheck(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -294,6 +305,17 @@ func handleBlockHeaders66(backend Backend, msg Decoder, peer *Peer) error {
|
||||||
}
|
}
|
||||||
requestTracker.Fulfil(peer.id, peer.version, BlockHeadersMsg, res.RequestId)
|
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)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
res.BlockHeadersPacket = headersCopy
|
||||||
return backend.Handle(peer, &res.BlockHeadersPacket)
|
return backend.Handle(peer, &res.BlockHeadersPacket)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -277,6 +277,9 @@ func (p *Peer) AsyncSendNewBlockHash(block *types.Block) {
|
||||||
func (p *Peer) SendNewBlock(block *types.Block, td *big.Int) error {
|
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
|
// Mark all the block hash as known, but ensure we don't overflow our limits
|
||||||
p.knownBlocks.Add(block.Hash())
|
p.knownBlocks.Add(block.Hash())
|
||||||
|
hCopy := block.Header()
|
||||||
|
hCopy.PrepareForNetwork()
|
||||||
|
block = block.CopyBlockDeepWithHeader(hCopy)
|
||||||
return p2p.Send(p.rw, NewBlockMsg, &NewBlockPacket{
|
return p2p.Send(p.rw, NewBlockMsg, &NewBlockPacket{
|
||||||
Block: block,
|
Block: block,
|
||||||
TD: td,
|
TD: td,
|
||||||
|
|
@ -297,6 +300,14 @@ func (p *Peer) AsyncSendNewBlock(block *types.Block, td *big.Int) {
|
||||||
|
|
||||||
// ReplyBlockHeaders is the eth/66 version of SendBlockHeaders.
|
// ReplyBlockHeaders is the eth/66 version of SendBlockHeaders.
|
||||||
func (p *Peer) ReplyBlockHeaders(id uint64, headers []*types.Header) error {
|
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{
|
return p2p.Send(p.rw, BlockHeadersMsg, BlockHeadersPacket66{
|
||||||
RequestId: id,
|
RequestId: id,
|
||||||
BlockHeadersPacket: headers,
|
BlockHeadersPacket: headers,
|
||||||
|
|
|
||||||
|
|
@ -370,6 +370,7 @@ func createStorageRequestResponse(t *testPeer, root common.Hash, accounts []comm
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
// 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) {
|
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
|
var size uint64
|
||||||
|
|
|
||||||
|
|
@ -388,11 +388,13 @@ const AssetDebug = false
|
||||||
// directory embedded in the file by go-bindata.
|
// directory embedded in the file by go-bindata.
|
||||||
// For example if you run go-bindata on data/... and data contains the
|
// For example if you run go-bindata on data/... and data contains the
|
||||||
// following hierarchy:
|
// following hierarchy:
|
||||||
|
//
|
||||||
// data/
|
// data/
|
||||||
// foo.txt
|
// foo.txt
|
||||||
// img/
|
// img/
|
||||||
// a.png
|
// a.png
|
||||||
// b.png
|
// b.png
|
||||||
|
//
|
||||||
// then AssetDir("data") would return []string{"foo.txt", "img"},
|
// then AssetDir("data") would return []string{"foo.txt", "img"},
|
||||||
// AssetDir("data/img") would return []string{"a.png", "b.png"},
|
// AssetDir("data/img") would return []string{"a.png", "b.png"},
|
||||||
// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and
|
// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ func init() {
|
||||||
// a reversed signature can be matched against the size of the data.
|
// a reversed signature can be matched against the size of the data.
|
||||||
//
|
//
|
||||||
// Example:
|
// Example:
|
||||||
|
//
|
||||||
// > debug.traceTransaction( "0x214e597e35da083692f5386141e69f47e973b2c56e7a8073b1ea08fd7571e9de", {tracer: "4byteTracer"})
|
// > debug.traceTransaction( "0x214e597e35da083692f5386141e69f47e973b2c56e7a8073b1ea08fd7571e9de", {tracer: "4byteTracer"})
|
||||||
// {
|
// {
|
||||||
// 0x27dc297e-128: 1,
|
// 0x27dc297e-128: 1,
|
||||||
|
|
|
||||||
2
go.mod
2
go.mod
|
|
@ -22,7 +22,7 @@ require (
|
||||||
github.com/edsrzf/mmap-go v1.0.0
|
github.com/edsrzf/mmap-go v1.0.0
|
||||||
github.com/ethereum/c-kzg-4844/bindings/go v0.0.0-20230126171313-363c7d7593b4
|
github.com/ethereum/c-kzg-4844/bindings/go v0.0.0-20230126171313-363c7d7593b4
|
||||||
github.com/fatih/color v1.7.0
|
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/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff
|
||||||
github.com/go-stack/stack v1.8.1
|
github.com/go-stack/stack v1.8.1
|
||||||
github.com/golang/protobuf v1.4.3
|
github.com/golang/protobuf v1.4.3
|
||||||
|
|
|
||||||
4
go.sum
4
go.sum
|
|
@ -134,8 +134,8 @@ 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/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 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
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.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA=
|
||||||
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
|
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/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.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||||
|
|
|
||||||
|
|
@ -498,7 +498,7 @@ func newLesClientService(ctx *adapters.ServiceContext, stack *node.Node) (node.L
|
||||||
config := ethconfig.Defaults
|
config := ethconfig.Defaults
|
||||||
config.SyncMode = (ethdownloader.SyncMode)(downloader.LightSync)
|
config.SyncMode = (ethdownloader.SyncMode)(downloader.LightSync)
|
||||||
config.Ethash.PowMode = ethash.ModeFake
|
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) {
|
func newLesServerService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ import (
|
||||||
"github.com/scroll-tech/go-ethereum/p2p/enr"
|
"github.com/scroll-tech/go-ethereum/p2p/enr"
|
||||||
"github.com/scroll-tech/go-ethereum/params"
|
"github.com/scroll-tech/go-ethereum/params"
|
||||||
"github.com/scroll-tech/go-ethereum/rlp"
|
"github.com/scroll-tech/go-ethereum/rlp"
|
||||||
|
"github.com/scroll-tech/go-ethereum/rollup/sync_service"
|
||||||
"github.com/scroll-tech/go-ethereum/rpc"
|
"github.com/scroll-tech/go-ethereum/rpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -79,7 +80,7 @@ type LightEthereum struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates an instance of the light client.
|
// 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)
|
chainDb, err := stack.OpenDatabase("lightchaindata", config.DatabaseCache, config.DatabaseHandles, "eth/db/chaindata/", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -109,7 +110,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) {
|
||||||
eventMux: stack.EventMux(),
|
eventMux: stack.EventMux(),
|
||||||
reqDist: newRequestDistributor(peers, &mclock.System{}),
|
reqDist: newRequestDistributor(peers, &mclock.System{}),
|
||||||
accountManager: stack.AccountManager(),
|
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),
|
bloomRequests: make(chan chan *bloombits.Retrieval),
|
||||||
bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations),
|
bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations),
|
||||||
p2pServer: stack.Server(),
|
p2pServer: stack.Server(),
|
||||||
|
|
|
||||||
|
|
@ -705,6 +705,11 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh
|
||||||
headerReqTimer.UpdateSince(request.Time)
|
headerReqTimer.UpdateSince(request.Time)
|
||||||
delete(q.headerPendPool, id)
|
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
|
// Ensure headers can be mapped onto the skeleton chain
|
||||||
target := q.headerTaskPool[request.From].Hash()
|
target := q.headerTaskPool[request.From].Hash()
|
||||||
|
|
||||||
|
|
@ -842,6 +847,11 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header,
|
||||||
reqTimer.UpdateSince(request.Time)
|
reqTimer.UpdateSince(request.Time)
|
||||||
delete(pendPool, id)
|
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 no data items were retrieved, mark them as unavailable for the origin peer
|
||||||
if results == 0 {
|
if results == 0 {
|
||||||
for _, header := range request.Headers {
|
for _, header := range request.Headers {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/scroll-tech/go-ethereum/consensus/system_contract"
|
||||||
|
|
||||||
"github.com/scroll-tech/go-ethereum/common"
|
"github.com/scroll-tech/go-ethereum/common"
|
||||||
"github.com/scroll-tech/go-ethereum/consensus"
|
"github.com/scroll-tech/go-ethereum/consensus"
|
||||||
"github.com/scroll-tech/go-ethereum/consensus/misc"
|
"github.com/scroll-tech/go-ethereum/consensus/misc"
|
||||||
|
|
@ -358,8 +360,11 @@ func (w *worker) mainLoop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
var retryableCommitError *retryableCommitError
|
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)
|
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 {
|
if _, err = w.tryCommitNewWork(time.Now(), w.current.header.ParentHash, w.current.reorging, w.current.reorgReason); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -194,7 +194,7 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) {
|
||||||
ethConf.SyncMode = downloader.LightSync
|
ethConf.SyncMode = downloader.LightSync
|
||||||
ethConf.NetworkId = uint64(config.EthereumNetworkID)
|
ethConf.NetworkId = uint64(config.EthereumNetworkID)
|
||||||
ethConf.DatabaseCache = config.EthereumDatabaseCache
|
ethConf.DatabaseCache = config.EthereumDatabaseCache
|
||||||
lesBackend, err := les.New(rawStack, ðConf)
|
lesBackend, err := les.New(rawStack, ðConf, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ethereum init: %v", err)
|
return nil, fmt.Errorf("ethereum init: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -650,6 +650,7 @@ type ChainConfig struct {
|
||||||
// Various consensus engines
|
// Various consensus engines
|
||||||
Ethash *EthashConfig `json:"ethash,omitempty"`
|
Ethash *EthashConfig `json:"ethash,omitempty"`
|
||||||
Clique *CliqueConfig `json:"clique,omitempty"`
|
Clique *CliqueConfig `json:"clique,omitempty"`
|
||||||
|
SystemContract *SystemContractConfig `json:"systemContract,omitempty"`
|
||||||
|
|
||||||
// Scroll genesis extension: enable scroll rollup-related traces & state transition
|
// Scroll genesis extension: enable scroll rollup-related traces & state transition
|
||||||
Scroll ScrollConfig `json:"scroll,omitempty"`
|
Scroll ScrollConfig `json:"scroll,omitempty"`
|
||||||
|
|
@ -771,6 +772,21 @@ func (c *CliqueConfig) String() string {
|
||||||
return "clique"
|
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.
|
// String implements the fmt.Stringer interface.
|
||||||
func (c *ChainConfig) String() string {
|
func (c *ChainConfig) String() string {
|
||||||
var engine interface{}
|
var engine interface{}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import (
|
||||||
const (
|
const (
|
||||||
VersionMajor = 5 // Major version component of the current release
|
VersionMajor = 5 // Major version component of the current release
|
||||||
VersionMinor = 8 // Minor 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
|
VersionMeta = "mainnet" // Version metadata to append to the version string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ type Client interface {
|
||||||
TransactionByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, isPending bool, err 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)
|
BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
|
||||||
CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
type MockNopClient struct{}
|
type MockNopClient struct{}
|
||||||
|
|
@ -58,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) {
|
func (m *MockNopClient) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MockNopClient) StorageAt(ctx context.Context, contract common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,4 +19,5 @@ type EthClient interface {
|
||||||
SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error)
|
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)
|
TransactionByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, isPending bool, err error)
|
||||||
BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, 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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue