以太坊C++系列(05)-状态通道的简要实现

背景描述

以太坊为作为一个公有链,允许任何人发布智能合约,但使用以太坊网络的成本很高,无论是普通交易或是智能合约都需要一定费用。尤其对于大批量的小额交易来讲,由于这些交易是需要全网共识的,如果频繁的执行智能合约,不但会增加以太坊网络的负担,光交易手续费一项,就让人望而却步。
状态通道为此提供了一种新的思路,通过将部分流程移出到链外来提高区块链的效率,但这并不会增加参与者的风险。
举一个简单的比方:区块链相当于银行,而状态通道相当于支付宝。假设Alice跟Bob各自有10000块钱。之前,如果Alice跟Bob如果要互相转账,都要去银行排队。若是大额的还好,如果他们之间每次转1分钱,假设Alice给Bob转了60000次,Bob给Alice转了80000次,那么他们各自得去银行60000次与80000次,而且每转1分钱可能需要付出比1分钱还要高的手续费。现在有了支付宝就不一样了,Alice跟Bob把这10000块钱转到支付宝,然后他们分别转的60000次与80000次都在支付宝里面进行,等转完之后,Alice或者Bob想要把钱提出来的时候,支付宝只要告诉银行说,Alice 现在的金额是 10000 - 60000 * 0.1 + 80000 * 0.1 = 12000 元,Bob现在的金额是 10000 + 60000 * 0.1 - 80000 * 0.1 = 8000。这样,我们需要在银行(区块链)中执行的 60000 + 80000次,减少为只要做 1 次。其他的 60000 + 80000 - 1都在支付宝(状态通道)里面完成了。

后台简要实现

状态通道结合了支付通道和Aeternity通道的特点,将私有privateState和公共publicState(区块链)数据完全分离,也就是在后台会存在两份LevelDB数据库。私有交易和链上交易分开执行的方式,一方面保护链上数据的安全性,同时私有交易可明显提高交易执行效率以及降低交易执行的开销。
其中privateState上的所有交易只需要在交易相关方节点上执行的交易,交易执行结果只记录在交易相关方privateState上,各个节点只维护自己的privateState账本,交易只广播给相关方。为了保证链上交易不受影响,私有交易有单独的rpc入口。此处privateState上的交易类似上面描述在支付宝里面的交易。
而publicState上的交易需要在区块链上执行并确认的交易,交易执行结果记录在区块链的State上,每个节点都维护相同的账本,交易需要在整个链内广播。此处publicState上的交易类似上面描述在银行里面的交易。

合约接口简要描述

StateChannelManager.sol

状态通道管理合约,区块链上用于管理状态通道的合约,存储在区块链publicState上。主要接口有如下:
function open(address _contractAddr, string _participants, uint _expireHeight)
建立通道,记录状态通道入口合约地址,参与方,通道超时高度等信息。
入参:

  • _contractAddr: address 通道合约地址。
  • _participants: string 通道参与方地址列表。
  • _expireHeight: uint 超时高度。

出参:无


function deposit(address _contractAddr, uint _amount)
账户金额锁定。
入参:

  • _contractAddr: address 通道合约地址。
  • _amount: uint 锁定金额。

出参:无


function commit(address _contractAddr, uint _seqNo, string _balanceList, string _signatureList, string _storageRoot)
提交确认。
入参:

  • _contractAddr: address 通道合约地址。
  • _seqNo: uint 私有合约序列号。
  • _balanceList: string 按顺序的余额列表 ,号分隔。
  • _signatureList: string 按顺序的签名列表 ,号分隔。
  • _storageRoot: string 私有合约的storageRoot。

出参:无


function close(address _contractAddr, uint _amount)
关闭通道。
入参:

  • _contractAddr: address 通道合约地址。

出参:无


function getChannelInfo(address _contractAddr) returns(string _json)
获取通道信息。
入参:

  • _contractAddr: address 通道合约地址。

出参:

  • _json: string 通道信息。一个string的JSON字符串。

PayChannel.sol

状态通道入口合约,具体业务逻辑功能合约,依具体业务场景不同而逻辑不同。存储在区块链privateState上。主要接口有如下:
function deposit(uint _amount)
账户金额充值。
入参:

  • _amount: uint 锁定金额。

出参:无


