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.

302 lines
8.0 KiB

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