Skip to content

Commit cea9b36

Browse files
committed
feat: address system contract consensus issues and comments
1 parent 365b829 commit cea9b36

File tree

11 files changed

+90
-57
lines changed

11 files changed

+90
-57
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: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,21 @@ package wrapper
22

33
import (
44
"math/big"
5-
"sync"
65

76
"github.com/scroll-tech/go-ethereum/common"
87
"github.com/scroll-tech/go-ethereum/consensus"
98
"github.com/scroll-tech/go-ethereum/consensus/clique"
109
"github.com/scroll-tech/go-ethereum/consensus/system_contract"
1110
"github.com/scroll-tech/go-ethereum/core/state"
1211
"github.com/scroll-tech/go-ethereum/core/types"
12+
"github.com/scroll-tech/go-ethereum/log"
1313
"github.com/scroll-tech/go-ethereum/rpc"
1414
)
1515

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

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

2929
// NewUpgradableEngine constructs a new upgradable consensus middleware.
3030
func NewUpgradableEngine(isUpgraded func(uint64) bool, clique consensus.Engine, system consensus.Engine) *UpgradableEngine {
31+
log.Info("Initializing upgradable consensus engine")
32+
3133
return &UpgradableEngine{
3234
isUpgraded: isUpgraded,
3335
clique: clique,
3436
system: system,
3537
}
3638
}
3739

38-
// chooseEngine returns the appropriate consensus engine based on the header's number.
40+
// chooseEngine returns the appropriate consensus engine based on the header's timestamp.
3941
func (ue *UpgradableEngine) chooseEngine(header *types.Header) consensus.Engine {
4042
if ue.isUpgraded(header.Time) {
4143
return ue.system
@@ -97,38 +99,34 @@ func (ue *UpgradableEngine) VerifyHeaders(chain consensus.ChainHeaderReader, hea
9799
systemHeaders := headers[splitIndex:]
98100
systemSeals := seals[splitIndex:]
99101

100-
// Create a wait group to merge results.
101-
var wg sync.WaitGroup
102-
wg.Add(2)
102+
log.Warn("Verifying EuclidV2 transition header chain", "startBlockNumber", headers[0].Number, "endBlockNumber", headers[len(headers)-1].Number)
103103

104-
// Launch concurrent verifications.
104+
// Do verification concurrently,
105+
// but make sure to run Clique first, then SystemContract,
106+
// so that the results are sent in the correct order.
105107
go func() {
106-
defer wg.Done()
107-
_, cliqueResults := ue.clique.VerifyHeaders(chain, cliqueHeaders, cliqueSeals)
108+
// Verify clique headers.
109+
abortClique, cliqueResults := ue.clique.VerifyHeaders(chain, cliqueHeaders, cliqueSeals)
108110
for err := range cliqueResults {
109111
select {
110112
case <-abort:
113+
close(abortClique)
111114
return
112115
case out <- err:
113116
}
114117
}
115-
}()
116118

117-
go func() {
118-
defer wg.Done()
119-
_, systemResults := ue.system.VerifyHeaders(chain, systemHeaders, systemSeals)
119+
// Verify system contract headers.
120+
abortSystem, systemResults := ue.system.VerifyHeaders(chain, systemHeaders, systemSeals)
120121
for err := range systemResults {
121122
select {
122123
case <-abort:
124+
close(abortSystem)
123125
return
124126
case out <- err:
125127
}
126128
}
127-
}()
128129

129-
// Close the out channel when both verifications are complete.
130-
go func() {
131-
wg.Wait()
132130
close(out)
133131
}()
134132

@@ -167,18 +165,7 @@ func (ue *UpgradableEngine) VerifyUncles(chain consensus.ChainReader, block *typ
167165

168166
// APIs returns any RPC APIs exposed by the consensus engine.
169167
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)
168+
return append(ue.clique.APIs(chain), ue.system.APIs(chain)...)
182169
}
183170

184171
// Close terminates the consensus engine.

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
`

miner/scroll_worker.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,10 @@ func (w *worker) mainLoop() {
363363
if errors.As(err, &retryableCommitError) || errors.Is(err, system_contract.ErrUnauthorizedSigner) {
364364
log.Warn("failed to commit to a block, retrying", "err", err)
365365
if errors.Is(err, system_contract.ErrUnauthorizedSigner) {
366-
time.Sleep(5 * time.Second) // half the time it takes for the system contract consensus to read and update the address locally.
366+
// half the time it takes for the system contract consensus to read and update the address locally.
367+
// note: a blocking wait here might be problematic, since it will prevent progress on
368+
// `updateSnapshot` and other functionalities.
369+
time.Sleep(5 * time.Second)
367370
}
368371
if _, err = w.tryCommitNewWork(time.Now(), w.current.header.ParentHash, w.current.reorging, w.current.reorgReason); err != nil {
369372
continue

params/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -793,8 +793,12 @@ func (c *ChainConfig) String() string {
793793
switch {
794794
case c.Ethash != nil:
795795
engine = c.Ethash
796+
case c.Clique != nil && c.SystemContract != nil:
797+
engine = "upgradable (clique + system_contract)"
796798
case c.Clique != nil:
797799
engine = c.Clique
800+
case c.SystemContract != nil:
801+
engine = c.SystemContract
798802
default:
799803
engine = "unknown"
800804
}

params/version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import (
2424
const (
2525
VersionMajor = 5 // Major version component of the current release
2626
VersionMinor = 8 // Minor version component of the current release
27-
VersionPatch = 14 // Patch version component of the current release
27+
VersionPatch = 15 // Patch version component of the current release
2828
VersionMeta = "mainnet" // Version metadata to append to the version string
2929
)
3030

0 commit comments

Comments
 (0)