function transfer(address _to, uint _amount)
转账。
入参:

  • _to: address 目标账户地址。
  • _amount: uint 转账金额。

出参:无


function query() returns(uint)
当前余额查询。
入参:无
出参:

  • _amount: uint 当前账户余额。

function seqno() constant returns(uint)
当前序列号。
入参:无
出参:

  • seqNo: uint 序列号。

Tickets.sol

状态通道入口合约阶段确认合约,目的是收集相关方签名确认,以便确认状态能同步到区块链上,存储在区块链privateState上。主要接口有如下:
function confirm(address _addr, uint _seqNo, uint _balance, string _storageRoot, string _signature)
账户金额充值。
入参:

  • _addr: address 合约地址。
  • _seqNo: uint 序列号。
  • _balance: uint 确认者的余额。
  • _storageRoot: string 合约账户storageRoot。
  • _signature: string 对storageRoot的签名值。

出参:无


function queryTicket(address _addr, uint _seqNo) view returns(string _json)
查询签名信息。
入参:

  • _to: address 合约地址。
  • _seqNo: uint 序列号。

出参:

  • _json: string 签名信息。一个string的JSON字符串。

状态通道使用流程概述

  1. 设置参与方列表。
  2. 在私链privateState发布入口合约。
  3. 在公链publicState打开状态通道。
  4. 在公链publicState锁定资产。
  5. 在私链privateState上充值。
  6. 在私链privateState上执行若干次业务。
  7. 在私链privateState上提交确认结果。
  8. 参与方全部确认之后,任意方获取私链入口合约的状态树根storageRoot与签名列表,在公链publicState执行确认。
  9. 公链区块高度大于最后一次确认高度时,任意参与方关闭状态通道。

