Skip to content

Commit

Permalink
feat(state channels): allow off chain updates to be cancelled with cu…
Browse files Browse the repository at this point in the history
…stom error code (#753)
  • Loading branch information
mpowaga authored and nduchak committed Nov 11, 2019
1 parent ddc6611 commit ae4426e
Show file tree
Hide file tree
Showing 2 changed files with 215 additions and 21 deletions.
97 changes: 80 additions & 17 deletions es/channel/handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,14 @@ function encodeRlpTx (rlpBinary) {
async function appendSignature (tx, signFn) {
const { signatures, encodedTx } = unpackTx(tx).tx
const result = await signFn(encodeRlpTx(encodedTx.rlpEncoded))
if (result) {
if (typeof result === 'string') {
const { tx: signedTx, txType } = unpackTx(result)
return encodeRlpTx(buildTx({
signatures: signatures.concat(signedTx.signatures),
encodedTx: signedTx.encodedTx.rlpEncoded
}, txType).rlpEncoded)
}
return result
}

function handleUnexpectedMessage (channel, message, state) {
Expand Down Expand Up @@ -209,8 +210,14 @@ export async function awaitingOffChainTx (channel, message, state) {
const signedTx = await appendSignature(message.params.data.signed_tx, tx =>
sign(tx, { updates: message.params.data.updates })
)
send(channel, { jsonrpc: '2.0', method: 'channels.update', params: { signed_tx: signedTx } })
return { handler: awaitingOffChainUpdate, state }
if (typeof signedTx === 'string') {
send(channel, { jsonrpc: '2.0', method: 'channels.update', params: { signed_tx: signedTx } })
return { handler: awaitingOffChainUpdate, state }
}
if (typeof signedTx === 'number') {
send(channel, { jsonrpc: '2.0', method: 'channels.update', params: { error: signedTx } })
return { handler: awaitingOffChainTx, state }
}
}
if (message.method === 'channels.error') {
state.reject(new Error(message.data.message))
Expand All @@ -227,6 +234,14 @@ export async function awaitingOffChainTx (channel, message, state) {
}
return { handler: channelOpen }
}
if (message.method === 'channels.conflict') {
state.resolve({
accepted: false,
errorCode: message.params.data.error_code,
errorMessage: message.params.data.error_msg
})
return { handler: channelOpen }
}
return handleUnexpectedMessage(channel, message, state)
}

Expand All @@ -237,7 +252,11 @@ export function awaitingOffChainUpdate (channel, message, state) {
return { handler: channelOpen }
}
if (message.method === 'channels.conflict') {
state.resolve({ accepted: false })
state.resolve({
accepted: false,
errorCode: message.params.data.error_code,
errorMessage: message.params.data.error_msg
})
return { handler: channelOpen }
}
if (message.error) {
Expand All @@ -262,10 +281,14 @@ export async function awaitingTxSignRequest (channel, message, state) {
const signedTx = await appendSignature(message.params.data.signed_tx, tx =>
options.get(channel).sign(tag, tx, { updates: message.params.data.updates })
)
if (signedTx) {
if (typeof signedTx === 'string') {
send(channel, { jsonrpc: '2.0', method: `channels.${tag}`, params: { signed_tx: signedTx } })
return { handler: channelOpen }
}
if (typeof signedTx === 'number') {
send(channel, { jsonrpc: '2.0', method: `channels.${tag}`, params: { error: signedTx } })
return { handler: awaitingUpdateConflict }
}
}
// soft-reject via competing update
send(channel, {
Expand Down Expand Up @@ -338,8 +361,14 @@ export async function awaitingWithdrawTx (channel, message, state) {
const signedTx = await appendSignature(message.params.data.signed_tx, tx =>
sign(tx, { updates: message.params.data.updates })
)
send(channel, { jsonrpc: '2.0', method: 'channels.withdraw_tx', params: { signed_tx: signedTx } })
return { handler: awaitingWithdrawCompletion, state }
if (typeof signedTx === 'string') {
send(channel, { jsonrpc: '2.0', method: 'channels.withdraw_tx', params: { signed_tx: signedTx } })
return { handler: awaitingWithdrawCompletion, state }
}
if (typeof signedTx === 'number') {
send(channel, { jsonrpc: '2.0', method: 'channels.withdraw_tx', params: { error: signedTx } })
return { handler: awaitingWithdrawCompletion, state }
}
}
return handleUnexpectedMessage(channel, message, state)
}
Expand Down Expand Up @@ -369,7 +398,11 @@ export function awaitingWithdrawCompletion (channel, message, state) {
return { handler: channelOpen }
}
if (message.method === 'channels.conflict') {
state.resolve({ accepted: false })
state.resolve({
accepted: false,
errorCode: message.params.data.error_code,
errorMessage: message.params.data.error_msg
})
return { handler: channelOpen }
}
return handleUnexpectedMessage(channel, message, state)
Expand All @@ -386,8 +419,14 @@ export async function awaitingDepositTx (channel, message, state) {
const signedTx = await appendSignature(message.params.data.signed_tx, tx =>
sign(tx, { updates: message.params.data.updates })
)
send(channel, { jsonrpc: '2.0', method: 'channels.deposit_tx', params: { signed_tx: signedTx } })
return { handler: awaitingDepositCompletion, state }
if (typeof signedTx === 'string') {
send(channel, { jsonrpc: '2.0', method: 'channels.deposit_tx', params: { signed_tx: signedTx } })
return { handler: awaitingDepositCompletion, state }
}
if (typeof signedTx === 'number') {
send(channel, { jsonrpc: '2.0', method: 'channels.deposit_tx', params: { error: signedTx } })
return { handler: awaitingDepositCompletion, state }
}
}
return handleUnexpectedMessage(channel, message, state)
}
Expand Down Expand Up @@ -417,7 +456,11 @@ export function awaitingDepositCompletion (channel, message, state) {
return { handler: channelOpen }
}
if (message.method === 'channels.conflict') {
state.resolve({ accepted: false })
state.resolve({
accepted: false,
errorCode: message.params.data.error_code,
errorMessage: message.params.data.error_msg
})
return { handler: channelOpen }
}
return handleUnexpectedMessage(channel, message, state)
Expand All @@ -431,8 +474,14 @@ export async function awaitingNewContractTx (channel, message, state) {
return { handler: awaitingNewContractCompletion, state }
}
const signedTx = await appendSignature(message.params.data.signed_tx, tx => state.sign(tx))
send(channel, { jsonrpc: '2.0', method: 'channels.update', params: { signed_tx: signedTx } })
return { handler: awaitingNewContractCompletion, state }
if (typeof signedTx === 'string') {
send(channel, { jsonrpc: '2.0', method: 'channels.update', params: { signed_tx: signedTx } })
return { handler: awaitingNewContractCompletion, state }
}
if (typeof signedTx === 'number') {
send(channel, { jsonrpc: '2.0', method: 'channels.update', params: { error: signedTx } })
return { handler: awaitingNewContractCompletion, state }
}
}
return handleUnexpectedMessage(channel, message, state)
}
Expand All @@ -453,7 +502,11 @@ export function awaitingNewContractCompletion (channel, message, state) {
return { handler: channelOpen }
}
if (message.method === 'channels.conflict') {
state.resolve({ accepted: false })
state.resolve({
accepted: false,
errorCode: message.params.data.error_code,
errorMessage: message.params.data.error_msg
})
return { handler: channelOpen }
}
return handleUnexpectedMessage(channel, message, state)
Expand All @@ -467,8 +520,14 @@ export async function awaitingCallContractUpdateTx (channel, message, state) {
return { handler: awaitingCallContractCompletion, state }
}
const signedTx = await appendSignature(message.params.data.signed_tx, tx => state.sign(tx))
send(channel, { jsonrpc: '2.0', method: 'channels.update', params: { signed_tx: signedTx } })
return { handler: awaitingCallContractCompletion, state }
if (typeof signedTx === 'string') {
send(channel, { jsonrpc: '2.0', method: 'channels.update', params: { signed_tx: signedTx } })
return { handler: awaitingCallContractCompletion, state }
}
if (typeof signedTx === 'number') {
send(channel, { jsonrpc: '2.0', method: 'channels.update', params: { error: signedTx } })
return { handler: awaitingCallContractCompletion, state }
}
}
return handleUnexpectedMessage(channel, message, state)
}
Expand All @@ -480,7 +539,11 @@ export function awaitingCallContractCompletion (channel, message, state) {
return { handler: channelOpen }
}
if (message.method === 'channels.conflict') {
state.resolve({ accepted: false })
state.resolve({
accepted: false,
errorCode: message.params.data.error_code,
errorMessage: message.params.data.error_msg
})
return { handler: channelOpen }
}
return handleUnexpectedMessage(channel, message, state)
Expand Down
139 changes: 135 additions & 4 deletions test/integration/channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ describe('Channel', function () {
let callerNonce
const initiatorSign = sinon.spy((tag, tx) => initiator.signTransaction(tx))
const responderSign = sinon.spy((tag, tx) => {
if (typeof responderShouldRejectUpdate === 'number') {
return responderShouldRejectUpdate
}
if (responderShouldRejectUpdate) {
return null
}
Expand Down Expand Up @@ -241,6 +244,32 @@ describe('Channel', function () {
})
})

it('can abort update sign request', async () => {
const errorCode = 12345
const result = await initiatorCh.update(
await initiator.address(),
await responder.address(),
100,
() => errorCode
)
result.should.eql({ accepted: false, errorCode, errorMessage: 'user-defined' })
})

it('can abort update with custom error code', async () => {
responderShouldRejectUpdate = 1234
const result = await initiatorCh.update(
await initiator.address(),
await responder.address(),
100,
tx => initiator.signTransaction(tx)
)
result.should.eql({
accepted: false,
errorCode: responderShouldRejectUpdate,
errorMessage: 'user-defined'
})
})

it('can post bignumber update and accept', async () => {
responderShouldRejectUpdate = false
const sign = sinon.spy(initiator.signTransaction.bind(initiator))
Expand Down Expand Up @@ -423,7 +452,7 @@ describe('Channel', function () {
sign,
{ onOnChainTx, onOwnWithdrawLocked, onWithdrawLocked }
)
result.should.eql({ accepted: false })
result.should.eql({ ...result, accepted: false })
sinon.assert.notCalled(onOnChainTx)
sinon.assert.notCalled(onOwnWithdrawLocked)
sinon.assert.notCalled(onWithdrawLocked)
Expand Down Expand Up @@ -462,6 +491,28 @@ describe('Channel', function () {
})
})

it('can abort withdraw sign request', async () => {
const errorCode = 12345
const result = await initiatorCh.withdraw(
100,
() => errorCode
)
result.should.eql({ accepted: false, errorCode, errorMessage: 'user-defined' })
})

it('can abort withdraw with custom error code', async () => {
responderShouldRejectUpdate = 12345
const result = await initiatorCh.withdraw(
100,
tx => initiator.signTransaction(tx)
)
result.should.eql({
accepted: false,
errorCode: responderShouldRejectUpdate,
errorMessage: 'user-defined'
})
})

it('can request a deposit and accept', async () => {
const sign = sinon.spy(initiator.signTransaction.bind(initiator))
const amount = BigNumber('2e18')
Expand Down Expand Up @@ -526,7 +577,7 @@ describe('Channel', function () {
sign,
{ onOnChainTx, onOwnDepositLocked, onDepositLocked }
)
result.should.eql({ accepted: false })
result.should.eql({ ...result, accepted: false })
sinon.assert.notCalled(onOnChainTx)
sinon.assert.notCalled(onOwnDepositLocked)
sinon.assert.notCalled(onDepositLocked)
Expand All @@ -553,6 +604,28 @@ describe('Channel', function () {
})
})

it('can abort deposit sign request', async () => {
const errorCode = 12345
const result = await initiatorCh.deposit(
100,
() => errorCode
)
result.should.eql({ accepted: false, errorCode, errorMessage: 'user-defined' })
})

it('can abort deposit with custom error code', async () => {
responderShouldRejectUpdate = 12345
const result = await initiatorCh.deposit(
100,
tx => initiator.signTransaction(tx)
)
result.should.eql({
accepted: false,
errorCode: responderShouldRejectUpdate,
errorMessage: 'user-defined'
})
})

it('can close a channel', async () => {
const sign = sinon.spy(initiator.signTransaction.bind(initiator))
const result = await initiatorCh.shutdown(sign)
Expand Down Expand Up @@ -782,7 +855,39 @@ describe('Channel', function () {
vmVersion: 4,
abiVersion: 1
}, async (tx) => initiator.signTransaction(tx))
result.should.eql({ accepted: false })
result.should.eql({ ...result, accepted: false })
})

it('can abort contract sign request', async () => {
const errorCode = 12345
const code = await initiator.compileContractAPI(identityContract, { backend: 'aevm' })
const callData = await initiator.contractEncodeCallDataAPI(identityContract, 'init', [], { backend: 'aevm' })
const result = await initiatorCh.createContract({
code,
callData,
deposit: BigNumber('10e18'),
vmVersion: 4,
abiVersion: 1
}, () => errorCode)
result.should.eql({ accepted: false, errorCode, errorMessage: 'user-defined' })
})

it('can abort contract with custom error code', async () => {
responderShouldRejectUpdate = 12345
const code = await initiator.compileContractAPI(identityContract, { backend: 'aevm' })
const callData = await initiator.contractEncodeCallDataAPI(identityContract, 'init', [], { backend: 'aevm' })
const result = await initiatorCh.createContract({
code,
callData,
deposit: BigNumber('10e18'),
vmVersion: 4,
abiVersion: 1
}, async (tx) => initiator.signTransaction(tx))
result.should.eql({
accepted: false,
errorCode: responderShouldRejectUpdate,
errorMessage: 'user-defined'
})
})

it('can call a contract and accept', async () => {
Expand All @@ -804,7 +909,33 @@ describe('Channel', function () {
contract: contractAddress,
abiVersion: 1
}, async (tx) => initiator.signTransaction(tx))
result.should.eql({ accepted: false })
result.should.eql({ ...result, accepted: false })
})

it('can abort contract call sign request', async () => {
const errorCode = 12345
const result = await initiatorCh.callContract({
amount: 0,
callData: await contractEncodeCall('main', ['42']),
contract: contractAddress,
abiVersion: 1
}, () => errorCode)
result.should.eql({ accepted: false, errorCode, errorMessage: 'user-defined' })
})

it('can abort contract call with custom error code', async () => {
responderShouldRejectUpdate = 12345
const result = await initiatorCh.callContract({
amount: 0,
callData: await contractEncodeCall('main', ['42']),
contract: contractAddress,
abiVersion: 1
}, async (tx) => initiator.signTransaction(tx))
result.should.eql({
accepted: false,
errorCode: responderShouldRejectUpdate,
errorMessage: 'user-defined'
})
})

it('can get contract call', async () => {
Expand Down

0 comments on commit ae4426e

Please sign in to comment.