This repository has been archived by the owner on Dec 13, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 44
/
middleware.ts
226 lines (209 loc) · 7.12 KB
/
middleware.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
import * as ethers from "ethers";
import { Instruction } from "../instructions";
import { CfState, Context } from "../state";
import {
ActionName,
ClientActionMessage,
InternalMessage,
OpCodeResult,
Signature
} from "../types";
import { StateTransition } from "./state-transition/state-transition";
/**
* CfMiddleware is the container holding the groups of middleware responsible
* for executing a given instruction in the Counterfactual VM.
*/
export class CfMiddleware {
/**
* Maps instruction to list of middleware that will process the instruction.
*/
public middlewares: Object;
constructor(readonly cfState: CfState, private cfOpGenerator: CfOpGenerator) {
this.middlewares = {};
this.add(
Instruction.OP_GENERATE,
async (message: InternalMessage, next: Function, context: Context) => {
return cfOpGenerator.generate(message, next, context, this.cfState);
}
);
this.add(
Instruction.STATE_TRANSITION_PROPOSE,
async (message: InternalMessage, next: Function, context: Context) => {
return StateTransition.propose(message, next, context, this.cfState);
}
);
this.add(
Instruction.STATE_TRANSITION_COMMIT,
async (message: InternalMessage, next: Function, context: Context) => {
return StateTransition.commit(message, next, context, this.cfState);
}
);
this.add(Instruction.KEY_GENERATE, KeyGenerator.generate);
this.add(Instruction.OP_SIGN_VALIDATE, SignatureValidator.validate);
this.add(Instruction.IO_PREPARE_SEND, NextMsgGenerator.generate);
}
public add(scope: Instruction, method: Function) {
if (scope in this.middlewares) {
this.middlewares[scope].push({ scope, method });
} else {
this.middlewares[scope] = [{ scope, method }];
}
}
public async run(msg: InternalMessage, context: Context) {
let counter = 0;
const middlewares = this.middlewares;
const opCode = msg.opCode;
this.executeAllMiddlewares(msg, context);
async function callback() {
if (counter === middlewares[opCode].length - 1) {
return Promise.resolve(null);
}
// This is hacky, prevents next from being called more than once
counter += 1;
const middleware = middlewares[opCode][counter];
if (opCode === Instruction.ALL || middleware.scope === opCode) {
return middleware.method(msg, callback, context);
}
return callback();
}
// TODO: Document or throw error about the fact that you _need_ to have
// a middleware otherwise this will error with:
// `TypeError: Cannot read property '0' of undefined`
return this.middlewares[opCode][0].method(msg, callback, context);
}
/**
* Runs the middlewares for Instruction.ALL.
*/
// TODO: currently this method seems to be passing null as the middleware callback and
// just iterating through all the middlewares. We should pass the callback similarly to how
// run does it, and rely on that for middleware cascading
private executeAllMiddlewares(msg, context) {
const all = this.middlewares[Instruction.ALL];
if (all && all.length > 0) {
all.forEach(middleware => {
middleware.method(msg, null, context);
});
}
}
}
/**
* Interface to dependency inject blockchain commitments. The middleware
* should be constructed with a CfOpGenerator, which is responsible for
* creating CfOperations, i.e. commitments, to be stored, used, and signed
* in the state channel system.
*/
export abstract class CfOpGenerator {
public abstract generate(
message: InternalMessage,
next: Function,
context: Context,
cfState: CfState
);
}
export class NextMsgGenerator {
public static generate(
internalMessage: InternalMessage,
next: Function,
context: Context
) {
const signature = NextMsgGenerator.signature(internalMessage, context);
const lastMsg = NextMsgGenerator.lastClientMsg(internalMessage, context);
const msg: ClientActionMessage = {
signature,
requestId: "none this should be a notification on completion",
appId: lastMsg.appId,
appName: lastMsg.appName,
action: lastMsg.action,
data: lastMsg.data,
multisigAddress: lastMsg.multisigAddress,
toAddress: lastMsg.fromAddress, // swap to/from here since sending to peer
fromAddress: lastMsg.toAddress,
seq: lastMsg.seq + 1
};
return msg;
}
/**
* @returns the last received client message for this protocol. If the
* protocol just started, then we haven't received a message from
* our peer, so just return our starting message. Otherwise, return
* the last message from our peer (from IO_WAIT).
*/
public static lastClientMsg(
internalMessage: InternalMessage,
context: Context
) {
const res = getLastResult(Instruction.IO_WAIT, context.results);
// TODO: make getLastResult's return value nullable
return JSON.stringify(res) === JSON.stringify({})
? internalMessage.clientMessage
: res.value;
}
public static signature(
internalMessage: InternalMessage,
context: Context
): Signature | undefined {
// first time we send an install message (from non-ack side) we don't have
// a signature since we are just exchanging an app-speicific ephemeral key.
const lastMsg = NextMsgGenerator.lastClientMsg(internalMessage, context);
if (
internalMessage.actionName === ActionName.INSTALL &&
lastMsg.seq === 0
) {
return undefined;
}
return getFirstResult(Instruction.OP_SIGN, context.results).value;
}
}
export class KeyGenerator {
/**
* After generating this machine's app/ephemeral key, mutate the
* client message by placing the ephemeral key on it for my address.
*/
public static generate(message: InternalMessage, next: Function) {
const wallet = ethers.Wallet.createRandom();
const installData = message.clientMessage.data;
// FIXME: properly assign ephemeral keys
// if (installData.peerA.address === message.clientMessage.fromAddress) {
// installData.keyA = wallet.address;
// } else {
// installData.keyB = wallet.address;
// }
// return wallet;
}
}
export class SignatureValidator {
public static async validate(
message: InternalMessage,
next: Function,
context: Context
) {
const incomingMessage = getFirstResult(
Instruction.IO_WAIT,
context.results
);
const op = getFirstResult(Instruction.OP_GENERATE, context.results);
// TODO: now validate the signature against the op hash
next();
}
}
/**
* Utilitiy for middleware to access return values of other middleware.
*/
export function getFirstResult(
toFindOpCode: Instruction,
results: { value: any; opCode }[]
): OpCodeResult {
// FIXME: (ts-strict) we should change the results data structure or design
return results.find(({ opCode, value }) => opCode === toFindOpCode)!;
}
export function getLastResult(
toFindOpCode: Instruction,
results: { value: any; opCode }[]
): OpCodeResult {
for (let k = results.length - 1; k >= 0; k -= 1) {
if (results[k].opCode === toFindOpCode) {
return results[k];
}
}
return Object.create(null);
}