Skip to content

Commit

Permalink
Implement watchGasPrice() for TON
Browse files Browse the repository at this point in the history
  • Loading branch information
swift1337 committed Oct 18, 2024
1 parent 55055f3 commit 908c722
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 11 deletions.
8 changes: 7 additions & 1 deletion pkg/ticker/ticker.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ type Opt func(*Ticker)
// WithLogger sets the logger for the Ticker.
func WithLogger(log zerolog.Logger, name string) Opt {
return func(t *Ticker) {
t.logger = log.With().Str("ticker_name", name).Logger()
t.logger = log.With().Str("ticker.name", name).Logger()
}
}

Expand All @@ -82,6 +82,7 @@ func New(interval time.Duration, task Task, opts ...Opt) *Ticker {
t := &Ticker{
interval: interval,
task: task,
logger: zerolog.Nop(),
}

for _, opt := range opts {
Expand Down Expand Up @@ -162,6 +163,11 @@ func (t *Ticker) SetInterval(interval time.Duration) {
return
}

t.logger.Info().
Dur("ticker.old_interval", t.interval).
Dur("ticker.new_interval", interval).
Msg("Changing interval")

t.interval = interval
t.ticker.Reset(interval)
}
Expand Down
51 changes: 51 additions & 0 deletions zetaclient/chains/ton/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import (
"net/url"
"time"

"github.com/pkg/errors"
"github.com/tonkeeper/tongo/config"
"github.com/tonkeeper/tongo/liteapi"
"github.com/tonkeeper/tongo/tlb"
)

type GlobalConfigurationFile = config.GlobalConfigurationFile
Expand Down Expand Up @@ -52,3 +55,51 @@ func ConfigFromSource(ctx context.Context, urlOrPath string) (*GlobalConfigurati

return ConfigFromPath(urlOrPath)
}

// ConfigGetter represents LiteAPI config getter.
type ConfigGetter interface {
GetConfigParams(ctx context.Context, mode liteapi.ConfigMode, params []uint32) (tlb.ConfigParams, error)
}

// FetchGasConfig fetches gas price from the config.
func FetchGasConfig(ctx context.Context, getter ConfigGetter) (tlb.GasLimitsPrices, error) {
// https://docs.ton.org/develop/howto/blockchain-configs
// https://tonviewer.com/config#21
const configKeyGas = 21

response, err := getter.GetConfigParams(ctx, 0, []uint32{configKeyGas})
if err != nil {
return tlb.GasLimitsPrices{}, errors.Wrap(err, "failed to get config params")
}

ref, ok := response.Config.Get(configKeyGas)
if !ok {
return tlb.GasLimitsPrices{}, errors.Errorf("config key %d not found", configKeyGas)
}

var cfg tlb.ConfigParam21
if err = tlb.Unmarshal(&ref.Value, &cfg); err != nil {
return tlb.GasLimitsPrices{}, errors.Wrap(err, "failed to unmarshal config param")
}

return cfg.GasLimitsPrices, nil
}

// ParseGasPrice parses gas price from the config and returns price in tons per 1 gas unit.
func ParseGasPrice(cfg tlb.GasLimitsPrices) (uint64, error) {
// from TON docs: gas_price: This parameter reflects
// the price of gas in the network, in nano tons per 65536 gas units (2^16).
switch cfg.SumType {
case "GasPrices":
return cfg.GasPrices.GasPrice >> 16, nil
case "GasPricesExt":
return cfg.GasPricesExt.GasPrice >> 16, nil
case "GasFlatPfx":
if cfg.GasFlatPfx.Other == nil {
return 0, errors.New("GasFlatPfx.Other is nil")
}
return ParseGasPrice(*cfg.GasFlatPfx.Other)
default:
return 0, errors.Errorf("unknown SumType: %q", cfg.SumType)
}
}
24 changes: 24 additions & 0 deletions zetaclient/chains/ton/liteapi/client_live_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ import (
"testing"
"time"

"cosmossdk.io/math"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tonkeeper/tongo/config"
"github.com/tonkeeper/tongo/liteapi"
"github.com/tonkeeper/tongo/tlb"
"github.com/tonkeeper/tongo/ton"
toncontracts "github.com/zeta-chain/node/pkg/contracts/ton"
zetaton "github.com/zeta-chain/node/zetaclient/chains/ton"
"github.com/zeta-chain/node/zetaclient/common"
)

Expand Down Expand Up @@ -164,6 +167,27 @@ func TestClient(t *testing.T) {

t.Logf("Masterchain block #%d is generated at %q (%s ago)", block.SeqNo, blockTime, since.String())
})

t.Run("GetGasConfig", func(t *testing.T) {
// ACT #1
gas, err := zetaton.FetchGasConfig(ctx, client)

// ASSERT #1
require.NoError(t, err)
require.NotEmpty(t, gas)

// ACT #2
gasPrice, err := zetaton.ParseGasPrice(gas)

// ASSERT #2
require.NoError(t, err)
require.NotEmpty(t, gasPrice)

gasPricePer1000 := math.NewUint(1000 * gasPrice)

t.Logf("Gas cost: %s per 1000 gas", toncontracts.FormatCoins(gasPricePer1000))
t.Logf("Compare with https://tonwhales.com/explorer/network")
})
}

