Add available field to listwallets RPC #11485

pull meshcollider wants to merge 3 commits into bitcoin:master from meshcollider:201710_listwallets_available changing 3 files +50 −12
  1. meshcollider commented at 11:24 am on October 11, 2017: contributor

    Modifies listwallets to return a list of ‘available’ wallets in the wallet directory, by looking for the BDB magic bytes (0x00053162 source: https://github.com/file/file/blob/master/magic/Magdir/database) .

    This is a breaking change to listwallets, but multiwallet RPC calls are experimental in 0.15 so shouldn’t be a big concern to change it. c.f. @jnewbery’s comment here. Split from #11466.

    Would be great if someone could confirm if this is endianness independent too :)

  2. fanquake added the label RPC/REST/ZMQ on Oct 11, 2017
  3. promag commented at 1:40 pm on October 11, 2017: member

    As said in #11466 (review), a different interface is:

    0{
    1  "wallets": [
    2    { "name": "foo", "loaded": true },
    3    { "name": "bar", "loaded": false }
    4  ]
    5}
    

    Which has the advantage of allowing to add new attributes later.

    Another option is to have a different RPC. So:

    • listwallets - returns a lists of active/loaded wallets
    • findwallets - returns a list of all possible/available wallets.

    This has the advantage that each RPC has it’s own purpose and implementation, which happen to be very different. For those that manage (keep record of) the wallets externally findwallets is of no interest, only listwallets.

  4. jnewbery commented at 2:14 pm on October 11, 2017: member
    concept ACK splitting this from #11466. And concept ACK using the BDB magic bytes rather than filename
  5. meshcollider renamed this:
    [WIP] Add `available` field to listwallets RPC
    Add `available` field to listwallets RPC
    on Oct 13, 2017
  6. meshcollider commented at 9:10 am on October 13, 2017: contributor
    Removed WIP, now uses BDB magic bytes, although I haven’t confirmed that this works on big endian or also potentially on old versions of BDB wallets if the magic bytes changed
  7. in src/wallet/rpcwallet.cpp:2659 in 0e961a28a9 outdated
    2656+    for (fs::directory_iterator it(walletdir); it != fs::directory_iterator(); ++it) {
    2657+        std::ifstream file(it->path().string(), std::ios::binary);
    2658+        file.seekg(12, std::ios::beg); // Magic bytes start at offset 12
    2659+        unsigned char fileBytes[4] = {0};
    2660+        file.read((char*)fileBytes, sizeof(fileBytes)); // Read 4 bytes of file to compare against magic
    2661+        if (is_regular_file(*it) && file.is_open() && memcmp(fileBytes, bdb_magic, sizeof(bdb_magic)) == 0) {
    


    jnewbery commented at 9:17 pm on October 13, 2017:

    Please explicitly use fs::is_regular_file() (this only compiles because of Argument Dependant Lookups http://en.cppreference.com/w/cpp/language/adl which makes it difficult to understand where this function is coming from).

    boost::filesystem::is_regular_file() will return true if called on a symlink to a regular file. We don’t want those to show up in the available list since they can’t be loaded as a wallet, so you should extend this if test with fs::is_symlink().

  8. jnewbery commented at 9:18 pm on October 13, 2017: member

    Looks good. Can you extend the checking to exclude symlinks from the available wallets list, and add a test to cover that?

    I think it might also be a good idea to exclude currently loaded wallets from the available list (ie the loaded and available lists should be mutually exclusive)

  9. jnewbery commented at 9:34 pm on October 13, 2017: member

    I also tested manually that files show up as wallets when bytes 12-15 match the magic bdb bytes:

     0 bitcoin-cli listwallets
     1{
     2  "loaded": [
     3    "wallet.dat"
     4  ],
     5  "available": [
     6    "wallet.dat"
     7  ]
     8}
     9[ubuntu] /home/ubuntu/.bitcoin/regtest
    10 bitcoin-cli stop
    11Bitcoin server stopping
    12[ubuntu] /home/ubuntu/.bitcoin/regtest
    13 for i in {1..20}; do head wallet.dat -c $i > wallet_prefix_$i; done
    14[ubuntu] /home/ubuntu/.bitcoin/regtest
    15 bitcoind
    16Bitcoin server starting
    17[ubuntu] /home/ubuntu/.bitcoin/regtest
    18 bitcoin-cli listwallets
    19{
    20  "loaded": [
    21    "wallet.dat"
    22  ],
    23  "available": [
    24    "wallet_prefix_20",
    25    "wallet_prefix_19",
    26    "wallet.dat",
    27    "wallet_prefix_16",
    28    "wallet_prefix_17",
    29    "wallet_prefix_15",
    30    "wallet_prefix_18"
    31  ]
    32}
    
  10. meshcollider commented at 10:27 pm on October 13, 2017: contributor
    Addressed @jnewbery nits and made available list mutually exclusive to loaded list. Thanks for the review :-)
  11. in src/wallet/rpcwallet.cpp:2774 in 8f2df4ae01 outdated
    2644@@ -2645,10 +2645,38 @@ UniValue listwallets(const JSONRPCRequest& request)
    2645 
    2646         LOCK(pwallet->cs_wallet);
    2647 
    2648-        obj.push_back(pwallet->GetName());
    2649+        loaded.push_back(pwallet->GetName());
    2650     }
    


    promag commented at 10:40 pm on October 13, 2017:

    Suggestion to remove loop below:

    0std::set<std::string> loaded_set;
    1...
    2    loaded.push_back(pwallet->GetName());
    3    loaded_set.insert(pwallet->GetName())
    4}
    5...
    6if (loaded_set.count(it->path().filename().string()) == 0) {
    7    available.push_back(it->path().filename().string());
    8}
    
  12. meshcollider commented at 10:54 pm on October 13, 2017: contributor
    Good idea @promag, thanks :-)
  13. in src/wallet/rpcwallet.cpp:2664 in 95e108b17f outdated
    2661+        file.seekg(12, std::ios::beg); // Magic bytes start at offset 12
    2662+        unsigned char fileBytes[4] = {0};
    2663+
    2664+        file.read((char*)fileBytes, sizeof(fileBytes)); // Read 4 bytes of file to compare against magic
    2665+
    2666+        if (fs::is_regular_file(*it) && !fs::is_symlink(*it) && file.is_open() && memcmp(fileBytes, bdb_magic, sizeof(bdb_magic)) == 0) {
    


    promag commented at 0:59 am on October 22, 2017:

    Some of these conditions can be moved before seekg (just continue. Only the compare is needed.

    Could use 64 bit int instead?


    meshcollider commented at 2:43 am on October 22, 2017:
    Fixed the conditional, but IMO the char array is clearer and perhaps safer than using an int :)
  14. meshcollider commented at 6:41 am on October 22, 2017: contributor
    Rebased on master to fix travis failure due to multiwallet.py being modified
  15. in src/wallet/rpcwallet.cpp:2675 in da994d87ad outdated
    2672+            if (loaded_set.count(it->path().filename().string()) == 0) {
    2673+                available.push_back(it->path().filename().string());
    2674+            }
    2675+        }
    2676+
    2677+        file.close();
    


    promag commented at 8:58 pm on October 23, 2017:
  16. in src/wallet/rpcwallet.cpp:2668 in da994d87ad outdated
    2665+
    2666+        file.seekg(12, std::ios::beg); // Magic bytes start at offset 12
    2667+        unsigned char fileBytes[4] = {0};
    2668+        file.read((char*)fileBytes, sizeof(fileBytes)); // Read 4 bytes of file to compare against magic
    2669+
    2670+        if (memcmp(fileBytes, bdb_magic, sizeof(bdb_magic)) == 0) {
    


    promag commented at 9:02 pm on October 23, 2017:

    Alternative, avoid nesting:

    0if (memcmp(...) != 0) continue;
    1
    2if (loaded_set.count(filename) > 0) continue;
    3
    4available.push_back(filename);
    

    Note that close is automatically called, see comment below.


    promag commented at 9:03 pm on October 23, 2017:

    Nit, add:

    0const std::string filename = it->path().filename().string();
    
  17. in src/wallet/rpcwallet.cpp:2665 in da994d87ad outdated
    2662+
    2663+        std::ifstream file(it->path().string(), std::ios::binary);
    2664+        if(!file.is_open()) continue;
    2665+
    2666+        file.seekg(12, std::ios::beg); // Magic bytes start at offset 12
    2667+        unsigned char fileBytes[4] = {0};
    


    promag commented at 9:04 pm on October 23, 2017:
    Nit: file_bytes.
  18. in src/wallet/rpcwallet.cpp:2658 in da994d87ad outdated
    2655+    UniValue available(UniValue::VARR);
    2656+    fs::path walletdir = GetDataDir();
    2657+    const unsigned char bdb_magic[4] = {0x62, 0x31, 0x05, 0x00}; // Berkeley DB Btree magic bytes
    2658+
    2659+    for (fs::directory_iterator it(walletdir); it != fs::directory_iterator(); ++it) {
    2660+
    


    promag commented at 9:04 pm on October 23, 2017:
    Nit, remove empty line.
  19. in test/functional/multiwallet.py:24 in da994d87ad outdated
    18@@ -19,7 +19,7 @@ def set_test_params(self):
    19         self.extra_args = [['-wallet=w1', '-wallet=w2', '-wallet=w3']]
    20 
    21     def run_test(self):
    22-        assert_equal(set(self.nodes[0].listwallets()), {"w1", "w2", "w3"})
    23+        assert_equal(set(self.nodes[0].listwallets()['loaded']), {"w1", "w2", "w3"})
    24 
    


    promag commented at 9:10 pm on October 23, 2017:
    Assert available is empty array?
  20. meshcollider commented at 0:31 am on October 24, 2017: contributor

    Fixed @promag’s nits, thanks :)

    BTW it would be good to have #11466 reviewed first because this is only really useful with that IMO

  21. meshcollider commented at 2:49 am on November 19, 2017: contributor
    Rebased and updated to work with new wallets directory from #11466
  22. MarcoFalke commented at 4:55 pm on November 27, 2017: member
    Needs rebase, but I assume this won’t work after #11687 anyway?
  23. laanwj commented at 8:10 am on November 28, 2017: member

    Needs rebase, but I assume this won’t work after #11687 anyway?

    It won’t work if someone uses external wallets, but it should still work if they have all wallets in the wallet directory? (which is the default)

  24. Add release notes for listwallets RPC change 87e3923907
  25. Add available BDB list to listwallets RPC 946b2ceb1a
  26. meshcollider commented at 0:21 am on November 29, 2017: contributor
    I’ve rebased. If #11687 defaults to creating directories for each wallet rather than individual BDB files, then this may need to be modified to list directories as well as BDB files? But yes should still work
  27. in test/functional/multiwallet.py:67 in 946b2ceb1a outdated
    63@@ -63,6 +64,10 @@ def run_test(self):
    64 
    65         self.start_node(0, self.extra_args[0])
    66 
    67+        # ensure listwallets only names BDB files, not symlinks, other files or directories
    


    jnewbery commented at 7:21 pm on November 29, 2017:

    I like this new test, but I think it can be improved in a couple of ways:

    • test that a non-empty file without the magic bytes is excluded
    • there’s an implicit assumption that the symlink and folder created earlier in the test are still around. You could explicitly assert that. If the earlier parts of this test get change, eg the file names are changed, then this check will continue to succeed but won’t be testing anything.

    What do you think about:

     0--- a/test/functional/multiwallet.py
     1+++ b/test/functional/multiwallet.py
     2@@ -65,8 +65,20 @@ class MultiWalletTest(BitcoinTestFramework):
     3         self.start_node(0, self.extra_args[0])
     4 
     5         # ensure listwallets only names BDB files, not symlinks, other files or directories
     6-        open(os.path.join(wallet_dir, 'w01.dat'), 'a').close()
     7-        assert_equal(set(self.nodes[0].listwallets()['available']), {"w22"})
     8+        with open(os.path.join(wallet_dir, 'w01.dat'), 'a') as wallet_file:
     9+            # Create a file containing a long string
    10+            print("a" * 2000, file=wallet_file)
    11+
    12+        available_wallets = self.nodes[0].listwallets()['available']
    13+
    14+        assert os.path.islink(os.path.join(wallet_dir, 'w12'))
    15+        assert 'w12' not in available_wallets
    16+
    17+        assert os.path.isdir(os.path.join(wallet_dir, 'w11'))
    18+        assert 'w11' not in available_wallets
    19+
    20+        assert os.path.isfile(os.path.join(wallet_dir, 'w01.dat'))
    21+        assert 'w01.dat' not in available_wallets
    
  28. jnewbery commented at 7:22 pm on November 29, 2017: member
    Tested ACK 946b2ceb1a8c6702fc3412b63f42439bedbafe0c. One suggested change for the new test.
  29. in src/wallet/rpcwallet.cpp:2752 in 946b2ceb1a outdated
    2748@@ -2749,16 +2749,17 @@ UniValue listwallets(const JSONRPCRequest& request)
    2749             "Returns a list of currently loaded wallets.\n"
    2750             "For full information on the wallet, use \"getwalletinfo\"\n"
    2751             "\nResult:\n"
    2752-            "[                         (json array of strings)\n"
    2753-            "  \"walletname\"            (string) the wallet name\n"
    2754-            "   ...\n"
    2755-            "]\n"
    2756+            "{\n"
    


    TheBlueMatt commented at 6:09 pm on December 4, 2017:
    Need to update the docs above. Maybe also note that false-positives may include files from exceedingly old (pre-0.8, I believe) nodes as well as duplicates of wallets which cannot be opened simultaneously.
  30. TheBlueMatt commented at 6:21 pm on December 4, 2017: member
    Code looks good, RPC docs could be more descriptive (and are out-of-date after the change).
  31. Fix listwallets documentation 3dac78f6f0
  32. meshcollider commented at 10:57 pm on December 12, 2017: contributor
    Update the documentation as per @TheBlueMatt suggestion
  33. in doc/release-notes.md:94 in 87e3923907 outdated
    90@@ -91,6 +91,10 @@ Low-level RPC changes
    91 - The wallet RPC `getreceivedbyaddress` will return an error if called with an address not in the wallet.
    92 
    93 
    94+- `listwallets` now returns two arrays of wallet names, one listing the `loaded` wallets
    


    ryanofsky commented at 3:05 pm on December 18, 2017:

    I don’t think it’s a big deal to break backwards compatibility here, but I also don’t see any benefit in doing so. I’d think that a better approach to overloading the listwallets function would be to add a new listwalletdir function that would return a list of wallet files inside -walletdir. Advantages to this approach:

    1. Backwards compatible
    2. Better performance for listwallets. It could continue to just return the loaded wallets from memory instead of having now having to do disk io.
    3. More descriptive function name. listwalletdir naming is consistent with recently added walletdir parameter, and tells you what the function actually does.
  34. in src/wallet/rpcwallet.cpp:2754 in 946b2ceb1a outdated
    2753-            "  \"walletname\"            (string) the wallet name\n"
    2754-            "   ...\n"
    2755-            "]\n"
    2756+            "{\n"
    2757+            "  \"loaded\":     [ \"walletname\" ... ] (json array of strings) Names of loaded wallets\n"
    2758+            "  \"available\":  [ \"walletname\" ... ] (json array of strings) Names of BDB files in wallet directory (may not always actually be wallets)\n"
    


    ryanofsky commented at 3:05 pm on December 18, 2017:
    Would suggest returning an array of objects rather than an array of strings to future proof this. This would allow including other information like sizes, last modification times, whether databases are flushed, or locked by another process. (Latter two would be meaningful after #11687 when wallet files can be opened in their own berkelydb environments instead of all using the same shared environment).

    promag commented at 10:19 pm on December 18, 2017:
    Partially agree with @ryanofsky. Still think one array is enough, see #11485 (comment). And listwallets could have an optional parameter to filter available, loaded or both.
  35. in src/wallet/rpcwallet.cpp:2783 in 946b2ceb1a outdated
    2780+    const unsigned char bdb_magic[4] = {0x62, 0x31, 0x05, 0x00}; // Berkeley DB Btree magic bytes
    2781+
    2782+    for (fs::directory_iterator it(walletdir); it != fs::directory_iterator(); ++it) {
    2783+        if (!fs::is_regular_file(*it) || fs::is_symlink(*it)) continue;
    2784+
    2785+        std::ifstream file(it->path().string(), std::ios::binary);
    


    ryanofsky commented at 3:25 pm on December 18, 2017:

    Note: to be compatible with #11687, you would drop the continue above and change this line to something like:

    0std::ifstream file(fs::is_regular_file(*it) ? it->path() : (it->path() / "wallet.dat")).string(), std::ios::binary);
    
  36. ryanofsky commented at 3:27 pm on December 18, 2017: member
    utACK 3dac78f6f012196e3a4351f9a60df2fd03596f7d
  37. ryanofsky commented at 8:50 pm on January 8, 2018: member
    Might be ready for merge. Unclear if “Code looks good” from @TheBlueMatt above is equivalent to a code ack.
  38. meshcollider commented at 10:11 pm on January 8, 2018: contributor
    @ryanofsky sorry I haven’t had time to work on your suggested changes above, I’m happy to let this wait until I’ve had time to address them
  39. MarcoFalke added the label Needs rebase on Jun 6, 2018
  40. laanwj added the label Up for grabs on Aug 31, 2018
  41. laanwj commented at 12:28 pm on August 31, 2018: member
    Closing for now w/ up for grabs, let me know when you start working on this and it needs to be reopened.
  42. laanwj closed this on Aug 31, 2018

  43. jnewbery commented at 2:20 pm on August 31, 2018: member

    Shame this has gone stale. I think it’s useful new functionality.

    I’d be very happy to re-review if someone picked this up again.

  44. promag commented at 10:02 pm on September 21, 2018: member

    I’m tempted to pick this as this is also useful in the UI — must be exposed in interfaces::Nodes.

    As suggested by @Sjors in #13100#pullrequestreview-135815243

    For opening an existing wallet I have a light preference for just scanning the default directory for wallets and showing a list of wallets to choose from. This also prevents confusion around wallets that are files vs. wallets that are directories. Offering multiple wallets and offering the ability to put wallets anywhere in the filesystem are two different features.

    Thoughts?

  45. promag referenced this in commit 56b16712f3 on Sep 21, 2018
  46. promag referenced this in commit 61fce73d0e on Sep 21, 2018
  47. promag referenced this in commit 90adb2cd61 on Sep 24, 2018
  48. promag referenced this in commit c3acd2b690 on Sep 26, 2018
  49. promag referenced this in commit ce27b39105 on Oct 17, 2018
  50. promag referenced this in commit fc4db35bfd on Oct 18, 2018
  51. laanwj referenced this in commit 8eb2cd1dda on Oct 18, 2018
  52. jfhk referenced this in commit 53a3443035 on Nov 14, 2018
  53. JeremyRubin referenced this in commit 621f85ec61 on Nov 24, 2018
  54. HashUnlimited referenced this in commit 7275b87bd0 on Nov 26, 2018
  55. laanwj removed the label Needs rebase on Oct 24, 2019
  56. jarolrod commented at 2:41 am on May 8, 2021: member
    @fanquake This shouldn’t be Up for grabs anymore. The main idea behind this PR was implemented in #14291.
  57. fanquake removed the label Up for grabs on May 8, 2021
  58. 5tefan referenced this in commit 31d09b36e1 on Aug 13, 2021
  59. 5tefan referenced this in commit f9498d0051 on Aug 14, 2021
  60. gades referenced this in commit 291b84c248 on May 31, 2022
  61. DrahtBot locked this on Aug 16, 2022

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2024-07-05 22:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me