Skip to content
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

RPC-API: add ability to recover wallet #1461

Merged
merged 1 commit into from
Apr 6, 2023
Merged

RPC-API: add ability to recover wallet #1461

merged 1 commit into from
Apr 6, 2023

Conversation

AdamISZ
Copy link
Member

@AdamISZ AdamISZ commented Mar 16, 2023

Fixes #1082.
This commit allows recovery of a wallet from a seedphrase with a new endpoint wallet/recover. 4 parameters are passed in, the same three as for wallet/create but also a bip39 seedphrase as the fourth argument.

@AdamISZ
Copy link
Member Author

AdamISZ commented Mar 16, 2023

A couple of initial comments:

I'm marking this as a solution to #1082 but I have not yet written another endpoint for blockchain rescan, I will add that in a new commit.

This is still draft because I have only done one primitive test, it will need tests added into CI as well.

Also, the response format for this new /recover endpoint is identical to the /create Response type, so it redundantly sends back the seedphrase as well as the cookie. Didn't seem like a big deal.

@AdamISZ
Copy link
Member Author

AdamISZ commented Mar 16, 2023

Before I actually write a second commit for rescanblockchain I want to raise the points that I'm unsure about, please chime in if you have an opinion:

  • The need to do a rescan after recovering will be dependent on whether you are recovering into the same Bitcoin Core instance with the same wallet file.
  • So, if a person is doing the above, they should not need any rescan action at all
  • If they are not doing the above, then they will likely see a zero balance and need to call the bitcoind rpc command rescanblockchain with an argument of a block height that is before the first usage of the wallet; and then, wait until that action completes before doing anything else.

The last of those three is of course the tricky one, but also tricky is how we distinguish the former from the latter.

The reason for setting up the websocket element was to address cases like this where the jmwalletd backend fires off an event elsewhere (here, a rescan on the bitcoind process) and has to notify a client when it completes. So that would be a natural part of how to address this, but .. I would appreciate any input on how you think it should work, for a client like JAM.

@AdamISZ
Copy link
Member Author

AdamISZ commented Mar 19, 2023

I'm almost ready to add a /rescanblockchain route but I currently need a way to check that the rescan is completed by using the bitcoind rpc; does anyone know the easiest way to do that? (I seem to remember at least one rpc call will tell you 'rescan in progress' but I can't remember which one).

@kristapsk
Copy link
Member

There is getrpcinfo. It will return rescanblockchain under active_commands while it is running. But not sure how reliable this is and aren't there better ways.

@AdamISZ
Copy link
Member Author

AdamISZ commented Mar 19, 2023