测试流程

  • 环境搭建:首先搭建主链,然后使用命令./jutools private create --address '0x00b20b6b6fe489a749baedb7aa389bf6806341d7' --outdir ../data/ --balance 1000000000000初始化私链。此命令主要完成私链的创世区块初始化,以及将合约PayChannel.solTickets.sol作为系统合约写入私链。其中 --address 后参数用非admin账户创建。
  • 安装Redis:以Ubuntu为例,执行命令sudo apt-get install redis-server
  • 设置状态通道参与方:在私链使用JSON-RPC接口admin_nodeInfo获取各自私链enode,将enode里面的0.0.0.0替换成自己的IP。然后通过JSON-RPC接口ju_setStateChannelPeers设置参与方列表。所有JSON-RPC调用均可使用网页以太坊开发工具集。示例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 获取一个参与方enode
    curl -X POST --data '{"jsonrpc":"2.0","method":"admin_nodeInfo","params":[],"id":1}'
    {
    "enode": "enode://5da1a7b5282917d443ae93939fa8ae0aa7a503846a958c7ed8b7f4fe436ef62a77265b0c3e782c7057a778842cb0684e04648d90a8162e60a045978a60d7c91a@0.0.0.0:16789",
    // 其他无关数据简化不写
    }

    // 获取另外一方enode(省略...)

    ju_setStateChannelPeers 使用说明
    参数:
    string - 入口合约地址
    string - 参与方enode列表,用","隔开。
    返回值:
    true 设置成功,false 设置失败。

    // 使用 ju_setStateChannelPeers 设置参与方列表
    curl -X POST --data '{"jsonrpc":"2.0","method":"ju_setStateChannelPeers","params":["0x1100000000000000000000000000000000000002","enode://5da1a7b5282917d443ae93939fa8ae0aa7a503846a958c7ed8b7f4fe436ef62a77265b0c3e782c7057a778842cb0684e04648d90a8162e60a045978a60d7c91a@192.168.10.1:16789,enode://7097031c7d4d81155f8c8aebd79b79c2c422e486f1fb0a03f778c1d28c6404746a598a1eef48af357339bccbe78c9171a2a5c7cfd7bf2e9774290d45d5f2502d@192.168.10.2:39290"],"id":1}'

    // 调用完毕可使用JSON-RPC接口admin_peers查看是否参与方加入成功
    curl -X POST --data '{"jsonrpc":"2.0","method":"admin_peers","params":[],"id":1}'

    如果返回的列表里面有加入的enode,那么表示此步骤成功。
  • 在公链调用合约 StateChannelManager 的 open函数登记一个状态通道。具体参数调用见上述说明。

  • 在公链调用合约 StateChannelManager 的 deposit函数锁定资产。
  • 做完上诉两步,在公链可使用 StateChannelManager 的 getChannelInfo 函数查看上诉等级通道与资产锁定是否成功。
  • 在私链调用合约 PayChannel 的 deposit 方法给私链各个账号充值(也可不充值),需要注意的是,私链上的所有账号金额之和必须等于公链上调用getChannelInfo返回的锁定的资产。充值完成之后,可调用合约 PayChannel 的 query 方法查看是否充值成功。
  • 在私链上调用合约 PayChannel 的 transfer 函数进行多次转账。转账之后,可调用合约 PayChannel 的 query 方法查看是否转账成功。
  • 各自在私链上调用合约 Tickets 的 confirm 函数进行提交状态确认。提交之后,可使用queryTicket 函数查看提交结果。

    • 其中第二个参数seqNo 使用合约 PayChannel 的 seqno 函数查到。
    • 其中第三个参数_storageRoot,在私链上使用JSON-RPC接口 eth_getStorageRoot 获取私链入口合约的状态树哈希。示例如下:

      1
      2
      3
      4
      5
      6
      7
      8
      curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getStorageRoot","params":["0x1100000000000000000000000000000000000002", "latest"],"id":1}'

      eth_getStorageRoot 使用说明
      参数:
      string - 入口合约地址
      QUANTITY|TAG - 整数块编号,或者字符串"latest", "earliest" 或 "pending",获取最新请使用 "latest" 即可。
      返回值:
      合约地址storageRoot
    • 其中第四个参数_signature,可调用JSON-RPC接口 personal_signData 获取。示例如下:

      1
      2
      3
      4
      5
      6
      7
      8
      curl -X POST --data '{"jsonrpc":"2.0","method":"personal_signData","params":["0xe6e1d884a49be0e189a6886234c4bc1c7f72c3d0759df31e871fbfa1c4ea8e3b", "0x63f215ab36d2ad1e7769a6125123afb6621df7391531eca47816265f7c2710a8"],"id":1}'

      personal_signData 使用说明
      参数:
      string - 待使用私钥签名的数据
      string - 参与方账号的私钥(可使用以太坊开发工具集以太坊工具的账号管理获取账号私钥。将后台目录keys账号json数据复制进去,输入密码按确定。)
      返回值:
      使用账号的私钥签名的数据合约地址storageRoot
  • 在公链调用合约 StateChannelManager 的 commit函数提交最终结果。

  • 在公链调用合约 StateChannelManager 的 close函数关闭状态通道。
  • 在公链调用合约 StateChannelManager 的 getChannelInfo函数查看上诉所有操作是否生效。

测试脚本

