les: update/recover stable checkpoint

This commit is contained in:
rjl493456442 2018-06-08 10:41:31 +08:00
parent 2b319f6b3e
commit 536eb1632d
7 changed files with 171 additions and 36 deletions

File diff suppressed because one or more lines are too long

View file

@ -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;
}

View file

@ -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))})
}

View file

@ -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) {

View file

@ -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)

View file

@ -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.

View file

@ -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