Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: add system config consensus to deprecate clique #1102

Merged
merged 39 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
b68dcb7
feat: add system config consensus to deprecate poa
yiweichi Dec 15, 2024
8e4deb1
fix: check extra field lens
yiweichi Dec 16, 2024
a77524f
Finish implementation with one signer only
ranchalp Jan 16, 2025
4047f24
Implement unit tests
ranchalp Jan 17, 2025
b68d2d6
Only check signature if block not requested
ranchalp Jan 21, 2025
ad96fa4
Remove Extra from rlp encoding (and hashing)
ranchalp Jan 22, 2025
ec2e3d0
Implement UpgradableEngine as middleware for Clique/SystemContract
ranchalp Jan 22, 2025
20185f2
Use isEuclid, add json tag to Requested, make go idiomatic
ranchalp Jan 23, 2025
4d00f42
Changes post integration test
ranchalp Feb 6, 2025
9d8a128
Merge branch 'develop' into feat-system-config-consensus
ranchalp Feb 6, 2025
80e06b8
Remove comment
ranchalp Feb 6, 2025
d4c725e
Address comments
ranchalp Feb 7, 2025
8f961e7
Merge
ranchalp Feb 10, 2025
ebd8e61
New field BlockSignature (not used in hashing/JSON)
ranchalp Feb 13, 2025
c3f5b46
Enforce .Extra to be an empty slice
ranchalp Feb 13, 2025
3190585
replace header.Requested for header.IsNewBlock
ranchalp Feb 13, 2025
38135a3
mark new block as IsNewBlock
ranchalp Feb 13, 2025
d59e79b
Make new RLP optional fields always default for legacy/reth compatibi…
ranchalp Feb 17, 2025
b077f71
Placing new fields as last non-zero rlp:optional values used by Scroll
ranchalp Feb 17, 2025
6b2a63b
Penalize nodes that send non-zero Euclid V2 header field values
ranchalp Feb 18, 2025
3fa378d
Bring back .Requested to downloader instead of .IsNewBlock
ranchalp Feb 20, 2025
6638057
Replace IsNewBlock for Requested
ranchalp Feb 24, 2025
cd6344d
Address comments
ranchalp Feb 24, 2025
f6bcdd0
merge
ranchalp Feb 24, 2025
9ca7498
Merge branch 'develop' into feat-system-config-consensus
ranchalp Feb 24, 2025
80eba61
prevent timing issues in tests
jonastheis Feb 25, 2025
b2fb9a2
fix ci
jonastheis Feb 25, 2025
396db64
chore: auto version bump [bot]
jonastheis Feb 25, 2025
cc5c7dc
Update consensus/system_contract/consensus.go
ranchalp Feb 25, 2025
e70acc6
Merge branch 'develop' into feat-system-config-consensus
ranchalp Feb 25, 2025
b914e00
chore: auto version bump [bot]
ranchalp Feb 25, 2025
0b00789
Update consensus/system_contract/consensus.go
ranchalp Feb 25, 2025
d1e4790
Remove whitespaces and merge version number
ranchalp Feb 25, 2025
3839bf7
chore: auto version bump [bot]
ranchalp Feb 25, 2025
3c2347e
validate that the read address from L1 is not empty and improved erro…
jonastheis Feb 25, 2025
07559cb
Fix indentation issue
ranchalp Feb 26, 2025
83bf259
Fix test
ranchalp Feb 26, 2025
3151105
goimports fix
ranchalp Feb 26, 2025
297186b
goimports
Thegaram Feb 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/faucet/faucet.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
20 changes: 10 additions & 10 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -1981,16 +1981,6 @@ func SetDNSDiscoveryDefaults(cfg *ethconfig.Config, genesis common.Hash) {
// The second return value is the full node instance, which may be nil if the
// node is running as a light client.
func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend, *eth.Ethereum) {
if cfg.SyncMode == downloader.LightSync {
backend, err := les.New(stack, cfg)
if err != nil {
Fatalf("Failed to register the Ethereum service: %v", err)
}
scrollTracerWrapper := tracing.NewTracerWrapper()
stack.RegisterAPIs(tracers.APIs(backend.ApiBackend, scrollTracerWrapper))
return backend.ApiBackend, nil
}

// initialize L1 client for sync service
// note: we need to do this here to avoid circular dependency
l1EndpointUrl := stack.Config().L1Endpoint
Expand All @@ -2006,6 +1996,16 @@ func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend
log.Info("Initialized L1 client", "endpoint", l1EndpointUrl)
}

if cfg.SyncMode == downloader.LightSync {
backend, err := les.New(stack, cfg, l1Client)
if err != nil {
Fatalf("Failed to register the Ethereum service: %v", err)
}
scrollTracerWrapper := tracing.NewTracerWrapper()
stack.RegisterAPIs(tracers.APIs(backend.ApiBackend, scrollTracerWrapper))
return backend.ApiBackend, nil
}

