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  }