You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

289 lines
7.7 KiB

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