From f5f34f81f8f3149adb002c65a7cc0cfa1244f77d Mon Sep 17 00:00:00 2001 From: Mission Liao Date: Wed, 3 Oct 2018 17:48:56 +0800 Subject: core: publish round based config (#165) --- core/consensus-timestamp.go | 23 +++++++++++++++++-- core/consensus-timestamp_test.go | 10 +++++---- core/consensus.go | 2 +- core/lattice.go | 48 +++++++++++++++++++++++++++++++++++++++- core/lattice_test.go | 21 +++++++++++++----- core/test/governance.go | 1 - core/total-ordering.go | 41 +++++++++++++++++++++++++++++++--- core/total-ordering_test.go | 27 +++++++++++++--------- core/types/config.go | 4 ---- 9 files changed, 145 insertions(+), 32 deletions(-) (limited to 'core') diff --git a/core/consensus-timestamp.go b/core/consensus-timestamp.go index c43ca82..9188c02 100644 --- a/core/consensus-timestamp.go +++ b/core/consensus-timestamp.go @@ -27,6 +27,10 @@ import ( // consensusTimestamp is for Concensus Timestamp Algorithm. type consensusTimestamp struct { chainTimestamps []time.Time + + // This part keeps configs for each round. + numChainsForRounds []uint32 + minRound uint64 } var ( @@ -36,8 +40,23 @@ var ( ) // newConsensusTimestamp create timestamper object. -func newConsensusTimestamp() *consensusTimestamp { - return &consensusTimestamp{} +func newConsensusTimestamp(round uint64, numChains uint32) *consensusTimestamp { + return &consensusTimestamp{ + numChainsForRounds: []uint32{numChains}, + minRound: round, + } +} + +// appendConfig appends a configuration for upcoming round. When you append +// a config for round R, next time you can only append the config for round R+1. +func (ct *consensusTimestamp) appendConfig( + round uint64, config *types.Config) error { + + if round != ct.minRound+uint64(len(ct.numChainsForRounds)) { + return ErrRoundNotIncreasing + } + ct.numChainsForRounds = append(ct.numChainsForRounds, config.NumChains) + return nil } // ProcessBlocks is the entry function. diff --git a/core/consensus-timestamp_test.go b/core/consensus-timestamp_test.go index be90f41..ea85c38 100644 --- a/core/consensus-timestamp_test.go +++ b/core/consensus-timestamp_test.go @@ -90,11 +90,12 @@ func (s *ConsensusTimestampTest) extractTimestamps( // TestTimestampPartition verifies that processing segments of compatction chain // should have the same result as processing the whole chain at once. func (s *ConsensusTimestampTest) TestTimestampPartition() { + var round uint64 blockNums := []int{50, 100, 30} chainNum := 19 sigma := 100 * time.Millisecond totalTimestamps := make([]time.Time, 0) - ct := newConsensusTimestamp() + ct := newConsensusTimestamp(round, uint32(chainNum)) totalBlockNum := 0 for _, blockNum := range blockNums { totalBlockNum += blockNum @@ -110,7 +111,7 @@ func (s *ConsensusTimestampTest) TestTimestampPartition() { totalChain = append(totalChain, chain...) totalTimestamps = append(totalTimestamps, timestamps...) } - ct2 := newConsensusTimestamp() + ct2 := newConsensusTimestamp(round, uint32(chainNum)) err := ct2.processBlocks(totalChain) s.Require().NoError(err) timestamps2 := s.extractTimestamps(totalChain) @@ -118,9 +119,10 @@ func (s *ConsensusTimestampTest) TestTimestampPartition() { } func (s *ConsensusTimestampTest) TestTimestampIncrease() { + var round uint64 chainNum := 19 sigma := 100 * time.Millisecond - ct := newConsensusTimestamp() + ct := newConsensusTimestamp(round, uint32(chainNum)) chain := s.generateBlocksWithTimestamp(1000, chainNum, time.Second, sigma) err := ct.processBlocks(chain) s.Require().NoError(err) @@ -129,7 +131,7 @@ func (s *ConsensusTimestampTest) TestTimestampIncrease() { s.False(timestamps[i].Before(timestamps[i-1])) } // Test if the processBlocks is stable. - ct2 := newConsensusTimestamp() + ct2 := newConsensusTimestamp(round, uint32(chainNum)) ct2.processBlocks(chain) s.Require().NoError(err) timestamps2 := s.extractTimestamps(chain) diff --git a/core/consensus.go b/core/consensus.go index 51c4e35..4bcc116 100644 --- a/core/consensus.go +++ b/core/consensus.go @@ -216,7 +216,7 @@ func NewConsensus( // Setup nonblocking module. nbModule := newNonBlocking(app, debugApp) // Init lattice. - lattice := NewLattice(config, authModule, nbModule, nbModule, db) + lattice := NewLattice(round, config, authModule, nbModule, nbModule, db) // Init configuration chain. ID := types.NewNodeID(prv.PublicKey()) cfgModule := newConfigurationChain( diff --git a/core/lattice.go b/core/lattice.go index 2da32ba..8906c74 100644 --- a/core/lattice.go +++ b/core/lattice.go @@ -18,6 +18,7 @@ package core import ( + "errors" "fmt" "sync" "time" @@ -45,6 +46,11 @@ var ( ErrIncorrectBlockTime = fmt.Errorf("block timestampe is incorrect") ) +// Errors for method usage +var ( + ErrRoundNotIncreasing = errors.New("round not increasing") +) + // latticeData is a module for storing lattice. type latticeData struct { // chains stores chains' blocks and other info. @@ -57,10 +63,15 @@ type latticeData struct { // parent/child blocks. minBlockTimeInterval time.Duration maxBlockTimeInterval time.Duration + + // This stores configuration for each round. + numChainsForRounds []uint32 + minRound uint64 } // newLatticeData creates a new latticeData struct. func newLatticeData( + round uint64, chainNum uint32, minBlockTimeInterval time.Duration, maxBlockTimeInterval time.Duration) (data *latticeData) { @@ -69,6 +80,8 @@ func newLatticeData( blockByHash: make(map[common.Hash]*types.Block), minBlockTimeInterval: minBlockTimeInterval, maxBlockTimeInterval: maxBlockTimeInterval, + numChainsForRounds: []uint32{chainNum}, + minRound: round, } for i := range data.chains { data.chains[i] = &chainStatus{ @@ -289,6 +302,18 @@ func (data *latticeData) nextPosition(chainID uint32) types.Position { return data.chains[chainID].nextPosition() } +// appendConfig appends a configuration for upcoming round. When you append +// a config for round R, next time you can only append the config for round R+1. +func (data *latticeData) appendConfig( + round uint64, config *types.Config) (err error) { + + if round != data.minRound+uint64(len(data.numChainsForRounds)) { + return ErrRoundNotIncreasing + } + data.numChainsForRounds = append(data.numChainsForRounds, config.NumChains) + return nil +} + type chainStatus struct { // ID keeps the chainID of this chain status. ID uint32 @@ -400,12 +425,14 @@ type Lattice struct { // NewLattice constructs an Lattice instance. func NewLattice( + round uint64, cfg *types.Config, authModule *Authenticator, app Application, debug Debug, db blockdb.BlockDatabase) (s *Lattice) { data := newLatticeData( + round, cfg.NumChains, cfg.MinBlockInterval, cfg.MaxBlockInterval) @@ -418,10 +445,11 @@ func NewLattice( pool: newBlockPool(cfg.NumChains), data: data, toModule: newTotalOrdering( + round, uint64(cfg.K), uint64(float32(cfg.NumChains-1)*cfg.PhiRatio+1), cfg.NumChains), - ctModule: newConsensusTimestamp(), + ctModule: newConsensusTimestamp(round, cfg.NumChains), } return } @@ -553,3 +581,21 @@ func (s *Lattice) NextPosition(chainID uint32) types.Position { return s.data.nextPosition(chainID) } + +// AppendConfig add new configs for upcoming rounds. If you add a config for +// round R, next time you can only add the config for round R+1. +func (s *Lattice) AppendConfig(round uint64, config *types.Config) (err error) { + s.lock.Lock() + defer s.lock.Unlock() + + if err = s.data.appendConfig(round, config); err != nil { + return + } + if err = s.toModule.appendConfig(round, config); err != nil { + return + } + if err = s.ctModule.appendConfig(round, config); err != nil { + return + } + return +} diff --git a/core/lattice_test.go b/core/lattice_test.go index 2da17e4..1df4f4e 100644 --- a/core/lattice_test.go +++ b/core/lattice_test.go @@ -93,7 +93,10 @@ type LatticeTestSuite struct { func (s *LatticeTestSuite) newTestLatticeMgr( cfg *types.Config) *testLatticeMgr { - var req = s.Require() + var ( + req = s.Require() + round uint64 + ) // Setup private key. prvKey, err := ecdsa.NewPrivateKey() req.Nil(err) @@ -108,6 +111,7 @@ func (s *LatticeTestSuite) newTestLatticeMgr( app: app, db: db, lattice: NewLattice( + round, cfg, NewAuthenticator(prvKey), app, @@ -150,6 +154,7 @@ func (s *LatticeTestSuite) prepareGenesisBlock( func (s *LatticeTestSuite) genTestCase1() (data *latticeData) { // Create new reliableBroadcast instance with 4 validators var ( + round uint64 b *types.Block delivered []*types.Block h common.Hash @@ -158,7 +163,7 @@ func (s *LatticeTestSuite) genTestCase1() (data *latticeData) { err error ) - data = newLatticeData(chainNum, 2*time.Nanosecond, 1000*time.Second) + data = newLatticeData(round, chainNum, 2*time.Nanosecond, 1000*time.Second) // Add genesis blocks. for i := uint32(0); i < chainNum; i++ { b = s.prepareGenesisBlock(i) @@ -464,8 +469,9 @@ func (s *LatticeTestSuite) TestAreAllAcksInLattice() { func (s *LatticeTestSuite) TestRandomIntensiveAcking() { var ( + round uint64 chainNum uint32 = 19 - data = newLatticeData(chainNum, 0, 1000*time.Second) + data = newLatticeData(round, chainNum, 0, 1000*time.Second) req = s.Require() delivered []*types.Block extracted []*types.Block @@ -503,6 +509,7 @@ func (s *LatticeTestSuite) TestRandomIntensiveAcking() { func (s *LatticeTestSuite) TestRandomlyGeneratedBlocks() { var ( + round uint64 chainNum uint32 = 19 blockNum = 50 repeat = 20 @@ -528,7 +535,7 @@ func (s *LatticeTestSuite) TestRandomlyGeneratedBlocks() { revealedHashesAsString := map[string]struct{}{} deliveredHashesAsString := map[string]struct{}{} for i := 0; i < repeat; i++ { - data := newLatticeData(chainNum, 0, 1000*time.Second) + data := newLatticeData(round, chainNum, 0, 1000*time.Second) deliveredHashes := common.Hashes{} revealedHashes := common.Hashes{} revealer.Reset() @@ -604,12 +611,14 @@ func (s *LatticeTestSuite) TestRandomlyGeneratedBlocks() { func (s *LatticeTestSuite) TestPrepareBlock() { var ( + round uint64 chainNum uint32 = 4 req = s.Require() - data = newLatticeData(chainNum, 0, 3000*time.Second) minInterval = 50 * time.Millisecond delivered []*types.Block err error + data = newLatticeData( + round, chainNum, 0, 3000*time.Second) ) // Setup genesis blocks. b00 := s.prepareGenesisBlock(0) @@ -737,7 +746,7 @@ func (s *LatticeTestSuite) TestNextPosition() { s.Equal(data.nextPosition(0), types.Position{ChainID: 0, Height: 4}) // Test 'NextPosition' method when lattice is empty. - data = newLatticeData(4, 0, 1000*time.Second) + data = newLatticeData(0, 4, 0, 1000*time.Second) s.Equal(data.nextPosition(0), types.Position{ChainID: 0, Height: 0}) } diff --git a/core/test/governance.go b/core/test/governance.go index 5c7ae02..95cd6f7 100644 --- a/core/test/governance.go +++ b/core/test/governance.go @@ -90,7 +90,6 @@ func (g *Governance) NodeSet(_ uint64) ( // Configuration returns the configuration at a given block height. func (g *Governance) Configuration(_ uint64) *types.Config { return &types.Config{ - NumShards: 1, NumChains: uint32(len(g.privateKeys)), LambdaBA: g.lambdaBA, LambdaDKG: g.lambdaDKG, diff --git a/core/total-ordering.go b/core/total-ordering.go index 8bf9ad7..dd2d99c 100644 --- a/core/total-ordering.go +++ b/core/total-ordering.go @@ -39,7 +39,14 @@ var ( ErrChainIDNotRecognized = fmt.Errorf("chain ID not recognized") ) -// totalOrderinWinRecord caches which chains this candidate +// totalOrderingConfig is the configuration for total ordering. +type totalOrderingConfig struct { + k uint64 + phi uint64 + numChains uint32 +} + +// totalOrderingWinRecord caches which chains this candidate // wins another one based on their height vector. type totalOrderingWinRecord struct { wins []int8 @@ -579,10 +586,14 @@ type totalOrdering struct { // candidateChainIDs records chain ID of all candidates. candidateChainIDs []uint32 + + // configs keeps configuration for each round in continuous way. + configs []*totalOrderingConfig + minRound uint64 } -func newTotalOrdering(k, phi uint64, chainNum uint32) *totalOrdering { - return &totalOrdering{ +func newTotalOrdering(round, k, phi uint64, chainNum uint32) *totalOrdering { + to := &totalOrdering{ pendings: make(map[common.Hash]*types.Block), k: k, phi: phi, @@ -595,6 +606,30 @@ func newTotalOrdering(k, phi uint64, chainNum uint32) *totalOrdering { candidates: make([]*totalOrderingCandidateInfo, chainNum), candidateChainIDs: make([]uint32, 0, chainNum), } + to.configs = []*totalOrderingConfig{ + &totalOrderingConfig{ + k: k, + phi: phi, + numChains: chainNum, + }} + to.minRound = round + return to +} + +// appendConfig add new configs for upcoming rounds. If you add a config for +// round R, next time you can only add the config for round R+1. +func (to *totalOrdering) appendConfig( + round uint64, config *types.Config) error { + + if round != to.minRound+uint64(len(to.configs)) { + return ErrRoundNotIncreasing + } + to.configs = append(to.configs, &totalOrderingConfig{ + numChains: config.NumChains, + k: uint64(config.K), + phi: uint64(float32(config.NumChains-1)*config.PhiRatio + 1), + }) + return nil } // buildBlockRelation populates the acked according their acking relationships. diff --git a/core/total-ordering_test.go b/core/total-ordering_test.go index 478697b..347ef22 100644 --- a/core/total-ordering_test.go +++ b/core/total-ordering_test.go @@ -77,6 +77,7 @@ func (s *TotalOrderingTestSuite) TestBlockRelation() { // // The DAG used below is: // A <- B <- C + var round uint64 nodes := test.GenerateRandomNodeIDs(5) vID := nodes[0] blockA := s.genGenesisBlock(nodes, 0, common.Hashes{}) @@ -101,7 +102,7 @@ func (s *TotalOrderingTestSuite) TestBlockRelation() { Acks: common.NewSortedHashes(common.Hashes{blockB.Hash}), } - to := newTotalOrdering(1, 3, uint32(len(nodes))) + to := newTotalOrdering(round, 1, 3, uint32(len(nodes))) s.checkNotDeliver(to, blockA) s.checkNotDeliver(to, blockB) s.checkNotDeliver(to, blockC) @@ -241,6 +242,7 @@ func (s *TotalOrderingTestSuite) TestGrade() { func (s *TotalOrderingTestSuite) TestCycleDetection() { // Make sure we don't get hang by cycle from // block's acks. + var round uint64 nodes := test.GenerateRandomNodeIDs(5) // create blocks with cycles in acking relation. @@ -282,7 +284,7 @@ func (s *TotalOrderingTestSuite) TestCycleDetection() { b10.Acks = append(b10.Acks, b10.Hash) // Make sure we won't hang when cycle exists. - to := newTotalOrdering(1, 3, uint32(len(nodes))) + to := newTotalOrdering(round, 1, 3, uint32(len(nodes))) s.checkNotDeliver(to, b00) s.checkNotDeliver(to, b01) s.checkNotDeliver(to, b02) @@ -294,8 +296,9 @@ func (s *TotalOrderingTestSuite) TestCycleDetection() { } func (s *TotalOrderingTestSuite) TestNotValidDAGDetection() { + var round uint64 nodes := test.GenerateRandomNodeIDs(5) - to := newTotalOrdering(1, 3, uint32(len(nodes))) + to := newTotalOrdering(round, 1, 3, uint32(len(nodes))) b00 := s.genGenesisBlock(nodes, 0, common.Hashes{}) b01 := &types.Block{ @@ -326,8 +329,9 @@ func (s *TotalOrderingTestSuite) TestEarlyDeliver() { // A B // Even when B is not received, A should // be able to be delivered. + var round uint64 nodes := test.GenerateRandomNodeIDs(5) - to := newTotalOrdering(2, 3, uint32(len(nodes))) + to := newTotalOrdering(round, 2, 3, uint32(len(nodes))) genNextBlock := func(b *types.Block) *types.Block { return &types.Block{ ProposerID: b.ProposerID, @@ -431,8 +435,9 @@ func (s *TotalOrderingTestSuite) TestEarlyDeliver() { func (s *TotalOrderingTestSuite) TestBasicCaseForK2() { // It's a handcrafted test case. + var round uint64 nodes := test.GenerateRandomNodeIDs(5) - to := newTotalOrdering(2, 3, uint32(len(nodes))) + to := newTotalOrdering(round, 2, 3, uint32(len(nodes))) // Setup blocks. b00 := s.genGenesisBlock(nodes, 0, common.Hashes{}) b10 := s.genGenesisBlock(nodes, 1, common.Hashes{}) @@ -766,9 +771,10 @@ func (s *TotalOrderingTestSuite) TestBasicCaseForK0() { // v v v v // o o o <- o Height: 0 var ( + round uint64 req = s.Require() nodes = test.GenerateRandomNodeIDs(5) - to = newTotalOrdering(0, 3, uint32(len(nodes))) + to = newTotalOrdering(round, 0, 3, uint32(len(nodes))) ) // Setup blocks. b00 := s.genGenesisBlock(nodes, 0, common.Hashes{}) @@ -938,6 +944,7 @@ func (s *TotalOrderingTestSuite) baseTestRandomlyGeneratedBlocks( func (s *TotalOrderingTestSuite) TestRandomlyGeneratedBlocks() { var ( + round uint64 chainNum = uint32(13) blockNum = 50 phi uint64 = 10 @@ -954,25 +961,25 @@ func (s *TotalOrderingTestSuite) TestRandomlyGeneratedBlocks() { for _, gen := range ackingCountGenerators { // Test for K=0. constructor := func(chainNum uint32) *totalOrdering { - return newTotalOrdering(0, phi, chainNum) + return newTotalOrdering(round, 0, phi, chainNum) } s.baseTestRandomlyGeneratedBlocks( constructor, chainNum, blockNum, gen, repeat) // Test for K=1, constructor = func(chainNum uint32) *totalOrdering { - return newTotalOrdering(1, phi, chainNum) + return newTotalOrdering(round, 1, phi, chainNum) } s.baseTestRandomlyGeneratedBlocks( constructor, chainNum, blockNum, gen, repeat) // Test for K=2, constructor = func(chainNum uint32) *totalOrdering { - return newTotalOrdering(2, phi, chainNum) + return newTotalOrdering(round, 2, phi, chainNum) } s.baseTestRandomlyGeneratedBlocks( constructor, chainNum, blockNum, gen, repeat) // Test for K=3, constructor = func(chainNum uint32) *totalOrdering { - return newTotalOrdering(3, phi, chainNum) + return newTotalOrdering(round, 3, phi, chainNum) } s.baseTestRandomlyGeneratedBlocks( constructor, chainNum, blockNum, gen, repeat) diff --git a/core/types/config.go b/core/types/config.go index 0b85363..384041d 100644 --- a/core/types/config.go +++ b/core/types/config.go @@ -26,7 +26,6 @@ import ( // Config stands for Current Configuration Parameters. type Config struct { // Network related. - NumShards uint32 NumChains uint32 // Lambda related. @@ -50,8 +49,6 @@ type Config struct { // Bytes returns []byte representation of Config. func (c *Config) Bytes() []byte { - binaryNumShards := make([]byte, 4) - binary.LittleEndian.PutUint32(binaryNumShards, c.NumShards) binaryNumChains := make([]byte, 4) binary.LittleEndian.PutUint32(binaryNumChains, c.NumChains) @@ -85,7 +82,6 @@ func (c *Config) Bytes() []byte { uint64(c.MaxBlockInterval.Nanoseconds())) enc := make([]byte, 0, 40) - enc = append(enc, binaryNumShards...) enc = append(enc, binaryNumChains...) enc = append(enc, binaryLambdaBA...) enc = append(enc, binaryLambdaDKG...) -- cgit v1.2.3