github.com/ava-labs/avalanchego@v1.11.11/vms/platformvm/vm.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package platformvm 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "net/http" 11 "time" 12 13 "github.com/gorilla/rpc/v2" 14 "go.uber.org/zap" 15 16 "github.com/ava-labs/avalanchego/api/metrics" 17 "github.com/ava-labs/avalanchego/cache" 18 "github.com/ava-labs/avalanchego/codec" 19 "github.com/ava-labs/avalanchego/codec/linearcodec" 20 "github.com/ava-labs/avalanchego/database" 21 "github.com/ava-labs/avalanchego/ids" 22 "github.com/ava-labs/avalanchego/snow" 23 "github.com/ava-labs/avalanchego/snow/consensus/snowman" 24 "github.com/ava-labs/avalanchego/snow/engine/common" 25 "github.com/ava-labs/avalanchego/snow/uptime" 26 "github.com/ava-labs/avalanchego/snow/validators" 27 "github.com/ava-labs/avalanchego/utils" 28 "github.com/ava-labs/avalanchego/utils/constants" 29 "github.com/ava-labs/avalanchego/utils/json" 30 "github.com/ava-labs/avalanchego/utils/logging" 31 "github.com/ava-labs/avalanchego/utils/timer/mockable" 32 "github.com/ava-labs/avalanchego/version" 33 "github.com/ava-labs/avalanchego/vms/components/avax" 34 "github.com/ava-labs/avalanchego/vms/platformvm/block" 35 "github.com/ava-labs/avalanchego/vms/platformvm/config" 36 "github.com/ava-labs/avalanchego/vms/platformvm/fx" 37 "github.com/ava-labs/avalanchego/vms/platformvm/network" 38 "github.com/ava-labs/avalanchego/vms/platformvm/reward" 39 "github.com/ava-labs/avalanchego/vms/platformvm/state" 40 "github.com/ava-labs/avalanchego/vms/platformvm/txs" 41 "github.com/ava-labs/avalanchego/vms/platformvm/utxo" 42 "github.com/ava-labs/avalanchego/vms/secp256k1fx" 43 "github.com/ava-labs/avalanchego/vms/txs/mempool" 44 45 snowmanblock "github.com/ava-labs/avalanchego/snow/engine/snowman/block" 46 blockbuilder "github.com/ava-labs/avalanchego/vms/platformvm/block/builder" 47 blockexecutor "github.com/ava-labs/avalanchego/vms/platformvm/block/executor" 48 platformvmmetrics "github.com/ava-labs/avalanchego/vms/platformvm/metrics" 49 txexecutor "github.com/ava-labs/avalanchego/vms/platformvm/txs/executor" 50 pmempool "github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool" 51 pvalidators "github.com/ava-labs/avalanchego/vms/platformvm/validators" 52 ) 53 54 var ( 55 _ snowmanblock.ChainVM = (*VM)(nil) 56 _ secp256k1fx.VM = (*VM)(nil) 57 _ validators.State = (*VM)(nil) 58 _ validators.SubnetConnector = (*VM)(nil) 59 ) 60 61 type VM struct { 62 config.Config 63 blockbuilder.Builder 64 *network.Network 65 validators.State 66 67 metrics platformvmmetrics.Metrics 68 69 // Used to get time. Useful for faking time during tests. 70 clock mockable.Clock 71 72 uptimeManager uptime.Manager 73 74 // The context of this vm 75 ctx *snow.Context 76 db database.Database 77 78 state state.State 79 80 fx fx.Fx 81 codecRegistry codec.Registry 82 83 // Bootstrapped remembers if this chain has finished bootstrapping or not 84 bootstrapped utils.Atomic[bool] 85 86 manager blockexecutor.Manager 87 88 // Cancelled on shutdown 89 onShutdownCtx context.Context 90 // Call [onShutdownCtxCancel] to cancel [onShutdownCtx] during Shutdown() 91 onShutdownCtxCancel context.CancelFunc 92 } 93 94 // Initialize this blockchain. 95 // [vm.ChainManager] and [vm.vdrMgr] must be set before this function is called. 96 func (vm *VM) Initialize( 97 ctx context.Context, 98 chainCtx *snow.Context, 99 db database.Database, 100 genesisBytes []byte, 101 _ []byte, 102 configBytes []byte, 103 toEngine chan<- common.Message, 104 _ []*common.Fx, 105 appSender common.AppSender, 106 ) error { 107 chainCtx.Log.Verbo("initializing platform chain") 108 109 execConfig, err := config.GetExecutionConfig(configBytes) 110 if err != nil { 111 return err 112 } 113 chainCtx.Log.Info("using VM execution config", zap.Reflect("config", execConfig)) 114 115 registerer, err := metrics.MakeAndRegister(chainCtx.Metrics, "") 116 if err != nil { 117 return err 118 } 119 120 // Initialize metrics as soon as possible 121 vm.metrics, err = platformvmmetrics.New(registerer) 122 if err != nil { 123 return fmt.Errorf("failed to initialize metrics: %w", err) 124 } 125 126 vm.ctx = chainCtx 127 vm.db = db 128 129 // Note: this codec is never used to serialize anything 130 vm.codecRegistry = linearcodec.NewDefault() 131 vm.fx = &secp256k1fx.Fx{} 132 if err := vm.fx.Initialize(vm); err != nil { 133 return err 134 } 135 136 rewards := reward.NewCalculator(vm.RewardConfig) 137 138 vm.state, err = state.New( 139 vm.db, 140 genesisBytes, 141 registerer, 142 vm.Config.Validators, 143 vm.Config.UpgradeConfig, 144 execConfig, 145 vm.ctx, 146 vm.metrics, 147 rewards, 148 ) 149 if err != nil { 150 return err 151 } 152 153 validatorManager := pvalidators.NewManager(chainCtx.Log, vm.Config, vm.state, vm.metrics, &vm.clock) 154 vm.State = validatorManager 155 utxoVerifier := utxo.NewVerifier(vm.ctx, &vm.clock, vm.fx) 156 vm.uptimeManager = uptime.NewManager(vm.state, &vm.clock) 157 vm.UptimeLockedCalculator.SetCalculator(&vm.bootstrapped, &chainCtx.Lock, vm.uptimeManager) 158 159 txExecutorBackend := &txexecutor.Backend{ 160 Config: &vm.Config, 161 Ctx: vm.ctx, 162 Clk: &vm.clock, 163 Fx: vm.fx, 164 FlowChecker: utxoVerifier, 165 Uptimes: vm.uptimeManager, 166 Rewards: rewards, 167 Bootstrapped: &vm.bootstrapped, 168 } 169 170 mempool, err := pmempool.New("mempool", registerer, toEngine) 171 if err != nil { 172 return fmt.Errorf("failed to create mempool: %w", err) 173 } 174 175 vm.manager = blockexecutor.NewManager( 176 mempool, 177 vm.metrics, 178 vm.state, 179 txExecutorBackend, 180 validatorManager, 181 ) 182 183 txVerifier := network.NewLockedTxVerifier(&txExecutorBackend.Ctx.Lock, vm.manager) 184 vm.Network, err = network.New( 185 chainCtx.Log, 186 chainCtx.NodeID, 187 chainCtx.SubnetID, 188 validators.NewLockedState( 189 &chainCtx.Lock, 190 validatorManager, 191 ), 192 txVerifier, 193 mempool, 194 txExecutorBackend.Config.PartialSyncPrimaryNetwork, 195 appSender, 196 registerer, 197 execConfig.Network, 198 ) 199 if err != nil { 200 return fmt.Errorf("failed to initialize network: %w", err) 201 } 202 203 vm.onShutdownCtx, vm.onShutdownCtxCancel = context.WithCancel(context.Background()) 204 // TODO: Wait for this goroutine to exit during Shutdown once the platformvm 205 // has better control of the context lock. 206 go vm.Network.PushGossip(vm.onShutdownCtx) 207 go vm.Network.PullGossip(vm.onShutdownCtx) 208 209 vm.Builder = blockbuilder.New( 210 mempool, 211 txExecutorBackend, 212 vm.manager, 213 ) 214 215 // Create all of the chains that the database says exist 216 if err := vm.initBlockchains(); err != nil { 217 return fmt.Errorf( 218 "failed to initialize blockchains: %w", 219 err, 220 ) 221 } 222 223 lastAcceptedID := vm.state.GetLastAccepted() 224 chainCtx.Log.Info("initializing last accepted", 225 zap.Stringer("blkID", lastAcceptedID), 226 ) 227 if err := vm.SetPreference(ctx, lastAcceptedID); err != nil { 228 return err 229 } 230 231 // Incrementing [awaitShutdown] would cause a deadlock since 232 // [periodicallyPruneMempool] grabs the context lock. 233 go vm.periodicallyPruneMempool(execConfig.MempoolPruneFrequency) 234 235 go func() { 236 err := vm.state.ReindexBlocks(&vm.ctx.Lock, vm.ctx.Log) 237 if err != nil { 238 vm.ctx.Log.Warn("reindexing blocks failed", 239 zap.Error(err), 240 ) 241 } 242 }() 243 244 return nil 245 } 246 247 func (vm *VM) periodicallyPruneMempool(frequency time.Duration) { 248 ticker := time.NewTicker(frequency) 249 defer ticker.Stop() 250 251 for { 252 select { 253 case <-vm.onShutdownCtx.Done(): 254 return 255 case <-ticker.C: 256 if err := vm.pruneMempool(); err != nil { 257 vm.ctx.Log.Debug("pruning mempool failed", 258 zap.Error(err), 259 ) 260 } 261 } 262 } 263 } 264 265 func (vm *VM) pruneMempool() error { 266 vm.ctx.Lock.Lock() 267 defer vm.ctx.Lock.Unlock() 268 269 // Packing all of the transactions in order performs additional checks that 270 // the MempoolTxVerifier doesn't include. So, evicting transactions from 271 // here is expected to happen occasionally. 272 blockTxs, err := vm.Builder.PackAllBlockTxs() 273 if err != nil { 274 return err 275 } 276 277 for _, tx := range blockTxs { 278 if err := vm.Builder.Add(tx); err != nil { 279 vm.ctx.Log.Debug( 280 "failed to reissue tx", 281 zap.Stringer("txID", tx.ID()), 282 zap.Error(err), 283 ) 284 } 285 } 286 287 return nil 288 } 289 290 // Create all chains that exist that this node validates. 291 func (vm *VM) initBlockchains() error { 292 if vm.Config.PartialSyncPrimaryNetwork { 293 vm.ctx.Log.Info("skipping primary network chain creation") 294 } else if err := vm.createSubnet(constants.PrimaryNetworkID); err != nil { 295 return err 296 } 297 298 if vm.SybilProtectionEnabled { 299 for subnetID := range vm.TrackedSubnets { 300 if err := vm.createSubnet(subnetID); err != nil { 301 return err 302 } 303 } 304 } else { 305 subnetIDs, err := vm.state.GetSubnetIDs() 306 if err != nil { 307 return err 308 } 309 for _, subnetID := range subnetIDs { 310 if err := vm.createSubnet(subnetID); err != nil { 311 return err 312 } 313 } 314 } 315 return nil 316 } 317 318 // Create the subnet with ID [subnetID] 319 func (vm *VM) createSubnet(subnetID ids.ID) error { 320 chains, err := vm.state.GetChains(subnetID) 321 if err != nil { 322 return err 323 } 324 for _, chain := range chains { 325 tx, ok := chain.Unsigned.(*txs.CreateChainTx) 326 if !ok { 327 return fmt.Errorf("expected tx type *txs.CreateChainTx but got %T", chain.Unsigned) 328 } 329 vm.Config.CreateChain(chain.ID(), tx) 330 } 331 return nil 332 } 333 334 // onBootstrapStarted marks this VM as bootstrapping 335 func (vm *VM) onBootstrapStarted() error { 336 vm.bootstrapped.Set(false) 337 return vm.fx.Bootstrapping() 338 } 339 340 // onNormalOperationsStarted marks this VM as bootstrapped 341 func (vm *VM) onNormalOperationsStarted() error { 342 if vm.bootstrapped.Get() { 343 return nil 344 } 345 vm.bootstrapped.Set(true) 346 347 if err := vm.fx.Bootstrapped(); err != nil { 348 return err 349 } 350 351 primaryVdrIDs := vm.Validators.GetValidatorIDs(constants.PrimaryNetworkID) 352 if err := vm.uptimeManager.StartTracking(primaryVdrIDs, constants.PrimaryNetworkID); err != nil { 353 return err 354 } 355 356 vl := validators.NewLogger(vm.ctx.Log, constants.PrimaryNetworkID, vm.ctx.NodeID) 357 vm.Validators.RegisterSetCallbackListener(constants.PrimaryNetworkID, vl) 358 359 for subnetID := range vm.TrackedSubnets { 360 vdrIDs := vm.Validators.GetValidatorIDs(subnetID) 361 if err := vm.uptimeManager.StartTracking(vdrIDs, subnetID); err != nil { 362 return err 363 } 364 365 vl := validators.NewLogger(vm.ctx.Log, subnetID, vm.ctx.NodeID) 366 vm.Validators.RegisterSetCallbackListener(subnetID, vl) 367 } 368 369 if err := vm.state.Commit(); err != nil { 370 return err 371 } 372 373 // Start the block builder 374 vm.Builder.StartBlockTimer() 375 return nil 376 } 377 378 func (vm *VM) SetState(_ context.Context, state snow.State) error { 379 switch state { 380 case snow.Bootstrapping: 381 return vm.onBootstrapStarted() 382 case snow.NormalOp: 383 return vm.onNormalOperationsStarted() 384 default: 385 return snow.ErrUnknownState 386 } 387 } 388 389 // Shutdown this blockchain 390 func (vm *VM) Shutdown(context.Context) error { 391 if vm.db == nil { 392 return nil 393 } 394 395 vm.onShutdownCtxCancel() 396 vm.Builder.ShutdownBlockTimer() 397 398 if vm.bootstrapped.Get() { 399 primaryVdrIDs := vm.Validators.GetValidatorIDs(constants.PrimaryNetworkID) 400 if err := vm.uptimeManager.StopTracking(primaryVdrIDs, constants.PrimaryNetworkID); err != nil { 401 return err 402 } 403 404 for subnetID := range vm.TrackedSubnets { 405 vdrIDs := vm.Validators.GetValidatorIDs(subnetID) 406 if err := vm.uptimeManager.StopTracking(vdrIDs, subnetID); err != nil { 407 return err 408 } 409 } 410 411 if err := vm.state.Commit(); err != nil { 412 return err 413 } 414 } 415 416 return errors.Join( 417 vm.state.Close(), 418 vm.db.Close(), 419 ) 420 } 421 422 func (vm *VM) ParseBlock(_ context.Context, b []byte) (snowman.Block, error) { 423 // Note: blocks to be parsed are not verified, so we must used blocks.Codec 424 // rather than blocks.GenesisCodec 425 statelessBlk, err := block.Parse(block.Codec, b) 426 if err != nil { 427 return nil, err 428 } 429 return vm.manager.NewBlock(statelessBlk), nil 430 } 431 432 func (vm *VM) GetBlock(_ context.Context, blkID ids.ID) (snowman.Block, error) { 433 return vm.manager.GetBlock(blkID) 434 } 435 436 // LastAccepted returns the block most recently accepted 437 func (vm *VM) LastAccepted(context.Context) (ids.ID, error) { 438 return vm.manager.LastAccepted(), nil 439 } 440 441 // SetPreference sets the preferred block to be the one with ID [blkID] 442 func (vm *VM) SetPreference(_ context.Context, blkID ids.ID) error { 443 if vm.manager.SetPreference(blkID) { 444 vm.Builder.ResetBlockTimer() 445 } 446 return nil 447 } 448 449 func (*VM) Version(context.Context) (string, error) { 450 return version.Current.String(), nil 451 } 452 453 // CreateHandlers returns a map where: 454 // * keys are API endpoint extensions 455 // * values are API handlers 456 func (vm *VM) CreateHandlers(context.Context) (map[string]http.Handler, error) { 457 server := rpc.NewServer() 458 server.RegisterCodec(json.NewCodec(), "application/json") 459 server.RegisterCodec(json.NewCodec(), "application/json;charset=UTF-8") 460 server.RegisterInterceptFunc(vm.metrics.InterceptRequest) 461 server.RegisterAfterFunc(vm.metrics.AfterRequest) 462 service := &Service{ 463 vm: vm, 464 addrManager: avax.NewAddressManager(vm.ctx), 465 stakerAttributesCache: &cache.LRU[ids.ID, *stakerAttributes]{ 466 Size: stakerAttributesCacheSize, 467 }, 468 } 469 err := server.RegisterService(service, "platform") 470 return map[string]http.Handler{ 471 "": server, 472 }, err 473 } 474 475 func (vm *VM) Connected(ctx context.Context, nodeID ids.NodeID, version *version.Application) error { 476 if err := vm.uptimeManager.Connect(nodeID, constants.PrimaryNetworkID); err != nil { 477 return err 478 } 479 return vm.Network.Connected(ctx, nodeID, version) 480 } 481 482 func (vm *VM) ConnectedSubnet(_ context.Context, nodeID ids.NodeID, subnetID ids.ID) error { 483 return vm.uptimeManager.Connect(nodeID, subnetID) 484 } 485 486 func (vm *VM) Disconnected(ctx context.Context, nodeID ids.NodeID) error { 487 if err := vm.uptimeManager.Disconnect(nodeID); err != nil { 488 return err 489 } 490 if err := vm.state.Commit(); err != nil { 491 return err 492 } 493 return vm.Network.Disconnected(ctx, nodeID) 494 } 495 496 func (vm *VM) CodecRegistry() codec.Registry { 497 return vm.codecRegistry 498 } 499 500 func (vm *VM) Clock() *mockable.Clock { 501 return &vm.clock 502 } 503 504 func (vm *VM) Logger() logging.Logger { 505 return vm.ctx.Log 506 } 507 508 func (vm *VM) GetBlockIDAtHeight(_ context.Context, height uint64) (ids.ID, error) { 509 return vm.state.GetBlockIDAtHeight(height) 510 } 511 512 func (vm *VM) issueTxFromRPC(tx *txs.Tx) error { 513 err := vm.Network.IssueTxFromRPC(tx) 514 if err != nil && !errors.Is(err, mempool.ErrDuplicateTx) { 515 vm.ctx.Log.Debug("failed to add tx to mempool", 516 zap.Stringer("txID", tx.ID()), 517 zap.Error(err), 518 ) 519 return err 520 } 521 522 return nil 523 }