From 2fb38fdc8c25737111a7db5857ce0b07f54a989e Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Tue, 17 Oct 2023 14:57:41 -0300 Subject: [PATCH 01/41] add a state logging helper --- clients/State.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/clients/State.ts b/clients/State.ts index e3d76c7..f115e2e 100644 --- a/clients/State.ts +++ b/clients/State.ts @@ -54,7 +54,23 @@ export function encodeState(state: StateStruct): string { return AbiCoder.defaultAbiCoder().encode(ABI, [state]); } +export function logState(s: StateStruct): void { + console.log("State:"); + console.log(` owner: ${s.owner}`); + console.log(` intermediary: ${s.intermediary}`); + console.log(` turnNum: ${s.turnNum}`); + console.log(` intermediaryBalance: ${Number(s.intermediaryBalance)}`); + console.log(` htlcs:`); + s.htlcs.forEach((h) => { + console.log(` to: ${h.to}`); + console.log(` amount: ${h.amount}`); + console.log(` hashLock: ${h.hashLock}`); + console.log(` timelock: ${h.timelock}\n\n`); + }); +} + export function hashState(state: StateStruct): string { + // logState(state); const encodedState = encodeState(state); return keccak256(encodedState); } From d7a0b10f7d28320641d8f30577f2707479c49883 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Tue, 17 Oct 2023 14:58:51 -0300 Subject: [PATCH 02/41] store the updated state when adding an HTLC... plus logging --- clients/OwnerClient.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/clients/OwnerClient.ts b/clients/OwnerClient.ts index e1b5681..1a3e714 100644 --- a/clients/OwnerClient.ts +++ b/clients/OwnerClient.ts @@ -42,9 +42,17 @@ export class OwnerClient extends StateChannelWallet { this.peerBroadcastChannel.onmessage = async (ev: scwMessageEvent) => { const req = ev.data; if (req.type === MessageType.ForwardPayment) { + console.log("received forward payment request"); // claim the payment if it is for us const preimage = this.hashStore.get(req.hashLock); + // todo: validate that the proposed state update is "good" + const mySig = this.signState(req.updatedState.state); + this.signedStates.push({ + ...req.updatedState, + ownerSignature: mySig.ownerSignature, + }); + if (preimage === undefined) { throw new Error("Hashlock not found"); @@ -58,6 +66,7 @@ export class OwnerClient extends StateChannelWallet { updatedState: updated, }); } else if (req.type === MessageType.UnlockHTLC) { + console.log("received unlock HTLC request"); // run the preimage through the state update function const updated = await this.unlockHTLC(req.preimage); const updatedHash = hashState(updated.state); @@ -109,6 +118,7 @@ export class OwnerClient extends StateChannelWallet { if (invoice.type !== MessageType.Invoice) { throw new Error("Unexpected response"); } + console.log("received invoice: " + JSON.stringify(invoice)); // create a state update with the hashlock const signedUpdate = this.addHTLC(amount, invoice.hashLock); From 22457b910970aa94ff7f4735053fb2a6171ebfa0 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Tue, 17 Oct 2023 15:19:30 -0300 Subject: [PATCH 03/41] add a signatureMessage type --- clients/Messages.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/clients/Messages.ts b/clients/Messages.ts index 25d1fd2..60ac473 100644 --- a/clients/Messages.ts +++ b/clients/Messages.ts @@ -7,6 +7,7 @@ export enum MessageType { ForwardPayment = "forwardPayment", UnlockHTLC = "unlockHTLC", UserOperation = "userOperation", + Signature = "signature", } export type Message = @@ -14,7 +15,8 @@ export type Message = | RequestInvoice | ForwardPaymentRequest | UnlockHTLCRequest - | UserOperation; + | UserOperation + | SignatureMessage; export interface Invoice { type: MessageType.Invoice; amount: number; @@ -36,6 +38,12 @@ export interface ForwardPaymentRequest { timelock: number; updatedState: SignedState; // includes the "source" HTLC which makes the payment safe for the intermediary } + +export interface SignatureMessage { + type: MessageType.Signature; + signature: string; +} + export interface UnlockHTLCRequest { type: MessageType.UnlockHTLC; /** From 0953c4e9e73d2a5f77ca448f8d1a3189d08140f0 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Tue, 17 Oct 2023 15:35:19 -0300 Subject: [PATCH 04/41] add an `ack` step to in-channel messaging --- clients/IntermediaryClient.ts | 19 +++++++++++++++---- clients/StateChannelWallet.ts | 29 ++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/clients/IntermediaryClient.ts b/clients/IntermediaryClient.ts index c2923de..adeff2a 100644 --- a/clients/IntermediaryClient.ts +++ b/clients/IntermediaryClient.ts @@ -128,24 +128,35 @@ export class IntermediaryClient extends StateChannelWallet { } private async handleUnlockHTLC(req: UnlockHTLCRequest): Promise { + console.log("received unlock HTLC request"); // run the preimage through the state update function - const updated = await this.unlockHTLC(req.preimage); - const updatedHash = hashState(updated.state); + const locallyUpdated = await this.unlockHTLC(req.preimage); + const updatedHash = hashState(locallyUpdated.state); // check that the proposed update is correct if (updatedHash !== hashState(req.updatedState.state)) { throw new Error("Invalid state update"); // todo: peerMessage to sender with failure } + const signer = ethers.recoverAddress( updatedHash, - req.updatedState.intermediarySignature, + req.updatedState.ownerSignature, ); - if (signer !== this.intermediaryAddress) { + if (signer !== this.ownerAddress) { throw new Error("Invalid signature"); // todo: peerMessage to sender with failure } + // update our state + this.addSignedState({ + state: req.updatedState.state, + ownerSignature: req.updatedState.ownerSignature, + intermediarySignature: locallyUpdated.intermediarySignature, + }); + // return our signature to the owner so that they can update the state + this.ack(locallyUpdated.intermediarySignature); + // Bob has claimed is payment, so we now claim our linked payment from Alice // via the channel coordinator void this.coordinator.unlockHTLC(req); diff --git a/clients/StateChannelWallet.ts b/clients/StateChannelWallet.ts index 017591b..0b6232b 100644 --- a/clients/StateChannelWallet.ts +++ b/clients/StateChannelWallet.ts @@ -108,8 +108,35 @@ export class StateChannelWallet { instance.ownerAddress = await instance.scwContract.owner(); } - sendPeerMessage(message: Message): void { + /** + * used to return a co-signature on proposed updates. + * + * @param signature the signature to send to the peer + */ + protected ack(signature: string): void { + const ackPipe = new BroadcastChannel(this.scwContract + "-ack"); + ackPipe.postMessage({ + MessageType: MessageType.Signature, + signature, + }); + } + + async sendPeerMessage(message: Message): Promise { + const ackPipe = new BroadcastChannel(this.scwContract + "-ack"); + + const resp = new Promise((resolve, reject) => { + ackPipe.onmessage = (ev: scwMessageEvent) => { + if (ev.data.type === MessageType.Signature) { + resolve(ev.data); + } else { + reject(new Error("Unexpected message type")); + } + }; + }); + this.peerBroadcastChannel.postMessage(message); + + return await resp; } /** From d10146d6609e58095c48097c3c59ff07405a1485 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Tue, 17 Oct 2023 16:47:05 -0300 Subject: [PATCH 05/41] getBytes before signing... like in State.ts (and in test file) --- clients/StateChannelWallet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/StateChannelWallet.ts b/clients/StateChannelWallet.ts index 0b6232b..767e8d3 100644 --- a/clients/StateChannelWallet.ts +++ b/clients/StateChannelWallet.ts @@ -258,7 +258,7 @@ export class StateChannelWallet { signState(s: StateStruct): SignedState { const stateHash = hashState(s); - const signature: string = this.signer.signMessageSync(stateHash); + const signature: string = this.signer.signMessageSync(getBytes(stateHash)); const signedState: SignedState = { state: s, From 785b22d4df514f374b801f53887022823ccf5865 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Tue, 17 Oct 2023 17:19:55 -0300 Subject: [PATCH 06/41] add a log fcn --- clients/IntermediaryClient.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clients/IntermediaryClient.ts b/clients/IntermediaryClient.ts index adeff2a..dc1c0e4 100644 --- a/clients/IntermediaryClient.ts +++ b/clients/IntermediaryClient.ts @@ -37,6 +37,10 @@ export class IntermediaryCoordinator { }); } + log(s: string): void { + console.log(`[Coordinator] ${s}`); + } + /** * forwardHTLC moves a payment across the network. It is called by a channelWallet who has * verified that the payment is safe to forward. From 3dc95ff0b1c1056913e0af34075962fc1497ac58 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Tue, 17 Oct 2023 17:35:41 -0300 Subject: [PATCH 07/41] relabel broadcastChannel, fix outgoing message --- clients/StateChannelWallet.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/clients/StateChannelWallet.ts b/clients/StateChannelWallet.ts index 767e8d3..bedbbb4 100644 --- a/clients/StateChannelWallet.ts +++ b/clients/StateChannelWallet.ts @@ -114,22 +114,24 @@ export class StateChannelWallet { * @param signature the signature to send to the peer */ protected ack(signature: string): void { - const ackPipe = new BroadcastChannel(this.scwContract + "-ack"); + const ackPipe = new BroadcastChannel(this.scBridgeWalletAddress + "-ack"); ackPipe.postMessage({ - MessageType: MessageType.Signature, + type: MessageType.Signature, signature, }); } async sendPeerMessage(message: Message): Promise { - const ackPipe = new BroadcastChannel(this.scwContract + "-ack"); + const ackPipe = new BroadcastChannel(this.scBridgeWalletAddress + "-ack"); const resp = new Promise((resolve, reject) => { ackPipe.onmessage = (ev: scwMessageEvent) => { if (ev.data.type === MessageType.Signature) { resolve(ev.data); } else { - reject(new Error("Unexpected message type")); + reject( + new Error(`Unexpected message type: ${JSON.stringify(ev.data)}`), + ); } }; }); From 5ecfb0375f53b0aff8954306942e3d37b52ce60d Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Tue, 17 Oct 2023 17:40:18 -0300 Subject: [PATCH 08/41] tmp: remove signature verification --- clients/IntermediaryClient.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/clients/IntermediaryClient.ts b/clients/IntermediaryClient.ts index dc1c0e4..b7fda6b 100644 --- a/clients/IntermediaryClient.ts +++ b/clients/IntermediaryClient.ts @@ -148,7 +148,11 @@ export class IntermediaryClient extends StateChannelWallet { req.updatedState.ownerSignature, ); if (signer !== this.ownerAddress) { - throw new Error("Invalid signature"); + // todo: fix signature recovery + // + // throw new Error( + // `Invalid signature: recovered ${signer}, wanted ${this.ownerAddress}`, + // ); // todo: peerMessage to sender with failure } From ffebcbdb2246ff1a6b3c436149c6e0e9f501e2b4 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Tue, 17 Oct 2023 17:50:08 -0300 Subject: [PATCH 09/41] add ACK and wait for ACK on bob receive/unlock --- clients/OwnerClient.ts | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/clients/OwnerClient.ts b/clients/OwnerClient.ts index 1a3e714..2b46e5e 100644 --- a/clients/OwnerClient.ts +++ b/clients/OwnerClient.ts @@ -43,27 +43,41 @@ export class OwnerClient extends StateChannelWallet { const req = ev.data; if (req.type === MessageType.ForwardPayment) { console.log("received forward payment request"); - // claim the payment if it is for us - const preimage = this.hashStore.get(req.hashLock); - // todo: validate that the proposed state update is "good" + + // add the HTLC to our state const mySig = this.signState(req.updatedState.state); - this.signedStates.push({ + this.addSignedState({ ...req.updatedState, ownerSignature: mySig.ownerSignature, + intermediarySignature: req.updatedState.intermediarySignature, }); + this.ack(mySig.ownerSignature); + + // claim the payment if it is for us + const preimage = this.hashStore.get(req.hashLock); if (preimage === undefined) { throw new Error("Hashlock not found"); - // todo: or forward the payment if it is multihop (not in scope for now) } - const updated = await this.unlockHTLC(preimage); - this.sendPeerMessage({ + // we are the end claimant, so we should: + // - unlock the payment + // - send the updated state to the intermediary + // - store the updated state with both signatures + this.log("attempting unlock w/ Irene"); + const updatedAfterUnlock = await this.unlockHTLC(preimage); + const intermediaryAck = await this.sendPeerMessage({ type: MessageType.UnlockHTLC, preimage, - updatedState: updated, + updatedState: updatedAfterUnlock, + }); + this.log("unlocked w/ Irene:" + intermediaryAck.signature); + this.addSignedState({ + state: updatedAfterUnlock.state, + ownerSignature: updatedAfterUnlock.ownerSignature, + intermediarySignature: intermediaryAck.signature, }); } else if (req.type === MessageType.UnlockHTLC) { console.log("received unlock HTLC request"); From fe54df9bed1fc3c7d1ac8fb3f1496d170bd45e83 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Tue, 17 Oct 2023 17:51:20 -0300 Subject: [PATCH 10/41] (bob) ingest the new state after removing HTLC --- clients/OwnerClient.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/clients/OwnerClient.ts b/clients/OwnerClient.ts index 2b46e5e..63cbdaa 100644 --- a/clients/OwnerClient.ts +++ b/clients/OwnerClient.ts @@ -95,9 +95,16 @@ export class OwnerClient extends StateChannelWallet { req.updatedState.intermediarySignature, ); if (signer !== this.intermediaryAddress) { - throw new Error("Invalid signature"); + // todo: fix signature recovery + // throw new Error("Invalid signature"); // todo: peerMessage to sender with failure } + this.ack(updated.ownerSignature); + this.addSignedState({ + state: req.updatedState.state, + ownerSignature: updated.ownerSignature, + intermediarySignature: req.updatedState.intermediarySignature, + }); } }; } From d03b4f0716d9e8eecf2cb6450604db54331685e8 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Tue, 17 Oct 2023 17:52:06 -0300 Subject: [PATCH 11/41] (alice) ingest the new state after adding an HTLC --- clients/OwnerClient.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/clients/OwnerClient.ts b/clients/OwnerClient.ts index 63cbdaa..5dcf31f 100644 --- a/clients/OwnerClient.ts +++ b/clients/OwnerClient.ts @@ -145,7 +145,7 @@ export class OwnerClient extends StateChannelWallet { const signedUpdate = this.addHTLC(amount, invoice.hashLock); // send the state update to the intermediary - this.sendPeerMessage({ + const intermediaryAck = await this.sendPeerMessage({ type: MessageType.ForwardPayment, target: payee, amount, @@ -153,6 +153,13 @@ export class OwnerClient extends StateChannelWallet { timelock: 0, // todo updatedState: signedUpdate, }); + + // and store co-signed state locally + this.addSignedState({ + state: signedUpdate.state, + ownerSignature: signedUpdate.ownerSignature, + intermediarySignature: intermediaryAck.signature, + }); } // Create L1 payment UserOperation and forward to intermediary From 3de9c62806552d50e3134c93fd55b97338b22290 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Tue, 17 Oct 2023 17:52:37 -0300 Subject: [PATCH 12/41] OwnerClient: add a logger --- clients/OwnerClient.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clients/OwnerClient.ts b/clients/OwnerClient.ts index 5dcf31f..176b3e4 100644 --- a/clients/OwnerClient.ts +++ b/clients/OwnerClient.ts @@ -19,6 +19,10 @@ export class OwnerClient extends StateChannelWallet { console.log("listening on " + this.globalBroadcastChannel.name); } + private log(s: string): void { + console.log(`[OwnerClient] ${s}`); + } + private attachMessageHandlers(): void { // These handlers are for messages from parties outside of our wallet / channel. this.globalBroadcastChannel.onmessage = async (ev: scwMessageEvent) => { From a28e9f742116ad92011a72baaf0452848e584f78 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Tue, 17 Oct 2023 17:53:34 -0300 Subject: [PATCH 13/41] add void return type... This perrMessage doesn't require a state update --- clients/OwnerClient.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clients/OwnerClient.ts b/clients/OwnerClient.ts index 176b3e4..8b77596 100644 --- a/clients/OwnerClient.ts +++ b/clients/OwnerClient.ts @@ -191,7 +191,8 @@ export class OwnerClient extends StateChannelWallet { ...userOp, signature, }; - this.sendPeerMessage({ + + void this.sendPeerMessage({ type: MessageType.UserOperation, ...signedUserOp, }); From a0d1c93d9a8584dab27f4424163693d0900690e8 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Tue, 17 Oct 2023 17:54:21 -0300 Subject: [PATCH 14/41] add helper to set `SignedState`s --- clients/StateChannelWallet.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/clients/StateChannelWallet.ts b/clients/StateChannelWallet.ts index bedbbb4..4fd7f98 100644 --- a/clients/StateChannelWallet.ts +++ b/clients/StateChannelWallet.ts @@ -101,6 +101,14 @@ export class StateChannelWallet { return instance; } + public addSignedState(ss: SignedState): void { + // todo: recover signers and throw if invalid + console.log("adding signed state"); + logState(ss.state); + + this.signedStates.push(ss); + } + protected static async hydrateWithChainData( instance: StateChannelWallet, ): Promise { From 4d97b6270d110428ddb9a085a021efceecbb49df Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Tue, 17 Oct 2023 18:03:44 -0300 Subject: [PATCH 15/41] add logging to error case --- clients/StateChannelWallet.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/clients/StateChannelWallet.ts b/clients/StateChannelWallet.ts index 4fd7f98..3632854 100644 --- a/clients/StateChannelWallet.ts +++ b/clients/StateChannelWallet.ts @@ -1,4 +1,4 @@ -import { ethers } from "ethers"; +import { ethers, getBytes } from "ethers"; import { type UserOperationStruct, type HTLCStruct, @@ -11,8 +11,13 @@ import { EntryPoint__factory, SCBridgeWallet__factory, } from "../typechain-types"; -import { type scwMessageEvent, type Message } from "./Messages"; -import { hashState } from "./State"; +import { + type scwMessageEvent, + type Message, + type SignatureMessage, + MessageType, +} from "./Messages"; +import { hashState, logState } from "./State"; const HTLC_TIMEOUT = 5 * 60; // 5 minutes @@ -248,7 +253,7 @@ export class StateChannelWallet { for (let i = this.signedStates.length - 1; i >= 0; i--) { const signedState = this.signedStates[i]; if ( - signedState.intermediarySignature !== "" && + signedState.intermediarySignature !== "" || signedState.ownerSignature !== "" ) { // todo: deep copy? @@ -332,6 +337,7 @@ export class StateChannelWallet { // with well-behaved clients, we should not see this if (unlockTarget === undefined) { + logState(this.currentState()); throw new Error("No matching HTLC found"); } From fa1022290870d4465ace46fe38bc7dac9aa2f06e Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Tue, 17 Oct 2023 18:08:06 -0300 Subject: [PATCH 16/41] relax check for "target" - either the SCW... or the owner address --- clients/IntermediaryClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/IntermediaryClient.ts b/clients/IntermediaryClient.ts index b7fda6b..2b4c017 100644 --- a/clients/IntermediaryClient.ts +++ b/clients/IntermediaryClient.ts @@ -50,7 +50,7 @@ export class IntermediaryCoordinator { forwardHTLC(htlc: ForwardPaymentRequest): void { // Locate the target client const targetClient = this.channelClients.find( - (c) => c.getAddress() === htlc.target, + (c) => c.getAddress() === htlc.target || c.ownerAddress === htlc.target, ); if (targetClient === undefined) { From 3324cf711ea7046ae81565dbb5051b89b635d999 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Tue, 17 Oct 2023 18:08:34 -0300 Subject: [PATCH 17/41] wait for ack, set new state on forwarded HTLC --- clients/IntermediaryClient.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/clients/IntermediaryClient.ts b/clients/IntermediaryClient.ts index 2b4c017..c808d4e 100644 --- a/clients/IntermediaryClient.ts +++ b/clients/IntermediaryClient.ts @@ -47,7 +47,7 @@ export class IntermediaryCoordinator { * * @param htlc the HTLC to forward */ - forwardHTLC(htlc: ForwardPaymentRequest): void { + async forwardHTLC(htlc: ForwardPaymentRequest): Promise { // Locate the target client const targetClient = this.channelClients.find( (c) => c.getAddress() === htlc.target || c.ownerAddress === htlc.target, @@ -61,7 +61,8 @@ export class IntermediaryCoordinator { const fee = 0; // for example const updatedState = targetClient.addHTLC(htlc.amount - fee, htlc.hashLock); - targetClient.sendPeerMessage({ + // this.log("adding HTLC to Irene-Bob"); + const ownerAck = await targetClient.sendPeerMessage({ type: MessageType.ForwardPayment, target: htlc.target, amount: htlc.amount, @@ -69,6 +70,13 @@ export class IntermediaryCoordinator { timelock: 0, // todo updatedState, }); + // this.log("added HTLC to Irene-Bob: " + ownerAck.signature); + + targetClient.addSignedState({ + ...updatedState, + intermediarySignature: updatedState.intermediarySignature, + ownerSignature: ownerAck.signature, + }); } async unlockHTLC(req: UnlockHTLCRequest): Promise { From 114aef2ad2bb5e934baec1a25dac72cd61b7a71b Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Tue, 17 Oct 2023 18:10:11 -0300 Subject: [PATCH 18/41] wait for ack, set new state on forward unlockHTLC --- clients/IntermediaryClient.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/clients/IntermediaryClient.ts b/clients/IntermediaryClient.ts index c808d4e..3b7f618 100644 --- a/clients/IntermediaryClient.ts +++ b/clients/IntermediaryClient.ts @@ -89,14 +89,24 @@ export class IntermediaryCoordinator { throw new Error("Target not found"); } + // this.log("removing HTLC from Alice-Irene:" + targetClient.getAddress()); // claim the payment and coordinate with the channel owner to update // the shared state const updated = await targetClient.unlockHTLC(req.preimage); - targetClient.sendPeerMessage({ + + // the intermediary asks the channel owner to update the state + const ownerAck = await targetClient.sendPeerMessage({ type: MessageType.UnlockHTLC, preimage: req.preimage, updatedState: updated, }); + // this.log("removed HTLC from Alice-Irene:" + ownerAck.signature); + + targetClient.addSignedState({ + state: updated.state, + intermediarySignature: updated.intermediarySignature, + ownerSignature: ownerAck.signature, + }); } } From 11f5f2933b85307a89c438c69ac1adfd6269a17f Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Tue, 17 Oct 2023 18:12:58 -0300 Subject: [PATCH 19/41] ingest new state, send ACK on forwardHTLC req --- clients/IntermediaryClient.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/clients/IntermediaryClient.ts b/clients/IntermediaryClient.ts index 3b7f618..94f028a 100644 --- a/clients/IntermediaryClient.ts +++ b/clients/IntermediaryClient.ts @@ -128,6 +128,7 @@ export class IntermediaryClient extends StateChannelWallet { // peer channel this.peerBroadcastChannel.onmessage = async (ev: scwMessageEvent) => { const req = ev.data; + let mySig: SignedState; switch (req.type) { case MessageType.ForwardPayment: @@ -135,7 +136,14 @@ export class IntermediaryClient extends StateChannelWallet { if (req.amount > (await this.getOwnerBalance())) { throw new Error("Insufficient balance"); } - this.coordinator.forwardHTLC(req); + mySig = this.signState(req.updatedState.state); + this.addSignedState({ + state: req.updatedState.state, + ownerSignature: req.updatedState.ownerSignature, + intermediarySignature: mySig.intermediarySignature, + }); + this.ack(mySig.intermediarySignature); + await this.coordinator.forwardHTLC(req); break; case MessageType.UserOperation: void this.handleUserOp(req); From 1ee4ebea54c84c71af400f64492423ff0d57e7cf Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Tue, 17 Oct 2023 18:13:39 -0300 Subject: [PATCH 20/41] add an ACK on UserOp handler --- clients/IntermediaryClient.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/clients/IntermediaryClient.ts b/clients/IntermediaryClient.ts index 94f028a..f2fa185 100644 --- a/clients/IntermediaryClient.ts +++ b/clients/IntermediaryClient.ts @@ -7,6 +7,7 @@ import { } from "./Messages"; import { Participant, + type SignedState, StateChannelWallet, type StateChannelWalletParams, } from "./StateChannelWallet"; @@ -239,6 +240,7 @@ export class IntermediaryClient extends StateChannelWallet { ); // Waiting for the transaction to be mined let's us catch the error await result.wait(); + this.ack(userOp.signature); } static async create( From e603a05413424c66a9de97b60a44410262aa59da Mon Sep 17 00:00:00 2001 From: Samuel Stokes Date: Tue, 17 Oct 2023 14:47:48 -0400 Subject: [PATCH 21/41] Add L1Payment modal --- src/Wallet.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Wallet.tsx b/src/Wallet.tsx index 43c6d9b..353060e 100644 --- a/src/Wallet.tsx +++ b/src/Wallet.tsx @@ -9,6 +9,9 @@ import { Avatar, Button, ButtonGroup, + Dialog, + DialogContent, + DialogTitle, Card, Container, Divider, @@ -86,6 +89,7 @@ const Wallet: React.FunctionComponent<{ role: Role }> = (props: { const resultHash = await wallet.payL1(payee, amount); setUserOpHash(resultHash); setErrorL1Pay(null); // Clear any previous error + setModalL1PayOpen(true); } catch (e: any) { console.error(e); setErrorL1Pay("Error initiating L1 payment"); From a0d637513fee02eea16ef8f0e909f54ee76d1b86 Mon Sep 17 00:00:00 2001 From: George Knee Date: Tue, 17 Oct 2023 18:05:22 +0100 Subject: [PATCH 22/41] poll clients for ui state --- src/Wallet.tsx | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/Wallet.tsx b/src/Wallet.tsx index 353060e..0cae240 100644 --- a/src/Wallet.tsx +++ b/src/Wallet.tsx @@ -111,6 +111,30 @@ const Wallet: React.FunctionComponent<{ role: Role }> = (props: { const [ownerBalance, intermediaryBalance] = useBalances(wallet); + useEffect(() => { + const interval = setInterval(() => { + wallet + .getOwnerBalance() + .then((b) => { + setOwnerBalance(b); + }) + .catch((e) => { + console.error(e); + }); + wallet + .getIntermediaryBalance() + .then((b) => { + setIntermediaryBalance(b); + }) + .catch((e) => { + console.error(e); + }); + }, 400); + return () => { + clearInterval(interval); + }; + }, []); + const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)"); const theme = React.useMemo( () => From 1211c9229a7906bef45f1c512ef32f4f7617d186 Mon Sep 17 00:00:00 2001 From: George Knee Date: Tue, 17 Oct 2023 18:11:38 +0100 Subject: [PATCH 23/41] wire up L1 pay --- src/Wallet.tsx | 2 +- src/constants.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Wallet.tsx b/src/Wallet.tsx index 0cae240..4567c74 100644 --- a/src/Wallet.tsx +++ b/src/Wallet.tsx @@ -29,7 +29,7 @@ import L1PaymentModal from "./modals/L1Payment"; import { OwnerClient } from "../clients/OwnerClient"; import { AddressIcon, AddressIconSmall } from "./AddressIcon"; import { blo } from "blo"; -import { UI_UPDATE_PERIOD } from "./constants"; +import { UI_UPDATE_PERIOD, PAYMENT_AMOUNT } from "./constants"; import { formatEther } from "ethers"; import { useBalances } from "./useBalances"; diff --git a/src/constants.ts b/src/constants.ts index 33a823c..207e280 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1 +1,2 @@ export const UI_UPDATE_PERIOD = 400; // ms +export const PAYMENT_AMOUNT = 1; From 362fe2c6d04346c12456664b5212c61bfa3e92f6 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Wed, 18 Oct 2023 00:53:42 -0300 Subject: [PATCH 24/41] refactor: extract `handleIncomingHTLC` function --- clients/OwnerClient.ts | 77 ++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/clients/OwnerClient.ts b/clients/OwnerClient.ts index 8b77596..e8337f7 100644 --- a/clients/OwnerClient.ts +++ b/clients/OwnerClient.ts @@ -46,43 +46,7 @@ export class OwnerClient extends StateChannelWallet { this.peerBroadcastChannel.onmessage = async (ev: scwMessageEvent) => { const req = ev.data; if (req.type === MessageType.ForwardPayment) { - console.log("received forward payment request"); - // todo: validate that the proposed state update is "good" - - // add the HTLC to our state - const mySig = this.signState(req.updatedState.state); - this.addSignedState({ - ...req.updatedState, - ownerSignature: mySig.ownerSignature, - intermediarySignature: req.updatedState.intermediarySignature, - }); - this.ack(mySig.ownerSignature); - - // claim the payment if it is for us - const preimage = this.hashStore.get(req.hashLock); - - if (preimage === undefined) { - throw new Error("Hashlock not found"); - // todo: or forward the payment if it is multihop (not in scope for now) - } - - // we are the end claimant, so we should: - // - unlock the payment - // - send the updated state to the intermediary - // - store the updated state with both signatures - this.log("attempting unlock w/ Irene"); - const updatedAfterUnlock = await this.unlockHTLC(preimage); - const intermediaryAck = await this.sendPeerMessage({ - type: MessageType.UnlockHTLC, - preimage, - updatedState: updatedAfterUnlock, - }); - this.log("unlocked w/ Irene:" + intermediaryAck.signature); - this.addSignedState({ - state: updatedAfterUnlock.state, - ownerSignature: updatedAfterUnlock.ownerSignature, - intermediarySignature: intermediaryAck.signature, - }); + void this.handleIncomingHTLC(req); } else if (req.type === MessageType.UnlockHTLC) { console.log("received unlock HTLC request"); // run the preimage through the state update function @@ -113,6 +77,45 @@ export class OwnerClient extends StateChannelWallet { }; } + private async handleIncomingHTLC(req: ForwardPaymentRequest): Promise { + console.log("received forward payment request"); + // todo: validate that the proposed state update is "good" + // add the HTLC to our state + const mySig = this.signState(req.updatedState.state); + this.addSignedState({ + ...req.updatedState, + ownerSignature: mySig.ownerSignature, + intermediarySignature: req.updatedState.intermediarySignature, + }); + this.ack(mySig.ownerSignature); + + // claim the payment if it is for us + const preimage = this.hashStore.get(req.hashLock); + + if (preimage === undefined) { + throw new Error("Hashlock not found"); + // todo: or forward the payment if it is multihop (not in scope for now) + } + + // we are the end claimant, so we should: + // - unlock the payment + // - send the updated state to the intermediary + // - store the updated state with both signatures + this.log("attempting unlock w/ Irene"); + const updatedAfterUnlock = await this.unlockHTLC(preimage); + const intermediaryAck = await this.sendPeerMessage({ + type: MessageType.UnlockHTLC, + preimage, + updatedState: updatedAfterUnlock, + }); + this.log("unlocked w/ Irene:" + intermediaryAck.signature); + this.addSignedState({ + state: updatedAfterUnlock.state, + ownerSignature: updatedAfterUnlock.ownerSignature, + intermediarySignature: intermediaryAck.signature, + }); + } + static async create(params: StateChannelWalletParams): Promise { const instance = new OwnerClient(params); From 2cf5ae306e62279de6edd0266bf326204d112254 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Wed, 18 Oct 2023 00:56:02 -0300 Subject: [PATCH 25/41] refactor: extract `handleUnlockHTLCRequest` func --- clients/OwnerClient.ts | 56 ++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/clients/OwnerClient.ts b/clients/OwnerClient.ts index e8337f7..08975c9 100644 --- a/clients/OwnerClient.ts +++ b/clients/OwnerClient.ts @@ -48,37 +48,41 @@ export class OwnerClient extends StateChannelWallet { if (req.type === MessageType.ForwardPayment) { void this.handleIncomingHTLC(req); } else if (req.type === MessageType.UnlockHTLC) { - console.log("received unlock HTLC request"); - // run the preimage through the state update function - const updated = await this.unlockHTLC(req.preimage); - const updatedHash = hashState(updated.state); - - // check that the proposed update is correct - if (updatedHash !== hashState(req.updatedState.state)) { - throw new Error("Invalid state update"); - // todo: peerMessage to sender with failure - } - const signer = ethers.recoverAddress( - updatedHash, - req.updatedState.intermediarySignature, - ); - if (signer !== this.intermediaryAddress) { - // todo: fix signature recovery - // throw new Error("Invalid signature"); - // todo: peerMessage to sender with failure - } - this.ack(updated.ownerSignature); - this.addSignedState({ - state: req.updatedState.state, - ownerSignature: updated.ownerSignature, - intermediarySignature: req.updatedState.intermediarySignature, - }); + await this.handleUnlockHTLCRequest(req); } }; } + private async handleUnlockHTLCRequest(req: UnlockHTLCRequest): Promise { + this.log("received unlock HTLC request"); + // run the preimage through the state update function + const updated = await this.unlockHTLC(req.preimage); + const updatedHash = hashState(updated.state); + + // check that the proposed update is correct + if (updatedHash !== hashState(req.updatedState.state)) { + throw new Error("Invalid state update"); + // todo: peerMessage to sender with failure + } + const signer = ethers.recoverAddress( + updatedHash, + req.updatedState.intermediarySignature, + ); + if (signer !== this.intermediaryAddress) { + // todo: fix signature recovery + // throw new Error("Invalid signature"); + // todo: peerMessage to sender with failure + } + this.ack(updated.ownerSignature); + this.addSignedState({ + state: req.updatedState.state, + ownerSignature: updated.ownerSignature, + intermediarySignature: req.updatedState.intermediarySignature, + }); + } + private async handleIncomingHTLC(req: ForwardPaymentRequest): Promise { - console.log("received forward payment request"); + this.log("received forward payment request"); // todo: validate that the proposed state update is "good" // add the HTLC to our state const mySig = this.signState(req.updatedState.state); From 65f7e269c99435aa2c16345dfa9d1094acca0e59 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Wed, 18 Oct 2023 00:59:33 -0300 Subject: [PATCH 26/41] refactor: to a switch statement --- clients/OwnerClient.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/clients/OwnerClient.ts b/clients/OwnerClient.ts index 08975c9..2fbeecf 100644 --- a/clients/OwnerClient.ts +++ b/clients/OwnerClient.ts @@ -45,10 +45,15 @@ export class OwnerClient extends StateChannelWallet { // These handlers are for messages from the channel/wallet peer (our intermediary). this.peerBroadcastChannel.onmessage = async (ev: scwMessageEvent) => { const req = ev.data; - if (req.type === MessageType.ForwardPayment) { - void this.handleIncomingHTLC(req); - } else if (req.type === MessageType.UnlockHTLC) { - await this.handleUnlockHTLCRequest(req); + switch (req.type) { + case MessageType.ForwardPayment: + await this.handleIncomingHTLC(req); + break; + case MessageType.UnlockHTLC: + await this.handleUnlockHTLCRequest(req); + break; + default: + throw new Error(`Message type ${req.type} not yet handled`); } }; } From 283e45211a4e03c6eadf3b65d6aa0e8914a2edbd Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Wed, 18 Oct 2023 01:15:43 -0300 Subject: [PATCH 27/41] remove non-useful debug line --- src/Intermediary.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Intermediary.tsx b/src/Intermediary.tsx index ac4c9f2..6cc2839 100644 --- a/src/Intermediary.tsx +++ b/src/Intermediary.tsx @@ -57,8 +57,6 @@ export const Coordinator: React.FunctionComponent = () => { new IntermediaryCoordinator([withAlice, withBob]), ); - console.log(withAlice.ownerAddress); - return ( Date: Wed, 18 Oct 2023 01:19:35 -0300 Subject: [PATCH 28/41] remove duplicate useEffect call --- src/Wallet.tsx | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/Wallet.tsx b/src/Wallet.tsx index 4567c74..f147e46 100644 --- a/src/Wallet.tsx +++ b/src/Wallet.tsx @@ -111,30 +111,6 @@ const Wallet: React.FunctionComponent<{ role: Role }> = (props: { const [ownerBalance, intermediaryBalance] = useBalances(wallet); - useEffect(() => { - const interval = setInterval(() => { - wallet - .getOwnerBalance() - .then((b) => { - setOwnerBalance(b); - }) - .catch((e) => { - console.error(e); - }); - wallet - .getIntermediaryBalance() - .then((b) => { - setIntermediaryBalance(b); - }) - .catch((e) => { - console.error(e); - }); - }, 400); - return () => { - clearInterval(interval); - }; - }, []); - const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)"); const theme = React.useMemo( () => From 97f71eae56d26f6f04c3ea48e3b0f396eddd2ed5 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Wed, 18 Oct 2023 01:36:04 -0300 Subject: [PATCH 29/41] refactor: extract handleForwardPaymentRequest func --- clients/IntermediaryClient.ts | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/clients/IntermediaryClient.ts b/clients/IntermediaryClient.ts index f2fa185..a0fe7e3 100644 --- a/clients/IntermediaryClient.ts +++ b/clients/IntermediaryClient.ts @@ -133,18 +133,7 @@ export class IntermediaryClient extends StateChannelWallet { switch (req.type) { case MessageType.ForwardPayment: - // todo: more robust checks. EG: signature of counterparty - if (req.amount > (await this.getOwnerBalance())) { - throw new Error("Insufficient balance"); - } - mySig = this.signState(req.updatedState.state); - this.addSignedState({ - state: req.updatedState.state, - ownerSignature: req.updatedState.ownerSignature, - intermediarySignature: mySig.intermediarySignature, - }); - this.ack(mySig.intermediarySignature); - await this.coordinator.forwardHTLC(req); + await this.handleForwardPaymentRequest(req); break; case MessageType.UserOperation: void this.handleUserOp(req); @@ -158,6 +147,23 @@ export class IntermediaryClient extends StateChannelWallet { }; } + private async handleForwardPaymentRequest( + req: ForwardPaymentRequest, + ): Promise { + // todo: more robust checks. EG: signature of counterparty + if (req.amount > (await this.getOwnerBalance())) { + throw new Error("Insufficient balance"); + } + const mySig = this.signState(req.updatedState.state); + this.addSignedState({ + state: req.updatedState.state, + ownerSignature: req.updatedState.ownerSignature, + intermediarySignature: mySig.intermediarySignature, + }); + this.ack(mySig.intermediarySignature); + await this.coordinator.forwardHTLC(req); + } + private async handleUnlockHTLC(req: UnlockHTLCRequest): Promise { console.log("received unlock HTLC request"); // run the preimage through the state update function From b5194524d83469aaa1a99048402ab4cbeaf2559d Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Wed, 18 Oct 2023 01:37:35 -0300 Subject: [PATCH 30/41] add a logger --- clients/IntermediaryClient.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/clients/IntermediaryClient.ts b/clients/IntermediaryClient.ts index a0fe7e3..f7534d7 100644 --- a/clients/IntermediaryClient.ts +++ b/clients/IntermediaryClient.ts @@ -116,6 +116,12 @@ export class IntermediaryClient extends StateChannelWallet { [], ); + private log(s: string): void { + console.log( + `[IntermediaryClient-${this.ownerAddress.substring(0, 5)}] ${s}`, + ); + } + constructor(params: StateChannelWalletParams) { super(params); this.attachMessageHandlers(); @@ -129,7 +135,7 @@ export class IntermediaryClient extends StateChannelWallet { // peer channel this.peerBroadcastChannel.onmessage = async (ev: scwMessageEvent) => { const req = ev.data; - let mySig: SignedState; + this.log("received peer message: " + JSON.stringify(req)); switch (req.type) { case MessageType.ForwardPayment: From 448f373382731e1a19871cd97c4fb2860ca4827d Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Wed, 18 Oct 2023 09:59:20 -0300 Subject: [PATCH 31/41] add imports --- clients/OwnerClient.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/clients/OwnerClient.ts b/clients/OwnerClient.ts index 2fbeecf..e4a5805 100644 --- a/clients/OwnerClient.ts +++ b/clients/OwnerClient.ts @@ -1,4 +1,10 @@ -import { type Invoice, type scwMessageEvent, MessageType } from "./Messages"; +import { + type Invoice, + type scwMessageEvent, + MessageType, + type ForwardPaymentRequest, + type UnlockHTLCRequest, +} from "./Messages"; import { ethers } from "ethers"; import { Participant, From b8ba84dff087d475f9f7c26d06800068e47b1c2f Mon Sep 17 00:00:00 2001 From: George Knee Date: Wed, 18 Oct 2023 13:27:30 +0100 Subject: [PATCH 32/41] use fns in useState --- src/Intermediary.tsx | 40 +++++++++++++++++++++------------------- src/Wallet.tsx | 37 +++++++++++++++++-------------------- 2 files changed, 38 insertions(+), 39 deletions(-) diff --git a/src/Intermediary.tsx b/src/Intermediary.tsx index 6cc2839..9265a0d 100644 --- a/src/Intermediary.tsx +++ b/src/Intermediary.tsx @@ -30,31 +30,33 @@ export const Coordinator: React.FunctionComponent = () => { const bobScwAddress = import.meta.env.VITE_BOB_SCW_ADDRESS; const [withAlice] = useState( - new IntermediaryClient({ - signingKey: myKey, - // @ts-expect-error - ownerAddress: import.meta.env.VITE_ALICE_ADDRESS, - intermediaryAddress: myAddress, - chainRpcUrl: "http://localhost:8545", - entrypointAddress, - scwAddress: aliceScwAddress, - }), + () => + new IntermediaryClient({ + signingKey: myKey, + // @ts-expect-error + ownerAddress: import.meta.env.VITE_ALICE_ADDRESS, + intermediaryAddress: myAddress, + chainRpcUrl: "http://localhost:8545", + entrypointAddress, + scwAddress: aliceScwAddress, + }), ); const [withBob] = useState( - new IntermediaryClient({ - signingKey: myKey, - // @ts-expect-error - ownerAddress: import.meta.env.VITE_BOB_ADDRESS, - intermediaryAddress: myAddress, - chainRpcUrl: "http://localhost:8545", - entrypointAddress, - scwAddress: bobScwAddress, - }), + () => + new IntermediaryClient({ + signingKey: myKey, + // @ts-expect-error + ownerAddress: import.meta.env.VITE_BOB_ADDRESS, + intermediaryAddress: myAddress, + chainRpcUrl: "http://localhost:8545", + entrypointAddress, + scwAddress: bobScwAddress, + }), ); // eslint-disable-next-line @typescript-eslint/no-unused-vars const [coordinator] = useState( - new IntermediaryCoordinator([withAlice, withBob]), + () => new IntermediaryCoordinator([withAlice, withBob]), ); return ( diff --git a/src/Wallet.tsx b/src/Wallet.tsx index f147e46..7b2f819 100644 --- a/src/Wallet.tsx +++ b/src/Wallet.tsx @@ -1,17 +1,11 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/no-unused-vars */ -import React, { useEffect, useState } from "react"; -import BoltIcon from "@mui/icons-material/Bolt"; import AccessTimeIcon from "@mui/icons-material/AccessTime"; -import logo from "./assets/logo.png"; -import "./Wallet.css"; +import BoltIcon from "@mui/icons-material/Bolt"; import { Avatar, Button, ButtonGroup, - Dialog, - DialogContent, - DialogTitle, Card, Container, Divider, @@ -24,13 +18,15 @@ import { createTheme, useMediaQuery, } from "@mui/material"; -import { type Role } from "./WalletContainer"; -import L1PaymentModal from "./modals/L1Payment"; -import { OwnerClient } from "../clients/OwnerClient"; -import { AddressIcon, AddressIconSmall } from "./AddressIcon"; import { blo } from "blo"; -import { UI_UPDATE_PERIOD, PAYMENT_AMOUNT } from "./constants"; import { formatEther } from "ethers"; +import React, { useState } from "react"; +import { OwnerClient } from "../clients/OwnerClient"; +import { AddressIcon, AddressIconSmall } from "./AddressIcon"; +import "./Wallet.css"; +import { type Role } from "./WalletContainer"; +import logo from "./assets/logo.png"; +import L1PaymentModal from "./modals/L1Payment"; import { useBalances } from "./useBalances"; let myAddress: string = "placholder"; @@ -99,14 +95,15 @@ const Wallet: React.FunctionComponent<{ role: Role }> = (props: { }; const [wallet, _] = useState( - new OwnerClient({ - signingKey: mySigningKey, - ownerAddress: myAddress, - intermediaryAddress: intermediary, - chainRpcUrl: "http://localhost:8545", - entrypointAddress, - scwAddress: myScwAddress, - }), + () => + new OwnerClient({ + signingKey: mySigningKey, + ownerAddress: myAddress, + intermediaryAddress: intermediary, + chainRpcUrl: "http://localhost:8545", + entrypointAddress, + scwAddress: myScwAddress, + }), ); const [ownerBalance, intermediaryBalance] = useBalances(wallet); From eb5f1e4ed0f962192bd5f6c498d61e3aa5a879ac Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Wed, 18 Oct 2023 10:27:35 -0300 Subject: [PATCH 33/41] address payment to owner address... (compatible w/ existing pipes) --- src/Wallet.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Wallet.tsx b/src/Wallet.tsx index 7b2f819..2ff7697 100644 --- a/src/Wallet.tsx +++ b/src/Wallet.tsx @@ -233,7 +233,7 @@ const Wallet: React.FunctionComponent<{ role: Role }> = (props: { size="medium" disabled={recipient.toLowerCase() !== myPeer.toLowerCase()} onClick={() => { - wallet.pay(myPeerSCWAddress, Number(payAmount)).catch((e) => { + wallet.pay(myPeer, Number(payAmount)).catch((e) => { console.error(e); }); }} From e7bb8e5bd9a5d0e6c93331a55df3088fedddcf49 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Wed, 18 Oct 2023 10:29:44 -0300 Subject: [PATCH 34/41] remove stringify which breaks on bigInts --- clients/IntermediaryClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/IntermediaryClient.ts b/clients/IntermediaryClient.ts index f7534d7..e77e0f4 100644 --- a/clients/IntermediaryClient.ts +++ b/clients/IntermediaryClient.ts @@ -135,7 +135,7 @@ export class IntermediaryClient extends StateChannelWallet { // peer channel this.peerBroadcastChannel.onmessage = async (ev: scwMessageEvent) => { const req = ev.data; - this.log("received peer message: " + JSON.stringify(req)); + this.log(`received message of type ${req.type}`); switch (req.type) { case MessageType.ForwardPayment: From c9879fb0035c30876acfff00c510606dd99a3c8a Mon Sep 17 00:00:00 2001 From: lalexgap Date: Wed, 18 Oct 2023 08:32:23 -0700 Subject: [PATCH 35/41] convert to BigInts --- clients/StateChannelWallet.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/StateChannelWallet.ts b/clients/StateChannelWallet.ts index 3632854..2385f96 100644 --- a/clients/StateChannelWallet.ts +++ b/clients/StateChannelWallet.ts @@ -304,7 +304,7 @@ export class StateChannelWallet { const htlc: HTLCStruct = { to: this.theirRole(), - amount, + amount: BigInt(amount), hashLock: hash, timelock: currentTimestamp + HTLC_TIMEOUT * 2, // payment creator always uses TIMEOUT * 2 }; @@ -318,7 +318,7 @@ export class StateChannelWallet { owner: this.ownerAddress, intermediary: this.intermediaryAddress, turnNum: Number(this.currentState().turnNum) + 1, - intermediaryBalance: updatedIntermediaryBalance, + intermediaryBalance: BigInt(updatedIntermediaryBalance), htlcs: [...this.currentState().htlcs, htlc], }; From 541fd90aa64646fee39ac5d5812e055a97a1fcf7 Mon Sep 17 00:00:00 2001 From: lalexgap Date: Wed, 18 Oct 2023 08:39:31 -0700 Subject: [PATCH 36/41] lint error --- clients/IntermediaryClient.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/clients/IntermediaryClient.ts b/clients/IntermediaryClient.ts index bd479bf..63298bd 100644 --- a/clients/IntermediaryClient.ts +++ b/clients/IntermediaryClient.ts @@ -7,7 +7,6 @@ import { } from "./Messages"; import { Participant, - type SignedState, StateChannelWallet, type StateChannelWalletParams, } from "./StateChannelWallet"; From 48b4e045131724e3431b94837cbb2efc95f0e2b0 Mon Sep 17 00:00:00 2001 From: lalexgap Date: Wed, 18 Oct 2023 08:42:36 -0700 Subject: [PATCH 37/41] Bonus lint errors --- clients/State.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clients/State.ts b/clients/State.ts index f115e2e..9993152 100644 --- a/clients/State.ts +++ b/clients/State.ts @@ -56,15 +56,15 @@ export function encodeState(state: StateStruct): string { export function logState(s: StateStruct): void { console.log("State:"); - console.log(` owner: ${s.owner}`); - console.log(` intermediary: ${s.intermediary}`); + console.log(` owner: ${s.owner as string}`); + console.log(` intermediary: ${s.intermediary as string}`); console.log(` turnNum: ${s.turnNum}`); console.log(` intermediaryBalance: ${Number(s.intermediaryBalance)}`); console.log(` htlcs:`); s.htlcs.forEach((h) => { console.log(` to: ${h.to}`); console.log(` amount: ${h.amount}`); - console.log(` hashLock: ${h.hashLock}`); + console.log(` hashLock: ${h.hashLock as string}`); console.log(` timelock: ${h.timelock}\n\n`); }); } From eac09e6e5e9e943872509d0b0a8ac7319bba144c Mon Sep 17 00:00:00 2001 From: lalexgap Date: Wed, 18 Oct 2023 08:55:14 -0700 Subject: [PATCH 38/41] switch to bigints --- clients/IntermediaryClient.ts | 7 +++++-- clients/Messages.ts | 8 ++++---- clients/OwnerClient.ts | 12 +++++++----- clients/StateChannelWallet.ts | 4 ++-- src/Wallet.tsx | 17 +++++++++++------ 5 files changed, 29 insertions(+), 19 deletions(-) diff --git a/clients/IntermediaryClient.ts b/clients/IntermediaryClient.ts index 63298bd..341eaf5 100644 --- a/clients/IntermediaryClient.ts +++ b/clients/IntermediaryClient.ts @@ -59,7 +59,10 @@ export class IntermediaryCoordinator { } const fee = 0; // for example - const updatedState = targetClient.addHTLC(htlc.amount - fee, htlc.hashLock); + const updatedState = targetClient.addHTLC( + htlc.amount - BigInt(fee), + htlc.hashLock, + ); // this.log("adding HTLC to Irene-Bob"); const ownerAck = await targetClient.sendPeerMessage({ @@ -67,7 +70,7 @@ export class IntermediaryCoordinator { target: htlc.target, amount: htlc.amount, hashLock: htlc.hashLock, - timelock: 0, // todo + timelock: BigInt(0), // todo updatedState, }); // this.log("added HTLC to Irene-Bob: " + ownerAck.signature); diff --git a/clients/Messages.ts b/clients/Messages.ts index 60ac473..d466042 100644 --- a/clients/Messages.ts +++ b/clients/Messages.ts @@ -19,12 +19,12 @@ export type Message = | SignatureMessage; export interface Invoice { type: MessageType.Invoice; - amount: number; + amount: bigint; hashLock: string; } interface RequestInvoice { type: MessageType.RequestInvoice; - amount: number; + amount: bigint; from: string; // where to send the invoice } export interface ForwardPaymentRequest { @@ -33,9 +33,9 @@ export interface ForwardPaymentRequest { * the scw address whose owner is the payee */ target: string; - amount: number; + amount: bigint; hashLock: string; - timelock: number; + timelock: bigint; updatedState: SignedState; // includes the "source" HTLC which makes the payment safe for the intermediary } diff --git a/clients/OwnerClient.ts b/clients/OwnerClient.ts index 18106ae..1e15913 100644 --- a/clients/OwnerClient.ts +++ b/clients/OwnerClient.ts @@ -149,7 +149,7 @@ export class OwnerClient extends StateChannelWallet { * @param payee the SCBridgeWallet address we want to pay to * @param amount the amount we want to pay */ - async pay(payee: string, amount: number): Promise { + async pay(payee: string, amount: bigint): Promise { // contact `payee` and request an invoice const invoice = await this.sendGlobalMessage(payee, { type: MessageType.RequestInvoice, @@ -171,7 +171,7 @@ export class OwnerClient extends StateChannelWallet { target: payee, amount, hashLock: invoice.hashLock, - timelock: 0, // todo + timelock: BigInt(0), // todo updatedState: signedUpdate, }); @@ -184,11 +184,11 @@ export class OwnerClient extends StateChannelWallet { } // Create L1 payment UserOperation and forward to intermediary - async payL1(payee: string, amount: number): Promise { + async payL1(payee: string, amount: bigint): Promise { // Only need to encode 'to' and 'amount' fields (i.e. no 'data') for basic eth transfer const callData = IAccount.encodeFunctionData("execute", [ payee, - ethers.parseEther(amount.toString()), + amount, "0x", // specifying no data makes sure the call is interpreted as a basic eth transfer ]); const partialUserOp: Partial = { @@ -215,7 +215,9 @@ export class OwnerClient extends StateChannelWallet { }); console.log( - `Initiated transfer of ${amount} ETH to ${payee} (userOpHash: ${hash})`, + `Initiated transfer of ${ethers.formatEther( + amount, + )} ETH to ${payee} (userOpHash: ${hash})`, ); // Increment nonce for next transfer diff --git a/clients/StateChannelWallet.ts b/clients/StateChannelWallet.ts index 2385f96..32cd6bc 100644 --- a/clients/StateChannelWallet.ts +++ b/clients/StateChannelWallet.ts @@ -286,7 +286,7 @@ export class StateChannelWallet { } // Craft an HTLC struct, put it inside a state, hash the state, sign and return it - addHTLC(amount: number, hash: string): SignedState { + addHTLC(amount: bigint, hash: string): SignedState { const currentTimestamp: number = Math.floor(Date.now() / 1000); // Unix timestamp in seconds if (this.myRole() === Participant.Intermediary) { @@ -311,7 +311,7 @@ export class StateChannelWallet { const updatedIntermediaryBalance = this.myRole() === Participant.Intermediary - ? Number(this.currentState().intermediaryBalance) - amount + ? BigInt(this.currentState().intermediaryBalance) - amount : this.currentState().intermediaryBalance; const updated: StateStruct = { diff --git a/src/Wallet.tsx b/src/Wallet.tsx index 0811c01..70ca492 100644 --- a/src/Wallet.tsx +++ b/src/Wallet.tsx @@ -20,7 +20,7 @@ import { useMediaQuery, } from "@mui/material"; import { blo } from "blo"; -import { formatEther } from "ethers"; +import { formatEther, ethers } from "ethers"; import { OwnerClient } from "../clients/OwnerClient"; import { AddressIcon, AddressIconSmall } from "./AddressIcon"; import "./Wallet.css"; @@ -80,7 +80,7 @@ const Wallet: React.FunctionComponent<{ role: Role }> = (props: { const [payAmount, setPayAmount] = useState("0.05"); const [errorL1Pay, setErrorL1Pay] = useState(null); - const handleL1Pay = async (payee: string, amount: number): Promise => { + const handleL1Pay = async (payee: string, amount: bigint): Promise => { try { const resultHash = await wallet.payL1(payee, amount); setUserOpHash(resultHash); @@ -224,7 +224,10 @@ const Wallet: React.FunctionComponent<{ role: Role }> = (props: { size="medium" disabled={recipient === ""} onClick={() => { - void handleL1Pay(myPeerSCWAddress, Number(payAmount)); + void handleL1Pay( + myPeerSCWAddress, + ethers.parseEther(payAmount), + ); }} > L1 Pay @@ -233,9 +236,11 @@ const Wallet: React.FunctionComponent<{ role: Role }> = (props: { size="medium" disabled={recipient.toLowerCase() !== myPeer.toLowerCase()} onClick={() => { - wallet.pay(myPeer, Number(payAmount)).catch((e) => { - console.error(e); - }); + wallet + .pay(myPeerSCWAddress, ethers.parseEther(payAmount)) + .catch((e) => { + console.error(e); + }); }} > L2 Pay From 5cdba7fa0c1ee91586d808e79ec3128414c8119e Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Wed, 18 Oct 2023 13:55:39 -0300 Subject: [PATCH 39/41] relabel global pipe w/ scw address --- clients/StateChannelWallet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/StateChannelWallet.ts b/clients/StateChannelWallet.ts index 32cd6bc..345d387 100644 --- a/clients/StateChannelWallet.ts +++ b/clients/StateChannelWallet.ts @@ -70,7 +70,7 @@ export class StateChannelWallet { this.ownerAddress + "-peer", ); this.globalBroadcastChannel = new BroadcastChannel( - this.ownerAddress + "-global", + this.scBridgeWalletAddress + "-global", ); const wallet = new ethers.Wallet(params.signingKey); From fe091b766ca2b0f7918a09375dbf07ae777fb3db Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Wed, 18 Oct 2023 13:56:06 -0300 Subject: [PATCH 40/41] avoid stringifying bigints (throws error) --- clients/OwnerClient.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/OwnerClient.ts b/clients/OwnerClient.ts index 1e15913..d4fe5f9 100644 --- a/clients/OwnerClient.ts +++ b/clients/OwnerClient.ts @@ -33,7 +33,7 @@ export class OwnerClient extends StateChannelWallet { // These handlers are for messages from parties outside of our wallet / channel. this.globalBroadcastChannel.onmessage = async (ev: scwMessageEvent) => { const req = ev.data; - console.log("received message: " + JSON.stringify(req)); + console.log("received message: ", req); if (req.type === MessageType.RequestInvoice) { const hash = await this.createNewHash(); @@ -160,7 +160,7 @@ export class OwnerClient extends StateChannelWallet { if (invoice.type !== MessageType.Invoice) { throw new Error("Unexpected response"); } - console.log("received invoice: " + JSON.stringify(invoice)); + console.log("received invoice: ", invoice); // create a state update with the hashlock const signedUpdate = this.addHTLC(amount, invoice.hashLock); From d559cfa9ac6def5581a129a3059f59f5889fc068 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Wed, 18 Oct 2023 14:30:23 -0300 Subject: [PATCH 41/41] remove unused const --- src/constants.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/constants.ts b/src/constants.ts index 207e280..33a823c 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,2 +1 @@ export const UI_UPDATE_PERIOD = 400; // ms -export const PAYMENT_AMOUNT = 1;