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

Bitcoin RPC Blockchain #22

Closed
wants to merge 3 commits into from
Closed

Bitcoin RPC Blockchain #22

wants to merge 3 commits into from

Conversation

justinmoon
Copy link
Contributor

@justinmoon justinmoon commented Jun 28, 2020

No description provided.

}
false
})
.any(|x| x);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to determine if Bitcoin Core knows about this script.

The only method of determining this that I know of is getaddressinfo, which requires we actually turn the script into an address. Since this module has not notion of "network", I try each one.

})
.collect::<Vec<_>>(),
Some(&ImportMultiOptions { rescan }),
)?;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I export all our scripts. It would be better if we could, say, export our last 100 scripts. Then check if the previous 100 scripts need exporting, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still isn't ideal, but I think we can iterate on this.

@justinmoon

This comment has been minimized.

.0
.as_mut()
.unwrap()
.get_block_info(&ltx.info.blockhash.unwrap()) // FIXME: don't unwrap
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm doing this because until now listtransactions doesn't include block heights.

Well Bitcoin 20.0 added this field. So we only need to call getblock if their node version is lower than 20.0.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once this is merged we can skip this if we find the height (it won't be there for pre-20.0 nodes)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using a rust-bitcoincore-rpc fork until ^^ gets added. This fork hacks around this issue which breaks getaddressinfo on 0.20.0 :/

I should probably swap out the fork before we merge this.

vec![].into_iter().collect()
}

