github.com/MetalBlockchain/metalgo@v1.11.9/indexer/indexer.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package indexer 5 6 import ( 7 "fmt" 8 "io" 9 "sync" 10 11 "github.com/gorilla/rpc/v2" 12 "go.uber.org/zap" 13 14 "github.com/MetalBlockchain/metalgo/api/server" 15 "github.com/MetalBlockchain/metalgo/chains" 16 "github.com/MetalBlockchain/metalgo/database" 17 "github.com/MetalBlockchain/metalgo/database/prefixdb" 18 "github.com/MetalBlockchain/metalgo/ids" 19 "github.com/MetalBlockchain/metalgo/snow" 20 "github.com/MetalBlockchain/metalgo/snow/engine/avalanche/vertex" 21 "github.com/MetalBlockchain/metalgo/snow/engine/common" 22 "github.com/MetalBlockchain/metalgo/snow/engine/snowman/block" 23 "github.com/MetalBlockchain/metalgo/utils/constants" 24 "github.com/MetalBlockchain/metalgo/utils/json" 25 "github.com/MetalBlockchain/metalgo/utils/logging" 26 "github.com/MetalBlockchain/metalgo/utils/timer/mockable" 27 "github.com/MetalBlockchain/metalgo/utils/wrappers" 28 ) 29 30 const ( 31 indexNamePrefix = "index-" 32 txPrefix = 0x01 33 vtxPrefix = 0x02 34 blockPrefix = 0x03 35 isIncompletePrefix = 0x04 36 previouslyIndexedPrefix = 0x05 37 ) 38 39 var ( 40 _ Indexer = (*indexer)(nil) 41 42 hasRunKey = []byte{0x07} 43 ) 44 45 // Config for an indexer 46 type Config struct { 47 DB database.Database 48 Log logging.Logger 49 IndexingEnabled bool 50 AllowIncompleteIndex bool 51 BlockAcceptorGroup snow.AcceptorGroup 52 TxAcceptorGroup snow.AcceptorGroup 53 VertexAcceptorGroup snow.AcceptorGroup 54 APIServer server.PathAdder 55 ShutdownF func() 56 } 57 58 // Indexer causes accepted containers for a given chain 59 // to be indexed by their ID and by the order in which 60 // they were accepted by this node. 61 // Indexer is threadsafe. 62 type Indexer interface { 63 chains.Registrant 64 // Close will do nothing and return nil after the first call 65 io.Closer 66 } 67 68 // NewIndexer returns a new Indexer and registers a new endpoint on the given API server. 69 func NewIndexer(config Config) (Indexer, error) { 70 indexer := &indexer{ 71 log: config.Log, 72 db: config.DB, 73 allowIncompleteIndex: config.AllowIncompleteIndex, 74 indexingEnabled: config.IndexingEnabled, 75 blockAcceptorGroup: config.BlockAcceptorGroup, 76 txAcceptorGroup: config.TxAcceptorGroup, 77 vertexAcceptorGroup: config.VertexAcceptorGroup, 78 txIndices: map[ids.ID]*index{}, 79 vtxIndices: map[ids.ID]*index{}, 80 blockIndices: map[ids.ID]*index{}, 81 pathAdder: config.APIServer, 82 shutdownF: config.ShutdownF, 83 } 84 85 hasRun, err := indexer.hasRun() 86 if err != nil { 87 return nil, err 88 } 89 indexer.hasRunBefore = hasRun 90 return indexer, indexer.markHasRun() 91 } 92 93 type indexer struct { 94 clock mockable.Clock 95 lock sync.RWMutex 96 log logging.Logger 97 db database.Database 98 closed bool 99 100 // Called in a goroutine on shutdown 101 shutdownF func() 102 103 // true if this is not the first run using this database 104 hasRunBefore bool 105 106 // Used to add API endpoint for new indices 107 pathAdder server.PathAdder 108 109 // If true, allow running in such a way that could allow the creation 110 // of an index which could be missing accepted containers. 111 allowIncompleteIndex bool 112 113 // If false, don't create index for a chain when RegisterChain is called 114 indexingEnabled bool 115 116 // Chain ID --> index of blocks of that chain (if applicable) 117 blockIndices map[ids.ID]*index 118 // Chain ID --> index of vertices of that chain (if applicable) 119 vtxIndices map[ids.ID]*index 120 // Chain ID --> index of txs of that chain (if applicable) 121 txIndices map[ids.ID]*index 122 123 // Notifies of newly accepted blocks 124 blockAcceptorGroup snow.AcceptorGroup 125 // Notifies of newly accepted transactions 126 txAcceptorGroup snow.AcceptorGroup 127 // Notifies of newly accepted vertices 128 vertexAcceptorGroup snow.AcceptorGroup 129 } 130 131 // Assumes [ctx.Lock] is not held 132 func (i *indexer) RegisterChain(chainName string, ctx *snow.ConsensusContext, vm common.VM) { 133 i.lock.Lock() 134 defer i.lock.Unlock() 135 136 if i.closed { 137 i.log.Debug("not registering chain to indexer", 138 zap.String("reason", "indexer is closed"), 139 zap.String("chainName", chainName), 140 ) 141 return 142 } else if ctx.SubnetID != constants.PrimaryNetworkID { 143 i.log.Debug("not registering chain to indexer", 144 zap.String("reason", "not in the primary network"), 145 zap.String("chainName", chainName), 146 ) 147 return 148 } 149 150 chainID := ctx.ChainID 151 if i.blockIndices[chainID] != nil || i.txIndices[chainID] != nil || i.vtxIndices[chainID] != nil { 152 i.log.Warn("chain is already being indexed", 153 zap.Stringer("chainID", chainID), 154 ) 155 return 156 } 157 158 // If the index is incomplete, make sure that's OK. Otherwise, cause node to die. 159 isIncomplete, err := i.isIncomplete(chainID) 160 if err != nil { 161 i.log.Error("couldn't get whether chain is incomplete", 162 zap.String("chainName", chainName), 163 zap.Error(err), 164 ) 165 if err := i.close(); err != nil { 166 i.log.Error("failed to close indexer", 167 zap.Error(err), 168 ) 169 } 170 return 171 } 172 173 // See if this chain was indexed in a previous run 174 previouslyIndexed, err := i.previouslyIndexed(chainID) 175 if err != nil { 176 i.log.Error("couldn't get whether chain was previously indexed", 177 zap.String("chainName", chainName), 178 zap.Error(err), 179 ) 180 if err := i.close(); err != nil { 181 i.log.Error("failed to close indexer", 182 zap.Error(err), 183 ) 184 } 185 return 186 } 187 188 if !i.indexingEnabled { // Indexing is disabled 189 if previouslyIndexed && !i.allowIncompleteIndex { 190 // We indexed this chain in a previous run but not in this run. 191 // This would create an incomplete index, which is not allowed, so exit. 192 i.log.Fatal("running would cause index to become incomplete but incomplete indices are disabled", 193 zap.String("chainName", chainName), 194 ) 195 if err := i.close(); err != nil { 196 i.log.Error("failed to close indexer", 197 zap.Error(err), 198 ) 199 } 200 return 201 } 202 203 // Creating an incomplete index is allowed. Mark index as incomplete. 204 err := i.markIncomplete(chainID) 205 if err == nil { 206 return 207 } 208 i.log.Fatal("couldn't mark chain as incomplete", 209 zap.String("chainName", chainName), 210 zap.Error(err), 211 ) 212 if err := i.close(); err != nil { 213 i.log.Error("failed to close indexer", 214 zap.Error(err), 215 ) 216 } 217 return 218 } 219 220 if !i.allowIncompleteIndex && isIncomplete && (previouslyIndexed || i.hasRunBefore) { 221 i.log.Fatal("index is incomplete but incomplete indices are disabled. Shutting down", 222 zap.String("chainName", chainName), 223 ) 224 if err := i.close(); err != nil { 225 i.log.Error("failed to close indexer", 226 zap.Error(err), 227 ) 228 } 229 return 230 } 231 232 // Mark that in this run, this chain was indexed 233 if err := i.markPreviouslyIndexed(chainID); err != nil { 234 i.log.Error("couldn't mark chain as indexed", 235 zap.String("chainName", chainName), 236 zap.Error(err), 237 ) 238 if err := i.close(); err != nil { 239 i.log.Error("failed to close indexer", 240 zap.Error(err), 241 ) 242 } 243 return 244 } 245 246 index, err := i.registerChainHelper(chainID, blockPrefix, chainName, "block", i.blockAcceptorGroup) 247 if err != nil { 248 i.log.Fatal("failed to create index", 249 zap.String("chainName", chainName), 250 zap.String("endpoint", "block"), 251 zap.Error(err), 252 ) 253 if err := i.close(); err != nil { 254 i.log.Error("failed to close indexer", 255 zap.Error(err), 256 ) 257 } 258 return 259 } 260 i.blockIndices[chainID] = index 261 262 switch vm.(type) { 263 case vertex.DAGVM: 264 vtxIndex, err := i.registerChainHelper(chainID, vtxPrefix, chainName, "vtx", i.vertexAcceptorGroup) 265 if err != nil { 266 i.log.Fatal("couldn't create index", 267 zap.String("chainName", chainName), 268 zap.String("endpoint", "vtx"), 269 zap.Error(err), 270 ) 271 if err := i.close(); err != nil { 272 i.log.Error("failed to close indexer", 273 zap.Error(err), 274 ) 275 } 276 return 277 } 278 i.vtxIndices[chainID] = vtxIndex 279 280 txIndex, err := i.registerChainHelper(chainID, txPrefix, chainName, "tx", i.txAcceptorGroup) 281 if err != nil { 282 i.log.Fatal("couldn't create index", 283 zap.String("chainName", chainName), 284 zap.String("endpoint", "tx"), 285 zap.Error(err), 286 ) 287 if err := i.close(); err != nil { 288 i.log.Error("failed to close indexer", 289 zap.Error(err), 290 ) 291 } 292 return 293 } 294 i.txIndices[chainID] = txIndex 295 case block.ChainVM: 296 default: 297 vmType := fmt.Sprintf("%T", vm) 298 i.log.Error("got unexpected vm type", 299 zap.String("vmType", vmType), 300 ) 301 if err := i.close(); err != nil { 302 i.log.Error("failed to close indexer", 303 zap.Error(err), 304 ) 305 } 306 } 307 } 308 309 func (i *indexer) registerChainHelper( 310 chainID ids.ID, 311 prefixEnd byte, 312 name, endpoint string, 313 acceptorGroup snow.AcceptorGroup, 314 ) (*index, error) { 315 prefix := make([]byte, ids.IDLen+wrappers.ByteLen) 316 copy(prefix, chainID[:]) 317 prefix[ids.IDLen] = prefixEnd 318 indexDB := prefixdb.New(prefix, i.db) 319 index, err := newIndex(indexDB, i.log, i.clock) 320 if err != nil { 321 _ = indexDB.Close() 322 return nil, err 323 } 324 325 // Register index to learn about new accepted vertices 326 if err := acceptorGroup.RegisterAcceptor(chainID, fmt.Sprintf("%s%s", indexNamePrefix, chainID), index, true); err != nil { 327 _ = index.Close() 328 return nil, err 329 } 330 331 // Create an API endpoint for this index 332 apiServer := rpc.NewServer() 333 codec := json.NewCodec() 334 apiServer.RegisterCodec(codec, "application/json") 335 apiServer.RegisterCodec(codec, "application/json;charset=UTF-8") 336 if err := apiServer.RegisterService(&service{index: index}, "index"); err != nil { 337 _ = index.Close() 338 return nil, err 339 } 340 if err := i.pathAdder.AddRoute(apiServer, "index/"+name, "/"+endpoint); err != nil { 341 _ = index.Close() 342 return nil, err 343 } 344 return index, nil 345 } 346 347 // Close this indexer. Stops indexing all chains. 348 // Closes [i.db]. Assumes Close is only called after 349 // the node is done making decisions. 350 // Calling Close after it has been called does nothing. 351 func (i *indexer) Close() error { 352 i.lock.Lock() 353 defer i.lock.Unlock() 354 355 return i.close() 356 } 357 358 func (i *indexer) close() error { 359 if i.closed { 360 return nil 361 } 362 i.closed = true 363 364 errs := &wrappers.Errs{} 365 for chainID, txIndex := range i.txIndices { 366 errs.Add( 367 txIndex.Close(), 368 i.txAcceptorGroup.DeregisterAcceptor(chainID, fmt.Sprintf("%s%s", indexNamePrefix, chainID)), 369 ) 370 } 371 for chainID, vtxIndex := range i.vtxIndices { 372 errs.Add( 373 vtxIndex.Close(), 374 i.vertexAcceptorGroup.DeregisterAcceptor(chainID, fmt.Sprintf("%s%s", indexNamePrefix, chainID)), 375 ) 376 } 377 for chainID, blockIndex := range i.blockIndices { 378 errs.Add( 379 blockIndex.Close(), 380 i.blockAcceptorGroup.DeregisterAcceptor(chainID, fmt.Sprintf("%s%s", indexNamePrefix, chainID)), 381 ) 382 } 383 errs.Add(i.db.Close()) 384 385 go i.shutdownF() 386 return errs.Err 387 } 388 389 func (i *indexer) markIncomplete(chainID ids.ID) error { 390 key := make([]byte, ids.IDLen+wrappers.ByteLen) 391 copy(key, chainID[:]) 392 key[ids.IDLen] = isIncompletePrefix 393 return i.db.Put(key, nil) 394 } 395 396 // Returns true if this chain is incomplete 397 func (i *indexer) isIncomplete(chainID ids.ID) (bool, error) { 398 key := make([]byte, ids.IDLen+wrappers.ByteLen) 399 copy(key, chainID[:]) 400 key[ids.IDLen] = isIncompletePrefix 401 return i.db.Has(key) 402 } 403 404 func (i *indexer) markPreviouslyIndexed(chainID ids.ID) error { 405 key := make([]byte, ids.IDLen+wrappers.ByteLen) 406 copy(key, chainID[:]) 407 key[ids.IDLen] = previouslyIndexedPrefix 408 return i.db.Put(key, nil) 409 } 410 411 // Returns true if this chain is incomplete 412 func (i *indexer) previouslyIndexed(chainID ids.ID) (bool, error) { 413 key := make([]byte, ids.IDLen+wrappers.ByteLen) 414 copy(key, chainID[:]) 415 key[ids.IDLen] = previouslyIndexedPrefix 416 return i.db.Has(key) 417 } 418 419 // Mark that the node has run at least once 420 func (i *indexer) markHasRun() error { 421 return i.db.Put(hasRunKey, nil) 422 } 423 424 // Returns true if the node has run before 425 func (i *indexer) hasRun() (bool, error) { 426 return i.db.Has(hasRunKey) 427 }