Skip to content
This repository has been archived by the owner on Nov 8, 2023. It is now read-only.

Add protocol message Acknowledgements - enable L2 payments #123

Merged
merged 43 commits into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
2fb38fd
add a state logging helper
Oct 17, 2023
d7a0b10
store the updated state when adding an HTLC...
Oct 17, 2023
22457b9
add a signatureMessage type
Oct 17, 2023
0953c4e
add an `ack` step to in-channel messaging
Oct 17, 2023
d10146d
getBytes before signing...
Oct 17, 2023
785b22d
add a log fcn
Oct 17, 2023
3dc95ff
relabel broadcastChannel, fix outgoing message
Oct 17, 2023
5ecfb03
tmp: remove signature verification
Oct 17, 2023
ffebcbd
add ACK and wait for ACK on bob receive/unlock
Oct 17, 2023
fe54df9
(bob) ingest the new state after removing HTLC
Oct 17, 2023
d03b4f0
(alice) ingest the new state after adding an HTLC
Oct 17, 2023
3de9c62
OwnerClient: add a logger
Oct 17, 2023
a28e9f7
add void return type...
Oct 17, 2023
a0d1c93
add helper to set `SignedState`s
Oct 17, 2023
4d97b62
add logging to error case
Oct 17, 2023
fa10222
relax check for "target" - either the SCW...
Oct 17, 2023
3324cf7
wait for ack, set new state on forwarded HTLC
Oct 17, 2023
114aef2
wait for ack, set new state on forward unlockHTLC
Oct 17, 2023
11f5f29
ingest new state, send ACK on forwardHTLC req
Oct 17, 2023
1ee4ebe
add an ACK on UserOp handler
Oct 17, 2023
e603a05
Add L1Payment modal
bitwiseguy Oct 17, 2023
a0d6375
poll clients for ui state
geoknee Oct 17, 2023
1211c92
wire up L1 pay
geoknee Oct 17, 2023
362fe2c
refactor: extract `handleIncomingHTLC` function
Oct 18, 2023
2cf5ae3
refactor: extract `handleUnlockHTLCRequest` func
Oct 18, 2023
65f7e26
refactor: to a switch statement
Oct 18, 2023
283e452
remove non-useful debug line
Oct 18, 2023
aa5cde1
remove duplicate useEffect call
Oct 18, 2023
97f71ea
refactor: extract handleForwardPaymentRequest func
Oct 18, 2023
b519452
add a logger
Oct 18, 2023
448f373
add imports
Oct 18, 2023
b8ba84d
use fns in useState
geoknee Oct 18, 2023
eb5f1e4
address payment to owner address...
Oct 18, 2023
e7bb8e5
remove stringify which breaks on bigInts
Oct 18, 2023
7abd081
Merge branch 'main' into cmk.rejigged-l2
Oct 18, 2023
c9879fb
convert to BigInts
lalexgap Oct 18, 2023
541fd90
lint error
lalexgap Oct 18, 2023
48b4e04
Bonus lint errors
lalexgap Oct 18, 2023
1a2fafc
Merge pull request #125 from statechannels/fix-rejigged
NiloCK Oct 18, 2023
eac09e6
switch to bigints
lalexgap Oct 18, 2023
5cdba7f
relabel global pipe w/ scw address
Oct 18, 2023
fe091b7
avoid stringifying bigints (throws error)
Oct 18, 2023
d559cfa
remove unused const
Oct 18, 2023
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
91 changes: 75 additions & 16 deletions clients/IntermediaryClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,20 @@ 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.
*
* @param htlc the HTLC to forward
*/
forwardHTLC(htlc: ForwardPaymentRequest): void {
async forwardHTLC(htlc: ForwardPaymentRequest): Promise<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) {
Expand All @@ -55,16 +59,27 @@ 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,
);

targetClient.sendPeerMessage({
// this.log("adding HTLC to Irene-Bob");
const ownerAck = await targetClient.sendPeerMessage({
type: MessageType.ForwardPayment,
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);

targetClient.addSignedState({
...updatedState,
intermediarySignature: updatedState.intermediarySignature,
ownerSignature: ownerAck.signature,
});
}

async unlockHTLC(req: UnlockHTLCRequest): Promise<void> {
Expand All @@ -77,14 +92,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,
});
}
}

Expand All @@ -93,6 +118,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();
Expand All @@ -106,14 +137,11 @@ export class IntermediaryClient extends StateChannelWallet {
// peer channel
this.peerBroadcastChannel.onmessage = async (ev: scwMessageEvent) => {
const req = ev.data;
this.log(`received message of type ${req.type}`);

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");
}
this.coordinator.forwardHTLC(req);
await this.handleForwardPaymentRequest(req);
break;
case MessageType.UserOperation:
void this.handleUserOp(req);
Expand All @@ -127,25 +155,55 @@ export class IntermediaryClient extends StateChannelWallet {
};
}

private async handleForwardPaymentRequest(
req: ForwardPaymentRequest,
): Promise<void> {
// 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<void> {
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(
ethers.hashMessage(getBytes(updatedHash)),
req.updatedState.intermediarySignature,
req.updatedState.ownerSignature,
);
if (signer !== this.intermediaryAddress) {
throw new Error("Invalid signature");
if (signer !== this.ownerAddress) {
throw new Error(
`Invalid signature: recovered ${signer}, wanted ${this.ownerAddress}`,
);
// 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);
Expand Down Expand Up @@ -194,6 +252,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(
Expand Down
18 changes: 13 additions & 5 deletions clients/Messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,24 @@ export enum MessageType {
ForwardPayment = "forwardPayment",
UnlockHTLC = "unlockHTLC",
UserOperation = "userOperation",
Signature = "signature",
}

export type Message =
| Invoice
| RequestInvoice
| ForwardPaymentRequest
| UnlockHTLCRequest
| UserOperation;
| UserOperation
| 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 {
Expand All @@ -31,11 +33,17 @@ 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
}

export interface SignatureMessage {
type: MessageType.Signature;
signature: string;
}

export interface UnlockHTLCRequest {
type: MessageType.UnlockHTLC;
/**
Expand Down
Loading