2022-07-19 11:08:06 -07:00
// The purpose of the Chain Governor is to limit the notional TVL that can leave a chain in a single day.
// It works by tracking transfers (types one and three) for a configured set of tokens from a configured set of emitters (chains).
//
// To compute the notional value of a transfer, the governor uses the amount from the transfer multiplied by the maximum of
// a hard coded price and the latest price pulled from CoinkGecko (every five minutes). Once a transfer is published,
// its value (as factored into the daily total) is fixed. However the value of pending transfers is computed using the latest price each interval.
//
// The governor maintains a rolling 24 hour window of transfers that have been received from a configured chain (emitter)
// and compares that value to the configured limit for that chain. If a new transfer would exceed the limit, it is enqueued
// until it can be published without exceeding the limit. Even if the governor has an enqueued transfer, it will still allow
// additional transfers that do not exceed the threshold.
//
// The chain governor checks for pending transfers each minute to see if any can be published yet. It will publish any that can be published
// without exceeding the daily limit, even if one in front of it in the queue is too big.
//
// All completed transfers from the last 24 hours and all pending transfers are stored in the Badger DB, and reloaded on start up.
//
// The chain governor supports admin client commands as documented in governor_cmd.go.
//
// The set of tokens to be monitored is specified in tokens.go, which can be auto generated using the tool in node/hack/governor. See the README there.
//
// The set of chains to be monitored is specified in chains.go, which can be edited by hand.
//
// To enable the chain governor, you must specified the --chainGovernorEnabled guardiand command line argument.
package governor
import (
"context"
2022-10-27 11:32:09 -07:00
"encoding/hex"
2022-07-19 11:08:06 -07:00
"fmt"
"math"
"math/big"
"sync"
"time"
"github.com/certusone/wormhole/node/pkg/common"
"github.com/certusone/wormhole/node/pkg/db"
2022-08-18 01:52:36 -07:00
"github.com/wormhole-foundation/wormhole/sdk"
"github.com/wormhole-foundation/wormhole/sdk/vaa"
2022-07-19 11:08:06 -07:00
"go.uber.org/zap"
)
const (
2022-10-27 11:32:09 -07:00
transferComplete = true
transferEnqueued = false
2022-07-19 11:08:06 -07:00
)
2022-09-12 15:19:52 -07:00
// WARNING: Change me in ./node/db as well
2023-05-04 11:05:28 -07:00
const maxEnqueuedTime = time . Hour * 24
2022-08-30 09:28:18 -07:00
2022-07-19 11:08:06 -07:00
type (
// Layout of the config data for each token
tokenConfigEntry struct {
chain uint16
addr string
symbol string
coinGeckoId string
decimals int64
price float64
}
// Layout of the config data for each chain
chainConfigEntry struct {
2022-08-30 09:28:18 -07:00
emitterChainID vaa . ChainID
dailyLimit uint64
bigTransactionSize uint64
2022-07-19 11:08:06 -07:00
}
// Key to the map of the tokens being monitored
tokenKey struct {
chain vaa . ChainID
addr vaa . Address
}
// Payload of the map of the tokens being monitored
tokenEntry struct {
price * big . Float
decimals * big . Int
symbol string
coinGeckoId string
token tokenKey
cfgPrice * big . Float
coinGeckoPrice * big . Float
priceTime time . Time
}
// Payload for each enqueued transfer
pendingEntry struct {
2022-08-30 09:28:18 -07:00
token * tokenEntry // Store a reference to the token so we can get the current price to compute the value each interval.
amount * big . Int
2022-10-27 11:32:09 -07:00
hash string
2022-08-30 09:28:18 -07:00
dbData db . PendingTransfer // This info gets persisted in the DB.
2022-07-19 11:08:06 -07:00
}
// Payload of the map of chains being monitored
chainEntry struct {
2022-08-30 09:28:18 -07:00
emitterChainId vaa . ChainID
emitterAddr vaa . Address
dailyLimit uint64
bigTransactionSize uint64
checkForBigTransactions bool
transfers [ ] * db . Transfer
pending [ ] * pendingEntry
2022-07-19 11:08:06 -07:00
}
)
2022-08-30 09:28:18 -07:00
func ( ce * chainEntry ) isBigTransfer ( value uint64 ) bool {
return value >= ce . bigTransactionSize && ce . checkForBigTransactions
}
2022-07-19 11:08:06 -07:00
type ChainGovernor struct {
2023-05-03 15:57:54 -07:00
db db . GovernorDB // protected by `mutex`
2022-09-26 09:24:30 -07:00
logger * zap . Logger
mutex sync . Mutex
2023-05-03 15:57:54 -07:00
tokens map [ tokenKey ] * tokenEntry // protected by `mutex`
tokensByCoinGeckoId map [ string ] [ ] * tokenEntry // protected by `mutex`
chains map [ vaa . ChainID ] * chainEntry // protected by `mutex`
msgsSeen map [ string ] bool // protected by `mutex` // Key is hash, payload is consts transferComplete and transferEnqueued.
msgsToPublish [ ] * common . MessagePublication // protected by `mutex`
2022-09-26 09:24:30 -07:00
dayLengthInMinutes int
2023-04-13 06:18:41 -07:00
coinGeckoQueries [ ] string
2023-05-24 11:17:08 -07:00
env common . Environment
2022-09-26 09:24:30 -07:00
nextStatusPublishTime time . Time
nextConfigPublishTime time . Time
statusPublishCounter int64
configPublishCounter int64
2022-07-19 11:08:06 -07:00
}
func NewChainGovernor (
logger * zap . Logger ,
2022-08-09 20:22:14 -07:00
db db . GovernorDB ,
2023-05-24 11:17:08 -07:00
env common . Environment ,
2022-07-19 11:08:06 -07:00
) * ChainGovernor {
return & ChainGovernor {
db : db ,
2023-04-18 09:38:24 -07:00
logger : logger . With ( zap . String ( "component" , "cgov" ) ) ,
2022-07-19 11:08:06 -07:00
tokens : make ( map [ tokenKey ] * tokenEntry ) ,
2022-08-10 08:53:48 -07:00
tokensByCoinGeckoId : make ( map [ string ] [ ] * tokenEntry ) ,
2022-07-19 11:08:06 -07:00
chains : make ( map [ vaa . ChainID ] * chainEntry ) ,
2022-10-27 11:32:09 -07:00
msgsSeen : make ( map [ string ] bool ) ,
2022-07-19 11:08:06 -07:00
env : env ,
}
}
func ( gov * ChainGovernor ) Run ( ctx context . Context ) error {
2023-04-18 09:38:24 -07:00
gov . logger . Info ( "starting chain governor" )
2022-07-19 11:08:06 -07:00
if err := gov . initConfig ( ) ; err != nil {
return err
}
2023-05-24 11:17:08 -07:00
if gov . env != common . GoTest {
2022-07-19 11:08:06 -07:00
if err := gov . loadFromDB ( ) ; err != nil {
return err
}
2022-08-30 09:28:18 -07:00
if err := gov . initCoinGecko ( ctx , true ) ; err != nil {
2022-07-19 11:08:06 -07:00
return err
}
}
return nil
}
func ( gov * ChainGovernor ) initConfig ( ) error {
gov . mutex . Lock ( )
defer gov . mutex . Unlock ( )
gov . dayLengthInMinutes = 24 * 60
configTokens := tokenList ( )
configChains := chainList ( )
2023-05-24 11:17:08 -07:00
if gov . env == common . UnsafeDevNet {
2022-07-19 11:08:06 -07:00
configTokens , configChains = gov . initDevnetConfig ( )
2023-05-24 11:17:08 -07:00
} else if gov . env == common . TestNet {
2022-07-19 11:08:06 -07:00
configTokens , configChains = gov . initTestnetConfig ( )
}
for _ , ct := range configTokens {
addr , err := vaa . StringToAddress ( ct . addr )
if err != nil {
return fmt . Errorf ( "invalid address: %s" , ct . addr )
}
cfgPrice := big . NewFloat ( ct . price )
initialPrice := new ( big . Float )
initialPrice . Set ( cfgPrice )
// Transfers have a maximum of eight decimal places.
dec := ct . decimals
if dec > 8 {
dec = 8
}
decimalsFloat := big . NewFloat ( math . Pow ( 10.0 , float64 ( dec ) ) )
decimals , _ := decimalsFloat . Int ( nil )
2023-04-17 12:43:14 -07:00
// Some Solana tokens don't have the symbol set. In that case, use the chain and token address as the symbol.
symbol := ct . symbol
if symbol == "" {
symbol = fmt . Sprintf ( "%d:%s" , ct . chain , ct . addr )
}
2022-07-19 11:08:06 -07:00
key := tokenKey { chain : vaa . ChainID ( ct . chain ) , addr : addr }
2023-04-17 12:43:14 -07:00
te := & tokenEntry { cfgPrice : cfgPrice , price : initialPrice , decimals : decimals , symbol : symbol , coinGeckoId : ct . coinGeckoId , token : key }
2022-07-19 11:08:06 -07:00
te . updatePrice ( )
2022-08-10 08:53:48 -07:00
gov . tokens [ key ] = te
// Multiple tokens can share a CoinGecko price, so we keep an array of tokens per CoinGecko ID.
cge , cgExists := gov . tokensByCoinGeckoId [ te . coinGeckoId ]
if ! cgExists {
gov . tokensByCoinGeckoId [ te . coinGeckoId ] = [ ] * tokenEntry { te }
} else {
cge = append ( cge , te )
gov . tokensByCoinGeckoId [ te . coinGeckoId ] = cge
}
2023-06-12 14:17:26 -07:00
if gov . env != common . GoTest {
gov . logger . Info ( "will monitor token:" , zap . Stringer ( "chain" , key . chain ) ,
zap . Stringer ( "addr" , key . addr ) ,
zap . String ( "symbol" , te . symbol ) ,
zap . String ( "coinGeckoId" , te . coinGeckoId ) ,
zap . String ( "price" , te . price . String ( ) ) ,
zap . Int64 ( "decimals" , dec ) ,
zap . Int64 ( "origDecimals" , ct . decimals ) ,
)
}
2022-07-19 11:08:06 -07:00
}
if len ( gov . tokens ) == 0 {
return fmt . Errorf ( "no tokens are configured" )
}
2022-08-18 01:52:36 -07:00
emitterMap := & sdk . KnownTokenbridgeEmitters
2023-05-24 11:17:08 -07:00
if gov . env == common . TestNet {
2022-08-18 01:52:36 -07:00
emitterMap = & sdk . KnownTestnetTokenbridgeEmitters
2023-05-24 11:17:08 -07:00
} else if gov . env == common . UnsafeDevNet {
2022-08-18 01:52:36 -07:00
emitterMap = & sdk . KnownDevnetTokenbridgeEmitters
2022-07-19 11:08:06 -07:00
}
for _ , cc := range configChains {
var emitterAddr vaa . Address
var err error
emitterAddrBytes , exists := ( * emitterMap ) [ cc . emitterChainID ]
if ! exists {
return fmt . Errorf ( "failed to look up token bridge emitter address for chain: %v" , cc . emitterChainID )
}
emitterAddr , err = vaa . BytesToAddress ( emitterAddrBytes )
if err != nil {
return fmt . Errorf ( "failed to convert emitter address for chain: %v" , cc . emitterChainID )
}
2022-08-30 09:28:18 -07:00
ce := & chainEntry {
emitterChainId : cc . emitterChainID ,
emitterAddr : emitterAddr ,
dailyLimit : cc . dailyLimit ,
bigTransactionSize : cc . bigTransactionSize ,
checkForBigTransactions : cc . bigTransactionSize != 0 ,
}
2022-07-19 11:08:06 -07:00
2023-06-12 14:17:26 -07:00
if gov . env != common . GoTest {
gov . logger . Info ( "will monitor chain:" , zap . Stringer ( "emitterChainId" , cc . emitterChainID ) ,
zap . Stringer ( "emitterAddr" , ce . emitterAddr ) ,
zap . String ( "dailyLimit" , fmt . Sprint ( ce . dailyLimit ) ) ,
zap . Uint64 ( "bigTransactionSize" , ce . bigTransactionSize ) ,
zap . Bool ( "checkForBigTransactions" , ce . checkForBigTransactions ) ,
)
}
2022-07-19 11:08:06 -07:00
gov . chains [ cc . emitterChainID ] = ce
}
if len ( gov . chains ) == 0 {
return fmt . Errorf ( "no chains are configured" )
}
return nil
}
// Returns true if the message can be published, false if it has been added to the pending list.
func ( gov * ChainGovernor ) ProcessMsg ( msg * common . MessagePublication ) bool {
publish , err := gov . ProcessMsgForTime ( msg , time . Now ( ) )
if err != nil {
2023-04-18 09:38:24 -07:00
gov . logger . Error ( "failed to process VAA: %v" , zap . Error ( err ) )
2022-07-19 11:08:06 -07:00
return false
}
return publish
}
func ( gov * ChainGovernor ) ProcessMsgForTime ( msg * common . MessagePublication , now time . Time ) ( bool , error ) {
if msg == nil {
return false , fmt . Errorf ( "msg is nil" )
}
gov . mutex . Lock ( )
defer gov . mutex . Unlock ( )
2023-02-13 07:10:52 -08:00
msgIsGoverned , ce , token , payload , err := gov . parseMsgAlreadyLocked ( msg )
2022-07-19 11:08:06 -07:00
if err != nil {
2023-02-13 07:10:52 -08:00
return false , err
2022-07-19 11:08:06 -07:00
}
2023-02-13 07:10:52 -08:00
if ! msgIsGoverned {
2022-07-19 11:08:06 -07:00
return true , nil
}
2022-10-27 11:32:09 -07:00
hash := gov . HashFromMsg ( msg )
xferComplete , alreadySeen := gov . msgsSeen [ hash ]
if alreadySeen {
if ! xferComplete {
2023-04-18 09:38:24 -07:00
gov . logger . Info ( "ignoring duplicate vaa because it is enqueued" ,
2022-10-27 11:32:09 -07:00
zap . String ( "msgID" , msg . MessageIDString ( ) ) ,
zap . String ( "hash" , hash ) ,
zap . Stringer ( "txHash" , msg . TxHash ) ,
)
return false , nil
}
2023-04-18 09:38:24 -07:00
gov . logger . Info ( "allowing duplicate vaa to be published again, but not adding it to the notional value" ,
2022-10-27 11:32:09 -07:00
zap . String ( "msgID" , msg . MessageIDString ( ) ) ,
zap . String ( "hash" , hash ) ,
zap . Stringer ( "txHash" , msg . TxHash ) ,
)
return true , nil
}
2022-07-19 11:08:06 -07:00
startTime := now . Add ( - time . Minute * time . Duration ( gov . dayLengthInMinutes ) )
2022-10-27 11:32:09 -07:00
prevTotalValue , err := gov . TrimAndSumValueForChain ( ce , startTime )
2022-07-19 11:08:06 -07:00
if err != nil {
2023-04-18 09:38:24 -07:00
gov . logger . Error ( "failed to trim transfers" ,
2022-10-27 11:32:09 -07:00
zap . String ( "msgID" , msg . MessageIDString ( ) ) ,
zap . String ( "hash" , hash ) ,
zap . Stringer ( "txHash" , msg . TxHash ) ,
zap . Error ( err ) ,
)
2022-07-19 11:08:06 -07:00
return false , err
}
value , err := computeValue ( payload . Amount , token )
if err != nil {
2023-04-18 09:38:24 -07:00
gov . logger . Error ( "failed to compute value of transfer" ,
2022-10-27 11:32:09 -07:00
zap . String ( "msgID" , msg . MessageIDString ( ) ) ,
zap . String ( "hash" , hash ) ,
zap . Stringer ( "txHash" , msg . TxHash ) ,
zap . Error ( err ) ,
)
2022-07-19 11:08:06 -07:00
return false , err
}
newTotalValue := prevTotalValue + value
2022-08-09 20:22:14 -07:00
if newTotalValue < prevTotalValue {
2023-04-18 09:38:24 -07:00
gov . logger . Error ( "total value has overflowed" ,
2022-10-27 11:32:09 -07:00
zap . String ( "msgID" , msg . MessageIDString ( ) ) ,
zap . String ( "hash" , hash ) ,
zap . Stringer ( "txHash" , msg . TxHash ) ,
zap . Uint64 ( "prevTotalValue" , prevTotalValue ) ,
zap . Uint64 ( "newTotalValue" , newTotalValue ) ,
)
2022-08-09 20:22:14 -07:00
return false , fmt . Errorf ( "total value has overflowed" )
}
2022-07-19 11:08:06 -07:00
2022-08-30 09:28:18 -07:00
enqueueIt := false
var releaseTime time . Time
if ce . isBigTransfer ( value ) {
enqueueIt = true
2022-09-12 15:19:52 -07:00
releaseTime = now . Add ( maxEnqueuedTime )
2023-04-18 09:38:24 -07:00
gov . logger . Error ( "enqueuing vaa because it is a big transaction" ,
2022-08-30 09:28:18 -07:00
zap . Uint64 ( "value" , value ) ,
zap . Uint64 ( "prevTotalValue" , prevTotalValue ) ,
zap . Uint64 ( "newTotalValue" , newTotalValue ) ,
2022-10-06 05:57:25 -07:00
zap . String ( "msgID" , msg . MessageIDString ( ) ) ,
2022-08-30 09:28:18 -07:00
zap . Stringer ( "releaseTime" , releaseTime ) ,
zap . Uint64 ( "bigTransactionSize" , ce . bigTransactionSize ) ,
2022-10-27 11:32:09 -07:00
zap . String ( "hash" , hash ) ,
zap . Stringer ( "txHash" , msg . TxHash ) ,
2022-08-30 09:28:18 -07:00
)
} else if newTotalValue > ce . dailyLimit {
enqueueIt = true
2022-09-12 15:19:52 -07:00
releaseTime = now . Add ( maxEnqueuedTime )
2023-04-18 09:38:24 -07:00
gov . logger . Error ( "enqueuing vaa because it would exceed the daily limit" ,
2022-08-09 20:22:14 -07:00
zap . Uint64 ( "value" , value ) ,
zap . Uint64 ( "prevTotalValue" , prevTotalValue ) ,
zap . Uint64 ( "newTotalValue" , newTotalValue ) ,
2022-08-30 09:28:18 -07:00
zap . Stringer ( "releaseTime" , releaseTime ) ,
2022-10-06 05:57:25 -07:00
zap . String ( "msgID" , msg . MessageIDString ( ) ) ,
2022-10-27 11:32:09 -07:00
zap . String ( "hash" , hash ) ,
zap . Stringer ( "txHash" , msg . TxHash ) ,
2022-08-30 09:28:18 -07:00
)
}
2022-07-19 11:08:06 -07:00
2022-08-30 09:28:18 -07:00
if enqueueIt {
dbData := db . PendingTransfer { ReleaseTime : releaseTime , Msg : * msg }
err = gov . db . StorePendingMsg ( & dbData )
2022-08-09 20:22:14 -07:00
if err != nil {
2023-04-18 09:38:24 -07:00
gov . logger . Error ( "failed to store pending vaa" ,
2022-10-27 11:32:09 -07:00
zap . String ( "msgID" , msg . MessageIDString ( ) ) ,
zap . String ( "hash" , hash ) ,
zap . Stringer ( "txHash" , msg . TxHash ) ,
zap . Error ( err ) ,
)
2022-08-09 20:22:14 -07:00
return false , err
2022-07-19 11:08:06 -07:00
}
2022-10-27 11:32:09 -07:00
ce . pending = append ( ce . pending , & pendingEntry { token : token , amount : payload . Amount , hash : hash , dbData : dbData } )
gov . msgsSeen [ hash ] = transferEnqueued
2022-07-19 11:08:06 -07:00
return false , nil
}
2023-04-18 09:38:24 -07:00
gov . logger . Info ( "posting vaa" ,
2022-08-09 20:22:14 -07:00
zap . Uint64 ( "value" , value ) ,
zap . Uint64 ( "prevTotalValue" , prevTotalValue ) ,
zap . Uint64 ( "newTotalValue" , newTotalValue ) ,
2022-10-27 11:32:09 -07:00
zap . String ( "msgID" , msg . MessageIDString ( ) ) ,
zap . String ( "hash" , hash ) ,
zap . Stringer ( "txHash" , msg . TxHash ) ,
)
xfer := db . Transfer { Timestamp : now ,
Value : value ,
OriginChain : token . token . chain ,
OriginAddress : token . token . addr ,
EmitterChain : msg . EmitterChain ,
EmitterAddress : msg . EmitterAddress ,
MsgID : msg . MessageIDString ( ) ,
Hash : hash ,
}
2022-10-06 05:57:25 -07:00
err = gov . db . StoreTransfer ( & xfer )
2022-08-09 20:22:14 -07:00
if err != nil {
2023-04-18 09:38:24 -07:00
gov . logger . Error ( "failed to store transfer" ,
2022-10-27 11:32:09 -07:00
zap . String ( "msgID" , msg . MessageIDString ( ) ) ,
zap . String ( "hash" , hash ) , zap . Error ( err ) ,
zap . Stringer ( "txHash" , msg . TxHash ) ,
)
2022-08-09 20:22:14 -07:00
return false , err
2022-07-19 11:08:06 -07:00
}
2022-10-27 11:32:09 -07:00
ce . transfers = append ( ce . transfers , & xfer )
gov . msgsSeen [ hash ] = transferComplete
2022-07-19 11:08:06 -07:00
return true , nil
}
2023-02-13 07:10:52 -08:00
// IsGovernedMsg determines if the message applies to the governor. It grabs the lock.
func ( gov * ChainGovernor ) IsGovernedMsg ( msg * common . MessagePublication ) ( msgIsGoverned bool , err error ) {
gov . mutex . Lock ( )
defer gov . mutex . Unlock ( )
msgIsGoverned , _ , _ , _ , err = gov . parseMsgAlreadyLocked ( msg )
return
}
// parseMsgAlreadyLocked determines if the message applies to the governor and also returns data useful to the governor. It assumes the caller holds the lock.
func ( gov * ChainGovernor ) parseMsgAlreadyLocked ( msg * common . MessagePublication ) ( bool , * chainEntry , * tokenEntry , * vaa . TransferPayloadHdr , error ) {
// If we don't care about this chain, the VAA can be published.
ce , exists := gov . chains [ msg . EmitterChain ]
if ! exists {
if msg . EmitterChain != vaa . ChainIDPythNet {
2023-04-18 09:38:24 -07:00
gov . logger . Info ( "ignoring vaa because the emitter chain is not configured" , zap . String ( "msgID" , msg . MessageIDString ( ) ) )
2023-02-13 07:10:52 -08:00
}
return false , nil , nil , nil , nil
}
// If we don't care about this emitter, the VAA can be published.
if msg . EmitterAddress != ce . emitterAddr {
2023-04-18 09:38:24 -07:00
gov . logger . Info ( "ignoring vaa because the emitter address is not configured" , zap . String ( "msgID" , msg . MessageIDString ( ) ) )
2023-02-13 07:10:52 -08:00
return false , nil , nil , nil , nil
}
// We only care about transfers.
if ! vaa . IsTransfer ( msg . Payload ) {
2023-04-18 09:38:24 -07:00
gov . logger . Info ( "ignoring vaa because it is not a transfer" , zap . String ( "msgID" , msg . MessageIDString ( ) ) )
2023-02-13 07:10:52 -08:00
return false , nil , nil , nil , nil
}
payload , err := vaa . DecodeTransferPayloadHdr ( msg . Payload )
if err != nil {
2023-04-18 09:38:24 -07:00
gov . logger . Error ( "failed to decode vaa" , zap . String ( "msgID" , msg . MessageIDString ( ) ) , zap . Error ( err ) )
2023-02-13 07:10:52 -08:00
return false , nil , nil , nil , err
}
// If we don't care about this token, the VAA can be published.
tk := tokenKey { chain : payload . OriginChain , addr : payload . OriginAddress }
token , exists := gov . tokens [ tk ]
if ! exists {
2023-04-18 09:38:24 -07:00
gov . logger . Info ( "ignoring vaa because the token is not in the list" , zap . String ( "msgID" , msg . MessageIDString ( ) ) )
2023-02-13 07:10:52 -08:00
return false , nil , nil , nil , nil
}
return true , ce , token , payload , nil
}
2022-07-19 11:08:06 -07:00
func ( gov * ChainGovernor ) CheckPending ( ) ( [ ] * common . MessagePublication , error ) {
return gov . CheckPendingForTime ( time . Now ( ) )
}
func ( gov * ChainGovernor ) CheckPendingForTime ( now time . Time ) ( [ ] * common . MessagePublication , error ) {
gov . mutex . Lock ( )
defer gov . mutex . Unlock ( )
2022-08-09 20:22:14 -07:00
// Note: Using Add() with a negative value because Sub() takes a time and returns a duration, which is not what we want.
2022-07-19 11:08:06 -07:00
startTime := now . Add ( - time . Minute * time . Duration ( gov . dayLengthInMinutes ) )
var msgsToPublish [ ] * common . MessagePublication
if len ( gov . msgsToPublish ) != 0 {
2023-04-18 09:38:24 -07:00
gov . logger . Info ( "posting released vaas" , zap . Int ( "num" , len ( gov . msgsToPublish ) ) )
2022-07-19 11:08:06 -07:00
msgsToPublish = gov . msgsToPublish
gov . msgsToPublish = nil
}
for _ , ce := range gov . chains {
// Keep going as long as we find something that will fit.
for {
foundOne := false
2022-10-27 11:32:09 -07:00
prevTotalValue , err := gov . TrimAndSumValueForChain ( ce , startTime )
2022-07-19 11:08:06 -07:00
if err != nil {
2023-04-18 09:38:24 -07:00
gov . logger . Error ( "failed to trim transfers" , zap . Error ( err ) )
2022-08-09 20:22:14 -07:00
gov . msgsToPublish = msgsToPublish
return nil , err
2022-07-19 11:08:06 -07:00
}
// Keep going until we find something that fits or hit the end.
for idx , pe := range ce . pending {
value , err := computeValue ( pe . amount , pe . token )
if err != nil {
2023-04-18 09:38:24 -07:00
gov . logger . Error ( "failed to compute value for pending vaa" ,
2022-08-09 20:22:14 -07:00
zap . Stringer ( "amount" , pe . amount ) ,
zap . Stringer ( "price" , pe . token . price ) ,
2022-10-06 05:57:25 -07:00
zap . String ( "msgID" , pe . dbData . Msg . MessageIDString ( ) ) ,
2022-08-09 20:22:14 -07:00
zap . Error ( err ) ,
)
gov . msgsToPublish = msgsToPublish
return nil , err
2022-07-19 11:08:06 -07:00
}
2022-08-30 09:28:18 -07:00
countsTowardsTransfers := true
if ce . isBigTransfer ( value ) {
if now . Before ( pe . dbData . ReleaseTime ) {
continue // Keep waiting for the timer to expire.
}
2022-08-09 20:22:14 -07:00
2022-08-30 09:28:18 -07:00
countsTowardsTransfers = false
2023-04-18 09:38:24 -07:00
gov . logger . Info ( "posting pending big vaa because the release time has been reached" ,
2022-08-30 09:28:18 -07:00
zap . Stringer ( "amount" , pe . amount ) ,
zap . Stringer ( "price" , pe . token . price ) ,
zap . Uint64 ( "value" , value ) ,
zap . Stringer ( "releaseTime" , pe . dbData . ReleaseTime ) ,
2022-10-06 05:57:25 -07:00
zap . String ( "msgID" , pe . dbData . Msg . MessageIDString ( ) ) )
2022-08-30 09:28:18 -07:00
} else if now . After ( pe . dbData . ReleaseTime ) {
countsTowardsTransfers = false
2023-04-18 09:38:24 -07:00
gov . logger . Info ( "posting pending vaa because the release time has been reached" ,
2022-08-30 09:28:18 -07:00
zap . Stringer ( "amount" , pe . amount ) ,
zap . Stringer ( "price" , pe . token . price ) ,
zap . Uint64 ( "value" , value ) ,
zap . Stringer ( "releaseTime" , pe . dbData . ReleaseTime ) ,
2022-10-06 05:57:25 -07:00
zap . String ( "msgID" , pe . dbData . Msg . MessageIDString ( ) ) )
2022-08-30 09:28:18 -07:00
} else {
newTotalValue := prevTotalValue + value
if newTotalValue < prevTotalValue {
gov . msgsToPublish = msgsToPublish
return nil , fmt . Errorf ( "total value has overflowed" )
}
if newTotalValue > ce . dailyLimit {
// This one won't fit. Keep checking other enqueued ones.
continue
}
2023-04-18 09:38:24 -07:00
gov . logger . Info ( "posting pending vaa" ,
2022-08-30 09:28:18 -07:00
zap . Stringer ( "amount" , pe . amount ) ,
zap . Stringer ( "price" , pe . token . price ) ,
zap . Uint64 ( "value" , value ) ,
zap . Uint64 ( "prevTotalValue" , prevTotalValue ) ,
zap . Uint64 ( "newTotalValue" , newTotalValue ) ,
2022-10-06 05:57:25 -07:00
zap . String ( "msgID" , pe . dbData . Msg . MessageIDString ( ) ) )
2022-07-19 11:08:06 -07:00
}
2022-08-30 09:28:18 -07:00
// If we get here, publish it and remove it from the pending list.
msgsToPublish = append ( msgsToPublish , & pe . dbData . Msg )
2022-07-19 11:08:06 -07:00
2022-08-30 09:28:18 -07:00
if countsTowardsTransfers {
2022-10-27 11:32:09 -07:00
xfer := db . Transfer { Timestamp : now ,
Value : value ,
OriginChain : pe . token . token . chain ,
OriginAddress : pe . token . token . addr ,
EmitterChain : pe . dbData . Msg . EmitterChain ,
EmitterAddress : pe . dbData . Msg . EmitterAddress ,
MsgID : pe . dbData . Msg . MessageIDString ( ) ,
Hash : pe . hash ,
}
2022-07-19 11:08:06 -07:00
2022-10-06 05:57:25 -07:00
if err := gov . db . StoreTransfer ( & xfer ) ; err != nil {
2022-08-30 09:28:18 -07:00
gov . msgsToPublish = msgsToPublish
return nil , err
}
2022-10-27 11:32:09 -07:00
ce . transfers = append ( ce . transfers , & xfer )
gov . msgsSeen [ pe . hash ] = transferComplete
} else {
delete ( gov . msgsSeen , pe . hash )
2022-07-19 11:08:06 -07:00
}
2022-08-30 09:28:18 -07:00
if err := gov . db . DeletePendingMsg ( & pe . dbData ) ; err != nil {
2022-08-09 20:22:14 -07:00
gov . msgsToPublish = msgsToPublish
return nil , err
2022-07-19 11:08:06 -07:00
}
ce . pending = append ( ce . pending [ : idx ] , ce . pending [ idx + 1 : ] ... )
foundOne = true
break // We messed up our loop indexing, so we have to break out and start over.
}
if ! foundOne {
break
}
}
}
return msgsToPublish , nil
}
func computeValue ( amount * big . Int , token * tokenEntry ) ( uint64 , error ) {
amountFloat := new ( big . Float )
amountFloat = amountFloat . SetInt ( amount )
valueFloat := new ( big . Float )
valueFloat = valueFloat . Mul ( amountFloat , token . price )
valueBigInt , _ := valueFloat . Int ( nil )
valueBigInt = valueBigInt . Div ( valueBigInt , token . decimals )
if ! valueBigInt . IsUint64 ( ) {
return 0 , fmt . Errorf ( "value is too large to fit in uint64" )
}
value := valueBigInt . Uint64 ( )
return value , nil
}
2022-10-27 11:32:09 -07:00
func ( gov * ChainGovernor ) TrimAndSumValueForChain ( ce * chainEntry , startTime time . Time ) ( sum uint64 , err error ) {
sum , ce . transfers , err = gov . TrimAndSumValue ( ce . transfers , startTime )
2022-07-19 11:08:06 -07:00
return sum , err
}
2022-10-27 11:32:09 -07:00
func ( gov * ChainGovernor ) TrimAndSumValue ( transfers [ ] * db . Transfer , startTime time . Time ) ( uint64 , [ ] * db . Transfer , error ) {
2022-07-19 11:08:06 -07:00
if len ( transfers ) == 0 {
return 0 , transfers , nil
}
var trimIdx int = - 1
var sum uint64
for idx , t := range transfers {
if t . Timestamp . Before ( startTime ) {
trimIdx = idx
} else {
sum += t . Value
}
}
if trimIdx >= 0 {
2022-10-27 11:32:09 -07:00
for idx := 0 ; idx <= trimIdx ; idx ++ {
if err := gov . db . DeleteTransfer ( transfers [ idx ] ) ; err != nil {
return 0 , transfers , err
2022-07-19 11:08:06 -07:00
}
2022-10-27 11:32:09 -07:00
delete ( gov . msgsSeen , transfers [ idx ] . Hash )
2022-07-19 11:08:06 -07:00
}
transfers = transfers [ trimIdx + 1 : ]
}
return sum , transfers , nil
}
func ( tk tokenKey ) String ( ) string {
return tk . chain . String ( ) + ":" + tk . addr . String ( )
}
2022-10-27 11:32:09 -07:00
func ( gov * ChainGovernor ) HashFromMsg ( msg * common . MessagePublication ) string {
v := msg . CreateVAA ( 0 ) // We can pass zero in as the guardian set index because it is not part of the digest.
2023-02-22 18:31:15 -08:00
digest := v . SigningDigest ( )
2022-10-27 11:32:09 -07:00
return hex . EncodeToString ( digest . Bytes ( ) )
}