diff options
author | Jimmy Hu <jimmy.hu@dexon.org> | 2019-02-27 10:41:01 +0800 |
---|---|---|
committer | Wei-Ning Huang <w@byzantine-lab.io> | 2019-06-12 17:27:22 +0800 |
commit | 2b2396b6bce0f21b515ac2d38556f6dca08b1770 (patch) | |
tree | 60d6c93689b54534ecc88bd1491bd82fa372b541 /vendor | |
parent | edb1273cb08d56df41b30b1f2f2e113f9b4296e4 (diff) | |
download | go-tangerine-2b2396b6bce0f21b515ac2d38556f6dca08b1770.tar go-tangerine-2b2396b6bce0f21b515ac2d38556f6dca08b1770.tar.gz go-tangerine-2b2396b6bce0f21b515ac2d38556f6dca08b1770.tar.bz2 go-tangerine-2b2396b6bce0f21b515ac2d38556f6dca08b1770.tar.lz go-tangerine-2b2396b6bce0f21b515ac2d38556f6dca08b1770.tar.xz go-tangerine-2b2396b6bce0f21b515ac2d38556f6dca08b1770.tar.zst go-tangerine-2b2396b6bce0f21b515ac2d38556f6dca08b1770.zip |
core: sync to latest core (#214)
* vendor: sync to latest core
* fix for single chain
Diffstat (limited to 'vendor')
20 files changed, 430 insertions, 510 deletions
diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/common/event.go b/vendor/github.com/dexon-foundation/dexon-consensus/common/event.go index 6c6bf49d4..4e4e23bf3 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/common/event.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/common/event.go @@ -20,26 +20,25 @@ package common import ( "container/heap" "sync" - "time" ) -type timeEventFn func(time.Time) +type heightEventFn func(uint64) -type timeEvent struct { - t time.Time - fn timeEventFn +type heightEvent struct { + h uint64 + fn heightEventFn } -// timeEvents implements a Min-Heap structure. -type timeEvents []timeEvent +// heightEvents implements a Min-Heap structure. +type heightEvents []heightEvent -func (h timeEvents) Len() int { return len(h) } -func (h timeEvents) Less(i, j int) bool { return h[i].t.Before(h[j].t) } -func (h timeEvents) Swap(i, j int) { h[i], h[j] = h[j], h[i] } -func (h *timeEvents) Push(x interface{}) { - *h = append(*h, x.(timeEvent)) +func (h heightEvents) Len() int { return len(h) } +func (h heightEvents) Less(i, j int) bool { return h[i].h < h[j].h } +func (h heightEvents) Swap(i, j int) { h[i], h[j] = h[j], h[i] } +func (h *heightEvents) Push(x interface{}) { + *h = append(*h, x.(heightEvent)) } -func (h *timeEvents) Pop() interface{} { +func (h *heightEvents) Pop() interface{} { old := *h n := len(old) x := old[n-1] @@ -49,54 +48,54 @@ func (h *timeEvents) Pop() interface{} { // Event implements the Observer pattern. type Event struct { - timeEvents timeEvents - timeEventsLock sync.Mutex + heightEvents heightEvents + heightEventsLock sync.Mutex } // NewEvent creates a new event instance. func NewEvent() *Event { - te := timeEvents{} - heap.Init(&te) + he := heightEvents{} + heap.Init(&he) return &Event{ - timeEvents: te, + heightEvents: he, } } -// RegisterTime to get notified on and after specific time. -func (e *Event) RegisterTime(t time.Time, fn timeEventFn) { - e.timeEventsLock.Lock() - defer e.timeEventsLock.Unlock() - heap.Push(&e.timeEvents, timeEvent{ - t: t, +// RegisterHeight to get notified on a specific height. +func (e *Event) RegisterHeight(h uint64, fn heightEventFn) { + e.heightEventsLock.Lock() + defer e.heightEventsLock.Unlock() + heap.Push(&e.heightEvents, heightEvent{ + h: h, fn: fn, }) } -// NotifyTime and trigger function callback. -func (e *Event) NotifyTime(t time.Time) { - fns := func() (fns []timeEventFn) { - e.timeEventsLock.Lock() - defer e.timeEventsLock.Unlock() - if len(e.timeEvents) == 0 { +// NotifyHeight and trigger function callback. +func (e *Event) NotifyHeight(h uint64) { + fns := func() (fns []heightEventFn) { + e.heightEventsLock.Lock() + defer e.heightEventsLock.Unlock() + if len(e.heightEvents) == 0 { return } - for !t.Before(e.timeEvents[0].t) { - te := heap.Pop(&e.timeEvents).(timeEvent) - fns = append(fns, te.fn) - if len(e.timeEvents) == 0 { + for h >= e.heightEvents[0].h { + he := heap.Pop(&e.heightEvents).(heightEvent) + fns = append(fns, he.fn) + if len(e.heightEvents) == 0 { return } } return }() for _, fn := range fns { - fn(t) + fn(h) } } // Reset clears all pending event func (e *Event) Reset() { - e.timeEventsLock.Lock() - defer e.timeEventsLock.Unlock() - e.timeEvents = timeEvents{} + e.heightEventsLock.Lock() + defer e.heightEventsLock.Unlock() + e.heightEvents = heightEvents{} } diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement-mgr.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement-mgr.go index 5f5b9ae5f..88cc432ff 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement-mgr.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement-mgr.go @@ -65,13 +65,29 @@ func genValidLeader( } type agreementMgrConfig struct { - beginTime time.Time - roundInterval time.Duration + roundBasedConfig + notarySetSize uint32 lambdaBA time.Duration crs common.Hash } +func (c *agreementMgrConfig) from( + round uint64, config *types.Config, crs common.Hash) { + c.notarySetSize = config.NotarySetSize + c.lambdaBA = config.LambdaBA + c.crs = crs + c.setupRoundBasedFields(round, config) +} + +func newAgreementMgrConfig(prev agreementMgrConfig, config *types.Config, + crs common.Hash) (c agreementMgrConfig) { + c = agreementMgrConfig{} + c.from(prev.roundID+1, config, crs) + c.setRoundBeginHeight(prev.roundEndHeight) + return +} + type baRoundSetting struct { notarySet map[types.NodeID]struct{} agr *agreement @@ -92,9 +108,8 @@ type agreementMgr struct { signer *utils.Signer bcModule *blockChain ctx context.Context - lastEndTime time.Time initRound uint64 - configs []*agreementMgrConfig + configs []agreementMgrConfig baModule *agreement processedBAResult map[types.Position]struct{} voteFilter *utils.VoteFilter @@ -104,8 +119,8 @@ type agreementMgr struct { } func newAgreementMgr(con *Consensus, initRound uint64, - initRoundBeginTime time.Time) *agreementMgr { - return &agreementMgr{ + initConfig agreementMgrConfig) (mgr *agreementMgr, err error) { + mgr = &agreementMgr{ con: con, ID: con.ID, app: con.app, @@ -117,9 +132,33 @@ func newAgreementMgr(con *Consensus, initRound uint64, bcModule: con.bcModule, ctx: con.ctx, initRound: initRound, - lastEndTime: initRoundBeginTime, processedBAResult: make(map[types.Position]struct{}, maxResultCache), + configs: []agreementMgrConfig{initConfig}, + voteFilter: utils.NewVoteFilter(), } + recv := &consensusBAReceiver{ + consensus: con, + restartNotary: make(chan types.Position, 1), + roundValue: &atomic.Value{}, + } + recv.roundValue.Store(uint64(0)) + agr := newAgreement( + mgr.ID, + recv, + newLeaderSelector(genValidLeader(mgr), mgr.logger), + mgr.signer, + mgr.logger) + // Hacky way to initialize first notarySet. + nodes, err := mgr.cache.GetNodeSet(initRound) + if err != nil { + return + } + agr.notarySet = nodes.GetSubSet( + int(initConfig.notarySetSize), types.NewNotarySetTarget(initConfig.crs)) + // Hacky way to make agreement module self contained. + recv.agreementModule = agr + mgr.baModule = agr + return } func (mgr *agreementMgr) run() { @@ -136,7 +175,7 @@ func (mgr *agreementMgr) run() { }() } -func (mgr *agreementMgr) getConfig(round uint64) *agreementMgrConfig { +func (mgr *agreementMgr) config(round uint64) *agreementMgrConfig { mgr.lock.RLock() defer mgr.lock.RUnlock() if round < mgr.initRound { @@ -146,7 +185,7 @@ func (mgr *agreementMgr) getConfig(round uint64) *agreementMgrConfig { if roundIndex >= uint64(len(mgr.configs)) { return nil } - return mgr.configs[roundIndex] + return &mgr.configs[roundIndex] } func (mgr *agreementMgr) appendConfig( @@ -156,52 +195,12 @@ func (mgr *agreementMgr) appendConfig( if round != uint64(len(mgr.configs))+mgr.initRound { return ErrRoundNotIncreasing } - newConfig := &agreementMgrConfig{ - beginTime: mgr.lastEndTime, - roundInterval: config.RoundInterval, - notarySetSize: config.NotarySetSize, - lambdaBA: config.LambdaBA, - crs: crs, - } - mgr.configs = append(mgr.configs, newConfig) - mgr.lastEndTime = mgr.lastEndTime.Add(config.RoundInterval) - // Prepare modules. - if mgr.baModule != nil { - return nil - } - recv := &consensusBAReceiver{ - consensus: mgr.con, - restartNotary: make(chan types.Position, 1), - roundValue: &atomic.Value{}, - } - recv.roundValue.Store(uint64(0)) - agrModule := newAgreement( - mgr.con.ID, - recv, - newLeaderSelector(genValidLeader(mgr), mgr.logger), - mgr.signer, - mgr.logger) - // Hacky way to initialize first notarySet. - nodes, err := mgr.cache.GetNodeSet(round) - if err != nil { - return err - } - agrModule.notarySet = nodes.GetSubSet( - int(config.NotarySetSize), types.NewNotarySetTarget(crs)) - // Hacky way to make agreement module self contained. - recv.agreementModule = agrModule - mgr.baModule = agrModule - mgr.voteFilter = utils.NewVoteFilter() + mgr.configs = append(mgr.configs, newAgreementMgrConfig( + mgr.configs[len(mgr.configs)-1], config, crs)) return nil } func (mgr *agreementMgr) processVote(v *types.Vote) (err error) { - if v.Position.ChainID > 0 { - mgr.logger.Error("Process vote for unknown chain to BA", - "position", v.Position, - "initRound", mgr.initRound) - return utils.ErrInvalidChainID - } if mgr.voteFilter.Filter(v) { return nil } @@ -212,13 +211,6 @@ func (mgr *agreementMgr) processVote(v *types.Vote) (err error) { } func (mgr *agreementMgr) processBlock(b *types.Block) error { - if b.Position.ChainID > 0 { - mgr.logger.Error("Process block for unknown chain to BA", - "position", b.Position, - "baRound", len(mgr.configs), - "initRound", mgr.initRound) - return utils.ErrInvalidChainID - } return mgr.baModule.processBlock(b) } @@ -247,13 +239,6 @@ func (mgr *agreementMgr) untouchAgreementResult( func (mgr *agreementMgr) processAgreementResult( result *types.AgreementResult) error { - if result.Position.ChainID > 0 { - mgr.logger.Error("Process unknown result for unknown chain to BA", - "position", result.Position, - "baRound", len(mgr.configs), - "initRound", mgr.initRound) - return utils.ErrInvalidChainID - } aID := mgr.baModule.agreementID() if isStop(aID) { return nil @@ -310,13 +295,12 @@ func (mgr *agreementMgr) runBA(initRound uint64) { var ( currentRound uint64 nextRound = initRound + curConfig = mgr.config(initRound) setting = baRoundSetting{ agr: mgr.baModule, recv: mgr.baModule.data.recv.(*consensusBAReceiver), } - roundBeginTime time.Time - roundEndTime time.Time - tickDuration time.Duration + tickDuration time.Duration ) // Check if this routine needs to awake in this round and prepare essential @@ -327,24 +311,20 @@ func (mgr *agreementMgr) runBA(initRound uint64) { nextRound++ }() // Wait until the configuartion for next round is ready. - var config *agreementMgrConfig for { - if config = mgr.getConfig(nextRound); config != nil { + if curConfig = mgr.config(nextRound); curConfig != nil { break } else { mgr.logger.Debug("round is not ready", "round", nextRound) time.Sleep(1 * time.Second) } } - // Set next checkpoint. - roundBeginTime = config.beginTime - roundEndTime = config.beginTime.Add(config.roundInterval) // Check if this node in notary set of this chain in this round. - notarySet, err := mgr.cache.GetNotarySet(nextRound, 0) + notarySet, err := mgr.cache.GetNotarySet(nextRound) if err != nil { panic(err) } - setting.crs = config.crs + setting.crs = curConfig.crs setting.notarySet = notarySet _, isNotary = setting.notarySet[mgr.ID] if isNotary { @@ -357,12 +337,12 @@ func (mgr *agreementMgr) runBA(initRound uint64) { "round", nextRound) } // Setup ticker - if tickDuration != config.lambdaBA { + if tickDuration != curConfig.lambdaBA { if setting.ticker != nil { setting.ticker.Stop() } setting.ticker = newTicker(mgr.gov, nextRound, TickerBA) - tickDuration = config.lambdaBA + tickDuration = curConfig.lambdaBA } return } @@ -373,29 +353,13 @@ Loop: break Loop default: } - now := time.Now().UTC() setting.recv.isNotary = checkRound() - // Sleep until round begin. Here a biased round begin time would be - // used instead of the one in config. The reason it to disperse the load - // of fullnodes to verify confirmed blocks from each chain. - if now.Before(pickBiasedTime(roundBeginTime, 4*tickDuration)) { - select { - case <-mgr.ctx.Done(): - break Loop - case <-time.After(roundBeginTime.Sub(now)): - } - // Clean the tick channel after awake: the tick would be queued in - // channel, thus the first few ticks would not tick on expected - // interval. - <-setting.ticker.Tick() - <-setting.ticker.Tick() - } // Run BA for this round. setting.recv.roundValue.Store(currentRound) - setting.recv.changeNotaryTime = roundEndTime + setting.recv.changeNotaryHeight = curConfig.roundEndHeight setting.recv.restartNotary <- types.Position{ - Round: setting.recv.round(), - ChainID: math.MaxUint32, + Round: setting.recv.round(), + Height: math.MaxUint64, } mgr.voteFilter = utils.NewVoteFilter() if err := mgr.baRoutineForOneRound(&setting); err != nil { @@ -450,7 +414,7 @@ func (mgr *agreementMgr) baRoutineForOneRound( if isStop(oldPos) && nextHeight == 0 { break } - if isStop(restartPos) && nextHeight == 0 { + if isStop(restartPos) { break } if nextHeight > restartPos.Height { diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement.go index 579cea8c3..43fddd0a0 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/agreement.go @@ -254,12 +254,12 @@ func (a *agreement) restart( func (a *agreement) stop() { a.restart(make(map[types.NodeID]struct{}), types.Position{ - ChainID: math.MaxUint32, + Height: math.MaxUint64, }, types.NodeID{}, common.Hash{}) } func isStop(aID types.Position) bool { - return aID.ChainID == math.MaxUint32 + return aID.Height == math.MaxUint64 } // clocks returns how many time this state is required. diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/blockchain.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/blockchain.go index d1aa644a5..03c8561c5 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/blockchain.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/blockchain.go @@ -110,7 +110,7 @@ func newBlockChainConfig(prev blockChainConfig, config *types.Config) ( c blockChainConfig) { c = blockChainConfig{} c.fromConfig(prev.roundID+1, config) - c.setRoundBeginTime(prev.roundEndTime) + c.setRoundBeginHeight(prev.roundEndHeight) return } @@ -131,9 +131,10 @@ type blockChain struct { configs []blockChainConfig pendingBlocks pendingBlockRecords confirmedBlocks types.BlocksByPosition + dMoment time.Time } -func newBlockChain(nID types.NodeID, initBlock *types.Block, +func newBlockChain(nID types.NodeID, dMoment time.Time, initBlock *types.Block, initConfig blockChainConfig, app Application, vGetter tsigVerifierGetter, signer *utils.Signer, logger common.Logger) *blockChain { if initBlock != nil { @@ -156,6 +157,7 @@ func newBlockChain(nID types.NodeID, initBlock *types.Block, app: app, logger: logger, configs: []blockChainConfig{initConfig}, + dMoment: dMoment, pendingRandomnesses: make( map[types.Position]*types.BlockRandomnessResult), } @@ -186,7 +188,7 @@ func (bc *blockChain) extractBlocks() (ret []*types.Block) { defer bc.lock.Unlock() for len(bc.confirmedBlocks) > 0 { c := bc.confirmedBlocks[0] - if c.Position.Round > 0 && len(c.Finalization.Randomness) == 0 { + if c.Position.Round >= DKGDelayRound && len(c.Finalization.Randomness) == 0 { break } c, bc.confirmedBlocks = bc.confirmedBlocks[0], bc.confirmedBlocks[1:] @@ -203,9 +205,6 @@ func (bc *blockChain) extractBlocks() (ret []*types.Block) { } func (bc *blockChain) sanityCheck(b *types.Block) error { - if b.Position.ChainID != 0 { - panic(fmt.Errorf("attempt to process block from non-zero chainID")) - } if b.IsEmpty() { panic(fmt.Errorf("pass empty block to sanity check: %s", b)) } @@ -228,7 +227,7 @@ func (bc *blockChain) sanityCheck(b *types.Block) error { } return ErrInvalidBlockHeight } - tipConfig := bc.getTipConfig() + tipConfig := bc.tipConfig() if tipConfig.isLastBlock(bc.lastConfirmed) { if b.Position.Round != bc.lastConfirmed.Position.Round+1 { return ErrRoundNotSwitch @@ -250,9 +249,6 @@ func (bc *blockChain) sanityCheck(b *types.Block) error { // addEmptyBlock is called when an empty block is confirmed by BA. func (bc *blockChain) addEmptyBlock(position types.Position) ( *types.Block, error) { - if position.ChainID != 0 { - panic(fmt.Errorf("attempt to process block from non-zero chainID")) - } bc.lock.Lock() defer bc.lock.Unlock() add := func() *types.Block { @@ -286,9 +282,6 @@ func (bc *blockChain) addEmptyBlock(position types.Position) ( // addBlock should be called when the block is confirmed by BA, we won't perform // sanity check against this block, it's ok to add block with skipping height. func (bc *blockChain) addBlock(b *types.Block) error { - if b.Position.ChainID != 0 { - panic(fmt.Errorf("attempt to process block from non-zero chainID")) - } bc.lock.Lock() defer bc.lock.Unlock() confirmed := false @@ -314,9 +307,6 @@ func (bc *blockChain) addBlock(b *types.Block) error { } func (bc *blockChain) addRandomness(r *types.BlockRandomnessResult) error { - if r.Position.ChainID != 0 { - panic(fmt.Errorf("attempt to process block from non-zero chainID")) - } if func() bool { bc.lock.RLock() defer bc.lock.RUnlock() @@ -361,8 +351,8 @@ func (bc *blockChain) tipRound() uint64 { if bc.lastConfirmed == nil { return 0 } - offset := uint64(0) - if bc.lastConfirmed.Timestamp.After(bc.getTipConfig().roundEndTime) { + offset, tipConfig := uint64(0), bc.tipConfig() + if tipConfig.isLastBlock(bc.lastConfirmed) { offset++ } return bc.lastConfirmed.Position.Round + offset @@ -392,7 +382,7 @@ func (bc *blockChain) nextBlock() (uint64, time.Time) { // lastConfirmed block in the scenario of "nextBlock" method. tip, config := bc.lastConfirmed, bc.configs[0] if tip == nil { - return 0, config.roundBeginTime + return 0, bc.dMoment } return tip.Position.Height + 1, tip.Timestamp.Add(config.minBlockInterval) } @@ -461,6 +451,11 @@ func (bc *blockChain) findPendingBlock(p types.Position) *types.Block { func (bc *blockChain) addPendingBlockRecord(p pendingBlockRecord) { if err := bc.pendingBlocks.insert(p); err != nil { + if err == ErrDuplicatedPendingBlock { + // TODO(mission): panic directly once our BA can confirm blocks + // uniquely and in sequence. + return + } panic(err) } if p.block != nil { @@ -507,7 +502,7 @@ func (bc *blockChain) purgeConfig() { func (bc *blockChain) verifyRandomness( blockHash common.Hash, round uint64, randomness []byte) (bool, error) { - if round == 0 { + if round < DKGDelayRound { return len(randomness) == 0, nil } v, ok, err := bc.vGetter.UpdateAndGet(round) @@ -528,26 +523,31 @@ func (bc *blockChain) prepareBlock(position types.Position, b = &types.Block{Position: position, Timestamp: proposeTime} tip := bc.lastConfirmed // Make sure we can propose a block at expected position for callers. - expectedPosition := types.Position{} if tip == nil { // The case for genesis block. - if !position.Equal(expectedPosition) { + if !position.Equal(types.Position{}) { b, err = nil, ErrNotGenesisBlock + return } else if empty { - b.Timestamp = bc.configs[0].roundBeginTime + b.Timestamp = bc.dMoment } } else { - expectedPosition.Height = tip.Position.Height + 1 - tipConfig := bc.getTipConfig() - if tipConfig.isLastBlock(tip) { - expectedPosition.Round = tip.Position.Round + 1 - } else { - expectedPosition.Round = tip.Position.Round - } - if !expectedPosition.Equal(position) { + tipConfig := bc.tipConfig() + if tip.Position.Height+1 != position.Height { b, err = nil, ErrNotFollowTipPosition return } + if tipConfig.isLastBlock(tip) { + if tip.Position.Round+1 != position.Round { + b, err = nil, ErrRoundNotSwitch + return + } + } else { + if tip.Position.Round != position.Round { + b, err = nil, ErrInvalidRoundID + return + } + } b.ParentHash = tip.Hash if !empty { bc.logger.Debug("Calling Application.PreparePayload", @@ -564,7 +564,6 @@ func (bc *blockChain) prepareBlock(position types.Position, if !b.Timestamp.After(tip.Timestamp) { b.Timestamp = tip.Timestamp.Add(tipConfig.minBlockInterval) } - } else { b.Witness.Height = tip.Witness.Height b.Witness.Data = make([]byte, len(tip.Witness.Data)) @@ -585,7 +584,7 @@ func (bc *blockChain) prepareBlock(position types.Position, return } -func (bc *blockChain) getTipConfig() blockChainConfig { +func (bc *blockChain) tipConfig() blockChainConfig { if bc.lastConfirmed == nil { panic(fmt.Errorf("attempting to access config without tip")) } diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/consensus.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/consensus.go index f4c0a372d..370df72cf 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/consensus.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/consensus.go @@ -58,12 +58,12 @@ var ( // consensusBAReceiver implements agreementReceiver. type consensusBAReceiver struct { // TODO(mission): consensus would be replaced by blockChain and network. - consensus *Consensus - agreementModule *agreement - changeNotaryTime time.Time - roundValue *atomic.Value - isNotary bool - restartNotary chan types.Position + consensus *Consensus + agreementModule *agreement + changeNotaryHeight uint64 + roundValue *atomic.Value + isNotary bool + restartNotary chan types.Position } func (recv *consensusBAReceiver) round() uint64 { @@ -241,10 +241,17 @@ CleanChannelLoop: } } newPos := block.Position - if block.Timestamp.After(recv.changeNotaryTime) { + if block.Position.Height+1 == recv.changeNotaryHeight { newPos.Round++ recv.roundValue.Store(newPos.Round) } + currentRound := recv.round() + if block.Position.Height > recv.changeNotaryHeight && + block.Position.Round <= currentRound { + panic(fmt.Errorf( + "round not switch when confirmig: %s, %d, should switch at %d", + block, currentRound, recv.changeNotaryHeight)) + } recv.restartNotary <- newPos } @@ -383,7 +390,6 @@ type Consensus struct { bcModule *blockChain dMoment time.Time nodeSetCache *utils.NodeSetCache - round uint64 roundForNewConfig uint64 lock sync.RWMutex ctx context.Context @@ -412,7 +418,7 @@ func NewConsensus( prv crypto.PrivateKey, logger common.Logger) *Consensus { return newConsensusForRound( - nil, dMoment, app, gov, db, network, prv, logger, true) + nil, 0, dMoment, app, gov, db, network, prv, logger, true) } // NewConsensusForSimulation creates an instance of Consensus for simulation, @@ -426,7 +432,7 @@ func NewConsensusForSimulation( prv crypto.PrivateKey, logger common.Logger) *Consensus { return newConsensusForRound( - nil, dMoment, app, gov, db, network, prv, logger, false) + nil, 0, dMoment, app, gov, db, network, prv, logger, false) } // NewConsensusFromSyncer constructs an Consensus instance from information @@ -440,7 +446,8 @@ func NewConsensusForSimulation( // their positions, in ascending order. func NewConsensusFromSyncer( initBlock *types.Block, - initRoundBeginTime time.Time, + initRoundBeginHeight uint64, + dMoment time.Time, app Application, gov Governance, db db.Database, @@ -451,8 +458,8 @@ func NewConsensusFromSyncer( cachedMessages []interface{}, logger common.Logger) (*Consensus, error) { // Setup Consensus instance. - con := newConsensusForRound(initBlock, initRoundBeginTime, app, gov, db, - networkModule, prv, logger, true) + con := newConsensusForRound(initBlock, initRoundBeginHeight, dMoment, app, + gov, db, networkModule, prv, logger, true) // Launch a dummy receiver before we start receiving from network module. con.dummyMsgBuffer = cachedMessages con.dummyCancel, con.dummyFinished = utils.LaunchDummyReceiver( @@ -486,9 +493,11 @@ func NewConsensusFromSyncer( } // newConsensusForRound creates a Consensus instance. +// TODO(mission): remove dMoment, it's no longer one part of consensus. func newConsensusForRound( initBlock *types.Block, - initRoundBeginTime time.Time, + initRoundBeginHeight uint64, + dMoment time.Time, app Application, gov Governance, db db.Database, @@ -511,6 +520,7 @@ func newConsensusForRound( initRound = initBlock.Position.Round } initConfig := utils.GetConfigWithPanic(gov, initRound, logger) + initCRS := utils.GetCRSWithPanic(gov, initRound, logger) // Init configuration chain. ID := types.NewNodeID(prv.PublicKey()) recv := &consensusDKGReceiver{ @@ -529,8 +539,8 @@ func newConsensusForRound( } bcConfig := blockChainConfig{} bcConfig.fromConfig(initRound, initConfig) - bcConfig.setRoundBeginTime(initRoundBeginTime) - bcModule := newBlockChain(ID, initBlock, bcConfig, appModule, + bcConfig.setRoundBeginHeight(initRoundBeginHeight) + bcModule := newBlockChain(ID, dMoment, initBlock, bcConfig, appModule, NewTSigVerifierCache(gov, 7), signer, logger) // Construct Consensus instance. con := &Consensus{ @@ -544,7 +554,7 @@ func newConsensusForRound( dkgReady: sync.NewCond(&sync.Mutex{}), cfgModule: cfgModule, bcModule: bcModule, - dMoment: initRoundBeginTime, + dMoment: dMoment, nodeSetCache: nodeSetCache, signer: signer, event: common.NewEvent(), @@ -555,8 +565,15 @@ func newConsensusForRound( processBlockChan: make(chan *types.Block, 1024), } con.ctx, con.ctxCancel = context.WithCancel(context.Background()) - con.baMgr = newAgreementMgr(con, initRound, initRoundBeginTime) - if err := con.prepare(initBlock); err != nil { + baConfig := agreementMgrConfig{} + baConfig.from(initRound, initConfig, initCRS) + baConfig.setRoundBeginHeight(initRoundBeginHeight) + var err error + con.baMgr, err = newAgreementMgr(con, initRound, baConfig) + if err != nil { + panic(err) + } + if err = con.prepare(initRoundBeginHeight, initBlock); err != nil { panic(err) } return con @@ -566,53 +583,28 @@ func newConsensusForRound( // 'initBlock' could be either: // - nil // - the last finalized block -func (con *Consensus) prepare(initBlock *types.Block) (err error) { +func (con *Consensus) prepare( + initRoundBeginHeight uint64, initBlock *types.Block) (err error) { // The block past from full node should be delivered already or known by // full node. We don't have to notify it. initRound := uint64(0) if initBlock != nil { initRound = initBlock.Position.Round } + // Setup blockChain module. con.roundForNewConfig = initRound + 1 initConfig := utils.GetConfigWithPanic(con.gov, initRound, con.logger) - // Setup context. - con.logger.Debug("Calling Governance.CRS", "round", initRound) - initCRS := con.gov.CRS(initRound) - if (initCRS == common.Hash{}) { - err = ErrCRSNotReady - return - } - if err = con.baMgr.appendConfig(initRound, initConfig, initCRS); err != nil { - return - } - // Setup blockChain module. initPlusOneCfg := utils.GetConfigWithPanic(con.gov, initRound+1, con.logger) if err = con.bcModule.appendConfig(initRound+1, initPlusOneCfg); err != nil { return } if initRound == 0 { - dkgSet, err := con.nodeSetCache.GetDKGSet(initRound) - if err != nil { - return err - } - if _, exist := dkgSet[con.ID]; exist { - con.logger.Info("Selected as DKG set", "round", initRound) - go func() { - // Sleep until dMoment come. - time.Sleep(con.dMoment.Sub(time.Now().UTC())) - // Network is not stable upon starting. Wait some time to ensure first - // DKG would success. Three is a magic number. - time.Sleep(initConfig.MinBlockInterval * 3) - con.cfgModule.registerDKG(initRound, getDKGThreshold(initConfig)) - con.event.RegisterTime(con.dMoment.Add(initConfig.RoundInterval/4), - func(time.Time) { - con.runDKG(initRound, initConfig) - }) - }() + if DKGDelayRound == 0 { + panic("not implemented yet") } } // Register events. - con.initialRound(con.dMoment, initRound, initConfig) + con.initialRound(initRoundBeginHeight, initRound, initConfig) return } @@ -672,18 +664,11 @@ func (con *Consensus) runDKG(round uint64, config *types.Config) { } con.dkgRunning = 1 go func() { - startTime := time.Now().UTC() defer func() { con.dkgReady.L.Lock() defer con.dkgReady.L.Unlock() con.dkgReady.Broadcast() con.dkgRunning = 2 - DKGTime := time.Now().Sub(startTime) - if DKGTime.Nanoseconds() >= - config.RoundInterval.Nanoseconds()/2 { - con.logger.Warn("Your computer cannot finish DKG on time!", - "nodeID", con.ID.String()) - } }() if err := con.cfgModule.runDKG(round); err != nil { con.logger.Error("Failed to runDKG", "error", err) @@ -739,25 +724,28 @@ func (con *Consensus) runCRS(round uint64) { } func (con *Consensus) initialRound( - startTime time.Time, round uint64, config *types.Config) { + startHeight uint64, round uint64, config *types.Config) { select { case <-con.ctx.Done(): return default: } - curDkgSet, err := con.nodeSetCache.GetDKGSet(round) - if err != nil { - con.logger.Error("Error getting DKG set", "round", round, "error", err) - curDkgSet = make(map[types.NodeID]struct{}) - } - // Initiate CRS routine. - if _, exist := curDkgSet[con.ID]; exist { - con.event.RegisterTime(startTime.Add(config.RoundInterval/2), - func(time.Time) { - go func() { - con.runCRS(round) - }() - }) + if round >= DKGDelayRound { + curDkgSet, err := con.nodeSetCache.GetDKGSet(round) + if err != nil { + con.logger.Error("Error getting DKG set", "round", round, "error", err) + curDkgSet = make(map[types.NodeID]struct{}) + } + // Initiate CRS routine. + if _, exist := curDkgSet[con.ID]; exist { + con.event.RegisterHeight( + startHeight+config.RoundLength/2, + func(uint64) { + go func() { + con.runCRS(round) + }() + }) + } } // checkCRS is a generator of checker to check if CRS for that round is // ready or not. @@ -774,76 +762,75 @@ func (con *Consensus) initialRound( } } // Initiate BA modules. - con.event.RegisterTime( - startTime.Add(config.RoundInterval/2+config.LambdaDKG), - func(time.Time) { - go func(nextRound uint64) { - if !checkWithCancel( - con.ctx, 500*time.Millisecond, checkCRS(nextRound)) { - con.logger.Debug("unable to prepare CRS for baMgr", - "round", nextRound) - return - } - // Notify BA for new round. - nextConfig := utils.GetConfigWithPanic( - con.gov, nextRound, con.logger) - nextCRS := utils.GetCRSWithPanic( - con.gov, nextRound, con.logger) - con.logger.Info("appendConfig for baMgr", "round", nextRound) - if err := con.baMgr.appendConfig( - nextRound, nextConfig, nextCRS); err != nil { - panic(err) - } - }(round + 1) - }) + con.event.RegisterHeight(startHeight+config.RoundLength/2, func(uint64) { + go func(nextRound uint64) { + if !checkWithCancel( + con.ctx, 500*time.Millisecond, checkCRS(nextRound)) { + con.logger.Debug("unable to prepare CRS for baMgr", + "round", nextRound) + return + } + // Notify BA for new round. + nextConfig := utils.GetConfigWithPanic( + con.gov, nextRound, con.logger) + nextCRS := utils.GetCRSWithPanic( + con.gov, nextRound, con.logger) + con.logger.Info("appendConfig for baMgr", "round", nextRound) + if err := con.baMgr.appendConfig( + nextRound, nextConfig, nextCRS); err != nil { + panic(err) + } + }(round + 1) + }) // Initiate DKG for this round. - con.event.RegisterTime(startTime.Add(config.RoundInterval/2+config.LambdaDKG), - func(time.Time) { - go func(nextRound uint64) { - // Normally, gov.CRS would return non-nil. Use this for in case of - // unexpected network fluctuation and ensure the robustness. - if !checkWithCancel( - con.ctx, 500*time.Millisecond, checkCRS(nextRound)) { - con.logger.Debug("unable to prepare CRS for DKG set", - "round", nextRound) - return - } - nextDkgSet, err := con.nodeSetCache.GetDKGSet(nextRound) - if err != nil { - con.logger.Error("Error getting DKG set", - "round", nextRound, - "error", err) - return - } - if _, exist := nextDkgSet[con.ID]; !exist { - return - } - con.logger.Info("Selected as DKG set", "round", nextRound) - con.cfgModule.registerDKG(nextRound, getDKGThreshold(config)) - con.event.RegisterTime( - startTime.Add(config.RoundInterval*2/3), - func(time.Time) { - func() { - con.dkgReady.L.Lock() - defer con.dkgReady.L.Unlock() - con.dkgRunning = 0 - }() - nextConfig := utils.GetConfigWithPanic( - con.gov, nextRound, con.logger) - con.runDKG(nextRound, nextConfig) - }) - }(round + 1) - }) + con.event.RegisterHeight(startHeight+config.RoundLength/2, func(uint64) { + go func(nextRound uint64) { + if nextRound < DKGDelayRound { + con.logger.Info("Skip runDKG for round", "round", nextRound) + return + } + // Normally, gov.CRS would return non-nil. Use this for in case of + // unexpected network fluctuation and ensure the robustness. + if !checkWithCancel( + con.ctx, 500*time.Millisecond, checkCRS(nextRound)) { + con.logger.Debug("unable to prepare CRS for DKG set", + "round", nextRound) + return + } + nextDkgSet, err := con.nodeSetCache.GetDKGSet(nextRound) + if err != nil { + con.logger.Error("Error getting DKG set", + "round", nextRound, + "error", err) + return + } + if _, exist := nextDkgSet[con.ID]; !exist { + return + } + con.logger.Info("Selected as DKG set", "round", nextRound) + con.cfgModule.registerDKG(nextRound, getDKGThreshold(config)) + con.event.RegisterHeight(startHeight+config.RoundLength*2/3, + func(uint64) { + func() { + con.dkgReady.L.Lock() + defer con.dkgReady.L.Unlock() + con.dkgRunning = 0 + }() + nextConfig := utils.GetConfigWithPanic( + con.gov, nextRound, con.logger) + con.runDKG(nextRound, nextConfig) + }) + }(round + 1) + }) // Prepare blockChain module for next round and next "initialRound" routine. - con.event.RegisterTime(startTime.Add(config.RoundInterval), - func(time.Time) { - // Change round. - // Get configuration for next round. - nextRound := round + 1 - nextConfig := utils.GetConfigWithPanic(con.gov, nextRound, con.logger) - con.initialRound( - startTime.Add(config.RoundInterval), nextRound, nextConfig) - }) + con.event.RegisterHeight(startHeight+config.RoundLength, func(uint64) { + // Change round. + // Get configuration for next round. + nextRound := round + 1 + nextConfig := utils.GetConfigWithPanic(con.gov, nextRound, con.logger) + con.initialRound( + startHeight+config.RoundLength, nextRound, nextConfig) + }) } // Stop the Consensus core. @@ -1194,7 +1181,7 @@ func (con *Consensus) deliverFinalizedBlocksWithoutLock() (err error) { "pending", con.bcModule.lastPendingBlock()) for _, b := range deliveredBlocks { con.deliverBlock(b) - go con.event.NotifyTime(b.Finalization.Timestamp) + go con.event.NotifyHeight(b.Finalization.Height) } return } diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/constant.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/constant.go index 563a321f5..f80e1b9d8 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/constant.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/constant.go @@ -17,9 +17,21 @@ package core +import "github.com/dexon-foundation/dexon-consensus/core/utils" + // ConfigRoundShift refers to the difference between block's round and config // round derived from its state. // // For example, when round shift is 2, a block in round 0 should derive config // for round 2. const ConfigRoundShift uint64 = 2 + +// DKGDelayRound refers to the round that first DKG is run. +// +// For example, when delay round is 1, new DKG will run at round 1. Round 0 will +// have neither DKG nor CRS. +const DKGDelayRound uint64 = 1 + +func init() { + utils.SetDKGDelayRound(DKGDelayRound) +} diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/interfaces.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/interfaces.go index 3879e36a5..b7e000ae6 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/interfaces.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/interfaces.go @@ -143,6 +143,12 @@ type Governance interface { // ReportForkBlock reports a node for forking blocks. ReportForkBlock(block1, block2 *types.Block) + + // ResetDKG resets latest DKG data and propose new CRS. + ResetDKG(newSignedCRS []byte) + + // DKGResetCount returns the reset count for DKG of given round. + DKGResetCount(round uint64) uint64 } // Ticker define the capability to tick by interval. diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/round-based-config.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/round-based-config.go index 67779a63c..4f3a4cbf9 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/round-based-config.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/round-based-config.go @@ -18,35 +18,34 @@ package core import ( - "time" + "fmt" "github.com/dexon-foundation/dexon-consensus/core/types" ) type roundBasedConfig struct { - roundID uint64 - - // roundBeginTime is the beginning of round, as local time. - roundBeginTime time.Time - roundInterval time.Duration - - // roundEndTime is a cache for begin + interval. - roundEndTime time.Time + roundID uint64 + roundBeginHeight uint64 + roundEndHeight uint64 + roundInterval uint64 } func (config *roundBasedConfig) setupRoundBasedFields( roundID uint64, cfg *types.Config) { config.roundID = roundID - config.roundInterval = cfg.RoundInterval + config.roundInterval = cfg.RoundLength } -func (config *roundBasedConfig) setRoundBeginTime(begin time.Time) { - config.roundBeginTime = begin - config.roundEndTime = begin.Add(config.roundInterval) +func (config *roundBasedConfig) setRoundBeginHeight(begin uint64) { + config.roundBeginHeight = begin + config.roundEndHeight = begin + config.roundInterval } // isLastBlock checks if a block is the last block of this round. func (config *roundBasedConfig) isLastBlock(b *types.Block) bool { - return b.Position.Round == config.roundID && - b.Timestamp.After(config.roundEndTime) + if b.Position.Round != config.roundID { + panic(fmt.Errorf("attempt to compare by different round: %s, %d", + b, config.roundID)) + } + return b.Position.Height+1 == config.roundEndHeight } diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/syncer/agreement.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/syncer/agreement.go index acc4f1c6c..9f1abcaf5 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/syncer/agreement.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/syncer/agreement.go @@ -160,8 +160,10 @@ func (a *agreement) processNewCRS(round uint64) { if round <= a.latestCRSRound { return } + prevRound := a.latestCRSRound + 1 + a.latestCRSRound = round // Verify all pending results. - for r := a.latestCRSRound + 1; r <= round; r++ { + for r := prevRound; r <= a.latestCRSRound; r++ { pendingsForRound := a.pendings[r] if pendingsForRound == nil { continue @@ -169,7 +171,9 @@ func (a *agreement) processNewCRS(round uint64) { delete(a.pendings, r) for _, res := range pendingsForRound { if err := core.VerifyAgreementResult(res, a.cache); err != nil { - a.logger.Error("invalid agreement result", "result", res) + a.logger.Error("invalid agreement result", + "result", res, + "error", err) continue } a.logger.Error("flush agreement result", "result", res) @@ -177,7 +181,6 @@ func (a *agreement) processNewCRS(round uint64) { break } } - a.latestCRSRound = round } // confirm notifies consensus the confirmation of a block in BA. diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/syncer/consensus.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/syncer/consensus.go index 618d90e8c..7ba659f27 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/syncer/consensus.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/syncer/consensus.go @@ -67,12 +67,13 @@ type Consensus struct { blocks types.BlocksByPosition agreementModule *agreement configs []*types.Config - roundBeginTimes []time.Time + roundBeginHeights []uint64 agreementRoundCut uint64 // lock for accessing all fields. lock sync.RWMutex duringBuffering bool + latestCRSRound uint64 moduleWaitGroup sync.WaitGroup agreementWaitGroup sync.WaitGroup pullChan chan common.Hash @@ -109,7 +110,7 @@ func NewConsensus( configs: []*types.Config{ utils.GetConfigWithPanic(gov, 0, logger), }, - roundBeginTimes: []time.Time{dMoment}, + roundBeginHeights: []uint64{0}, receiveChan: make(chan *types.Block, 1000), pullChan: make(chan common.Hash, 1000), randomnessResults: make(map[common.Hash]*types.BlockRandomnessResult), @@ -280,7 +281,8 @@ func (con *Consensus) GetSyncedConsensus() (*core.Consensus, error) { var err error con.syncedConsensus, err = core.NewConsensusFromSyncer( con.syncedLastBlock, - con.roundBeginTimes[con.syncedLastBlock.Position.Round], + con.roundBeginHeights[con.syncedLastBlock.Position.Round], + con.dMoment, con.app, con.gov, con.db, @@ -336,7 +338,6 @@ func (con *Consensus) buildEmptyBlock(b *types.Block, parent *types.Block) { b.Witness.Height = parent.Witness.Height b.Witness.Data = make([]byte, len(parent.Witness.Data)) copy(b.Witness.Data, parent.Witness.Data) - b.Acks = common.NewSortedHashes(common.Hashes{parent.Hash}) } // setupConfigs is called by SyncBlocks with blocks from compaction chain. In @@ -368,9 +369,9 @@ func (con *Consensus) setupConfigsUntilRound(round uint64) { for r := uint64(len(con.configs)); r <= round; r++ { cfg := utils.GetConfigWithPanic(con.gov, r, con.logger) con.configs = append(con.configs, cfg) - con.roundBeginTimes = append( - con.roundBeginTimes, - con.roundBeginTimes[r-1].Add(con.configs[r-1].RoundInterval)) + con.roundBeginHeights = append( + con.roundBeginHeights, + con.roundBeginHeights[r-1]+con.configs[r-1].RoundLength) } } @@ -416,6 +417,11 @@ func (con *Consensus) cacheRandomnessResult(r *types.BlockRandomnessResult) { if len(con.blocks) > 0 && r.Position.Older(con.blocks[0].Position) { return true } + if r.Position.Round > con.latestCRSRound { + // We can't process randomness from rounds that its CRS is still + // unknown. + return true + } _, exists := con.randomnessResults[r.BlockHash] return exists }() { @@ -453,41 +459,22 @@ func (con *Consensus) startNetwork() { con.moduleWaitGroup.Add(1) go func() { defer con.moduleWaitGroup.Done() - Loop: + loop: for { select { case val := <-con.network.ReceiveChan(): - var pos types.Position switch v := val.(type) { case *types.Block: - pos = v.Position case *types.AgreementResult: - pos = v.Position case *types.BlockRandomnessResult: con.cacheRandomnessResult(v) - continue Loop + continue loop default: - continue Loop - } - if func() bool { - con.lock.RLock() - defer con.lock.RUnlock() - if pos.ChainID > 0 { - // This error might be easily encountered when the - // "latest" parameter of SyncBlocks is turned on too - // early. - con.logger.Error( - "Unknown chainID message received (syncer)", - "position", pos, - ) - return false - } - return true - }() { - con.agreementModule.inputChan <- val + continue loop } + con.agreementModule.inputChan <- val case <-con.ctx.Done(): - return + break loop } } }() @@ -505,6 +492,11 @@ func (con *Consensus) startCRSMonitor() { } con.logger.Debug("CRS is ready", "round", round) lastNotifiedRound = round + func() { + con.lock.Lock() + defer con.lock.Unlock() + con.latestCRSRound = round + }() for func() bool { con.lock.RLock() defer con.lock.RUnlock() diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/ticker.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/ticker.go index ffd5ab45d..636fb8c49 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/ticker.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/ticker.go @@ -18,6 +18,9 @@ package core import ( + "context" + "fmt" + "sync" "time" "github.com/dexon-foundation/dexon-consensus/core/utils" @@ -36,32 +39,65 @@ const ( // defaultTicker is a wrapper to implement ticker interface based on // time.Ticker. type defaultTicker struct { - ticker *time.Ticker - duration time.Duration + ticker *time.Ticker + tickerChan chan time.Time + duration time.Duration + ctx context.Context + ctxCancel context.CancelFunc + waitGroup sync.WaitGroup } // newDefaultTicker constructs an defaultTicker instance by giving an interval. func newDefaultTicker(lambda time.Duration) *defaultTicker { - return &defaultTicker{ - ticker: time.NewTicker(lambda), - duration: lambda, - } + ticker := &defaultTicker{duration: lambda} + ticker.init() + return ticker } // Tick implements Tick method of ticker interface. func (t *defaultTicker) Tick() <-chan time.Time { - return t.ticker.C + return t.tickerChan } // Stop implements Stop method of ticker interface. func (t *defaultTicker) Stop() { t.ticker.Stop() + t.ctxCancel() + t.waitGroup.Wait() + t.ctx = nil + t.ctxCancel = nil + close(t.tickerChan) + t.tickerChan = nil } // Restart implements Stop method of ticker interface. func (t *defaultTicker) Restart() { - t.ticker.Stop() + t.Stop() + t.init() +} + +func (t *defaultTicker) init() { t.ticker = time.NewTicker(t.duration) + t.tickerChan = make(chan time.Time) + t.ctx, t.ctxCancel = context.WithCancel(context.Background()) + t.waitGroup.Add(1) + go t.monitor() +} + +func (t *defaultTicker) monitor() { + defer t.waitGroup.Done() +loop: + for { + select { + case <-t.ctx.Done(): + break loop + case v := <-t.ticker.C: + select { + case t.tickerChan <- v: + default: + } + } + } } // newTicker is a helper to setup a ticker by giving an Governance. If @@ -82,8 +118,8 @@ func newTicker(gov Governance, round uint64, tickerType TickerType) (t Ticker) { duration = utils.GetConfigWithPanic(gov, round, nil).LambdaBA case TickerDKG: duration = utils.GetConfigWithPanic(gov, round, nil).LambdaDKG - case TickerCRS: - duration = utils.GetConfigWithPanic(gov, round, nil).RoundInterval / 2 + default: + panic(fmt.Errorf("unknown ticker type: %d", tickerType)) } t = newDefaultTicker(duration) } diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/types/block.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/types/block.go index a2b697ce0..2b23e96e3 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/types/block.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/types/block.go @@ -23,7 +23,6 @@ import ( "bytes" "fmt" "io" - "sort" "time" "github.com/dexon-foundation/dexon/rlp" @@ -125,17 +124,16 @@ type Witness struct { // Block represents a single event broadcasted on the network. type Block struct { - ProposerID NodeID `json:"proposer_id"` - ParentHash common.Hash `json:"parent_hash"` - Hash common.Hash `json:"hash"` - Position Position `json:"position"` - Timestamp time.Time `json:"timestamp"` - Acks common.SortedHashes `json:"acks"` - Payload []byte `json:"payload"` - PayloadHash common.Hash `json:"payload_hash"` - Witness Witness `json:"witness"` - Finalization FinalizationResult `json:"finalization"` - Signature crypto.Signature `json:"signature"` + ProposerID NodeID `json:"proposer_id"` + ParentHash common.Hash `json:"parent_hash"` + Hash common.Hash `json:"hash"` + Position Position `json:"position"` + Timestamp time.Time `json:"timestamp"` + Payload []byte `json:"payload"` + PayloadHash common.Hash `json:"payload_hash"` + Witness Witness `json:"witness"` + Finalization FinalizationResult `json:"finalization"` + Signature crypto.Signature `json:"signature"` CRSSignature crypto.Signature `json:"crs_signature"` } @@ -146,7 +144,6 @@ type rlpBlock struct { Hash common.Hash Position Position Timestamp *rlpTimestamp - Acks common.SortedHashes Payload []byte PayloadHash common.Hash Witness *Witness @@ -164,7 +161,6 @@ func (b *Block) EncodeRLP(w io.Writer) error { Hash: b.Hash, Position: b.Position, Timestamp: &rlpTimestamp{b.Timestamp}, - Acks: b.Acks, Payload: b.Payload, PayloadHash: b.PayloadHash, Witness: &b.Witness, @@ -185,7 +181,6 @@ func (b *Block) DecodeRLP(s *rlp.Stream) error { Hash: dec.Hash, Position: dec.Position, Timestamp: dec.Timestamp.Time, - Acks: dec.Acks, Payload: dec.Payload, PayloadHash: dec.PayloadHash, Witness: *dec.Witness, @@ -208,7 +203,6 @@ func (b *Block) Clone() (bcopy *Block) { bcopy.ParentHash = b.ParentHash bcopy.Hash = b.Hash bcopy.Position.Round = b.Position.Round - bcopy.Position.ChainID = b.Position.ChainID bcopy.Position.Height = b.Position.Height bcopy.Signature = b.Signature.Clone() bcopy.CRSSignature = b.CRSSignature.Clone() @@ -217,8 +211,6 @@ func (b *Block) Clone() (bcopy *Block) { bcopy.Witness.Data = make([]byte, len(b.Witness.Data)) copy(bcopy.Witness.Data, b.Witness.Data) bcopy.Timestamp = b.Timestamp - bcopy.Acks = make(common.SortedHashes, len(b.Acks)) - copy(bcopy.Acks, b.Acks) bcopy.Payload = make([]byte, len(b.Payload)) copy(bcopy.Payload, b.Payload) bcopy.PayloadHash = b.PayloadHash @@ -240,14 +232,6 @@ func (b *Block) IsEmpty() bool { return b.ProposerID.Hash == common.Hash{} } -// IsAcking checks if a block acking another by it's hash. -func (b *Block) IsAcking(hash common.Hash) bool { - idx := sort.Search(len(b.Acks), func(i int) bool { - return bytes.Compare(b.Acks[i][:], hash[:]) >= 0 - }) - return !(idx == len(b.Acks) || b.Acks[idx] != hash) -} - // ByHash is the helper type for sorting slice of blocks by hash. type ByHash []*Block diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/types/config.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/types/config.go index c9d31f8c4..eda09f06e 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/types/config.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/types/config.go @@ -19,52 +19,38 @@ package types import ( "encoding/binary" - "math" "time" ) // Config stands for Current Configuration Parameters. type Config struct { - // Network related. - NumChains uint32 - // Lambda related. LambdaBA time.Duration LambdaDKG time.Duration - // Total ordering related. - K int - PhiRatio float32 - // Set related. NotarySetSize uint32 DKGSetSize uint32 // Time related. - RoundInterval time.Duration + RoundLength uint64 MinBlockInterval time.Duration } // Clone return a copied configuration. func (c *Config) Clone() *Config { return &Config{ - NumChains: c.NumChains, LambdaBA: c.LambdaBA, LambdaDKG: c.LambdaDKG, - K: c.K, - PhiRatio: c.PhiRatio, NotarySetSize: c.NotarySetSize, DKGSetSize: c.DKGSetSize, - RoundInterval: c.RoundInterval, + RoundLength: c.RoundLength, MinBlockInterval: c.MinBlockInterval, } } // Bytes returns []byte representation of Config. func (c *Config) Bytes() []byte { - binaryNumChains := make([]byte, 4) - binary.LittleEndian.PutUint32(binaryNumChains, c.NumChains) - binaryLambdaBA := make([]byte, 8) binary.LittleEndian.PutUint64( binaryLambdaBA, uint64(c.LambdaBA.Nanoseconds())) @@ -72,32 +58,23 @@ func (c *Config) Bytes() []byte { binary.LittleEndian.PutUint64( binaryLambdaDKG, uint64(c.LambdaDKG.Nanoseconds())) - binaryK := make([]byte, 4) - binary.LittleEndian.PutUint32(binaryK, uint32(c.K)) - binaryPhiRatio := make([]byte, 4) - binary.LittleEndian.PutUint32(binaryPhiRatio, math.Float32bits(c.PhiRatio)) - binaryNotarySetSize := make([]byte, 4) binary.LittleEndian.PutUint32(binaryNotarySetSize, c.NotarySetSize) binaryDKGSetSize := make([]byte, 4) binary.LittleEndian.PutUint32(binaryDKGSetSize, c.DKGSetSize) - binaryRoundInterval := make([]byte, 8) - binary.LittleEndian.PutUint64(binaryRoundInterval, - uint64(c.RoundInterval.Nanoseconds())) + binaryRoundLength := make([]byte, 8) + binary.LittleEndian.PutUint64(binaryRoundLength, c.RoundLength) binaryMinBlockInterval := make([]byte, 8) binary.LittleEndian.PutUint64(binaryMinBlockInterval, uint64(c.MinBlockInterval.Nanoseconds())) enc := make([]byte, 0, 40) - enc = append(enc, binaryNumChains...) enc = append(enc, binaryLambdaBA...) enc = append(enc, binaryLambdaDKG...) - enc = append(enc, binaryK...) - enc = append(enc, binaryPhiRatio...) enc = append(enc, binaryNotarySetSize...) enc = append(enc, binaryDKGSetSize...) - enc = append(enc, binaryRoundInterval...) + enc = append(enc, binaryRoundLength...) enc = append(enc, binaryMinBlockInterval...) return enc } diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/types/position.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/types/position.go index 902a55fec..81d23c266 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/types/position.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/types/position.go @@ -23,33 +23,22 @@ import ( // Position describes the position in the block lattice of an entity. type Position struct { - ChainID uint32 `json:"chain_id"` - Round uint64 `json:"round"` - Height uint64 `json:"height"` + Round uint64 `json:"round"` + Height uint64 `json:"height"` } func (pos Position) String() string { - return fmt.Sprintf("Position{Round:%d Chain:%d Height:%d}", - pos.Round, pos.ChainID, pos.Height) + return fmt.Sprintf("Position{Round:%d Height:%d}", pos.Round, pos.Height) } -// Equal checks if two positions are equal, it panics when their chainIDs -// are different. +// Equal checks if two positions are equal. func (pos Position) Equal(other Position) bool { - if pos.ChainID != other.ChainID { - panic(fmt.Errorf("unexpected chainID %d, should be %d", - other.ChainID, pos.ChainID)) - } return pos.Round == other.Round && pos.Height == other.Height } // Newer checks if one block is newer than another one on the same chain. // If two blocks on different chain compared by this function, it would panic. func (pos Position) Newer(other Position) bool { - if pos.ChainID != other.ChainID { - panic(fmt.Errorf("unexpected chainID %d, should be %d", - other.ChainID, pos.ChainID)) - } return pos.Round > other.Round || (pos.Round == other.Round && pos.Height > other.Height) } @@ -57,10 +46,6 @@ func (pos Position) Newer(other Position) bool { // Older checks if one block is older than another one on the same chain. // If two blocks on different chain compared by this function, it would panic. func (pos Position) Older(other Position) bool { - if pos.ChainID != other.ChainID { - panic(fmt.Errorf("unexpected chainID %d, should be %d", - other.ChainID, pos.ChainID)) - } return pos.Round < other.Round || (pos.Round == other.Round && pos.Height < other.Height) } diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/types/vote.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/types/vote.go index 6481eb46d..c4a625edd 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/types/vote.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/types/vote.go @@ -66,7 +66,7 @@ type Vote struct { } func (v *Vote) String() string { - return fmt.Sprintf("Vote{BP:%s %s Period:%d Type:%d Hash:%s}", + return fmt.Sprintf("Vote{VP:%s %s Period:%d Type:%d Hash:%s}", v.ProposerID.String()[:6], v.Position, v.Period, v.Type, v.BlockHash.String()[:6]) } diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/utils.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/utils.go index 14780e73b..46aa77a6b 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/utils.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/utils.go @@ -21,7 +21,6 @@ import ( "context" "errors" "fmt" - "math/rand" "os" "sort" "time" @@ -130,11 +129,6 @@ func removeFromSortedUint32Slice(xs []uint32, x uint32) []uint32 { return append(xs[:indexToRemove], xs[indexToRemove+1:]...) } -// pickBiasedTime returns a biased time based on a given range. -func pickBiasedTime(base time.Time, biasedRange time.Duration) time.Time { - return base.Add(time.Duration(rand.Intn(int(biasedRange)))) -} - // HashConfigurationBlock returns the hash value of configuration block. func HashConfigurationBlock( notarySet map[types.NodeID]struct{}, @@ -165,8 +159,7 @@ func HashConfigurationBlock( // instance. func VerifyAgreementResult( res *types.AgreementResult, cache *utils.NodeSetCache) error { - notarySet, err := cache.GetNotarySet( - res.Position.Round, res.Position.ChainID) + notarySet, err := cache.GetNotarySet(res.Position.Round) if err != nil { return err } diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/crypto.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/crypto.go index 43bbde13d..f5343ca38 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/crypto.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/crypto.go @@ -37,12 +37,6 @@ func hashWitness(witness *types.Witness) (common.Hash, error) { // HashBlock generates hash of a types.Block. func HashBlock(block *types.Block) (common.Hash, error) { hashPosition := hashPosition(block.Position) - // Handling Block.Acks. - binaryAcks := make([][]byte, len(block.Acks)) - for idx, ack := range block.Acks { - binaryAcks[idx] = ack[:] - } - hashAcks := crypto.Keccak256Hash(binaryAcks...) binaryTimestamp, err := block.Timestamp.UTC().MarshalBinary() if err != nil { return common.Hash{}, err @@ -56,7 +50,6 @@ func HashBlock(block *types.Block) (common.Hash, error) { block.ProposerID.Hash[:], block.ParentHash[:], hashPosition[:], - hashAcks[:], binaryTimestamp[:], block.PayloadHash[:], binaryWitness[:]) @@ -140,9 +133,6 @@ func VerifyCRSSignature(block *types.Block, crs common.Hash) ( } func hashPosition(position types.Position) common.Hash { - binaryChainID := make([]byte, 4) - binary.LittleEndian.PutUint32(binaryChainID, position.ChainID) - binaryRound := make([]byte, 8) binary.LittleEndian.PutUint64(binaryRound, position.Round) @@ -150,7 +140,6 @@ func hashPosition(position types.Position) common.Hash { binary.LittleEndian.PutUint64(binaryHeight, position.Height) return crypto.Keccak256Hash( - binaryChainID, binaryRound, binaryHeight, ) diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/nodeset-cache.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/nodeset-cache.go index 83541283b..6249665ac 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/nodeset-cache.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/nodeset-cache.go @@ -19,7 +19,6 @@ package utils import ( "errors" - "fmt" "sync" "github.com/dexon-foundation/dexon-consensus/common" @@ -34,8 +33,6 @@ var ( ErrCRSNotReady = errors.New("crs is not ready") // ErrConfigurationNotReady means we go nil configuration. ErrConfigurationNotReady = errors.New("configuration is not ready") - // ErrInvalidChainID means the chain ID is unexpected. - ErrInvalidChainID = errors.New("invalid chain id") ) type sets struct { @@ -125,12 +122,8 @@ func (cache *NodeSetCache) GetNodeSet(round uint64) (*types.NodeSet, error) { } // GetNotarySet returns of notary set of this round. -// TODO(mission): remove chainID parameter. func (cache *NodeSetCache) GetNotarySet( - round uint64, chainID uint32) (map[types.NodeID]struct{}, error) { - if chainID != 0 { - panic(fmt.Errorf("non-zero chainID found: %d", chainID)) - } + round uint64) (map[types.NodeID]struct{}, error) { IDs, err := cache.getOrUpdate(round) if err != nil { return nil, err @@ -196,12 +189,9 @@ func (cache *NodeSetCache) getOrUpdate(round uint64) (nIDs *sets, err error) { // // This cache would maintain 10 rounds before the updated round and purge // rounds not in this range. -func (cache *NodeSetCache) update( - round uint64) (nIDs *sets, err error) { - +func (cache *NodeSetCache) update(round uint64) (nIDs *sets, err error) { cache.lock.Lock() defer cache.lock.Unlock() - // Get information for the requested round. keySet := cache.nsIntf.NodeSet(round) if keySet == nil { @@ -232,14 +222,15 @@ func (cache *NodeSetCache) update( err = ErrConfigurationNotReady return } - nodesPerChain := cfg.RoundInterval / cfg.MinBlockInterval nIDs = &sets{ - crs: crs, - nodeSet: nodeSet, - notarySet: make(map[types.NodeID]struct{}), - dkgSet: nodeSet.GetSubSet( - int(cfg.DKGSetSize), types.NewDKGSetTarget(crs)), - leaderNode: make(map[uint64]types.NodeID, nodesPerChain), + crs: crs, + nodeSet: nodeSet, + notarySet: make(map[types.NodeID]struct{}), + leaderNode: make(map[uint64]types.NodeID, cfg.RoundLength), + } + if round >= dkgDelayRound { + nIDs.dkgSet = nodeSet.GetSubSet( + int(cfg.DKGSetSize), types.NewDKGSetTarget(crs)) } nIDs.notarySet = nodeSet.GetSubSet( int(cfg.NotarySetSize), types.NewNotarySetTarget(crs)) @@ -261,12 +252,9 @@ func (cache *NodeSetCache) update( return } -func (cache *NodeSetCache) get( - round uint64) (nIDs *sets, exists bool) { - +func (cache *NodeSetCache) get(round uint64) (nIDs *sets, exists bool) { cache.lock.RLock() defer cache.lock.RUnlock() - nIDs, exists = cache.rounds[round] return } diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/utils.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/utils.go index 8486d2854..203f57fc2 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/utils.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/utils.go @@ -26,6 +26,13 @@ import ( typesDKG "github.com/dexon-foundation/dexon-consensus/core/types/dkg" ) +var dkgDelayRound uint64 + +// SetDKGDelayRound sets the variable. +func SetDKGDelayRound(delay uint64) { + dkgDelayRound = delay +} + type configAccessor interface { Configuration(round uint64) *types.Config } diff --git a/vendor/vendor.json b/vendor/vendor.json index f424fa691..0955ef4dd 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -139,18 +139,18 @@ "versionExact": "dev" }, { - "checksumSHA1": "pnv6DaNi8uUdtYFqNdLjgpeCp6A=", + "checksumSHA1": "8EuKVkP1v/w5fRuuvUaXX5k/F+I=", "path": "github.com/dexon-foundation/dexon-consensus/common", - "revision": "2cf18fd299ea0fc270b213343314cab652cac271", - "revisionTime": "2019-02-18T10:44:35Z", + "revision": "d4b4c8a05e94f66c85e7b4238ae5947b26f13c40", + "revisionTime": "2019-03-04T10:30:03Z", "version": "single-chain", "versionExact": "single-chain" }, { - "checksumSHA1": "VI5+nQ9e4RiSE4JBOc92UU65eVY=", + "checksumSHA1": "RD3L08SnpZty2qH5mNERxYxB7gg=", "path": "github.com/dexon-foundation/dexon-consensus/core", - "revision": "2cf18fd299ea0fc270b213343314cab652cac271", - "revisionTime": "2019-02-18T10:44:35Z", + "revision": "d4b4c8a05e94f66c85e7b4238ae5947b26f13c40", + "revisionTime": "2019-03-04T10:30:03Z", "version": "single-chain", "versionExact": "single-chain" }, @@ -165,64 +165,64 @@ { "checksumSHA1": "tQSbYCu5P00lUhKsx3IbBZCuSLY=", "path": "github.com/dexon-foundation/dexon-consensus/core/crypto", - "revision": "2cf18fd299ea0fc270b213343314cab652cac271", - "revisionTime": "2019-02-18T10:44:35Z", + "revision": "d4b4c8a05e94f66c85e7b4238ae5947b26f13c40", + "revisionTime": "2019-03-04T10:30:03Z", "version": "single-chain", "versionExact": "single-chain" }, { "checksumSHA1": "Nlv7pi1DIBftY+r6CFP8LBIQA3U=", "path": "github.com/dexon-foundation/dexon-consensus/core/crypto/dkg", - "revision": "2cf18fd299ea0fc270b213343314cab652cac271", - "revisionTime": "2019-02-18T10:44:35Z", + "revision": "d4b4c8a05e94f66c85e7b4238ae5947b26f13c40", + "revisionTime": "2019-03-04T10:30:03Z", "version": "single-chain", "versionExact": "single-chain" }, { "checksumSHA1": "BhLKK8RveoLaeXc9UyUKMwQqchU=", "path": "github.com/dexon-foundation/dexon-consensus/core/crypto/ecdsa", - "revision": "2cf18fd299ea0fc270b213343314cab652cac271", - "revisionTime": "2019-02-18T10:44:35Z", + "revision": "d4b4c8a05e94f66c85e7b4238ae5947b26f13c40", + "revisionTime": "2019-03-04T10:30:03Z", "version": "single-chain", "versionExact": "single-chain" }, { "checksumSHA1": "zpuCdMT8MGsy4pLgHKpg/Wd4izU=", "path": "github.com/dexon-foundation/dexon-consensus/core/db", - "revision": "2cf18fd299ea0fc270b213343314cab652cac271", - "revisionTime": "2019-02-18T10:44:35Z", + "revision": "d4b4c8a05e94f66c85e7b4238ae5947b26f13c40", + "revisionTime": "2019-03-04T10:30:03Z", "version": "single-chain", "versionExact": "single-chain" }, { - "checksumSHA1": "KYpliqwnJejH8V/GqygKSiQETbo=", + "checksumSHA1": "T9TNx0oUpaRdlbCuy7AvkK1eQ18=", "path": "github.com/dexon-foundation/dexon-consensus/core/syncer", - "revision": "2cf18fd299ea0fc270b213343314cab652cac271", - "revisionTime": "2019-02-18T10:44:35Z", + "revision": "d4b4c8a05e94f66c85e7b4238ae5947b26f13c40", + "revisionTime": "2019-03-04T10:30:03Z", "version": "single-chain", "versionExact": "single-chain" }, { - "checksumSHA1": "g9eEP7SxpQf10C3wCsNN/Hn9XK0=", + "checksumSHA1": "id8imcgp3SqYhIx0k3Chd0VZrUQ=", "path": "github.com/dexon-foundation/dexon-consensus/core/types", - "revision": "2cf18fd299ea0fc270b213343314cab652cac271", - "revisionTime": "2019-02-18T10:44:35Z", + "revision": "d4b4c8a05e94f66c85e7b4238ae5947b26f13c40", + "revisionTime": "2019-03-04T10:30:03Z", "version": "single-chain", "versionExact": "single-chain" }, { "checksumSHA1": "s28gYj+iji8oT7N7Su6HIFHMuwI=", "path": "github.com/dexon-foundation/dexon-consensus/core/types/dkg", - "revision": "2cf18fd299ea0fc270b213343314cab652cac271", - "revisionTime": "2019-02-18T10:44:35Z", + "revision": "d4b4c8a05e94f66c85e7b4238ae5947b26f13c40", + "revisionTime": "2019-03-04T10:30:03Z", "version": "single-chain", "versionExact": "single-chain" }, { - "checksumSHA1": "1z0CIfS1QRV9xoA9wrTyVUMmXjc=", + "checksumSHA1": "C8Q8sfOSs+FPw/5sPwAR04QFc3U=", "path": "github.com/dexon-foundation/dexon-consensus/core/utils", - "revision": "2cf18fd299ea0fc270b213343314cab652cac271", - "revisionTime": "2019-02-18T10:44:35Z", + "revision": "d4b4c8a05e94f66c85e7b4238ae5947b26f13c40", + "revisionTime": "2019-03-04T10:30:03Z", "version": "single-chain", "versionExact": "single-chain" }, |