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  }