mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-23 13:16: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
|
||||
utils.SetDNSDiscoveryDefaults(&cfg, genesis.ToBlock(nil).Hash())
|
||||
|
||||
lesBackend, err := les.New(stack, &cfg)
|
||||
lesBackend, err := les.New(stack, &cfg, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to register the Ethereum service: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// node is running as a light client.
|
||||
func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend, *eth.Ethereum) {
|
||||
if cfg.SyncMode == downloader.LightSync {
|
||||
backend, err := les.New(stack, cfg)
|
||||
if err != nil {
|
||||
Fatalf("Failed to register the Ethereum service: %v", err)
|
||||
}
|
||||
scrollTracerWrapper := tracing.NewTracerWrapper()
|
||||
stack.RegisterAPIs(tracers.APIs(backend.ApiBackend, scrollTracerWrapper))
|
||||
return backend.ApiBackend, nil
|
||||
}
|
||||
|
||||
// initialize L1 client for sync service
|
||||
// note: we need to do this here to avoid circular dependency
|
||||
l1EndpointUrl := stack.Config().L1Endpoint
|
||||
|
|
@ -2073,6 +2063,16 @@ func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend
|
|||
log.Info("Initialized L1 client", "endpoint", l1EndpointUrl)
|
||||
}
|
||||
|
||||
if cfg.SyncMode == downloader.LightSync {
|
||||
backend, err := les.New(stack, cfg, l1Client)
|
||||
if err != nil {
|
||||
Fatalf("Failed to register the Ethereum service: %v", err)
|
||||
}
|
||||
scrollTracerWrapper := tracing.NewTracerWrapper()
|
||||
stack.RegisterAPIs(tracers.APIs(backend.ApiBackend, scrollTracerWrapper))
|
||||
return backend.ApiBackend, nil
|
||||
}
|
||||
|
||||
backend, err := eth.New(stack, cfg, l1Client)
|
||||
if err != nil {
|
||||
Fatalf("Failed to register the Ethereum service: %v", err)
|
||||
|
|
|
|||
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 *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.
|
||||
// Included for Ethereum compatibility in Scroll SDK
|
||||
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.
|
||||
// Included for Ethereum compatibility in Scroll SDK
|
||||
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
|
||||
|
||||
//Hacky: used internally to mark the header as requested by the downloader at the deliver queue
|
||||
Requested bool `json:"-" rlp:"-"`
|
||||
}
|
||||
|
||||
// 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
|
||||
// RLP encoding.
|
||||
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())
|
||||
|
|
@ -153,6 +168,14 @@ func (h *Header) SanityCheck() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// NetworkCompatibleEuclidV2Fields Enforces that both IsEuclidV2 and BlockSignature are empty when received over the network
|
||||
func (h *Header) NetworkCompatibleEuclidV2Fields() error {
|
||||
if h.IsEuclidV2 || h.BlockSignature != nil {
|
||||
return fmt.Errorf("header contains disallowed Euclid V2 fields (only used locally)")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EmptyBody returns true if there is no additional 'body' to complete the header
|
||||
// that is: no transactions and no uncles.
|
||||
func (h *Header) EmptyBody() bool {
|
||||
|
|
@ -164,6 +187,22 @@ func (h *Header) EmptyReceipts() bool {
|
|||
return h.ReceiptHash == EmptyRootHash
|
||||
}
|
||||
|
||||
func (h *Header) PrepareForNetwork() {
|
||||
if h.IsEuclidV2 {
|
||||
h.IsEuclidV2 = false
|
||||
h.Extra = h.BlockSignature
|
||||
h.BlockSignature = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Header) PrepareFromNetwork(isEuclidV2 bool) {
|
||||
if isEuclidV2 {
|
||||
h.IsEuclidV2 = true
|
||||
h.BlockSignature = h.Extra
|
||||
h.Extra = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Body is a simple (mutable, non-safe) data container for storing and moving
|
||||
// a block's data contents (transactions and uncles) together.
|
||||
type Body struct {
|
||||
|
|
@ -262,6 +301,10 @@ func CopyHeader(h *Header) *Header {
|
|||
cpy.Extra = make([]byte, len(h.Extra))
|
||||
copy(cpy.Extra, h.Extra)
|
||||
}
|
||||
if len(h.BlockSignature) > 0 {
|
||||
cpy.BlockSignature = make([]byte, len(h.BlockSignature))
|
||||
copy(cpy.BlockSignature, h.BlockSignature)
|
||||
}
|
||||
return &cpy
|
||||
}
|
||||
|
||||
|
|
@ -460,6 +503,22 @@ func (b *Block) CountL2Tx() int {
|
|||
return count
|
||||
}
|
||||
|
||||
func (b *Block) CopyBlockDeepWithHeader(header *Header) *Block {
|
||||
return &Block{
|
||||
header: CopyHeader(header),
|
||||
uncles: b.uncles, // slice reference (the slice header is copied but the underlying array is shared)
|
||||
transactions: b.transactions, // reference copy
|
||||
// caches (atomic.Value fields) are reused as-is; if necessary, you might want to load and store their values
|
||||
hash: b.hash,
|
||||
size: b.size,
|
||||
l1MsgCount: b.l1MsgCount,
|
||||
// Other fields are copied by value (td is a pointer so it's shared)
|
||||
td: b.td,
|
||||
ReceivedAt: b.ReceivedAt,
|
||||
ReceivedFrom: b.ReceivedFrom,
|
||||
}
|
||||
}
|
||||
|
||||
type Blocks []*Block
|
||||
|
||||
type BlockWithRowConsumption struct {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ import (
|
|||
"github.com/scroll-tech/go-ethereum/common/hexutil"
|
||||
"github.com/scroll-tech/go-ethereum/consensus"
|
||||
"github.com/scroll-tech/go-ethereum/consensus/clique"
|
||||
"github.com/scroll-tech/go-ethereum/consensus/system_contract"
|
||||
"github.com/scroll-tech/go-ethereum/consensus/wrapper"
|
||||
"github.com/scroll-tech/go-ethereum/core"
|
||||
"github.com/scroll-tech/go-ethereum/core/bloombits"
|
||||
"github.com/scroll-tech/go-ethereum/core/rawdb"
|
||||
|
|
@ -156,7 +158,7 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client l1.Client) (*Ether
|
|||
chainDb: chainDb,
|
||||
eventMux: stack.EventMux(),
|
||||
accountManager: stack.AccountManager(),
|
||||
engine: ethconfig.CreateConsensusEngine(stack, chainConfig, ðashConfig, config.Miner.Notify, config.Miner.Noverify, chainDb),
|
||||
engine: ethconfig.CreateConsensusEngine(stack, chainConfig, ðashConfig, config.Miner.Notify, config.Miner.Noverify, chainDb, l1Client),
|
||||
closeBloomHandler: make(chan struct{}),
|
||||
networkID: config.NetworkId,
|
||||
gasPrice: config.Miner.GasPrice,
|
||||
|
|
@ -279,6 +281,7 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client l1.Client) (*Ether
|
|||
|
||||
config.Miner.SigningDisabled = config.DA.ProduceBlocks
|
||||
eth.miner = miner.New(eth, &config.Miner, eth.blockchain.Config(), eth.EventMux(), eth.engine, eth.isLocalBlock, config.EnableDASyncing && !config.DA.ProduceBlocks)
|
||||
// Some of the extraData is used with Clique consensus (before EuclidV2). After EuclidV2 we use SystemContract consensus where this is overridden when creating a block.
|
||||
eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))
|
||||
|
||||
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)
|
||||
return fmt.Errorf("etherbase missing: %v", err)
|
||||
}
|
||||
if clique, ok := s.engine.(*clique.Clique); ok {
|
||||
if wrapper, ok := s.engine.(*wrapper.UpgradableEngine); ok {
|
||||
wallet, err := s.accountManager.Find(accounts.Account{Address: eb})
|
||||
if wallet == nil || err != nil {
|
||||
log.Error("Etherbase account unavailable locally", "err", err)
|
||||
return fmt.Errorf("signer missing: %v", err)
|
||||
}
|
||||
wrapper.Authorize(eb, wallet.SignData, wallet.SignData)
|
||||
} else if clique, ok := s.engine.(*clique.Clique); ok {
|
||||
wallet, err := s.accountManager.Find(accounts.Account{Address: eb})
|
||||
if wallet == nil || err != nil {
|
||||
log.Error("Etherbase account unavailable locally", "err", err)
|
||||
return fmt.Errorf("signer missing: %v", err)
|
||||
}
|
||||
clique.Authorize(eb, wallet.SignData)
|
||||
} else if systemContract, ok := s.engine.(*system_contract.SystemContract); ok {
|
||||
wallet, err := s.accountManager.Find(accounts.Account{Address: eb})
|
||||
if wallet == nil || err != nil {
|
||||
log.Error("Etherbase account unavailable locally", "err", err)
|
||||
return fmt.Errorf("signer missing: %v", err)
|
||||
}
|
||||
systemContract.Authorize(eb, wallet.SignData)
|
||||
}
|
||||
// If mining is started, we can disable the transaction rejection mechanism
|
||||
// introduced to speed sync times.
|
||||
|
|
|
|||
|
|
@ -702,9 +702,11 @@ func (d *Downloader) fetchHead(p *peerConnection) (head *types.Header, pivot *ty
|
|||
// calculateRequestSpan calculates what headers to request from a peer when trying to determine the
|
||||
// common ancestor.
|
||||
// It returns parameters to be used for peer.RequestHeadersByNumber:
|
||||
//
|
||||
// from - starting block number
|
||||
// count - number of headers to request
|
||||
// skip - number of headers to skip
|
||||
//
|
||||
// and also returns 'max', the last block which is expected to be returned by the remote peers,
|
||||
// given the (from,count,skip)
|
||||
func calculateRequestSpan(remoteHeight, localHeight uint64) (int64, int, int, uint64) {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// item - the fetchRequest
|
||||
// progress - whether any progress was made
|
||||
// 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)
|
||||
delete(q.headerPendPool, id)
|
||||
|
||||
// Hacky: mark that the header was explicitly requested
|
||||
for _, header := range headers {
|
||||
header.Requested = true
|
||||
}
|
||||
|
||||
// Ensure headers can be mapped onto the skeleton chain
|
||||
target := q.headerTaskPool[request.From].Hash()
|
||||
|
||||
|
|
@ -842,6 +848,11 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header,
|
|||
reqTimer.UpdateSince(request.Time)
|
||||
delete(pendPool, id)
|
||||
|
||||
// Hacky: mark that the header was explicitly requested
|
||||
for _, header := range request.Headers {
|
||||
header.Requested = true
|
||||
}
|
||||
|
||||
// If no data items were retrieved, mark them as unavailable for the origin peer
|
||||
if results == 0 {
|
||||
for _, header := range request.Headers {
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ func (r *resultStore) SetThrottleThreshold(threshold uint64) uint64 {
|
|||
// wants to reserve headers for fetching.
|
||||
//
|
||||
// It returns the following:
|
||||
//
|
||||
// stale - if true, this item is already passed, and should not be requested again
|
||||
// throttled - if true, the store is at capacity, this particular header is not prio now
|
||||
// item - the result to store data into
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
package ethconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
"os"
|
||||
"os/user"
|
||||
|
|
@ -25,10 +26,13 @@ import (
|
|||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/consensus/wrapper"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/consensus"
|
||||
"github.com/scroll-tech/go-ethereum/consensus/clique"
|
||||
"github.com/scroll-tech/go-ethereum/consensus/ethash"
|
||||
"github.com/scroll-tech/go-ethereum/consensus/system_contract"
|
||||
"github.com/scroll-tech/go-ethereum/core"
|
||||
"github.com/scroll-tech/go-ethereum/eth/downloader"
|
||||
"github.com/scroll-tech/go-ethereum/eth/gasprice"
|
||||
|
|
@ -38,6 +42,7 @@ import (
|
|||
"github.com/scroll-tech/go-ethereum/node"
|
||||
"github.com/scroll-tech/go-ethereum/params"
|
||||
"github.com/scroll-tech/go-ethereum/rollup/da_syncer"
|
||||
"github.com/scroll-tech/go-ethereum/rollup/sync_service"
|
||||
)
|
||||
|
||||
// FullNodeGPO contains default gasprice oracle settings for full node.
|
||||
|
|
@ -228,11 +233,27 @@ type Config struct {
|
|||
}
|
||||
|
||||
// CreateConsensusEngine creates a consensus engine for the given chain configuration.
|
||||
func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database) consensus.Engine {
|
||||
// If proof-of-authority is requested, set it up
|
||||
func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database, l1Client sync_service.EthClient) consensus.Engine {
|
||||
// Case 1: Both SystemContract and Clique are defined: create an upgradable engine.
|
||||
if chainConfig.SystemContract != nil && chainConfig.Clique != nil {
|
||||
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 {
|
||||
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
|
||||
switch config.PowMode {
|
||||
case ethash.ModeFake:
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/crypto"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/consensus/ethash"
|
||||
"github.com/scroll-tech/go-ethereum/core"
|
||||
|
|
@ -48,7 +50,26 @@ type testEthHandler struct {
|
|||
txBroadcasts event.Feed
|
||||
}
|
||||
|
||||
func (h *testEthHandler) Chain() *core.BlockChain { panic("no backing chain") }
|
||||
func (h *testEthHandler) Chain() *core.BlockChain {
|
||||
chainConfig := ¶ms.ChainConfig{}
|
||||
engine := ethash.NewFaker()
|
||||
chaindb := rawdb.NewMemoryDatabase()
|
||||
|
||||
// Import the canonical chain
|
||||
cacheConfig := &core.CacheConfig{}
|
||||
key1, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
addr1 := crypto.PubkeyToAddress(key1.PublicKey)
|
||||
genesis := core.GenesisBlockForTesting(chaindb, addr1, big.NewInt(1000000))
|
||||
rawdb.WriteBlock(chaindb, genesis)
|
||||
rawdb.WriteCanonicalHash(chaindb, genesis.Hash(), genesis.NumberU64())
|
||||
rawdb.WriteHeadBlockHash(chaindb, genesis.Hash())
|
||||
|
||||
chain, err := core.NewBlockChain(chaindb, cacheConfig, chainConfig, engine, vm.Config{}, nil, nil)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to create chain: %v", err))
|
||||
}
|
||||
return chain
|
||||
}
|
||||
func (h *testEthHandler) StateBloom() *trie.SyncBloom { panic("no backing state bloom") }
|
||||
func (h *testEthHandler) TxPool() eth.TxPool { panic("no backing tx pool") }
|
||||
func (h *testEthHandler) AcceptTxs() bool { return true }
|
||||
|
|
@ -505,8 +526,7 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo
|
|||
var response *types.Header
|
||||
if checkpoint {
|
||||
number := (uint64(rand.Intn(500))+1)*params.CHTFrequency - 1
|
||||
response = &types.Header{Number: big.NewInt(int64(number)), Extra: []byte("valid")}
|
||||
|
||||
response = &types.Header{Number: big.NewInt(int64(number))}
|
||||
handler.handler.checkpointNumber = number
|
||||
handler.handler.checkpointHash = response.Hash()
|
||||
}
|
||||
|
|
@ -554,6 +574,7 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo
|
|||
query.Origin.Number, query.Amount, query.Skip, query.Reverse,
|
||||
response.Number.Uint64(), 1, 0, false)
|
||||
}
|
||||
|
||||
// Create a block to reply to the challenge if no timeout is simulated.
|
||||
if !timeout {
|
||||
if empty {
|
||||
|
|
@ -565,7 +586,8 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo
|
|||
t.Fatalf("failed to answer challenge: %v", err)
|
||||
}
|
||||
} else {
|
||||
if err := remote.ReplyBlockHeaders(request.RequestId, []*types.Header{{Number: response.Number}}); err != nil {
|
||||
number := new(big.Int).Add(response.Number, big.NewInt(1)) // mismatching headers to same request
|
||||
if err := remote.ReplyBlockHeaders(request.RequestId, []*types.Header{{Number: number}}); err != nil {
|
||||
t.Fatalf("failed to answer challenge: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -573,7 +595,6 @@ func testCheckpointChallenge(t *testing.T, syncmode downloader.SyncMode, checkpo
|
|||
}
|
||||
// Wait until the test timeout passes to ensure proper cleanup
|
||||
time.Sleep(syncChallengeTimeout + 300*time.Millisecond)
|
||||
|
||||
// Verify that the remote peer is maintained or dropped.
|
||||
if drop {
|
||||
<-handlerDone
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/scroll-tech/go-ethereum/core/types"
|
||||
"github.com/scroll-tech/go-ethereum/log"
|
||||
"github.com/scroll-tech/go-ethereum/metrics"
|
||||
"github.com/scroll-tech/go-ethereum/p2p"
|
||||
"github.com/scroll-tech/go-ethereum/rlp"
|
||||
"github.com/scroll-tech/go-ethereum/trie"
|
||||
)
|
||||
|
|
@ -266,6 +267,16 @@ func handleNewBlock(backend Backend, msg Decoder, peer *Peer) error {
|
|||
if err := msg.Decode(ann); err != nil {
|
||||
return fmt.Errorf("%w: message %v: %v", errDecode, msg, err)
|
||||
}
|
||||
|
||||
hCopy := ann.Block.Header()
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
|
@ -294,6 +305,17 @@ func handleBlockHeaders66(backend Backend, msg Decoder, peer *Peer) error {
|
|||
}
|
||||
requestTracker.Fulfil(peer.id, peer.version, BlockHeadersMsg, res.RequestId)
|
||||
|
||||
headersCopy := make([]*types.Header, 0, len(res.BlockHeadersPacket))
|
||||
for _, header := range res.BlockHeadersPacket {
|
||||
hCopy := types.CopyHeader(header)
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -277,6 +277,9 @@ func (p *Peer) AsyncSendNewBlockHash(block *types.Block) {
|
|||
func (p *Peer) SendNewBlock(block *types.Block, td *big.Int) error {
|
||||
// Mark all the block hash as known, but ensure we don't overflow our limits
|
||||
p.knownBlocks.Add(block.Hash())
|
||||
hCopy := block.Header()
|
||||
hCopy.PrepareForNetwork()
|
||||
block = block.CopyBlockDeepWithHeader(hCopy)
|
||||
return p2p.Send(p.rw, NewBlockMsg, &NewBlockPacket{
|
||||
Block: block,
|
||||
TD: td,
|
||||
|
|
@ -297,6 +300,14 @@ func (p *Peer) AsyncSendNewBlock(block *types.Block, td *big.Int) {
|
|||
|
||||
// ReplyBlockHeaders is the eth/66 version of SendBlockHeaders.
|
||||
func (p *Peer) ReplyBlockHeaders(id uint64, headers []*types.Header) error {
|
||||
headersCopy := make([]*types.Header, 0, len(headers))
|
||||
for _, header := range headers {
|
||||
hCopy := types.CopyHeader(header)
|
||||
hCopy.PrepareForNetwork()
|
||||
headersCopy = append(headersCopy, hCopy)
|
||||
}
|
||||
headers = headersCopy
|
||||
|
||||
return p2p.Send(p.rw, BlockHeadersMsg, BlockHeadersPacket66{
|
||||
RequestId: id,
|
||||
BlockHeadersPacket: headers,
|
||||
|
|
|
|||
|
|
@ -370,6 +370,7 @@ func createStorageRequestResponse(t *testPeer, root common.Hash, accounts []comm
|
|||
}
|
||||
|
||||
// the createStorageRequestResponseAlwaysProve tests a cornercase, where it always
|
||||
//
|
||||
// supplies the proof for the last account, even if it is 'complete'.h
|
||||
func createStorageRequestResponseAlwaysProve(t *testPeer, root common.Hash, accounts []common.Hash, bOrigin, bLimit []byte, max uint64) (hashes [][]common.Hash, slots [][][]byte, proofs [][]byte) {
|
||||
var size uint64
|
||||
|
|
|
|||
|
|
@ -388,11 +388,13 @@ const AssetDebug = false
|
|||
// directory embedded in the file by go-bindata.
|
||||
// For example if you run go-bindata on data/... and data contains the
|
||||
// following hierarchy:
|
||||
//
|
||||
// data/
|
||||
// foo.txt
|
||||
// img/
|
||||
// a.png
|
||||
// b.png
|
||||
//
|
||||
// then AssetDir("data") would return []string{"foo.txt", "img"},
|
||||
// AssetDir("data/img") would return []string{"a.png", "b.png"},
|
||||
// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ func init() {
|
|||
// a reversed signature can be matched against the size of the data.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// > debug.traceTransaction( "0x214e597e35da083692f5386141e69f47e973b2c56e7a8073b1ea08fd7571e9de", {tracer: "4byteTracer"})
|
||||
// {
|
||||
// 0x27dc297e-128: 1,
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -22,7 +22,7 @@ require (
|
|||
github.com/edsrzf/mmap-go v1.0.0
|
||||
github.com/ethereum/c-kzg-4844/bindings/go v0.0.0-20230126171313-363c7d7593b4
|
||||
github.com/fatih/color v1.7.0
|
||||
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5
|
||||
github.com/fjl/memsize v0.0.2
|
||||
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff
|
||||
github.com/go-stack/stack v1.8.1
|
||||
github.com/golang/protobuf v1.4.3
|
||||
|
|
|
|||
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/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
|
||||
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
|
||||
github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA=
|
||||
github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
|
||||
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
|
|
|
|||
|
|
@ -498,7 +498,7 @@ func newLesClientService(ctx *adapters.ServiceContext, stack *node.Node) (node.L
|
|||
config := ethconfig.Defaults
|
||||
config.SyncMode = (ethdownloader.SyncMode)(downloader.LightSync)
|
||||
config.Ethash.PowMode = ethash.ModeFake
|
||||
return New(stack, &config)
|
||||
return New(stack, &config, nil)
|
||||
}
|
||||
|
||||
func newLesServerService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) {
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ import (
|
|||
"github.com/scroll-tech/go-ethereum/p2p/enr"
|
||||
"github.com/scroll-tech/go-ethereum/params"
|
||||
"github.com/scroll-tech/go-ethereum/rlp"
|
||||
"github.com/scroll-tech/go-ethereum/rollup/sync_service"
|
||||
"github.com/scroll-tech/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
|
|
@ -79,7 +80,7 @@ type LightEthereum struct {
|
|||
}
|
||||
|
||||
// New creates an instance of the light client.
|
||||
func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) {
|
||||
func New(stack *node.Node, config *ethconfig.Config, l1Client sync_service.EthClient) (*LightEthereum, error) {
|
||||
chainDb, err := stack.OpenDatabase("lightchaindata", config.DatabaseCache, config.DatabaseHandles, "eth/db/chaindata/", false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -109,7 +110,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) {
|
|||
eventMux: stack.EventMux(),
|
||||
reqDist: newRequestDistributor(peers, &mclock.System{}),
|
||||
accountManager: stack.AccountManager(),
|
||||
engine: ethconfig.CreateConsensusEngine(stack, chainConfig, &config.Ethash, nil, false, chainDb),
|
||||
engine: ethconfig.CreateConsensusEngine(stack, chainConfig, &config.Ethash, nil, false, chainDb, l1Client),
|
||||
bloomRequests: make(chan chan *bloombits.Retrieval),
|
||||
bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations),
|
||||
p2pServer: stack.Server(),
|
||||
|
|
|
|||
|
|
@ -705,6 +705,11 @@ func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh
|
|||
headerReqTimer.UpdateSince(request.Time)
|
||||
delete(q.headerPendPool, id)
|
||||
|
||||
// Hacky: mark that the header was explicitly requested
|
||||
for _, header := range headers {
|
||||
header.Requested = true
|
||||
}
|
||||
|
||||
// Ensure headers can be mapped onto the skeleton chain
|
||||
target := q.headerTaskPool[request.From].Hash()
|
||||
|
||||
|
|
@ -842,6 +847,11 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header,
|
|||
reqTimer.UpdateSince(request.Time)
|
||||
delete(pendPool, id)
|
||||
|
||||
// Hacky: mark that the header was explicitly requested
|
||||
for _, header := range request.Headers {
|
||||
header.Requested = true
|
||||
}
|
||||
|
||||
// If no data items were retrieved, mark them as unavailable for the origin peer
|
||||
if results == 0 {
|
||||
for _, header := range request.Headers {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/consensus/system_contract"
|
||||
|
||||
"github.com/scroll-tech/go-ethereum/common"
|
||||
"github.com/scroll-tech/go-ethereum/consensus"
|
||||
"github.com/scroll-tech/go-ethereum/consensus/misc"
|
||||
|
|
@ -358,8 +360,11 @@ func (w *worker) mainLoop() {
|
|||
}
|
||||
|
||||
var retryableCommitError *retryableCommitError
|
||||
if errors.As(err, &retryableCommitError) {
|
||||
if errors.As(err, &retryableCommitError) || errors.Is(err, system_contract.ErrUnauthorizedSigner) {
|
||||
log.Warn("failed to commit to a block, retrying", "err", err)
|
||||
if errors.Is(err, system_contract.ErrUnauthorizedSigner) {
|
||||
time.Sleep(5 * time.Second) // half the time it takes for the system contract consensus to read and update the address locally.
|
||||
}
|
||||
if _, err = w.tryCommitNewWork(time.Now(), w.current.header.ParentHash, w.current.reorging, w.current.reorgReason); err != nil {
|
||||
continue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) {
|
|||
ethConf.SyncMode = downloader.LightSync
|
||||
ethConf.NetworkId = uint64(config.EthereumNetworkID)
|
||||
ethConf.DatabaseCache = config.EthereumDatabaseCache
|
||||
lesBackend, err := les.New(rawStack, ðConf)
|
||||
lesBackend, err := les.New(rawStack, ðConf, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ethereum init: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -650,6 +650,7 @@ type ChainConfig struct {
|
|||
// Various consensus engines
|
||||
Ethash *EthashConfig `json:"ethash,omitempty"`
|
||||
Clique *CliqueConfig `json:"clique,omitempty"`
|
||||
SystemContract *SystemContractConfig `json:"systemContract,omitempty"`
|
||||
|
||||
// Scroll genesis extension: enable scroll rollup-related traces & state transition
|
||||
Scroll ScrollConfig `json:"scroll,omitempty"`
|
||||
|
|
@ -771,6 +772,21 @@ func (c *CliqueConfig) String() string {
|
|||
return "clique"
|
||||
}
|
||||
|
||||
// SystemContractConfig is the consensus engine configs for rollup sequencer sealing.
|
||||
type SystemContractConfig struct {
|
||||
Period uint64 `json:"period"` // Number of seconds between blocks to enforce
|
||||
|
||||
SystemContractAddress common.Address `json:"system_contract_address"` // address of system contract on L1
|
||||
SystemContractSlot common.Hash `json:"system_contract_slot"` // slot of signer address in system contract on L1
|
||||
|
||||
RelaxedPeriod bool `json:"relaxed_period"` // Relaxes the period to be just an upper bound
|
||||
}
|
||||
|
||||
// String implements the stringer interface, returning the consensus engine details.
|
||||
func (c *SystemContractConfig) String() string {
|
||||
return "system_contract"
|
||||
}
|
||||
|
||||
// String implements the fmt.Stringer interface.
|
||||
func (c *ChainConfig) String() string {
|
||||
var engine interface{}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import (
|
|||
const (
|
||||
VersionMajor = 5 // Major version component of the current release
|
||||
VersionMinor = 8 // Minor version component of the current release
|
||||
VersionPatch = 13 // Patch version component of the current release
|
||||
VersionPatch = 14 // Patch version component of the current release
|
||||
VersionMeta = "mainnet" // Version metadata to append to the version string
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ type Client interface {
|
|||
TransactionByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, isPending bool, err error)
|
||||
BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
|
||||
CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
|
||||
StorageAt(ctx context.Context, contract common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error)
|
||||
}
|
||||
|
||||
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) {
|
||||
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)
|
||||
TransactionByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, isPending bool, err error)
|
||||
BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
|
||||
StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue