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.

268 lines
7.1 KiB

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