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 }