Skip to content

Commit

Permalink
Merge branch 'dev' of github.com:liquality/chainabstractionlayer into…
Browse files Browse the repository at this point in the history
… dev
  • Loading branch information
harshjv committed Mar 1, 2019
2 parents 7673eb9 + a15d96e commit 86d2586
Show file tree
Hide file tree
Showing 11 changed files with 359 additions and 108 deletions.
8 changes: 6 additions & 2 deletions src/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ export default class Client {
* Generate a secret.
* @param {!string} message - Message to be used for generating secret.
* @param {!string} address - can pass address for async claim and refunds to get deterministic secret
* @return {Promise<string>} Resolves with secret
* @return {Promise<string>} Resolves with a 32 byte secret
*/
async generateSecret (message) {
const address = (await this.getMethod('getAddresses')())[0].address
Expand Down Expand Up @@ -480,7 +480,7 @@ export default class Client {
* @param {!string} initiationTxHash - The transaction hash of the swap initiation.
* @param {!string} recipientAddress - Recepient address for the swap in hex.
* @param {!string} refundAddress - Refund address for the swap in hex.
* @param {!string} secret - Secret for the swap in hex.
* @param {!string} secret - 32 byte secret for the swap in hex.
* @param {!number} expiration - Expiration time for the swap.
* @return {Promise<string, TypeError>} Resolves with redeem swap contract bytecode.
* Rejects with InvalidProviderResponseError if provider's response is invalid.
Expand All @@ -490,6 +490,10 @@ export default class Client {
throw new TypeError('Initiation transaction hash should be a valid hex string')
}

if (!(/[A-Fa-f0-9]{64}/.test(secret))) {
throw new TypeError('Secret should be a 32 byte hex string')
}

return this.getMethod('claimSwap')(initiationTxHash, recipientAddress, refundAddress, secret, expiration)
}

Expand Down
47 changes: 28 additions & 19 deletions src/providers/bitcoin/BitcoinJsLibSwapProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ export default class BitcoinJsLibSwapProvider extends Provider {

return [
'63', // OP_IF
'82', // OP_SIZE
'01', // OP_PUSHDATA(1)
'20', // Hex 32
'88', // OP_EQUALVERIFY
'a8', // OP_SHA256
'20', secretHash, // OP_PUSHDATA(20) {secretHash}
'88', // OP_EQUALVERIFY
Expand Down Expand Up @@ -137,22 +141,27 @@ export default class BitcoinJsLibSwapProvider extends Provider {
return initiateSwapTransaction
}

async findClaimSwapTransaction (initiationTxHash, secretHash) {
let blockNumber = await this.getMethod('getBlockHeight')()
let claimSwapTransaction = null
while (!claimSwapTransaction) {
let block
try {
block = await this.getMethod('getBlockByNumber')(blockNumber, true)
} catch (e) { }
if (block) {
claimSwapTransaction = block.transactions.find(tx =>
tx._raw.vin.find(vin => vin.txid === initiationTxHash)
)
blockNumber++
}
async findSwapTransaction (recipientAddress, refundAddress, secretHash, expiration, predicate) {
const script = this.createSwapScript(recipientAddress, refundAddress, secretHash, expiration)
const scriptPubKey = padHexStart(script)
const p2shAddress = pubKeyToAddress(scriptPubKey, this._network.name, 'scriptHash')
let swapTransaction = null
while (!swapTransaction) {
let p2shTransactions = await this.getMethod('getAddressDeltas')([p2shAddress])
const p2shMempoolTransactions = await this.getMethod('getAddressMempool')([p2shAddress])
p2shTransactions = p2shTransactions.concat(p2shMempoolTransactions)
const transactionIds = p2shTransactions.map(tx => tx.txid)
const transactions = await Promise.all(transactionIds.map(this.getMethod('getTransactionByHash')))
swapTransaction = transactions.find(predicate)
await sleep(5000)
}
return swapTransaction
}

async findClaimSwapTransaction (initiationTxHash, recipientAddress, refundAddress, secretHash, expiration) {
const claimSwapTransaction = await this.findSwapTransaction(recipientAddress, refundAddress, secretHash, expiration,
tx => tx._raw.vin.find(vin => vin.txid === initiationTxHash)
)

return {
...claimSwapTransaction,
Expand All @@ -161,11 +170,11 @@ export default class BitcoinJsLibSwapProvider extends Provider {
}

async getSwapSecret (claimTxHash) {
const claimTxRaw = await this.getMethod('getRawTransactionByHash')(claimTxHash)
const claimTx = await this.getMethod('decodeRawTransaction')(claimTxRaw)
const script = Buffer.from(claimTx._raw.data.vin[0].scriptSig.hex, 'hex')
const claimTx = await this.getMethod('getTransactionByHash')(claimTxHash)
const script = Buffer.from(claimTx._raw.vin[0].scriptSig.hex, 'hex')
const sigLength = script[0]
const secretLength = script.slice(sigLength + 1)[0]
return script.slice(sigLength + 2, sigLength + secretLength + 2).toString('hex')
const pubKeyLen = script.slice(sigLength + 1)[0]
const secretLength = script.slice(sigLength + pubKeyLen + 2)[0]
return script.slice(sigLength + pubKeyLen + 3, sigLength + pubKeyLen + secretLength + 3).toString('hex')
}
}
19 changes: 12 additions & 7 deletions src/providers/bitcoin/BitcoinSwapProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ export default class BitcoinSwapProvider extends Provider {

return [
'63', // OP_IF
'82', // OP_SIZE
'01', // OP_PUSHDATA(1)
'20', // Hex 32
'88', // OP_EQUALVERIFY
'a8', // OP_SHA256
'20', secretHash, // OP_PUSHDATA(20) {secretHash}
'88', // OP_EQUALVERIFY
Expand Down Expand Up @@ -62,7 +66,7 @@ export default class BitcoinSwapProvider extends Provider {
async _redeemSwap (initiationTxHash, recipientAddress, refundAddress, secretParam, expiration, isClaim) {
const feePerByte = await this.getMethod('getFeePerByte')()
const secretHash = isClaim ? sha256(secretParam) : secretParam
const lockTime = isClaim ? 0 : expiration + 100
const lockTime = isClaim ? 0 : expiration
const lockTimeHex = isClaim ? padHexStart('0', 8) : padHexStart(scriptNumEncode(lockTime).toString('hex'), 8)
const to = isClaim ? recipientAddress : refundAddress
const script = this.createSwapScript(recipientAddress, refundAddress, secretHash, expiration)
Expand Down Expand Up @@ -105,7 +109,7 @@ export default class BitcoinSwapProvider extends Provider {
padHexStart((secret.length / 2).toString(16)), // OP_PUSHDATA({secretLength})
secret
]
: ['00'] // OP_0
: [] // OP_0

const signatureEncoded = signature + '01'
const signaturePushDataOpcode = padHexStart((signatureEncoded.length / 2).toString(16))
Expand All @@ -114,10 +118,10 @@ export default class BitcoinSwapProvider extends Provider {
const bytecode = [
signaturePushDataOpcode,
signatureEncoded,
...encodedSecret,
redeemEncoded,
pubKeyPushDataOpcode,
pubKey
pubKey,
...encodedSecret,
redeemEncoded
]

return bytecode.join('')
Expand Down Expand Up @@ -187,8 +191,9 @@ export default class BitcoinSwapProvider extends Provider {
const claimTx = await this.getMethod('getTransactionByHash')(claimTxHash)
const script = Buffer.from(claimTx._raw.vin[0].scriptSig.hex, 'hex')
const sigLength = script[0]
const secretLength = script.slice(sigLength + 1)[0]
return script.slice(sigLength + 2, sigLength + secretLength + 2).toString('hex')
const pubKeyLen = script.slice(sigLength + 1)[0]
const secretLength = script.slice(sigLength + pubKeyLen + 2)[0]
return script.slice(sigLength + pubKeyLen + 3, sigLength + pubKeyLen + secretLength + 3).toString('hex')
}

generateSigTxInput (txHashLE, voutIndex, script) {
Expand Down
19 changes: 13 additions & 6 deletions src/providers/ethereum/EthereumSwapProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export default class EthereumSwapProvider extends Provider {
'60', refundDestinationEncoded, // PUSH1 {refundDestinationEncoded}
'57',

'00', // STOP
'fe', // INVALID

'5b', // JUMPDEST
'73', ensureAddressStandardFormat(recipientAddress), // PUSH20 {recipientAddressEncoded}
Expand All @@ -83,8 +83,7 @@ export default class EthereumSwapProvider extends Provider {

async claimSwap (initiationTxHash, recipientAddress, refundAddress, secret, expiration) {
const initiationTransaction = await this.getMethod('getTransactionReceipt')(initiationTxHash)
const data = padHexStart(secret, 64)
return this.getMethod('sendTransaction')(initiationTransaction.contractAddress, 0, data)
return this.getMethod('sendTransaction')(initiationTransaction.contractAddress, 0, secret)
}

async refundSwap (initiationTxHash, recipientAddress, refundAddress, secretHash, expiration) {
Expand All @@ -108,24 +107,32 @@ export default class EthereumSwapProvider extends Provider {
while (!initiateSwapTransaction) {
const block = await this.getMethod('getBlockByNumber')(blockNumber, true)
if (block) {
initiateSwapTransaction = block.transactions.find(transaction =>
const transaction = block.transactions.find(transaction =>
this.doesTransactionMatchSwapParams(transaction, value, recipientAddress, refundAddress, secretHash, expiration)
)
if (transaction) {
const transactionReceipt = await this.getMethod('getTransactionReceipt')(transaction.hash)
if (transactionReceipt.status === '1') initiateSwapTransaction = transaction
}
blockNumber++
}
await sleep(5000)
}
return initiateSwapTransaction
}

async findClaimSwapTransaction (initiationTxHash, secretHash) {
async findClaimSwapTransaction (initiationTxHash) {
let blockNumber = await this.getMethod('getBlockHeight')()
let claimSwapTransaction = null
while (!claimSwapTransaction) {
const initiationTransaction = await this.getMethod('getTransactionReceipt')(initiationTxHash)
const block = await this.getMethod('getBlockByNumber')(blockNumber, true)
if (block && initiationTransaction) {
claimSwapTransaction = block.transactions.find(transaction => transaction.to === initiationTransaction.contractAddress)
const transaction = block.transactions.find(transaction => transaction.to === initiationTransaction.contractAddress)
if (transaction) {
const transactionReceipt = await this.getMethod('getTransactionReceipt')(transaction.hash)
if (transactionReceipt.status === '1') claimSwapTransaction = transaction
}
blockNumber++
}
await sleep(5000)
Expand Down
37 changes: 6 additions & 31 deletions test/integration/swap/chainToChain.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import chai, { expect } from 'chai'
import chaiAsPromised from 'chai-as-promised'
import { crypto } from '../../../src'
import { chains, metaMaskConnector, initiateAndVerify, claimAndVerify, getSwapParams } from './common'
import { chains, initiateAndVerify, claimAndVerify, getSwapParams, mineBitcoinBlocks, connectMetaMask } from './common'
import config from './config'

process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0
Expand All @@ -19,7 +19,8 @@ async function testSwap (chain1, chain2) {

const chain1InitiationTxId = await initiateAndVerify(chain1, secretHash, chain1SwapParams)
const chain2InitiationTxId = await initiateAndVerify(chain2, secretHash, chain2SwapParams)
const revealedSecret = await claimAndVerify(chain1, chain1InitiationTxId, secret, chain1SwapParams)
const claimTx = await claimAndVerify(chain1, chain1InitiationTxId, secret, chain1SwapParams)
const revealedSecret = claimTx.secret
expect(revealedSecret).to.equal(secret)
await claimAndVerify(chain2, chain2InitiationTxId, revealedSecret, chain2SwapParams)
}
Expand All @@ -28,22 +29,8 @@ describe('Swap Chain to Chain', function () {
this.timeout(config.timeout)

describe('Ledger to MetaMask', function () {
let interval

before(async () => {
console.log('\x1b[36m', 'Starting MetaMask connector on http://localhost:3333 - Open in browser to continue', '\x1b[0m')
await metaMaskConnector.start()
if (config.bitcoin.mineBlocks) {
interval = setInterval(() => {
chains.bitcoinWithNode.client.generateBlock(1)
}, 1000)
}
})

after(async () => {
await metaMaskConnector.stop()
if (config.bitcoin.mineBlocks) clearInterval(interval)
})
mineBitcoinBlocks()
connectMetaMask()

it('BTC (Ledger) - ETH (MetaMask)', async () => {
await testSwap(chains.bitcoinWithLedger, chains.ethereumWithMetaMask)
Expand All @@ -55,19 +42,7 @@ describe('Swap Chain to Chain', function () {
})

describe('Node to Node', function () {
if (config.bitcoin.mineBlocks) {
let interval

before(async () => {
interval = setInterval(() => {
chains.bitcoinWithNode.client.generateBlock(1)
}, 1000)
})

after(async () => {
clearInterval(interval)
})
}
mineBitcoinBlocks()

it('BTC (Node) - ETH (Node)', async () => {
await testSwap(chains.bitcoinWithNode, chains.ethereumWithNode)
Expand Down
78 changes: 70 additions & 8 deletions test/integration/swap/common.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { expect } from 'chai'
/* eslint-env mocha */
import chai, { expect } from 'chai'
import chaiAsPromised from 'chai-as-promised'
import MetaMaskConnector from 'node-metamask'
import { Client, providers, crypto } from '../../../src'
import { Client, Provider, providers, crypto } from '../../../src'
import config from './config'

chai.use(chaiAsPromised)

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

const metaMaskConnector = new MetaMaskConnector({ port: config.ethereum.metaMaskConnector.port })

const bitcoinNetworks = providers.bitcoin.networks
Expand All @@ -15,15 +21,27 @@ const bitcoinWithNode = new Client()
bitcoinWithNode.addProvider(new providers.bitcoin.BitcoreRPCProvider(config.bitcoin.rpc.host, config.bitcoin.rpc.username, config.bitcoin.rpc.password))
bitcoinWithNode.addProvider(new providers.bitcoin.BitcoinJsLibSwapProvider({ network: bitcoinNetworks[config.bitcoin.network] }))

// TODO: required for BITCOIN too?
class RandomEthereumAddressProvider extends Provider {
getUnusedAddress () { // Mock unique address
const randomString = parseInt(Math.random() * 1000000000000).toString()
const randomHash = crypto.sha256(randomString)
const address = randomHash.substr(0, 40)
return { address }
}
}

const ethereumNetworks = providers.ethereum.networks
const ethereumWithMetaMask = new Client()
ethereumWithMetaMask.addProvider(new providers.ethereum.EthereumRPCProvider(config.ethereum.rpc.host))
ethereumWithMetaMask.addProvider(new providers.ethereum.EthereumMetaMaskProvider(metaMaskConnector.getProvider(), ethereumNetworks[config.ethereum.network]))
ethereumWithMetaMask.addProvider(new providers.ethereum.EthereumSwapProvider())
ethereumWithMetaMask.addProvider(new RandomEthereumAddressProvider())

const ethereumWithNode = new Client()
ethereumWithNode.addProvider(new providers.ethereum.EthereumRPCProvider(config.ethereum.rpc.host))
ethereumWithNode.addProvider(new providers.ethereum.EthereumSwapProvider())
ethereumWithNode.addProvider(new RandomEthereumAddressProvider())

const chains = {
bitcoinWithLedger: { id: 'Bitcoin Ledger', name: 'bitcoin', client: bitcoinWithLedger },
Expand All @@ -33,9 +51,8 @@ const chains = {
}

async function getSwapParams (chain) {
const unusedAddress = await chain.client.getUnusedAddress()
const recipientAddress = unusedAddress.address
const refundAddress = unusedAddress.address
const recipientAddress = (await chain.client.getUnusedAddress()).address
const refundAddress = (await chain.client.getUnusedAddress()).address
const expiration = parseInt(Date.now() / 1000) + parseInt(Math.random() * 1000000)
const value = config[chain.name].value

Expand Down Expand Up @@ -74,9 +91,54 @@ async function claimAndVerify (chain, initiationTxId, secret, swapParams) {
chain.client.findClaimSwapTransaction(initiationTxId, swapParams.recipientAddress, swapParams.refundAddress, secretHash, swapParams.expiration),
chain.client.claimSwap(initiationTxId, swapParams.recipientAddress, swapParams.refundAddress, secret, swapParams.expiration)
])
expect(claimTx.hash).to.equal(claimTxId)
console.log(`${chain.id} Claimed ${claimTxId}`)
return claimTx.secret
return claimTx
}

async function refund (chain, initiationTxId, secretHash, swapParams) {
console.log('\x1b[33m', `Refunding ${chain.id}: Watch prompt on wallet`, '\x1b[0m')
const refundTxId = await chain.client.refundSwap(initiationTxId, swapParams.recipientAddress, swapParams.refundAddress, secretHash, swapParams.expiration)
console.log(`${chain.id} Refunded ${refundTxId}`)
return refundTxId
}

export { chains, metaMaskConnector, initiateAndVerify, claimAndVerify, getSwapParams }
async function expectBalance (chain, address, func, comparison) {
const balanceBefore = await chain.client.getBalance([address])
await func()
await sleep(5000) // Await block time
const balanceAfter = await chain.client.getBalance([address])
comparison(balanceBefore, balanceAfter)
}

function mineBitcoinBlocks () {
if (config.bitcoin.mineBlocks) {
let interval
before(async () => {
interval = setInterval(() => {
chains.bitcoinWithNode.client.generateBlock(1)
}, 1000)
})
after(() => clearInterval(interval))
}
}

function connectMetaMask () {
before(async () => {
console.log('\x1b[36m', 'Starting MetaMask connector on http://localhost:3333 - Open in browser to continue', '\x1b[0m')
await metaMaskConnector.start()
})
after(async () => metaMaskConnector.stop())
}

export {
chains,
metaMaskConnector,
initiateAndVerify,
claimAndVerify,
refund,
getSwapParams,
expectBalance,
sleep,
mineBitcoinBlocks,
connectMetaMask
}

0 comments on commit 86d2586

Please sign in to comment.