github.com/MetalBlockchain/metalgo@v1.11.9/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 "math" 11 "net/http" 12 "time" 13 14 "github.com/gorilla/rpc/v2" 15 "go.uber.org/zap" 16 17 "github.com/MetalBlockchain/metalgo/api/metrics" 18 "github.com/MetalBlockchain/metalgo/cache" 19 "github.com/MetalBlockchain/metalgo/codec" 20 "github.com/MetalBlockchain/metalgo/codec/linearcodec" 21 "github.com/MetalBlockchain/metalgo/database" 22 "github.com/MetalBlockchain/metalgo/ids" 23 "github.com/MetalBlockchain/metalgo/snow" 24 "github.com/MetalBlockchain/metalgo/snow/consensus/snowman" 25 "github.com/MetalBlockchain/metalgo/snow/engine/common" 26 "github.com/MetalBlockchain/metalgo/snow/uptime" 27 "github.com/MetalBlockchain/metalgo/snow/validators" 28 "github.com/MetalBlockchain/metalgo/utils" 29 "github.com/MetalBlockchain/metalgo/utils/constants" 30 "github.com/MetalBlockchain/metalgo/utils/json" 31 "github.com/MetalBlockchain/metalgo/utils/logging" 32 "github.com/MetalBlockchain/metalgo/utils/timer/mockable" 33 "github.com/MetalBlockchain/metalgo/version" 34 "github.com/MetalBlockchain/metalgo/vms/components/avax" 35 "github.com/MetalBlockchain/metalgo/vms/platformvm/block" 36 "github.com/MetalBlockchain/metalgo/vms/platformvm/config" 37 "github.com/MetalBlockchain/metalgo/vms/platformvm/fx" 38 "github.com/MetalBlockchain/metalgo/vms/platformvm/network" 39 "github.com/MetalBlockchain/metalgo/vms/platformvm/reward" 40 "github.com/MetalBlockchain/metalgo/vms/platformvm/state" 41 "github.com/MetalBlockchain/metalgo/vms/platformvm/txs" 42 "github.com/MetalBlockchain/metalgo/vms/platformvm/utxo" 43 "github.com/MetalBlockchain/metalgo/vms/secp256k1fx" 44 "github.com/MetalBlockchain/metalgo/vms/txs/mempool" 45 46 snowmanblock "github.com/MetalBlockchain/metalgo/snow/engine/snowman/block" 47 blockbuilder "github.com/MetalBlockchain/metalgo/vms/platformvm/block/builder" 48 blockexecutor "github.com/MetalBlockchain/metalgo/vms/platformvm/block/executor" 49 platformvmmetrics "github.com/MetalBlockchain/metalgo/vms/platformvm/metrics" 50 txexecutor "github.com/MetalBlockchain/metalgo/vms/platformvm/txs/executor" 51 pmempool "github.com/MetalBlockchain/metalgo/vms/platformvm/txs/mempool" 52 pvalidators "github.com/MetalBlockchain/metalgo/vms/platformvm/validators" 53 ) 54 55 var ( 56 _ snowmanblock.ChainVM = (*VM)(nil) 57 _ secp256k1fx.VM = (*VM)(nil) 58 _ validators.State = (*VM)(nil) 59 _ validators.SubnetConnector = (*VM)(nil) 60 ) 61 62 type VM struct { 63 config.Config 64 blockbuilder.Builder 65 *network.Network 66 validators.State 67 68 metrics platformvmmetrics.Metrics 69 70 // Used to get time. Useful for faking time during tests. 71 clock mockable.Clock 72 73 uptimeManager uptime.Manager 74 75 // The context of this vm 76 ctx *snow.Context 77 db database.Database 78 79 state state.State 80 81 fx fx.Fx 82 codecRegistry codec.Registry 83 84 // Bootstrapped remembers if this chain has finished bootstrapping or not 85 bootstrapped utils.Atomic[bool] 86 87 manager blockexecutor.Manager 88 89 // Cancelled on shutdown 90 onShutdownCtx context.Context 91 // Call [onShutdownCtxCancel] to cancel [onShutdownCtx] during Shutdown() 92 onShutdownCtxCancel context.CancelFunc 93 } 94 95 // Initialize this blockchain. 96 // [vm.ChainManager] and [vm.vdrMgr] must be set before this function is called. 97 func (vm *VM) Initialize( 98 ctx context.Context, 99 chainCtx *snow.Context, 100 db database.Database, 101 genesisBytes []byte, 102 _ []byte, 103 configBytes []byte, 104 toEngine chan<- common.Message, 105 _ []*common.Fx, 106 appSender common.AppSender, 107 ) error { 108 chainCtx.Log.Verbo("initializing platform chain") 109 110 execConfig, err := config.GetExecutionConfig(configBytes) 111 if err != nil { 112 return err 113 } 114 chainCtx.Log.Info("using VM execution config", zap.Reflect("config", execConfig)) 115 116 registerer, err := metrics.MakeAndRegister(chainCtx.Metrics, "") 117 if err != nil { 118 return err 119 } 120 121 // Initialize metrics as soon as possible 122 vm.metrics, err = platformvmmetrics.New(registerer) 123 if err != nil { 124 return fmt.Errorf("failed to initialize metrics: %w", err) 125 } 126 127 vm.ctx = chainCtx 128 vm.db = db 129 130 // Note: this codec is never used to serialize anything 131 vm.codecRegistry = linearcodec.NewDefault() 132 vm.fx = &secp256k1fx.Fx{} 133 if err := vm.fx.Initialize(vm); err != nil { 134 return err 135 } 136 137 rewards := reward.NewCalculator(vm.RewardConfig) 138 139 vm.state, err = state.New( 140 vm.db, 141 genesisBytes, 142 registerer, 143 &vm.Config, 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.PackBlockTxs(math.MaxInt) 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 }