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

Commit

Permalink
Merge pull request #1 from keep-network/master
Browse files Browse the repository at this point in the history
upstream sync
  • Loading branch information
daramir authored Sep 23, 2020
2 parents fad577c + 40a8660 commit fc4627e
Show file tree
Hide file tree
Showing 9 changed files with 4,132 additions and 3,828 deletions.
2 changes: 1 addition & 1 deletion bin/tbtc.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env node --experimental-modules
#!/usr/bin/env NODE_BACKEND=js node --experimental-modules --experimental-json-modules
import Subproviders from "@0x/subproviders"
import Web3 from "web3"
import ProviderEngine from "web3-provider-engine"
Expand Down
7,818 changes: 4,051 additions & 3,767 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@keep-network/tbtc.js",
"version": "0.18.0-rc",
"version": "0.18.2",
"type": "module",
"description": "tbtc.js provides JS bindings to the tBTC system that establishes a TBTC ERC20 token supply-pegged to BTC.",
"repository": {
Expand Down Expand Up @@ -30,20 +30,20 @@
"author": "Antonio Salazar Cardozo <[email protected]>",
"license": "MIT",
"dependencies": {
"@keep-network/keep-ecdsa": ">1.2.0-rc <1.2.0",
"@keep-network/tbtc": ">1.1.0-rc <1.1.0",
"bcoin": "git+https://github.com/bcoin-org/bcoin.git#v2.1.2",
"bcrypto": "^4.1.0",
"@keep-network/keep-ecdsa": "1.2.1",
"@keep-network/tbtc": "1.1.0",
"bcoin": "git+https://github.com/keep-network/bcoin.git#355c21aec91128362668162fe5a309dbc0c59c75",
"bufio": "^1.0.6",
"electrum-client-js": "git+https://github.com/keep-network/electrum-client-js.git#v0.1.0",
"web3-utils": "^1.2.8"
},
"peerDependencies": {
"web3": "^1.2.0",
"web3": "^1.2.11",
"web3-provider-engine": "^15.0.7"
},
"devDependencies": {
"@0x/subproviders": "^6.0.8",
"web3": "^1.2.11",
"chai": "^4.2.0",
"eslint": "^6.8.0",
"eslint-config-keep": "git+https://github.com/keep-network/eslint-config-keep.git#0.3.0",
Expand Down
63 changes: 40 additions & 23 deletions src/BitcoinHelpers.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
/** @typedef { import("web3-eth-contract").Contract } EthereumContract */

import secp256k1 from "bcrypto/lib/secp256k1.js"
import BcryptoSignature from "bcrypto/lib/internal/signature.js"
import BcoinPrimitives from "bcoin/lib/primitives/index.js"
import secp256k1 from "bcrypto/lib/secp256k1-browser.js"
import KeyRing from "bcoin/lib/primitives/keyring.js"
import Outpoint from "bcoin/lib/primitives/outpoint.js"
import Input from "bcoin/lib/primitives/input.js"
import Output from "bcoin/lib/primitives/output.js"
import TX from "bcoin/lib/primitives/tx.js"
import BcoinScript from "bcoin/lib/script/index.js"

import { BitcoinSPV } from "./lib/BitcoinSPV.js"
Expand All @@ -12,8 +15,8 @@ import ElectrumClient from "./lib/ElectrumClient.js"
/** @typedef { import("./lib/ElectrumClient.js").Config } ElectrumConfig */

import BN from "bn.js"
import { backoffRetrier } from "./lib/backoff.js"

const { KeyRing, Outpoint, Input, Output, TX } = BcoinPrimitives
const { Script } = BcoinScript

/** @enum {string} */
Expand Down Expand Up @@ -112,13 +115,6 @@ const BitcoinHelpers = {
* @return {Buffer} The signature in the DER format.
*/
signatureDER: function(r, s) {
const size = secp256k1.size
const signature = new BcryptoSignature(
size,
Buffer.from(r, "hex"),
Buffer.from(s, "hex")
)

// Verifies if either of `r` or `s` values equals zero or is greater or equal
// curve's order. If so throws an error.
// Checks if `s` is a high value. As per BIP-0062 signature's `s` value should
Expand All @@ -127,10 +123,10 @@ const BitcoinHelpers = {
// Checks `s` per BIP-62: signature's `s` value should be in a low half of
// curve's order. If it's not, it's converted to `-s`.
const bitcoinSignature = secp256k1.signatureNormalize(
signature.encode(size)
Buffer.from(r.concat(s), "hex")
)

return BcryptoSignature.toDER(bitcoinSignature, size)
return secp256k1.signatureExport(bitcoinSignature)
},
/**
* Takes the x and y coordinates of a public key point and returns a
Expand Down Expand Up @@ -191,6 +187,25 @@ const BitcoinHelpers = {

return address.toBech32(network)
},
/**
* Converts a public key to the public key byte format expected by various
* bcoin functions.
*
* @param {string} publicKeyString Public key as a hexadecimal
* representation of 64-byte concatenation of x and y coordinates.
* @return {{ x: Buffer, y: Buffer }} An object containing the x and y
* components of the public key as separate buffers.
*/
splitPublicKey: function(publicKeyString) {
const [xString, yString] = [
publicKeyString.substring(0, publicKeyString.length / 2),
publicKeyString.substring(publicKeyString.length / 2)
]
return {
x: Buffer.from(xString, "hex"),
y: Buffer.from(yString, "hex")
}
},
/**
* Converts public key to bitcoin Witness Public Key Hash Address according to
* [BIP-173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki).
Expand All @@ -200,12 +215,13 @@ const BitcoinHelpers = {
* @return {string} A Bitcoin P2WPKH address for given network.
*/
publicKeyToP2WPKHAddress: function(publicKeyString, network) {
const publicKeyBytes = Buffer.from(publicKeyString, "hex")

// Witness program requires usage of compressed public keys.
const compress = true

const publicKey = secp256k1.publicKeyImport(publicKeyBytes, compress)
const publicKey = secp256k1.publicKeyImport(
BitcoinHelpers.Address.splitPublicKey(publicKeyString),
compress
)
const keyRing = KeyRing.fromKey(publicKey, compress)
const p2wpkhAddress = Script.fromProgram(
0,
Expand Down Expand Up @@ -459,11 +475,13 @@ const BitcoinHelpers = {
* contract and returns its reported minimum fee, rather than
* calling electrumClient.blockchainEstimateFee.
*
* @return {Promise<number>} The estimated fee to execute the provided
* @return {Promise<BN>} The estimated fee to execute the provided
* transaction.
*/
estimateFee: async function(tbtcConstantsContract) {
return tbtcConstantsContract.methods.getMinimumRedemptionFee().call()
return new BN(
await tbtcConstantsContract.methods.getMinimumRedemptionFee().call()
).muln(4)
},
/**
* For the given `transactionID`, constructs an SPV proof that proves it
Expand All @@ -481,10 +499,9 @@ const BitcoinHelpers = {
getSPVProof: async function(transactionID, confirmations) {
return await BitcoinHelpers.withElectrumClient(async electrumClient => {
const spv = new BitcoinSPV(electrumClient)
const proof = await spv.getTransactionProof(
transactionID,
confirmations
)
const proof = await backoffRetrier(3, err =>
String(err).includes("not in block at height")
)(async () => spv.getTransactionProof(transactionID, confirmations))

return {
...proof,
Expand Down Expand Up @@ -547,7 +564,7 @@ const BitcoinHelpers = {
// Public Key
let compressedPublicKey
try {
const publicKeyBytes = Buffer.from(publicKey, "hex")
const publicKeyBytes = BitcoinHelpers.Address.splitPublicKey(publicKey)
compressedPublicKey = secp256k1.publicKeyImport(publicKeyBytes, true)
} catch (err) {
throw new Error(`failed to import public key: [${err}]`)
Expand Down
2 changes: 1 addition & 1 deletion src/Deposit.js
Original file line number Diff line number Diff line change
Expand Up @@ -831,7 +831,7 @@ export default class Deposit {
this.factory.constants()
)
const utxoValue = await this.contract.methods.utxoValue().call()
const outputValue = toBN(utxoValue).sub(toBN(transactionFee))
const outputValue = toBN(utxoValue).sub(transactionFee)
const outputValueBytes = outputValue.toArrayLike(Buffer, "le", 8)

let transaction
Expand Down
29 changes: 16 additions & 13 deletions src/EthereumHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
/** @typedef { import("web3-utils").AbiItem } AbiItem */
/** @typedef { import("web3-core").TransactionReceipt } TransactionReceipt */

import { backoffRetrier } from "./lib/backoff"
import { backoffRetrier } from "./lib/backoff.js"

/**
* @typedef {object} DeploymentInfo
Expand Down Expand Up @@ -166,14 +166,14 @@ function bytesToRaw(bytesString) {
* with `estimateGas`, `send`, and `call` variants available.
* @param {Partial<SendOptions>} [sendParams] The parameters to pass to
* `estimateGas` and `send` for transaction processing.
* @param {boolean} [forceSend=false] Force the transaction send through even
* @param {boolean} [forceSend] Force the transaction send through even
* if gas estimation fails.
*
* @return {Promise<any>} A promise to the result of sending the bound contract
* method. Fails the promise if gas estimation fails, extracting an
* on-chain error if possible.
*/
async function sendSafely(boundContractMethod, sendParams, forceSend) {
async function sendSafely(boundContractMethod, sendParams, forceSend = false) {
try {
// Clone `sendParams` so we aren't exposed to providers that modify `sendParams`.
const gasEstimate = await boundContractMethod.estimateGas({ ...sendParams })
Expand Down Expand Up @@ -218,12 +218,12 @@ async function sendSafely(boundContractMethod, sendParams, forceSend) {
*
* @param {ContractSendMethod} boundContractMethod A bound web3 contract method
* with `estimateGas`, `send`, and `call` variants available.
* @param {Partial<SendOptions>} sendParams The parameters to pass to
* @param {Partial<SendOptions>} [sendParams] The parameters to pass to
* `estimateGas` and `send` for transaction processing.
* @param {boolean} forceSend Force the transaction send through even if gas
* estimation fails.
* @param {number} totalAttempts Total attempts number which should be performed
* in case of an error before rethrowing it to the caller.
* @param {boolean} [forceSend] Force the transaction send through even
* if gas estimation fails.
* @param {number} [totalAttempts] Total attempts number which should be
* performed in case of an error before rethrowing it to the caller.
*
* @return {Promise<any>} A promise to the result of sending the bound contract
* method. Fails the promise if gas estimation fails, extracting an
Expand All @@ -232,8 +232,8 @@ async function sendSafely(boundContractMethod, sendParams, forceSend) {
async function sendSafelyRetryable(
boundContractMethod,
sendParams,
forceSend,
totalAttempts
forceSend = false,
totalAttempts = 3
) {
return backoffRetrier(totalAttempts)(async () => {
return await sendSafely(boundContractMethod, sendParams, forceSend)
Expand All @@ -254,9 +254,12 @@ async function sendSafelyRetryable(
* method. Fails the promise if gas estimation fails, extracting an
* on-chain error if possible.
*/
async function callWithRetry(boundContractMethod, sendParams, totalAttempts) {
const retries = typeof totalAttempts === "undefined" ? 3 : totalAttempts
return backoffRetrier(retries)(async () => {
async function callWithRetry(
boundContractMethod,
sendParams,
totalAttempts = 3
) {
return backoffRetrier(totalAttempts)(async () => {
// @ts-ignore A newer version of Web3 is needed to include call in TS.
return await boundContractMethod.call({
from: "", // FIXME Need systemic handling of default from address.
Expand Down
2 changes: 1 addition & 1 deletion src/lib/BitcoinSPV.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// JS implementation of merkle.py script from [summa-tx/bitcoin-spv] repository.
//
// [summa-tx/bitcoin-spv]: https://github.com/summa-tx/bitcoin-spv/
import Hash256 from "bcrypto/lib/hash256.js"
import Hash256 from "bcrypto/lib/hash256-browser.js"
import BcryptoMerkle from "bcrypto/lib/merkle.js"
const { deriveRoot } = BcryptoMerkle

Expand Down
16 changes: 6 additions & 10 deletions src/lib/ElectrumClient.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import ElectrumClient from "electrum-client-js"
import sha256 from "bcrypto/lib/sha256.js"
import { backoffRetrier } from "./backoff"
import sha256 from "bcrypto/lib/sha256-browser.js"
const { digest } = sha256

/**
Expand Down Expand Up @@ -405,14 +404,11 @@ export default class Client {
* hexadecimal form.
*/
async getTransactionMerkle(txHash, blockHeight) {
return backoffRetrier(3, _ => _.message.includes("not in block at height"))(
async () =>
/** @type {TransactionMerkleBranch} */ (await this.electrumClient
.blockchain_transaction_getMerkle(txHash, blockHeight)
.catch(err => {
throw new Error(`failed to get transaction merkle: [${err}]`)
}))
)
return /** @type {TransactionMerkleBranch} */ (await this.electrumClient
.blockchain_transaction_getMerkle(txHash, blockHeight)
.catch(err => {
throw new Error(`failed to get transaction merkle: [${err}]`)
}))
}

/**
Expand Down
16 changes: 10 additions & 6 deletions src/lib/backoff.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,18 @@ export function retryAll(error) {
* @template T
* @param {number} retries The number of retries to perform before bubbling the
* failure out.
* @param {ErrorMatcherFn} [matcher=retryAll] A matcher function that receives
* the error when an exception is thrown, and returns true if
* @param {ErrorMatcherFn} [errorMatcher=retryAll] A matcher function that
* receives the error when an exception is thrown, and returns true if
* the error should lead to a retry. A false return will rethrow the
* error and terminate the retry loop.
* @return {RetrierFn<T>}
*/
export function backoffRetrier(retries, matcher) {
const errorMatcher = matcher || retryAll // default to always retrying

return async (/** @type {() => Promise<any>} */ fn) => {
export function backoffRetrier(retries, errorMatcher = retryAll) {
/**
* @param {function(): Promise<T>} fn
* @return {Promise<T>}
*/
return async fn => {
for (let attempt = 0; attempt < retries; attempt++) {
try {
console.debug(`making attempt number ${attempt}`)
Expand Down

0 comments on commit fc4627e

Please sign in to comment.