mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
les: update/recover stable checkpoint
This commit is contained in:
parent
2b319f6b3e
commit
536eb1632d
7 changed files with 171 additions and 36 deletions
File diff suppressed because one or more lines are too long
|
|
@ -209,6 +209,6 @@ contract Registrar {
|
|||
// The number of confirmations needed before a checkpoint can be registered.
|
||||
// We have to make sure the checkpoint registered will not be invalid due to
|
||||
// chain reorg.
|
||||
uint constant confirmations = 500;
|
||||
uint constant confirmations = 256;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,19 +32,14 @@ import (
|
|||
var (
|
||||
// registrar contract address for mainnet and testnet.
|
||||
RegistrarAddr = map[common.Hash]common.Address{
|
||||
params.MainnetGenesisHash: common.HexToAddress(""),
|
||||
params.TestnetGenesisHash: common.HexToAddress(""),
|
||||
// params.MainnetGenesisHash: common.HexToAddress(""),
|
||||
// params.TestnetGenesisHash: common.HexToAddress(""),
|
||||
params.RinkebyGenesisHash: common.HexToAddress("0xe3f2686a5d0c56a2d853c19c46b173a755263be8"),
|
||||
}
|
||||
)
|
||||
|
||||
var errEventNotFound = errors.New("contract event not found")
|
||||
|
||||
const (
|
||||
sectionSize = 32768 // The frequency for creating a checkpoint
|
||||
checkpointConfirmation = 500 // The number of confirmations needed before a checkpoint can be accepted
|
||||
)
|
||||
|
||||
type Registrar struct {
|
||||
contract *contract.Contract
|
||||
}
|
||||
|
|
@ -67,15 +62,14 @@ func (registrar *Registrar) WatchNewCheckpointEvent(sink chan<- *contract.Contra
|
|||
}
|
||||
|
||||
// FilterNewCheckpointEvent filters out NewCheckpointEvent for specific section number.
|
||||
func (registrar *Registrar) FilterNewCheckpointEvent(head uint64, section uint64) (*contract.ContractNewCheckpointEventIterator, error) {
|
||||
start := (section + 1) * sectionSize
|
||||
end := head - checkpointConfirmation
|
||||
if end < start {
|
||||
func (registrar *Registrar) FilterNewCheckpointEvent(head, section, sectionSize, processConfirm uint64) (*contract.ContractNewCheckpointEventIterator, error) {
|
||||
start := (section+1)*sectionSize + processConfirm
|
||||
if head < start {
|
||||
return nil, errEventNotFound
|
||||
}
|
||||
opt := &bind.FilterOpts{
|
||||
Start: start,
|
||||
End: &end,
|
||||
End: &head,
|
||||
}
|
||||
return registrar.contract.FilterNewCheckpointEvent(opt, []*big.Int{big.NewInt(int64(section))})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -213,9 +213,27 @@ func TestCheckpointRegister(t *testing.T) {
|
|||
return nil
|
||||
}, "register unstable checkpoint")
|
||||
|
||||
contractBackend.ShiftBlocks(light.CheckpointFrequency + light.CheckpointProcessConfirmations)
|
||||
|
||||
// Register by unauthorized user
|
||||
validateOperation(t, c, contractBackend, func() {
|
||||
user2, _ := crypto.GenerateKey()
|
||||
unauthorized := bind.NewKeyedTransactor(user2)
|
||||
c.SetCheckpoint(unauthorized, big.NewInt(int64(trustedCheckpoint.SectionIdx)), trustedCheckpoint.SectionHead,
|
||||
trustedCheckpoint.ChtRoot, trustedCheckpoint.BloomTrieRoot)
|
||||
}, func(events <-chan *contract.ContractNewCheckpointEvent, events2 <-chan *contract.ContractAddAdminEvent, events3 <-chan *contract.ContractRemoveAdminEvent) error {
|
||||
hash, err := c.GetCheckpoint(nil, big.NewInt(int64(trustedCheckpoint.SectionIdx)))
|
||||
if err != nil {
|
||||
return errors.New("get checkpoint failed")
|
||||
}
|
||||
if hash != emptyHash {
|
||||
return errors.New("unstable checkpoint should be banned")
|
||||
}
|
||||
return nil
|
||||
}, "register by unauthorized user")
|
||||
|
||||
// Register a stable checkpoint
|
||||
validateOperation(t, c, contractBackend, func() {
|
||||
contractBackend.ShiftBlocks(sectionSize + checkpointConfirmation)
|
||||
c.SetCheckpoint(transactOpts, big.NewInt(int64(trustedCheckpoint.SectionIdx)), trustedCheckpoint.SectionHead,
|
||||
trustedCheckpoint.ChtRoot, trustedCheckpoint.BloomTrieRoot)
|
||||
}, func(events <-chan *contract.ContractNewCheckpointEvent, events2 <-chan *contract.ContractAddAdminEvent, events3 <-chan *contract.ContractRemoveAdminEvent) error {
|
||||
|
|
@ -223,7 +241,7 @@ func TestCheckpointRegister(t *testing.T) {
|
|||
if err != nil {
|
||||
return errors.New("get checkpoint failed")
|
||||
}
|
||||
if common.Hash(hash).Hex() != crypto.Keccak256Hash(trustedCheckpoint.SectionHead.Bytes(), trustedCheckpoint.ChtRoot.Bytes(), trustedCheckpoint.BloomTrieRoot.Bytes()).Hex() {
|
||||
if !trustedCheckpoint.HashEqual(common.Hash(hash)) {
|
||||
return errors.New("register stable checkpoint failed")
|
||||
}
|
||||
if !validateEvents(1, events) {
|
||||
|
|
@ -242,7 +260,7 @@ func TestCheckpointRegister(t *testing.T) {
|
|||
if err != nil {
|
||||
return errors.New("get checkpoint failed")
|
||||
}
|
||||
if common.Hash(hash).Hex() != crypto.Keccak256Hash(trustedCheckpoint.SectionHead.Bytes(), trustedCheckpoint.ChtRoot.Bytes(), trustedCheckpoint.BloomTrieRoot.Bytes()).Hex() {
|
||||
if !trustedCheckpoint.HashEqual(common.Hash(hash)) {
|
||||
return errors.New("register stable checkpoint failed")
|
||||
}
|
||||
if !validateEvents(1, events) {
|
||||
|
|
|
|||
126
les/server.go
126
les/server.go
|
|
@ -19,6 +19,7 @@ package les
|
|||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
|
@ -28,6 +29,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/les/flowcontrol"
|
||||
"github.com/ethereum/go-ethereum/light"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
|
|
@ -36,8 +38,13 @@ import (
|
|||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// chainHeadChanSize is the size of channel listening to ChainHeadEvent.
|
||||
const SubscribeChainHeadEvent = 10
|
||||
|
||||
type LesServer struct {
|
||||
config *eth.Config
|
||||
backend *eth.EthAPIBackend
|
||||
chaindb ethdb.Database
|
||||
protocolManager *ProtocolManager
|
||||
fcManager *flowcontrol.ClientManager // nil if our node is client only
|
||||
fcCostStats *requestCostStats
|
||||
|
|
@ -69,6 +76,8 @@ func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
|
|||
|
||||
srv := &LesServer{
|
||||
config: config,
|
||||
backend: eth.APIBackend,
|
||||
chaindb: eth.ChainDb(),
|
||||
protocolManager: pm,
|
||||
quitSync: quitSync,
|
||||
lesTopics: lesTopics,
|
||||
|
|
@ -85,14 +94,14 @@ func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
|
|||
// convert last LES/2 section index back to LES/1 index for chtIndexer.SectionHead
|
||||
chtLastSectionV1 := (chtLastSection+1)*(light.CHTFrequencyClient/light.CHTFrequencyServer) - 1
|
||||
chtSectionHead := srv.chtIndexer.SectionHead(chtLastSectionV1)
|
||||
chtRoot := light.GetChtV2Root(pm.chainDb, chtLastSection, chtSectionHead)
|
||||
chtRoot := light.GetChtV2Root(srv.chaindb, chtLastSection, chtSectionHead)
|
||||
logger.Info("Loaded CHT", "section", chtLastSection, "head", chtSectionHead, "root", chtRoot)
|
||||
}
|
||||
bloomTrieSectionCount, _, _ := srv.bloomTrieIndexer.Sections()
|
||||
if bloomTrieSectionCount != 0 {
|
||||
bloomTrieLastSection := bloomTrieSectionCount - 1
|
||||
bloomTrieSectionHead := srv.bloomTrieIndexer.SectionHead(bloomTrieLastSection)
|
||||
bloomTrieRoot := light.GetBloomTrieRoot(pm.chainDb, bloomTrieLastSection, bloomTrieSectionHead)
|
||||
bloomTrieRoot := light.GetBloomTrieRoot(srv.chaindb, bloomTrieLastSection, bloomTrieSectionHead)
|
||||
logger.Info("Loaded bloom trie", "section", bloomTrieLastSection, "head", bloomTrieSectionHead, "root", bloomTrieRoot)
|
||||
}
|
||||
|
||||
|
|
@ -137,6 +146,7 @@ func (s *LesServer) Start(srvr *p2p.Server) {
|
|||
s.privateKey = srvr.PrivateKey
|
||||
s.protocolManager.blockLoop()
|
||||
if s.registrar != nil {
|
||||
s.stableCheckpoint = s.recoverCheckpoint()
|
||||
go s.checkpointLoop()
|
||||
}
|
||||
}
|
||||
|
|
@ -204,29 +214,66 @@ func (s *LesServer) getCheckpoint(index uint64) (common.Hash, common.Hash, commo
|
|||
return sectionHead, chtRoot, bloomTrieRoot
|
||||
}
|
||||
|
||||
// checkpointLoop starts a standalone goroutine to watch new checkpoint event and updates local's stable checkpoint.
|
||||
// checkpointLoop starts a standalone goroutine to watch new checkpoint events and updates local's stable checkpoint.
|
||||
func (s *LesServer) checkpointLoop() (err error) {
|
||||
sink := make(chan *contract.ContractNewCheckpointEvent)
|
||||
sub, err := s.registrar.WatchNewCheckpointEvent(sink)
|
||||
var (
|
||||
eventCh = make(chan *contract.ContractNewCheckpointEvent)
|
||||
headCh = make(chan core.ChainHeadEvent, SubscribeChainHeadEvent)
|
||||
announcement = make(map[uint64]common.Hash)
|
||||
)
|
||||
eventSub, err := s.registrar.WatchNewCheckpointEvent(eventCh)
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
return err
|
||||
}
|
||||
headSub := s.backend.SubscribeChainHeadEvent(headCh)
|
||||
if headSub == nil {
|
||||
eventSub.Unsubscribe()
|
||||
return errors.New("subscribe head event failed")
|
||||
}
|
||||
defer func() {
|
||||
sub.Unsubscribe()
|
||||
eventSub.Unsubscribe()
|
||||
headSub.Unsubscribe()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case event := <-sink:
|
||||
// Note several duplicate events can be received because of latest checkpoint modification is allowed.
|
||||
// Always update local checkpoint when the section index is not less than the local one.
|
||||
// todo(rjl493456442) update local checkpoint
|
||||
if event.Index.Uint64() >= s.stableCheckpoint.SectionIdx {
|
||||
log.Info("update checkpoint", "section", event.Index, "hash", common.Hash(event.CheckpointHash).Hex(),
|
||||
"grantor", event.Grantor.Hex())
|
||||
case event := <-eventCh:
|
||||
if event == nil {
|
||||
// This should never happen.
|
||||
log.Info("Ignore empty checkpoint event")
|
||||
continue
|
||||
}
|
||||
// Note several duplicate events may be received because of chain reorg and the modification of the latest checkpoint.
|
||||
if s.stableCheckpoint == nil || event.Index.Uint64() >= s.stableCheckpoint.SectionIdx {
|
||||
log.Info("Receive new checkpoint event", "section", event.Index, "hash", common.Hash(event.CheckpointHash).Hex(),
|
||||
"grantor", event.Grantor.Hex())
|
||||
announcement[event.Index.Uint64()] = common.Hash(event.CheckpointHash)
|
||||
}
|
||||
case head := <-headCh:
|
||||
number := head.Block.NumberU64()
|
||||
if number < light.CheckpointConfirmations+light.CheckpointFrequency {
|
||||
continue
|
||||
}
|
||||
idx := (number-light.CheckpointConfirmations)/light.CheckpointFrequency - 1
|
||||
if s.stableCheckpoint == nil || idx > s.stableCheckpoint.SectionIdx {
|
||||
hash, ok := announcement[idx]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
sectionHead := s.bloomTrieIndexer.SectionHead(idx)
|
||||
checkpoint := &light.TrustedCheckpoint{
|
||||
SectionIdx: idx,
|
||||
SectionHead: sectionHead,
|
||||
ChtRoot: light.GetChtV2Root(s.chaindb, idx, sectionHead),
|
||||
BloomTrieRoot: light.GetBloomTrieRoot(s.chaindb, idx, sectionHead),
|
||||
}
|
||||
if checkpoint.HashEqual(common.Hash(hash)) {
|
||||
light.WriteTrustedCheckpoint(s.chaindb, checkpoint)
|
||||
s.stableCheckpoint = checkpoint
|
||||
log.Info("Update stable checkpoint", "section", checkpoint.SectionIdx)
|
||||
delete(announcement, idx)
|
||||
}
|
||||
}
|
||||
|
||||
case <-s.quitSync:
|
||||
// Les server is closed.
|
||||
return
|
||||
|
|
@ -234,6 +281,53 @@ func (s *LesServer) checkpointLoop() (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
// recoveryCheckpoint filters checkpoint announcement events and recovers stable checkpoint.
|
||||
func (s *LesServer) recoverCheckpoint() *light.TrustedCheckpoint {
|
||||
var (
|
||||
sectionCnt, _, _ = s.bloomTrieIndexer.Sections()
|
||||
stable = light.ReadTrustedCheckpoint(s.chaindb)
|
||||
unstableIdx = sectionCnt - 1
|
||||
headHash = rawdb.ReadHeadHeaderHash(s.chaindb)
|
||||
headNumber = rawdb.ReadHeaderNumber(s.chaindb, headHash)
|
||||
)
|
||||
if headNumber == nil {
|
||||
return nil
|
||||
}
|
||||
for stable == nil || stable.SectionIdx < unstableIdx {
|
||||
if (unstableIdx+1)*light.CheckpointFrequency+light.CheckpointConfirmations <= *headNumber {
|
||||
iter, err := s.registrar.FilterNewCheckpointEvent(*headNumber, unstableIdx, light.CheckpointFrequency, light.CheckpointProcessConfirmations)
|
||||
if err == nil {
|
||||
for iter.Next() {
|
||||
sectionHead := s.bloomTrieIndexer.SectionHead(unstableIdx)
|
||||
checkpoint := &light.TrustedCheckpoint{
|
||||
SectionIdx: unstableIdx,
|
||||
SectionHead: sectionHead,
|
||||
ChtRoot: light.GetChtV2Root(s.chaindb, unstableIdx, sectionHead),
|
||||
BloomTrieRoot: light.GetBloomTrieRoot(s.chaindb, unstableIdx, sectionHead),
|
||||
}
|
||||
if checkpoint.HashEqual(common.Hash(iter.Event.CheckpointHash)) {
|
||||
light.WriteTrustedCheckpoint(s.chaindb, checkpoint)
|
||||
iter.Close()
|
||||
log.Info("Recover checkpoint", "index", checkpoint.SectionIdx)
|
||||
return checkpoint
|
||||
}
|
||||
}
|
||||
iter.Close()
|
||||
}
|
||||
}
|
||||
if unstableIdx == 0 {
|
||||
break
|
||||
}
|
||||
unstableIdx -= 1
|
||||
}
|
||||
if stable == nil {
|
||||
log.Info("No stable checkpoint")
|
||||
} else {
|
||||
log.Info("Recover checkpoint", "index", stable.SectionIdx)
|
||||
}
|
||||
return stable
|
||||
}
|
||||
|
||||
func (pm *ProtocolManager) blockLoop() {
|
||||
pm.wg.Add(1)
|
||||
headCh := make(chan core.ChainHeadEvent, 10)
|
||||
|
|
|
|||
|
|
@ -20,17 +20,27 @@ import (
|
|||
"io"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
var (
|
||||
// checkpointKey tracks the latest stable checkpoint.
|
||||
checkpointKey = []byte("Checkpoint")
|
||||
const (
|
||||
// CheckpointFrequency is the block frequency for creating checkpoint
|
||||
CheckpointFrequency = 32768
|
||||
|
||||
// CheckpointProcessConfirmations is the number before a checkpoint is generated
|
||||
CheckpointProcessConfirmations = 256
|
||||
|
||||
// CheckpointConfirmations is the number of confirmations before a checkpoint is stable
|
||||
CheckpointConfirmations = 8192
|
||||
)
|
||||
|
||||
// checkpointKey tracks the latest stable checkpoint.
|
||||
var checkpointKey = []byte("Checkpoint")
|
||||
|
||||
// TrustedCheckpoint represents a set of post-processed trie roots (CHT and BloomTrie) associated with
|
||||
// the appropriate section index and head hash.
|
||||
//
|
||||
|
|
@ -68,6 +78,15 @@ func (c *TrustedCheckpoint) DecodeRLP(s *rlp.Stream) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// HashEqual returns an indicator comparing the itself hash with given one.
|
||||
// A nil argument is equivalent to an empty slice.
|
||||
func (c *TrustedCheckpoint) HashEqual(hash common.Hash) bool {
|
||||
if c.SectionHead == (common.Hash{}) && c.ChtRoot == (common.Hash{}) && c.BloomTrieRoot == (common.Hash{}) {
|
||||
return hash == common.Hash{}
|
||||
}
|
||||
return crypto.Keccak256Hash(c.SectionHead.Bytes(), c.ChtRoot.Bytes(), c.BloomTrieRoot.Bytes()) == hash
|
||||
}
|
||||
|
||||
var (
|
||||
// Hardcode checkpoint for mainnet and testnet(ropsten). Will be deleted eventually once checkpoint contract
|
||||
// works.
|
||||
|
|
|
|||
|
|
@ -39,6 +39,16 @@ func TestRWCheckpoint(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestHashEqual(t *testing.T) {
|
||||
if !testCheckpoint.HashEqual(common.HexToHash("0x6142a271d44a56107cd9de0be0a04211841593906b310f8c4d33be56b6e78959")) {
|
||||
t.Error("checkpoint should hash equal to given one")
|
||||
}
|
||||
emptyCheckpoint := &TrustedCheckpoint{}
|
||||
if !emptyCheckpoint.HashEqual(common.Hash{}) {
|
||||
t.Error("empty checkpoint should equal to empty hash")
|
||||
}
|
||||
}
|
||||
|
||||
func assertCheckpointEqual(ckp1, ckp2 *TrustedCheckpoint) bool {
|
||||
return ckp1.SectionIdx == ckp2.SectionIdx && ckp1.SectionHead == ckp2.SectionHead && ckp1.ChtRoot == ckp2.ChtRoot &&
|
||||
ckp1.BloomTrieRoot == ckp2.BloomTrieRoot
|
||||
|
|
|
|||
Loading…
Reference in a new issue