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  }