Skip to content

Commit

Permalink
feat: fast inbound confirmation for EVM and Bitcoin chain (#3551)
Browse files Browse the repository at this point in the history
* initiate fast confirmation for inbound

* add changelog and switch EVM chain to FAST mode

* increase default multiplier to 0.00025

* implement evm and btc chain fast inbound comfirmation; add E2E tests

* make changelog explicit; go mod tidy; clean up uncessary logic

* wrap common code into updateZRC20LiquidityCap; fix typo in bitcoin fast confirmation E2E

* created a e2e util function WaitForZetaBlocks to wait for give number of Zeta blocks

* ensure FAST confirmation mode works faster than SAFE mode in E2E; replace argument type string with common.Address

* add description to RPC messages; clean up unnecessary lib; replace asset string with common.Address

* rename method IsFungible as IsAsset

* use default liquidity cap divisor unless custom number is provided; move divisor related file to chains pkg; renaming and comments

* remove method GetInboundConfirmationMode to avoid opaque assumption

* increase Bitcoin SAFE confirmation to fix E2E failure

* add extra description for the liquidity cap divisor 4000

Co-authored-by: Lucas Bertrand <[email protected]>

---------

Co-authored-by: Dmitry S <[email protected]>
Co-authored-by: Lucas Bertrand <[email protected]>
  • Loading branch information
3 people authored Feb 28, 2025
1 parent 0c5bccb commit 7c0b865
Show file tree
Hide file tree
Showing 51 changed files with 1,822 additions and 226 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
* [3548](https://github.com/zeta-chain/node/pull/3548) - ensure cctx list is sorted by creation time
* [3562](https://github.com/zeta-chain/node/pull/3562) - add Sui withdrawals
* [3600](https://github.com/zeta-chain/node/pull/3600) - add dedicated zetaclient restricted addresses config. This file will be automatically reloaded when it changes without needing to restart zetaclient.
* [3551](https://github.com/zeta-chain/node/pull/3551) - support for EVM chain and Bitcoin chain inbound fast confirmation

### Refactor

Expand Down
10 changes: 9 additions & 1 deletion cmd/zetae2e/local/bitcoin.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func startBitcoinTests(
bitcoinDepositTests := []string{
e2etests.TestBitcoinDonationName,
e2etests.TestBitcoinDepositName,
e2etests.TestBitcoinDepositFastConfirmationName,
e2etests.TestBitcoinDepositAndCallName,
e2etests.TestBitcoinDepositAndCallRevertName,
e2etests.TestBitcoinStdMemoDepositName,
Expand Down Expand Up @@ -141,7 +142,14 @@ func initBitcoinRunner(
verbose, initNetwork, createWallet bool,
) *runner.E2ERunner {
// initialize runner for bitcoin test
runner, err := initTestRunner(name, conf, deployerRunner, account, runner.NewLogger(verbose, printColor, name))
runner, err := initTestRunner(
name,
conf,
deployerRunner,
account,
runner.NewLogger(verbose, printColor, name),
runner.WithZetaTxServer(deployerRunner.ZetaTxServer),
)
testutil.NoError(err)

// setup TSS address and setup deployer wallet
Expand Down
1 change: 1 addition & 0 deletions cmd/zetae2e/local/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func startEVMTests(eg *errgroup.Group, conf config.Config, deployerRunner *runne
eg.Go(evmTestRoutine(conf, "eth", conf.AdditionalAccounts.UserEther, color.FgHiGreen, deployerRunner, verbose,
e2etests.TestETHDepositName,
e2etests.TestETHDepositAndCallName,
e2etests.TestETHDepositFastConfirmationName,
e2etests.TestETHWithdrawName,
e2etests.TestETHWithdrawAndArbitraryCallName,
e2etests.TestETHWithdrawAndCallName,
Expand Down
33 changes: 33 additions & 0 deletions docs/openapi/openapi.swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28188,6 +28188,31 @@ paths:
type: boolean
tags:
- Query
/zeta-chain/fungible/foreign_coins/{chainId}/{asset}:
get:
summary: Queries a ForeignCoins by chain_id and asset.
operationId: ForeignCoinsFromAsset
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/zetachain.zetacore.fungible.QueryGetForeignCoinsFromAssetResponse'
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/google.rpc.Status'
parameters:
- name: chainId
in: path
required: true
type: string
format: int64
- name: asset
in: path
required: true
type: string
tags:
- Query
/zeta-chain/fungible/foreign_coins/{index}:
get:
summary: Queries a ForeignCoins by index.
Expand Down Expand Up @@ -58246,6 +58271,14 @@ definitions:
properties:
codeHash:
type: string
zetachain.zetacore.fungible.QueryGetForeignCoinsFromAssetResponse:
type: object
properties:
foreignCoins:
$ref: '#/definitions/zetachain.zetacore.fungible.ForeignCoins'
description: |-
QueryGetForeignCoinsFromAssetResponse defines the response type for the
ForeignCoinsFromAsset RPC method.
zetachain.zetacore.fungible.QueryGetForeignCoinsResponse:
type: object
properties:
Expand Down
14 changes: 14 additions & 0 deletions e2e/e2etests/e2etests.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const (
*/
TestETHDepositName = "eth_deposit"
TestETHDepositAndCallName = "eth_deposit_and_call"
TestETHDepositFastConfirmationName = "eth_deposit_fast_confirmation"
TestETHDepositAndCallNoMessageName = "eth_deposit_and_call_no_message"
TestETHDepositAndCallRevertName = "eth_deposit_and_call_revert"
TestETHDepositAndCallRevertWithCallName = "eth_deposit_and_call_revert_with_call"
Expand Down Expand Up @@ -98,6 +99,7 @@ const (
*/
TestBitcoinDepositName = "bitcoin_deposit"
TestBitcoinDepositAndCallName = "bitcoin_deposit_and_call"
TestBitcoinDepositFastConfirmationName = "bitcoin_deposit_fast_confirmation"
TestBitcoinDepositAndCallRevertName = "bitcoin_deposit_and_call_revert"
TestBitcoinDepositAndCallRevertWithDustName = "bitcoin_deposit_and_call_revert_with_dust"
TestBitcoinDepositAndWithdrawWithDustName = "bitcoin_deposit_and_withdraw_with_dust"
Expand Down Expand Up @@ -245,6 +247,12 @@ var AllE2ETests = []runner.E2ETest{
},
TestETHDepositAndCall,
),
runner.NewE2ETest(
TestETHDepositFastConfirmationName,
"deposit Ether into ZEVM using fast confirmation",
[]runner.ArgDefinition{},
TestETHDepositFastConfirmation,
),
runner.NewE2ETest(
TestETHDepositAndCallNoMessageName,
"deposit Ether into ZEVM and call a contract using no message content",
Expand Down Expand Up @@ -739,6 +747,12 @@ var AllE2ETests = []runner.E2ETest{
},
TestBitcoinDeposit,
),
runner.NewE2ETest(
TestBitcoinDepositFastConfirmationName,
"deposit Bitcoin into ZEVM using fast confirmation",
[]runner.ArgDefinition{},
TestBitcoinDepositFastConfirmation,
),
runner.NewE2ETest(
TestBitcoinDepositAndCallName,
"deposit Bitcoin into ZEVM and call a contract",
Expand Down
4 changes: 0 additions & 4 deletions e2e/e2etests/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,6 @@ func withdrawBTCZRC20(r *runner.E2ERunner, to btcutil.Address, amount *big.Int)
receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout)
utils.RequireTxSuccessful(r, receipt)

// mine 10 blocks to confirm the withdrawal tx
_, err = r.GenerateToAddressIfLocalBitcoin(10, to)
require.NoError(r, err)

// get cctx and check status
cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, receipt.TxHash.Hex(), r.CctxClient, r.Logger, r.CctxTimeout)
utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ import (

// TestBitcoinDepositAndCallRevertWithDust sends a Bitcoin deposit that reverts with a dust amount in the revert outbound.
func TestBitcoinDepositAndCallRevertWithDust(r *runner.E2ERunner, args []string) {
// Given "Live" BTC network
stop := r.MineBlocksIfLocalBitcoin()
defer stop()

require.Len(r, args, 0)

// 0.002 BTC is consumed in a deposit and revert, the dust is set to 1000 satoshis in the protocol
Expand Down
4 changes: 0 additions & 4 deletions e2e/e2etests/test_bitcoin_deposit_call.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ import (
)

func TestBitcoinDepositAndCall(r *runner.E2ERunner, args []string) {
// Given "Live" BTC network
stop := r.MineBlocksIfLocalBitcoin()
defer stop()

// Given amount to send
require.Len(r, args, 1)
amount := utils.ParseFloat(r, args[0])
Expand Down
112 changes: 112 additions & 0 deletions e2e/e2etests/test_bitcoin_deposit_fast_confirmation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package e2etests

import (
"math/big"
"time"

sdkmath "cosmossdk.io/math"
"github.com/btcsuite/btcd/btcutil"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/stretchr/testify/require"

"github.com/zeta-chain/node/e2e/runner"
"github.com/zeta-chain/node/e2e/utils"
"github.com/zeta-chain/node/pkg/chains"
mathpkg "github.com/zeta-chain/node/pkg/math"
crosschaintypes "github.com/zeta-chain/node/x/crosschain/types"
observertypes "github.com/zeta-chain/node/x/observer/types"
)

// TestBitcoinDepositFastConfirmation tests the fast confirmation of Bitcoin deposits
func TestBitcoinDepositFastConfirmation(r *runner.E2ERunner, args []string) {
require.Len(r, args, 0)

// ARRANGE
// enable inbound fast confirmation by updating the chain params
chainID := r.GetBitcoinChainID()
reqQuery := &observertypes.QueryGetChainParamsForChainRequest{ChainId: chainID}
resOldChainParams, err := r.ObserverClient.GetChainParamsForChain(r.Ctx, reqQuery)
require.NoError(r, err)

// define new confirmation params
chainParams := *resOldChainParams.ChainParams
chainParams.ConfirmationParams = &observertypes.ConfirmationParams{
SafeInboundCount: 6, // approx 36 seconds, much longer than Fast confirmation time (6 second)
FastInboundCount: 1,
SafeOutboundCount: 1,
FastOutboundCount: 1,
}
err = r.ZetaTxServer.UpdateChainParams(&chainParams)
require.NoError(r, err, "failed to enable inbound fast confirmation")

// it takes 1 Zeta block time for zetaclient to pick up the new chain params
// wait for 2 blocks to ensure the new chain params are effective
utils.WaitForZetaBlocks(r.Ctx, r, r.ZEVMClient, 2, 20*time.Second)
r.Logger.Info("enabled inbound fast confirmation")

// query current BTC ZRC20 supply
supply, err := r.BTCZRC20.TotalSupply(&bind.CallOpts{})
supplyUint := sdkmath.NewUintFromBigInt(supply)
require.NoError(r, err)

// set ZRC20 liquidity cap to 150% of the current supply
// note: the percentage should not be too small as it may block other tests
liquidityCap, _ := mathpkg.IncreaseUintByPercent(supplyUint, 50)
require.True(r, liquidityCap.GT(sdkmath.ZeroUint()))
res, err := r.ZetaTxServer.SetZRC20LiquidityCap(r.BTCZRC20Addr, liquidityCap)
require.NoError(r, err)
r.Logger.Info("set liquidity cap to %s tx hash: %s", liquidityCap.String(), res.TxHash)

// ACT-1
// deposit with exactly fast amount cap, should be fast confirmed
fastAmountCap := chains.CalcInboundFastConfirmationAmountCap(chainID, liquidityCap)
fastAmountCapFloat := float64(fastAmountCap.Uint64()) / btcutil.SatoshiPerBitcoin
txHash := r.DepositBTCWithExactAmount(fastAmountCapFloat, nil)
r.Logger.Info("deposited exactly fast amount %d cap tx hash: %s", fastAmountCap, txHash)

// ASSERT-1
// wait for the cctx to be FAST confirmed
timeStart := time.Now()
cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.String(), r.CctxClient, r.Logger, r.CctxTimeout)
require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status)
require.Equal(r, crosschaintypes.ConfirmationMode_FAST, cctx.InboundParams.ConfirmationMode)
fastConfirmTime := time.Since(timeStart)

r.Logger.Info("FAST confirmed deposit succeeded in %f seconds", fastConfirmTime.Seconds())

// ACT-2
// deposit with amount more than fast amount cap
amountMoreThanCap := big.NewInt(0).Add(fastAmountCap.BigInt(), big.NewInt(1))
amountMoreThanCapFloat := float64(amountMoreThanCap.Uint64()) / btcutil.SatoshiPerBitcoin
txHash = r.DepositBTCWithExactAmount(amountMoreThanCapFloat, nil)
r.Logger.Info("deposited more than fast amount cap %d tx hash: %s", amountMoreThanCap, txHash)

// mine blocks at normal speed
stop := r.MineBlocksIfLocalBitcoin()
defer stop()

// ASSERT-2
// wait for the cctx to be SAFE confirmed
timeStart = time.Now()
cctx = utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.String(), r.CctxClient, r.Logger, r.CctxTimeout)
require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status)
require.Equal(r, crosschaintypes.ConfirmationMode_SAFE, cctx.InboundParams.ConfirmationMode)
safeConfirmTime := time.Since(timeStart)

r.Logger.Info("SAFE confirmed deposit succeeded in %f seconds", safeConfirmTime.Seconds())

// ensure FAST confirmation is faster than SAFE confirmation
// using one BTC block time is good enough to check the difference
timeSaved := safeConfirmTime - fastConfirmTime
r.Logger.Info("FAST confirmation saved %f seconds", timeSaved.Seconds())
require.True(r, timeSaved > runner.BTCRegnetBlockTime)

// TEARDOWN
// restore old chain params
err = r.ZetaTxServer.UpdateChainParams(resOldChainParams.ChainParams)
require.NoError(r, err, "failed to restore chain params")

// remove the liquidity cap
_, err = r.ZetaTxServer.RemoveZRC20LiquidityCap(r.BTCZRC20Addr)
require.NoError(r, err)
}
4 changes: 0 additions & 4 deletions e2e/e2etests/test_bitcoin_donation.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ import (
)

func TestBitcoinDonation(r *runner.E2ERunner, args []string) {
// Given "Live" BTC network
stop := r.MineBlocksIfLocalBitcoin()
defer stop()

// Given amount to send
require.Len(r, args, 1)
amount := utils.ParseFloat(r, args[0])
Expand Down
4 changes: 0 additions & 4 deletions e2e/e2etests/test_bitcoin_std_deposit.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ import (
)

func TestBitcoinStdMemoDeposit(r *runner.E2ERunner, args []string) {
// start mining blocks if local bitcoin
stop := r.MineBlocksIfLocalBitcoin()
defer stop()

// parse amount to deposit
require.Len(r, args, 1)
amount := utils.ParseFloat(r, args[0])
Expand Down
4 changes: 0 additions & 4 deletions e2e/e2etests/test_bitcoin_std_deposit_and_call.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ import (
)

func TestBitcoinStdMemoDepositAndCall(r *runner.E2ERunner, args []string) {
// start mining blocks if local bitcoin
stop := r.MineBlocksIfLocalBitcoin()
defer stop()

// parse amount to deposit
require.Len(r, args, 1)
amount := utils.ParseFloat(r, args[0])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ import (
)

func TestBitcoinStdMemoInscribedDepositAndCall(r *runner.E2ERunner, args []string) {
// Start mining blocks
stop := r.MineBlocksIfLocalBitcoin()
defer stop()

// Given amount to send and fee rate
require.Len(r, args, 2)
amount := utils.ParseFloat(r, args[0])
Expand Down
4 changes: 0 additions & 4 deletions e2e/e2etests/test_crosschain_swap.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,6 @@ func TestCrosschainSwap(r *runner.E2ERunner, _ []string) {
// check the cctx status
utils.RequireCCTXStatus(r, cctx1, types.CctxStatus_OutboundMined)

// mine 10 blocks to confirm the outbound tx
_, err = r.GenerateToAddressIfLocalBitcoin(10, r.BTCDeployerAddress)
require.NoError(r, err)

// cctx1 index acts like the inboundHash for the second cctx (the one that withdraws BTC)
cctx2 := utils.WaitCctxMinedByInboundHash(r.Ctx, cctx1.Index, r.CctxClient, r.Logger, r.CctxTimeout)

Expand Down
Loading

0 comments on commit 7c0b865

Please sign in to comment.