backend, err := eth.New(stack, cfg, l1Client)
if err != nil {
Fatalf("Failed to register the Ethereum service: %v", err)
Expand Down
198 changes: 198 additions & 0 deletions consensus/consensus_wrapper/consensus.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package consensus_wrapper

import (
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/consensus"
"github.com/scroll-tech/go-ethereum/core/state"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/rpc"
"math/big"
"sync"
)

// UpgradableEngine implements consensus.Engine and acts as a middleware to dispatch
// calls to either Clique or SystemContract consensus based on block height.
type UpgradableEngine struct {
// forkBlock is the block number at which the switchover to SystemContract occurs.
forkBlock *big.Int

// clique is the original Clique consensus engine.
clique consensus.Engine

// system is the new SystemContract consensus engine.
system consensus.Engine
}

// NewUpgradableEngine constructs a new upgradable consensus middleware.
func NewUpgradableEngine(forkBlock *big.Int, clique consensus.Engine, system consensus.Engine) *UpgradableEngine {
return &UpgradableEngine{
forkBlock: forkBlock,
clique: clique,
system: system,
}
}

// chooseEngine returns the appropriate consensus engine based on the header's number.
func (ue *UpgradableEngine) chooseEngine(header *types.Header) consensus.Engine {
if header.Number == nil {
// Fallback: treat as pre-fork if header number is unknown.
return ue.clique
}
// If block number is >= forkBlock, use the new SystemContract consensus, else use Clique.
if header.Number.Cmp(ue.forkBlock) >= 0 {
return ue.system
}
return ue.clique
}

// --------------------
// Methods to implement consensus.Engine

// Author returns the author of the block based on the header.
func (ue *UpgradableEngine) Author(header *types.Header) (common.Address, error) {
return ue.chooseEngine(header).Author(header)
}

// VerifyHeader checks whether a header conforms to the consensus rules of the engine.
func (ue *UpgradableEngine) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, seal bool) error {
return ue.chooseEngine(header).VerifyHeader(chain, header, seal)
}

// VerifyHeaders verifies a batch of headers concurrently. In our use-case,
// headers can only be all system, all clique, or start with clique and then switch once to system.
func (ue *UpgradableEngine) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
abort := make(chan struct{})
out := make(chan error)

// If there are no headers, return a closed error channel.
if len(headers) == 0 {
close(out)
return nil, out
}

// Choose engine for the first and last header.
firstEngine := ue.chooseEngine(headers[0])
lastEngine := ue.chooseEngine(headers[len(headers)-1])

// If the first header is system, then all headers must be system.
if firstEngine == ue.system {
return firstEngine.VerifyHeaders(chain, headers, seals)
}

// If first and last headers are both clique, then all headers are clique.
if firstEngine == lastEngine {
return firstEngine.VerifyHeaders(chain, headers, seals)
}

// Otherwise, headers start as clique then switch to system. Since we assume
// a single switchover, find the first header that uses system.
splitIndex := 0
for i, header := range headers {
if ue.chooseEngine(header) == ue.system {
splitIndex = i
break
}
}
// It's expected that splitIndex is > 0.
cliqueHeaders := headers[:splitIndex]
cliqueSeals := seals[:splitIndex]
systemHeaders := headers[splitIndex:]
systemSeals := seals[splitIndex:]

// Create a wait group to merge results.
var wg sync.WaitGroup
wg.Add(2)

// Launch concurrent verifications.
go func() {
defer wg.Done()
_, cliqueResults := ue.clique.VerifyHeaders(chain, cliqueHeaders, cliqueSeals)
for err := range cliqueResults {
select {
case <-abort:
return
case out <- err:
}
}
}()

go func() {
defer wg.Done()
_, systemResults := ue.system.VerifyHeaders(chain, systemHeaders, systemSeals)
for err := range systemResults {
select {
case <-abort:
return
case out <- err:
}
}
}()

// Close the out channel when both verifications are complete.
go func() {
wg.Wait()
close(out)
}()

return abort, out
}

// Prepare prepares a block header for sealing.
func (ue *UpgradableEngine) Prepare(chain consensus.ChainHeaderReader, header *types.Header) error {
return ue.chooseEngine(header).Prepare(chain, header)
}

// Seal instructs the engine to start sealing a block.
func (ue *UpgradableEngine) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
return ue.chooseEngine(block.Header()).Seal(chain, block, results, stop)
}

// CalcDifficulty calculates the block difficulty if applicable.
func (ue *UpgradableEngine) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, parent *types.Header) *big.Int {
return ue.chooseEngine(parent).CalcDifficulty(chain, time, parent)
}

// Finalize finalizes the block, applying any post-transaction rules.
func (ue *UpgradableEngine) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) {
ue.chooseEngine(header).Finalize(chain, header, state, txs, uncles)
}

// FinalizeAndAssemble finalizes and assembles a new block.
func (ue *UpgradableEngine) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
return ue.chooseEngine(header).FinalizeAndAssemble(chain, header, state, txs, uncles, receipts)
}

// VerifyUncles verifies that no uncles are attached to the block.
func (ue *UpgradableEngine) VerifyUncles(chain consensus.ChainReader, block *types.Block) error {
return ue.chooseEngine(block.Header()).VerifyUncles(chain, block)
}

// APIs returns any RPC APIs exposed by the consensus engine.
func (ue *UpgradableEngine) APIs(chain consensus.ChainHeaderReader) []rpc.API {
// Determine the current chain head.
head := chain.CurrentHeader()
if head == nil {
// Fallback: return the clique APIs (or an empty slice) if we don't have a header.
return ue.clique.APIs(chain)
}

// Choose engine based on whether the chain head is before or after the fork block.
if head.Number.Cmp(ue.forkBlock) >= 0 {
return ue.system.APIs(chain)
}
return ue.clique.APIs(chain)
}

// Close terminates the consensus engine.
func (ue *UpgradableEngine) Close() error {
// Always close both engines.
if err := ue.clique.Close(); err != nil {
return err
}
return ue.system.Close()
}

// SealHash returns the hash of a block prior to it being sealed.
func (ue *UpgradableEngine) SealHash(header *types.Header) common.Hash {
return ue.chooseEngine(header).SealHash(header)
}
18 changes: 18 additions & 0 deletions consensus/system_contract/api.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading