Skip to content

Commit

Permalink
update BTC CCTX gas rate in block beginner
Browse files Browse the repository at this point in the history
  • Loading branch information
ws4charlie committed Jan 7, 2025
1 parent f730749 commit 5d993a6
Show file tree
Hide file tree
Showing 3 changed files with 266 additions and 37 deletions.
66 changes: 58 additions & 8 deletions x/crosschain/keeper/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,18 @@ func (k Keeper) IterateAndUpdateCctxGasPrice(

IterateChains:
for _, chain := range chains {
if zetachains.IsZetaChain(chain.ChainId, additionalChains) {
continue
}

// support only external evm chains and bitcoin chain
if IsGasStabilityPoolEnabledChain(chain.ChainId, additionalChains) {
// use provided updateFunc if available, otherwise get updater based on chain type
updater, found := GetCctxGasPriceUpdater(chain.ChainId, additionalChains)
if found && updateFunc == nil {
updateFunc = updater
}

if updateFunc != nil {
res, err := k.ListPendingCctx(sdk.UnwrapSDKContext(ctx), &types.QueryListPendingCctxRequest{
ChainId: chain.ChainId,
Limit: gasPriceIncreaseFlags.MaxPendingCctxs,
Expand Down Expand Up @@ -100,9 +110,9 @@ IterateChains:
return cctxCount, gasPriceIncreaseFlags
}

// CheckAndUpdateCctxGasPrice checks if the retry interval is reached and updates the gas price if so
// CheckAndUpdateCctxGasPriceEVM checks if the retry interval is reached and updates the gas price if so
// The function returns the gas price increase and the additional fees paid from the gas stability pool
func CheckAndUpdateCctxGasPrice(
func CheckAndUpdateCctxGasPriceEVM(
ctx sdk.Context,
k Keeper,
cctx types.CrossChainTx,
Expand Down Expand Up @@ -176,14 +186,54 @@ func CheckAndUpdateCctxGasPrice(
return gasPriceIncrease, additionalFees, nil
}

// IsGasStabilityPoolEnabledChain returns true if given chainID is enabled for gas stability pool
func IsGasStabilityPoolEnabledChain(chainID int64, additionalChains []zetachains.Chain) bool {
// CheckAndUpdateCctxGasRateBTC checks if the retry interval is reached and updates the gas rate if so
// Zetacore only needs to update the gas rate in CCTX and fee bumping will be handled by zetaclient
func CheckAndUpdateCctxGasRateBTC(
ctx sdk.Context,
k Keeper,
cctx types.CrossChainTx,
flags observertypes.GasPriceIncreaseFlags,
) (math.Uint, math.Uint, error) {
// skip if gas price or gas limit is not set
if cctx.GetCurrentOutboundParam().GasPrice == "" || cctx.GetCurrentOutboundParam().CallOptions.GasLimit == 0 {
return math.ZeroUint(), math.ZeroUint(), nil
}

// skip if retry interval is not reached
lastUpdated := time.Unix(cctx.CctxStatus.LastUpdateTimestamp, 0)
if ctx.BlockTime().Before(lastUpdated.Add(flags.RetryInterval)) {
return math.ZeroUint(), math.ZeroUint(), nil
}

// compute gas price increase
chainID := cctx.GetCurrentOutboundParam().ReceiverChainId
medianGasPrice, _, isFound := k.GetMedianGasValues(ctx, chainID)
if !isFound {
return math.ZeroUint(), math.ZeroUint(), cosmoserrors.Wrap(
types.ErrUnableToGetGasPrice,
fmt.Sprintf("cannot get gas price for chain %d", chainID),
)
}

// set new gas rate and last update timestamp
// there is no priority fee in Bitcoin, we reuse 'GasPriorityFee' to store latest gas rate in satoshi/vByte
cctx.GetCurrentOutboundParam().GasPriorityFee = medianGasPrice.String()
k.SetCrossChainTx(ctx, cctx)

return math.ZeroUint(), math.ZeroUint(), nil
}

// GetCctxGasPriceUpdater returns the function to update gas price according to chain type
func GetCctxGasPriceUpdater(chainID int64, additionalChains []zetachains.Chain) (CheckAndUpdateCctxGasPriceFunc, bool) {
switch {
case zetachains.IsEVMChain(chainID, additionalChains):
return !zetachains.IsZetaChain(chainID, additionalChains)
if !zetachains.IsZetaChain(chainID, additionalChains) {
return CheckAndUpdateCctxGasPriceEVM, true
}
return nil, false
case zetachains.IsBitcoinChain(chainID, additionalChains):
return true
return CheckAndUpdateCctxGasRateBTC, true
default:
return false
return nil, false
}
}
235 changes: 207 additions & 28 deletions x/crosschain/keeper/abci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package keeper_test

import (
"errors"
"reflect"
"testing"
"time"

Expand Down Expand Up @@ -88,23 +89,29 @@ func TestKeeper_IterateAndUpdateCctxGasPrice(t *testing.T) {
ctx = ctx.WithBlockHeight(observertypes.DefaultCrosschainFlags().GasPriceIncreaseFlags.EpochLength * 2)
cctxCount, flags = k.IterateAndUpdateCctxGasPrice(ctx, supportedChains, updateFunc)

// 2 eth + 5 bsc = 7
require.Equal(t, 7, cctxCount)
// 2 eth + 5 btc + 5 bsc = 12
require.Equal(t, 12, cctxCount)
require.Equal(t, customFlags, flags)

// check that the update function was called with the cctx index
require.Equal(t, 7, len(updateFuncMap))
require.Equal(t, 12, len(updateFuncMap))
require.Contains(t, updateFuncMap, sample.GetCctxIndexFromString("1-10"))
require.Contains(t, updateFuncMap, sample.GetCctxIndexFromString("1-11"))

require.Contains(t, updateFuncMap, sample.GetCctxIndexFromString("8332-20"))
require.Contains(t, updateFuncMap, sample.GetCctxIndexFromString("8332-21"))
require.Contains(t, updateFuncMap, sample.GetCctxIndexFromString("8332-22"))
require.Contains(t, updateFuncMap, sample.GetCctxIndexFromString("8332-23"))
require.Contains(t, updateFuncMap, sample.GetCctxIndexFromString("8332-24"))

require.Contains(t, updateFuncMap, sample.GetCctxIndexFromString("56-30"))
require.Contains(t, updateFuncMap, sample.GetCctxIndexFromString("56-31"))
require.Contains(t, updateFuncMap, sample.GetCctxIndexFromString("56-32"))
require.Contains(t, updateFuncMap, sample.GetCctxIndexFromString("56-33"))
require.Contains(t, updateFuncMap, sample.GetCctxIndexFromString("56-34"))
}

func TestCheckAndUpdateCctxGasPrice(t *testing.T) {
func Test_CheckAndUpdateCctxGasPriceEVM(t *testing.T) {
sampleTimestamp := time.Now()
retryIntervalReached := sampleTimestamp.Add(observertypes.DefaultGasPriceIncreaseFlags.RetryInterval + time.Second)
retryIntervalNotReached := sampleTimestamp.Add(
Expand Down Expand Up @@ -407,7 +414,7 @@ func TestCheckAndUpdateCctxGasPrice(t *testing.T) {
}

// check and update gas price
gasPriceIncrease, feesPaid, err := keeper.CheckAndUpdateCctxGasPrice(ctx, *k, tc.cctx, tc.flags)
gasPriceIncrease, feesPaid, err := keeper.CheckAndUpdateCctxGasPriceEVM(ctx, *k, tc.cctx, tc.flags)

if tc.isError {
require.Error(t, err)
Expand Down Expand Up @@ -451,47 +458,219 @@ func TestCheckAndUpdateCctxGasPrice(t *testing.T) {
}
}

func TestIsGasStabilityPoolEnabledChain(t *testing.T) {
func Test_CheckAndUpdateCctxGasRateBTC(t *testing.T) {
sampleTimestamp := time.Now()
gasRateUpdateInterval := observertypes.DefaultGasPriceIncreaseFlags.RetryInterval
retryIntervalReached := sampleTimestamp.Add(gasRateUpdateInterval + time.Second)
retryIntervalNotReached := sampleTimestamp.Add(gasRateUpdateInterval - time.Second)

tt := []struct {
name string
cctx types.CrossChainTx
flags observertypes.GasPriceIncreaseFlags
blockTimestamp time.Time
medianGasPrice uint64
medianPriorityFee uint64
shouldUpdate bool
isError bool
}{
{
name: "can update gas rate when retry interval is reached",
cctx: types.CrossChainTx{
Index: "a",
CctxStatus: &types.Status{
CreatedTimestamp: sampleTimestamp.Unix(),
LastUpdateTimestamp: sampleTimestamp.Unix(),
},
OutboundParams: []*types.OutboundParams{
{
ReceiverChainId: 8332,
CallOptions: &types.CallOptions{
GasLimit: 254,
},
GasPrice: "10",
},
},
},
flags: observertypes.DefaultGasPriceIncreaseFlags,
blockTimestamp: retryIntervalReached,
medianGasPrice: 12,
shouldUpdate: true,
},
{
name: "skip if gas price is not set",
cctx: types.CrossChainTx{
Index: "b1",
OutboundParams: []*types.OutboundParams{
{
GasPrice: "",
},
},
},
flags: observertypes.DefaultGasPriceIncreaseFlags,
blockTimestamp: retryIntervalReached,
medianGasPrice: 12,
},
{
name: "skip if gas limit is not set",
cctx: types.CrossChainTx{
Index: "b2",
OutboundParams: []*types.OutboundParams{
{
CallOptions: &types.CallOptions{
GasLimit: 0,
},
GasPrice: "10",
},
},
},
flags: observertypes.DefaultGasPriceIncreaseFlags,
blockTimestamp: retryIntervalReached,
medianGasPrice: 12,
},
{
name: "skip if retry interval is not reached",
cctx: types.CrossChainTx{
Index: "b3",
CctxStatus: &types.Status{
CreatedTimestamp: sampleTimestamp.Unix(),
LastUpdateTimestamp: sampleTimestamp.Unix(),
},
OutboundParams: []*types.OutboundParams{
{
CallOptions: &types.CallOptions{
GasLimit: 254,
},
GasPrice: "10",
},
},
},
flags: observertypes.DefaultGasPriceIncreaseFlags,
blockTimestamp: retryIntervalNotReached,
medianGasPrice: 12,
},
{
name: "returns error if can't find median gas price",
cctx: types.CrossChainTx{
Index: "b4",
CctxStatus: &types.Status{
CreatedTimestamp: sampleTimestamp.Unix(),
LastUpdateTimestamp: sampleTimestamp.Unix(),
},
OutboundParams: []*types.OutboundParams{
{
ReceiverChainId: 8332,
CallOptions: &types.CallOptions{
GasLimit: 254,
},
GasPrice: "10",
},
},
},
flags: observertypes.DefaultGasPriceIncreaseFlags,
blockTimestamp: retryIntervalReached,
medianGasPrice: 0,
isError: true,
},
}
for _, tc := range tt {
tc := tc
t.Run(tc.name, func(t *testing.T) {
k, ctx := testkeeper.CrosschainKeeperAllMocks(t)
chainID := tc.cctx.GetCurrentOutboundParam().ReceiverChainId

// set median gas price if not zero
if tc.medianGasPrice != 0 {
k.SetGasPrice(ctx, types.GasPrice{
ChainId: chainID,
Prices: []uint64{tc.medianGasPrice},
MedianIndex: 0,
})

// ensure median gas price is set
medianGasPrice, _, isFound := k.GetMedianGasValues(ctx, chainID)
require.True(t, isFound)
require.True(t, medianGasPrice.Equal(math.NewUint(tc.medianGasPrice)))
}

// set block timestamp
ctx = ctx.WithBlockTime(tc.blockTimestamp)

// check and update gas rate
gasPriceIncrease, feesPaid, err := keeper.CheckAndUpdateCctxGasRateBTC(ctx, *k, tc.cctx, tc.flags)
if tc.isError {
require.Error(t, err)
return
}
require.NoError(t, err)

// check values
require.True(t, gasPriceIncrease.IsZero())
require.True(t, feesPaid.IsZero())

// check cctx if gas rate is updated
if tc.shouldUpdate {
cctx, found := k.GetCrossChainTx(ctx, tc.cctx.Index)
require.True(t, found)
newGasPrice, err := cctx.GetCurrentOutboundParam().GetGasPriorityFeeUInt64()
require.NoError(t, err)
require.Equal(t, tc.medianGasPrice, newGasPrice)
require.EqualValues(t, tc.blockTimestamp.Unix(), cctx.CctxStatus.LastUpdateTimestamp)
}
})
}
}

func Test_GetCctxGasPriceUpdater(t *testing.T) {
tests := []struct {
name string
chainID int64
expected bool
name string
chainID int64
found bool
updateFunc keeper.CheckAndUpdateCctxGasPriceFunc
}{
{
name: "Ethereum is enabled",
chainID: chains.Ethereum.ChainId,
expected: true,
name: "Ethereum is enabled",
chainID: chains.Ethereum.ChainId,
found: true,
updateFunc: keeper.CheckAndUpdateCctxGasPriceEVM,
},
{
name: "Binance Smart Chain is enabled",
chainID: chains.BscMainnet.ChainId,
expected: true,
name: "Binance Smart Chain is enabled",
chainID: chains.BscMainnet.ChainId,
found: true,
updateFunc: keeper.CheckAndUpdateCctxGasPriceEVM,
},
{
name: "Bitcoin is enabled",
chainID: chains.BitcoinMainnet.ChainId,
expected: true,
name: "Bitcoin is enabled",
chainID: chains.BitcoinMainnet.ChainId,
found: true,
updateFunc: keeper.CheckAndUpdateCctxGasRateBTC,
},
{
name: "ZetaChain is not enabled",
chainID: chains.ZetaChainMainnet.ChainId,
expected: false,
name: "ZetaChain is not enabled",
chainID: chains.ZetaChainMainnet.ChainId,
found: false,
updateFunc: nil,
},
{
name: "Solana is not enabled",
chainID: chains.SolanaMainnet.ChainId,
expected: false,
name: "Solana is not enabled",
chainID: chains.SolanaMainnet.ChainId,
found: false,
updateFunc: nil,
},
{
name: "TON is not enabled",
chainID: chains.TONMainnet.ChainId,
expected: false,
name: "TON is not enabled",
chainID: chains.TONMainnet.ChainId,
found: false,
updateFunc: nil,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.expected, keeper.IsGasStabilityPoolEnabledChain(tt.chainID, []zetachains.Chain{}))
updateFunc, found := keeper.GetCctxGasPriceUpdater(tt.chainID, []zetachains.Chain{})
require.Equal(t, tt.found, found)
require.Equal(t, reflect.ValueOf(tt.updateFunc).Pointer(), reflect.ValueOf(updateFunc).Pointer())
})
}
}
2 changes: 1 addition & 1 deletion x/crosschain/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) {

// iterate and update gas price for cctx that are pending for too long
// error is logged in the function
am.keeper.IterateAndUpdateCctxGasPrice(ctx, supportedChains, keeper.CheckAndUpdateCctxGasPrice)
am.keeper.IterateAndUpdateCctxGasPrice(ctx, supportedChains, nil)
}

// EndBlock executes all ABCI EndBlock logic respective to the crosschain module. It
Expand Down

0 comments on commit 5d993a6

Please sign in to comment.