Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(state channels): allow off chain updates to be cancelled with cu… #753

Merged
merged 2 commits into from
Nov 6, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -402,7 +431,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 @@ -441,6 +470,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 @@ -505,7 +556,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 @@ -532,6 +583,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 @@ -761,7 +834,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 @@ -783,7 +888,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