请结合上述流程,查看该脚本。可在Node.js环境下或者以太坊工具集JavaScript模块运行。

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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
(async () => {
const Web3 = require("web3");
const Client = require("axios");
let sleep = time => {
return new Promise(resolve => setTimeout(resolve, time));
};

const ipA = "10.10.8.168";
const ipB = "10.10.8.169";
const urlA = `http://${ipA}:6789`;
const urlB = `http://${ipB}:6789`;
const PrivateurlA = `http://${ipA}:5789`;
const PrivateurlB = `http://${ipB}:5789`;

const pwd = "12345678";

const stateChannelManagerAddress = "0x7767408a6e129342c27b2bc831bdd4e42b82126b";
const payChannelAddress = "0x1100000000000000000000000000000000000002";
const ticketsAddress = "0x1100000000000000000000000000000000000028";

const stateChannelManagerAbi = [
{
constant: false,
inputs: [
{
name: "_contractAddr",
type: "address"
}
],
name: "close",
outputs: [],
payable: false,
type: "function"
},
{
constant: true,
inputs: [
{
name: "_contractAddr",
type: "address"
}
],
name: "getChannelInfo",
outputs: [
{
name: "_json",
type: "string"
}
],
payable: false,
type: "function"
},
{
constant: false,
inputs: [
{
name: "_contractAddr",
type: "address"
},
{
name: "_amount",
type: "uint256"
}
],
name: "deposit",
outputs: [],
payable: false,
type: "function"
},
{
constant: false,
inputs: [
{
name: "_contractAddr",
type: "address"
},
{
name: "_seqNo",
type: "uint256"
},
{
name: "_balanceList",
type: "string"
},
{
name: "_signatureList",
type: "string"
},
{
name: "_storageRoot",
type: "string"
}
],
name: "commit",
outputs: [],
payable: false,
type: "function"
},
{
constant: false,
inputs: [
{
name: "_contractAddr",
type: "address"
},
{
name: "_participants",
type: "string"
},
{
name: "_expireHeight",
type: "uint256"
}
],
name: "open",
outputs: [],
payable: false,
type: "function"
}
];
const payChannelAbi = [
{
constant: false,
inputs: [],
name: "query",
outputs: [
{
name: "",
type: "uint256"
}
],
payable: false,
type: "function"
},
{
constant: false,
inputs: [
{
name: "_to",
type: "address"
},
{
name: "_amount",
type: "uint256"
}
],
name: "transfer",
outputs: [],
payable: false,
type: "function"
},
{
constant: false,
inputs: [
{
name: "_amount",
type: "uint256"
}
],
name: "deposit",
outputs: [],
payable: false,
type: "function"
},
{
constant: true,
inputs: [],
name: "seqno",
outputs: [
{
name: "",
type: "uint256"
}
],
payable: false,
type: "function"
}
];
const ticketsAbi = [
{
constant: false,
inputs: [
{
name: "_addr",
type: "address"
},
{
name: "_seqNo",
type: "uint256"
},
{
name: "_balance",
type: "uint256"
},
{
name: "_storageRoot",
type: "string"
},
{
name: "_signature",
type: "string"
}
],
name: "confirm",
outputs: [],
payable: false,
type: "function"
},
{
constant: true,
inputs: [
{
name: "_addr",
type: "address"
},
{
name: "_seqNo",
type: "uint256"
}
],
name: "queryTicket",
outputs: [
{
name: "jsonResult",
type: "string"
}
],
payable: false,
type: "function"
}
];

Date.prototype.Format = function (fmt) {
var o = {
"M+": this.getMonth() + 1, //月份
"d+": this.getDate(), //日
"h+": this.getHours(), //小时
"m+": this.getMinutes(), //分
"s+": this.getSeconds(), //秒
"q+": Math.floor((this.getMonth() + 3) / 3), //季度
S: this.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o) if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length));
return fmt;
};

function getId() {
let id = parseInt(new Date().Format("hhmmss"));
return id;
}

let clientA = Client.create({
baseURL: urlA
});
let clientB = Client.create({
baseURL: urlB
});

let privateClientA = Client.create({
baseURL: PrivateurlA
});
let privateClientB = Client.create({
baseURL: PrivateurlB
});

const web3A = new Web3(urlA);
const web3B = new Web3(urlB);
const privateWeb3A = new Web3(PrivateurlA);
const privateWeb3B = new Web3(PrivateurlB);

let initClientDispatch = client => {
client.dispatch = async data => {
let replay = await client.post("", data);
return new Promise((resolve, reject) => {
if (replay.status === 200) {
resolve(replay.data);
} else {
reject("request error");
}
});
};
};
initClientDispatch(clientA);
initClientDispatch(clientB);
initClientDispatch(privateClientA);
initClientDispatch(privateClientB);

let rpc = async (client, method, params) => {
let data = {
method: method,
jsonrpc: "2.0",
id: parseInt(getId()),
params: params
};
try {
let replay = await client.dispatch(data);
return Promise.resolve(replay.result);
} catch (error) {
return Promise.reject(error);
}
};

let contractExcute = async (web3, abi, address, from, funcName, params, send = true) => {
let contract = new web3.eth.Contract(abi, address, null);
let func = contract.methods[funcName].apply(contract.methods, params);

try {
if (send) {
let options = {
gas: "0xe8d4a50fff",
gasPrice: "0x174876e800",
from: from
};
return Promise.resolve(await func.send(options));
} else {
return Promise.resolve(await func.call({ from: from }));
}
} catch (error) {
return Promise.reject(error);
}
};

