// 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 core import ( "time" "github.com/dexon-foundation/dexon-consensus-core/core/types" ) type negativeAck struct { // owner is the ID of proposer itself, this is used when deciding // a node to be restricted or not. owner types.NodeID numOfNodes int // timeDelay and timeExpire are for nack timeout. timeDelay time.Duration timeExpire time.Duration // restricteds stores nodes which has been restricted and the time it's // restricted. restricteds map[types.NodeID]time.Time // lastVotes and lockedVotes store the votes for nack. lastVotes[nid1][nid2] // and lockedVotes[nid1][nid2] both mean that nid2 votes nid1. The difference // is lockedVotes works only when nid1 is restricted, so that the votes are // needed to be locked. lastVotes map[types.NodeID]map[types.NodeID]struct{} lockedVotes map[types.NodeID]map[types.NodeID]struct{} // timeDiffs is the cache for last time stamps. timeDiffs[nid1][nid2] means // the last updated timestamps nid1 sees nid2. timeDiffs map[types.NodeID]map[types.NodeID]map[types.NodeID]time.Time } // newNegativeAck creates a new negaticeAck instance. func newNegativeAck(nid types.NodeID) *negativeAck { n := &negativeAck{ owner: nid, numOfNodes: 0, restricteds: make(map[types.NodeID]time.Time), lastVotes: make(map[types.NodeID]map[types.NodeID]struct{}), lockedVotes: make(map[types.NodeID]map[types.NodeID]struct{}), timeDiffs: make(map[types.NodeID]map[types.NodeID]map[types.NodeID]time.Time), } n.addNode(nid) return n } // processNewVote is called when a new "vote" occurs, that is, a node // sees that other 2f + 1 nodes think a node is slow. "nid" is the // node which propesed the block which the timestamps votes and "h" is // the node been voted to be nacked. func (n *negativeAck) processNewVote( nid types.NodeID, h types.NodeID, ) []types.NodeID { nackeds := []types.NodeID{} if _, exist := n.restricteds[h]; exist { n.lockedVotes[h][nid] = struct{}{} if len(n.lockedVotes[h]) > 2*(n.numOfNodes-1)/3 { nackeds = append(nackeds, h) delete(n.restricteds, h) } } else { if n.owner == nid { n.restrict(h) } else { n.lastVotes[h][nid] = struct{}{} if len(n.lastVotes[h]) > (n.numOfNodes-1)/3 { n.restrict(h) } } } return nackeds } // processTimestamps process new timestamps of a block which is proposed by // node nid, and returns the nodes being nacked. func (n *negativeAck) processTimestamps( nid types.NodeID, ts map[types.NodeID]time.Time, ) []types.NodeID { n.checkRestrictExpire() nackeds := []types.NodeID{} for h := range n.timeDiffs { if n.timeDiffs[nid][h][h].Equal(ts[h]) { votes := 0 for hh := range n.timeDiffs { if ts[hh].Sub(n.timeDiffs[nid][h][hh]) >= n.timeDelay { votes++ } } if votes > 2*((n.numOfNodes-1)/3) { n.lastVotes[h][nid] = struct{}{} nack := n.processNewVote(nid, h) for _, i := range nack { nackeds = append(nackeds, i) } } else { delete(n.lastVotes[h], nid) } } else { for hh := range n.timeDiffs { n.timeDiffs[nid][h][hh] = ts[hh] } delete(n.lastVotes[h], nid) } } return nackeds } func (n *negativeAck) checkRestrictExpire() { expired := []types.NodeID{} now := time.Now() for h, t := range n.restricteds { if now.Sub(t) >= n.timeExpire { expired = append(expired, h) } } for _, h := range expired { delete(n.restricteds, h) } } func (n *negativeAck) restrict(nid types.NodeID) { if _, exist := n.restricteds[nid]; !exist { n.restricteds[nid] = time.Now().UTC() n.lockedVotes[nid] = map[types.NodeID]struct{}{} for h := range n.lastVotes[nid] { n.lockedVotes[nid][h] = struct{}{} } } } func (n *negativeAck) getRestrictedNodes() map[types.NodeID]struct{} { n.checkRestrictExpire() ret := map[types.NodeID]struct{}{} for h := range n.restricteds { ret[h] = struct{}{} } return ret } func (n *negativeAck) setTimeDelay(t time.Duration) { n.timeDelay = t } func (n *negativeAck) setTimeExpire(t time.Duration) { n.timeExpire = t } func (n *negativeAck) addNode(nid types.NodeID) { n.numOfNodes++ n.lastVotes[nid] = make(map[types.NodeID]struct{}) n.lockedVotes[nid] = make(map[types.NodeID]struct{}) newTimeDiff := make(map[types.NodeID]map[types.NodeID]time.Time) for h := range n.timeDiffs { newTimeDiff2 := make(map[types.NodeID]time.Time) for hh := range n.timeDiffs { newTimeDiff2[hh] = time.Time{} } newTimeDiff[h] = newTimeDiff2 } n.timeDiffs[nid] = newTimeDiff for h := range n.timeDiffs { n.timeDiffs[h][nid] = make(map[types.NodeID]time.Time) } } func (n *negativeAck) deleteNode(nid types.NodeID) { n.numOfNodes-- delete(n.timeDiffs, nid) for h := range n.lastVotes { delete(n.lastVotes[h], nid) } delete(n.lastVotes, nid) delete(n.lockedVotes, nid) for h := range n.timeDiffs { delete(n.timeDiffs[h], nid) for hh := range n.timeDiffs[h] { delete(n.timeDiffs[h][hh], nid) } } delete(n.restricteds, nid) }