From 58608f0a8c0361d86515dd1827b41df3be42f4f3 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Fri, 5 May 2023 16:32:55 +0200 Subject: [PATCH 1/4] Fixed bitcoin calls --- examples/btc/main_btc.v | 31 ++++- lib/btc/btc.v | 199 +++++++++++++++++----------- lib/btc/types.v | 79 +++++++++++ server/pkg/btc/client.go | 279 ++++++++++++++++++++++++--------------- 4 files changed, 400 insertions(+), 188 deletions(-) diff --git a/examples/btc/main_btc.v b/examples/btc/main_btc.v index eb960d762..6cb86b46f 100644 --- a/examples/btc/main_btc.v +++ b/examples/btc/main_btc.v @@ -11,10 +11,10 @@ const ( default_server_address = 'ws://127.0.0.1:8080' ) -fn execute_rpcs(mut client RpcWsClient, mut logger log.Logger, host string, user string, password string) ! { +fn execute_rpcs(mut client RpcWsClient, mut logger log.Logger, host string, user string, password string, mywallet string) ! { mut btc_client := btc.new(mut client) - btc_client.load(host: host, user: user, pass: password)! + btc_client.load(host: host, user: user, pass: password, wallet: mywallet)! amount_blocks := btc_client.get_block_count()! logger.info("Block count: ${amount_blocks}") @@ -41,7 +41,29 @@ fn execute_rpcs(mut client RpcWsClient, mut logger log.Logger, host string, user result := btc_client.create_wallet(name:"mywallet2", passphrase:"mypassphrase")! logger.info("Result of creating wallet: ${result}") */ - + + wallet_info := btc_client.get_wallet_info()! + logger.info("Wallet info: ${wallet_info}") + + /* + accounts := btc_client.list_labels()! + logger.info("Accounts in mywallet: ${accounts.keys()}") + */ + + balance := btc_client.get_balance()! + logger.info("Balance is ${balance}") + + by_address := btc_client.list_received_by_address()! + logger.info("Received by address: ${by_address}") + + /* + by_label := btc_client.list_received_by_label()! + logger.info("Received by label: ${by_label}") + */ + + peer_info := btc_client.get_peer_info()! + logger.info("Peer info: ${peer_info}") + } fn main() { @@ -54,6 +76,7 @@ fn main() { password := fp.string('pass', `p`, '', 'The password to use to connect to the bitcoin node.') host := fp.string('host', `h`, '', 'The address of the bitcoin node to connect to.') user := fp.string('user', `u`, '', 'The user to use to connect to the bitcoin node.') + wallet := fp.string('wallet', `w`, '', 'The wallet you want to use.') debug_log := fp.bool('debug', 0, false, 'By setting this flag the client will print debug logs too.') _ := fp.finalize() or { @@ -73,7 +96,7 @@ fn main() { _ := spawn myclient.run() - execute_rpcs(mut myclient, mut logger, host, user, password) or { + execute_rpcs(mut myclient, mut logger, host, user, password, wallet) or { logger.error('Failed executing calls: ${err}') exit(1) } diff --git a/lib/btc/btc.v b/lib/btc/btc.v index 16da16ec3..00ad05c54 100644 --- a/lib/btc/btc.v +++ b/lib/btc/btc.v @@ -18,33 +18,30 @@ pub struct Load { host string user string pass string + wallet string } // args to import bitcoin address [params] -pub struct ImportAddressRescan { - address string - account string - rescan bool +pub struct ImportAddress { + address string // the Bitcoin address (or hex-encoded script) + label string // an optional label + rescan bool = true // whether or not to scan the chain and mempool for wallet transactions + p2sh bool // add the p2sh version of the script as well } [params] -pub struct ImportPrivKeyLabel { - wif string - label string -} - -[params] -pub struct ImportPrivKeyRescan { +pub struct ImportPrivKey { wif string - label string - rescan bool + label string // an optional label (default: current label if address exists, otherwise "") + rescan bool = true // scan the chain and mempool for wallet transactions } [params] -pub struct ImportPubKeyRescan { +pub struct ImportPubKey { pub_key string - rescan bool + label string // an optional label + rescan bool // scan the chain and mempool for wallet transactions } [params] @@ -53,7 +50,7 @@ pub struct RenameAccount { new_account string } -// send amount of token to address, with/without comment +// send amount of token to address, with/without comment [params] pub struct SendToAddress { address string @@ -64,7 +61,7 @@ pub struct SendToAddress { [params] pub struct EstimateSmartFee { - conf_target i64 = 1 // confirmation target in blocks + conf_target i64 = 1 // confirmation target in blocks (value between 1 and 1008) mode string = "CONSERVATIVE" // defines the different fee estimation modes, should be one of UNSET, ECONOMICAL or CONSERVATIVE } @@ -81,13 +78,19 @@ pub struct GetChainTxStats { block_hash_end string // provide statistics for amount_of_blocks blocks up until the block with the hash provided in block_hash_end } +[params] +pub struct GetNewAddress { + label string + address_type string +} + [params] pub struct CreateWallet { - name string - disable_private_keys bool - create_blank_wallet bool - passphrase string - avoid_reuse bool + name string // name of the wallet to create + disable_private_keys bool // disable the possibility of private keys (only watchonlys are possible in this mode) + create_blank_wallet bool // create a blank wallet (has no keys or HD seed) + passphrase string // encrypt the wallet with this passphrase + avoid_reuse bool // keep track of coin reuse, and treat dirty and clean coins differently with privacy considerations in mind } [params] @@ -99,7 +102,12 @@ pub struct Move { comment string } -[openrpc: exclude] +[params] +pub struct SetLabel { + label string // the label to assign to the address + address string // the bitcoin address to be associated with a label +} + pub fn new(mut client RpcWsClient) BtcClient { return BtcClient{ client: &client @@ -113,34 +121,10 @@ pub fn (mut c BtcClient) load(params Load) !string { ], btc.default_timeout)! } -// Imports the passed public address. -pub fn (mut c BtcClient) import_address(address string) ! { - _ := c.client.send_json_rpc[[]string, string]('btc.ImportAddress', [ - address, - ], btc.default_timeout)! -} - // Imports the passed public address. When rescan is true, // the block history is scanned for transactions addressed to provided address. -pub fn (mut c BtcClient) import_address_rescan(args ImportAddressRescan) ! { - _ := c.client.send_json_rpc[[]ImportAddressRescan, string]('btc.ImportAddressRescan', [ - args, - ], btc.default_timeout)! -} - -// Imports the passed private key which must be the wallet import format (WIF). -// The WIF string must be a base58-encoded string. -pub fn (mut c BtcClient) import_priv_key(wif string) ! { - _ := c.client.send_json_rpc[[]string, string]('btc.ImportPrivKey', [ - wif, - ], btc.default_timeout)! -} - -// Imports the passed private key which must be the wallet import -// format (WIF). It sets the account label to the one provided. -// The WIF string must be a base58-encoded string. -pub fn (mut c BtcClient) import_priv_key_label(args ImportPrivKeyLabel) ! { - _ := c.client.send_json_rpc[[]ImportPrivKeyLabel, string]('btc.ImportPrivKeyLabel', [ +pub fn (mut c BtcClient) import_address(args ImportAddress) ! { + _ := c.client.send_json_rpc[[]ImportAddress, string]('btc.ImportAddress', [ args, ], btc.default_timeout)! } @@ -149,27 +133,27 @@ pub fn (mut c BtcClient) import_priv_key_label(args ImportPrivKeyLabel) ! { // format (WIF). It sets the account label to the one provided. When rescan is true, // the block history is scanned for transactions addressed to provided privKey. // The WIF string must be a base58-encoded string. -pub fn (mut c BtcClient) import_priv_key_rescan(args ImportPrivKeyRescan) ! { - _ := c.client.send_json_rpc[[]ImportPrivKeyRescan, string]('btc.ImportPrivKeyRescan', [ +pub fn (mut c BtcClient) import_priv_key(args ImportPrivKey) ! { + _ := c.client.send_json_rpc[[]ImportPrivKey, string]('btc.ImportPrivKey', [ args, ], btc.default_timeout)! } -// Imports the passed public key. -pub fn (mut c BtcClient) import_pub_key(pub_key string) ! { - _ := c.client.send_json_rpc[[]string, string]('btc.ImportPubKey', [pub_key], btc.default_timeout)! -} - // Imports the passed public key. When rescan is true, the block history is scanned for transactions addressed to provided pubkey. -pub fn (mut c BtcClient) import_pub_key_rescan(args ImportPubKeyRescan) ! { - _ := c.client.send_json_rpc[[]ImportPubKeyRescan, string]('btc.ImportPubKeyRescan', [ +pub fn (mut c BtcClient) import_pub_key_rescan(args ImportPubKey) ! { + _ := c.client.send_json_rpc[[]ImportPubKey, string]('btc.ImportPubKey', [ args, ], btc.default_timeout)! } +// List the accounts of a wallet +pub fn (mut c BtcClient) list_labels() !map[string]i64 { + return c.client.send_json_rpc[[]string, map[string]i64]('btc.ListLabels', []string{}, btc.default_timeout)! +} + // Allows you to rename an account. pub fn (mut c BtcClient) rename_account(args RenameAccount) ! { - c.client.send_json_rpc[[]RenameAccount, string]('btc.RenameAccount', [args], btc.default_timeout)! + _ := c.client.send_json_rpc[[]RenameAccount, string]('btc.RenameAccount', [args], btc.default_timeout)! } // Sends the passed amount to the given address with a comment if provided and returns the hash of the transaction @@ -195,15 +179,9 @@ pub fn (mut c BtcClient) generate_blocks_to_address(args GenerateToAddress) ![]s [args], btc.default_timeout)! } -// Returns the account associated with the passed address. The address should be the string encoded version of a valid address. -pub fn (mut c BtcClient) get_account(address string) !string { - return c.client.send_json_rpc[[]string, string]('btc.GetAccount', [address], btc.default_timeout)! -} - -// Returns the current Bitcoin address for receiving payments to the specified account. -pub fn (mut c BtcClient) get_account_address(account string) !string { - return c.client.send_json_rpc[[]string, string]('btc.GetAccountAddress', [account], - btc.default_timeout)! +// Associates the given label to the given address +pub fn (mut c BtcClient) set_label(args SetLabel) ! { + _ := c.client.send_json_rpc[[]SetLabel, string]('btc.SetLabel', [args], btc.default_timeout)! } // Returns information about the given bitcoin address. @@ -212,19 +190,19 @@ pub fn (mut c BtcClient) get_address_info(address string) !GetAddressInfoResult [address], btc.default_timeout)! } -// Returns the list of addresses associated with the provided account. The returned list will be the string encoded versions of the addresses. -pub fn (mut c BtcClient) get_addresses_by_account(account string) ![]string { - return c.client.send_json_rpc[[]string, []string]('btc.GetAddressesByAccount', [ - account, +// Returns the list of addresses associated with the provided label. The returned list will be the string encoded versions of the addresses. +pub fn (mut c BtcClient) get_addresses_by_label(label string) ![]string { + return c.client.send_json_rpc[[]string, []string]('btc.GetAddressesByLabel', [ + label, ], btc.default_timeout)! } -// Returns the available balance for the specified account using the default number of minimum confirmations. You can provide * as an account to get the balance of all accounts. -pub fn (mut c BtcClient) get_balance(account string) !i64 { - return c.client.send_json_rpc[[]string, i64]('btc.GetBalance', [account], btc.default_timeout)! +// Returns the available balance using the default number of minimum confirmations. +pub fn (mut c BtcClient) get_balance() !i64 { + return c.client.send_json_rpc[[]string, i64]('btc.GetBalance', []string{}, btc.default_timeout)! } -// Returns the number of blocks in the longest block chain. +// Returns the height of the most-work fully-validated chain. pub fn (mut c BtcClient) get_block_count() !i64 { return c.client.send_json_rpc[[]string, i64]('btc.GetBlockCount', []string{}, btc.default_timeout)! } @@ -240,6 +218,12 @@ pub fn (mut c BtcClient) get_block_stats(hash string) !GetBlockStatsResult { [hash], btc.default_timeout)! } +// Returns information on the state of the blockchain. +pub fn (mut c BtcClient) get_blockchain_info() !GetBlockChainInfo { + return c.client.send_json_rpc[[]string, GetBlockChainInfo]('btc.GetBlockStats', + []string{}, btc.default_timeout)! +} + // Returns information about a block and its transactions given the hash of that block. pub fn (mut c BtcClient) get_block_verbose_tx(hash string) !GetBlockVerboseTxResult { return c.client.send_json_rpc[[]string, GetBlockVerboseTxResult]('btc.GetBlockVerboseTx', @@ -253,6 +237,11 @@ pub fn (mut c BtcClient) get_chain_tx_stats(args GetChainTxStats) !GetChainTxSta [args], btc.default_timeout)! } +// Returns the number of connections to other nodes. +pub fn (mut c BtcClient) get_connection_count() !i64 { + return c.client.send_json_rpc[[]string, i64]('btc.GetConnectionCount', []string{}, btc.default_timeout)! +} + // Returns the proof-of-work difficulty as a multiple of the minimum difficulty. pub fn (mut c BtcClient) get_difficulty() !f64 { return c.client.send_json_rpc[[]string, f64]('btc.GetDifficulty', []string{}, btc.default_timeout)! @@ -264,9 +253,10 @@ pub fn (mut c BtcClient) get_mining_info() !GetMiningInfoResult { []string{}, btc.default_timeout)! } -// Returns a new address. The returned string will be the encoded address (format will be based on the chain's parameters). -pub fn (mut c BtcClient) get_new_address(account string) !string { - return c.client.send_json_rpc[[]string, string]('btc.GetNewAddress', [account], btc.default_timeout)! +// Returns a new address. The returned string will be the encoded address based on the address_type provided. If +// address_type is left empty the default address type will be used from the chain's parameters. +pub fn (mut c BtcClient) get_new_address(args GetNewAddress) !string { + return c.client.send_json_rpc[[]GetNewAddress, string]('btc.GetNewAddress', [args], btc.default_timeout)! } // Returns data about known node addresses. @@ -287,12 +277,61 @@ pub fn (mut c BtcClient) get_raw_transaction(tx_hash string) !Transaction { btc.default_timeout)! } -// Creates a new wallet account taken into account the provided arguments. +// Returns the total amount received by the specified label +pub fn (mut c BtcClient) get_received_by_label(label string) !Transaction { + return c.client.send_json_rpc[[]string, Transaction]('btc.GetReceivedByLabel', [label], + btc.default_timeout)! +} + +// Load a specific wallet before executing wallet specific requests +pub fn (mut c BtcClient) load_wallet(wallet_name string) !LoadWalletResult { + return c.client.send_json_rpc[[]string, LoadWalletResult]('btc.LoadWallet', [wallet_name], + btc.default_timeout)! +} + +// Return wallet information +pub fn (mut c BtcClient) get_wallet_info() !GetWalletInfoResult { + return c.client.send_json_rpc[[]string, GetWalletInfoResult]('btc.GetWalletInfo', []string{}, + btc.default_timeout)! +} + +// Lists the received transactions by label +pub fn (mut c BtcClient) list_received_by_label() ![]ListReceivedByLabelResult { + return c.client.send_json_rpc[[]string, []ListReceivedByLabelResult]('btc.ListReceivedByLabel', []string{}, + btc.default_timeout)! +} + +// Lists the received transactions by address +pub fn (mut c BtcClient) list_received_by_address() ![]ListReceivedByAddressResult { + return c.client.send_json_rpc[[]string, []ListReceivedByAddressResult]('btc.ListReceivedByAddress', []string{}, + btc.default_timeout)! +} + +// List all transactions since block with hash. +pub fn (mut c BtcClient) list_since_block(hash string) !ListSinceBlockResult { + return c.client.send_json_rpc[[]string, ListSinceBlockResult]('btc.ListSinceBlock', [hash], + btc.default_timeout)! +} + +// List all transactions for a specific label. +pub fn (mut c BtcClient) list_transactions(label string) ![]ListTransactionsResult { + return c.client.send_json_rpc[[]string, []ListTransactionsResult]('btc.ListTransactions', [label], + btc.default_timeout)! +} + +// Creates a new wallet taking into account the provided arguments. pub fn (mut c BtcClient) create_wallet(args CreateWallet) !CreateWalletResult { return c.client.send_json_rpc[[]CreateWallet, CreateWalletResult]('btc.CreateWallet', [args], btc.default_timeout)! } +// Sets the transaction fee per kilobyte paid by transactions created by this wallet. +pub fn (mut c BtcClient) set_tx_fee(fee i64) ! { + _ := c.client.send_json_rpc[[]i64, string]('btc.SetTxFee', + [fee], btc.default_timeout)! +} + +// Deprecated // Moves specified amount from one account in your wallet to another. Only funds with the default number of minimum confirmations will be used. // A comment can also be added to the transaction. pub fn (mut c BtcClient) move(args Move) !bool { diff --git a/lib/btc/types.v b/lib/btc/types.v index 9684c11dd..c5b7c82da 100644 --- a/lib/btc/types.v +++ b/lib/btc/types.v @@ -228,8 +228,87 @@ pub struct TxOut { pk_script []byte } +pub struct LoadWalletResult { + name string + warning string +} + +pub struct GetWalletInfoResult { + walletname string + walletversion int + txcount int + keypoololdest int + keypoolsize int + keypoolsize_hd_internal ?int + unlocked_until ?int + paytxfee f64 + hdseedid string + private_keys_enabled bool + avoid_reuse bool +} + // CreateWalletResult models the result of the createwallet command. pub struct CreateWalletResult { name string warning string } + +pub struct GetBlockChainInfo { + chain string + blocks int + headers int + bestblockhash string + difficulty f64 + mediantime i64 + verificationprogress f64 + initialblockdownload bool + pruned bool + pruneheight int + chainwork string + size_on_disk i64 +} + +pub struct ListReceivedByLabelResult { + account string + amount f64 + confirmations u64 +} + +pub struct ListReceivedByAddressResult { + account string + address string + amount f64 + confirmations u64 + txids []string + involveswatchonly bool [json: 'involvesWatchonly'] +} + +pub struct ListSinceBlockResult { + transactions []ListTransactionsResult + lastblock string +} + +pub struct ListTransactionsResult{ + abandoned bool + account string + address string + amount f64 + bip125_replaceable string [json: 'bip125-replaceable'] + blockhash string + blockheight int + blockindex i64 + blocktime i64 + category string + confirmations i64 + fee f64 + generated bool + involveswatchonly bool + label string + time i64 + timereceived i64 + trusted bool + txid string + vout u32 + walletconflicts []string + otheraccount string +} \ No newline at end of file diff --git a/server/pkg/btc/client.go b/server/pkg/btc/client.go index 07ed74ea6..b9a171261 100644 --- a/server/pkg/btc/client.go +++ b/server/pkg/btc/client.go @@ -2,6 +2,7 @@ package btc import ( "context" + "errors" "github.com/LeeSmet/go-jsonrpc" "github.com/btcsuite/btcd/btcjson" @@ -27,30 +28,28 @@ type ( } Load struct { - Host string `json:"host"` - User string `json:"user"` - Pass string `json:"pass"` + Host string `json:"host"` + User string `json:"user"` + Pass string `json:"pass"` + Wallet string `json:"wallet"` } - ImportAddressRescan struct { + ImportAddress struct { Address string `json:"address"` - Account string `json:"account"` + Label string `json:"label"` Rescan bool `json:"rescan"` + P2SH bool `json:"p2sh"` } - ImportPrivKeyLabel struct { - WIF string `json:"wif"` - Label string `json:"label"` - } - - ImportPrivKeyRescan struct { + ImportPrivKey struct { WIF string `json:"wif"` Label string `json:"label"` Rescan bool `json:"rescan"` } - ImportPubKeyRescan struct { + ImportPubKey struct { PubKey string `json:"pub_key"` + Label string `json:"label"` Rescan bool `json:"rescan"` } @@ -90,6 +89,11 @@ type ( AvoidReuse bool `json:"avoid_reuse"` } + GetNewAddress struct { + Label string `json:"label"` + AddressType string `json:"address_type"` + } + Move struct { FromAccount string `json:"from_account"` ToAccount string `json:"to_account"` @@ -97,6 +101,11 @@ type ( MinConfirmations int `json:"min_confirmations"` Comment string `json:"comment"` } + + SetLabel struct { + Address string `json:"address"` + Label string `json:"label"` + } ) // State from a connection. If no state is present, it is initialized @@ -127,7 +136,7 @@ func (c *Client) Load(ctx context.Context, conState jsonrpc.State, args Load) er client, err := btcRpcClient.New( &btcRpcClient.ConnConfig{ - Host: args.Host, + Host: args.Host + "/wallet/" + args.Wallet, User: args.User, Pass: args.Pass, HTTPPostMode: true, @@ -142,62 +151,19 @@ func (c *Client) Load(ctx context.Context, conState jsonrpc.State, args Load) er return nil } -func (c *Client) ImportAddress(ctx context.Context, conState jsonrpc.State, address string) error { - log.Debug().Msgf("BTC: importing address %s", address) - - state := State(conState) - if state.client == nil { - return pkg.ErrClientNotConnected{} - } - - return state.client.ImportAddress(address) -} - -func (c *Client) ImportAddressRescan(ctx context.Context, conState jsonrpc.State, args ImportAddressRescan) error { - log.Debug().Msgf("BTC: importing address rescan %s for account %s", args.Address, args.Account) - - state := State(conState) - if state.client == nil { - return pkg.ErrClientNotConnected{} - } - - return state.client.ImportAddressRescan(args.Address, args.Account, args.Rescan) -} - -func (c *Client) ImportPrivKey(ctx context.Context, conState jsonrpc.State, wif string) error { - log.Debug().Msg("BTC: importing private key") +func (c *Client) ImportAddress(ctx context.Context, conState jsonrpc.State, args ImportAddress) error { + log.Debug().Msgf("BTC: importing address %s with label %s (rescan: %t, p2sh: %s)", args.Address, args.Label, args.Rescan, args.P2SH) state := State(conState) if state.client == nil { return pkg.ErrClientNotConnected{} } - privKeyWIF, err := btcutil.DecodeWIF(wif) - if err != nil { - return err - } - - return state.client.ImportPrivKey(privKeyWIF) + return state.client.ImportAddressRescan(args.Address, args.Label, args.Rescan) } -func (c *Client) ImportPrivKeyLabel(ctx context.Context, conState jsonrpc.State, args ImportPrivKeyLabel) error { - log.Debug().Msgf("BTC: importing private key with label %s", args.Label) - - state := State(conState) - if state.client == nil { - return pkg.ErrClientNotConnected{} - } - - privKeyWIF, err := btcutil.DecodeWIF(args.WIF) - if err != nil { - return err - } - - return state.client.ImportPrivKeyLabel(privKeyWIF, args.Label) -} - -func (c *Client) ImportPrivKeyRescan(ctx context.Context, conState jsonrpc.State, args ImportPrivKeyRescan) error { - log.Debug().Msgf("BTC: importing private key rescan with label %s", args.Label) +func (c *Client) ImportPrivKeyRescan(ctx context.Context, conState jsonrpc.State, args ImportPrivKey) error { + log.Debug().Msgf("BTC: importing private key to label %s (rescan: %t)", args.Label, args.Rescan) state := State(conState) if state.client == nil { @@ -212,26 +178,26 @@ func (c *Client) ImportPrivKeyRescan(ctx context.Context, conState jsonrpc.State return state.client.ImportPrivKeyRescan(privKeyWIF, args.Label, args.Rescan) } -func (c *Client) ImportPubKey(ctx context.Context, conState jsonrpc.State, pubKey string) error { - log.Debug().Msg("BTC: importing public key") +func (c *Client) ImportPubKey(ctx context.Context, conState jsonrpc.State, args ImportPubKey) error { + log.Debug().Msgf("BTC: importing public key (rescan: %t)", args.Rescan) state := State(conState) if state.client == nil { return pkg.ErrClientNotConnected{} } - return state.client.ImportPubKey(pubKey) + return state.client.ImportPubKeyRescan(args.PubKey, args.Rescan) } -func (c *Client) ImportPubKeyRescan(ctx context.Context, conState jsonrpc.State, args ImportPubKeyRescan) error { - log.Debug().Msg("BTC: importing public key rescan") +func (c *Client) ListLabels(ctx context.Context, conState jsonrpc.State) (map[string]btcutil.Amount, error) { + log.Debug().Msg("BTC: listing labels") state := State(conState) if state.client == nil { - return pkg.ErrClientNotConnected{} + return map[string]btcutil.Amount{}, pkg.ErrClientNotConnected{} } - return state.client.ImportPubKeyRescan(args.PubKey, args.Rescan) + return state.client.ListAccounts() } func (c *Client) RenameAccount(ctx context.Context, conState jsonrpc.State, args RenameAccount) error { @@ -330,40 +296,24 @@ func (c *Client) GenerateBlocksToAddress(ctx context.Context, conState jsonrpc.S return hashesToStrings(hashes), err } -func (c *Client) GetAccount(ctx context.Context, conState jsonrpc.State, address string) (string, error) { - log.Debug().Msgf("BTC: getting account name for address %s", address) +func (c *Client) SetLabel(ctx context.Context, conState jsonrpc.State, args SetLabel) error { + log.Debug().Msgf("BTC: setting label on address %s: %s", args.Address, args.Label) state := State(conState) if state.client == nil { - return "", pkg.ErrClientNotConnected{} - } - - decodedAddress, err := btcutil.DecodeAddress(address, nil) - if err != nil { - return "", err - } - - return state.client.GetAccount(decodedAddress) -} - -func (c *Client) GetAccountAddress(ctx context.Context, conState jsonrpc.State, account string) (string, error) { - log.Debug().Msgf("BTC: getting account address of account %s", account) - - state := State(conState) - if state.client == nil { - return "", pkg.ErrClientNotConnected{} + return pkg.ErrClientNotConnected{} } - address, err := state.client.GetAccountAddress(account) + decodedAddress, err := btcutil.DecodeAddress(args.Address, nil) if err != nil { - return "", err + return err } - return address.EncodeAddress(), nil + return state.client.SetAccount(decodedAddress, args.Label) } func (c *Client) GetAddressInfo(ctx context.Context, conState jsonrpc.State, address string) (*btcjson.GetAddressInfoResult, error) { - log.Debug().Msgf("BTC: getting address info of address %s", address) + log.Debug().Msgf("BTC: getting address info for %s", address) state := State(conState) if state.client == nil { @@ -373,15 +323,15 @@ func (c *Client) GetAddressInfo(ctx context.Context, conState jsonrpc.State, add return state.client.GetAddressInfo(address) } -func (c *Client) GetAddressesByAccount(ctx context.Context, conState jsonrpc.State, account string) ([]string, error) { - log.Debug().Msgf("BTC: getting address of account %s", account) +func (c *Client) GetAddressesByLabel(ctx context.Context, conState jsonrpc.State, label string) ([]string, error) { + log.Debug().Msgf("BTC: getting addresses by label %s", label) state := State(conState) if state.client == nil { return []string{}, pkg.ErrClientNotConnected{} } - addresses, err := state.client.GetAddressesByAccount(account) + addresses, err := state.client.GetAddressesByAccount(label) if err != nil { return []string{}, err } @@ -394,15 +344,15 @@ func (c *Client) GetAddressesByAccount(ctx context.Context, conState jsonrpc.Sta return addressesEncoded, nil } -func (c *Client) GetBalance(ctx context.Context, conState jsonrpc.State, account string) (btcutil.Amount, error) { - log.Debug().Msgf("BTC: getting balance of account %s", account) +func (c *Client) GetBalance(ctx context.Context, conState jsonrpc.State) (btcutil.Amount, error) { + log.Debug().Msgf("BTC: getting balance of wallet") state := State(conState) if state.client == nil { return 0, pkg.ErrClientNotConnected{} } - return state.client.GetBalance(account) + return state.client.GetBalance("*") } func (c *Client) GetBlockCount(ctx context.Context, conState jsonrpc.State) (int64, error) { @@ -443,6 +393,17 @@ func (c *Client) GetBlockStats(ctx context.Context, conState jsonrpc.State, hash return state.client.GetBlockStats(hash, nil) } +func (c *Client) GetBlockChainInfo(ctx context.Context, conState jsonrpc.State) (*btcjson.GetBlockChainInfoResult, error) { + log.Debug().Msg("BTC: getting blockchain info") + + state := State(conState) + if state.client == nil { + return nil, pkg.ErrClientNotConnected{} + } + + return state.client.GetBlockChainInfo() +} + func (c *Client) GetBlockVerboseTx(ctx context.Context, conState jsonrpc.State, hash string) (*btcjson.GetBlockVerboseTxResult, error) { log.Debug().Msgf("BTC: getting block verbose tx for block at height %s", hash) @@ -480,6 +441,17 @@ func (c *Client) GetChainTxStats(ctx context.Context, conState jsonrpc.State, ar return state.client.GetChainTxStats() } +func (c *Client) GetConnectionCount(ctx context.Context, conState jsonrpc.State) (int64, error) { + log.Debug().Msg("BTC: connection count") + + state := State(conState) + if state.client == nil { + return 0, pkg.ErrClientNotConnected{} + } + + return state.client.GetConnectionCount() +} + func (c *Client) GetDifficulty(ctx context.Context, conState jsonrpc.State) (float64, error) { log.Debug().Msg("BTC: getting difficulty") @@ -502,15 +474,21 @@ func (c *Client) GetMiningInfo(ctx context.Context, conState jsonrpc.State) (*bt return state.client.GetMiningInfo() } -func (c *Client) GetNewAddress(ctx context.Context, conState jsonrpc.State, account string) (string, error) { - log.Debug().Msgf("BTC: getting new address for account %s", account) +func (c *Client) GetNewAddress(ctx context.Context, conState jsonrpc.State, args GetNewAddress) (string, error) { + log.Debug().Msgf("BTC: getting new address of type %s for label %s", args.AddressType, args.Label) state := State(conState) if state.client == nil { return "", pkg.ErrClientNotConnected{} } - address, err := state.client.GetNewAddress(account) + var address btcutil.Address + var err error + if args.AddressType != "" { + address, err = state.client.GetNewAddressType(args.Label, args.AddressType) + } else { + address, err = state.client.GetNewAddress(args.Label) + } if err != nil { return "", err } @@ -556,6 +534,88 @@ func (c *Client) GetRawTransaction(ctx context.Context, conState jsonrpc.State, return state.client.GetRawTransaction(txHashDecoded) } +func (c *Client) GetReceivedByLabel(ctx context.Context, conState jsonrpc.State, label string) (btcutil.Amount, error) { + log.Debug().Msgf("BTC: getting amount received by label %s", label) + + state := State(conState) + if state.client == nil { + return 0, pkg.ErrClientNotConnected{} + } + + return state.client.GetReceivedByAccount(label) +} + +func (c *Client) LoadWallet(ctx context.Context, conState jsonrpc.State, walletName string) (*btcjson.LoadWalletResult, error) { + log.Debug().Msgf("BTC: loading wallet %s", walletName) + + state := State(conState) + if state.client == nil { + return nil, pkg.ErrClientNotConnected{} + } + + return state.client.LoadWallet(walletName) +} + +func (c *Client) GetWalletInfo(ctx context.Context, conState jsonrpc.State) (*btcjson.GetWalletInfoResult, error) { + log.Debug().Msg("BTC: getting wallet info") + + state := State(conState) + if state.client == nil { + return nil, pkg.ErrClientNotConnected{} + } + + return state.client.GetWalletInfo() +} + +func (c *Client) ListReceivedByLabel(ctx context.Context, conState jsonrpc.State) ([]btcjson.ListReceivedByAccountResult, error) { + log.Debug().Msg("BTC: listing received transactions by label") + + state := State(conState) + if state.client == nil { + return nil, pkg.ErrClientNotConnected{} + } + + return state.client.ListReceivedByAccount() +} + +func (c *Client) ListReceivedByAddress(ctx context.Context, conState jsonrpc.State) ([]btcjson.ListReceivedByAddressResult, error) { + log.Debug().Msg("BTC: listing received transactions by address") + + state := State(conState) + if state.client == nil { + return nil, pkg.ErrClientNotConnected{} + } + + return state.client.ListReceivedByAddress() +} + +func (c *Client) ListSinceBlock(ctx context.Context, conState jsonrpc.State, hash string) (*btcjson.ListSinceBlockResult, error) { + log.Debug().Msg("BTC: listing since block") + + state := State(conState) + if state.client == nil { + return nil, pkg.ErrClientNotConnected{} + } + + blockHash, err := chainhash.NewHashFromStr(hash) + if err != nil { + return nil, err + } + + return state.client.ListSinceBlock(blockHash) +} + +func (c *Client) ListTransactions(ctx context.Context, conState jsonrpc.State, label string) ([]btcjson.ListTransactionsResult, error) { + log.Debug().Msg("BTC: listing transactions for label %s") + + state := State(conState) + if state.client == nil { + return nil, pkg.ErrClientNotConnected{} + } + + return state.client.ListTransactions(label) +} + func (c *Client) CreateWallet(ctx context.Context, conState jsonrpc.State, args CreateWallet) (*btcjson.CreateWalletResult, error) { log.Debug().Msgf("BTC: creating wallet with name %s (AvoidReuse:%t, CreateBlackWallet:%t, DisablePrivateKeys:%t)", args.Name, args.AvoidReuse, args.CreateBlackWallet, args.DisablePrivateKeys) @@ -563,8 +623,12 @@ func (c *Client) CreateWallet(ctx context.Context, conState jsonrpc.State, args if state.client == nil { return nil, pkg.ErrClientNotConnected{} } + if args.Passphrase == "" { + return nil, errors.New("passphrase cannot be empty") + } options := []btcRpcClient.CreateWalletOpt{} + options = append(options, btcRpcClient.WithCreateWalletPassphrase(args.Passphrase)) if args.DisablePrivateKeys { options = append(options, btcRpcClient.WithCreateWalletDisablePrivateKeys()) } @@ -574,13 +638,21 @@ func (c *Client) CreateWallet(ctx context.Context, conState jsonrpc.State, args if args.CreateBlackWallet { options = append(options, btcRpcClient.WithCreateWalletBlank()) } - if args.Passphrase != "" { - options = append(options, btcRpcClient.WithCreateWalletPassphrase(args.Passphrase)) - } return state.client.CreateWallet(args.Name, options...) } +func (c *Client) SetTxFee(tx context.Context, conState jsonrpc.State, fee btcutil.Amount) error { + log.Debug().Msg("BTC: setting transaction fee") + + state := State(conState) + if state.client == nil { + return nil + } + + return state.client.SetTxFee(fee) +} + func (c *Client) Move(ctx context.Context, conState jsonrpc.State, args Move) (bool, error) { log.Debug().Msgf("BTC: moving %d from account %s to %s", args.Amount, args.FromAccount, args.ToAccount) @@ -596,5 +668,4 @@ func (c *Client) Move(ctx context.Context, conState jsonrpc.State, args Move) (b return state.client.MoveMinConf(args.FromAccount, args.ToAccount, args.Amount, args.MinConfirmations) } return state.client.Move(args.FromAccount, args.ToAccount, args.Amount) - } From 0c7b4b37e18e40f3147b722077c806ab135f71c8 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Tue, 9 May 2023 09:27:51 +0200 Subject: [PATCH 2/4] Extra documentation, fixed loading wallet --- examples/btc/main_btc.v | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/btc/main_btc.v b/examples/btc/main_btc.v index 6cb86b46f..e14416718 100644 --- a/examples/btc/main_btc.v +++ b/examples/btc/main_btc.v @@ -42,6 +42,8 @@ fn execute_rpcs(mut client RpcWsClient, mut logger log.Logger, host string, user logger.info("Result of creating wallet: ${result}") */ + btc_client.load_wallet("mywallet2")! + wallet_info := btc_client.get_wallet_info()! logger.info("Wallet info: ${wallet_info}") From 11d7e7c4915911fc12956ca2b53d6b5abef311cc Mon Sep 17 00:00:00 2001 From: brandonpille Date: Tue, 9 May 2023 09:57:54 +0200 Subject: [PATCH 3/4] Changes in client --- lib/btc/btc.v | 10 ++++----- server/pkg/btc/client.go | 46 +++++++++++++++++++++++++++++++++------- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/lib/btc/btc.v b/lib/btc/btc.v index 00ad05c54..219894ab1 100644 --- a/lib/btc/btc.v +++ b/lib/btc/btc.v @@ -15,10 +15,10 @@ mut: // configurations to load bitcoin client [params] pub struct Load { - host string - user string - pass string - wallet string + host string // the address of the node including the port (for example: 185.69.167.219:8332) + user string // the user name that you can use to connect to the btc node + pass string // the password needed to connect to the btc node + wallet string // optional, the wallet name you want to use for future calls (can be changed with the LoadWallet method) } // args to import bitcoin address @@ -283,7 +283,7 @@ pub fn (mut c BtcClient) get_received_by_label(label string) !Transaction { btc.default_timeout)! } -// Load a specific wallet before executing wallet specific requests +// Load a specific wallet. This is needed if you want to switch wallets or if you didn't pass any wallet name at Load time. pub fn (mut c BtcClient) load_wallet(wallet_name string) !LoadWalletResult { return c.client.send_json_rpc[[]string, LoadWalletResult]('btc.LoadWallet', [wallet_name], btc.default_timeout)! diff --git a/server/pkg/btc/client.go b/server/pkg/btc/client.go index b9a171261..34339840e 100644 --- a/server/pkg/btc/client.go +++ b/server/pkg/btc/client.go @@ -24,7 +24,8 @@ type ( } // state managed by nostr client btcState struct { - client *btcRpcClient.Client + client *btcRpcClient.Client + connectionSettings Load } Load struct { @@ -131,10 +132,8 @@ func NewClient() *Client { return &Client{} } -func (c *Client) Load(ctx context.Context, conState jsonrpc.State, args Load) error { - log.Debug().Msgf("BTC: connecting to btc node %s", args.Host) - - client, err := btcRpcClient.New( +func NewBtcRpcClient(args Load) (*btcRpcClient.Client, error) { + return btcRpcClient.New( &btcRpcClient.ConnConfig{ Host: args.Host + "/wallet/" + args.Wallet, User: args.User, @@ -142,11 +141,19 @@ func (c *Client) Load(ctx context.Context, conState jsonrpc.State, args Load) er HTTPPostMode: true, DisableTLS: true, }, nil) +} + +func (c *Client) Load(ctx context.Context, conState jsonrpc.State, args Load) error { + log.Debug().Msgf("BTC: connecting to btc node %s", args.Host) + + client, err := NewBtcRpcClient(args) if err != nil { return err } + state := State(conState) state.client = client + state.connectionSettings = args return nil } @@ -545,15 +552,38 @@ func (c *Client) GetReceivedByLabel(ctx context.Context, conState jsonrpc.State, return state.client.GetReceivedByAccount(label) } -func (c *Client) LoadWallet(ctx context.Context, conState jsonrpc.State, walletName string) (*btcjson.LoadWalletResult, error) { +func (c *Client) ImportWallet(ctx context.Context, conState jsonrpc.State, walletDump string) error { + log.Debug().Msg("BTC: importing wallet") + + state := State(conState) + if state.client == nil { + return pkg.ErrClientNotConnected{} + } + + return state.client.ImportWallet(walletDump) +} + +func (c *Client) LoadWallet(ctx context.Context, conState jsonrpc.State, walletName string) error { log.Debug().Msgf("BTC: loading wallet %s", walletName) state := State(conState) if state.client == nil { - return nil, pkg.ErrClientNotConnected{} + return pkg.ErrClientNotConnected{} } - return state.client.LoadWallet(walletName) + _, err := state.client.LoadWallet(walletName) + if err != nil && err.Error() != "-35: Wallet \""+walletName+"\" is already loaded." { + return err + } + + // Set the default wallet for future calls + state.connectionSettings.Wallet = walletName + client, err := NewBtcRpcClient(state.connectionSettings) + if err != nil { + return err + } + state.client = client + return nil } func (c *Client) GetWalletInfo(ctx context.Context, conState jsonrpc.State) (*btcjson.GetWalletInfoResult, error) { From 727df7efe281320dad967a307ee34f3a9414b7f1 Mon Sep 17 00:00:00 2001 From: brandonpille Date: Wed, 5 Jul 2023 11:23:04 +0200 Subject: [PATCH 4/4] Small changes --- examples/btc/main_btc.v | 12 ++++++-- server/pkg/btc/client.go | 62 ++++------------------------------------ 2 files changed, 16 insertions(+), 58 deletions(-) diff --git a/examples/btc/main_btc.v b/examples/btc/main_btc.v index e14416718..dd98811ef 100644 --- a/examples/btc/main_btc.v +++ b/examples/btc/main_btc.v @@ -38,7 +38,7 @@ fn execute_rpcs(mut client RpcWsClient, mut logger log.Logger, host string, user logger.info("Fee estimation: ${fee_estimation}") /* - result := btc_client.create_wallet(name:"mywallet2", passphrase:"mypassphrase")! + result := btc_client.create_wallet(name:"mywallet3", passphrase:"mypassphrase")! logger.info("Result of creating wallet: ${result}") */ @@ -60,12 +60,20 @@ fn execute_rpcs(mut client RpcWsClient, mut logger log.Logger, host string, user /* by_label := btc_client.list_received_by_label()! - logger.info("Received by label: ${by_label}") + logger.info("Received by label: ${by_label}") */ + /* peer_info := btc_client.get_peer_info()! logger.info("Peer info: ${peer_info}") + */ + + transactions := btc_client.list_transactions("*")! + logger.info("Transactions: ${transactions}") + + btc_client.get_received_by_label("")! + //btc_client.rename_account(old_account: "", new_account: "")! } fn main() { diff --git a/server/pkg/btc/client.go b/server/pkg/btc/client.go index 34339840e..93c26355c 100644 --- a/server/pkg/btc/client.go +++ b/server/pkg/btc/client.go @@ -71,12 +71,6 @@ type ( Mode btcjson.EstimateSmartFeeMode `json:"mode"` } - GenerateToAddress struct { - NumBlocks int64 `json:"num_blocks"` - Address string `json:"address"` - MaxTries int64 `json:"max_tries"` - } - GetChainTxStatsNBlocksBlockHash struct { AmountOfBlocks int32 `json:"amount_of_blocks"` BlockHashEnd string `json:"block_hash_end"` @@ -159,7 +153,7 @@ func (c *Client) Load(ctx context.Context, conState jsonrpc.State, args Load) er } func (c *Client) ImportAddress(ctx context.Context, conState jsonrpc.State, args ImportAddress) error { - log.Debug().Msgf("BTC: importing address %s with label %s (rescan: %t, p2sh: %s)", args.Address, args.Label, args.Rescan, args.P2SH) + log.Debug().Msgf("BTC: importing address %s with label %s (rescan: %t, p2sh: %t)", args.Address, args.Label, args.Rescan, args.P2SH) state := State(conState) if state.client == nil { @@ -196,6 +190,7 @@ func (c *Client) ImportPubKey(ctx context.Context, conState jsonrpc.State, args return state.client.ImportPubKeyRescan(args.PubKey, args.Rescan) } +// TODO ListAccounts is deprecated: it should be modified once the following issue is done: https://github.com/btcsuite/btcd/issues/1974 func (c *Client) ListLabels(ctx context.Context, conState jsonrpc.State) (map[string]btcutil.Amount, error) { log.Debug().Msg("BTC: listing labels") @@ -207,6 +202,7 @@ func (c *Client) ListLabels(ctx context.Context, conState jsonrpc.State) (map[st return state.client.ListAccounts() } +// TODO RenameAccount is deprecated: it should be modified once the following issue is done: https://github.com/btcsuite/btcd/issues/1974 func (c *Client) RenameAccount(ctx context.Context, conState jsonrpc.State, args RenameAccount) error { log.Debug().Msgf("BTC: renaming account from %s to %s", args.OldAccount, args.NewAccount) @@ -244,7 +240,7 @@ func (c *Client) SendToAddress(ctx context.Context, conState jsonrpc.State, args } func (c *Client) EstimateSmartFee(ctx context.Context, conState jsonrpc.State, args EstimateSmartFee) (*btcjson.EstimateSmartFeeResult, error) { - log.Debug().Msgf("BTC: estimating smart fee for %s blocks with estimation mode %s", args.ConfTarget, args.Mode) + log.Debug().Msgf("BTC: estimating smart fee for %d blocks with estimation mode %s", args.ConfTarget, args.Mode) state := State(conState) if state.client == nil { @@ -266,43 +262,6 @@ func hashesToStrings(hashes []*chainhash.Hash) []string { return blockHashes } -func (c *Client) GenerateBlocks(ctx context.Context, conState jsonrpc.State, numBlocks uint32) ([]string, error) { - log.Debug().Msgf("BTC: generating %d blocks", numBlocks) - - state := State(conState) - if state.client == nil { - return nil, pkg.ErrClientNotConnected{} - } - - hashes, err := state.client.Generate(numBlocks) - if err != nil { - return []string{}, err - } - - return hashesToStrings(hashes), err -} - -func (c *Client) GenerateBlocksToAddress(ctx context.Context, conState jsonrpc.State, args GenerateToAddress) ([]string, error) { - log.Debug().Msgf("BTC: generating %d blocks for address %s", args.NumBlocks, args.Address) - - state := State(conState) - if state.client == nil { - return nil, pkg.ErrClientNotConnected{} - } - - address, err := btcutil.DecodeAddress(args.Address, nil) - if err != nil { - return nil, err - } - - hashes, err := state.client.GenerateToAddress(args.NumBlocks, address, &args.MaxTries) - if err != nil { - return nil, err - } - - return hashesToStrings(hashes), err -} - func (c *Client) SetLabel(ctx context.Context, conState jsonrpc.State, args SetLabel) error { log.Debug().Msgf("BTC: setting label on address %s: %s", args.Address, args.Label) @@ -541,6 +500,7 @@ func (c *Client) GetRawTransaction(ctx context.Context, conState jsonrpc.State, return state.client.GetRawTransaction(txHashDecoded) } +// TODO GetReceivedByAccount is deprecated: it should be modified once the following issue is done: https://github.com/btcsuite/btcd/issues/1974 func (c *Client) GetReceivedByLabel(ctx context.Context, conState jsonrpc.State, label string) (btcutil.Amount, error) { log.Debug().Msgf("BTC: getting amount received by label %s", label) @@ -552,17 +512,6 @@ func (c *Client) GetReceivedByLabel(ctx context.Context, conState jsonrpc.State, return state.client.GetReceivedByAccount(label) } -func (c *Client) ImportWallet(ctx context.Context, conState jsonrpc.State, walletDump string) error { - log.Debug().Msg("BTC: importing wallet") - - state := State(conState) - if state.client == nil { - return pkg.ErrClientNotConnected{} - } - - return state.client.ImportWallet(walletDump) -} - func (c *Client) LoadWallet(ctx context.Context, conState jsonrpc.State, walletName string) error { log.Debug().Msgf("BTC: loading wallet %s", walletName) @@ -597,6 +546,7 @@ func (c *Client) GetWalletInfo(ctx context.Context, conState jsonrpc.State) (*bt return state.client.GetWalletInfo() } +// TODO ListReceivedByAccount is deprecated: it should be modified once the following issue is done: https://github.com/btcsuite/btcd/issues/1974 func (c *Client) ListReceivedByLabel(ctx context.Context, conState jsonrpc.State) ([]btcjson.ListReceivedByAccountResult, error) { log.Debug().Msg("BTC: listing received transactions by label")