github.com/adoriasoft/tendermint@v0.34.0-dev1.0.20200722151356-96d84601a75a/statesync/snapshots.go (about) 1 package statesync 2 3 import ( 4 "crypto/sha256" 5 "fmt" 6 "math/rand" 7 "sort" 8 9 tmsync "github.com/tendermint/tendermint/libs/sync" 10 "github.com/tendermint/tendermint/p2p" 11 ) 12 13 // snapshotKey is a snapshot key used for lookups. 14 type snapshotKey [sha256.Size]byte 15 16 // snapshot contains data about a snapshot. 17 type snapshot struct { 18 Height uint64 19 Format uint32 20 Chunks uint32 21 Hash []byte 22 Metadata []byte 23 24 trustedAppHash []byte // populated by light client 25 } 26 27 // Key generates a snapshot key, used for lookups. It takes into account not only the height and 28 // format, but also the chunks, hash, and metadata in case peers have generated snapshots in a 29 // non-deterministic manner. All fields must be equal for the snapshot to be considered the same. 30 func (s *snapshot) Key() snapshotKey { 31 // Hash.Write() never returns an error. 32 hasher := sha256.New() 33 hasher.Write([]byte(fmt.Sprintf("%v:%v:%v", s.Height, s.Format, s.Chunks))) 34 hasher.Write(s.Hash) 35 hasher.Write(s.Metadata) 36 var key snapshotKey 37 copy(key[:], hasher.Sum(nil)) 38 return key 39 } 40 41 // snapshotPool discovers and aggregates snapshots across peers. 42 type snapshotPool struct { 43 stateProvider StateProvider 44 45 tmsync.Mutex 46 snapshots map[snapshotKey]*snapshot 47 snapshotPeers map[snapshotKey]map[p2p.ID]p2p.Peer 48 49 // indexes for fast searches 50 formatIndex map[uint32]map[snapshotKey]bool 51 heightIndex map[uint64]map[snapshotKey]bool 52 peerIndex map[p2p.ID]map[snapshotKey]bool 53 54 // blacklists for rejected items 55 formatBlacklist map[uint32]bool 56 peerBlacklist map[p2p.ID]bool 57 snapshotBlacklist map[snapshotKey]bool 58 } 59 60 // newSnapshotPool creates a new snapshot pool. The state source is used for 61 func newSnapshotPool(stateProvider StateProvider) *snapshotPool { 62 return &snapshotPool{ 63 stateProvider: stateProvider, 64 snapshots: make(map[snapshotKey]*snapshot), 65 snapshotPeers: make(map[snapshotKey]map[p2p.ID]p2p.Peer), 66 formatIndex: make(map[uint32]map[snapshotKey]bool), 67 heightIndex: make(map[uint64]map[snapshotKey]bool), 68 peerIndex: make(map[p2p.ID]map[snapshotKey]bool), 69 formatBlacklist: make(map[uint32]bool), 70 peerBlacklist: make(map[p2p.ID]bool), 71 snapshotBlacklist: make(map[snapshotKey]bool), 72 } 73 } 74 75 // Add adds a snapshot to the pool, unless the peer has already sent recentSnapshots snapshots. It 76 // returns true if this was a new, non-blacklisted snapshot. The snapshot height is verified using 77 // the light client, and the expected app hash is set for the snapshot. 78 func (p *snapshotPool) Add(peer p2p.Peer, snapshot *snapshot) (bool, error) { 79 appHash, err := p.stateProvider.AppHash(snapshot.Height) 80 if err != nil { 81 return false, err 82 } 83 snapshot.trustedAppHash = appHash 84 key := snapshot.Key() 85 86 p.Lock() 87 defer p.Unlock() 88 89 switch { 90 case p.formatBlacklist[snapshot.Format]: 91 return false, nil 92 case p.peerBlacklist[peer.ID()]: 93 return false, nil 94 case p.snapshotBlacklist[key]: 95 return false, nil 96 case len(p.peerIndex[peer.ID()]) >= recentSnapshots: 97 return false, nil 98 } 99 100 if p.snapshotPeers[key] == nil { 101 p.snapshotPeers[key] = make(map[p2p.ID]p2p.Peer) 102 } 103 p.snapshotPeers[key][peer.ID()] = peer 104 105 if p.peerIndex[peer.ID()] == nil { 106 p.peerIndex[peer.ID()] = make(map[snapshotKey]bool) 107 } 108 p.peerIndex[peer.ID()][key] = true 109 110 if p.snapshots[key] != nil { 111 return false, nil 112 } 113 p.snapshots[key] = snapshot 114 115 if p.formatIndex[snapshot.Format] == nil { 116 p.formatIndex[snapshot.Format] = make(map[snapshotKey]bool) 117 } 118 p.formatIndex[snapshot.Format][key] = true 119 120 if p.heightIndex[snapshot.Height] == nil { 121 p.heightIndex[snapshot.Height] = make(map[snapshotKey]bool) 122 } 123 p.heightIndex[snapshot.Height][key] = true 124 125 return true, nil 126 } 127 128 // Best returns the "best" currently known snapshot, if any. 129 func (p *snapshotPool) Best() *snapshot { 130 ranked := p.Ranked() 131 if len(ranked) == 0 { 132 return nil 133 } 134 return ranked[0] 135 } 136 137 // GetPeer returns a random peer for a snapshot, if any. 138 func (p *snapshotPool) GetPeer(snapshot *snapshot) p2p.Peer { 139 peers := p.GetPeers(snapshot) 140 if len(peers) == 0 { 141 return nil 142 } 143 return peers[rand.Intn(len(peers))] 144 } 145 146 // GetPeers returns the peers for a snapshot. 147 func (p *snapshotPool) GetPeers(snapshot *snapshot) []p2p.Peer { 148 key := snapshot.Key() 149 p.Lock() 150 defer p.Unlock() 151 152 peers := make([]p2p.Peer, 0, len(p.snapshotPeers[key])) 153 for _, peer := range p.snapshotPeers[key] { 154 peers = append(peers, peer) 155 } 156 // sort results, for testability (otherwise order is random, so tests randomly fail) 157 sort.Slice(peers, func(a int, b int) bool { 158 return peers[a].ID() < peers[b].ID() 159 }) 160 return peers 161 } 162 163 // Ranked returns a list of snapshots ranked by preference. The current heuristic is very naïve, 164 // preferring the snapshot with the greatest height, then greatest format, then greatest number of 165 // peers. This can be improved quite a lot. 166 func (p *snapshotPool) Ranked() []*snapshot { 167 p.Lock() 168 defer p.Unlock() 169 170 candidates := make([]*snapshot, 0, len(p.snapshots)) 171 for _, snapshot := range p.snapshots { 172 candidates = append(candidates, snapshot) 173 } 174 175 sort.Slice(candidates, func(i, j int) bool { 176 a := candidates[i] 177 b := candidates[j] 178 179 switch { 180 case a.Height > b.Height: 181 return true 182 case a.Height < b.Height: 183 return false 184 case a.Format > b.Format: 185 return true 186 case a.Format < b.Format: 187 return false 188 case len(p.snapshotPeers[a.Key()]) > len(p.snapshotPeers[b.Key()]): 189 return true 190 default: 191 return false 192 } 193 }) 194 195 return candidates 196 } 197 198 // Reject rejects a snapshot. Rejected snapshots will never be used again. 199 func (p *snapshotPool) Reject(snapshot *snapshot) { 200 key := snapshot.Key() 201 p.Lock() 202 defer p.Unlock() 203 204 p.snapshotBlacklist[key] = true 205 p.removeSnapshot(key) 206 } 207 208 // RejectFormat rejects a snapshot format. It will never be used again. 209 func (p *snapshotPool) RejectFormat(format uint32) { 210 p.Lock() 211 defer p.Unlock() 212 213 p.formatBlacklist[format] = true 214 for key := range p.formatIndex[format] { 215 p.removeSnapshot(key) 216 } 217 } 218 219 // RejectPeer rejects a peer. It will never be used again. 220 func (p *snapshotPool) RejectPeer(peerID p2p.ID) { 221 if peerID == "" { 222 return 223 } 224 p.Lock() 225 defer p.Unlock() 226 227 p.removePeer(peerID) 228 p.peerBlacklist[peerID] = true 229 } 230 231 // RemovePeer removes a peer from the pool, and any snapshots that no longer have peers. 232 func (p *snapshotPool) RemovePeer(peerID p2p.ID) { 233 p.Lock() 234 defer p.Unlock() 235 p.removePeer(peerID) 236 } 237 238 // removePeer removes a peer. The caller must hold the mutex lock. 239 func (p *snapshotPool) removePeer(peerID p2p.ID) { 240 for key := range p.peerIndex[peerID] { 241 delete(p.snapshotPeers[key], peerID) 242 if len(p.snapshotPeers[key]) == 0 { 243 p.removeSnapshot(key) 244 } 245 } 246 delete(p.peerIndex, peerID) 247 } 248 249 // removeSnapshot removes a snapshot. The caller must hold the mutex lock. 250 func (p *snapshotPool) removeSnapshot(key snapshotKey) { 251 snapshot := p.snapshots[key] 252 if snapshot == nil { 253 return 254 } 255 256 delete(p.snapshots, key) 257 delete(p.formatIndex[snapshot.Format], key) 258 delete(p.heightIndex[snapshot.Height], key) 259 for peerID := range p.snapshotPeers[key] { 260 delete(p.peerIndex[peerID], key) 261 } 262 delete(p.snapshotPeers, key) 263 }