github.com/MetalBlockchain/metalgo@v1.11.9/vms/avm/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 avm 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "net/http" 11 "reflect" 12 "sync" 13 14 "github.com/gorilla/rpc/v2" 15 "github.com/prometheus/client_golang/prometheus" 16 "go.uber.org/zap" 17 18 "github.com/MetalBlockchain/metalgo/api/metrics" 19 "github.com/MetalBlockchain/metalgo/cache" 20 "github.com/MetalBlockchain/metalgo/database" 21 "github.com/MetalBlockchain/metalgo/database/versiondb" 22 "github.com/MetalBlockchain/metalgo/ids" 23 "github.com/MetalBlockchain/metalgo/pubsub" 24 "github.com/MetalBlockchain/metalgo/snow" 25 "github.com/MetalBlockchain/metalgo/snow/consensus/snowman" 26 "github.com/MetalBlockchain/metalgo/snow/consensus/snowstorm" 27 "github.com/MetalBlockchain/metalgo/snow/engine/avalanche/vertex" 28 "github.com/MetalBlockchain/metalgo/snow/engine/common" 29 "github.com/MetalBlockchain/metalgo/utils/json" 30 "github.com/MetalBlockchain/metalgo/utils/linked" 31 "github.com/MetalBlockchain/metalgo/utils/set" 32 "github.com/MetalBlockchain/metalgo/utils/timer/mockable" 33 "github.com/MetalBlockchain/metalgo/version" 34 "github.com/MetalBlockchain/metalgo/vms/avm/block" 35 "github.com/MetalBlockchain/metalgo/vms/avm/config" 36 "github.com/MetalBlockchain/metalgo/vms/avm/network" 37 "github.com/MetalBlockchain/metalgo/vms/avm/state" 38 "github.com/MetalBlockchain/metalgo/vms/avm/txs" 39 "github.com/MetalBlockchain/metalgo/vms/avm/utxo" 40 "github.com/MetalBlockchain/metalgo/vms/components/avax" 41 "github.com/MetalBlockchain/metalgo/vms/components/index" 42 "github.com/MetalBlockchain/metalgo/vms/components/keystore" 43 "github.com/MetalBlockchain/metalgo/vms/secp256k1fx" 44 "github.com/MetalBlockchain/metalgo/vms/txs/mempool" 45 46 blockbuilder "github.com/MetalBlockchain/metalgo/vms/avm/block/builder" 47 blockexecutor "github.com/MetalBlockchain/metalgo/vms/avm/block/executor" 48 extensions "github.com/MetalBlockchain/metalgo/vms/avm/fxs" 49 avmmetrics "github.com/MetalBlockchain/metalgo/vms/avm/metrics" 50 txexecutor "github.com/MetalBlockchain/metalgo/vms/avm/txs/executor" 51 xmempool "github.com/MetalBlockchain/metalgo/vms/avm/txs/mempool" 52 ) 53 54 const assetToFxCacheSize = 1024 55 56 var ( 57 errIncompatibleFx = errors.New("incompatible feature extension") 58 errUnknownFx = errors.New("unknown feature extension") 59 errGenesisAssetMustHaveState = errors.New("genesis asset must have non-empty state") 60 61 _ vertex.LinearizableVMWithEngine = (*VM)(nil) 62 ) 63 64 type VM struct { 65 network.Atomic 66 67 config.Config 68 69 metrics avmmetrics.Metrics 70 71 avax.AddressManager 72 ids.Aliaser 73 utxo.Spender 74 75 // Contains information of where this VM is executing 76 ctx *snow.Context 77 78 // Used to check local time 79 clock mockable.Clock 80 81 registerer prometheus.Registerer 82 83 connectedPeers map[ids.NodeID]*version.Application 84 85 parser block.Parser 86 87 pubsub *pubsub.Server 88 89 appSender common.AppSender 90 91 // State management 92 state state.State 93 94 // Set to true once this VM is marked as `Bootstrapped` by the engine 95 bootstrapped bool 96 97 // asset id that will be used for fees 98 feeAssetID ids.ID 99 100 // Asset ID --> Bit set with fx IDs the asset supports 101 assetToFxCache *cache.LRU[ids.ID, set.Bits64] 102 103 baseDB database.Database 104 db *versiondb.Database 105 106 typeToFxIndex map[reflect.Type]int 107 fxs []*extensions.ParsedFx 108 109 walletService WalletService 110 111 addressTxsIndexer index.AddressTxsIndexer 112 113 txBackend *txexecutor.Backend 114 115 // Cancelled on shutdown 116 onShutdownCtx context.Context 117 // Call [onShutdownCtxCancel] to cancel [onShutdownCtx] during Shutdown() 118 onShutdownCtxCancel context.CancelFunc 119 awaitShutdown sync.WaitGroup 120 121 networkConfig network.Config 122 // These values are only initialized after the chain has been linearized. 123 blockbuilder.Builder 124 chainManager blockexecutor.Manager 125 network *network.Network 126 } 127 128 func (vm *VM) Connected(ctx context.Context, nodeID ids.NodeID, version *version.Application) error { 129 // If the chain isn't linearized yet, we must track the peers externally 130 // until the network is initialized. 131 if vm.network == nil { 132 vm.connectedPeers[nodeID] = version 133 return nil 134 } 135 return vm.network.Connected(ctx, nodeID, version) 136 } 137 138 func (vm *VM) Disconnected(ctx context.Context, nodeID ids.NodeID) error { 139 // If the chain isn't linearized yet, we must track the peers externally 140 // until the network is initialized. 141 if vm.network == nil { 142 delete(vm.connectedPeers, nodeID) 143 return nil 144 } 145 return vm.network.Disconnected(ctx, nodeID) 146 } 147 148 /* 149 ****************************************************************************** 150 ********************************* Common VM ********************************** 151 ****************************************************************************** 152 */ 153 154 func (vm *VM) Initialize( 155 _ context.Context, 156 ctx *snow.Context, 157 db database.Database, 158 genesisBytes []byte, 159 _ []byte, 160 configBytes []byte, 161 _ chan<- common.Message, 162 fxs []*common.Fx, 163 appSender common.AppSender, 164 ) error { 165 noopMessageHandler := common.NewNoOpAppHandler(ctx.Log) 166 vm.Atomic = network.NewAtomic(noopMessageHandler) 167 168 avmConfig, err := ParseConfig(configBytes) 169 if err != nil { 170 return err 171 } 172 ctx.Log.Info("VM config initialized", 173 zap.Reflect("config", avmConfig), 174 ) 175 176 vm.registerer, err = metrics.MakeAndRegister(ctx.Metrics, "") 177 if err != nil { 178 return err 179 } 180 181 vm.connectedPeers = make(map[ids.NodeID]*version.Application) 182 183 // Initialize metrics as soon as possible 184 vm.metrics, err = avmmetrics.New(vm.registerer) 185 if err != nil { 186 return fmt.Errorf("failed to initialize metrics: %w", err) 187 } 188 189 vm.AddressManager = avax.NewAddressManager(ctx) 190 vm.Aliaser = ids.NewAliaser() 191 192 vm.ctx = ctx 193 vm.appSender = appSender 194 vm.baseDB = db 195 vm.db = versiondb.New(db) 196 vm.assetToFxCache = &cache.LRU[ids.ID, set.Bits64]{Size: assetToFxCacheSize} 197 198 vm.pubsub = pubsub.New(ctx.Log) 199 200 typedFxs := make([]extensions.Fx, len(fxs)) 201 vm.fxs = make([]*extensions.ParsedFx, len(fxs)) 202 for i, fxContainer := range fxs { 203 if fxContainer == nil { 204 return errIncompatibleFx 205 } 206 fx, ok := fxContainer.Fx.(extensions.Fx) 207 if !ok { 208 return errIncompatibleFx 209 } 210 typedFxs[i] = fx 211 vm.fxs[i] = &extensions.ParsedFx{ 212 ID: fxContainer.ID, 213 Fx: fx, 214 } 215 } 216 217 vm.typeToFxIndex = map[reflect.Type]int{} 218 vm.parser, err = block.NewCustomParser( 219 vm.typeToFxIndex, 220 &vm.clock, 221 ctx.Log, 222 typedFxs, 223 ) 224 if err != nil { 225 return err 226 } 227 228 codec := vm.parser.Codec() 229 vm.Spender = utxo.NewSpender(&vm.clock, codec) 230 231 state, err := state.New( 232 vm.db, 233 vm.parser, 234 vm.registerer, 235 avmConfig.ChecksumsEnabled, 236 ) 237 if err != nil { 238 return err 239 } 240 241 vm.state = state 242 243 if err := vm.initGenesis(genesisBytes); err != nil { 244 return err 245 } 246 247 vm.walletService.vm = vm 248 vm.walletService.pendingTxs = linked.NewHashmap[ids.ID, *txs.Tx]() 249 250 // use no op impl when disabled in config 251 if avmConfig.IndexTransactions { 252 vm.ctx.Log.Warn("deprecated address transaction indexing is enabled") 253 vm.addressTxsIndexer, err = index.NewIndexer(vm.db, vm.ctx.Log, "", vm.registerer, avmConfig.IndexAllowIncomplete) 254 if err != nil { 255 return fmt.Errorf("failed to initialize address transaction indexer: %w", err) 256 } 257 } else { 258 vm.ctx.Log.Info("address transaction indexing is disabled") 259 vm.addressTxsIndexer, err = index.NewNoIndexer(vm.db, avmConfig.IndexAllowIncomplete) 260 if err != nil { 261 return fmt.Errorf("failed to initialize disabled indexer: %w", err) 262 } 263 } 264 265 vm.txBackend = &txexecutor.Backend{ 266 Ctx: ctx, 267 Config: &vm.Config, 268 Fxs: vm.fxs, 269 TypeToFxIndex: vm.typeToFxIndex, 270 Codec: vm.parser.Codec(), 271 FeeAssetID: vm.feeAssetID, 272 Bootstrapped: false, 273 } 274 275 vm.onShutdownCtx, vm.onShutdownCtxCancel = context.WithCancel(context.Background()) 276 vm.networkConfig = avmConfig.Network 277 return vm.state.Commit() 278 } 279 280 // onBootstrapStarted is called by the consensus engine when it starts bootstrapping this chain 281 func (vm *VM) onBootstrapStarted() error { 282 vm.txBackend.Bootstrapped = false 283 for _, fx := range vm.fxs { 284 if err := fx.Fx.Bootstrapping(); err != nil { 285 return err 286 } 287 } 288 return nil 289 } 290 291 func (vm *VM) onNormalOperationsStarted() error { 292 vm.txBackend.Bootstrapped = true 293 for _, fx := range vm.fxs { 294 if err := fx.Fx.Bootstrapped(); err != nil { 295 return err 296 } 297 } 298 299 vm.bootstrapped = true 300 return nil 301 } 302 303 func (vm *VM) SetState(_ context.Context, state snow.State) error { 304 switch state { 305 case snow.Bootstrapping: 306 return vm.onBootstrapStarted() 307 case snow.NormalOp: 308 return vm.onNormalOperationsStarted() 309 default: 310 return snow.ErrUnknownState 311 } 312 } 313 314 func (vm *VM) Shutdown(context.Context) error { 315 if vm.state == nil { 316 return nil 317 } 318 319 vm.onShutdownCtxCancel() 320 vm.awaitShutdown.Wait() 321 322 return errors.Join( 323 vm.state.Close(), 324 vm.baseDB.Close(), 325 ) 326 } 327 328 func (*VM) Version(context.Context) (string, error) { 329 return version.Current.String(), nil 330 } 331 332 func (vm *VM) CreateHandlers(context.Context) (map[string]http.Handler, error) { 333 codec := json.NewCodec() 334 335 rpcServer := rpc.NewServer() 336 rpcServer.RegisterCodec(codec, "application/json") 337 rpcServer.RegisterCodec(codec, "application/json;charset=UTF-8") 338 rpcServer.RegisterInterceptFunc(vm.metrics.InterceptRequest) 339 rpcServer.RegisterAfterFunc(vm.metrics.AfterRequest) 340 // name this service "avm" 341 if err := rpcServer.RegisterService(&Service{vm: vm}, "avm"); err != nil { 342 return nil, err 343 } 344 345 walletServer := rpc.NewServer() 346 walletServer.RegisterCodec(codec, "application/json") 347 walletServer.RegisterCodec(codec, "application/json;charset=UTF-8") 348 walletServer.RegisterInterceptFunc(vm.metrics.InterceptRequest) 349 walletServer.RegisterAfterFunc(vm.metrics.AfterRequest) 350 // name this service "wallet" 351 err := walletServer.RegisterService(&vm.walletService, "wallet") 352 353 return map[string]http.Handler{ 354 "": rpcServer, 355 "/wallet": walletServer, 356 "/events": vm.pubsub, 357 }, err 358 } 359 360 /* 361 ****************************************************************************** 362 ********************************** Chain VM ********************************** 363 ****************************************************************************** 364 */ 365 366 func (vm *VM) GetBlock(_ context.Context, blkID ids.ID) (snowman.Block, error) { 367 return vm.chainManager.GetBlock(blkID) 368 } 369 370 func (vm *VM) ParseBlock(_ context.Context, blkBytes []byte) (snowman.Block, error) { 371 blk, err := vm.parser.ParseBlock(blkBytes) 372 if err != nil { 373 return nil, err 374 } 375 return vm.chainManager.NewBlock(blk), nil 376 } 377 378 func (vm *VM) SetPreference(_ context.Context, blkID ids.ID) error { 379 vm.chainManager.SetPreference(blkID) 380 return nil 381 } 382 383 func (vm *VM) LastAccepted(context.Context) (ids.ID, error) { 384 return vm.chainManager.LastAccepted(), nil 385 } 386 387 func (vm *VM) GetBlockIDAtHeight(_ context.Context, height uint64) (ids.ID, error) { 388 return vm.state.GetBlockIDAtHeight(height) 389 } 390 391 /* 392 ****************************************************************************** 393 *********************************** DAG VM *********************************** 394 ****************************************************************************** 395 */ 396 397 func (vm *VM) Linearize(ctx context.Context, stopVertexID ids.ID, toEngine chan<- common.Message) error { 398 time := version.GetCortinaTime(vm.ctx.NetworkID) 399 err := vm.state.InitializeChainState(stopVertexID, time) 400 if err != nil { 401 return err 402 } 403 404 mempool, err := xmempool.New("mempool", vm.registerer, toEngine) 405 if err != nil { 406 return fmt.Errorf("failed to create mempool: %w", err) 407 } 408 409 vm.chainManager = blockexecutor.NewManager( 410 mempool, 411 vm.metrics, 412 vm.state, 413 vm.txBackend, 414 &vm.clock, 415 vm.onAccept, 416 ) 417 418 vm.Builder = blockbuilder.New( 419 vm.txBackend, 420 vm.chainManager, 421 &vm.clock, 422 mempool, 423 ) 424 425 // Invariant: The context lock is not held when calling network.IssueTx. 426 vm.network, err = network.New( 427 vm.ctx.Log, 428 vm.ctx.NodeID, 429 vm.ctx.SubnetID, 430 vm.ctx.ValidatorState, 431 vm.parser, 432 network.NewLockedTxVerifier( 433 &vm.ctx.Lock, 434 vm.chainManager, 435 ), 436 mempool, 437 vm.appSender, 438 vm.registerer, 439 vm.networkConfig, 440 ) 441 if err != nil { 442 return fmt.Errorf("failed to initialize network: %w", err) 443 } 444 445 // Notify the network of our current peers 446 for nodeID, version := range vm.connectedPeers { 447 if err := vm.network.Connected(ctx, nodeID, version); err != nil { 448 return err 449 } 450 } 451 vm.connectedPeers = nil 452 453 // Note: It's important only to switch the networking stack after the full 454 // chainVM has been initialized. Traffic will immediately start being 455 // handled asynchronously. 456 vm.Atomic.Set(vm.network) 457 458 vm.awaitShutdown.Add(2) 459 go func() { 460 defer vm.awaitShutdown.Done() 461 462 // Invariant: PushGossip must never grab the context lock. 463 vm.network.PushGossip(vm.onShutdownCtx) 464 }() 465 go func() { 466 defer vm.awaitShutdown.Done() 467 468 // Invariant: PullGossip must never grab the context lock. 469 vm.network.PullGossip(vm.onShutdownCtx) 470 }() 471 472 return nil 473 } 474 475 func (vm *VM) ParseTx(_ context.Context, bytes []byte) (snowstorm.Tx, error) { 476 tx, err := vm.parser.ParseTx(bytes) 477 if err != nil { 478 return nil, err 479 } 480 481 err = tx.Unsigned.Visit(&txexecutor.SyntacticVerifier{ 482 Backend: vm.txBackend, 483 Tx: tx, 484 }) 485 if err != nil { 486 return nil, err 487 } 488 489 return &Tx{ 490 vm: vm, 491 tx: tx, 492 }, nil 493 } 494 495 /* 496 ****************************************************************************** 497 ********************************** JSON API ********************************** 498 ****************************************************************************** 499 */ 500 501 // issueTxFromRPC attempts to send a transaction to consensus. 502 // 503 // Invariant: The context lock is not held 504 // Invariant: This function is only called after Linearize has been called. 505 func (vm *VM) issueTxFromRPC(tx *txs.Tx) (ids.ID, error) { 506 txID := tx.ID() 507 err := vm.network.IssueTxFromRPC(tx) 508 if err != nil && !errors.Is(err, mempool.ErrDuplicateTx) { 509 vm.ctx.Log.Debug("failed to add tx to mempool", 510 zap.Stringer("txID", txID), 511 zap.Error(err), 512 ) 513 return txID, err 514 } 515 return txID, nil 516 } 517 518 /* 519 ****************************************************************************** 520 ********************************** Helpers *********************************** 521 ****************************************************************************** 522 */ 523 524 func (vm *VM) initGenesis(genesisBytes []byte) error { 525 genesisCodec := vm.parser.GenesisCodec() 526 genesis := Genesis{} 527 if _, err := genesisCodec.Unmarshal(genesisBytes, &genesis); err != nil { 528 return err 529 } 530 531 stateInitialized, err := vm.state.IsInitialized() 532 if err != nil { 533 return err 534 } 535 536 // secure this by defaulting to avaxAsset 537 vm.feeAssetID = vm.ctx.AVAXAssetID 538 539 for index, genesisTx := range genesis.Txs { 540 if len(genesisTx.Outs) != 0 { 541 return errGenesisAssetMustHaveState 542 } 543 544 tx := &txs.Tx{ 545 Unsigned: &genesisTx.CreateAssetTx, 546 } 547 if err := tx.Initialize(genesisCodec); err != nil { 548 return err 549 } 550 551 txID := tx.ID() 552 if err := vm.Alias(txID, genesisTx.Alias); err != nil { 553 return err 554 } 555 556 if !stateInitialized { 557 vm.initState(tx) 558 } 559 if index == 0 { 560 vm.ctx.Log.Info("fee asset is established", 561 zap.String("alias", genesisTx.Alias), 562 zap.Stringer("assetID", txID), 563 ) 564 vm.feeAssetID = txID 565 } 566 } 567 568 if !stateInitialized { 569 return vm.state.SetInitialized() 570 } 571 572 return nil 573 } 574 575 func (vm *VM) initState(tx *txs.Tx) { 576 txID := tx.ID() 577 vm.ctx.Log.Info("initializing genesis asset", 578 zap.Stringer("txID", txID), 579 ) 580 vm.state.AddTx(tx) 581 for _, utxo := range tx.UTXOs() { 582 vm.state.AddUTXO(utxo) 583 } 584 } 585 586 // LoadUser returns: 587 // 1) The UTXOs that reference one or more addresses controlled by the given user 588 // 2) A keychain that contains this user's keys 589 // If [addrsToUse] has positive length, returns UTXOs that reference one or more 590 // addresses controlled by the given user that are also in [addrsToUse]. 591 func (vm *VM) LoadUser( 592 username string, 593 password string, 594 addrsToUse set.Set[ids.ShortID], 595 ) ( 596 []*avax.UTXO, 597 *secp256k1fx.Keychain, 598 error, 599 ) { 600 user, err := keystore.NewUserFromKeystore(vm.ctx.Keystore, username, password) 601 if err != nil { 602 return nil, nil, err 603 } 604 // Drop any potential error closing the database to report the original 605 // error 606 defer user.Close() 607 608 kc, err := keystore.GetKeychain(user, addrsToUse) 609 if err != nil { 610 return nil, nil, err 611 } 612 613 utxos, err := avax.GetAllUTXOs(vm.state, kc.Addresses()) 614 if err != nil { 615 return nil, nil, fmt.Errorf("problem retrieving user's UTXOs: %w", err) 616 } 617 618 return utxos, kc, user.Close() 619 } 620 621 // selectChangeAddr returns the change address to be used for [kc] when [changeAddr] is given 622 // as the optional change address argument 623 func (vm *VM) selectChangeAddr(defaultAddr ids.ShortID, changeAddr string) (ids.ShortID, error) { 624 if changeAddr == "" { 625 return defaultAddr, nil 626 } 627 addr, err := avax.ParseServiceAddress(vm, changeAddr) 628 if err != nil { 629 return ids.ShortID{}, fmt.Errorf("couldn't parse changeAddr: %w", err) 630 } 631 return addr, nil 632 } 633 634 // lookupAssetID looks for an ID aliased by [asset] and if it fails 635 // attempts to parse [asset] into an ID 636 func (vm *VM) lookupAssetID(asset string) (ids.ID, error) { 637 if assetID, err := vm.Lookup(asset); err == nil { 638 return assetID, nil 639 } 640 if assetID, err := ids.FromString(asset); err == nil { 641 return assetID, nil 642 } 643 return ids.Empty, fmt.Errorf("asset '%s' not found", asset) 644 } 645 646 // Invariant: onAccept is called when [tx] is being marked as accepted, but 647 // before its state changes are applied. 648 // Invariant: any error returned by onAccept should be considered fatal. 649 // TODO: Remove [onAccept] once the deprecated APIs this powers are removed. 650 func (vm *VM) onAccept(tx *txs.Tx) error { 651 // Fetch the input UTXOs 652 txID := tx.ID() 653 inputUTXOIDs := tx.Unsigned.InputUTXOs() 654 inputUTXOs := make([]*avax.UTXO, 0, len(inputUTXOIDs)) 655 for _, utxoID := range inputUTXOIDs { 656 // Don't bother fetching the input UTXO if its symbolic 657 if utxoID.Symbolic() { 658 continue 659 } 660 661 utxo, err := vm.state.GetUTXO(utxoID.InputID()) 662 if err == database.ErrNotFound { 663 vm.ctx.Log.Debug("dropping utxo from index", 664 zap.Stringer("txID", txID), 665 zap.Stringer("utxoTxID", utxoID.TxID), 666 zap.Uint32("utxoOutputIndex", utxoID.OutputIndex), 667 ) 668 continue 669 } 670 if err != nil { 671 // should never happen because the UTXO was previously verified to 672 // exist 673 return fmt.Errorf("error finding UTXO %s: %w", utxoID, err) 674 } 675 inputUTXOs = append(inputUTXOs, utxo) 676 } 677 678 outputUTXOs := tx.UTXOs() 679 // index input and output UTXOs 680 if err := vm.addressTxsIndexer.Accept(txID, inputUTXOs, outputUTXOs); err != nil { 681 return fmt.Errorf("error indexing tx: %w", err) 682 } 683 684 vm.pubsub.Publish(NewPubSubFilterer(tx)) 685 vm.walletService.decided(txID) 686 return nil 687 }