-
Notifications
You must be signed in to change notification settings - Fork 36.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add available
field to listwallets RPC
#11485
Add available
field to listwallets RPC
#11485
Conversation
As said in #11466 (comment), a different interface is: {
"wallets": [
{ "name": "foo", "loaded": true },
{ "name": "bar", "loaded": false }
]
} Which has the advantage of allowing to add new attributes later. Another option is to have a different RPC. So:
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 |
concept ACK splitting this from #11466. And concept ACK using the BDB magic bytes rather than filename |
available
field to listwallets RPCavailable
field to listwallets RPC
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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)
src/wallet/rpcwallet.cpp
Outdated
file.seekg(12, std::ios::beg); // Magic bytes start at offset 12 | ||
unsigned char fileBytes[4] = {0}; | ||
file.read((char*)fileBytes, sizeof(fileBytes)); // Read 4 bytes of file to compare against magic | ||
if (is_regular_file(*it) && file.is_open() && memcmp(fileBytes, bdb_magic, sizeof(bdb_magic)) == 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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()
.
I also tested manually that files show up as wallets when bytes 12-15 match the magic bdb bytes:
|
Addressed @jnewbery nits and made available list mutually exclusive to loaded list. Thanks for the review :-) |
src/wallet/rpcwallet.cpp
Outdated
@@ -2645,10 +2645,38 @@ UniValue listwallets(const JSONRPCRequest& request) | |||
|
|||
LOCK(pwallet->cs_wallet); | |||
|
|||
obj.push_back(pwallet->GetName()); | |||
loaded.push_back(pwallet->GetName()); | |||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion to remove loop below:
std::set<std::string> loaded_set;
...
loaded.push_back(pwallet->GetName());
loaded_set.insert(pwallet->GetName())
}
...
if (loaded_set.count(it->path().filename().string()) == 0) {
available.push_back(it->path().filename().string());
}
Good idea @promag, thanks :-) |
src/wallet/rpcwallet.cpp
Outdated
|
||
file.read((char*)fileBytes, sizeof(fileBytes)); // Read 4 bytes of file to compare against magic | ||
|
||
if (fs::is_regular_file(*it) && !fs::is_symlink(*it) && file.is_open() && memcmp(fileBytes, bdb_magic, sizeof(bdb_magic)) == 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some of these conditions can be moved before seekg (just continue
. Only the compare is needed.
Could use 64 bit int instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed the conditional, but IMO the char array is clearer and perhaps safer than using an int :)
Rebased on master to fix travis failure due to multiwallet.py being modified |
src/wallet/rpcwallet.cpp
Outdated
} | ||
} | ||
|
||
file.close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove, see https://stackoverflow.com/a/748059.
src/wallet/rpcwallet.cpp
Outdated
unsigned char fileBytes[4] = {0}; | ||
file.read((char*)fileBytes, sizeof(fileBytes)); // Read 4 bytes of file to compare against magic | ||
|
||
if (memcmp(fileBytes, bdb_magic, sizeof(bdb_magic)) == 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternative, avoid nesting:
if (memcmp(...) != 0) continue;
if (loaded_set.count(filename) > 0) continue;
available.push_back(filename);
Note that close is automatically called, see comment below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit, add:
const std::string filename = it->path().filename().string();
src/wallet/rpcwallet.cpp
Outdated
if(!file.is_open()) continue; | ||
|
||
file.seekg(12, std::ios::beg); // Magic bytes start at offset 12 | ||
unsigned char fileBytes[4] = {0}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: file_bytes
.
src/wallet/rpcwallet.cpp
Outdated
const unsigned char bdb_magic[4] = {0x62, 0x31, 0x05, 0x00}; // Berkeley DB Btree magic bytes | ||
|
||
for (fs::directory_iterator it(walletdir); it != fs::directory_iterator(); ++it) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit, remove empty line.
test/functional/multiwallet.py
Outdated
@@ -19,7 +19,7 @@ def set_test_params(self): | |||
self.extra_args = [['-wallet=w1', '-wallet=w2', '-wallet=w3']] | |||
|
|||
def run_test(self): | |||
assert_equal(set(self.nodes[0].listwallets()), {"w1", "w2", "w3"}) | |||
assert_equal(set(self.nodes[0].listwallets()['loaded']), {"w1", "w2", "w3"}) | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assert available is empty array?
Rebased and updated to work with new wallets directory from #11466 |
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) |
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested ACK 946b2ce. One suggested change for the new test.
@@ -63,6 +64,10 @@ def run_test(self): | |||
|
|||
self.start_node(0, self.extra_args[0]) | |||
|
|||
# ensure listwallets only names BDB files, not symlinks, other files or directories |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
--- a/test/functional/multiwallet.py
+++ b/test/functional/multiwallet.py
@@ -65,8 +65,20 @@ class MultiWalletTest(BitcoinTestFramework):
self.start_node(0, self.extra_args[0])
# ensure listwallets only names BDB files, not symlinks, other files or directories
- open(os.path.join(wallet_dir, 'w01.dat'), 'a').close()
- assert_equal(set(self.nodes[0].listwallets()['available']), {"w22"})
+ with open(os.path.join(wallet_dir, 'w01.dat'), 'a') as wallet_file:
+ # Create a file containing a long string
+ print("a" * 2000, file=wallet_file)
+
+ available_wallets = self.nodes[0].listwallets()['available']
+
+ assert os.path.islink(os.path.join(wallet_dir, 'w12'))
+ assert 'w12' not in available_wallets
+
+ assert os.path.isdir(os.path.join(wallet_dir, 'w11'))
+ assert 'w11' not in available_wallets
+
+ assert os.path.isfile(os.path.join(wallet_dir, 'w01.dat'))
+ assert 'w01.dat' not in available_wallets
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code looks good, RPC docs could be more descriptive (and are out-of-date after the change).
" \"walletname\" (string) the wallet name\n" | ||
" ...\n" | ||
"]\n" | ||
"{\n" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Update the documentation as per @TheBlueMatt suggestion |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
utACK 3dac78f
@@ -91,6 +91,10 @@ Low-level RPC changes | |||
- The wallet RPC `getreceivedbyaddress` will return an error if called with an address not in the wallet. | |||
|
|||
|
|||
- `listwallets` now returns two arrays of wallet names, one listing the `loaded` wallets |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
- Backwards compatible
- Better performance for listwallets. It could continue to just return the loaded wallets from memory instead of having now having to do disk io.
- More descriptive function name.
listwalletdir
naming is consistent with recently addedwalletdir
parameter, and tells you what the function actually does.
src/wallet/rpcwallet.cpp
Outdated
"]\n" | ||
"{\n" | ||
" \"loaded\": [ \"walletname\" ... ] (json array of strings) Names of loaded wallets\n" | ||
" \"available\": [ \"walletname\" ... ] (json array of strings) Names of BDB files in wallet directory (may not always actually be wallets)\n" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
for (fs::directory_iterator it(walletdir); it != fs::directory_iterator(); ++it) { | ||
if (!fs::is_regular_file(*it) || fs::is_symlink(*it)) continue; | ||
|
||
std::ifstream file(it->path().string(), std::ios::binary); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: to be compatible with #11687, you would drop the continue
above and change this line to something like:
std::ifstream file(fs::is_regular_file(*it) ? it->path() : (it->path() / "wallet.dat")).string(), std::ios::binary);
Might be ready for merge. Unclear if "Code looks good" from @TheBlueMatt above is equivalent to a code ack. |
@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 |
Closing for now w/ up for grabs, let me know when you start working on this and it needs to be reopened. |
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. |
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 (review)
Thoughts? |
ListWalletDir returns all available wallets in the current wallet directory. Based on MeshCollider work in pull bitcoin#11485.
d56a068 docs: Add release notes for listwalletdir RPC (João Barbosa) 0cb3cad qa: Add tests for listwalletdir RPC (João Barbosa) cc33773 rpc: Add listwalletdir RPC (João Barbosa) d1b03b8 interfaces: Add getWalletDir and listWalletDir to Node (João Barbosa) fc4db35 wallet: Add ListWalletDir utility (João Barbosa) Pull request description: `ListWalletDir` returns all available wallets in the current wallet directory. Based on MeshCollider work in pull #11485. Tree-SHA512: 5843e3dbd1e0449f55bb8ea7c241a536078ff6ffcaad88ce5fcf8963971d48c78600fbc4f44919523b8a92329d5d8a5f567a3e0ccb0270fdd27366e19603a716
ListWalletDir returns all available wallets in the current wallet directory. Based on MeshCollider work in pull bitcoin#11485.
ListWalletDir returns all available wallets in the current wallet directory. Based on MeshCollider work in pull bitcoin#11485.
d56a068 docs: Add release notes for listwalletdir RPC (João Barbosa) 0cb3cad qa: Add tests for listwalletdir RPC (João Barbosa) cc33773 rpc: Add listwalletdir RPC (João Barbosa) d1b03b8 interfaces: Add getWalletDir and listWalletDir to Node (João Barbosa) fc4db35 wallet: Add ListWalletDir utility (João Barbosa) Pull request description: `ListWalletDir` returns all available wallets in the current wallet directory. Based on MeshCollider work in pull bitcoin#11485. Tree-SHA512: 5843e3dbd1e0449f55bb8ea7c241a536078ff6ffcaad88ce5fcf8963971d48c78600fbc4f44919523b8a92329d5d8a5f567a3e0ccb0270fdd27366e19603a716
d56a068 docs: Add release notes for listwalletdir RPC (João Barbosa) 0cb3cad qa: Add tests for listwalletdir RPC (João Barbosa) cc33773 rpc: Add listwalletdir RPC (João Barbosa) d1b03b8 interfaces: Add getWalletDir and listWalletDir to Node (João Barbosa) fc4db35 wallet: Add ListWalletDir utility (João Barbosa) Pull request description: `ListWalletDir` returns all available wallets in the current wallet directory. Based on MeshCollider work in pull bitcoin#11485. Tree-SHA512: 5843e3dbd1e0449f55bb8ea7c241a536078ff6ffcaad88ce5fcf8963971d48c78600fbc4f44919523b8a92329d5d8a5f567a3e0ccb0270fdd27366e19603a716
d56a068 docs: Add release notes for listwalletdir RPC (João Barbosa) 0cb3cad qa: Add tests for listwalletdir RPC (João Barbosa) cc33773 rpc: Add listwalletdir RPC (João Barbosa) d1b03b8 interfaces: Add getWalletDir and listWalletDir to Node (João Barbosa) fc4db35 wallet: Add ListWalletDir utility (João Barbosa) Pull request description: `ListWalletDir` returns all available wallets in the current wallet directory. Based on MeshCollider work in pull bitcoin#11485. Tree-SHA512: 5843e3dbd1e0449f55bb8ea7c241a536078ff6ffcaad88ce5fcf8963971d48c78600fbc4f44919523b8a92329d5d8a5f567a3e0ccb0270fdd27366e19603a716
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 :)