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

Adds descriptor module with getdescriptorinfo rpc #1152

Merged
merged 4 commits into from
Jul 17, 2023

Conversation

Vasu-08
Copy link
Contributor

@Vasu-08 Vasu-08 commented May 22, 2023

This pr implements getdescriptorinfo rpc by adding a descriptor module.

If the argument string of getdescriptorinfo() rpc is a valid string it is parsed into a AbstractDescriptor object.
Along with that various parts of the string, such as origin information, keys (Public/Private), and derivation path (along with hardened or unhardened information) are parsed into PubKeyProvider object respectively. If the descriiptor string contains a checksum it will be validiated and if a checksum is not present we will calculate one.

Note: The input descriptor string doesn't require checksum to be present.

The AbstractDescriptor class has PKDescriptor, PKHDescriptor, WPKHDescriptor, AddressDescriptor, MultisigDescriptor, WSHDescriptor, SHDescriptor, RawDescriptor, ComboDescriptor as its subclasses.

The KeyProvider class will have ConstPubkeyProvider (for parsing constant keys with/without origin info) and HDKeyProvider (for parsing extended (hd - bip32) keys with/without origin info) as its subclasses.

Functions for parsing descriptor string will be present as global function in parser.js.
Checksum functions are implemented in common.js.

Functions checking isRange(), isSolvable(), hasPrivateKeys() etc. will be present in the AbstractDescriptor class.

@Vasu-08 Vasu-08 marked this pull request as draft May 22, 2023 17:27
@codecov-commenter
Copy link

codecov-commenter commented May 22, 2023

Codecov Report

Patch coverage: 90.93% and project coverage change: +0.69 🎉

Comparison is base (bb0375e) 69.56% compared to head (5b09c3f) 70.26%.

❗ Current head 5b09c3f differs from pull request most recent head f388594. Consider uploading reports for the commit f388594 to get more accurate results

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1152      +/-   ##
==========================================
+ Coverage   69.56%   70.26%   +0.69%     
==========================================
  Files         159      174      +15     
  Lines       26607    27367     +760     
==========================================
+ Hits        18509    19229     +720     
- Misses       8098     8138      +40     
Impacted Files Coverage Δ
lib/bcoin.js 0.00% <0.00%> (ø)
lib/descriptor/index.js 0.00% <0.00%> (ø)
lib/descriptor/type/combo.js 89.65% <89.65%> (ø)
lib/descriptor/type/addr.js 90.90% <90.90%> (ø)
lib/hd/keyorigininfo.js 92.00% <92.00%> (ø)
lib/descriptor/abstractdescriptor.js 92.06% <92.06%> (ø)
lib/descriptor/type/pk.js 92.30% <92.30%> (ø)
lib/descriptor/type/sh.js 92.30% <92.30%> (ø)
lib/node/rpc.js 41.86% <92.30%> (+0.47%) ⬆️
lib/descriptor/type/pkh.js 92.85% <92.85%> (ø)
... and 9 more

... and 4 files with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Do you have feedback about the report comment? Let us know in this issue.

lib/hd/keyorigin.js Outdated Show resolved Hide resolved
lib/hd/keyorigin.js Outdated Show resolved Hide resolved
lib/hd/keyorigin.js Outdated Show resolved Hide resolved
lib/hd/keyorigin.js Outdated Show resolved Hide resolved
lib/hd/keyorigin.js Outdated Show resolved Hide resolved
lib/wallet/rpc.js Outdated Show resolved Hide resolved
@Vasu-08 Vasu-08 force-pushed the descriptor branch 3 times, most recently from 918ed8c to e47a36c Compare May 28, 2023 06:20
@Vasu-08 Vasu-08 force-pushed the descriptor branch 7 times, most recently from baa629e to 1c28e9d Compare June 3, 2023 11:42
@Vasu-08 Vasu-08 changed the title Adds descriptor module Adds descriptor module with getdescriptorinfo rpc Jul 1, 2023
@Vasu-08 Vasu-08 force-pushed the descriptor branch 2 times, most recently from 4ec0130 to 474641d Compare July 2, 2023 16:03
@Vasu-08 Vasu-08 force-pushed the descriptor branch 2 times, most recently from e33ac76 to 2bb70a0 Compare July 3, 2023 21:38
Copy link
Member

