Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement core space gas station #195

Merged
merged 3 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func startNativeSpaceRpcServer(ctx context.Context, wg *sync.WaitGroup, storeCtx
}

// initialize gas station handler
gasHandler := handler.NewGasStationHandler(storeCtx.CfxDB, storeCtx.CfxCache)
gasHandler := handler.MustNewCfxGasStationHandlerFromViper(clientProvider)

if storeCtx.CfxDB != nil {
// initialize pruned logs handler
Expand Down
9 changes: 9 additions & 0 deletions config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,15 @@ eth:
# The cold interval before the circuit breaker turns to be half-open since being turned open.
openColdTime: 15s

# # Gas station configurations
# gasstation:
# # Whether to enable gas station.
# enabled: true
# # The number of blocks (or epochs) from the latest block (or epoch) to peek for gas price estimation.
# historicalPeekCount: 100
# # Percentiles of average txn gas price mapped to three levels of urgency (`low`, `medium` and `high`).
# percentiles: [1, 50, 99]

# Blockchain sync configurations
sync:
# Core space sync configurations
Expand Down
39 changes: 38 additions & 1 deletion node/cfxclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/Conflux-Chain/confura/store/mysql"
"github.com/Conflux-Chain/confura/util/rpc"
sdk "github.com/Conflux-Chain/go-conflux-sdk"
"github.com/pkg/errors"
)

// CfxClientProvider provides core space client by router.
Expand All @@ -23,7 +24,7 @@ func NewCfxClientProvider(db *mysql.MysqlStore, router Router) *CfxClientProvide
}
}

