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

feat: (BitcoinRBF-Step1) zetacore feeds latest gas price to pending Bitcoin CCTXs #3377

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## unreleased

### Features

* [3377](https://github.com/zeta-chain/node/pull/3377) - have zetacore feed latest gas price to pending Bitcoin cctxs

### Refactor

* [3332](https://github.com/zeta-chain/node/pull/3332) - implement orchestrator V2. Move BTC observer-signer to V2
Expand Down
13 changes: 11 additions & 2 deletions testutil/sample/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/cometbft/cometbft/crypto/secp256k1"
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
Expand Down Expand Up @@ -91,15 +92,23 @@ func EthAddressFromRand(r *rand.Rand) ethcommon.Address {
}

// BtcAddressP2WPKH returns a sample btc P2WPKH address
func BtcAddressP2WPKH(t *testing.T, net *chaincfg.Params) string {
func BtcAddressP2WPKH(t *testing.T, net *chaincfg.Params) *btcutil.AddressWitnessPubKeyHash {
privateKey, err := btcec.NewPrivateKey()
require.NoError(t, err)

pubKeyHash := btcutil.Hash160(privateKey.PubKey().SerializeCompressed())
addr, err := btcutil.NewAddressWitnessPubKeyHash(pubKeyHash, net)
require.NoError(t, err)

return addr.String()
return addr
}

// BtcAddressP2WPKH returns a pkscript for a sample btc P2WPKH address
func BtcAddressP2WPKHScript(t *testing.T, net *chaincfg.Params) []byte {
addr := BtcAddressP2WPKH(t, net)
script, err := txscript.PayToAddrScript(addr)
require.NoError(t, err)
return script
}

// SolanaPrivateKey returns a sample solana private key
Expand Down
138 changes: 96 additions & 42 deletions x/crosschain/keeper/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,20 @@ const (
RemainingFeesToStabilityPoolPercent = 95
)

// CheckAndUpdateCctxGasPriceFunc is a function type for checking and updating the gas price of a cctx
type CheckAndUpdateCctxGasPriceFunc func(
// CheckAndUpdateCCTXGasPriceFunc is a function type for checking and updating the gas price of a cctx
type CheckAndUpdateCCTXGasPriceFunc func(
ctx sdk.Context,
k Keeper,
cctx types.CrossChainTx,
flags observertypes.GasPriceIncreaseFlags,
) (math.Uint, math.Uint, error)

// IterateAndUpdateCctxGasPrice iterates through all cctx and updates the gas price if pending for too long
// IterateAndUpdateCCTXGasPrice iterates through all cctx and updates the gas price if pending for too long
// The function returns the number of cctxs updated and the gas price increase flags used
func (k Keeper) IterateAndUpdateCctxGasPrice(
func (k Keeper) IterateAndUpdateCCTXGasPrice(
ctx sdk.Context,
chains []zetachains.Chain,
updateFunc CheckAndUpdateCctxGasPriceFunc,
updateFunc CheckAndUpdateCCTXGasPriceFunc,
) (int, observertypes.GasPriceIncreaseFlags) {
// fetch the gas price increase flags or use default
gasPriceIncreaseFlags := observertypes.DefaultGasPriceIncreaseFlags
Expand All @@ -52,46 +52,47 @@ func (k Keeper) IterateAndUpdateCctxGasPrice(

IterateChains:
for _, chain := range chains {
// support only external evm chains
if zetachains.IsEVMChain(chain.ChainId, additionalChains) && !zetachains.IsZetaChain(chain.ChainId, additionalChains) {
res, err := k.ListPendingCctx(sdk.UnwrapSDKContext(ctx), &types.QueryListPendingCctxRequest{
ChainId: chain.ChainId,
Limit: gasPriceIncreaseFlags.MaxPendingCctxs,
})
if err != nil {
ctx.Logger().Info("GasStabilityPool: fetching pending cctx failed",
"chainID", chain.ChainId,
"err", err.Error(),
)
continue IterateChains
}
if !IsCCTXGasPriceUpdateSupported(chain.ChainId, additionalChains) {
continue
}

res, err := k.ListPendingCctx(sdk.UnwrapSDKContext(ctx), &types.QueryListPendingCctxRequest{
ChainId: chain.ChainId,
Limit: gasPriceIncreaseFlags.MaxPendingCctxs,
})
if err != nil {
ctx.Logger().Info("GasStabilityPool: fetching pending cctx failed",
"chainID", chain.ChainId,
"err", err.Error(),
)
continue IterateChains
}

// iterate through all pending cctx
for _, pendingCctx := range res.CrossChainTx {
if pendingCctx != nil {
gasPriceIncrease, additionalFees, err := updateFunc(ctx, k, *pendingCctx, gasPriceIncreaseFlags)
if err != nil {
ctx.Logger().Info("GasStabilityPool: updating gas price for pending cctx failed",
"cctxIndex", pendingCctx.Index,
// iterate through all pending cctx
for _, pendingCctx := range res.CrossChainTx {
if pendingCctx != nil {
gasPriceIncrease, additionalFees, err := updateFunc(ctx, k, *pendingCctx, gasPriceIncreaseFlags)
if err != nil {
ctx.Logger().Info("GasStabilityPool: updating gas price for pending cctx failed",
"cctxIndex", pendingCctx.Index,
"err", err.Error(),
)
continue IterateChains
}
if !gasPriceIncrease.IsNil() && !gasPriceIncrease.IsZero() {
// Emit typed event for gas price increase
if err := ctx.EventManager().EmitTypedEvent(
&types.EventCCTXGasPriceIncreased{
CctxIndex: pendingCctx.Index,
GasPriceIncrease: gasPriceIncrease.String(),
AdditionalFees: additionalFees.String(),
}); err != nil {
ctx.Logger().Error(
"GasStabilityPool: failed to emit EventCCTXGasPriceIncreased",
"err", err.Error(),
)
continue IterateChains
}
if !gasPriceIncrease.IsNil() && !gasPriceIncrease.IsZero() {
// Emit typed event for gas price increase
if err := ctx.EventManager().EmitTypedEvent(
&types.EventCCTXGasPriceIncreased{
CctxIndex: pendingCctx.Index,
GasPriceIncrease: gasPriceIncrease.String(),
AdditionalFees: additionalFees.String(),
}); err != nil {
ctx.Logger().Error(
"GasStabilityPool: failed to emit EventCCTXGasPriceIncreased",
"err", err.Error(),
)
}
cctxCount++
}
cctxCount++
}
}
}
Expand All @@ -102,7 +103,7 @@ IterateChains:

// CheckAndUpdateCctxGasPrice 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 CheckAndUpdateCCTXGasPrice(
ctx sdk.Context,
k Keeper,
cctx types.CrossChainTx,
Expand All @@ -128,6 +129,30 @@ func CheckAndUpdateCctxGasPrice(
fmt.Sprintf("cannot get gas price for chain %d", chainID),
)
}

// dispatch to chain-specific gas price update function
additionalChains := k.GetAuthorityKeeper().GetAdditionalChainList(ctx)
switch {
case zetachains.IsEVMChain(chainID, additionalChains):
return CheckAndUpdateCCTXGasPriceEVM(ctx, k, medianGasPrice, medianPriorityFee, cctx, flags)
case zetachains.IsBitcoinChain(chainID, additionalChains):
return CheckAndUpdateCCTXGasPriceBTC(ctx, k, medianGasPrice, cctx)
default:
return math.ZeroUint(), math.ZeroUint(), nil
}
}

// CheckAndUpdateCCTXGasPriceEVM updates the gas price for the given EVM chain CCTX
func CheckAndUpdateCCTXGasPriceEVM(
ctx sdk.Context,
k Keeper,
medianGasPrice math.Uint,
medianPriorityFee math.Uint,
cctx types.CrossChainTx,
flags observertypes.GasPriceIncreaseFlags,
) (math.Uint, math.Uint, error) {
// compute gas price increase
chainID := cctx.GetCurrentOutboundParam().ReceiverChainId
gasPriceIncrease := medianGasPrice.MulUint64(uint64(flags.GasPriceIncreasePercent)).QuoUint64(100)

// compute new gas price
Expand Down Expand Up @@ -175,3 +200,32 @@ func CheckAndUpdateCctxGasPrice(

return gasPriceIncrease, additionalFees, nil
}

// CheckAndUpdateCCTXGasPriceBTC updates the fee rate for the given Bitcoin chain CCTX
func CheckAndUpdateCCTXGasPriceBTC(
ctx sdk.Context,
k Keeper,
medianGasPrice math.Uint,
cctx types.CrossChainTx,
) (math.Uint, math.Uint, error) {
// zetacore simply update 'GasPriorityFee', and zetaclient will use it to schedule RBF tx
// there is no priority fee in Bitcoin, the 'GasPriorityFee' is repurposed to store latest fee rate in sat/vB
cctx.GetCurrentOutboundParam().GasPriorityFee = medianGasPrice.String()
k.SetCrossChainTx(ctx, cctx)

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

// IsCCTXGasPriceUpdateSupported checks if the given chain supports gas price update
func IsCCTXGasPriceUpdateSupported(chainID int64, additionalChains []zetachains.Chain) bool {
switch {
case zetachains.IsZetaChain(chainID, additionalChains):
return false
case zetachains.IsEVMChain(chainID, additionalChains):
return true
case zetachains.IsBitcoinChain(chainID, additionalChains):
return true
default:
return false
}
}
Loading
Loading