Bitcoin Forum

Bitcoin => Development & Technical Discussion => Topic started by: sudonims on March 28, 2022, 01:47:34 PM



Title: Get UTXOs of a wallet...
Post by: sudonims on March 28, 2022, 01:47:34 PM
I'm trying to work on something and I need a programmatic way to get UTXOs related to a certain wallet...


e.g, if I run a btc-core node with wallet A having addresses [A1,...,A10], then I want a programmatic way to get a set of all UTXOs related to wallet A i.e for addresses [A1,...,A10]... Preferrebly in C++... Thanks in advance...


Title: Re: Get UTXOs of a wallet...
Post by: BlackHatCoiner on March 28, 2022, 01:53:35 PM
In Bitcoin Core you can use:
Code:
bitcoin-cli listunspent
(dev link (https://developer.bitcoin.org/reference/rpc/listunspent.html))

in3rsha has also written this: bitcoin-utxo-dump (https://github.com/in3rsha/bitcoin-utxo-dump). (In Go language)


Title: Re: Get UTXOs of a wallet...
Post by: n0nce on March 28, 2022, 01:56:19 PM
I'm trying to work on something and I need a programmatic way to get UTXOs related to a certain wallet...


e.g, if I run a btc-core node with wallet A having addresses [A1,...,A10], then I want a programmatic way to get a set of all UTXOs related to wallet A i.e for addresses [A1,...,A10]... Preferrebly in C++... Thanks in advance...
I can't provide you code right now, but these are the RPC commands that you should look into:

Wallet RPCs (https://developer.bitcoin.org/reference/rpc/#wallet-rpcs)

I also found this resource (https://upcoder.com/7/bitcoin-rpc-from-python/) that shows how to call RPC methods from Python using simple network requests. Should be easy to follow along and code it in C++.

Code: (https://upcoder.com/7/bitcoin-rpc-from-python/)
~snip~
headers = {'content-type': 'application/json'}
payload = json.dumps({"method": 'getblock', "params": ["0000000000005e5fd51f764d230441092f1b69d1a1eeab334c5bb32412e8dc51"], "jsonrpc": "2.0"})
response = requests.get(serverURL, headers=headers, data=payload)
~snip~


Title: Re: Get UTXOs of a wallet...
Post by: sudonims on March 28, 2022, 01:57:42 PM
In Bitcoin Core you can use:
Code:
bitcoin-cli listunspent
(dev link (https://developer.bitcoin.org/reference/rpc/listunspent.html))

in3rsha has also written this: bitcoin-utxo-dump (https://github.com/in3rsha/bitcoin-utxo-dump). (In Go language)

Thanks... I will check it out...


Title: Re: Get UTXOs of a wallet...
Post by: n0nce on March 28, 2022, 02:46:13 PM
Okay, I had a few minutes and threw something together using this one-file HTTP lib for C++ (https://github.com/yhirose/cpp-httplib/) and the example in Python I linked to, earlier (https://upcoder.com/7/bitcoin-rpc-from-python/).
It's not fully working yet, but it should be a good starting point. :)

It might actually run, honestly, just don't have RPC credentials handy right now.
This leverages the built-in local HTTP API that Bitcoin Core offers. If you want to go through the RPC interface, that should also work, but I believe this is much easier by avoiding permission issues to the RPC interface for instance.

Code:
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "./httplib.h"

int main() {
  int rpcPort = 8332;
  std::string rpcUser = "bitcoinrpc";
  std::string rpcPassword = "";
  std::string serverURL = "http://" + rpcUser + ":" + rpcPassword + "@localhost:" + std::to_string(rpcPort);

  httplib::Headers headers = {{"content-type", " application/json"}};
  std::string body = "{'method': 'getblock', 'params': ['0000000000005e5fd51f764d230441092f1b69d1a1eeab334c5bb32412e8dc51'], 'jsonrpc': '2.0'}";

  httplib::Client client(serverURL);

  if (auto res = client.Post("", headers, body, "application/json")) {
    std::cout << res->status << std::endl;
    std::cout << res->body << std::endl;
  } else {
    auto err = res.error();
    std::cout << "[Error]: " <<  err << std::endl;
  }
}

Compile & link right libraries: g++ -lssl -lcrypto bitcointest.cpp -o bitcointest


Edit: There's some issues with this, especially if your node is configured to use a cookie file like mine.

I checked the bitcoin-cli source code and we can see that the cookie content is passed in the HTTP request header.
This function call (https://github.com/bitcoin/bitcoin/blob/3c565302aaa91ae46aa09b8aeb95206711d9d8a6/src/bitcoin-cli.cpp#L759) retrieves the cookie's contents and stores them in strRPCUserColonPass.

This line (https://github.com/bitcoin/bitcoin/blob/3c565302aaa91ae46aa09b8aeb95206711d9d8a6/src/bitcoin-cli.cpp#L771) adds its BASE64-encoding to the Authentication header.
Code:
evhttp_add_header(output_headers, "Authorization", (std::string("Basic ") + EncodeBase64(strRPCUserColonPass)).c_str());

When comparing to the Python project I linked earlier, make sure not to use a GET request, but POST instead, as evidenced by this line (https://github.com/bitcoin/bitcoin/blob/3c565302aaa91ae46aa09b8aeb95206711d9d8a6/src/bitcoin-cli.cpp#L790) (EVHTTP_REQ_POST).


Title: Re: Get UTXOs of a wallet...
Post by: n0nce on March 30, 2022, 05:29:33 PM
Alright guys, I got it working with the .cookie file.
As we can see from Bitcoin Core code (https://github.com/bitcoin/bitcoin/blob/3c565302aaa91ae46aa09b8aeb95206711d9d8a6/src/bitcoin-cli.cpp#L771), the cookie file specifies a HTTP Basic Auth user named __cookie__ and a random password that changes after each restart of Bitcoin Core.
It is also explained quite well here on StackOverflow (https://bitcoin.stackexchange.com/questions/46782/rpc-cookie-authentication/80969#80969): __cookie__ is the basic auth username and abc123 is a randomly generated password.

Therefore, it's enough to add those values to a normal HTTP request.
A MVP example using cURL would be (replacing xxxxx with actual content from the .cookie file):
Code:
curl --user __cookie__:xxxxx -X POST -H 'Content-Type: application/json' -H 'X-Auth-Token: undefined' --data-raw '{"id":1, "jsonrpc":"2.0", "method":"getblockchaininfo"}' http://127.0.0.1:8332

It correctly returns:
Code:
{
  "result": {
    "chain": "main",
    "blocks": 729713,
    "headers": 729713,
    "bestblockhash": "000000000000000000000fbfac1a91cdeaf64d689f7673d02613da9d10bfb284",
    "difficulty": 27452707696466.39,
    "mediantime": 1648651391,
    "verificationprogress": 0.9999999227202188,
    "initialblockdownload": false,
    "chainwork": "00000000000000000000000000000000000000002b2a9f5f3ef98696e155655c",
    "size_on_disk": 452426899872,
    "pruned": false,
    "softforks": {
      "bip34": {
        "type": "buried",
        "active": true,
        "height": 227931
      },
      "bip66": {
        "type": "buried",
        "active": true,
        "height": 363725
      },
      "bip65": {
        "type": "buried",
        "active": true,
        "height": 388381
      },
      "csv": {
        "type": "buried",
        "active": true,
        "height": 419328
      },
      "segwit": {
        "type": "buried",
        "active": true,
        "height": 481824
      },
      "taproot": {
        "type": "bip9",
        "bip9": {
          "status": "active",
          "start_time": 1619222400,
          "timeout": 1628640000,
          "since": 709632,
          "min_activation_height": 709632
        },
        "height": 709632,
        "active": true
      }
    },
    "warnings": ""
  },
  "error": null,
  "id": 1
}

Working Python3 code:
Code:
import requests, json

rpcPort = 8332
rpcUser = '__cookie__'
rpcPassword = 'xxxxx'
serverURL = 'http://' + rpcUser + ':' + rpcPassword + '@localhost:' + str(rpcPort)

headers = {'Content-Type': 'application/json'}
payload = json.dumps({'id': 1, 'method': 'getblockchaininfo', 'jsonrpc': '2.0'})

print(f'serverURL: {serverURL}')
print(f'payload: {payload}')

try:
  response = requests.post(serverURL, headers=headers, data=payload)
  print(response)
  print(response.status_code)
  print(response.reason)

  print(response.text)
except requests.exceptions.HTTPError as e:
  print(e)
  print(e.status_code)
  print(e.reason)

My C++ version which also works fine and just requires that httplib.h file to be downloaded and placed in the same folder:
Code:
#include "./httplib.h"

int main() {
  int rpcPort = 8332;
  const char* rpcUser     = "__cookie__";
  const char* rpcPassword = "8abb90c7902e13105976a94ff5abbc4c9526f745235438bccc8b23aa2d9df0b3";
  std::string serverURL   = "127.0.0.1";
  std::string body        = "{\"id\":1, \"method\":\"getblockchaininfo\", \"jsonrpc\":\"2.0\"}";

  httplib::Client cli(serverURL, rpcPort);
  cli.set_basic_auth(rpcUser, rpcPassword);

  if (auto res = cli.Post("/", body, "application/json")) {
    std::cout << res->status << std::endl;
    std::cout << res->body << std::endl;
  }
  else {
    auto err = res.error();
    std::cout << "Error: " <<  err << std::endl;
  }
}

Code:
$~/test/cppbitcointest> g++ -l:libcrypto.so.1.1 bitcointest.cpp -o bitcointest && ./bitcointest 
200
{"result":{"chain":"main","blocks":729726,"headers":729726,"bestblockhash":"00000000000000000007f5b05ab0ce3816fcf0c6593b3aa11dc062d4ecf3142e","difficulty":27452707696466.39,"mediantime":1648656001,"verificationprogress":0.9999907234912998,"initialblockdownload":false,"chainwork":"00000000000000000000000000000000000000002b2be3f65323c1f032368092","size_on_disk":452447125068,"pruned":false,"softforks":{"bip34":{"type":"buried","active":true,"height":227931},"bip66":{"type":"buried","active":true,"height":363725},"bip65":{"type":"buried","active":true,"height":388381},"csv":{"type":"buried","active":true,"height":419328},"segwit":{"type":"buried","active":true,"height":481824},"taproot":{"type":"bip9","bip9":{"status":"active","start_time":1619222400,"timeout":1628640000,"since":709632,"min_activation_height":709632},"height":709632,"active":true}},"warnings":""},"error":null,"id":1}

I believe the RPC you're looking for is listunspent (https://developer.bitcoin.org/reference/rpc/listunspent.html).
By replacing the body in the above code examples with the following, it should return what you're looking for.
Code:
'{"jsonrpc": "1.0", "id": "curltest", "method": "listunspent", "params": [6, 9999999]}'