// GetClient gets client of specific group (or use normal HTTP group as default.
// GetClient gets client of specific group (or use normal HTTP group as default).
func (p *CfxClientProvider) GetClient(key string, groups ...Group) (sdk.ClientOperator, error) {
client, err := p.getClient(key, cfxNodeGroup(groups...))
if err != nil {
Expand All @@ -44,6 +45,25 @@ func (p *CfxClientProvider) GetClientByIP(ctx context.Context, groups ...Group)
return client.(sdk.ClientOperator), nil
}

// GetClientsByGroup gets all clients of specific group.
func (p *CfxClientProvider) GetClientsByGroup(grp Group) (clients []sdk.ClientOperator, err error) {
np := locateNodeProvider(p.router)
if np == nil {
return nil, errors.New("unsupported router type")
}

nodeUrls := np.ListNodesByGroup(grp)
for _, url := range nodeUrls {
if c, err := p.getOrRegisterClient(string(url), grp); err == nil {
clients = append(clients, c.(sdk.ClientOperator))
} else {
return nil, err
}
}

return clients, nil
}

func cfxNodeGroup(groups ...Group) Group {
grp := GroupCfxHttp
if len(groups) > 0 {
Expand All @@ -52,3 +72,20 @@ func cfxNodeGroup(groups ...Group) Group {

return grp
}

// locateNodeProvider finds node provider from the router chain or nil.
func locateNodeProvider(r Router) NodeProvider {
if np, ok := r.(NodeProvider); ok {
return np
}

if cr, ok := r.(*chainedRouter); ok {
for _, r := range cr.routers {
if np := locateNodeProvider(r); np != nil {
return np
}
}
}

return nil
}
25 changes: 14 additions & 11 deletions node/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,24 +132,27 @@ func (p *clientProvider) populateCache(token string) (grp Group, ok bool) {

// getClient gets client based on keyword and node group type.
func (p *clientProvider) getClient(key string, group Group) (interface{}, error) {
clients := p.getOrRegisterGroup(group)

logger := logrus.WithFields(logrus.Fields{
"key": key,
"group": group,
})

url := p.router.Route(group, []byte(key))
if len(url) == 0 {
logger.WithError(ErrClientUnavailable).Error("Failed to get full node client from provider")
logrus.WithFields(logrus.Fields{
"key": key,
"group": group,
}).Error("No full node client available from router")
return nil, ErrClientUnavailable
}

return p.getOrRegisterClient(url, group)
}

// getOrRegisterClient gets or registers RPC client for fullnode proxy.
func (p *clientProvider) getOrRegisterClient(url string, group Group) (interface{}, error) {
clients := p.getOrRegisterGroup(group)
nodeName := rpc.Url2NodeName(url)

logger = logger.WithFields(logrus.Fields{
"node": nodeName,
"url": url,
logger := logrus.WithFields(logrus.Fields{
"node": nodeName,
"url": url,
"group": group,
})
logger.Trace("Route RPC requests")

Expand Down
23 changes: 22 additions & 1 deletion node/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ type Router interface {
Route(group Group, key []byte) string
}

// NodeProvider provides full node URLs by group.
type NodeProvider interface {
ListNodesByGroup(group Group) (urls []string)
}

// MustNewRouter creates an instance of Router.
func MustNewRouter(redisURL string, nodeRPCURL string, groupConf map[Group]UrlConfig) Router {
var routers []Router
Expand Down Expand Up @@ -235,7 +240,6 @@ func NewLocalRouter(group2Urls map[Group][]string) *LocalRouter {
for k, v := range group2Urls {
groups[k] = newLocalNodeGroup(v)
}

return &LocalRouter{groups: groups}
}

Expand All @@ -255,6 +259,23 @@ func (r *LocalRouter) Route(group Group, key []byte) string {
return ""
}

// ListNodesByGroup returns all node URLs in a group.
func (r *LocalRouter) ListNodesByGroup(group Group) (urls []string) {
r.mu.Lock()
defer r.mu.Unlock()

item, ok := r.groups[group]
if !ok {
return nil
}

for _, v := range item.nodes {
urls = append(urls, v.String())
}

return urls
}

func NewLocalRouterFromNodeRPC(client *rpc.Client) (*LocalRouter, error) {
router := &LocalRouter{
groups: make(map[Group]*localNodeGroup),
Expand Down
2 changes: 1 addition & 1 deletion rpc/apis.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func filterExposedApis(allApis []API, exposedModules []string) (map[string]inter

// nativeSpaceApis returns the collection of built-in RPC APIs for core space.
func nativeSpaceApis(
clientProvider *node.CfxClientProvider, gashandler *handler.GasStationHandler, option ...CfxAPIOption,
clientProvider *node.CfxClientProvider, gashandler *handler.CfxGasStationHandler, option ...CfxAPIOption,
) []API {
return []API{
{
Expand Down
57 changes: 52 additions & 5 deletions rpc/gastation_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,70 @@ package rpc

import (
"context"
"math/big"

"github.com/Conflux-Chain/confura/rpc/handler"
"github.com/Conflux-Chain/confura/types"
cfxtypes "github.com/Conflux-Chain/go-conflux-sdk/types"
logutil "github.com/Conflux-Chain/go-conflux-util/log"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/sirupsen/logrus"
)

// gasStationAPI provides core space gasstation API.
type gasStationAPI struct {
handler *handler.GasStationHandler
handler *handler.CfxGasStationHandler
etLogger *logutil.ErrorTolerantLogger
}

func newGasStationAPI(handler *handler.GasStationHandler) *gasStationAPI {
return &gasStationAPI{handler: handler}
// newGasStationAPI creates a new instance of gasStationAPI.
func newGasStationAPI(handler *handler.CfxGasStationHandler) *gasStationAPI {
return &gasStationAPI{
handler: handler,
etLogger: logutil.NewErrorTolerantLogger(logutil.DefaultETConfig),
}
}

func (api *gasStationAPI) Price(ctx context.Context) (*types.GasStationPrice, error) {
//return api.handler.GetPrice()
// SuggestedGasFees retrieves the suggested gas fees.
func (api *gasStationAPI) SuggestedGasFees(ctx context.Context) (*types.SuggestedGasFees, error) {
cfx := GetCfxClientFromContext(ctx)

// Attempt to get suggested gas fees from the handler if available.
if api.handler != nil {
gasFee, err := api.handler.Suggest(cfx)
api.etLogger.Log(
logrus.StandardLogger(), err, "Failed to get suggested gas fees from handler",
)
return gasFee, err
}

// Fallback to fetching gas fees directly from the blockchain.
latestBlock, err := cfx.GetBlockSummaryByEpoch(cfxtypes.EpochLatestState)
if err != nil {
return nil, err
}

priorityFee, err := cfx.GetMaxPriorityFeePerGas()
if err != nil {
return nil, err
}

baseFeePerGas := latestBlock.BaseFeePerGas.ToInt()
gasFeeEstimation := types.GasFeeEstimation{
SuggestedMaxPriorityFeePerGas: priorityFee,
SuggestedMaxFeePerGas: (*hexutil.Big)(big.NewInt(0).Add(baseFeePerGas, priorityFee.ToInt())),
}

return &types.SuggestedGasFees{
Low: gasFeeEstimation,
Medium: gasFeeEstimation,
High: gasFeeEstimation,
EstimatedBaseFee: (*hexutil.Big)(baseFeePerGas),
}, nil
}

// TODO: Deprecate it if not used by the community any more.
func (api *gasStationAPI) Price(ctx context.Context) (*types.GasStationPrice, error) {
// Use oracle gas price from the blockchain.
cfx := GetCfxClientFromContext(ctx)
price, err := cfx.GetGasPrice()
Expand Down
Loading
Loading