Skip to content

Commit

Permalink
feat(btc): implement new btc rpc package (#3349)
Browse files Browse the repository at this point in the history
* Implement BTC client

* Port helpers from btc rpc/rpc.go

* Add prom metrics

* Add prom metrics [2]

* Port rpc_live_test

* Drop RPC package

* BTC Client mocks. Drop old interface/mocks

* Fix zetaclient/... unit tests

* Add support for more commands

* Make appContext update deterministic

* Fix e2e

* Update changelog

* Add ISC license

* Remove redundant method from observer.RPC

* Fix raw request

* Reduce metrics cardinality

* Update changelog.md

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

---------

Co-authored-by: Lucas Bertrand <[email protected]>
  • Loading branch information
swift1337 and lumtis authored Jan 16, 2025
1 parent a5e759d commit ff1c157
Show file tree
Hide file tree
Showing 40 changed files with 2,789 additions and 1,654 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Refactor

* [3332](https://github.com/zeta-chain/node/pull/3332) - implement orchestrator V2. Move BTC observer-signer to V2
* [3349](https://github.com/zeta-chain/node/pull/3349) - implement new bitcoin rpc in zetaclient with improved performance and observability

## v25.0.0

Expand Down
4 changes: 2 additions & 2 deletions cmd/zetaclientd/inbound.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import (
"github.com/zeta-chain/node/pkg/coin"
"github.com/zeta-chain/node/testutil/sample"
"github.com/zeta-chain/node/zetaclient/chains/base"
btcclient "github.com/zeta-chain/node/zetaclient/chains/bitcoin/client"
btcobserver "github.com/zeta-chain/node/zetaclient/chains/bitcoin/observer"
btcrpc "github.com/zeta-chain/node/zetaclient/chains/bitcoin/rpc"
evmobserver "github.com/zeta-chain/node/zetaclient/chains/evm/observer"
"github.com/zeta-chain/node/zetaclient/config"
zctx "github.com/zeta-chain/node/zetaclient/context"
Expand Down Expand Up @@ -162,7 +162,7 @@ func InboundGetBallot(_ *cobra.Command, args []string) error {
return fmt.Errorf("unable to find btc config")
}

rpcClient, err := btcrpc.NewRPCClient(bitcoinConfig)
rpcClient, err := btcclient.New(bitcoinConfig, chain.ID(), zerolog.Nop())
if err != nil {
return errors.Wrap(err, "unable to create rpc client")
}
Expand Down
35 changes: 19 additions & 16 deletions cmd/zetae2e/config/clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,23 @@ import (
"context"
"fmt"

"github.com/btcsuite/btcd/rpcclient"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/gagliardetto/solana-go/rpc"
"github.com/rs/zerolog"
ton "github.com/tonkeeper/tongo/liteapi"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"

"github.com/zeta-chain/node/e2e/config"
"github.com/zeta-chain/node/e2e/runner"
tonrunner "github.com/zeta-chain/node/e2e/runner/ton"
"github.com/zeta-chain/node/pkg/chains"
"github.com/zeta-chain/node/pkg/retry"
zetacore_rpc "github.com/zeta-chain/node/pkg/rpc"
btcclient "github.com/zeta-chain/node/zetaclient/chains/bitcoin/client"
tonconfig "github.com/zeta-chain/node/zetaclient/chains/ton"
zetaclientconfig "github.com/zeta-chain/node/zetaclient/config"
)

// getClientsFromConfig get clients from config
Expand Down Expand Up @@ -72,27 +75,27 @@ func getClientsFromConfig(ctx context.Context, conf config.Config, account confi
}

// getBtcClient get btc client
func getBtcClient(rpcConf config.BitcoinRPC) (*rpcclient.Client, error) {
var param string
switch rpcConf.Params {
func getBtcClient(e2eConfig config.BitcoinRPC) (*btcclient.Client, error) {
cfg := zetaclientconfig.BTCConfig{
RPCUsername: e2eConfig.User,
RPCPassword: e2eConfig.Pass,
RPCHost: e2eConfig.Host,
RPCParams: string(e2eConfig.Params),
}

var chain chains.Chain
switch e2eConfig.Params {
case config.Regnet:
chain = chains.BitcoinRegtest
case config.Testnet3:
param = "testnet3"
chain = chains.BitcoinTestnet
case config.Mainnet:
param = "mainnet"
chain = chains.BitcoinMainnet
default:
return nil, fmt.Errorf("invalid bitcoin params %s", rpcConf.Params)
return nil, fmt.Errorf("invalid bitcoin params %s", e2eConfig.Params)
}

connCfg := &rpcclient.ConnConfig{
Host: rpcConf.Host,
User: rpcConf.User,
Pass: rpcConf.Pass,
HTTPPostMode: rpcConf.HTTPPostMode,
DisableTLS: rpcConf.DisableTLS,
Params: param,
}
return rpcclient.New(connCfg, nil)
return btcclient.New(cfg, chain.ChainId, zerolog.Nop())
}

// getEVMClient get evm client
Expand Down
20 changes: 8 additions & 12 deletions e2e/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,10 @@ type RPCs struct {

// BitcoinRPC contains the configuration for the Bitcoin RPC endpoint
type BitcoinRPC struct {
User string `yaml:"user"`
Pass string `yaml:"pass"`
Host string `yaml:"host"`
HTTPPostMode bool `yaml:"http_post_mode"`
DisableTLS bool `yaml:"disable_tls"`
Params BitcoinNetworkType `yaml:"params"`
User string `yaml:"user"`
Pass string `yaml:"pass"`
Host string `yaml:"host"`
Params BitcoinNetworkType `yaml:"params"`
}

// Contracts contains the addresses of predeployed contracts
Expand Down Expand Up @@ -166,12 +164,10 @@ func DefaultConfig() Config {
Zevm: "http://zetacore0:8545",
EVM: "http://eth:8545",
Bitcoin: BitcoinRPC{
Host: "bitcoin:18443",
User: "smoketest",
Pass: "123",
HTTPPostMode: true,
DisableTLS: true,
Params: Regnet,
Host: "bitcoin:18443",
User: "smoketest",
Pass: "123",
Params: Regnet,
},
ZetaCoreGRPC: "zetacore0:9090",
ZetaCoreRPC: "http://zetacore0:26657",
Expand Down
2 changes: 1 addition & 1 deletion e2e/e2etests/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func withdrawBTCZRC20(r *runner.E2ERunner, to btcutil.Address, amount *big.Int)
hash, err := chainhash.NewHashFromStr(outTxHash)
require.NoError(r, err)

rawTx, err := r.BtcRPCClient.GetRawTransactionVerbose(hash)
rawTx, err := r.BtcRPCClient.GetRawTransactionVerbose(r.Ctx, hash)
require.NoError(r, err)

r.Logger.Info("raw tx:")
Expand Down
2 changes: 1 addition & 1 deletion e2e/e2etests/test_crosschain_swap.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func TestCrosschainSwap(r *runner.E2ERunner, _ []string) {
outboundHash, err := chainhash.NewHashFromStr(cctx.GetCurrentOutboundParam().Hash)
require.NoError(r, err)

txraw, err := r.BtcRPCClient.GetRawTransactionVerbose(outboundHash)
txraw, err := r.BtcRPCClient.GetRawTransactionVerbose(r.Ctx, outboundHash)
require.NoError(r, err)

r.Logger.Info("out txid %s", txraw.Txid)
Expand Down
2 changes: 1 addition & 1 deletion e2e/runner/accounting.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func (r *E2ERunner) CheckBTCTSSBalance() error {
if err != nil {
continue
}
utxos, err := r.BtcRPCClient.ListUnspent()
utxos, err := r.BtcRPCClient.ListUnspent(r.Ctx)
if err != nil {
continue
}
Expand Down
2 changes: 1 addition & 1 deletion e2e/runner/balances.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ func (r *E2ERunner) GetBitcoinBalance() (string, error) {

// GetBitcoinBalanceByAddress get btc balance by address.
func (r *E2ERunner) GetBitcoinBalanceByAddress(address btcutil.Address) (btcutil.Amount, error) {
unspentList, err := r.BtcRPCClient.ListUnspentMinMaxAddresses(1, 9999999, []btcutil.Address{address})
unspentList, err := r.BtcRPCClient.ListUnspentMinMaxAddresses(r.Ctx, 1, 9999999, []btcutil.Address{address})
if err != nil {
return 0, errors.Wrap(err, "failed to list unspent")
}
Expand Down
19 changes: 11 additions & 8 deletions e2e/runner/bitcoin.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
func (r *E2ERunner) ListDeployerUTXOs() ([]btcjson.ListUnspentResult, error) {
// query UTXOs from node
utxos, err := r.BtcRPCClient.ListUnspentMinMaxAddresses(
r.Ctx,
1,
9999999,
[]btcutil.Address{r.BTCDeployerAddress},
Expand Down Expand Up @@ -59,6 +60,7 @@ func (r *E2ERunner) ListDeployerUTXOs() ([]btcjson.ListUnspentResult, error) {
func (r *E2ERunner) GetTop20UTXOsForTssAddress() ([]btcjson.ListUnspentResult, error) {
// query UTXOs from node
utxos, err := r.BtcRPCClient.ListUnspentMinMaxAddresses(
r.Ctx,
0,
9999999,
[]btcutil.Address{r.BTCTSSAddress},
Expand Down Expand Up @@ -256,7 +258,7 @@ func (r *E2ERunner) sendToAddrFromDeployerWithMemo(

// create raw
r.Logger.Info("ADDRESS: %s, %s", btcDeployerAddress.EncodeAddress(), to.EncodeAddress())
tx, err := btcRPC.CreateRawTransaction(inputs, amountMap, nil)
tx, err := btcRPC.CreateRawTransaction(r.Ctx, inputs, amountMap, nil)
require.NoError(r, err)

// this adds a OP_RETURN + single BYTE len prefix to the data
Expand Down Expand Up @@ -293,22 +295,23 @@ func (r *E2ERunner) sendToAddrFromDeployerWithMemo(
}
}

stx, signed, err := btcRPC.SignRawTransactionWithWallet2(tx, inputsForSign)
stx, signed, err := btcRPC.SignRawTransactionWithWallet2(r.Ctx, tx, inputsForSign)
require.NoError(r, err)
require.True(r, signed, "btc transaction is not signed")

txid, err := btcRPC.SendRawTransaction(stx, true)
txid, err := btcRPC.SendRawTransaction(r.Ctx, stx, true)
require.NoError(r, err)
r.Logger.Info("txid: %+v", txid)
_, err = r.GenerateToAddressIfLocalBitcoin(6, btcDeployerAddress)
require.NoError(r, err)
gtx, err := btcRPC.GetTransaction(txid)
gtx, err := btcRPC.GetTransaction(r.Ctx, txid)
require.NoError(r, err)
r.Logger.Info("rawtx confirmation: %d", gtx.BlockIndex)
rawtx, err := btcRPC.GetRawTransactionVerbose(txid)
rawtx, err := btcRPC.GetRawTransactionVerbose(r.Ctx, txid)
require.NoError(r, err)

events, err := btcobserver.FilterAndParseIncomingTx(
r.Ctx,
btcRPC,
[]btcjson.TxRawResult{*rawtx},
0,
Expand Down Expand Up @@ -367,7 +370,7 @@ func (r *E2ERunner) InscribeToTSSFromDeployerWithMemo(
require.NoError(r, err)

// submit the reveal transaction
txid, err := r.BtcRPCClient.SendRawTransaction(revealTx, true)
txid, err := r.BtcRPCClient.SendRawTransaction(r.Ctx, revealTx, true)
require.NoError(r, err)
r.Logger.Info("reveal txid: %s", txid.String())

Expand All @@ -394,7 +397,7 @@ func (r *E2ERunner) GenerateToAddressIfLocalBitcoin(
) ([]*chainhash.Hash, error) {
// if not local bitcoin network, do nothing
if r.IsLocalBitcoin() {
return r.BtcRPCClient.GenerateToAddress(numBlocks, address, nil)
return r.BtcRPCClient.GenerateToAddress(r.Ctx, numBlocks, address, nil)
}
return nil, nil
}
Expand All @@ -405,7 +408,7 @@ func (r *E2ERunner) QueryOutboundReceiverAndAmount(txid string) (string, int64)
require.NoError(r, err)

// query outbound raw transaction
revertTx, err := r.BtcRPCClient.GetRawTransaction(txHash)
revertTx, err := r.BtcRPCClient.GetRawTransaction(r.Ctx, txHash)
require.NoError(r, err, revertTx)
require.True(r, len(revertTx.MsgTx().TxOut) >= 2, "bitcoin outbound must have at least two outputs")

Expand Down
4 changes: 2 additions & 2 deletions e2e/runner/clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"fmt"
"net/http"

"github.com/btcsuite/btcd/rpcclient"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/gagliardetto/solana-go/rpc"
Expand All @@ -13,14 +12,15 @@ import (

tonrunner "github.com/zeta-chain/node/e2e/runner/ton"
zetacore_rpc "github.com/zeta-chain/node/pkg/rpc"
btcclient "github.com/zeta-chain/node/zetaclient/chains/bitcoin/client"
)

// Clients contains all the RPC clients and gRPC clients for E2E tests
type Clients struct {
Zetacore zetacore_rpc.Clients

// the RPC clients for external chains in the localnet
BtcRPC *rpcclient.Client
BtcRPC *btcclient.Client
Solana *rpc.Client
Evm *ethclient.Client
EvmAuth *bind.TransactOpts
Expand Down
4 changes: 2 additions & 2 deletions e2e/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (

"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/rpcclient"
"github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
Expand Down Expand Up @@ -48,6 +47,7 @@ import (
fungibletypes "github.com/zeta-chain/node/x/fungible/types"
lightclienttypes "github.com/zeta-chain/node/x/lightclient/types"
observertypes "github.com/zeta-chain/node/x/observer/types"
btcclient "github.com/zeta-chain/node/zetaclient/chains/bitcoin/client"
)

type E2ERunnerOption func(*E2ERunner)
Expand Down Expand Up @@ -86,7 +86,7 @@ type E2ERunner struct {
// rpc clients
ZEVMClient *ethclient.Client
EVMClient *ethclient.Client
BtcRPCClient *rpcclient.Client
BtcRPCClient *btcclient.Client
SolanaClient *rpc.Client

// zetacored grpc clients
Expand Down
10 changes: 5 additions & 5 deletions e2e/runner/setup_bitcoin.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func (r *E2ERunner) AddTSSToNode() {
}()

// import the TSS address
err := r.BtcRPCClient.ImportAddress(r.BTCTSSAddress.EncodeAddress())
err := r.BtcRPCClient.ImportAddress(r.Ctx, r.BTCTSSAddress.EncodeAddress())
require.NoError(r, err)

// mine some blocks to get some BTC into the deployer address
Expand All @@ -38,12 +38,12 @@ func (r *E2ERunner) SetupBitcoinAccounts(createWallet bool) {
r.SetupBtcAddress(createWallet)

// import the TSS address to index TSS utxos and transactions
err := r.BtcRPCClient.ImportAddress(r.BTCTSSAddress.EncodeAddress())
err := r.BtcRPCClient.ImportAddress(r.Ctx, r.BTCTSSAddress.EncodeAddress())
require.NoError(r, err)
r.Logger.Info("⚙️ imported BTC TSSAddress: %s", r.BTCTSSAddress.EncodeAddress())

// import deployer address to index deployer utxos and transactions
err = r.BtcRPCClient.ImportAddress(r.BTCDeployerAddress.EncodeAddress())
err = r.BtcRPCClient.ImportAddress(r.Ctx, r.BTCDeployerAddress.EncodeAddress())
require.NoError(r, err)
r.Logger.Info("⚙️ imported BTCDeployerAddress: %s", r.BTCDeployerAddress.EncodeAddress())
}
Expand Down Expand Up @@ -98,12 +98,12 @@ func (r *E2ERunner) SetupBtcAddress(createWallet bool) {
require.NoError(r, err)
argsRawMsg = append(argsRawMsg, encodedArg)
}
_, err := r.BtcRPCClient.RawRequest("createwallet", argsRawMsg)
_, err := r.BtcRPCClient.RawRequest(r.Ctx, "createwallet", argsRawMsg)
if err != nil {
require.ErrorContains(r, err, "Database already exists")
}

err = r.BtcRPCClient.ImportPrivKeyRescan(privkeyWIF, r.Name, true)
err = r.BtcRPCClient.ImportPrivKeyRescan(r.Ctx, privkeyWIF, r.Name, true)
require.NoError(r, err, "failed to execute ImportPrivKeyRescan")
}
}
Loading

0 comments on commit ff1c157

Please sign in to comment.