-
Notifications
You must be signed in to change notification settings - Fork 31
Bitcoin SPV, Funding Proof and Electrum Client #8
Conversation
JS implementation of merkle.py script from [summa-tx/bitcoin-spv] repository.
Changed manual parsing of transaction to usage of bcoin libraries. This gives more flexibility and greater cases coverage.
Each library is an independent module located in lib/ directory. They can later be extracted to a separate repositories if needed. client/ directory contains dapp facing functions. Dapp should be calling functions exposed in the client/.
Converter to calculate an address for given chain (testnet, mainnet) from a 64 byte public key.
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.
😄 awesome turnaround with this
.eslintrc
Outdated
@@ -0,0 +1,17 @@ | |||
{ |
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.
FYI @Shadowfiend this was another reason why the linting is setup like this. Separate .eslint configs, because the frontend uses funky React syntax (JSX).
client/package.json
Outdated
"version": "0.0.1", | ||
"description": "", | ||
"scripts": { | ||
"test": "CONFIG_FILE=${npm_package_config_file} mocha --timeout 5000" |
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.
Don't forget the lint commands! You can use the same lint config from tbtc
:
"js:lint": "eslint ${npm_package_config_eslintPaths}",
"js:lint:fix": "eslint --fix ${npm_package_config_eslintPaths}",
},
"config": {
"eslintPaths": "test/ migrations/ demo/"
},
Also make sure to only specify the eslintPaths where these changes lie :). The frontend code won't be linted yet, there's some additional work with parsing JSX.
client/test/FundingProofTest.js
Outdated
|
||
it('getProof', async () => { | ||
const proofFile = fs.readFileSync('./test/data/proof.json', 'utf8') | ||
expectedResult = JSON.parse(proofFile) |
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.
expectedResult
referenced before definition? Add a const
?
lib/bitcoin-spv/index.js
Outdated
const ProofVerification = require('./src/ProofVerification') | ||
|
||
module.exports = BitcoinSPV | ||
module.exports.verifyProof = ProofVerification |
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.
Preferred syntax:
module.exports = {
BitcoinSPV,
verifyProof: ProofVerification
}
It would be better not to merge namespaces/objects, since JS is so loosely typed.
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.
It would be better not to merge namespaces/objects, since JS is so loosely typed.
What do you mean? How can we improve?
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.
So JS is a terrible language for doing magic with, principally because it just keeps chugging along no matter how illogical something seems. This is why the whole "immutable" movement has been gaining a lot of steam - JS has different semantics for getting properties, setting properties.
I'll give some examples:
let obj = {}
// differences in error semantics
// fail silently
obj.xyz === undefined
obj['xyz'] === undefined
// fail properly
obj.xyz.abc
// VM70:1 Uncaught TypeError: Cannot read property 'abc' of undefined
// at <anonymous>:1:9
let obj2 = { abc: 1 }
obj.xyz = obj2
// reference semantics
obj.xyz.abc === 1
obj2.xyz = 2
obj.xyz.abc === 2
// value semantics
// this will probably surprise you
obj2 = { abc: 5 }
obj.xyz.abc === 2
In the frontend world there are two major trends:
- Making things declarative, rather than imperative
- Making things immutable by default
module.exports = {
BitcoinSPV,
verifyProof: ProofVerification
}
^^ the above is an example of embodying the first principle, and making way for the second. It declares the exports once, which makes the code declarative (there's one simple place where it all happens at once).
lib/bitcoin-spv/src/BitcoinSPV.js
Outdated
|
||
this.close() | ||
|
||
return Promise.resolve({ |
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.
It should work with plain return {}
} | ||
|
||
getMerkleRoot(blockHeight) { | ||
return new Promise((resolve, reject) => { |
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.
Is there a reason that the syntax here is different to async/await
above?
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.
Yes, me learning 😉 I'll update it to async/await
.
this.getMerkleRoot(blockHeight) | ||
.then((merkleRoot) => { | ||
proof = Buffer.concat([proof, fromHex(merkleRoot)]) | ||
resolve({ 'proof': toHex(proof), 'position': position }) |
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.
Prefer prop keys without string quotes '
outputs.forEach((output, index) => { | ||
output.scriptPubKey.addresses.forEach((a) => { | ||
if (a == address) { | ||
resolve(index) |
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.
This will call resolve
twice potentially, which can get messy. A less brittle approach could be for(let item of array) {}
and then return
ing from the loop.
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.
Won't it resolve on a first hit?
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.
Yes but it will also:
- continue looping
- call resolve again, potentially, which would error as "promise already resolved", probably with no context/trace
const chai = require('chai') | ||
const assert = chai.assert | ||
|
||
const config = Config.readFile(process.env.CONFIG_FILE) |
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.
Why not just directly require
the config if it's only used in this test? Seems like we've got a couple of layers of indirection going on here?
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.
Do you mean this:
const config = require('../src/Config').readFile(process.env.CONFIG_FILE)
?
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.
Protip! if it's JSON, require will parse it for you ;) no need for readFile, just require it like a js file
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.
Notable, require
caches its results. Probably fine here, but keep in mind if you ever need to reload your config, require
is not the way to go.
|
||
describe('findOutputForAddress', async () => { | ||
afterEach(() => { | ||
assert.equal( |
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.
Agree with Piotr, let's prefer putting the assertions within the it
block, and only do setup in before
and after
. If it gets greater than one line, then extract it to a common function.
To execute tests in submodules run `npm run test` in main directory.
@liamzebedee thank you for your valuable inputs! I've learned a lot! |
Decided to merge libs into one package for easier development. These are really small modules. We can consider extracting them in the future.
@liamzebedee code updated. |
Replaced electrum-client dependency with electrum js.
- extract config file to a variable instead of requiring it as a parameter - add docs
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.
Good stuff, only minor things.
@@ -1,4 +1,4 @@ | |||
const ElectrumCli = require('electrum-client') | |||
const electrumjs = require('electrumjs') |
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 was searching for the docs on this pkg. Seems quite early:
Note that this is a work in progress and it is not ready for production whatsoever. The package has not even been published to NPM yet. Proceed at your own risk.
It was last updated 18 days ago on NPM. Code looks good, they're generating API's from a template. Nonetheless, I'm slightly worried they might push and break it accidentally. Would it be a good idea to fork it to keep-network, and install from git:// instead of NPM?
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.
Yeah, we will fork it for sure. We already forked a previous electrum-client
and I discovered that it doesn't meat all our needs. I didn't want to fork another one before testing that it really covers what we want in all steps.
@@ -0,0 +1,24 @@ | |||
// PAGE 3: Pay BTC |
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.
getAddress
is one I've started implementing.
None of the BTC functions are implemented, only ETH-related ones. But I have started building a little skeleton since there is crossover in the flow.
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.
Mentioning this because while it's not necessarily related to the review, it's easier (?) to communicate information here with context than in Flowdock with no code. :)
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.
Get address will be implemented in #14
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.
Nice one with implementing ETH part!
We can use it to get a public key and then call https://github.com/keep-network/tbtc-dapp/blob/funding/lib/tbtc-helpers/src/Address.js#L18 to convert a public key to a BTC address.
@liamzebedee ready for a next round. |
@liamzebedee ready for a next round. |
Arc Reactor: Migrate tbtc.js to the latest tBTC contracts
This PR contains:
Closes: #4
Closes: #9