Skip to content
This repository has been archived by the owner on Mar 28, 2023. It is now read-only.

Bitcoin SPV, Funding Proof and Electrum Client #8

Merged
merged 43 commits into from
Jul 30, 2019
Merged

Conversation

nkuba
Copy link
Member

@nkuba nkuba commented Jul 24, 2019

This PR contains:

  • Bitcoin SPV implementation in JS,
  • Electrum client to use to get data from bitcoin chain,
  • Bitcoin transaction parser,
  • Scripts to get funding proof details required by tBTC.

Closes: #4
Closes: #9

nkuba added 14 commits July 24, 2019 11:02
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.
Copy link
Contributor

@liamzebedee liamzebedee left a 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 @@
{
Copy link
Contributor

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).

"version": "0.0.1",
"description": "",
"scripts": {
"test": "CONFIG_FILE=${npm_package_config_file} mocha --timeout 5000"
Copy link
Contributor

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.


it('getProof', async () => {
const proofFile = fs.readFileSync('./test/data/proof.json', 'utf8')
expectedResult = JSON.parse(proofFile)
Copy link
Contributor

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?

const ProofVerification = require('./src/ProofVerification')

module.exports = BitcoinSPV
module.exports.verifyProof = ProofVerification
Copy link
Contributor

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.

Copy link
Member Author

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?

Copy link
Contributor

@liamzebedee liamzebedee Jul 25, 2019

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:

  1. Making things declarative, rather than imperative
  2. 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).


this.close()

return Promise.resolve({
Copy link
Contributor

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) => {
Copy link
Contributor

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?

Copy link
Member Author

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 })
Copy link
Contributor

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)
Copy link
Contributor

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 returning from the loop.

Copy link
Member Author

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?

Copy link
Contributor

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:

  1. continue looping
  2. 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)
Copy link
Contributor

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?

Copy link
Member Author

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)?

Copy link
Contributor

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

Copy link
Contributor

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(
Copy link
Contributor

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.

@nkuba
Copy link
Member Author

nkuba commented Jul 25, 2019

@liamzebedee thank you for your valuable inputs! I've learned a lot!

@nkuba nkuba requested a review from liamzebedee July 25, 2019 10:56
nkuba added 2 commits July 25, 2019 13:01
Decided to merge libs into one package for easier development. These are
really small modules. We can consider extracting them in the future.
@liamzebedee liamzebedee mentioned this pull request Jul 26, 2019
@nkuba
Copy link
Member Author

nkuba commented Jul 29, 2019

@liamzebedee code updated.

Copy link
Contributor

@liamzebedee liamzebedee left a 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.

lib/tbtc-helpers/src/Address.js Show resolved Hide resolved
@@ -1,4 +1,4 @@
const ElectrumCli = require('electrum-client')
const electrumjs = require('electrumjs')
Copy link
Contributor

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?

Copy link
Member Author

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.

lib/tbtc-helpers/src/ElectrumClient.js Show resolved Hide resolved
client/src/Deposit.js Show resolved Hide resolved
@@ -0,0 +1,24 @@
// PAGE 3: Pay BTC
Copy link
Contributor

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.

Copy link
Contributor

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. :)

Copy link
Member Author

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

Copy link
Member Author

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.

client/test/FundingProofTest.js Show resolved Hide resolved
client/src/FundingProof.js Show resolved Hide resolved
@nkuba
Copy link
Member Author

nkuba commented Jul 29, 2019

@liamzebedee ready for a next round.

@nkuba
Copy link
Member Author

nkuba commented Jul 30, 2019

@liamzebedee ready for a next round.

@nkuba nkuba requested a review from liamzebedee July 30, 2019 12:48
@liamzebedee liamzebedee merged commit 3e9e0ac into master Jul 30, 2019
@Shadowfiend Shadowfiend deleted the funding branch July 30, 2019 13:56
@liamzebedee liamzebedee mentioned this pull request Jul 31, 2019
liamzebedee added a commit that referenced this pull request Apr 20, 2020
Arc Reactor: Migrate tbtc.js to the latest tBTC contracts
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Electrum Client implementation In-browser SPV proof and transaction details parsing
4 participants