@pinheadmz pinheadmz left a comment

Choose a reason for hiding this comment

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

code review ACK 2bb70a0

The commits look super clean now THANK YOU, this was very easy to read and review. I left quite a few nits and questions below. I think everything is looking really good, just some clean up and some ideas about making your base classes more abstract.

While you address those comments I'm going to review the test vectors more closely and then play with the actual RPC feature and library functions and try to break them ;-)

GREAT WORK, keep it up! Almost done. <3

fromOptions(options) {
assert(options, 'requires options');

if (options.fingerPrint !== null) {
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 these need to be != so that undefined and null are treated the same

lib/hd/common.js Outdated
* @param {Boolean} hard
* @returns {Number[]}
* @returns {Array}
Copy link
Member

Choose a reason for hiding this comment

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

Why change this? isn't Number[] correct?

Comment on lines 93 to 94
const fingerPrint = isValidFingerPrint(slashSplit[0]);
this.fingerPrint = fingerPrint;
Copy link
Member

Choose a reason for hiding this comment

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

could just be...

Suggested change
const fingerPrint = isValidFingerPrint(slashSplit[0]);
this.fingerPrint = fingerPrint;
this.fingerPrint = isValidFingerPrint(slashSplit[0]);

* @returns {Number}
*/

function isValidFingerPrint(str) {
Copy link
Member

Choose a reason for hiding this comment

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

maybe call this validateFingerprint() ? since it doesn't return a boolean like most is...() functions

Comment on lines 1 to 2
/* eslint-disable quotes */
/* eslint-disable no-unused-vars */
Copy link
Member

Choose a reason for hiding this comment

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

nits:

  • I dont think you need no-unsued-vars
  • but you should set /* eslint-env mocha */
  • I understand you want to use double quotes because there's so many single-quotes in the test vectors, I think that is ok. Still looks ugly to me in the first 5 lines though


this.network = Network.get(options.network);

for (const keyProvider of options.keyProviders) {
Copy link
Member

Choose a reason for hiding this comment

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

didn't you just assert there will only be one of these? Same with pk() maybe others...

super();
this.name = types.MULTI;
this.isSorted = false;
this.threshold = null;
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 this should initialize as 0 for type optimization ?

}

this.network = Network.get(options.network);
this.script = options.script;
Copy link
Member

Choose a reason for hiding this comment

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

should have init value in constructor

Comment on lines 43 to 56
class Descriptor {
/**
* Create a descriptor
* @constructor
*/

constructor() {
this.name = null;
this.keyProviders = [];
this.subdescriptors = [];
this.network = null;
}
Copy link
Member

Choose a reason for hiding this comment

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

See my comment about making AbstractKeyProvider - I think more stuff can be moved to the base class, like parsing some of the common options like network. keyProviders must always be an array so that can be checked in base class, then each extended class can check those array lengths, etc.

lib/node/rpc.js Outdated
@@ -2765,6 +2769,26 @@ class RPC extends RPCBase {
};
}

async getDescriptorInfo(args, help) {
Copy link
Member

Choose a reason for hiding this comment

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

lets put this function after the setloglevel implementation (up on line 2328 or so). Down here is for helper functions

@Vasu-08 Vasu-08 force-pushed the descriptor branch 7 times, most recently from 02796a5 to e96f319 Compare July 11, 2023 17:05
Copy link
Member

@pinheadmz pinheadmz left a comment

Choose a reason for hiding this comment

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

code review ACK e96f319

Everything looks awesome, just a few picky nits and we can land this beast!

fromOptions(options) {
assert(options, 'requires options');

if (options.fingerPrint) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (options.fingerPrint) {
if (options.fingerPrint != null) {

See for example ChainOptions.fromOptions() in chain.js L2639

lib/hd/common.js Outdated
* @param {Boolean} hard
* @returns {Number[]}
*/

common.parsePath = function parsePath(path, hard) {
assert(typeof path === 'string');
common.parsePathToArray = function parsePathToArray(path, hard) {
Copy link
Member

Choose a reason for hiding this comment

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

Should this be parsePathFromArray() ?

Copy link
Contributor Author

@Vasu-08 Vasu-08 Jul 15, 2023

Choose a reason for hiding this comment

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

Actually this function parses path from String so maybe I can rename it to parsePathFromString?

Copy link
Member

Choose a reason for hiding this comment

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

does it?

  assert(Array.isArray(path), 'Path must be an array.');

Copy link
Contributor Author

@Vasu-08 Vasu-08 Jul 17, 2023

Choose a reason for hiding this comment

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

Ohh sorry I thought you were mentioning parsePath function. I'll change parsePathToArray to parsePathFromArray

toJSON() {
return {
fingerPrint: this.format().fingerPrint,
path: this.format().path
};
}
Copy link
Member

Choose a reason for hiding this comment

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

should this just return this.format()? And so only call it once?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes no need. Good catch.

@@ -0,0 +1,27 @@
/*!
Copy link
Member

Choose a reason for hiding this comment

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

Lets add a link to this module in lib/bcoin.js as well

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah should have done this way earlier. Thanks for pointing this out :). Also added KeyOriginInfo in HD here.


common.isHex = function isHex(str) {
assert(typeof str === 'string');
return /^[0-9a-fA-F]+$/.test(str);
Copy link
Member

Choose a reason for hiding this comment

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

This should also probably ensure an even number of characters

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm a bit unsure whether this function should exist. I only found 1 usage in keyprovider.js L87. I think we can remove this function from here.

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 think we can have it for now . People could use it instead of calling the external dependencies. (base16)

Comment on lines 55 to 58
parseOptions(options) {
this.ring = options.ring;
this.pos = options.pos;
this.originInfo = options.originInfo;
this.hardenedMarker = options.hardenedMarker;
this.network = Network.get(options.network);
}
Copy link
Member

Choose a reason for hiding this comment

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

Don't we still need to do input validation here of the passed-in options?

Copy link
Contributor Author

@Vasu-08 Vasu-08 Jul 15, 2023

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 required to do that here since we are already parsing and validating the inputs by the parsing functions of KeyProvider class.

Comment on lines +236 to +229
isRange() {
return false;
}
Copy link
Member

Choose a reason for hiding this comment

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

If these functions all return null because direct instances of the abstract KeyProvider class shouldn't be used, these methods should probably throw errors. See the methods in blockstore/abstract.js

options.script = desc.script;
options.network = desc.network;

switch (type) {
Copy link
Member

Choose a reason for hiding this comment

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

You already have parser.parse() is there a reason to implement that again here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

parser.parse() parses descriptor only when it is in string form. To parse descriptor from options object this function is needed.

Comment on lines 53 to 59
if (options.subdescriptors) {
assert(options.subdescriptors.length === 0);
}

if (options.keyProviders) {
assert(options.keyProviders.length === 0);
}
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 you can drop the if's and just assert. Because of the abstractdescriptor constructor, we know those arrays will exist, we just need to ensure that they are empty.

*/

constructor() {
this.name = null;
Copy link
Member

Choose a reason for hiding this comment

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

this should be this.type in all the classes, sorry I shouldve caught that sooner

@Vasu-08 Vasu-08 force-pushed the descriptor branch 3 times, most recently from 1b87d14 to e94d8cb Compare July 15, 2023 14:27
Copy link
Collaborator

@theanmolsharma theanmolsharma left a comment

Choose a reason for hiding this comment

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

ACK e94d8cb
Excellent work on the PR! Just a few small nits to fix before we can merge.

lib/descriptor/common.js Outdated Show resolved Hide resolved
lib/descriptor/common.js Outdated Show resolved Hide resolved
lib/descriptor/common.js Show resolved Hide resolved
lib/descriptor/keyprovider.js Outdated Show resolved Hide resolved
lib/descriptor/keyprovider.js Outdated Show resolved Hide resolved
lib/descriptor/parser.js Show resolved Hide resolved
lib/hd/keyorigininfo.js Show resolved Hide resolved
lib/descriptor/type/addr.js Outdated Show resolved Hide resolved
lib/descriptor/type/multisig.js Outdated Show resolved Hide resolved
test/descriptor-test.js Show resolved Hide resolved
Copy link
Collaborator

@theanmolsharma theanmolsharma left a comment

Choose a reason for hiding this comment

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

Tested ACK 5b09c3f

@pinheadmz pinheadmz merged commit a4bf281 into bcoin-org:master Jul 17, 2023
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.

4 participants