以太坊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调用均可使用网页以太坊开发工具集。示例如下:
    
    // 获取一个参与方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 获取私链入口合约的状态树哈希。示例如下:
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 获取。示例如下:
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模块运行。
```javascript
(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);
    }
})();
暂无评论

发送评论 编辑评论


				
上一篇
下一篇