diff options
Diffstat (limited to 'vendor/github.com/dexon-foundation/dexon-consensus')
12 files changed, 648 insertions, 111 deletions
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 88cc432ff..7b5effba8 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,7 +65,7 @@ func genValidLeader( } type agreementMgrConfig struct { - roundBasedConfig + utils.RoundBasedConfig notarySetSize uint32 lambdaBA time.Duration @@ -77,14 +77,14 @@ func (c *agreementMgrConfig) from( c.notarySetSize = config.NotarySetSize c.lambdaBA = config.LambdaBA c.crs = crs - c.setupRoundBasedFields(round, config) + 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) + c.from(prev.RoundID()+1, config, crs) + c.AppendTo(prev.RoundBasedConfig) return } @@ -356,7 +356,7 @@ Loop: setting.recv.isNotary = checkRound() // Run BA for this round. setting.recv.roundValue.Store(currentRound) - setting.recv.changeNotaryHeight = curConfig.roundEndHeight + setting.recv.changeNotaryHeight = curConfig.RoundEndHeight() setting.recv.restartNotary <- types.Position{ Round: setting.recv.round(), Height: math.MaxUint64, 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 b6c8b1da5..19a580b4f 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/blockchain.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/blockchain.go @@ -103,21 +103,21 @@ func (pb pendingBlockRecords) searchByPosition(p types.Position) ( } type blockChainConfig struct { - roundBasedConfig + utils.RoundBasedConfig minBlockInterval time.Duration } func (c *blockChainConfig) fromConfig(round uint64, config *types.Config) { c.minBlockInterval = config.MinBlockInterval - c.setupRoundBasedFields(round, config) + c.SetupRoundBasedFields(round, config) } func newBlockChainConfig(prev blockChainConfig, config *types.Config) ( c blockChainConfig) { c = blockChainConfig{} - c.fromConfig(prev.roundID+1, config) - c.setRoundBeginHeight(prev.roundEndHeight) + c.fromConfig(prev.RoundID()+1, config) + c.AppendTo(prev.RoundBasedConfig) return } @@ -145,14 +145,14 @@ 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 { - if initConfig.roundID != initBlock.Position.Round { + if initConfig.RoundID() != initBlock.Position.Round { panic(fmt.Errorf("incompatible config/block %s %d", - initBlock, initConfig.roundID)) + initBlock, initConfig.RoundID())) } } else { - if initConfig.roundID != 0 { + if initConfig.RoundID() != 0 { panic(fmt.Errorf("genesis config should from round 0 %d", - initConfig.roundID)) + initConfig.RoundID())) } } return &blockChain{ @@ -235,7 +235,7 @@ func (bc *blockChain) sanityCheck(b *types.Block) error { return ErrInvalidBlockHeight } tipConfig := bc.tipConfig() - if tipConfig.isLastBlock(bc.lastConfirmed) { + if tipConfig.IsLastBlock(bc.lastConfirmed) { if b.Position.Round != bc.lastConfirmed.Position.Round+1 { return ErrRoundNotSwitch } @@ -359,7 +359,7 @@ func (bc *blockChain) tipRound() uint64 { return 0 } offset, tipConfig := uint64(0), bc.tipConfig() - if tipConfig.isLastBlock(bc.lastConfirmed) { + if tipConfig.IsLastBlock(bc.lastConfirmed) { offset++ } return bc.lastConfirmed.Position.Round + offset @@ -499,12 +499,12 @@ func (bc *blockChain) checkIfBlocksConfirmed() { } func (bc *blockChain) purgeConfig() { - for bc.configs[0].roundID < bc.lastConfirmed.Position.Round { + for bc.configs[0].RoundID() < bc.lastConfirmed.Position.Round { bc.configs = bc.configs[1:] } - if bc.configs[0].roundID != bc.lastConfirmed.Position.Round { + if bc.configs[0].RoundID() != bc.lastConfirmed.Position.Round { panic(fmt.Errorf("mismatched tip config: %d %d", - bc.configs[0].roundID, bc.lastConfirmed.Position.Round)) + bc.configs[0].RoundID(), bc.lastConfirmed.Position.Round)) } } @@ -556,7 +556,7 @@ func (bc *blockChain) prepareBlock(position types.Position, b, err = nil, ErrNotFollowTipPosition return } - if tipConfig.isLastBlock(tip) { + if tipConfig.IsLastBlock(tip) { if tip.Position.Round+1 != position.Round { b, err = nil, ErrRoundNotSwitch return @@ -610,9 +610,9 @@ func (bc *blockChain) tipConfig() blockChainConfig { if bc.lastConfirmed == nil { panic(fmt.Errorf("attempting to access config without tip")) } - if bc.lastConfirmed.Position.Round != bc.configs[0].roundID { + if bc.lastConfirmed.Position.Round != bc.configs[0].RoundID() { panic(fmt.Errorf("inconsist config and tip: %d %d", - bc.lastConfirmed.Position.Round, bc.configs[0].roundID)) + bc.lastConfirmed.Position.Round, bc.configs[0].RoundID())) } return bc.configs[0] } diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/configuration-chain.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/configuration-chain.go index 9e8df6bd1..9214cd97d 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/configuration-chain.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/configuration-chain.go @@ -106,6 +106,12 @@ func (cc *configurationChain) registerDKG(round uint64, threshold int) { cc.recv, round, threshold) + err = cc.db.PutOrUpdateDKGMasterPrivateShares(round, *cc.dkg.prvShares) + if err != nil { + cc.logger.Error("Error put or update DKG master private shares", "error", + err) + return + } go func() { ticker := newTicker(cc.gov, round, TickerDKG) defer ticker.Stop() @@ -287,33 +293,58 @@ func (cc *configurationChain) getDKGInfo( } func (cc *configurationChain) recoverDKGInfo(round uint64) error { - cc.dkgResult.Lock() - defer cc.dkgResult.Unlock() - _, signerExists := cc.dkgSigner[round] - _, npksExists := cc.npks[round] + var npksExists, signerExists bool + func() { + cc.dkgResult.Lock() + defer cc.dkgResult.Unlock() + _, signerExists = cc.dkgSigner[round] + _, npksExists = cc.npks[round] + }() if signerExists && npksExists { return nil } if !cc.gov.IsDKGFinal(round) { return ErrDKGNotReady } - // Restore group public key. - npks, err := typesDKG.NewNodePublicKeys(round, - cc.gov.DKGMasterPublicKeys(round), - cc.gov.DKGComplaints(round), - utils.GetDKGThreshold( - utils.GetConfigWithPanic(cc.gov, round, cc.logger))) - if err != nil { - return err - } - // Check if we have private shares in DB. - prvKey, err := cc.db.GetDKGPrivateKey(round) - if err != nil { - return err - } - cc.npks[round] = npks - cc.dkgSigner[round] = &dkgShareSecret{ - privateKey: &prvKey, + + if !npksExists { + threshold := utils.GetDKGThreshold( + utils.GetConfigWithPanic(cc.gov, round, cc.logger)) + // Restore group public key. + cc.logger.Debug("Calling Governance.DKGMasterPublicKeys for recoverDKGInfo", + "round", round) + cc.logger.Debug("Calling Governance.DKGComplaints for recoverDKGInfo", + "round", round) + npks, err := typesDKG.NewNodePublicKeys(round, + cc.gov.DKGMasterPublicKeys(round), + cc.gov.DKGComplaints(round), + threshold) + if err != nil { + cc.logger.Warn("Failed to create DKGGroupPublicKey", + "round", round, "error", err) + return err + } + func() { + cc.dkgResult.Lock() + defer cc.dkgResult.Unlock() + cc.npks[round] = npks + }() + } + if !signerExists { + // Check if we have private shares in DB. + prvKey, err := cc.db.GetDKGPrivateKey(round) + if err != nil { + cc.logger.Warn("Failed to create DKGPrivateKey", + "round", round, "error", err) + return err + } + func() { + cc.dkgResult.Lock() + defer cc.dkgResult.Unlock() + cc.dkgSigner[round] = &dkgShareSecret{ + privateKey: &prvKey, + } + }() } return nil } 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 ccb332684..4201cbcc2 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/consensus.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/consensus.go @@ -538,6 +538,11 @@ func newConsensusForRound( logger: logger, } cfgModule := newConfigurationChain(ID, recv, gov, nodeSetCache, db, logger) + dkg, err := recoverDKGProtocol(ID, recv, initRound, utils.GetDKGThreshold(initConfig), db) + if err != nil { + panic(err) + } + cfgModule.dkg = dkg recv.cfgModule = cfgModule appModule := app if usingNonBlocking { @@ -545,7 +550,7 @@ func newConsensusForRound( } bcConfig := blockChainConfig{} bcConfig.fromConfig(initRound, initConfig) - bcConfig.setRoundBeginHeight(initRoundBeginHeight) + bcConfig.SetRoundBeginHeight(initRoundBeginHeight) bcModule := newBlockChain(ID, dMoment, initBlock, bcConfig, appModule, NewTSigVerifierCache(gov, 7), signer, logger) // Construct Consensus instance. @@ -573,8 +578,7 @@ func newConsensusForRound( con.ctx, con.ctxCancel = context.WithCancel(context.Background()) baConfig := agreementMgrConfig{} baConfig.from(initRound, initConfig, initCRS) - baConfig.setRoundBeginHeight(initRoundBeginHeight) - var err error + baConfig.SetRoundBeginHeight(initRoundBeginHeight) con.baMgr, err = newAgreementMgr(con, initRound, baConfig) if err != nil { panic(err) @@ -845,6 +849,10 @@ func (con *Consensus) initialRound( con.logger.Warn("Failed to update nodeSetCache", "round", round+1, "error", err) } + if _, _, err := con.bcModule.vGetter.UpdateAndGet(round + 1); err != nil { + con.logger.Warn("Failed to update tsigVerifierCache", + "round", round+1, "error", err) + } }() }) } diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/crypto/dkg/dkg.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/crypto/dkg/dkg.go index f6b3e0e1b..425d96b95 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/crypto/dkg/dkg.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/crypto/dkg/dkg.go @@ -131,6 +131,33 @@ func (prvs *PrivateKeyShares) Equal(other *PrivateKeyShares) bool { return true } +// EncodeRLP implements rlp.Encoder +func (prvs *PrivateKeyShares) EncodeRLP(w io.Writer) error { + mpks := make([][]byte, len(prvs.masterPrivateKey)) + for i, m := range prvs.masterPrivateKey { + mpks[i] = m.GetLittleEndian() + } + return rlp.Encode(w, mpks) +} + +// DecodeRLP implements rlp.Decoder +func (prvs *PrivateKeyShares) DecodeRLP(s *rlp.Stream) error { + var dec [][]byte + if err := s.Decode(&dec); err != nil { + return err + } + + for _, k := range dec { + var key bls.SecretKey + if err := key.SetLittleEndian(k); err != nil { + return err + } + prvs.masterPrivateKey = append(prvs.masterPrivateKey, key) + } + + return nil +} + // PublicKeyShares represents a public key shares for DKG protocol. type PublicKeyShares struct { shareCaches []PublicKey diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/db/interfaces.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/db/interfaces.go index ebbbbd475..e95861112 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/db/interfaces.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/db/interfaces.go @@ -50,6 +50,12 @@ var ( // ErrDKGPrivateKeyDoesNotExist raised when the DKG private key of the // requested round does not exists. ErrDKGPrivateKeyDoesNotExist = errors.New("dkg private key does not exists") + // ErrDKGMasterPrivateSharesExists raised when attempting to save DKG master private shares + // that already saved. + ErrDKGMasterPrivateSharesExists = errors.New("dkg master private shares exists") + // ErrDKGMasterPrivateSharesDoesNotExist raised when the DKG master private shares of the + // requested round does not exists. + ErrDKGMasterPrivateSharesDoesNotExist = errors.New("dkg master private shares does not exists") ) // Database is the interface for a Database. @@ -76,6 +82,7 @@ type Reader interface { // DKG Private Key related methods. HasDKGPrivateKey(round uint64) (bool, error) GetDKGPrivateKey(round uint64) (dkg.PrivateKey, error) + GetDKGMasterPrivateShares(round uint64) (shares dkg.PrivateKeyShares, err error) } // Writer defines the interface for writing blocks into DB. @@ -84,6 +91,7 @@ type Writer interface { PutBlock(block types.Block) error PutCompactionChainTipInfo(common.Hash, uint64) error PutDKGPrivateKey(uint64, dkg.PrivateKey) error + PutOrUpdateDKGMasterPrivateShares(round uint64, shares dkg.PrivateKeyShares) error } // BlockIterator defines an iterator on blocks hold diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/db/level-db.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/db/level-db.go index 1fe29fa10..efa1fecbc 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/db/level-db.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/db/level-db.go @@ -29,9 +29,10 @@ import ( ) var ( - blockKeyPrefix = []byte("b-") - compactionChainTipInfoKey = []byte("cc-tip") - dkgPrivateKeyKeyPrefix = []byte("dkg-prvs") + blockKeyPrefix = []byte("b-") + compactionChainTipInfoKey = []byte("cc-tip") + dkgPrivateKeyKeyPrefix = []byte("dkg-prvs") + dkgMasterPrivateSharesPrefix = []byte("dkg-master-private-shares") ) type compactionChainTipInfo struct { @@ -188,6 +189,11 @@ func (lvl *LevelDBBackedDB) HasDKGPrivateKey(round uint64) (bool, error) { return lvl.db.Has(lvl.getDKGPrivateKeyKey(round), nil) } +// HasDKGMasterPrivateSharesKey check existence of DKG master private shares of one round. +func (lvl *LevelDBBackedDB) HasDKGMasterPrivateSharesKey(round uint64) (bool, error) { + return lvl.db.Has(lvl.getDKGMasterPrivateSharesKey(round), nil) +} + // GetDKGPrivateKey get DKG private key of one round. func (lvl *LevelDBBackedDB) GetDKGPrivateKey(round uint64) ( prv dkg.PrivateKey, err error) { @@ -221,6 +227,32 @@ func (lvl *LevelDBBackedDB) PutDKGPrivateKey( lvl.getDKGPrivateKeyKey(round), marshaled, nil) } +// GetDKGMasterPrivateShares get DKG master private shares of one round. +func (lvl *LevelDBBackedDB) GetDKGMasterPrivateShares(round uint64) ( + shares dkg.PrivateKeyShares, err error) { + queried, err := lvl.db.Get(lvl.getDKGMasterPrivateSharesKey(round), nil) + if err != nil { + if err == leveldb.ErrNotFound { + err = ErrDKGMasterPrivateSharesDoesNotExist + } + return + } + + err = rlp.DecodeBytes(queried, &shares) + return +} + +// PutOrUpdateDKGMasterPrivateShares save DKG master private shares of one round. +func (lvl *LevelDBBackedDB) PutOrUpdateDKGMasterPrivateShares( + round uint64, shares dkg.PrivateKeyShares) error { + marshaled, err := rlp.EncodeToBytes(&shares) + if err != nil { + return err + } + return lvl.db.Put( + lvl.getDKGMasterPrivateSharesKey(round), marshaled, nil) +} + func (lvl *LevelDBBackedDB) getBlockKey(hash common.Hash) (ret []byte) { ret = make([]byte, len(blockKeyPrefix)+len(hash[:])) copy(ret, blockKeyPrefix) @@ -236,3 +268,10 @@ func (lvl *LevelDBBackedDB) getDKGPrivateKeyKey( ret[len(dkgPrivateKeyKeyPrefix):], round) return } + +func (lvl *LevelDBBackedDB) getDKGMasterPrivateSharesKey(round uint64) (ret []byte) { + ret = make([]byte, len(dkgMasterPrivateSharesPrefix)+8) + copy(ret, dkgMasterPrivateSharesPrefix) + binary.LittleEndian.PutUint64(ret[len(dkgMasterPrivateSharesPrefix):], round) + return +} diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/db/memory.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/db/memory.go index 4bc08e704..548e41e90 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/db/memory.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/db/memory.go @@ -42,24 +42,27 @@ func (seq *blockSeqIterator) NextBlock() (types.Block, error) { // MemBackedDB is a memory backed DB implementation. type MemBackedDB struct { - blocksLock sync.RWMutex - blockHashSequence common.Hashes - blocksByHash map[common.Hash]*types.Block - compactionChainTipLock sync.RWMutex - compactionChainTipHash common.Hash - compactionChainTipHeight uint64 - dkgPrivateKeysLock sync.RWMutex - dkgPrivateKeys map[uint64]*dkg.PrivateKey - persistantFilePath string + blocksLock sync.RWMutex + blockHashSequence common.Hashes + blocksByHash map[common.Hash]*types.Block + compactionChainTipLock sync.RWMutex + compactionChainTipHash common.Hash + compactionChainTipHeight uint64 + dkgPrivateKeysLock sync.RWMutex + dkgPrivateKeys map[uint64]*dkg.PrivateKey + dkgMasterPrivateSharesLock sync.RWMutex + dkgMasterPrivateShares map[uint64]*dkg.PrivateKeyShares + persistantFilePath string } // NewMemBackedDB initialize a memory-backed database. func NewMemBackedDB(persistantFilePath ...string) ( dbInst *MemBackedDB, err error) { dbInst = &MemBackedDB{ - blockHashSequence: common.Hashes{}, - blocksByHash: make(map[common.Hash]*types.Block), - dkgPrivateKeys: make(map[uint64]*dkg.PrivateKey), + blockHashSequence: common.Hashes{}, + blocksByHash: make(map[common.Hash]*types.Block), + dkgPrivateKeys: make(map[uint64]*dkg.PrivateKey), + dkgMasterPrivateShares: make(map[uint64]*dkg.PrivateKeyShares), } if len(persistantFilePath) == 0 || len(persistantFilePath[0]) == 0 { return @@ -197,6 +200,34 @@ func (m *MemBackedDB) PutDKGPrivateKey( return nil } +// HasDKGMasterPrivateShares check existence of DKG master private shares of one round. +func (m *MemBackedDB) HasDKGMasterPrivateShares(round uint64) (bool, error) { + m.dkgMasterPrivateSharesLock.RLock() + defer m.dkgMasterPrivateSharesLock.RUnlock() + _, exists := m.dkgMasterPrivateShares[round] + return exists, nil +} + +// GetDKGMasterPrivateShares get DKG master private shares of one round. +func (m *MemBackedDB) GetDKGMasterPrivateShares(round uint64) ( + dkg.PrivateKeyShares, error) { + m.dkgMasterPrivateSharesLock.RLock() + defer m.dkgMasterPrivateSharesLock.RUnlock() + if shares, exists := m.dkgMasterPrivateShares[round]; exists { + return *shares, nil + } + return dkg.PrivateKeyShares{}, ErrDKGMasterPrivateSharesDoesNotExist +} + +// PutOrUpdateDKGMasterPrivateShares save DKG master private shares of one round. +func (m *MemBackedDB) PutOrUpdateDKGMasterPrivateShares( + round uint64, shares dkg.PrivateKeyShares) error { + m.dkgMasterPrivateSharesLock.Lock() + defer m.dkgMasterPrivateSharesLock.Unlock() + m.dkgMasterPrivateShares[round] = &shares + return nil +} + // Close implement Closer interface, which would release allocated resource. func (m *MemBackedDB) Close() (err error) { // Save internal state to a pretty-print json file. It's a temporary way diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/dkg-tsig-protocol.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/dkg-tsig-protocol.go index 6c812db73..259d07a4b 100644 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/dkg-tsig-protocol.go +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/dkg-tsig-protocol.go @@ -24,6 +24,7 @@ import ( "github.com/dexon-foundation/dexon-consensus/common" "github.com/dexon-foundation/dexon-consensus/core/crypto" "github.com/dexon-foundation/dexon-consensus/core/crypto/dkg" + "github.com/dexon-foundation/dexon-consensus/core/db" "github.com/dexon-foundation/dexon-consensus/core/types" typesDKG "github.com/dexon-foundation/dexon-consensus/core/types/dkg" "github.com/dexon-foundation/dexon-consensus/core/utils" @@ -164,6 +165,35 @@ func newDKGProtocol( } } +func recoverDKGProtocol( + ID types.NodeID, + recv dkgReceiver, + round uint64, + threshold int, + coreDB db.Database) (*dkgProtocol, error) { + shares, err := coreDB.GetDKGMasterPrivateShares(round) + if err != nil { + if err == db.ErrDKGMasterPrivateSharesDoesNotExist { + return nil, nil + } + + return nil, err + } + return &dkgProtocol{ + ID: ID, + recv: recv, + round: round, + threshold: threshold, + idMap: make(map[types.NodeID]dkg.ID), + mpkMap: make(map[types.NodeID]*dkg.PublicKeyShares), + masterPrivateShare: &shares, + prvShares: dkg.NewEmptyPrivateKeyShares(), + prvSharesReceived: make(map[types.NodeID]struct{}), + nodeComplained: make(map[types.NodeID]struct{}), + antiComplaintReceived: make(map[types.NodeID]map[types.NodeID]struct{}), + }, nil +} + func (d *dkgProtocol) processMasterPublicKeys( mpks []*typesDKG.MasterPublicKey) (err error) { d.idMap = make(map[types.NodeID]dkg.ID, len(mpks)) 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 deleted file mode 100644 index 4f3a4cbf9..000000000 --- a/vendor/github.com/dexon-foundation/dexon-consensus/core/round-based-config.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2018 The dexon-consensus Authors -// This file is part of the dexon-consensus library. -// -// The dexon-consensus library is free software: you can redistribute it -// and/or modify it under the terms of the GNU Lesser General Public License as -// published by the Free Software Foundation, either version 3 of the License, -// or (at your option) any later version. -// -// The dexon-consensus library is distributed in the hope that it will be -// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser -// General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the dexon-consensus library. If not, see -// <http://www.gnu.org/licenses/>. - -package core - -import ( - "fmt" - - "github.com/dexon-foundation/dexon-consensus/core/types" -) - -type roundBasedConfig struct { - roundID uint64 - roundBeginHeight uint64 - roundEndHeight uint64 - roundInterval uint64 -} - -func (config *roundBasedConfig) setupRoundBasedFields( - roundID uint64, cfg *types.Config) { - config.roundID = roundID - config.roundInterval = cfg.RoundLength -} - -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 { - 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/utils/round-based-config.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/round-based-config.go new file mode 100644 index 000000000..3219a1379 --- /dev/null +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/round-based-config.go @@ -0,0 +1,112 @@ +// Copyright 2018 The dexon-consensus Authors +// This file is part of the dexon-consensus library. +// +// The dexon-consensus library is free software: you can redistribute it +// and/or modify it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The dexon-consensus library is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the dexon-consensus library. If not, see +// <http://www.gnu.org/licenses/>. + +package utils + +import ( + "fmt" + + "github.com/dexon-foundation/dexon-consensus/core/types" +) + +// RoundBasedConfig is based config for rounds and provide boundary checking +// for rounds. +type RoundBasedConfig struct { + roundID uint64 + roundBeginHeight uint64 + roundEndHeight uint64 + roundLength uint64 +} + +// SetupRoundBasedFields setup round based fields, including round ID, the +// length of rounds. +func (c *RoundBasedConfig) SetupRoundBasedFields( + roundID uint64, cfg *types.Config) { + if c.roundLength > 0 { + panic(fmt.Errorf("duplicated set round based fields: %d", + c.roundLength)) + } + c.roundID = roundID + c.roundLength = cfg.RoundLength +} + +// SetRoundBeginHeight gives the beginning height for the initial round provided +// when constructed. +func (c *RoundBasedConfig) SetRoundBeginHeight(begin uint64) { + if c.roundBeginHeight != 0 { + panic(fmt.Errorf("duplicated set round begin height: %d", + c.roundBeginHeight)) + } + c.roundBeginHeight = begin + c.roundEndHeight = begin + c.roundLength +} + +// IsLastBlock checks if a block is the last block of this round. +func (c *RoundBasedConfig) IsLastBlock(b *types.Block) bool { + if b.Position.Round != c.roundID { + panic(fmt.Errorf("attempt to compare by different round: %s, %d", + b, c.roundID)) + } + return b.Position.Height+1 == c.roundEndHeight +} + +// ExtendLength extends round ending height by the length of current round. +func (c *RoundBasedConfig) ExtendLength() { + c.roundEndHeight += c.roundLength +} + +// Contains checks if a block height is in this round. +func (c *RoundBasedConfig) Contains(h uint64) bool { + return c.roundBeginHeight <= h && c.roundEndHeight > h +} + +// RoundID returns the round ID of this config. +func (c *RoundBasedConfig) RoundID() uint64 { + if c.roundLength == 0 { + panic(fmt.Errorf("config is not initialized: %d", c.roundID)) + } + return c.roundID +} + +// RoundEndHeight returns next checkpoint to varify if this round is ended. +func (c *RoundBasedConfig) RoundEndHeight() uint64 { + if c.roundLength == 0 { + panic(fmt.Errorf("config is not initialized: %d", c.roundID)) + } + return c.roundEndHeight +} + +// AppendTo a config in previous round. +func (c *RoundBasedConfig) AppendTo(other RoundBasedConfig) { + if c.roundID != other.roundID+1 { + panic(fmt.Errorf("round IDs of configs not continuous: %d %d", + c.roundID, other.roundID)) + } + c.SetRoundBeginHeight(other.roundEndHeight) +} + +// LastPeriodBeginHeight returns the begin height of last period. For example, +// if a round is extended twice, then the return from this method is: +// +// begin + 2 * roundLength - roundLength +// +func (c *RoundBasedConfig) LastPeriodBeginHeight() uint64 { + if c.roundLength == 0 { + panic(fmt.Errorf("config is not initialized: %d", c.roundID)) + } + return c.roundEndHeight - c.roundLength +} diff --git a/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/round-event.go b/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/round-event.go new file mode 100644 index 000000000..bab1d32d2 --- /dev/null +++ b/vendor/github.com/dexon-foundation/dexon-consensus/core/utils/round-event.go @@ -0,0 +1,302 @@ +// Copyright 2019 The dexon-consensus Authors +// This file is part of the dexon-consensus library. +// +// The dexon-consensus library is free software: you can redistribute it +// and/or modify it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation, either version 3 of the License, +// or (at your option) any later version. +// +// The dexon-consensus library is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser +// General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the dexon-consensus library. If not, see +// <http://www.gnu.org/licenses/>. + +package utils + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/dexon-foundation/dexon-consensus/common" + "github.com/dexon-foundation/dexon-consensus/core/types" + typesDKG "github.com/dexon-foundation/dexon-consensus/core/types/dkg" +) + +// ErrUnmatchedBlockHeightWithGov is for invalid parameters for NewRoundEvent. +type ErrUnmatchedBlockHeightWithGov struct { + round uint64 + reset uint64 + blockHeight uint64 +} + +func (e ErrUnmatchedBlockHeightWithGov) Error() string { + return fmt.Sprintf("unsynced block height and gov: round:%d reset:%d h:%d", + e.round, e.reset, e.blockHeight) +} + +// RoundEventParam defines the parameters passed to event handlers of +// RoundEvent. +type RoundEventParam struct { + // 'Round' of next checkpoint, might be identical to previous checkpoint. + Round uint64 + // the count of reset DKG for 'Round+1'. + Reset uint64 + // the begin block height of this event, the end block height of this event + // would be BeginHeight + config.RoundLength. + BeginHeight uint64 + // The configuration for 'Round'. + Config *types.Config + // The CRS for 'Round'. + CRS common.Hash +} + +// NextRoundCheckpoint returns the height to check if the next round is ready. +func (e RoundEventParam) NextRoundCheckpoint() uint64 { + return e.BeginHeight + e.Config.RoundLength*8/10 +} + +// roundEventFn defines the fingerprint of handlers of round events. +type roundEventFn func([]RoundEventParam) + +// governanceAccessor is a subset of core.Governance to break the dependency +// between core and utils package. +type governanceAccessor interface { + // Configuration returns the configuration at a given round. + // Return the genesis configuration if round == 0. + Configuration(round uint64) *types.Config + + // CRS returns the CRS for a given round. + // Return the genesis CRS if round == 0. + CRS(round uint64) common.Hash + + // DKGComplaints gets all the DKGComplaints of round. + DKGComplaints(round uint64) []*typesDKG.Complaint + + // DKGMasterPublicKeys gets all the DKGMasterPublicKey of round. + DKGMasterPublicKeys(round uint64) []*typesDKG.MasterPublicKey + + // IsDKGFinal checks if DKG is final. + IsDKGFinal(round uint64) bool + + // DKGResetCount returns the reset count for DKG of given round. + DKGResetCount(round uint64) uint64 +} + +// RoundEvent would be triggered when either: +// - the next DKG set setup is ready. +// - the next DKG set setup is failed, and previous DKG set already reset the +// CRS. +type RoundEvent struct { + gov governanceAccessor + logger common.Logger + lock sync.Mutex + handlers []roundEventFn + config RoundBasedConfig + lastTriggeredRound uint64 + lastTriggeredResetCount uint64 + roundShift uint64 + ctx context.Context + ctxCancel context.CancelFunc +} + +// NewRoundEvent creates an RoundEvent instance. +func NewRoundEvent(parentCtx context.Context, gov governanceAccessor, + logger common.Logger, initRound uint64, + initRoundBeginHeight, initBlockHeight uint64, + roundShift uint64) (*RoundEvent, error) { + // We need to generate valid ending block height of this round (taken + // DKG reset count into consideration). + e := &RoundEvent{ + gov: gov, + logger: logger, + lastTriggeredRound: initRound, + roundShift: roundShift, + } + e.ctx, e.ctxCancel = context.WithCancel(parentCtx) + e.config = RoundBasedConfig{} + e.config.SetupRoundBasedFields(initRound, GetConfigWithPanic( + gov, initRound, logger)) + e.config.SetRoundBeginHeight(initRoundBeginHeight) + // Make sure the DKG reset count in current governance can cover the initial + // block height. + resetCount := gov.DKGResetCount(initRound + 1) + remains := resetCount + for ; resetCount > 0 && !e.config.Contains(initBlockHeight); remains-- { + e.config.ExtendLength() + } + if !e.config.Contains(initBlockHeight) { + return nil, ErrUnmatchedBlockHeightWithGov{ + round: initRound, + reset: resetCount, + blockHeight: initBlockHeight, + } + } + e.lastTriggeredResetCount = resetCount - remains + return e, nil +} + +// Register a handler to be called when new round is confirmed or new DKG reset +// is detected. +func (e *RoundEvent) Register(h roundEventFn) { + e.lock.Lock() + defer e.lock.Unlock() + e.handlers = append(e.handlers, h) +} + +// ValidateNextRound validate if the DKG set for next round is ready to go or +// failed to setup, all registered handlers would be called once some decision +// is made on chain. +// +// This method would block until at least one event is triggered. Multiple +// trigger in one call is possible. +func (e *RoundEvent) ValidateNextRound(blockHeight uint64) { + // To make triggers continuous and sequential, the next validation should + // wait for previous one finishing. That's why I use mutex here directly. + var events []RoundEventParam + e.lock.Lock() + defer e.lock.Unlock() + e.logger.Info("ValidateNextRound", + "height", blockHeight, + "round", e.lastTriggeredRound, + "count", e.lastTriggeredResetCount) + defer func() { + if len(events) == 0 { + return + } + for _, h := range e.handlers { + // To make sure all handlers receive triggers sequentially, we can't + // raise go routines here. + h(events) + } + }() + startRound := e.lastTriggeredRound + for { + var ( + dkgFailed, triggered bool + param RoundEventParam + beginHeight = blockHeight + ) + for { + param, dkgFailed, triggered = e.check(beginHeight, startRound, + dkgFailed) + if !triggered { + break + } + events = append(events, param) + beginHeight = param.BeginHeight + } + if len(events) > 0 { + break + } + select { + case <-e.ctx.Done(): + return + case <-time.After(500 * time.Millisecond): + } + } +} + +func (e *RoundEvent) check(blockHeight, startRound uint64, lastDKGCheck bool) ( + param RoundEventParam, dkgFailed bool, triggered bool) { + defer func() { + if !triggered { + return + } + // A simple assertion to make sure we didn't pick the wrong round. + if e.config.RoundID() != e.lastTriggeredRound { + panic(fmt.Errorf("triggered round not matched: %d, %d", + e.config.RoundID(), e.lastTriggeredRound)) + } + param.Round = e.lastTriggeredRound + param.Reset = e.lastTriggeredResetCount + param.BeginHeight = e.config.LastPeriodBeginHeight() + param.CRS = GetCRSWithPanic(e.gov, e.lastTriggeredRound, e.logger) + param.Config = GetConfigWithPanic(e.gov, e.lastTriggeredRound, e.logger) + e.logger.Info("new RoundEvent triggered", + "round", e.lastTriggeredRound, + "reset", e.lastTriggeredResetCount, + "begin-height", e.config.LastPeriodBeginHeight(), + "crs", param.CRS.String()[:6], + ) + }() + // Make sure current last config covers the blockHeight. + if !e.config.Contains(blockHeight) { + panic(ErrUnmatchedBlockHeightWithGov{ + round: e.lastTriggeredRound, + reset: e.lastTriggeredResetCount, + blockHeight: blockHeight, + }) + } + nextRound := e.lastTriggeredRound + 1 + if nextRound >= startRound+e.roundShift { + // Avoid access configuration newer than last confirmed one over + // 'roundShift' rounds. Fullnode might crash if we access it before it + // knows. + return + } + nextCfg := GetConfigWithPanic(e.gov, nextRound, e.logger) + resetCount := e.gov.DKGResetCount(nextRound) + if resetCount > e.lastTriggeredResetCount { + e.lastTriggeredResetCount++ + e.config.ExtendLength() + triggered = true + return + } + if lastDKGCheck { + // We know that DKG already failed, now wait for the DKG set from + // previous round to reset DKG and don't have to reconstruct the + // group public key again. + dkgFailed = true + return + } + if nextRound >= dkgDelayRound { + if !e.gov.IsDKGFinal(nextRound) { + e.logger.Debug("DKG is not final, waiting for DKG reset", + "round", nextRound, + "reset", e.lastTriggeredResetCount) + return + } + if _, err := typesDKG.NewGroupPublicKey( + nextRound, + e.gov.DKGMasterPublicKeys(nextRound), + e.gov.DKGComplaints(nextRound), + GetDKGThreshold(nextCfg)); err != nil { + e.logger.Debug( + "group public key setup failed, waiting for DKG reset", + "round", nextRound, + "reset", e.lastTriggeredResetCount) + dkgFailed = true + return + } + } + // The DKG set for next round is well prepared. + e.lastTriggeredRound = nextRound + e.lastTriggeredResetCount = 0 + rCfg := RoundBasedConfig{} + rCfg.SetupRoundBasedFields(nextRound, nextCfg) + rCfg.AppendTo(e.config) + e.config = rCfg + triggered = true + return +} + +// Stop the event source and block until last trigger returns. +func (e *RoundEvent) Stop() { + e.ctxCancel() +} + +// LastPeriod returns block height related info of the last period, including +// begin height and round length. +func (e *RoundEvent) LastPeriod() (begin uint64, length uint64) { + e.lock.Lock() + defer e.lock.Unlock() + begin = e.config.LastPeriodBeginHeight() + length = e.config.RoundEndHeight() - e.config.LastPeriodBeginHeight() + return +} |