diff options
-rw-r--r-- | core/compaction-chain.go | 2 | ||||
-rw-r--r-- | core/compaction-chain_test.go | 12 | ||||
-rw-r--r-- | core/consensus-timestamp.go | 2 | ||||
-rw-r--r-- | core/consensus-timestamp_test.go | 2 | ||||
-rw-r--r-- | core/crypto.go | 20 | ||||
-rw-r--r-- | core/crypto_test.go | 16 | ||||
-rw-r--r-- | core/interfaces.go | 7 | ||||
-rw-r--r-- | core/lattice.go | 13 | ||||
-rw-r--r-- | core/lattice_test.go | 33 | ||||
-rw-r--r-- | core/nonblocking.go | 11 | ||||
-rw-r--r-- | core/nonblocking_test.go | 8 | ||||
-rw-r--r-- | core/test/app.go | 15 | ||||
-rw-r--r-- | core/test/app_test.go | 7 | ||||
-rw-r--r-- | core/test/stopper_test.go | 4 | ||||
-rw-r--r-- | core/types/block.go | 22 | ||||
-rw-r--r-- | simulation/app.go | 46 |
16 files changed, 159 insertions, 61 deletions
diff --git a/core/compaction-chain.go b/core/compaction-chain.go index 21b8412..c6d423d 100644 --- a/core/compaction-chain.go +++ b/core/compaction-chain.go @@ -59,7 +59,7 @@ func (cc *compactionChain) sanityCheck(witnessBlock *types.Block) error { func (cc *compactionChain) processBlock(block *types.Block) error { prevBlock := cc.lastBlock() if prevBlock != nil { - block.Witness.Height = prevBlock.Witness.Height + 1 + block.ConsensusHeight = prevBlock.ConsensusHeight + 1 } cc.prevBlockLock.Lock() defer cc.prevBlockLock.Unlock() diff --git a/core/compaction-chain_test.go b/core/compaction-chain_test.go index b8f446e..6f62aa2 100644 --- a/core/compaction-chain_test.go +++ b/core/compaction-chain_test.go @@ -48,10 +48,8 @@ func (s *CompactionChainTestSuite) generateBlocks( blocks := make([]*types.Block, size) for idx := range blocks { blocks[idx] = &types.Block{ - Hash: common.NewRandomHash(), - Witness: types.Witness{ - Timestamp: now, - }, + Hash: common.NewRandomHash(), + ConsensusTimestamp: now, } now = now.Add(100 * time.Millisecond) } @@ -68,10 +66,8 @@ func (s *CompactionChainTestSuite) TestProcessBlock() { blocks := make([]*types.Block, 10) for idx := range blocks { blocks[idx] = &types.Block{ - Hash: common.NewRandomHash(), - Witness: types.Witness{ - Timestamp: now, - }, + Hash: common.NewRandomHash(), + ConsensusTimestamp: now, } now = now.Add(100 * time.Millisecond) } diff --git a/core/consensus-timestamp.go b/core/consensus-timestamp.go index 9188c02..62298d9 100644 --- a/core/consensus-timestamp.go +++ b/core/consensus-timestamp.go @@ -63,7 +63,7 @@ func (ct *consensusTimestamp) appendConfig( func (ct *consensusTimestamp) processBlocks(blocks []*types.Block) (err error) { for _, block := range blocks { if !block.IsGenesis() { - block.Witness.Timestamp, err = getMedianTime(ct.chainTimestamps) + block.ConsensusTimestamp, err = getMedianTime(ct.chainTimestamps) if err != nil { return } diff --git a/core/consensus-timestamp_test.go b/core/consensus-timestamp_test.go index ea85c38..615142c 100644 --- a/core/consensus-timestamp_test.go +++ b/core/consensus-timestamp_test.go @@ -82,7 +82,7 @@ func (s *ConsensusTimestampTest) extractTimestamps( if block.IsGenesis() { continue } - timestamps = append(timestamps, block.Witness.Timestamp) + timestamps = append(timestamps, block.ConsensusTimestamp) } return timestamps } diff --git a/core/crypto.go b/core/crypto.go index a0bacac..6fc0d1b 100644 --- a/core/crypto.go +++ b/core/crypto.go @@ -25,6 +25,19 @@ import ( "github.com/dexon-foundation/dexon-consensus-core/core/types" ) +func hashWitness(witness *types.Witness) (common.Hash, error) { + binaryTimestamp, err := witness.Timestamp.MarshalBinary() + if err != nil { + return common.Hash{}, err + } + binaryHeight := make([]byte, 8) + binary.LittleEndian.PutUint64(binaryHeight, witness.Height) + return crypto.Keccak256Hash( + binaryHeight, + binaryTimestamp, + witness.Data), nil +} + func hashBlock(block *types.Block) (common.Hash, error) { hashPosition := hashPosition(block.Position) // Handling Block.Acks. @@ -37,6 +50,10 @@ func hashBlock(block *types.Block) (common.Hash, error) { if err != nil { return common.Hash{}, err } + binaryWitness, err := hashWitness(&block.Witness) + if err != nil { + return common.Hash{}, err + } payloadHash := crypto.Keccak256Hash(block.Payload) hash := crypto.Keccak256Hash( @@ -45,7 +62,8 @@ func hashBlock(block *types.Block) (common.Hash, error) { hashPosition[:], hashAcks[:], binaryTimestamp[:], - payloadHash[:]) + payloadHash[:], + binaryWitness[:]) return hash, nil } diff --git a/core/crypto_test.go b/core/crypto_test.go index 1fdc802..dabcb21 100644 --- a/core/crypto_test.go +++ b/core/crypto_test.go @@ -40,12 +40,10 @@ func (s *CryptoTestSuite) prepareBlock(prevBlock *types.Block) *types.Block { now := time.Now().UTC() if prevBlock == nil { return &types.Block{ - Acks: common.NewSortedHashes(acks), - Timestamp: now, - Witness: types.Witness{ - Timestamp: time.Now(), - Height: 0, - }, + Acks: common.NewSortedHashes(acks), + Timestamp: now, + ConsensusTimestamp: time.Now(), + ConsensusHeight: 0, } } s.Require().NotEqual(prevBlock.Hash, common.Hash{}) @@ -56,10 +54,8 @@ func (s *CryptoTestSuite) prepareBlock(prevBlock *types.Block) *types.Block { Position: types.Position{ Height: prevBlock.Position.Height + 1, }, - Witness: types.Witness{ - Timestamp: time.Now(), - Height: prevBlock.Witness.Height + 1, - }, + ConsensusTimestamp: time.Now(), + ConsensusHeight: prevBlock.ConsensusHeight + 1, } } diff --git a/core/interfaces.go b/core/interfaces.go index fa593eb..c367933 100644 --- a/core/interfaces.go +++ b/core/interfaces.go @@ -28,8 +28,11 @@ import ( // Application describes the application interface that interacts with DEXON // consensus core. type Application interface { - // PrepareBlock is called when consensus core is preparing a block. - PrepareBlock(position types.Position) (payload []byte, witnessData []byte) + // PreparePayload is called when consensus core is preparing a block. + PreparePayload(position types.Position) (payload []byte) + + // PrepareWitness will return the witness data no lower than consensusHeight. + PrepareWitness(consensusHeight uint64) (witness types.Witness) // VerifyBlock verifies if the block is valid. VerifyBlock(block *types.Block) bool diff --git a/core/lattice.go b/core/lattice.go index 8906c74..bb3bccd 100644 --- a/core/lattice.go +++ b/core/lattice.go @@ -38,6 +38,8 @@ var ( ErrInvalidChainID = fmt.Errorf("invalid chain id") ErrInvalidProposerID = fmt.Errorf("invalid proposer id") ErrInvalidTimestamp = fmt.Errorf("invalid timestamp") + ErrInvalidWitness = fmt.Errorf("invalid witness data") + ErrInvalidBlock = fmt.Errorf("invalid block") ErrForkBlock = fmt.Errorf("fork block") ErrNotAckParent = fmt.Errorf("not ack parent") ErrDoubleAck = fmt.Errorf("double ack") @@ -146,6 +148,9 @@ func (data *latticeData) sanityCheck(b *types.Block) error { if bParent.Position.Height != b.Position.Height-1 { return ErrInvalidBlockHeight } + if bParent.Witness.Height > b.Witness.Height { + return ErrInvalidWitness + } // Check if its timestamp is valid. if !b.Timestamp.After(bParent.Timestamp) { return ErrInvalidTimestamp @@ -290,6 +295,7 @@ func (data *latticeData) prepareBlock(block *types.Block) { if uint32(chainID) == block.Position.ChainID { block.ParentHash = curBlock.Hash block.Position.Height = curBlock.Position.Height + 1 + block.Witness.Height = curBlock.Witness.Height } } block.Acks = common.NewSortedHashes(acks) @@ -465,7 +471,8 @@ func (s *Lattice) PrepareBlock( // TODO(mission): the proposeTime might be earlier than tip block of // that chain. We should let latticeData suggest the time. b.Timestamp = proposeTime - b.Payload, b.Witness.Data = s.app.PrepareBlock(b.Position) + b.Payload = s.app.PreparePayload(b.Position) + b.Witness = s.app.PrepareWitness(b.Witness.Height) if err = s.authModule.SignBlock(b); err != nil { return } @@ -492,6 +499,10 @@ func (s *Lattice) SanityCheck(b *types.Block) (err error) { err = ErrIncorrectSignature return } + if !s.app.VerifyBlock(b) { + err = ErrInvalidBlock + return err + } s.lock.RLock() defer s.lock.RUnlock() if err = s.data.sanityCheck(b); err != nil { diff --git a/core/lattice_test.go b/core/lattice_test.go index 1be9a0e..329d698 100644 --- a/core/lattice_test.go +++ b/core/lattice_test.go @@ -184,6 +184,9 @@ func (s *LatticeTestSuite) genTestCase1() (data *latticeData) { Height: 1, }, Acks: common.NewSortedHashes(common.Hashes{h}), + Witness: types.Witness{ + Height: 1, + }, } s.hashBlock(b) delivered, err = data.addBlock(b) @@ -205,6 +208,9 @@ func (s *LatticeTestSuite) genTestCase1() (data *latticeData) { h, data.chains[1].getBlockByHeight(0).Hash, }), + Witness: types.Witness{ + Height: 2, + }, } s.hashBlock(b) delivered, err = data.addBlock(b) @@ -224,6 +230,9 @@ func (s *LatticeTestSuite) genTestCase1() (data *latticeData) { Height: 3, }, Acks: common.NewSortedHashes(common.Hashes{h}), + Witness: types.Witness{ + Height: 3, + }, } s.hashBlock(b) delivered, err = data.addBlock(b) @@ -243,6 +252,9 @@ func (s *LatticeTestSuite) genTestCase1() (data *latticeData) { Height: 1, }, Acks: common.NewSortedHashes(common.Hashes{h}), + Witness: types.Witness{ + Height: 1, + }, } s.hashBlock(b) delivered, err = data.addBlock(b) @@ -399,6 +411,27 @@ func (s *LatticeTestSuite) TestSanityCheckInDataLayer() { req.NotNil(err) req.Equal(err.Error(), ErrDuplicatedAckOnOneChain.Error()) + // Witness height decreases. + h = data.chains[0].getBlockByHeight(3).Hash + b = &types.Block{ + ParentHash: h, + Position: types.Position{ + ChainID: 0, + Height: 4, + }, + Timestamp: time.Now().UTC(), + Acks: common.NewSortedHashes(common.Hashes{ + h, + }), + Witness: types.Witness{ + Height: 2, + }, + } + s.hashBlock(b) + err = data.sanityCheck(b) + req.NotNil(err) + req.Equal(err.Error(), ErrInvalidWitness.Error()) + // Add block 3-1 which acks 3-0, and violet reasonable block time interval. h = data.chains[2].getBlockByHeight(0).Hash b = &types.Block{ diff --git a/core/nonblocking.go b/core/nonblocking.go index 2e5bfeb..4948a4f 100644 --- a/core/nonblocking.go +++ b/core/nonblocking.go @@ -117,9 +117,14 @@ func (nb *nonBlocking) wait() { nb.running.Wait() } -// PrepareBlock cannot be non-blocking. -func (nb *nonBlocking) PrepareBlock(position types.Position) ([]byte, []byte) { - return nb.app.PrepareBlock(position) +// PreparePayload cannot be non-blocking. +func (nb *nonBlocking) PreparePayload(position types.Position) []byte { + return nb.app.PreparePayload(position) +} + +// PrepareWitness cannot be non-blocking. +func (nb *nonBlocking) PrepareWitness(height uint64) types.Witness { + return nb.app.PrepareWitness(height) } // VerifyBlock cannot be non-blocking. diff --git a/core/nonblocking_test.go b/core/nonblocking_test.go index 8c3cda9..7599456 100644 --- a/core/nonblocking_test.go +++ b/core/nonblocking_test.go @@ -45,8 +45,12 @@ func newSlowApp(sleep time.Duration) *slowApp { } } -func (app *slowApp) PrepareBlock(_ types.Position) ([]byte, []byte) { - return []byte{}, []byte{} +func (app *slowApp) PreparePayload(_ types.Position) []byte { + return []byte{} +} + +func (app *slowApp) PrepareWitness(_ uint64) types.Witness { + return types.Witness{} } func (app *slowApp) VerifyBlock(_ *types.Block) bool { diff --git a/core/test/app.go b/core/test/app.go index 3ec670c..bf14d1b 100644 --- a/core/test/app.go +++ b/core/test/app.go @@ -96,9 +96,16 @@ func NewApp() *App { } } -// PrepareBlock implements Application interface. -func (app *App) PrepareBlock(position types.Position) ([]byte, []byte) { - return []byte{}, []byte{} +// PreparePayload implements Application interface. +func (app *App) PreparePayload(position types.Position) []byte { + return []byte{} +} + +// PrepareWitness implements Application interface. +func (app *App) PrepareWitness(height uint64) types.Witness { + return types.Witness{ + Height: height, + } } // VerifyBlock implements Application. @@ -143,7 +150,7 @@ func (app *App) BlockDelivered(block types.Block) { defer app.deliveredLock.Unlock() app.Delivered[block.Hash] = &AppDeliveredRecord{ - ConsensusTime: block.Witness.Timestamp, + ConsensusTime: block.ConsensusTimestamp, When: time.Now().UTC(), } app.DeliverSequence = append(app.DeliverSequence, block.Hash) diff --git a/core/test/app_test.go b/core/test/app_test.go index baea838..21d4e24 100644 --- a/core/test/app_test.go +++ b/core/test/app_test.go @@ -81,10 +81,9 @@ func (s *AppTestSuite) deliverBlock( app *App, hash common.Hash, timestamp time.Time) { app.BlockDelivered(types.Block{ - Hash: hash, - Witness: types.Witness{ - Timestamp: timestamp, - }}) + Hash: hash, + ConsensusTimestamp: timestamp, + }) } func (s *AppTestSuite) TestCompare() { diff --git a/core/test/stopper_test.go b/core/test/stopper_test.go index de1c1a5..df207ec 100644 --- a/core/test/stopper_test.go +++ b/core/test/stopper_test.go @@ -62,8 +62,8 @@ func (s *StopperTestSuite) TestStopByConfirmedBlocks() { app.TotalOrderingDelivered(hashes, false) for _, h := range hashes { app.BlockDelivered(types.Block{ - Hash: h, - Witness: types.Witness{Timestamp: time.Time{}}}) + Hash: h, + ConsensusTimestamp: time.Time{}}) } } } diff --git a/core/types/block.go b/core/types/block.go index 1d994e6..c785969 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -61,15 +61,17 @@ func NewBlock() (b *Block) { // 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:"timestamps"` - Acks common.SortedHashes `json:"acks"` - Payload []byte `json:"payload"` - Witness Witness `json:"witness"` - 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:"timestamps"` + Acks common.SortedHashes `json:"acks"` + Payload []byte `json:"payload"` + ConsensusTimestamp time.Time `json:"consensus_timestamp"` + ConsensusHeight uint64 `json:"consensus_height"` + Witness Witness `json:"witness"` + Signature crypto.Signature `json:"signature"` CRSSignature crypto.Signature `json:"crs_signature"` } @@ -89,6 +91,8 @@ func (b *Block) Clone() (bcopy *Block) { bcopy.Position.Height = b.Position.Height bcopy.Signature = b.Signature.Clone() bcopy.CRSSignature = b.CRSSignature.Clone() + bcopy.ConsensusTimestamp = b.ConsensusTimestamp + bcopy.ConsensusHeight = b.ConsensusHeight bcopy.Witness.Timestamp = b.Witness.Timestamp bcopy.Witness.Height = b.Witness.Height bcopy.Timestamp = b.Timestamp diff --git a/simulation/app.go b/simulation/app.go index 5f1a85a..a46f3a6 100644 --- a/simulation/app.go +++ b/simulation/app.go @@ -37,20 +37,23 @@ type simApp struct { // blockSeen stores the time when block is delivered by Total Ordering. blockSeen map[common.Hash]time.Time // uncofirmBlocks stores the blocks whose timestamps are not ready. - unconfirmedBlocks map[types.NodeID]common.Hashes - blockByHash map[common.Hash]*types.Block - blockByHashMutex sync.RWMutex + unconfirmedBlocks map[types.NodeID]common.Hashes + blockByHash map[common.Hash]*types.Block + blockByHashMutex sync.RWMutex + latestWitness types.Witness + latestWitnessReady *sync.Cond } // newSimApp returns point to a new instance of simApp. func newSimApp(id types.NodeID, netModule *network) *simApp { return &simApp{ - NodeID: id, - netModule: netModule, - DeliverID: 0, - blockSeen: make(map[common.Hash]time.Time), - unconfirmedBlocks: make(map[types.NodeID]common.Hashes), - blockByHash: make(map[common.Hash]*types.Block), + NodeID: id, + netModule: netModule, + DeliverID: 0, + blockSeen: make(map[common.Hash]time.Time), + unconfirmedBlocks: make(map[types.NodeID]common.Hashes), + blockByHash: make(map[common.Hash]*types.Block), + latestWitnessReady: sync.NewCond(&sync.Mutex{}), } } @@ -89,9 +92,19 @@ func (a *simApp) getAckedBlocks(ackHash common.Hash) (output common.Hashes) { return } -// PrepareBlock implements core.Application. -func (a *simApp) PrepareBlock(position types.Position) ([]byte, []byte) { - return []byte{}, []byte{} +// PreparePayload implements core.Application. +func (a *simApp) PreparePayload(position types.Position) []byte { + return []byte{} +} + +// PrepareWitness implements core.Application. +func (a *simApp) PrepareWitness(height uint64) types.Witness { + a.latestWitnessReady.L.Lock() + defer a.latestWitnessReady.L.Unlock() + for a.latestWitness.Height < height { + a.latestWitnessReady.Wait() + } + return a.latestWitness } // StronglyAcked is called when a block is strongly acked by DEXON @@ -120,6 +133,15 @@ func (a *simApp) BlockDelivered(block types.Block) { // TODO(jimmy-dexon) : Remove block in this hash if it's no longer needed. a.blockByHash[block.Hash] = &block }() + func() { + a.latestWitnessReady.L.Lock() + defer a.latestWitnessReady.L.Unlock() + a.latestWitness = types.Witness{ + Timestamp: block.ConsensusTimestamp, + Height: block.ConsensusHeight, + } + a.latestWitnessReady.Broadcast() + }() seenTime, exist := a.blockSeen[block.Hash] if !exist { |