func mustCreateClient(t *testing.T) *liteapi.Client {
Expand Down
73 changes: 64 additions & 9 deletions zetaclient/chains/ton/observer/observer.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/zeta-chain/node/x/crosschain/types"
"github.com/zeta-chain/node/zetaclient/chains/base"
"github.com/zeta-chain/node/zetaclient/chains/interfaces"
zetaton "github.com/zeta-chain/node/zetaclient/chains/ton"
"github.com/zeta-chain/node/zetaclient/common"
)

Expand All @@ -28,9 +29,11 @@ type Observer struct {
}

// LiteClient represents a TON client
// see https://github.com/ton-blockchain/ton/blob/master/tl/generate/scheme/tonlib_api.tl
//
//go:generate mockery --name LiteClient --filename ton_liteclient.go --case underscore --output ../../../testutils/mocks
type LiteClient interface {
zetaton.ConfigGetter
GetMasterchainInfo(ctx context.Context) (liteclient.LiteServerMasterchainInfoC, error)
GetBlockHeader(ctx context.Context, blockID ton.BlockIDExt, mode uint32) (tlb.BlockInfo, error)
GetTransactionsSince(ctx context.Context, acc ton.AccountID, lt uint64, bits ton.Bits256) ([]ton.Transaction, error)
Expand Down Expand Up @@ -88,9 +91,54 @@ func (ob *Observer) VoteOutboundIfConfirmed(_ context.Context, _ *types.CrossCha
}

// watchGasPrice observes TON gas price and votes it to Zetacore.
func (ob *Observer) watchGasPrice(_ context.Context) error {
// todo implement me
return nil
func (ob *Observer) watchGasPrice(ctx context.Context) error {
task := func(ctx context.Context, t *ticker.Ticker) error {
if err := ob.postGasPrice(ctx); err != nil {
ob.Logger().GasPrice.Err(err).Msg("reportGasPrice error")
}

newInternal := ticker.SecondsFromUint64(ob.ChainParams().GasPriceTicker)
t.SetInterval(newInternal)

return nil
}

ob.Logger().GasPrice.Info().Msgf("WatchGasPrice started")

return ticker.Run(
ctx,
ticker.SecondsFromUint64(ob.ChainParams().GasPriceTicker),
task,
ticker.WithStopChan(ob.StopChannel()),
ticker.WithLogger(ob.Logger().GasPrice, "WatchGasPrice"),
)
}

// postGasPrice fetches on-chain gas config and reports it to Zetacore.
func (ob *Observer) postGasPrice(ctx context.Context) error {
cfg, err := zetaton.FetchGasConfig(ctx, ob.client)
if err != nil {
return errors.Wrap(err, "failed to fetch gas config")
}

gasPrice, err := zetaton.ParseGasPrice(cfg)
if err != nil {
return errors.Wrap(err, "failed to parse gas price")
}

blockID, err := ob.getLatestMasterchainBlock(ctx)
if err != nil {
return errors.Wrap(err, "failed to get latest masterchain block")
}

// There's no concept of priority fee in TON
const priorityFee = 0

_, errVote := ob.
ZetacoreClient().
PostVoteGasPrice(ctx, ob.Chain(), gasPrice, priorityFee, uint64(blockID.Seqno))

return errVote
}

// watchRPCStatus observes TON RPC status.
Expand All @@ -114,20 +162,18 @@ func (ob *Observer) watchRPCStatus(ctx context.Context) error {

// checkRPCStatus checks TON RPC status and alerts if necessary.
func (ob *Observer) checkRPCStatus(ctx context.Context) error {
mc, err := ob.client.GetMasterchainInfo(ctx)
blockID, err := ob.getLatestMasterchainBlock(ctx)
if err != nil {
return errors.Wrap(err, "failed to get masterchain info")
return errors.Wrap(err, "failed to get latest masterchain block")
}

blockID := mc.Last

block, err := ob.client.GetBlockHeader(ctx, blockID.ToBlockIdExt(), 0)
block, err := ob.client.GetBlockHeader(ctx, blockID, 0)
if err != nil {
return errors.Wrap(err, "failed to get masterchain block header")
}

if block.NotMaster {
return errors.Errorf("block %q is not a master block", blockID.ToBlockIdExt().BlockID.String())
return errors.Errorf("block %q is not a master block", blockID.BlockID.String())
}

blockTime := time.Unix(int64(block.GenUtime), 0).UTC()
Expand All @@ -139,3 +185,12 @@ func (ob *Observer) checkRPCStatus(ctx context.Context) error {

return nil
}

func (ob *Observer) getLatestMasterchainBlock(ctx context.Context) (ton.BlockIDExt, error) {
mc, err := ob.client.GetMasterchainInfo(ctx)
if err != nil {
return ton.BlockIDExt{}, errors.Wrap(err, "failed to get masterchain info")
}

return mc.Last.ToBlockIdExt(), nil
}
32 changes: 31 additions & 1 deletion zetaclient/testutils/mocks/ton_liteclient.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 908c722

Please sign in to comment.