/* Package stateapiupdater is responsible for generating and storing the object response of the GET /state endpoint exposed through the api package. This object is extensively defined at the OpenAPI spec located at api/swagger.yml. Deployment considerations: in a setup where multiple processes are used (dedicated api process, separated coord / sync, ...), only one process should care of using this package. */ package stateapiupdater import ( "database/sql" "fmt" "sync" "github.com/hermeznetwork/hermez-node/common" "github.com/hermeznetwork/hermez-node/db/historydb" "github.com/hermeznetwork/hermez-node/log" "github.com/hermeznetwork/tracerr" ) // Updater is an utility object to facilitate updating the StateAPI type Updater struct { hdb *historydb.HistoryDB state historydb.StateAPI config historydb.NodeConfig vars common.SCVariablesPtr consts historydb.Constants rw sync.RWMutex rfp *RecommendedFeePolicy } // RecommendedFeePolicy describes how the recommended fee is calculated type RecommendedFeePolicy struct { PolicyType RecommendedFeePolicyType `validate:"required"` StaticValue float64 } // RecommendedFeePolicyType describes the different available recommended fee strategies type RecommendedFeePolicyType string const ( // RecommendedFeePolicyTypeStatic always give the same StaticValue as recommended fee RecommendedFeePolicyTypeStatic RecommendedFeePolicyType = "Static" // RecommendedFeePolicyTypeAvgLastHour set the recommended fee using the average fee of the last hour RecommendedFeePolicyTypeAvgLastHour RecommendedFeePolicyType = "AvgLastHour" ) func (rfp *RecommendedFeePolicy) valid() bool { switch rfp.PolicyType { case RecommendedFeePolicyTypeStatic: if rfp.StaticValue == 0 { log.Warn("RcommendedFee is set to 0 USD, and the policy is static") } return true case RecommendedFeePolicyTypeAvgLastHour: return true default: return false } } // NewUpdater creates a new Updater func NewUpdater(hdb *historydb.HistoryDB, config *historydb.NodeConfig, vars *common.SCVariables, consts *historydb.Constants, rfp *RecommendedFeePolicy) (*Updater, error) { if ok := rfp.valid(); !ok { return nil, tracerr.Wrap(fmt.Errorf("Invalid recommended fee policy: %v", rfp.PolicyType)) } u := Updater{ hdb: hdb, config: *config, consts: *consts, state: historydb.StateAPI{ NodePublicInfo: historydb.NodePublicInfo{ ForgeDelay: config.ForgeDelay, }, }, rfp: rfp, } u.SetSCVars(vars.AsPtr()) return &u, nil } // Store the State in the HistoryDB func (u *Updater) Store() error { u.rw.RLock() defer u.rw.RUnlock() return tracerr.Wrap(u.hdb.SetStateInternalAPI(&u.state)) } // SetSCVars sets the smart contract vars (ony updates those that are not nil) func (u *Updater) SetSCVars(vars *common.SCVariablesPtr) { u.rw.Lock() defer u.rw.Unlock() if vars.Rollup != nil { u.vars.Rollup = vars.Rollup rollupVars := historydb.NewRollupVariablesAPI(u.vars.Rollup) u.state.Rollup = *rollupVars } if vars.Auction != nil { u.vars.Auction = vars.Auction auctionVars := historydb.NewAuctionVariablesAPI(u.vars.Auction) u.state.Auction = *auctionVars } if vars.WDelayer != nil { u.vars.WDelayer = vars.WDelayer u.state.WithdrawalDelayer = *u.vars.WDelayer } } // UpdateRecommendedFee update Status.RecommendedFee information func (u *Updater) UpdateRecommendedFee() error { switch u.rfp.PolicyType { case RecommendedFeePolicyTypeStatic: u.rw.Lock() u.state.RecommendedFee = common.RecommendedFee{ ExistingAccount: u.rfp.StaticValue, CreatesAccount: u.rfp.StaticValue, CreatesAccountInternal: u.rfp.StaticValue, } u.rw.Unlock() case RecommendedFeePolicyTypeAvgLastHour: recommendedFee, err := u.hdb.GetRecommendedFee(u.config.MinFeeUSD, u.config.MaxFeeUSD) if err != nil { return tracerr.Wrap(err) } u.rw.Lock() u.state.RecommendedFee = *recommendedFee u.rw.Unlock() default: return tracerr.New("Invalid recommende fee policy") } return nil } // UpdateMetrics update Status.Metrics information func (u *Updater) UpdateMetrics() error { u.rw.RLock() lastBatch := u.state.Network.LastBatch u.rw.RUnlock() if lastBatch == nil { return nil } lastBatchNum := lastBatch.BatchNum metrics, poolLoad, err := u.hdb.GetMetricsInternalAPI(lastBatchNum) if err != nil { return tracerr.Wrap(err) } u.rw.Lock() u.state.Metrics = *metrics u.state.NodePublicInfo.PoolLoad = poolLoad u.rw.Unlock() return nil } // UpdateNetworkInfoBlock update Status.Network block related information func (u *Updater) UpdateNetworkInfoBlock(lastEthBlock, lastSyncBlock common.Block) { u.rw.Lock() u.state.Network.LastSyncBlock = lastSyncBlock.Num u.state.Network.LastEthBlock = lastEthBlock.Num u.rw.Unlock() } // UpdateNetworkInfo update Status.Network information func (u *Updater) UpdateNetworkInfo( lastEthBlock, lastSyncBlock common.Block, lastBatchNum common.BatchNum, currentSlot int64, ) error { // Get last batch in API format lastBatch, err := u.hdb.GetBatchInternalAPI(lastBatchNum) if tracerr.Unwrap(err) == sql.ErrNoRows { lastBatch = nil } else if err != nil { return tracerr.Wrap(err) } u.rw.RLock() auctionVars := u.vars.Auction u.rw.RUnlock() // Get next forgers lastClosedSlot := currentSlot + int64(auctionVars.ClosedAuctionSlots) nextForgers, err := u.hdb.GetNextForgersInternalAPI(auctionVars, &u.consts.Auction, lastSyncBlock, currentSlot, lastClosedSlot) if tracerr.Unwrap(err) == sql.ErrNoRows { nextForgers = nil } else if err != nil { return tracerr.Wrap(err) } bucketUpdates, err := u.hdb.GetBucketUpdatesInternalAPI() if err == sql.ErrNoRows { bucketUpdates = nil } else if err != nil { return tracerr.Wrap(err) } u.rw.Lock() // Update NodeInfo struct for i, bucketParams := range u.state.Rollup.Buckets { for _, bucketUpdate := range bucketUpdates { if bucketUpdate.NumBucket == i { bucketParams.Withdrawals = bucketUpdate.Withdrawals u.state.Rollup.Buckets[i] = bucketParams break } } } // Update pending L1s pendingL1s, err := u.hdb.GetUnforgedL1UserTxsCount() if err != nil { return tracerr.Wrap(err) } u.state.Network.LastSyncBlock = lastSyncBlock.Num u.state.Network.LastEthBlock = lastEthBlock.Num u.state.Network.LastBatch = lastBatch u.state.Network.CurrentSlot = currentSlot u.state.Network.NextForgers = nextForgers u.state.Network.PendingL1Txs = pendingL1s u.rw.Unlock() return nil }