github.com/MetalBlockchain/metalgo@v1.11.9/vms/manager.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package vms
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"sync"
    11  
    12  	"golang.org/x/exp/maps"
    13  
    14  	"github.com/MetalBlockchain/metalgo/ids"
    15  	"github.com/MetalBlockchain/metalgo/snow/engine/common"
    16  	"github.com/MetalBlockchain/metalgo/utils/logging"
    17  )
    18  
    19  var (
    20  	ErrNotFound = errors.New("not found")
    21  
    22  	_ Manager = (*manager)(nil)
    23  )
    24  
    25  // A Factory creates new instances of a VM
    26  type Factory interface {
    27  	New(logging.Logger) (interface{}, error)
    28  }
    29  
    30  // Manager tracks a collection of VM factories, their aliases, and their
    31  // versions.
    32  // It has the following functionality:
    33  //
    34  //  1. Register a VM factory. To register a VM is to associate its ID with a
    35  //     VMFactory which, when New() is called upon it, creates a new instance of
    36  //     that VM.
    37  //  2. Get a VM factory. Given the ID of a VM that has been registered, return
    38  //     the factory that the ID is associated with.
    39  //  3. Manage the aliases of VMs
    40  //  4. Manage the versions of VMs
    41  type Manager interface {
    42  	ids.Aliaser
    43  
    44  	// Return a factory that can create new instances of the vm whose ID is
    45  	// [vmID]
    46  	GetFactory(vmID ids.ID) (Factory, error)
    47  
    48  	// Map [vmID] to [factory]. [factory] creates new instances of the vm whose
    49  	// ID is [vmID]
    50  	RegisterFactory(ctx context.Context, vmID ids.ID, factory Factory) error
    51  
    52  	// ListFactories returns all the IDs that have had factories registered.
    53  	ListFactories() ([]ids.ID, error)
    54  
    55  	// Versions returns the primary alias of the VM mapped to the reported
    56  	// version of the VM for all the registered VMs that reported versions.
    57  	Versions() (map[string]string, error)
    58  }
    59  
    60  type manager struct {
    61  	// Note: The string representation of a VM's ID is also considered to be an
    62  	// alias of the VM. That is, [vmID].String() is an alias for [vmID].
    63  	ids.Aliaser
    64  
    65  	log logging.Logger
    66  
    67  	lock sync.RWMutex
    68  
    69  	// Key: A VM's ID
    70  	// Value: A factory that creates new instances of that VM
    71  	factories map[ids.ID]Factory
    72  
    73  	// Key: A VM's ID
    74  	// Value: version the VM returned
    75  	versions map[ids.ID]string
    76  }
    77  
    78  // NewManager returns an instance of a VM manager
    79  func NewManager(log logging.Logger, aliaser ids.Aliaser) Manager {
    80  	return &manager{
    81  		Aliaser:   aliaser,
    82  		log:       log,
    83  		factories: make(map[ids.ID]Factory),
    84  		versions:  make(map[ids.ID]string),
    85  	}
    86  }
    87  
    88  func (m *manager) GetFactory(vmID ids.ID) (Factory, error) {
    89  	m.lock.RLock()
    90  	defer m.lock.RUnlock()
    91  
    92  	if factory, ok := m.factories[vmID]; ok {
    93  		return factory, nil
    94  	}
    95  	return nil, fmt.Errorf("%q was %w", vmID, ErrNotFound)
    96  }
    97  
    98  func (m *manager) RegisterFactory(ctx context.Context, vmID ids.ID, factory Factory) error {
    99  	m.lock.Lock()
   100  	defer m.lock.Unlock()
   101  
   102  	if _, exists := m.factories[vmID]; exists {
   103  		return fmt.Errorf("%q was already registered as a vm", vmID)
   104  	}
   105  	if err := m.Alias(vmID, vmID.String()); err != nil {
   106  		return err
   107  	}
   108  
   109  	m.factories[vmID] = factory
   110  
   111  	vm, err := factory.New(m.log)
   112  	if err != nil {
   113  		return err
   114  	}
   115  
   116  	commonVM, ok := vm.(common.VM)
   117  	if !ok {
   118  		return nil
   119  	}
   120  
   121  	version, err := commonVM.Version(ctx)
   122  	if err != nil {
   123  		// Drop the shutdown error to surface the original error
   124  		_ = commonVM.Shutdown(ctx)
   125  		return err
   126  	}
   127  
   128  	m.versions[vmID] = version
   129  	return commonVM.Shutdown(ctx)
   130  }
   131  
   132  func (m *manager) ListFactories() ([]ids.ID, error) {
   133  	m.lock.RLock()
   134  	defer m.lock.RUnlock()
   135  
   136  	return maps.Keys(m.factories), nil
   137  }
   138  
   139  func (m *manager) Versions() (map[string]string, error) {
   140  	m.lock.RLock()
   141  	defer m.lock.RUnlock()
   142  
   143  	versions := make(map[string]string, len(m.versions))
   144  	for vmID, version := range m.versions {
   145  		alias, err := m.PrimaryAlias(vmID)
   146  		if err != nil {
   147  			return nil, err
   148  		}
   149  		versions[alias] = version
   150  	}
   151  	return versions, nil
   152  }