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 }