async fn setup<D: BatchDatabase + DatabaseUtils, P: Progress>(
Copy link
Contributor Author

@justinmoon justinmoon Jul 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would also be nice if this could create watch-only wallets in Bitcoin Core. Otherwise the caller needs it's own "setup" logic.

To do this Magical would need to know the watch-only wallet name:

pub struct BitcoinRpcBlockchain(Option<Client>, Option<String>);

Where the string is the watch-only wallet name. None would just use default wallet. Or we could not use an Option to force people to use a watch-only wallet, which seems like a better idea. Then the BitcoinRpcBlockchain.setup could:

  • Call loadwallet
    • If it succeeds, check if the node recognizes our addresses.
      • If yes, we're done.
      • If no, export addresses.
    • If it fails, call createwallet to create a new watch-only wallet.
      • If this succeeds, export addresses

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I implemented this, but didn't use loadwallet because this RPC call isn't supported yet

@justinmoon justinmoon changed the title WIP: Bitcoin RPC Bitcoin RPC Blockchain Aug 2, 2020
- Create feature
- Make sync algorithm more batch-friendly
- Improve error handling
- Add more fields to the RpcBlockchain struct: `wallet_name`, `network`,
and `rescan_since`
- Create and load watch-only wallets
@justinmoon
Copy link
Contributor Author

This is ready for review.

setup now checks that a valid bitcoin core version is being used and creates / loads watch-only wallets.

sync has been made more batching-friendly (waiting for library support) and attempts reorg protection. I take the highest height associated with any transaction in sled and assume that any transaction 100 blocks or more before this height is considered settled. Our recursive listtransactions calls terminate once such a transaction is observed on the assumption that all older transactions will be "even more settled"!

In normal usage once we add batching, sync will make 3-4 RPC calls depending on node version:

  • Batched getaddressinfo to see if Bitcoin Core recognizes our addresses. Perhaps this could exist on a separate .import method on the OnlineBlockchain trait?
  • One listtransactinons call. It will be rare that user has added more "wallet transactions" (inputs and outputs related to the wallet each count separately here) between two sync calls.
  • Batched gettransaction to fetch the full transaction for each new txid from ^^.
  • If we're using 0.20.0 we're done. If using earlier node version, then blockheight is not included in either the listtransactions or gettransaction. Thus, we must make a final batched call to getblock to get the block height for each transaction. Perhaps this could be done in parallel with gettranaction calls?

I think this is pretty good. I wonder if that will be fast enough over Tor? In my limited experience doing RPC over Tor, it seems to take a few seconds sometimes. This is where it would be nice to move the import logic to a separate and paralleliz-able call ...

@@ -19,6 +19,7 @@ reqwest = { version = "0.10", optional = true, features = ["json"] }
futures = { version = "0.3", optional = true }
clap = { version = "2.33", optional = true }
base64 = { version = "^0.11", optional = true }
bitcoincore-rpc = { git = "https://github.com/justinmoon/rust-bitcoincore-rpc", branch = "magical", optional = true }

# Platform-specific dependencies
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I do something like this? Bitcoin RPC doesn't work in the browser ...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine leaving it in the normal dependencies for now, even the electrum-client doesn't work but it's still up there...

load_wallet_err
{
if load_wallet_err.message == format!("Wallet file verification failed: Error loading wallet {}. Duplicate -wallet filename specified.", config.wallet_name) {
info!("Watch-only wallet already loaded: \"{}\"", &config.wallet_name);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once listwallets support is added to rust-bitcoincore-rpc, then we can remove this ugly hack!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe here you could use the raw call() method to make the listwallets call. Still kinda hacky but less than having to try I guess

panic!(
"couldn't create watch-only bitcoin core wallet: {}",
error.to_string()
);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't know what to do here.

  • wallet_name doesn't exist
  • can't create wallet_name

Maybe return a Error::BitcoinRpcCreateWalletFailed error or something?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, i think it makes sense to have a specific error for this

Copy link
Member

@afilini afilini left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall I really like this, thanks!

I would wait a little bit and see if they merge your fix for the getaddressinfo labels so that we can point the dependency to their git repo. If that starts taking a long time, I can just fork it in this org and we can use that fork temporarily.

I will make a little list of things that are kinda suboptimal now and that could be improved by a future pull request, so that we don't forget. Let me know if you think there's more to add:

  • Implement RPC batching (waiting for library support)
  • Use of listwallets (again, waiting for library support or alternative methods, see comment below)
  • Support pruned nodes by just rescanning the utxo set (or offer this as an option for users who don't care about the full history)
  • Advertise GetAnyTx on nodes that have txindex enabled. (I'm not sure if this can be checked with an RPC call, maybe we could just add an option and if the user knows that the node is configured correctly, he can enable it)

Comment on lines +109 to +115
let db_tip_height = database.iter_txs(false)?.iter().fold(0, |acc, tx| {
if let Some(height) = tx.height {
std::cmp::max(acc, height)
} else {
acc
}
});
Copy link
Member

@afilini afilini Aug 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a suggestion based on my own personal preference, feel free to keep it the way it is if you like it more:

let db_top_height = database.iter_txs(false)?.iter().filter_map(|tx| tx.height).max().unwrap_or(0);

I haven't tested it but I think they should be equivalent. I personally like it more this way because I think the methods used on the iterator express what you are trying to do better (find the maximum height). fold is generally used when you want to squash together all the items of an iterator, so it might be a little more confusing to somebody reading it because that's not really what you are doing here.

panic!(
"couldn't create watch-only bitcoin core wallet: {}",
error.to_string()
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, i think it makes sense to have a specific error for this


impl OnlineBlockchain for RpcBlockchain {
fn get_capabilities(&self) -> HashSet<Capability> {
vec![].into_iter().collect()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the FullHistory capability should be advertised. It basically means that you can get all the historical transactions of the wallet and not just the ones that have outputs in the UTXO set.

Eventually I think it will make sense to add more options (and sometimes just adapt to the node's configuration) and dinamically add/remove capabilities, like:

  • If the node has txindex enabled, then it also supports the GetAnyTx capability
  • If somebody wants to speed up the sync of his wallet or if the node is pruned, instead of doing a full rescan it could just rescan the utxo set. In that case FullHistory should be removed.

Anyways, I think it's fine like this for now, all these improvements could be part of a follow-up pull request.

load_wallet_err
{
if load_wallet_err.message == format!("Wallet file verification failed: Error loading wallet {}. Duplicate -wallet filename specified.", config.wallet_name) {
info!("Watch-only wallet already loaded: \"{}\"", &config.wallet_name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe here you could use the raw call() method to make the listwallets call. Still kinda hacky but less than having to try I guess

@justinmoon
Copy link
Contributor Author

This is outdated. We can just track this as an issue for now: #79.

@justinmoon justinmoon closed this Oct 6, 2020
@RCasatta RCasatta mentioned this pull request May 17, 2021
6 tasks
nickfarrow pushed a commit to nickfarrow/bdk that referenced this pull request Feb 21, 2022
Removed Ord/PartialOrd derive rules for types.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants