From 1ba190a5a7df9a6f387fdad52131d950e693583d Mon Sep 17 00:00:00 2001 From: Kosta Korenkov Date: Mon, 25 Nov 2019 08:40:16 +0300 Subject: [PATCH 1/2] feat(exit): emit signed exit transfer tx details before signing the exit message. This way the client has a chance to store the transfer tx, to resume exit process later e.g. if the user rejected exit signature. --- index.d.ts | 28 ++++++++++++----- lib/exit.js | 86 ++++++++++++++++++++++++++++++---------------------- package.json | 3 +- yarn.lock | 18 +++++++++++ 4 files changed, 91 insertions(+), 44 deletions(-) diff --git a/index.d.ts b/index.d.ts index 3817310..64121a6 100644 --- a/index.d.ts +++ b/index.d.ts @@ -9,7 +9,7 @@ declare module "leap-core" { import Web3 from 'web3'; import { Callback } from 'web3/types'; - import { Transaction } from 'web3/eth/types'; + import { Transaction, PromiEvent } from 'web3/eth/types'; import { BigIntType } from 'jsbi-utils'; export enum Type { @@ -317,6 +317,13 @@ declare module "leap-core" { }>; } + export type FastSellRequest = { + tx: Transaction; + sigHashBuff: Buffer; + effectiveBlock: number; + signedData?: Array; + }; + type SpendCondSimResult = { error?: string; outputs: Output[]; @@ -369,17 +376,24 @@ declare module "leap-core" { amount: BigIntType | number, color: number, plasmaChain: ExtendedWeb3, - rootChain: ExtendedWeb3, + rootChain: Web3, marketMakerUrl: string, - signer: Signer, - ): Promise; + signer?: Signer, + ): PromiEvent; static fastSellUTXO( utxo: Unspent, plasmaChain: ExtendedWeb3, - rootChain: ExtendedWeb3, + rootChain: Web3, + marketMakerUrl: string, + signer?: Signer, + ): PromiEvent; + + static signAndSendFastSellRequest( + fastSellRequest: FastSellRequest, + rootChain: Web3, marketMakerUrl: string, - signer: Signer, - ): Promise; + signer?: Signer, + ): PromiEvent; } } diff --git a/lib/exit.js b/lib/exit.js index 60e1948..be756a1 100644 --- a/lib/exit.js +++ b/lib/exit.js @@ -1,12 +1,14 @@ import util from 'ethereumjs-util'; import { bi } from 'jsbi-utils'; import fetch from 'node-fetch'; +import Web3PromiEvent from 'web3-core-promievent'; import Tx from './transaction'; import Outpoint from './outpoint'; import Output from './output'; import Input from './input'; -import { periodBlockRange, sendSignedTransaction } from './helpers'; +import Period from './period'; +import { sendSignedTransaction } from './helpers'; export default class Exit { @@ -79,58 +81,70 @@ export default class Exit { } static fastSellUTXO(utxo, plasmaChain, rootChain, marketMakerUrl, signer) { - return plasmaChain.getConfig().then(nodeConfig => { + const promiEvent = Web3PromiEvent(); + plasmaChain.getConfig().then(nodeConfig => { const inputs = [new Input(utxo.outpoint)]; const outputs = [ new Output(utxo.output.value, nodeConfig.exitHandlerAddr, utxo.output.color) ]; const exitingUtxoTransfer = Tx.transfer(inputs, outputs); - return Exit.fastSell(exitingUtxoTransfer, plasmaChain, rootChain, marketMakerUrl, signer); - }) + return Exit.fastSell(exitingUtxoTransfer, plasmaChain, rootChain, marketMakerUrl, promiEvent, signer); + }); + return promiEvent.eventEmitter; } static fastSellAmount(account, amount, color, plasmaChain, rootChain, marketMakerUrl, signer) { - return Promise.all([plasmaChain.getUnspent(account, color), plasmaChain.getConfig()]).then(([unspent, nodeConfig]) => { + const promiEvent = Web3PromiEvent(); + Promise.all([plasmaChain.getUnspent(account, color), plasmaChain.getConfig()]).then(([unspent, nodeConfig]) => { const exitingUtxoTransfer = Tx.transferFromUtxos( unspent, account, nodeConfig.exitHandlerAddr, amount, color, ); - return Exit.fastSell(exitingUtxoTransfer, plasmaChain, rootChain, marketMakerUrl, signer); + return Exit.fastSell(exitingUtxoTransfer, plasmaChain, rootChain, marketMakerUrl, promiEvent, signer); }); + return promiEvent.eventEmitter; } - static fastSell(spendTx, plasmaChain, rootChain, marketMakerUrl, signer) { + static fastSell(spendTx, plasmaChain, rootChain, marketMakerUrl, _promiEvent, signer) { const amount = bi(spendTx.outputs[0].value); + const fastSellRequest = {}; + const promiEvent = _promiEvent || Web3PromiEvent(); + (signer + ? signer.signTx(spendTx) + : spendTx.signWeb3(rootChain) + ).then(signedTx => sendSignedTransaction(plasmaChain, signedTx.hex()) + ).then(transferTx => { + const tx = Tx.fromRaw(transferTx.raw); + const utxoId = (new Outpoint(tx.hash(), 0)).getUtxoId(); + const sigHashBuff = Exit.sigHashBuff(utxoId, amount); - return (signer - ? signer.signTx(spendTx) - : spendTx.signWeb3(rootChain) - ).then(signedTx => sendSignedTransaction(plasmaChain, signedTx.hex()) - ).then(transferTx => { - const tx = Tx.fromRaw(transferTx.raw); - const utxoId = (new Outpoint(tx.hash(), 0)).getUtxoId(); - const sigHashBuff = Exit.sigHashBuff(utxoId, amount); - const sigHash = `0x${sigHashBuff.toString('hex')}`; - - return Promise.all([ - plasmaChain.eth.getTransaction( + fastSellRequest.tx = transferTx; + fastSellRequest.sigHashBuff = sigHashBuff; + [,fastSellRequest.effectiveBlock] = Period.periodBlockRange(transferTx.blockNumber); + + return plasmaChain.eth.getTransaction( util.bufferToHex(tx.inputs[0].prevout.hash) - ), - signer - ? signer.signMessage(sigHash) - : Tx.signMessageWithWeb3(rootChain, sigHash) - ]) - .then(([inputTx, sig]) => { + ); + }).then((inputTx) => { + fastSellRequest.inputTx = inputTx; + promiEvent.eventEmitter.emit('transfer', fastSellRequest); + return Exit.signAndSendFastSellRequest(fastSellRequest, rootChain, marketMakerUrl, signer); + }).then(promiEvent.resolve) + .catch(promiEvent.reject); + return promiEvent.eventEmitter; + } + + static signAndSendFastSellRequest(fastSellRequest, rootChain, marketMakerUrl, signer) { + const sigHash = `0x${fastSellRequest.sigHashBuff.toString('hex')}`; + return (signer + ? signer.signMessage(sigHash) + : Tx.signMessageWithWeb3(rootChain, sigHash)) + .then((sig) => { const vBuff = Buffer.alloc(32); vBuff.writeInt8(sig.v, 31); const signedData = Exit.bufferToBytes32Array( - Buffer.concat([sigHashBuff, Buffer.from(sig.r), Buffer.from(sig.s), vBuff]) + Buffer.concat([fastSellRequest.sigHashBuff, Buffer.from(sig.r), Buffer.from(sig.s), vBuff]) ); - const setup = { - inputTx, - tx: transferTx, - effectiveBlock: periodBlockRange(transferTx.blockNumber)[1], - signedData - }; + fastSellRequest.signedData = signedData; return fetch( marketMakerUrl, @@ -141,10 +155,10 @@ export default class Exit { headers: { "Content-Type": "application/json", }, - body: JSON.stringify(setup), + body: JSON.stringify(fastSellRequest), } - ).then(response => response.json()); - }) - }); + ) + .then(response => response.json()); + }); } } diff --git a/package.json b/package.json index 757f1f7..442899c 100755 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "@types/web3": "^1.0.18", "ethereumjs-util": "6.0.0", "jsbi-utils": "^1.0.0", - "node-fetch": "^2.3.0" + "node-fetch": "^2.3.0", + "web3-core-promievent": "^1.2.4" } } diff --git a/yarn.lock b/yarn.lock index 633bc58..deea69b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1015,6 +1015,11 @@ ansi-to-html@^0.6.4: dependencies: entities "^1.1.1" +any-promise@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= + anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" @@ -2445,6 +2450,11 @@ ethjs-util@^0.1.6: is-hex-prefixed "1.0.0" strip-hex-prefix "1.0.0" +eventemitter3@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" + integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== + events@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" @@ -5975,6 +5985,14 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +web3-core-promievent@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.2.4.tgz#75e5c0f2940028722cdd21ba503ebd65272df6cb" + integrity sha512-gEUlm27DewUsfUgC3T8AxkKi8Ecx+e+ZCaunB7X4Qk3i9F4C+5PSMGguolrShZ7Zb6717k79Y86f3A00O0VAZw== + dependencies: + any-promise "1.3.0" + eventemitter3 "3.1.2" + webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" From 77a956e21daedc05bd8c5e048db5d98a618a1625 Mon Sep 17 00:00:00 2001 From: Kosta Korenkov Date: Mon, 25 Nov 2019 11:46:52 +0300 Subject: [PATCH 2/2] correct PromiEvent typedef import --- index.d.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index 64121a6..65ef5f2 100644 --- a/index.d.ts +++ b/index.d.ts @@ -9,7 +9,8 @@ declare module "leap-core" { import Web3 from 'web3'; import { Callback } from 'web3/types'; - import { Transaction, PromiEvent } from 'web3/eth/types'; + import { Transaction } from 'web3/eth/types'; + import PromiEvent from 'web3/promiEvent'; import { BigIntType } from 'jsbi-utils'; export enum Type {