let ret = {};
try {
const SEND = true;
const CALL = false;
const contractAddress = "0x00000000000000000000000000" + new Date().Format("yyyyMMddhhmmss");
ret.contractAddress = contractAddress;

let result = null;
let params = null;

// let accounts1 = await rpc(clientA, "eth_accounts", []);
// let accounts2 = await rpc(clientB, "eth_accounts", []);
let accountA = "0x00b20b6b6fe489a749baedb7aa389bf6806341d7";
let accountB = "0x002566a7a94203639b08ddb4e12fc1680942564c";
let accountPrivateKeyA = "0x63f215ab36d2ad1e7769a6125123afb6621df7391531eca47816265f7c2710a8";
let accountPrivateKeyB = "0x07e1f6fe69cd66672bd9a5ccea5b0251887209dfc63c245752d988a3b1513cfe";

let peers = await rpc(privateClientA, "admin_peers", []);
if (!(peers && peers.length >= 1)) {
// 获取节点
let nodeInfo1 = await rpc(privateClientA, "admin_nodeInfo", []);
let nodeInfo2 = await rpc(privateClientB, "admin_nodeInfo", []);
ret.enode1 = nodeInfo1.enode.replace("0.0.0.0", ipA);
ret.enode2 = nodeInfo2.enode.replace("0.0.0.0", ipB);

// 设置参与方(参与方有bug,不会跳过自己)
result = await rpc(privateClientA, "ju_setStateChannelPeers", [payChannelAddress, ret.enode2]);
if (!result) throw "ju_setStateChannelPeers failed!";

while (true) {
await sleep(3000);
console.log("confirm ju_setStateChannelPeers......");
let peers = await rpc(privateClientA, "admin_peers", []);
if (peers && peers.length >= 1) break;
}
console.log("ju_setStateChannelPeers success");
} else {
console.log("ju_setStateChannelPeers has set");
}

// 解锁账号,为打开通道做准备
console.log("personal_unlockAccount");
await rpc(clientA, "personal_unlockAccount", [accountA, pwd, 24 * 60 * 60]);
await rpc(clientB, "personal_unlockAccount", [accountB, pwd, 24 * 60 * 60]);
await rpc(privateClientA, "personal_unlockAccount", [accountA, pwd, 24 * 60 * 60]);
await rpc(privateClientB, "personal_unlockAccount", [accountB, pwd, 24 * 60 * 60]);

// 调用open函数打开状态通道
console.log("stateChannelManager open");
const expireHeight = 26;
params = [contractAddress, accountA + "," + accountB, expireHeight];
await contractExcute(web3A, stateChannelManagerAbi, stateChannelManagerAddress, accountA, "open", params, SEND);

// 获取私链A, B总额
console.log("payChannel query");
params = [];
ret.balanceAExcept = ret.balanceABegin = parseInt(await contractExcute(privateWeb3A, payChannelAbi, payChannelAddress, accountA, "query", params, CALL));
params = [];
ret.balanceBExcept = ret.balanceBBegin = parseInt(await contractExcute(privateWeb3B, payChannelAbi, payChannelAddress, accountB, "query", params, CALL));

// 资产锁定
console.log("stateChannelManager deposit");
ret.depositAmountTotal = ret.balanceABegin + ret.balanceBBegin;
if (ret.depositAmountTotal === 0) {
ret.depositAmountTotal = 10000;
}
params = [contractAddress, ret.depositAmountTotal];
await contractExcute(web3A, stateChannelManagerAbi, stateChannelManagerAddress, accountA, "deposit", params, SEND);

// 获取私链序列号
console.log("ju_getTransactionSeqNo");
params = [contractAddress];
ret.transactionSeqNo = await rpc(privateClientA, "ju_getTransactionSeqNo", params);

// 充值或者追加保证金
let deposit = ret.depositAmountTotal - ret.balanceABegin - ret.balanceBBegin;
if (deposit > 0) {
console.log("payChannel deposit " + deposit);
params = [deposit];
ret.balanceAExcept += deposit;
await contractExcute(privateWeb3A, payChannelAbi, payChannelAddress, accountA, "deposit", params, SEND);
ret.balanceABegin += deposit;
} else if (deposit < 0) {
deposit *= -1;
console.log("stateChannelManager deposit " + deposit);
ret.depositAmountTotal += deposit
params = [contractAddress, deposit];
await contractExcute(web3A, stateChannelManagerAbi, stateChannelManagerAddress, accountA, "deposit", params, SEND);
} else {
console.log("stateChannelManager deposit == balanceA + balanceB");
}

// 获取状态通道信息
console.log("stateChannelManager getChannelInfo");
params = [contractAddress];
result = await contractExcute(web3A, stateChannelManagerAbi, stateChannelManagerAddress, accountA, "getChannelInfo", params, CALL);
console.log("stateChannelManager getChannelInfo result = ", result);
ret.channelInfoBegin = JSON.parse(result);

// 执行私链转账给另外账户
let count = 10;
ret.transfetBalanceLogs = [];
while (count--) {
await sleep(3000);
let num = Math.random();
let transfetBalance = parseInt(Math.random() * 10 + 1) * 100;
let log = (num < 0.5 ? "A -> B" : "B -> A") + " payChannel transfer " + transfetBalance;
console.log(log);
ret.transfetBalanceLogs.push(log);
if (num < 0.5) {
params = [accountB, transfetBalance];
await contractExcute(privateWeb3A, payChannelAbi, payChannelAddress, accountA, "transfer", params, SEND);
ret.balanceAExcept -= transfetBalance;
ret.balanceBExcept += transfetBalance;
} else {
params = [accountA, transfetBalance];
await contractExcute(privateWeb3B, payChannelAbi, payChannelAddress, accountB, "transfer", params, SEND);
ret.balanceAExcept += transfetBalance;
ret.balanceBExcept -= transfetBalance;
}
}

// 再次查询余额
console.log("payChannel query");
params = [];
ret.balanceALast = parseInt(await contractExcute(privateWeb3A, payChannelAbi, payChannelAddress, accountA, "query", params, CALL));

params = [];
ret.balanceBLast = parseInt(await contractExcute(privateWeb3B, payChannelAbi, payChannelAddress, accountB, "query", params, CALL));

// 查询合约storageRoot
console.log("eth_getStorageRoot");
params = [payChannelAddress, "latest"];
ret.storageRoot = await rpc(privateClientA, "eth_getStorageRoot", params);

// 对storageRoot签名
console.log("personal_signData");
params = [ret.storageRoot, accountPrivateKeyA];
ret.signstorageRootA = await rpc(clientA, "personal_signData", params);
params = [ret.storageRoot, accountPrivateKeyB];
ret.signstorageRootB = await rpc(clientA, "personal_signData", params);

// 获取seqNo
console.log("payChannel seqno");
ret.seqNo = await contractExcute(privateWeb3A, payChannelAbi, payChannelAddress, accountA, "seqno", [], CALL);

// 各自提交结果
console.log("tickets confirm");
params = [contractAddress, ret.seqNo, ret.balanceALast, ret.storageRoot, ret.signstorageRootA];
await contractExcute(privateWeb3A, ticketsAbi, ticketsAddress, accountA, "confirm", params, SEND);

params = [contractAddress, ret.seqNo, ret.balanceBLast, ret.storageRoot, ret.signstorageRootB];
await contractExcute(privateWeb3B, ticketsAbi, ticketsAddress, accountB, "confirm", params, SEND);

// 查询签名
console.log("tickets queryTicket");
params = [contractAddress, ret.seqNo];
result = await contractExcute(privateWeb3A, ticketsAbi, ticketsAddress, accountA, "queryTicket", params, CALL);
ret.ticket = JSON.parse(result);

// 提交最终结果
console.log("stateChannelManager commit");
params = [contractAddress, ret.seqNo, `${ret.balanceALast},${ret.balanceBLast}`, `${ret.signstorageRootA},${ret.signstorageRootB}`, ret.storageRoot];
console.log("commit params", params);
await contractExcute(web3A, stateChannelManagerAbi, stateChannelManagerAddress, accountA, "commit", params, SEND);

// 关闭状态通道
console.log("stateChannelManager close");
params = [contractAddress];
await contractExcute(web3A, stateChannelManagerAbi, stateChannelManagerAddress, accountA, "close", params, SEND);

// 获取状态通道关闭状态
console.log("stateChannelManager getChannelInfo");
params = [contractAddress];
result = await contractExcute(web3A, stateChannelManagerAbi, stateChannelManagerAddress, accountA, "getChannelInfo", params, CALL);
ret.channelInfoEnd = JSON.parse(result);

console.log(ret);
} catch (error) {
console.log("error :" + error);
}
})();

您的支持将鼓励我继续创作!
0%