code.vegaprotocol.io/vega@v0.79.0/core/assets/assets.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package assets
    17  
    18  import (
    19  	"context"
    20  	"errors"
    21  	"fmt"
    22  	"sort"
    23  	"strings"
    24  	"sync"
    25  	"time"
    26  
    27  	"code.vegaprotocol.io/vega/core/assets/builtin"
    28  	"code.vegaprotocol.io/vega/core/assets/erc20"
    29  	"code.vegaprotocol.io/vega/core/broker"
    30  	"code.vegaprotocol.io/vega/core/events"
    31  	nweth "code.vegaprotocol.io/vega/core/nodewallets/eth"
    32  	"code.vegaprotocol.io/vega/core/types"
    33  	"code.vegaprotocol.io/vega/logging"
    34  )
    35  
    36  var (
    37  	ErrAssetDoesNotExist        = errors.New("asset does not exist")
    38  	ErrUnknownAssetSource       = errors.New("unknown asset source")
    39  	ErrErc20AddressAlreadyInUse = errors.New("erc20 address already in use")
    40  	ErrUnknownChainID           = errors.New("erc20 chain-id does not correspond to a bridge")
    41  )
    42  
    43  //go:generate go run github.com/golang/mock/mockgen -destination mocks/mocks.go -package mocks code.vegaprotocol.io/vega/core/assets ERC20BridgeView,Notary
    44  
    45  type ERC20BridgeView interface {
    46  	FindAsset(asset *types.AssetDetails) error
    47  }
    48  
    49  type Notary interface {
    50  	StartAggregate(resID string, kind types.NodeSignatureKind, signature []byte)
    51  	OfferSignatures(kind types.NodeSignatureKind, f func(id string) []byte)
    52  }
    53  
    54  type Service struct {
    55  	log *logging.Logger
    56  	cfg Config
    57  
    58  	broker broker.Interface
    59  
    60  	// id to asset
    61  	// these assets exists and have been save
    62  	amu    sync.RWMutex
    63  	assets map[string]*Asset
    64  
    65  	// this is a list of pending asset which are currently going through
    66  	// proposal, they can later on be promoted to the asset lists once
    67  	// the proposal is accepted by both the nodes and the users
    68  	pamu                sync.RWMutex
    69  	pendingAssets       map[string]*Asset
    70  	pendingAssetUpdates map[string]*Asset
    71  
    72  	ethWallet nweth.EthereumWallet
    73  
    74  	primaryEthChainID string
    75  	primaryEthClient  erc20.ETHClient
    76  	primaryBridgeView ERC20BridgeView
    77  
    78  	secondaryEthChainID string
    79  	secondaryEthClient  erc20.ETHClient
    80  	secondaryBridgeView ERC20BridgeView
    81  
    82  	notary Notary
    83  	ass    *assetsSnapshotState
    84  
    85  	ethToVega   map[string]string
    86  	isValidator bool
    87  }
    88  
    89  func New(
    90  	ctx context.Context,
    91  	log *logging.Logger,
    92  	cfg Config,
    93  	nw nweth.EthereumWallet,
    94  	primaryEthClient erc20.ETHClient,
    95  	secondaryEthClient erc20.ETHClient,
    96  	broker broker.Interface,
    97  	primaryBridgeView ERC20BridgeView,
    98  	secondaryBridgeView ERC20BridgeView,
    99  	notary Notary,
   100  	isValidator bool,
   101  ) (*Service, error) {
   102  	log = log.Named(namedLogger)
   103  	log.SetLevel(cfg.Level.Get())
   104  
   105  	s := &Service{
   106  		log:                 log,
   107  		cfg:                 cfg,
   108  		broker:              broker,
   109  		assets:              map[string]*Asset{},
   110  		pendingAssets:       map[string]*Asset{},
   111  		pendingAssetUpdates: map[string]*Asset{},
   112  		ethWallet:           nw,
   113  		primaryEthClient:    primaryEthClient,
   114  		secondaryEthClient:  secondaryEthClient,
   115  		notary:              notary,
   116  		ass:                 &assetsSnapshotState{},
   117  		isValidator:         isValidator,
   118  		ethToVega:           map[string]string{},
   119  		primaryBridgeView:   primaryBridgeView,
   120  		secondaryBridgeView: secondaryBridgeView,
   121  	}
   122  
   123  	if isValidator {
   124  		primaryChainID, err := s.primaryEthClient.ChainID(ctx)
   125  		if err != nil {
   126  			return nil, fmt.Errorf("could not fetch chain ID from the primary ethereum client: %w", err)
   127  		}
   128  		s.primaryEthChainID = primaryChainID.String()
   129  
   130  		secondaryChainID, err := s.secondaryEthClient.ChainID(ctx)
   131  		if err != nil {
   132  			return nil, fmt.Errorf("could not fetch chain ID from the secondary ethereum client: %w", err)
   133  		}
   134  		s.secondaryEthChainID = secondaryChainID.String()
   135  	}
   136  
   137  	return s, nil
   138  }
   139  
   140  // ReloadConf updates the internal configuration.
   141  func (s *Service) ReloadConf(cfg Config) {
   142  	s.log.Info("reloading configuration")
   143  	if s.log.GetLevel() != cfg.Level.Get() {
   144  		s.log.Info("updating log level",
   145  			logging.String("old", s.log.GetLevel().String()),
   146  			logging.String("new", cfg.Level.String()),
   147  		)
   148  		s.log.SetLevel(cfg.Level.Get())
   149  	}
   150  
   151  	s.cfg = cfg
   152  }
   153  
   154  // Enable move the state of an from pending the list of valid and accepted assets.
   155  func (s *Service) Enable(ctx context.Context, assetID string) error {
   156  	s.pamu.Lock()
   157  	defer s.pamu.Unlock()
   158  	asset, ok := s.pendingAssets[assetID]
   159  	if !ok {
   160  		return ErrAssetDoesNotExist
   161  	}
   162  
   163  	asset.SetEnabled()
   164  	s.amu.Lock()
   165  	defer s.amu.Unlock()
   166  	s.assets[assetID] = asset
   167  	if asset.IsERC20() {
   168  		eth, _ := asset.ERC20()
   169  		s.ethToVega[eth.ProtoAsset().GetDetails().GetErc20().GetContractAddress()] = assetID
   170  	}
   171  	delete(s.pendingAssets, assetID)
   172  	s.broker.Send(events.NewAssetEvent(ctx, *asset.Type()))
   173  	return nil
   174  }
   175  
   176  // EnactPendingAsset the given id for an asset has just been enacted by the governance engine so we
   177  // now need to generate signatures so that the asset can be listed.
   178  func (s *Service) EnactPendingAsset(id string) {
   179  	pa, _ := s.Get(id)
   180  	var err error
   181  	var signature []byte
   182  	if s.isValidator {
   183  		switch {
   184  		case pa.IsERC20():
   185  			asset, _ := pa.ERC20()
   186  			_, signature, err = asset.SignListAsset()
   187  			if err != nil {
   188  				s.log.Panic("couldn't to sign transaction to list asset, is the node properly configured as a validator?",
   189  					logging.Error(err))
   190  			}
   191  		default:
   192  			s.log.Panic("trying to generate signatures for an unknown asset type")
   193  		}
   194  	}
   195  
   196  	s.notary.StartAggregate(id, types.NodeSignatureKindAssetNew, signature)
   197  }
   198  
   199  // ValidateEthereumAddress checks that the given ERC20 address and chainID corresponds to one of Vega's bridges
   200  // and isn't the address of an asset that already exists.
   201  func (s *Service) ValidateEthereumAddress(address, chainID string) error {
   202  	if chainID != s.primaryEthChainID && chainID != s.secondaryEthChainID {
   203  		return ErrUnknownChainID
   204  	}
   205  
   206  	for _, a := range s.assets {
   207  		if source, ok := a.ERC20(); ok {
   208  			if source.ChainID() != chainID {
   209  				// asset is on a different chain, definitely is not a dupe of it
   210  				continue
   211  			}
   212  
   213  			if strings.EqualFold(source.Address(), address) {
   214  				return ErrErc20AddressAlreadyInUse
   215  			}
   216  		}
   217  	}
   218  	for _, a := range s.pendingAssets {
   219  		if source, ok := a.ERC20(); ok {
   220  			if source.ChainID() != chainID {
   221  				// asset is on a different chain, definitely is not a dupe of it
   222  				continue
   223  			}
   224  
   225  			if strings.EqualFold(source.Address(), address) {
   226  				return ErrErc20AddressAlreadyInUse
   227  			}
   228  		}
   229  	}
   230  
   231  	return nil
   232  }
   233  
   234  // SetPendingListing update the state of an asset from proposed
   235  // to pending listing on the bridge.
   236  func (s *Service) SetPendingListing(ctx context.Context, assetID string) error {
   237  	s.pamu.Lock()
   238  	defer s.pamu.Unlock()
   239  	asset, ok := s.pendingAssets[assetID]
   240  	if !ok {
   241  		return ErrAssetDoesNotExist
   242  	}
   243  
   244  	asset.SetPendingListing()
   245  	s.broker.Send(events.NewAssetEvent(ctx, *asset.Type()))
   246  	return nil
   247  }
   248  
   249  // SetRejected update the state of an asset from proposed
   250  // to pending listing on the bridge.
   251  func (s *Service) SetRejected(ctx context.Context, assetID string) error {
   252  	s.pamu.Lock()
   253  	defer s.pamu.Unlock()
   254  	asset, ok := s.pendingAssets[assetID]
   255  	if !ok {
   256  		return ErrAssetDoesNotExist
   257  	}
   258  
   259  	asset.SetRejected()
   260  	s.broker.Send(events.NewAssetEvent(ctx, *asset.Type()))
   261  	delete(s.pendingAssets, assetID)
   262  	return nil
   263  }
   264  
   265  func (s *Service) GetVegaIDFromEthereumAddress(address string) string {
   266  	s.amu.Lock()
   267  	defer s.amu.Unlock()
   268  	return s.ethToVega[address]
   269  }
   270  
   271  func (s *Service) IsEnabled(assetID string) bool {
   272  	s.amu.RLock()
   273  	defer s.amu.RUnlock()
   274  	_, ok := s.assets[assetID]
   275  	return ok
   276  }
   277  
   278  // SetBridgeChainID sets the chain-ids for the bridge once we have processed the network parameters
   279  // this is necessary so that non-validator nodes (which cannot just ask the eth-client) can know what they
   280  // are.
   281  func (s *Service) SetBridgeChainID(chainID string, primary bool) {
   282  	if primary {
   283  		s.primaryEthChainID = chainID
   284  		return
   285  	}
   286  	s.secondaryEthChainID = chainID
   287  }
   288  
   289  func (s *Service) OnTick(_ context.Context, _ time.Time) {
   290  	s.notary.OfferSignatures(types.NodeSignatureKindAssetNew, s.offerERC20NotarySignatures)
   291  }
   292  
   293  func (s *Service) offerERC20NotarySignatures(id string) []byte {
   294  	if !s.isValidator {
   295  		return nil
   296  	}
   297  
   298  	pa, err := s.Get(id)
   299  	if err != nil {
   300  		s.log.Panic("unable to find asset", logging.AssetID(id))
   301  	}
   302  
   303  	asset, _ := pa.ERC20()
   304  	_, signature, err := asset.SignListAsset()
   305  	if err != nil {
   306  		s.log.Panic("couldn't to sign transaction to list asset, is the node properly configured as a validator?",
   307  			logging.Error(err))
   308  	}
   309  
   310  	return signature
   311  }
   312  
   313  func (s *Service) assetFromDetails(assetID string, assetDetails *types.AssetDetails) (*Asset, error) {
   314  	switch assetDetails.Source.(type) {
   315  	case *types.AssetDetailsBuiltinAsset:
   316  		return &Asset{
   317  			builtin.New(assetID, assetDetails),
   318  		}, nil
   319  	case *types.AssetDetailsErc20:
   320  		var asset *erc20.ERC20
   321  		if s.isValidator {
   322  			client, err := s.ethClientByChainID(assetDetails.GetERC20().ChainID)
   323  			if err != nil {
   324  				return nil, err
   325  			}
   326  			a, err := erc20.New(assetID, assetDetails, s.ethWallet, client)
   327  			if err != nil {
   328  				return nil, err
   329  			}
   330  			asset = a
   331  		} else {
   332  			a, err := erc20.New(assetID, assetDetails, nil, nil)
   333  			if err != nil {
   334  				return nil, err
   335  			}
   336  			asset = a
   337  		}
   338  		return &Asset{asset}, nil
   339  	default:
   340  		return nil, ErrUnknownAssetSource
   341  	}
   342  }
   343  
   344  func (s *Service) buildAssetUpdateFromProto(asset *types.Asset) (*Asset, error) {
   345  	switch asset.Details.Source.(type) {
   346  	case *types.AssetDetailsBuiltinAsset:
   347  		return &Asset{
   348  			builtin.New(asset.ID, asset.Details),
   349  		}, nil
   350  	case *types.AssetDetailsErc20:
   351  		var (
   352  			erc20Asset *erc20.ERC20
   353  			err        error
   354  		)
   355  		if s.isValidator {
   356  			client, err := s.ethClientByChainID(asset.Details.GetERC20().ChainID)
   357  			if err != nil {
   358  				return nil, err
   359  			}
   360  			a, err := erc20.New(asset.ID, asset.Details, s.ethWallet, client)
   361  			if err != nil {
   362  				return nil, err
   363  			}
   364  			erc20Asset = a
   365  		} else {
   366  			a, err := erc20.New(asset.ID, asset.Details, nil, nil)
   367  			if err != nil {
   368  				return nil, err
   369  			}
   370  			erc20Asset = a
   371  		}
   372  		if err != nil {
   373  			return nil, err
   374  		}
   375  		return &Asset{erc20Asset}, nil
   376  	default:
   377  		return nil, ErrUnknownAssetSource
   378  	}
   379  }
   380  
   381  // NewAsset add a new asset to the pending list of assets
   382  // the ref is the reference of proposal which submitted the new asset
   383  // returns the assetID and an error.
   384  func (s *Service) NewAsset(ctx context.Context, proposalID string, assetDetails *types.AssetDetails) (string, error) {
   385  	s.pamu.Lock()
   386  	defer s.pamu.Unlock()
   387  	asset, err := s.assetFromDetails(proposalID, assetDetails)
   388  	if err != nil {
   389  		return "", err
   390  	}
   391  	s.pendingAssets[proposalID] = asset
   392  	s.broker.Send(events.NewAssetEvent(ctx, *asset.Type()))
   393  
   394  	return proposalID, err
   395  }
   396  
   397  func (s *Service) StageAssetUpdate(updatedAssetProto *types.Asset) error {
   398  	s.pamu.Lock()
   399  	defer s.pamu.Unlock()
   400  	if _, ok := s.assets[updatedAssetProto.ID]; !ok {
   401  		return ErrAssetDoesNotExist
   402  	}
   403  
   404  	updatedAsset, err := s.buildAssetUpdateFromProto(updatedAssetProto)
   405  	if err != nil {
   406  		return fmt.Errorf("couldn't update asset: %w", err)
   407  	}
   408  	s.pendingAssetUpdates[updatedAssetProto.ID] = updatedAsset
   409  	return nil
   410  }
   411  
   412  func (s *Service) ApplyAssetUpdate(ctx context.Context, assetID string) error {
   413  	s.pamu.Lock()
   414  	defer s.pamu.Unlock()
   415  
   416  	updatedAsset, ok := s.pendingAssetUpdates[assetID]
   417  	if !ok {
   418  		return ErrAssetDoesNotExist
   419  	}
   420  
   421  	s.amu.Lock()
   422  	defer s.amu.Unlock()
   423  
   424  	currentAsset, ok := s.assets[assetID]
   425  	if !ok {
   426  		return ErrAssetDoesNotExist
   427  	}
   428  	updatedAsset.SetEnabled()
   429  	if err := currentAsset.Update(updatedAsset); err != nil {
   430  		s.log.Panic("couldn't update the asset", logging.Error(err))
   431  	}
   432  
   433  	delete(s.pendingAssetUpdates, assetID)
   434  	s.broker.Send(events.NewAssetEvent(ctx, *updatedAsset.Type()))
   435  	return nil
   436  }
   437  
   438  func (s *Service) GetEnabledAssets() []*types.Asset {
   439  	s.amu.RLock()
   440  	defer s.amu.RUnlock()
   441  	ret := make([]*types.Asset, 0, len(s.assets))
   442  	for _, a := range s.assets {
   443  		ret = append(ret, a.ToAssetType())
   444  	}
   445  	sort.SliceStable(ret, func(i, j int) bool { return ret[i].ID < ret[j].ID })
   446  	return ret
   447  }
   448  
   449  func (s *Service) getPendingAssets() []*types.Asset {
   450  	s.pamu.RLock()
   451  	defer s.pamu.RUnlock()
   452  	ret := make([]*types.Asset, 0, len(s.assets))
   453  	for _, a := range s.pendingAssets {
   454  		ret = append(ret, a.ToAssetType())
   455  	}
   456  	sort.SliceStable(ret, func(i, j int) bool { return ret[i].ID < ret[j].ID })
   457  	return ret
   458  }
   459  
   460  func (s *Service) getPendingAssetUpdates() []*types.Asset {
   461  	s.pamu.RLock()
   462  	defer s.pamu.RUnlock()
   463  	ret := make([]*types.Asset, 0, len(s.assets))
   464  	for _, a := range s.pendingAssetUpdates {
   465  		ret = append(ret, a.ToAssetType())
   466  	}
   467  	sort.SliceStable(ret, func(i, j int) bool { return ret[i].ID < ret[j].ID })
   468  	return ret
   469  }
   470  
   471  func (s *Service) Get(assetID string) (*Asset, error) {
   472  	s.amu.RLock()
   473  	defer s.amu.RUnlock()
   474  	asset, ok := s.assets[assetID]
   475  	if ok {
   476  		return asset, nil
   477  	}
   478  	s.pamu.RLock()
   479  	defer s.pamu.RUnlock()
   480  	asset, ok = s.pendingAssets[assetID]
   481  	if ok {
   482  		return asset, nil
   483  	}
   484  	return nil, ErrAssetDoesNotExist
   485  }
   486  
   487  // ValidateAssetNonValidator is only to be used by non-validators
   488  // at startup when loading genesis file. We just assume assets are
   489  // valid.
   490  func (s *Service) ValidateAssetNonValidator(assetID string) error {
   491  	// get the asset to validate from the assets pool
   492  	asset, err := s.Get(assetID)
   493  	// if we get an error here, we'll never change the state of the proposal,
   494  	// so it will be dismissed later on by all the whole network
   495  	if err != nil || asset == nil {
   496  		s.log.Error("Validating asset, unable to get the asset",
   497  			logging.AssetID(assetID),
   498  			logging.Error(err),
   499  		)
   500  		return errors.New("invalid asset ID")
   501  	}
   502  
   503  	asset.SetValid()
   504  	return nil
   505  }
   506  
   507  func (s *Service) ValidateAsset(assetID string) error {
   508  	// get the asset to validate from the assets pool
   509  	asset, err := s.Get(assetID)
   510  	// if we get an error here, we'll never change the state of the proposal,
   511  	// so it will be dismissed later on by all the whole network
   512  	if err != nil || asset == nil {
   513  		s.log.Error("Validating asset, unable to get the asset",
   514  			logging.AssetID(assetID),
   515  			logging.Error(err),
   516  		)
   517  		return errors.New("invalid asset ID")
   518  	}
   519  
   520  	return s.validateAsset(asset)
   521  }
   522  
   523  func (s *Service) validateAsset(a *Asset) error {
   524  	if erc20Asset, ok := a.ERC20(); ok {
   525  		details := erc20Asset.Type().Details
   526  		bridgeView, err := s.bridgeViewByChainID(details.GetERC20().ChainID)
   527  		if err != nil {
   528  			return err
   529  		}
   530  		if err := bridgeView.FindAsset(details); err != nil {
   531  			return err
   532  		}
   533  		// no error, our asset exists on chain
   534  		erc20Asset.SetValid()
   535  	}
   536  	return nil
   537  }
   538  
   539  func (s *Service) ethClientByChainID(chainID string) (erc20.ETHClient, error) {
   540  	switch chainID {
   541  	case s.primaryEthChainID:
   542  		return s.primaryEthClient, nil
   543  	case s.secondaryEthChainID:
   544  		return s.secondaryEthClient, nil
   545  	default:
   546  		return nil, fmt.Errorf("chain id %q is not supported", chainID)
   547  	}
   548  }
   549  
   550  func (s *Service) bridgeViewByChainID(chainID string) (ERC20BridgeView, error) {
   551  	switch chainID {
   552  	case s.primaryEthChainID:
   553  		return s.primaryBridgeView, nil
   554  	case s.secondaryEthChainID:
   555  		return s.secondaryBridgeView, nil
   556  	default:
   557  		return nil, fmt.Errorf("chain id %q is not supported", chainID)
   558  	}
   559  }