Calling rescanblockchain in a running process is proving to be a much more difficult task than I had anticipated (or more accurately, I'd just forgotten the nuances here):

  • The rescanblockchain RPC call is blocking. You are expected to just do it in another thread if you want to use the RPC otherwise
  • Our jsonrpc module uses a single connection and is not asynchronous, either. Even if I put the rescanblockchain RPC in a separate thread, our monitoring RPC calls in the WalletService start failing from the main thread. (To be clear, we have tried very hard to remove all threading from the application anyway, so it wouldn't be desirable to go this route).

Not addressing this at all is hardly desirable either: as long as the rescan operation is continuing, the entire jmwalletd process is blocked and can't respond to client queries (and for mainnet, this could be an hour plus...).

@AdamISZ
Copy link
Member Author

AdamISZ commented Mar 19, 2023

There is getrpcinfo. It will return rescanblockchain under active_commands while it is running. But not sure how reliable this is and aren't there better ways.

Right, thanks, I found getwalletinfo which works very well (it has a 'scanning' field, true/false). But see previous comment as to why this still isn't enough.

@AdamISZ AdamISZ marked this pull request as ready for review March 19, 2023 22:37
@AdamISZ
Copy link
Member Author

AdamISZ commented Mar 19, 2023

Marked this as ready for review. In rudimentary testing with POSTMAN on signet, the following work:

  • Use /recover to recover with a seedphrase
  • Start a rescan from a specific blockheight with /rescanblockchain
  • Check the status of rescanning is True/False using a call to /session

My resolution to the above difficulties was to start a daemon-type subthread for this one specific call, and create a new JsonRPC object to handle that request. It seems to work.

This needs review, but also I should try to add some kind of test to the test suite.

@AdamISZ
Copy link
Member Author

AdamISZ commented Mar 23, 2023

Added some basic testing into test_wallet_rpc module.

jmclient/jmclient/wallet_rpc.py Outdated Show resolved Hide resolved
jmclient/jmclient/blockchaininterface.py Outdated Show resolved Hide resolved
jmclient/jmclient/blockchaininterface.py Outdated Show resolved Hide resolved
jmclient/jmclient/blockchaininterface.py Outdated Show resolved Hide resolved
jmclient/jmclient/wallet_service.py Outdated Show resolved Hide resolved
jmclient/jmclient/wallet_service.py Outdated Show resolved Hide resolved
jmclient/jmclient/wallet_service.py Outdated Show resolved Hide resolved
@AdamISZ
Copy link
Member Author

AdamISZ commented Mar 23, 2023

OK, squashed and did a few more manual tests. I'm now confident that this is correct. Wil address @kristapsk 's syntax comments and then wait for any testing feedback.

Fixes #1082:

This commit allows recovery of a wallet from a seedphrase
with a new endpoint wallet/recover. 4 parameters are passed in,
the same three as for wallet/create but also a bip39 seedphrase
as the fourth argument.

This commit also adds a rescanblockchain RPC call and status:

This adds a new endpoint /rescanblockchain which is (Core) wallet specific
(due to underlying JM architecture). This action is spawned in a thread,
since the bitcoind RPC call is blocking and can take a very long time.
To monitor the status of the rescan, an extra field `rescanning` is
added to the /session endpoint.

Also adds test of rpc wallet recovery
@AdamISZ
Copy link
Member Author

AdamISZ commented Mar 23, 2023

OK, squashed and did a few more manual tests. I'm now confident that this is correct. Wil address @kristapsk 's syntax comments and then wait for any testing feedback.

Done

@kristapsk
Copy link
Member

@AdamISZ What tools do you use to test this? I remember there was some simple JM RPC client, but can't find it now, don't remember how it was called or who made it.

@AdamISZ
Copy link
Member Author

AdamISZ commented Mar 26, 2023

@AdamISZ What tools do you use to test this? I remember there was some simple JM RPC client, but can't find it now, don't remember how it was called or who made it.

I use POSTMAN. Here's a screenshot:
Screenshot from 2023-03-26 15-34-54

You can enter the Body of POST requests using json formatted text.

@AdamISZ
Copy link
Member Author

AdamISZ commented Mar 26, 2023

But you're right, someone wrote some python code to test it, I have that memory too .. who was it? I don't think it was @PulpCattel ?

@PulpCattel
Copy link
Member

Yes, I made this repo to test the RPC interface when it was introduced. I will eventually update it.

@kristapsk
Copy link
Member

  • Check the status of rescanning is True/False using a call to /session

Looking at getwalletinfo help page. Wouldn't it be useful (and simple) to also return duration and progress if rescanning is in progress?

  "scanning" : {                          (json object) current scanning details, or false if no scan is in progress
    "duration" : n,                       (numeric) elapsed seconds since scan start
    "progress" : n                        (numeric) scanning progress percentage [0.0, 1.0]
  },

@AdamISZ
Copy link
Member Author

AdamISZ commented Mar 28, 2023

  • Check the status of rescanning is True/False using a call to /session

Looking at getwalletinfo help page. Wouldn't it be useful (and simple) to also return duration and progress if rescanning is in progress?

  "scanning" : {                          (json object) current scanning details, or false if no scan is in progress
    "duration" : n,                       (numeric) elapsed seconds since scan start
    "progress" : n                        (numeric) scanning progress percentage [0.0, 1.0]
  },

Yes, I noticed this also when running the tests. I think it would be a really nice augmentation to simply rescanning=true/false, but it can be done in a later update.

A related point occurred to me today: almost everyone running JAM is doing so on a packaged node which is providing other services as well as Joinmarket. Is it OK for us to just decide to do a rescan at any time? Might that not affect other running services? @openoms any thoughts?

@openoms
Copy link
Contributor

openoms commented Mar 28, 2023

In my experience a rescan can add load, but finishes within an hour in the worst case even on a RasperryPi and bitcoind remains functional. I am monitoring 20+ wallets with Specter and could rescan the wallets on demand without problems.

It is ok to trigger them as needed.

@kristapsk
Copy link
Member

I think it would be a really nice augmentation to simply rescanning=true/false, but it can be done in a later update.

Agree, that can be added later.

@AdamISZ
Copy link
Member Author

AdamISZ commented Mar 30, 2023

In my experience a rescan can add load, but finishes within an hour in the worst case even on a RasperryPi and bitcoind remains functional. I am monitoring 20+ wallets with Specter and could rescan the wallets on demand without problems.

It is ok to trigger them as needed.

That's good to know, thanks. The main reason I was asking, though, is that I was concerned if maybe some RPC calls might be disabled during rescan; thinking about it, they're probably wallet RPCs, and I guess that most applications are not using bitcoin core's wallet feature?

@kristapsk
Copy link
Member

I guess that most applications are not using bitcoin core's wallet feature?

That's true, RaspiBolt at one point even had disabled wallet feature in Core by default, which was a problem for JoinMarket guide.

@AdamISZ
Copy link
Member Author

AdamISZ commented Apr 6, 2023

Perhaps a little impatient, but it's been a while and this seems to be highly desired, so I'm going to go ahead and merge without further test or review (we've had a little).

kristapsk added a commit that referenced this pull request Apr 29, 2023
…etwalletinfo()` for `bci`

e31e839 Add get_wallet_rescan_status() instead of getwalletinfo() for bci (Kristaps Kaupe)

Pull request description:

  Noticed this when tried to rebase #1462 after merging of #1477. #1461 added public `getwalletinfo()` method to `BitcoinCoreInterface`, which was used by code outside of `jmclient/jmclient/blockchaininterface.py`. This is bad approach, as it relies on Bitcoin Core RPC `getwalletinfo` returned `dict`, which contains a lots of different stuff too, could lead to more problems in future introducing other blockchain interface classes. Let's instead have generic method returning just wallet rescan status. Also it now returns `Tuple[bool, Optional[Decimal]]` with rescan status percentage, if rescan is in progress, although that's not used by any other code for now.

ACKs for top commit:
  AdamISZ:
    utACK e31e839 , very much agree with the thinking here.

Tree-SHA512: 2d8c9b8157847e713838099d0f62dfcd5321c9498cf8453a9087407e2cd9c32906739c8e71460fc6ac6426662d2ac501261080fea08388d928933f788bda9a8d
@theborakompanioni
Copy link
Contributor

theborakompanioni commented Jul 26, 2023

Hey @AdamISZ,

I have a question regarding how to correctly use this from a client perspective.
Testing this by trying to recover a wallet that has some funds on the following addresses:

  • 1st address of Account 0 (m/84'/1'/0'/0/0)
  • 201st change address of Account 4 (m/84'/1'/4'/1/201)

After importing, the funds on address m/84'/1'/0'/0/0 are present - great.
But how do I make sure the UTXO on m/84'/1'/4'/1/201 is found?
I tried increasing the gap limit to 205 before rescanning, locking/unlock the wallet, etc. - without success.
First idea was to just generate some addresses manually (in all accounts) before rescanning, but since the second UTXO is in the "change" account, I am not able to do that.

I know this is a theoretical scenario, but even if the funds are on other addresses (beyond the gap limit), I was not able to recover them successfully.

Now before I try to be too clever, I just thought I'd ask what to do.
What is your suggestion on how to import the wallet described above and recover the whole balance?

@AdamISZ
Copy link
Member Author

AdamISZ commented Aug 3, 2023

Hey @AdamISZ,

I have a question regarding how to correctly use this from a client perspective. Testing this by trying to recover a wallet that has some funds on the following addresses:

* 1st address of Account 0 (`m/84'/1'/0'/0/0`)

* 201st change address of Account 4 (`m/84'/1'/4'/1/201`)

After importing, the funds on address m/84'/1'/0'/0/0 are present - great. But how do I make sure the UTXO on m/84'/1'/4'/1/201 is found? I tried increasing the gap limit to 205 before rescanning, locking/unlock the wallet, etc. - without success. First idea was to just generate some addresses manually (in all accounts) before rescanning, but since the second UTXO is in the "change" account, I am not able to do that.

I know this is a theoretical scenario, but even if the funds are on other addresses (beyond the gap limit), I was not able to recover them successfully.

Now before I try to be too clever, I just thought I'd ask what to do. What is your suggestion on how to import the wallet described above and recover the whole balance?

I was able to replicate this effect on regtest, for now outside the API (because I want to understand it "raw" first, before considering API commands) (also, I will document exactly what I did later, but for now, just an outline comment):

I find that if I do the following: (1) run wallet-tool with gap limit set to 202 (whatever is higher than your funding), (2) run rescanblockchain N with N sufficient to find the txs and then (3) run wallet-tool with gap limit set to 202 again, then it works.

While awkward the logic makes sense, right: the first run of wallet tool display is going to import as many addresses into the backend Core wallet as it thinks it needs; and with -g 202 you are telling it to import all that way up. Now, after the imports have occurred, the rescanblockchain command is looking for all those addresses (if you do it first, before using -g, it doesn't have any knowledge of the index 201 addresses), so it finds all the transactions. The third step, of running wallet-tool display with still -g 202 set, will pick up the new transactions. I guess at this point there should be no further funds-finding issues since after that you have the wallet_index set in the JM wallet file, so it persists. I need to check a few more things.

But anyway just a quick initial report on what I'm seeing. I'll look at the same sequence of events via API next.

@AdamISZ
Copy link
Member Author

AdamISZ commented Aug 5, 2023

@theborakompanioni ok I'm reasonably sure I understand what's going on now, after trying via the API. I can indeed reproduce what you're saying.

So the problem is how we're handling gap limit. The method to open the wallet (open_wallet, open_test_wallet_maybe) has to explicitly pass in the gap_limit value to the BaseWallet constructor, if it wants to be anything other than default 6; we do this explicitly in a few places in the codebase, but not in the calls that come via the RPC. Moreover, this gap limit setting is only coming from command line options, via the optparse parser, not from a setting which exists (wrongly placed? I'll check later) in the config file, so doing configset in the API won't matter anyway.
So what I'll do is re-do my API calls setup with the gaplimit setting being taken from the config file, so that you can edit it with configset, and if that works, I'll PR.

@AdamISZ
Copy link
Member Author

AdamISZ commented Aug 6, 2023

See #1525 . I think it should do what is needed. Sorry for this screw up, I can imagine quite a few people had problems because of it!

@AdamISZ AdamISZ deleted the rpc_recovery branch August 10, 2023 18:56
@theborakompanioni
Copy link
Contributor

  • Check the status of rescanning is True/False using a call to /session

Looking at getwalletinfo help page. Wouldn't it be useful (and simple) to also return duration and progress if rescanning is in progress?

  "scanning" : {                          (json object) current scanning details, or false if no scan is in progress
    "duration" : n,                       (numeric) elapsed seconds since scan start
    "progress" : n                        (numeric) scanning progress percentage [0.0, 1.0]
  },

Yes, I noticed this also when running the tests. I think it would be a really nice augmentation to simply rescanning=true/false, but it can be done in a later update.

Related joinmarket-webui/jam#743
Any further thoughts on this? Ability to show a progress percentage would be nice (e.g. "Rescanning in progress (21%)")

May I open a ticket?

@kristapsk
Copy link
Member

May I open a ticket?

Yes, of course.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

FEATURE: ability to import/recover wallet via rpc
5 participants