// Copyright 2018 The dexon-consensus-core Authors // This file is part of the dexon-consensus-core library. // // The dexon-consensus-core 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-core 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-core library. If not, see // . package integration import ( "fmt" "math" "sort" "time" "github.com/dexon-foundation/dexon-consensus-core/common" "github.com/dexon-foundation/dexon-consensus-core/core" "github.com/dexon-foundation/dexon-consensus-core/core/blockdb" "github.com/dexon-foundation/dexon-consensus-core/core/test" "github.com/dexon-foundation/dexon-consensus-core/core/types" "github.com/dexon-foundation/dexon-consensus-core/crypto" "github.com/dexon-foundation/dexon-consensus-core/crypto/eth" ) type consensusEventType int const ( evtProposeBlock consensusEventType = iota evtReceiveBlock ) type consensusEventPayload struct { Type consensusEventType PiggyBack interface{} } // NewProposeBlockEvent constructs an test.Event that would trigger // block proposing. func NewProposeBlockEvent(nID types.NodeID, when time.Time) *test.Event { return test.NewEvent(nID, when, &consensusEventPayload{ Type: evtProposeBlock, }) } // NewReceiveBlockEvent constructs an test.Event that would trigger // block received. func NewReceiveBlockEvent( nID types.NodeID, when time.Time, block *types.Block) *test.Event { return test.NewEvent(nID, when, &consensusEventPayload{ Type: evtReceiveBlock, PiggyBack: block, }) } // Node is designed to work with test.Scheduler. type Node struct { ID types.NodeID chainNum uint32 chainID uint32 shard *core.Shard app *test.App db blockdb.BlockDatabase broadcastTargets map[types.NodeID]struct{} networkLatency test.LatencyModel proposingLatency test.LatencyModel } // NewNode constructs an instance of Node. func NewNode( app *test.App, gov core.Governance, db blockdb.BlockDatabase, privateKey crypto.PrivateKey, networkLatency test.LatencyModel, proposingLatency test.LatencyModel) *Node { var ( shardID = uint32(0) chainID = uint32(math.MaxUint32) governanceConfig = gov.GetConfiguration(0) broadcastTargets = gov.GetNotarySet(0) nodeID = types.NewNodeID(privateKey.PublicKey()) ) hashes := common.Hashes{} for nID := range broadcastTargets { hashes = append(hashes, nID.Hash) } sort.Sort(hashes) for i, h := range hashes { if h == nodeID.Hash { chainID = uint32(i) } } delete(broadcastTargets, nodeID) return &Node{ ID: nodeID, chainID: chainID, chainNum: governanceConfig.NumChains, broadcastTargets: broadcastTargets, networkLatency: networkLatency, proposingLatency: proposingLatency, app: app, db: db, shard: core.NewShard( shardID, governanceConfig, privateKey, eth.SigToPub, app, app, db), } } // Handle implements test.EventHandler interface. func (n *Node) Handle(e *test.Event) (events []*test.Event) { payload := e.Payload.(*consensusEventPayload) switch payload.Type { case evtProposeBlock: events, e.ExecError = n.handleProposeBlock(e.Time, payload.PiggyBack) case evtReceiveBlock: events, e.ExecError = n.handleReceiveBlock(payload.PiggyBack) default: panic(fmt.Errorf("unknown consensus event type: %v", payload.Type)) } return } func (n *Node) handleProposeBlock(when time.Time, _ interface{}) ( events []*test.Event, err error) { b, err := n.prepareBlock(when) if err != nil { return } if err = n.processBlock(b); err != nil { return } // Create 'block received' event for each other nodes. for nID := range n.broadcastTargets { events = append(events, NewReceiveBlockEvent( nID, when.Add(n.networkLatency.Delay()), b.Clone())) } // Create next 'block proposing' event for this nodes. events = append(events, NewProposeBlockEvent( n.ID, when.Add(n.proposingLatency.Delay()))) return } func (n *Node) handleReceiveBlock(piggyback interface{}) ( events []*test.Event, err error) { err = n.processBlock(piggyback.(*types.Block)) if err != nil { panic(err) } return } func (n *Node) prepareBlock(when time.Time) (b *types.Block, err error) { b = &types.Block{ Position: types.Position{ ChainID: n.chainID, }} err = n.shard.PrepareBlock(b, when) return } func (n *Node) processBlock(b *types.Block) (err error) { // TODO(mission): this segment of code is identical to testShardMgr in // core/shard_test.go, except the compaction-chain part. var ( delivered []*types.Block verified []*types.Block pendings = []*types.Block{b} ) if err = n.shard.SanityCheck(b); err != nil { if err == core.ErrAckingBlockNotExists { err = nil } return } for { if len(pendings) == 0 { break } b, pendings = pendings[0], pendings[1:] if verified, delivered, err = n.shard.ProcessBlock(b); err != nil { return } // Deliver blocks. for _, b = range delivered { if err = n.db.Update(*b); err != nil { return } n.app.BlockDelivered(*b) } // Update pending blocks for verified block (pass sanity check). pendings = append(pendings, verified...) } return }