github.com/koko1123/flow-go-1@v0.29.6/network/p2p/cache/node_blocklist_wrapper.go (about)

     1  package cache
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"sync"
     7  
     8  	"github.com/dgraph-io/badger/v3"
     9  	"github.com/libp2p/go-libp2p/core/peer"
    10  
    11  	"github.com/koko1123/flow-go-1/model/flow"
    12  	"github.com/koko1123/flow-go-1/module"
    13  	"github.com/koko1123/flow-go-1/storage"
    14  	"github.com/koko1123/flow-go-1/storage/badger/operation"
    15  )
    16  
    17  // IdentifierSet represents a set of node IDs (operator-defined) whose communication should be blocked.
    18  type IdentifierSet map[flow.Identifier]struct{}
    19  
    20  // Contains returns true iff id ∈ s
    21  func (s IdentifierSet) Contains(id flow.Identifier) bool {
    22  	_, found := s[id]
    23  	return found
    24  }
    25  
    26  // NodeBlocklistWrapper is a wrapper for an `module.IdentityProvider` instance, where the
    27  // wrapper overrides the `Ejected` flag to true for all NodeIDs in a `blocklist`.
    28  // To avoid modifying the source of the identities, the wrapper creates shallow copies
    29  // of the identities (whenever necessary) and modifies the `Ejected` flag only in
    30  // the copy.
    31  // The `NodeBlocklistWrapper` internally represents the `blocklist` as a map, to enable
    32  // performant lookup. However, the exported API works with `flow.IdentifierList` for
    33  // blocklist, as this is a broadly supported data structure which lends itself better
    34  // to config or command-line inputs.
    35  type NodeBlocklistWrapper struct {
    36  	m  sync.RWMutex
    37  	db *badger.DB
    38  
    39  	identityProvider module.IdentityProvider
    40  	blocklist        IdentifierSet // `IdentifierSet` is a map, hence efficient O(1) lookup
    41  }
    42  
    43  var _ module.IdentityProvider = (*NodeBlocklistWrapper)(nil)
    44  
    45  // NewNodeBlocklistWrapper wraps the given `IdentityProvider`. The blocklist is
    46  // loaded from the database (or assumed to be empty if no database entry is present).
    47  func NewNodeBlocklistWrapper(identityProvider module.IdentityProvider, db *badger.DB) (*NodeBlocklistWrapper, error) {
    48  	blocklist, err := retrieveBlocklist(db)
    49  	if err != nil {
    50  		return nil, fmt.Errorf("failed to read set of blocked node IDs from data base: %w", err)
    51  	}
    52  
    53  	return &NodeBlocklistWrapper{
    54  		db:               db,
    55  		identityProvider: identityProvider,
    56  		blocklist:        blocklist,
    57  	}, nil
    58  }
    59  
    60  // Update sets the wrapper's internal set of blocked nodes to `blocklist`. Empty list and `nil`
    61  // (equivalent to empty list) are accepted inputs. To avoid legacy entries in the data base, this
    62  // function purges the entire data base entry if `blocklist` is empty.
    63  // This implementation is _eventually consistent_, where changes are written to the data base first
    64  // and then (non-atomically!) the in-memory set of blocked nodes is updated. This strongly
    65  // benefits performance and modularity. No errors are expected during normal operations.
    66  func (w *NodeBlocklistWrapper) Update(blocklist flow.IdentifierList) error {
    67  	b := blocklist.Lookup() // converts slice to map
    68  
    69  	w.m.Lock()
    70  	defer w.m.Unlock()
    71  	err := persistBlocklist(b, w.db)
    72  	if err != nil {
    73  		return fmt.Errorf("failed to persist set of blocked nodes to the data base: %w", err)
    74  	}
    75  	w.blocklist = b
    76  	return nil
    77  }
    78  
    79  // ClearBlocklist purges the set of blocked node IDs. Convenience function
    80  // equivalent to w.Update(nil). No errors are expected during normal operations.
    81  func (w *NodeBlocklistWrapper) ClearBlocklist() error {
    82  	return w.Update(nil)
    83  }
    84  
    85  // GetBlocklist returns the set of blocked node IDs.
    86  func (w *NodeBlocklistWrapper) GetBlocklist() flow.IdentifierList {
    87  	w.m.RLock()
    88  	defer w.m.RUnlock()
    89  
    90  	identifiers := make(flow.IdentifierList, 0, len(w.blocklist))
    91  	for i := range w.blocklist {
    92  		identifiers = append(identifiers, i)
    93  	}
    94  	return identifiers
    95  }
    96  
    97  // Identities returns the full identities of _all_ nodes currently known to the
    98  // protocol that pass the provided filter. Caution, this includes ejected nodes.
    99  // Please check the `Ejected` flag in the returned identities (or provide a
   100  // filter for removing ejected nodes).
   101  func (w *NodeBlocklistWrapper) Identities(filter flow.IdentityFilter) flow.IdentityList {
   102  	identities := w.identityProvider.Identities(filter)
   103  	if len(identities) == 0 {
   104  		return identities
   105  	}
   106  
   107  	// Iterate over all returned identities and set ejected flag to true. We
   108  	// copy both the return slice and identities of blocked nodes to avoid
   109  	// any possibility of accidentally modifying the wrapped IdentityProvider
   110  	idtx := make(flow.IdentityList, 0, len(identities))
   111  	w.m.RLock()
   112  	for _, identity := range identities {
   113  		if w.blocklist.Contains(identity.NodeID) {
   114  			var i flow.Identity = *identity // shallow copy is sufficient, because `Ejected` flag is in top-level struct
   115  			i.Ejected = true
   116  			if filter(&i) { // we need to check the filter here again, because the filter might drop ejected nodes and we are modifying the ejected status here
   117  				idtx = append(idtx, &i)
   118  			}
   119  		} else {
   120  			idtx = append(idtx, identity)
   121  		}
   122  	}
   123  	w.m.RUnlock()
   124  	return idtx
   125  }
   126  
   127  // ByNodeID returns the full identity for the node with the given Identifier,
   128  // where Identifier is the way the protocol refers to the node. The function
   129  // has the same semantics as a map lookup, where the boolean return value is
   130  // true if and only if Identity has been found, i.e. `Identity` is not nil.
   131  // Caution: function returns include ejected nodes. Please check the `Ejected`
   132  // flag in the identity.
   133  func (w *NodeBlocklistWrapper) ByNodeID(identifier flow.Identifier) (*flow.Identity, bool) {
   134  	identity, b := w.identityProvider.ByNodeID(identifier)
   135  	return w.setEjectedIfBlocked(identity), b
   136  }
   137  
   138  // setEjectedIfBlocked checks whether the node with the given identity is on the `blocklist`.
   139  // Shortcuts:
   140  //   - If the node's identity is nil, there is nothing to do because we don't generate identities here.
   141  //   - If the node is already ejected, we don't have to check the blocklist.
   142  func (w *NodeBlocklistWrapper) setEjectedIfBlocked(identity *flow.Identity) *flow.Identity {
   143  	if identity == nil || identity.Ejected {
   144  		return identity
   145  	}
   146  
   147  	w.m.RLock()
   148  	isBlocked := w.blocklist.Contains(identity.NodeID)
   149  	w.m.RUnlock()
   150  	if !isBlocked {
   151  		return identity
   152  	}
   153  
   154  	// For blocked nodes, we want to return their `Identity` with the `Ejected` flag
   155  	// set to true. Caution: we need to copy the `Identity` before we override `Ejected`, as we
   156  	// would otherwise potentially change the wrapped IdentityProvider.
   157  	var i flow.Identity = *identity // shallow copy is sufficient, because `Ejected` flag is in top-level struct
   158  	i.Ejected = true
   159  	return &i
   160  }
   161  
   162  // ByPeerID returns the full identity for the node with the given peer ID,
   163  // peer.ID is the libp2p-level identifier of a Flow node. The function
   164  // has the same semantics as a map lookup, where the boolean return value is
   165  // true if and only if Identity has been found, i.e. `Identity` is not nil.
   166  // Caution: function returns include ejected nodes. Please check the `Ejected`
   167  // flag in the identity.
   168  func (w *NodeBlocklistWrapper) ByPeerID(p peer.ID) (*flow.Identity, bool) {
   169  	identity, b := w.identityProvider.ByPeerID(p)
   170  	return w.setEjectedIfBlocked(identity), b
   171  }
   172  
   173  // persistBlocklist writes the given blocklist to the database. To avoid legacy
   174  // entries in the database, we prune the entire data base entry if `blocklist` is
   175  // empty. No errors are expected during normal operations.
   176  func persistBlocklist(blocklist IdentifierSet, db *badger.DB) error {
   177  	if len(blocklist) == 0 {
   178  		return db.Update(operation.PurgeBlocklist())
   179  	}
   180  	return db.Update(operation.PersistBlocklist(blocklist))
   181  }
   182  
   183  // retrieveBlocklist reads the set of blocked nodes from the data base.
   184  // In case no database entry exists, an empty set (nil map) is returned.
   185  // No errors are expected during normal operations.
   186  func retrieveBlocklist(db *badger.DB) (IdentifierSet, error) {
   187  	var blocklist map[flow.Identifier]struct{}
   188  	err := db.View(operation.RetrieveBlocklist(&blocklist))
   189  	if err != nil && !errors.Is(err, storage.ErrNotFound) {
   190  		return nil, fmt.Errorf("unexpected error reading set of blocked nodes from data base: %w", err)
   191  	}
   192  	return blocklist, nil
   193  }