code.vegaprotocol.io/vega@v0.79.0/core/staking/accounting.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 staking
    17  
    18  import (
    19  	"context"
    20  	"encoding/hex"
    21  	"errors"
    22  	"fmt"
    23  	"sort"
    24  	"time"
    25  
    26  	"code.vegaprotocol.io/vega/core/contracts/erc20"
    27  	"code.vegaprotocol.io/vega/core/events"
    28  	"code.vegaprotocol.io/vega/core/types"
    29  	vgcrypto "code.vegaprotocol.io/vega/libs/crypto"
    30  	"code.vegaprotocol.io/vega/libs/num"
    31  	"code.vegaprotocol.io/vega/logging"
    32  	vgproto "code.vegaprotocol.io/vega/protos/vega"
    33  	commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1"
    34  
    35  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    36  	ethcmn "github.com/ethereum/go-ethereum/common"
    37  )
    38  
    39  var (
    40  	ErrNoBalanceForParty                = errors.New("no balance for party")
    41  	ErrStakeTotalSupplyAlreadyProcessed = errors.New("stake total supply already processed")
    42  	ErrStakeTotalSupplyBeingProcessed   = errors.New("stake total supply being processed")
    43  )
    44  
    45  // Broker - the event bus.
    46  type Broker interface {
    47  	Send(events.Event)
    48  	SendBatch([]events.Event)
    49  }
    50  
    51  type EthereumClientCaller interface {
    52  	bind.ContractCaller
    53  }
    54  
    55  // EvtForwarder forwarder information to the tendermint chain to be agreed on by validators.
    56  type EvtForwarder interface {
    57  	ForwardFromSelf(evt *commandspb.ChainEvent)
    58  }
    59  
    60  type Accounting struct {
    61  	log              *logging.Logger
    62  	ethClient        EthereumClientCaller
    63  	cfg              Config
    64  	timeService      TimeService
    65  	broker           Broker
    66  	accounts         map[string]*Account
    67  	hashableAccounts []*Account
    68  	isValidator      bool
    69  
    70  	stakingAssetTotalSupply *num.Uint
    71  	stakingBridgeAddresses  []ethcmn.Address
    72  	chainID                 string
    73  
    74  	// snapshot bits
    75  	accState accountingSnapshotState
    76  
    77  	// only used in upgrade from v0.76.8
    78  	ethSource EthereumEventSource
    79  
    80  	// these two are used in order to propagate
    81  	// the staking asset total supply at genesis.
    82  	evtFwd                  EvtForwarder
    83  	witness                 Witness
    84  	pendingStakeTotalSupply *pendingStakeTotalSupply
    85  }
    86  
    87  type pendingStakeTotalSupply struct {
    88  	sts     *types.StakeTotalSupply
    89  	chainID string
    90  	check   func() error
    91  }
    92  
    93  func (p pendingStakeTotalSupply) GetID() string {
    94  	return hex.EncodeToString(vgcrypto.Hash([]byte(p.sts.String())))
    95  }
    96  
    97  func (p pendingStakeTotalSupply) GetChainID() string { return p.chainID }
    98  
    99  func (p pendingStakeTotalSupply) GetType() types.NodeVoteType {
   100  	return types.NodeVoteTypeStakeTotalSupply
   101  }
   102  
   103  func (p *pendingStakeTotalSupply) Check(ctx context.Context) error { return p.check() }
   104  
   105  func NewAccounting(
   106  	log *logging.Logger,
   107  	cfg Config,
   108  	ts TimeService,
   109  	broker Broker,
   110  	ethClient EthereumClientCaller,
   111  	evtForward EvtForwarder,
   112  	witness Witness,
   113  	isValidator bool,
   114  	ethSource EthereumEventSource,
   115  ) (acc *Accounting) {
   116  	log = log.Named("accounting")
   117  
   118  	return &Accounting{
   119  		log:                     log,
   120  		cfg:                     cfg,
   121  		timeService:             ts,
   122  		broker:                  broker,
   123  		ethClient:               ethClient,
   124  		accounts:                map[string]*Account{},
   125  		stakingAssetTotalSupply: num.UintZero(),
   126  		accState:                accountingSnapshotState{},
   127  		evtFwd:                  evtForward,
   128  		witness:                 witness,
   129  		isValidator:             isValidator,
   130  		ethSource:               ethSource,
   131  	}
   132  }
   133  
   134  func (a *Accounting) Hash() []byte {
   135  	output := make([]byte, len(a.hashableAccounts)*32)
   136  	var i int
   137  	for _, k := range a.hashableAccounts {
   138  		bal := k.Balance.Bytes()
   139  		copy(output[i:], bal[:])
   140  		i += 32
   141  	}
   142  	h := vgcrypto.Hash(output)
   143  	a.log.Debug("stake accounts state hash", logging.String("hash", hex.EncodeToString(h)))
   144  	return h
   145  }
   146  
   147  func (a *Accounting) AddEvent(ctx context.Context, evt *types.StakeLinking) {
   148  	acc, ok := a.accounts[evt.Party]
   149  	if !ok {
   150  		acc = NewStakingAccount(evt.Party)
   151  	}
   152  
   153  	// errors here do not really matter I'd say
   154  	// they are either validation issue, that we can just log
   155  	// but should never happen as things should be created properly
   156  	// or errors from event being received in the wrong order
   157  	// but that we cannot really prevent and that the account
   158  	// would recover from by itself later on.
   159  	// Negative balance is possible when processing orders in disorder,
   160  	// not a big deal
   161  	if err := acc.AddEvent(evt); err != nil && err != ErrNegativeBalance {
   162  		a.log.Error("could not add event to staking account",
   163  			logging.Error(err))
   164  		return
   165  	}
   166  
   167  	// only add the account if all went well
   168  	if !ok {
   169  		a.broker.Send(events.NewPartyEvent(ctx, types.Party{Id: evt.Party}))
   170  		a.accounts[evt.Party] = acc
   171  		a.hashableAccounts = append(a.hashableAccounts, acc)
   172  	}
   173  }
   174  
   175  // GetAllAvailableBalances returns the staking balance for all parties.
   176  func (a *Accounting) GetAllAvailableBalances() map[string]*num.Uint {
   177  	balances := map[string]*num.Uint{}
   178  	for party, acc := range a.accounts {
   179  		balances[party] = acc.GetAvailableBalance()
   180  	}
   181  	return balances
   182  }
   183  
   184  func (a *Accounting) UpdateStakingBridgeAddress(ethCfg *types.EthereumConfig) error {
   185  	a.stakingBridgeAddresses = ethCfg.StakingBridgeAddresses()
   186  
   187  	a.chainID = ethCfg.ChainID()
   188  
   189  	if !a.accState.isRestoring {
   190  		if err := a.updateStakingAssetTotalSupply(); err != nil {
   191  			return fmt.Errorf("couldn't update the total supply of the staking asset: %w", err)
   192  		}
   193  	}
   194  
   195  	return nil
   196  }
   197  
   198  func (a *Accounting) ProcessStakeTotalSupply(_ context.Context, evt *types.StakeTotalSupply) error {
   199  	if a.stakingAssetTotalSupply.NEQ(num.UintZero()) {
   200  		return ErrStakeTotalSupplyAlreadyProcessed
   201  	}
   202  
   203  	if a.pendingStakeTotalSupply != nil {
   204  		return ErrStakeTotalSupplyBeingProcessed
   205  	}
   206  
   207  	expectedSupply := evt.TotalSupply.Clone()
   208  
   209  	a.pendingStakeTotalSupply = &pendingStakeTotalSupply{
   210  		sts:     evt,
   211  		chainID: a.chainID,
   212  		check: func() error {
   213  			totalSupply, err := a.getStakeAssetTotalSupply(a.stakingBridgeAddresses[0])
   214  			if err != nil {
   215  				return err
   216  			}
   217  
   218  			if totalSupply.NEQ(expectedSupply) {
   219  				return fmt.Errorf(
   220  					"invalid stake asset total supply, expected %s got %s",
   221  					expectedSupply.String(), totalSupply.String(),
   222  				)
   223  			}
   224  
   225  			return nil
   226  		},
   227  	}
   228  
   229  	a.log.Info("stake total supply event received, starting validation",
   230  		logging.String("event", evt.String()))
   231  
   232  	return a.witness.StartCheck(
   233  		a.pendingStakeTotalSupply,
   234  		a.onStakeTotalSupplyVerified,
   235  		a.timeService.GetTimeNow().Add(timeTilCancel),
   236  	)
   237  }
   238  
   239  func (a *Accounting) getLastBlockSeen() uint64 {
   240  	var block uint64
   241  	for _, acc := range a.hashableAccounts {
   242  		if len(acc.Events) == 0 {
   243  			continue
   244  		}
   245  		height := acc.Events[len(acc.Events)-1].BlockHeight
   246  		if block < height {
   247  			block = height
   248  		}
   249  	}
   250  	return block
   251  }
   252  
   253  func (a *Accounting) onStakeTotalSupplyVerified(event interface{}, ok bool) {
   254  	if ok {
   255  		a.stakingAssetTotalSupply = a.pendingStakeTotalSupply.sts.TotalSupply
   256  		a.log.Info("stake total supply finalized",
   257  			logging.BigUint("total-supply", a.stakingAssetTotalSupply))
   258  	}
   259  	a.pendingStakeTotalSupply = nil
   260  }
   261  
   262  func (a *Accounting) updateStakingAssetTotalSupply() error {
   263  	if !a.isValidator {
   264  		// nothing to do here if we are not a validator
   265  		return nil
   266  	}
   267  
   268  	totalSupply, err := a.getStakeAssetTotalSupply(a.stakingBridgeAddresses[0])
   269  	if err != nil {
   270  		return err
   271  	}
   272  
   273  	// send the event to be forwarded
   274  	a.evtFwd.ForwardFromSelf(&commandspb.ChainEvent{
   275  		TxId: "internal",
   276  		Event: &commandspb.ChainEvent_StakingEvent{
   277  			StakingEvent: &vgproto.StakingEvent{
   278  				Action: &vgproto.StakingEvent_TotalSupply{
   279  					TotalSupply: &vgproto.StakeTotalSupply{
   280  						TokenAddress: a.stakingBridgeAddresses[0].Hex(),
   281  						TotalSupply:  totalSupply.String(),
   282  					},
   283  				},
   284  			},
   285  		},
   286  	})
   287  
   288  	return nil
   289  }
   290  
   291  func (a *Accounting) getStakeAssetTotalSupply(address ethcmn.Address) (*num.Uint, error) {
   292  	sc, err := NewStakingCaller(address, a.ethClient)
   293  	if err != nil {
   294  		return nil, err
   295  	}
   296  
   297  	st, err := sc.StakingToken(&bind.CallOpts{})
   298  	if err != nil {
   299  		return nil, err
   300  	}
   301  
   302  	tc, err := erc20.NewErc20Caller(st, a.ethClient)
   303  	if err != nil {
   304  		return nil, err
   305  	}
   306  
   307  	ts, err := tc.TotalSupply(&bind.CallOpts{})
   308  	if err != nil {
   309  		return nil, err
   310  	}
   311  
   312  	totalSupply, overflowed := num.UintFromBig(ts)
   313  	if overflowed {
   314  		return nil, fmt.Errorf("failed to convert big.Int to num.Uint: %s", ts.String())
   315  	}
   316  
   317  	symbol, _ := tc.Symbol(&bind.CallOpts{})
   318  	decimals, _ := tc.Decimals(&bind.CallOpts{})
   319  
   320  	a.log.Debug("staking asset loaded",
   321  		logging.String("symbol", symbol),
   322  		logging.Uint8("decimals", decimals),
   323  		logging.String("total-supply", ts.String()),
   324  	)
   325  
   326  	return totalSupply, nil
   327  }
   328  
   329  func (a *Accounting) GetAllStakingParties() []string {
   330  	keys := make([]string, 0, len(a.accounts))
   331  	for k := range a.accounts {
   332  		keys = append(keys, k)
   333  	}
   334  	sort.Strings(keys)
   335  	return keys
   336  }
   337  
   338  func (a *Accounting) GetAvailableBalance(party string) (*num.Uint, error) {
   339  	acc, ok := a.accounts[party]
   340  	if !ok {
   341  		return num.UintZero(), ErrNoBalanceForParty
   342  	}
   343  
   344  	return acc.GetAvailableBalance(), nil
   345  }
   346  
   347  func (a *Accounting) GetAvailableBalanceAt(
   348  	party string, at time.Time,
   349  ) (*num.Uint, error) {
   350  	acc, ok := a.accounts[party]
   351  	if !ok {
   352  		return num.UintZero(), ErrNoBalanceForParty
   353  	}
   354  
   355  	return acc.GetAvailableBalanceAt(at)
   356  }
   357  
   358  func (a *Accounting) GetAvailableBalanceInRange(
   359  	party string, from, to time.Time,
   360  ) (*num.Uint, error) {
   361  	acc, ok := a.accounts[party]
   362  	if !ok {
   363  		return num.UintZero(), ErrNoBalanceForParty
   364  	}
   365  
   366  	return acc.GetAvailableBalanceInRange(from, to)
   367  }
   368  
   369  func (a *Accounting) GetStakingAssetTotalSupply() *num.Uint {
   370  	return a.stakingAssetTotalSupply.Clone()
   371  }