Skip to content

Commit 1dedddb

Browse files
authored
feat: address system contract consensus issues and comments (#1126)
* feat: address system contract consensus issues and comments * fix Euclid header chain verification
1 parent 365b829 commit 1dedddb

File tree

11 files changed

+127
-69
lines changed

11 files changed

+127
-69
lines changed

consensus/clique/clique.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@ type Clique struct {
191191
// New creates a Clique proof-of-authority consensus engine with the initial
192192
// signers set to the ones provided by the user.
193193
func New(config *params.CliqueConfig, db ethdb.Database) *Clique {
194+
log.Info("Initializing clique consensus engine")
195+
194196
// Set any missing consensus parameters to their defaults
195197
conf := *config
196198
if conf.Epoch == 0 {

consensus/system_contract/api.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,20 @@ package system_contract
22

33
import (
44
"github.com/scroll-tech/go-ethereum/common"
5-
"github.com/scroll-tech/go-ethereum/consensus"
6-
"github.com/scroll-tech/go-ethereum/rpc"
75
)
86

97
// API is a user facing RPC API to allow controlling the signer and voting
108
// mechanisms of the proof-of-authority scheme.
119
type API struct {
12-
chain consensus.ChainHeaderReader
10+
system_contract *SystemContract
1311
}
1412

1513
// GetSigners retrieves the list of authorized signers at the specified block.
16-
func (api *API) GetSigners(number *rpc.BlockNumber) ([]common.Address, error) {
17-
return nil, nil
14+
func (api *API) GetLocalSigner() (common.Address, error) {
15+
return api.system_contract.localSignerAddress(), nil
16+
}
17+
18+
// GetSigners retrieves the list of authorized signers at the specified block.
19+
func (api *API) GetAuthorizedSigner() (common.Address, error) {
20+
return api.system_contract.currentSignerAddressL1(), nil
1821
}

consensus/system_contract/consensus.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,27 @@ var (
3636
// errUnknownBlock is returned when the list of signers is requested for a block
3737
// that is not part of the local blockchain.
3838
errUnknownBlock = errors.New("unknown block")
39+
3940
// errNonceNotEmpty is returned if a nonce value is non-zero
4041
errInvalidNonce = errors.New("nonce not empty nor zero")
42+
4143
// errMissingSignature is returned if a block's extra-data section doesn't seem
4244
// to contain a 65 byte secp256k1 signature.
4345
errMissingSignature = errors.New("extra-data 65 byte signature missing")
46+
4447
// errInvalidMixDigest is returned if a block's mix digest is non-zero.
4548
errInvalidMixDigest = errors.New("non-zero mix digest")
49+
4650
// errInvalidUncleHash is returned if a block contains an non-empty uncle list.
4751
errInvalidUncleHash = errors.New("non empty uncle hash")
52+
4853
// errInvalidDifficulty is returned if a difficulty value is non-zero
4954
errInvalidDifficulty = errors.New("non-one difficulty")
55+
5056
// errInvalidTimestamp is returned if the timestamp of a block is lower than
51-
// the previous block's timestamp + the minimum block period.
57+
// the previous block's timestamp.
5258
errInvalidTimestamp = errors.New("invalid timestamp")
59+
5360
// errInvalidExtra is returned if the extra data is not empty
5461
errInvalidExtra = errors.New("invalid extra")
5562
)
@@ -335,10 +342,6 @@ func ecrecover(header *types.Header) (common.Address, error) {
335342

336343
// SystemContractRLP returns the rlp bytes which needs to be signed for the system contract
337344
// sealing. The RLP to sign consists of the entire header apart from the ExtraData
338-
//
339-
// Note, the method requires the extra data to be at least 65 bytes, otherwise it
340-
// panics. This is done to avoid accidentally using both forms (signature present
341-
// or not), which could be abused to produce different hashes for the same header.
342345
func SystemContractRLP(header *types.Header) []byte {
343346
b := new(bytes.Buffer)
344347
encodeSigHeader(b, header)
@@ -354,8 +357,12 @@ func (s *SystemContract) CalcDifficulty(chain consensus.ChainHeaderReader, time
354357
// controlling the signer voting.
355358
func (s *SystemContract) APIs(chain consensus.ChainHeaderReader) []rpc.API {
356359
return []rpc.API{{
357-
Namespace: "system_contract",
358-
Service: &API{},
360+
// note: cannot use underscore in namespace,
361+
// but overlap with another module's name space works fine.
362+
Namespace: "scroll",
363+
Version: "1.0",
364+
Service: &API{system_contract: s},
365+
Public: false,
359366
}}
360367
}
361368

consensus/system_contract/system_contract.go

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ const (
1616
defaultSyncInterval = 10 * time.Second
1717
)
1818

19-
// SystemContract
19+
// SystemContract is the proof-of-authority consensus engine that fetches
20+
// the authorized signer from the SystemConfig contract, starting from EuclidV2.
2021
type SystemContract struct {
2122
config *params.SystemContractConfig // Consensus engine configuration parameters
22-
client sync_service.EthClient
23+
client sync_service.EthClient // RPC client to fetch info from L1
2324

2425
signerAddressL1 common.Address // Address of the signer stored in L1 System Contract
2526

@@ -32,8 +33,10 @@ type SystemContract struct {
3233
}
3334

3435
// New creates a SystemContract consensus engine with the initial
35-
// signers set to the ones provided by the user.
36+
// authorized signer fetched from L1 (if available).
3637
func New(ctx context.Context, config *params.SystemContractConfig, client sync_service.EthClient) *SystemContract {
38+
log.Info("Initializing system_contract consensus engine", "config", config)
39+
3740
ctx, cancel := context.WithCancel(ctx)
3841

3942
s := &SystemContract{
@@ -53,7 +56,7 @@ func New(ctx context.Context, config *params.SystemContractConfig, client sync_s
5356
// Authorize injects a private key into the consensus engine to mint new blocks
5457
// with.
5558
func (s *SystemContract) Authorize(signer common.Address, signFn SignerFn) {
56-
log.Info("Authorizing system contract", "signer", signer.Hex())
59+
log.Info("Authorizing system contract consensus", "signer", signer.Hex())
5760
s.lock.Lock()
5861
defer s.lock.Unlock()
5962

@@ -93,19 +96,17 @@ func (s *SystemContract) fetchAddressFromL1() error {
9396

9497
// Validate the address is not empty
9598
if bAddress == (common.Address{}) {
96-
return fmt.Errorf("retrieved empty signer address from L1 System Contract")
99+
return fmt.Errorf("retrieved empty signer address from L1 System Contract: contract=%s, slot=%s", s.config.SystemContractAddress.Hex(), s.config.SystemContractSlot.Hex())
97100
}
98101

99102
log.Debug("Read address from system contract", "address", bAddress.Hex())
100103

101-
s.lock.RLock()
102-
addressChanged := s.signerAddressL1 != bAddress
103-
s.lock.RUnlock()
104+
s.lock.Lock()
105+
defer s.lock.Unlock()
104106

105-
if addressChanged {
106-
s.lock.Lock()
107+
if s.signerAddressL1 != bAddress {
107108
s.signerAddressL1 = bAddress
108-
s.lock.Unlock()
109+
log.Info("Updated new signer from L1 system contract", "signer", bAddress.Hex())
109110
}
110111

111112
return nil
@@ -123,3 +124,10 @@ func (s *SystemContract) currentSignerAddressL1() common.Address {
123124

124125
return s.signerAddressL1
125126
}
127+
128+
func (s *SystemContract) localSignerAddress() common.Address {
129+
s.lock.RLock()
130+
defer s.lock.RUnlock()
131+
132+
return s.signer
133+
}

consensus/wrapper/consensus.go

Lines changed: 52 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,22 @@ package wrapper
22

33
import (
44
"math/big"
5-
"sync"
5+
"time"
66

77
"github.com/scroll-tech/go-ethereum/common"
88
"github.com/scroll-tech/go-ethereum/consensus"
99
"github.com/scroll-tech/go-ethereum/consensus/clique"
1010
"github.com/scroll-tech/go-ethereum/consensus/system_contract"
1111
"github.com/scroll-tech/go-ethereum/core/state"
1212
"github.com/scroll-tech/go-ethereum/core/types"
13+
"github.com/scroll-tech/go-ethereum/log"
1314
"github.com/scroll-tech/go-ethereum/rpc"
1415
)
1516

1617
// UpgradableEngine implements consensus.Engine and acts as a middleware to dispatch
1718
// calls to either Clique or SystemContract consensus.
1819
type UpgradableEngine struct {
19-
// forkBlock is the block number at which the switchover to SystemContract occurs.
20+
// isUpgraded takes a block timestamp, and returns true once the engine should be upgraded to SystemContract.
2021
isUpgraded func(uint64) bool
2122

2223
// clique is the original Clique consensus engine.
@@ -28,14 +29,16 @@ type UpgradableEngine struct {
2829

2930
// NewUpgradableEngine constructs a new upgradable consensus middleware.
3031
func NewUpgradableEngine(isUpgraded func(uint64) bool, clique consensus.Engine, system consensus.Engine) *UpgradableEngine {
32+
log.Info("Initializing upgradable consensus engine")
33+
3134
return &UpgradableEngine{
3235
isUpgraded: isUpgraded,
3336
clique: clique,
3437
system: system,
3538
}
3639
}
3740

38-
// chooseEngine returns the appropriate consensus engine based on the header's number.
41+
// chooseEngine returns the appropriate consensus engine based on the header's timestamp.
3942
func (ue *UpgradableEngine) chooseEngine(header *types.Header) consensus.Engine {
4043
if ue.isUpgraded(header.Time) {
4144
return ue.system
@@ -60,12 +63,12 @@ func (ue *UpgradableEngine) VerifyHeader(chain consensus.ChainHeaderReader, head
6063
// headers can only be all system, all clique, or start with clique and then switch once to system.
6164
func (ue *UpgradableEngine) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
6265
abort := make(chan struct{})
63-
out := make(chan error)
66+
results := make(chan error, len(headers))
6467

6568
// If there are no headers, return a closed error channel.
6669
if len(headers) == 0 {
67-
close(out)
68-
return nil, out
70+
close(results)
71+
return nil, results
6972
}
7073

7174
// Choose engine for the first and last header.
@@ -97,42 +100,55 @@ func (ue *UpgradableEngine) VerifyHeaders(chain consensus.ChainHeaderReader, hea
97100
systemHeaders := headers[splitIndex:]
98101
systemSeals := seals[splitIndex:]
99102

100-
// Create a wait group to merge results.
101-
var wg sync.WaitGroup
102-
wg.Add(2)
103+
log.Info("Verifying EuclidV2 transition header chain")
103104

104-
// Launch concurrent verifications.
105+
// Do verification concurrently,
106+
// but make sure to run Clique first, then SystemContract,
107+
// so that the results are sent in the correct order.
105108
go func() {
106-
defer wg.Done()
107-
_, cliqueResults := ue.clique.VerifyHeaders(chain, cliqueHeaders, cliqueSeals)
108-
for err := range cliqueResults {
109+
defer close(results)
110+
111+
// Verify clique headers.
112+
log.Info("Start EuclidV2 transition verification in Clique section", "startBlockNumber", cliqueHeaders[0].Number, "endBlockNumber", cliqueHeaders[len(cliqueHeaders)-1].Number)
113+
abortClique, cliqueResults := ue.clique.VerifyHeaders(chain, cliqueHeaders, cliqueSeals)
114+
115+
// Note: cliqueResults is not closed so we cannot directly iterate over it
116+
for i := 0; i < len(cliqueHeaders); i++ {
109117
select {
110118
case <-abort:
119+
close(abortClique)
120+
log.Warn("Aborted EuclidV2 transition verification in Clique section")
111121
return
112-
case out <- err:
122+
case err := <-cliqueResults:
123+
results <- err
113124
}
114125
}
115-
}()
116126

117-
go func() {
118-
defer wg.Done()
119-
_, systemResults := ue.system.VerifyHeaders(chain, systemHeaders, systemSeals)
120-
for err := range systemResults {
127+
// Not sure why we need this here, but without this we get err="unknown ancestor"
128+
// at the 1st Euclid block. It seems that `VerifyHeaders` start processing the next
129+
// header before the previous one was written into `chain`.
130+
time.Sleep(2 * time.Second)
131+
132+
// Verify system contract headers.
133+
log.Info("Start EuclidV2 transition verification in SystemContract section", "startBlockNumber", systemHeaders[0].Number, "endBlockNumber", systemHeaders[len(systemHeaders)-1].Number)
134+
abortSystem, systemResults := ue.system.VerifyHeaders(chain, systemHeaders, systemSeals)
135+
136+
// Note: systemResults is not closed so we cannot directly iterate over it
137+
for i := 0; i < len(systemHeaders); i++ {
121138
select {
122139
case <-abort:
140+
close(abortSystem)
141+
log.Info("Aborted EuclidV2 transition verification in SystemContract section")
123142
return
124-
case out <- err:
143+
case err := <-systemResults:
144+
results <- err
125145
}
126146
}
127-
}()
128147

129-
// Close the out channel when both verifications are complete.
130-
go func() {
131-
wg.Wait()
132-
close(out)
148+
log.Info("Completed EuclidV2 transition verification")
133149
}()
134150

135-
return abort, out
151+
return abort, results
136152
}
137153

138154
// Prepare prepares a block header for sealing.
@@ -167,27 +183,22 @@ func (ue *UpgradableEngine) VerifyUncles(chain consensus.ChainReader, block *typ
167183

168184
// APIs returns any RPC APIs exposed by the consensus engine.
169185
func (ue *UpgradableEngine) APIs(chain consensus.ChainHeaderReader) []rpc.API {
170-
// Determine the current chain head.
171-
head := chain.CurrentHeader()
172-
if head == nil {
173-
// Fallback: return the clique APIs (or an empty slice) if we don't have a header.
174-
return ue.clique.APIs(chain)
175-
}
176-
177-
// Choose engine based on whether the chain head is before or after the fork block.
178-
if ue.isUpgraded(head.Time) {
179-
return ue.system.APIs(chain)
180-
}
181-
return ue.clique.APIs(chain)
186+
return append(ue.clique.APIs(chain), ue.system.APIs(chain)...)
182187
}
183188

184189
// Close terminates the consensus engine.
185190
func (ue *UpgradableEngine) Close() error {
186191
// Always close both engines.
187-
if err := ue.clique.Close(); err != nil {
188-
return err
192+
err1 := ue.clique.Close()
193+
err2 := ue.system.Close()
194+
195+
if err1 != nil || err2 != nil {
196+
log.Error("Error while closing upgradable engine", "cliqueError", err1, "systemContractError", err2)
197+
}
198+
if err1 != nil {
199+
return err1
189200
}
190-
return ue.system.Close()
201+
return err2
191202
}
192203

193204
// SealHash returns the hash of a block prior to it being sealed.

core/types/block.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,17 @@ type Header struct {
8686
// BaseFee was added by EIP-1559 and is ignored in legacy headers.
8787
BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"`
8888

89-
// BlockSignature was added by EuclidV2 to make Extra empty and is ignored during hashing
89+
// Note: The subsequent fields are rlp:"optional". When encoding a struct with optional
90+
// fields, the output RLP list contains all values up to the last non-zero optional field.
91+
92+
// BlockSignature was added by EuclidV2 to make Extra empty and is ignored during hashing.
93+
// This field is stored in db but not included in messages sent on the network wire protocol,
94+
// or in RPC responses. See also `PrepareForNetwork` and `PrepareFromNetwork`.
9095
BlockSignature []byte `json:"-" rlp:"optional"`
9196

92-
// IsEuclidV2 was added by EuclidV2 to make Extra empty and is ignored during hashing
97+
// IsEuclidV2 was added by EuclidV2 to make Extra empty and is ignored during hashing.
98+
// This field is stored in db but not included in messages sent on the network wire protocol,
99+
// or in RPC responses. See also `PrepareForNetwork` and `PrepareFromNetwork`.
93100
IsEuclidV2 bool `json:"-" rlp:"optional"`
94101

95102
// WithdrawalsHash was added by EIP-4895 and is ignored in legacy headers.
@@ -108,7 +115,9 @@ type Header struct {
108115
// Included for Ethereum compatibility in Scroll SDK
109116
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
110117

111-
//Hacky: used internally to mark the header as requested by the downloader at the deliver queue
118+
// Hacky: used internally to mark the header as requested by the downloader at the deliver queue.
119+
// Note: This is only used internally to mark a previously requested block, it is not included
120+
// in db, on the network wire protocol, or in RPC responses.
112121
Requested bool `json:"-" rlp:"-"`
113122
}
114123

eth/protocols/eth/handlers.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ func handleNewBlock(backend Backend, msg Decoder, peer *Peer) error {
270270

271271
hCopy := ann.Block.Header()
272272
if err := hCopy.NetworkCompatibleEuclidV2Fields(); err != nil {
273+
log.Warn("Disconnecting peer in handleNewBlock", "peer", peer.id, "reason", err)
273274
peer.Peer.Disconnect(p2p.DiscUselessPeer)
274275
return err
275276
}
@@ -309,6 +310,7 @@ func handleBlockHeaders66(backend Backend, msg Decoder, peer *Peer) error {
309310
for _, header := range res.BlockHeadersPacket {
310311
hCopy := types.CopyHeader(header)
311312
if err := hCopy.NetworkCompatibleEuclidV2Fields(); err != nil {
313+
log.Warn("Disconnecting peer in handleBlockHeaders66", "peer", peer.id, "reason", err)
312314
peer.Peer.Disconnect(p2p.DiscUselessPeer)
313315
return err
314316
}

internal/web3ext/web3ext.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -975,6 +975,14 @@ web3._extend({
975975
name: 'syncStatus',
976976
getter: 'scroll_syncStatus',
977977
}),
978+
new web3._extend.Property({
979+
name: 'authorizedSigner',
980+
getter: 'scroll_getAuthorizedSigner',
981+
}),
982+
new web3._extend.Property({
983+
name: 'localSigner',
984+
getter: 'scroll_getLocalSigner',
985+
}),
978986
]
979987
});
980988
`

0 commit comments

Comments
 (0)