欢迎来到墨客子链中文文档

名词解释

Vnode

墨客主网(母链)节点,用于主网挖矿,主网账本同步,主网交易以及子链数据传输的节点。

Scs

墨客子链节点,用于子链挖矿,子链账本同步以及子链业务逻辑执行的节点,也称为子链矿工

子链矿工池

存储子链矿工的池子,本质上池子是一个智能合约,需要子链节点注册

vnode代理池

存储vnode代理节点,本质上池子是一个智能合约,需要vnode节点

子链控制合约

用于控制整个子链的流程

监听节点(Monitor)

监听节点是一个特殊的Scs节点,可以用来监听某条子链的运行情况,当一个节点成为监听节点后,其只负责同步该子链的区块信息,不参与子链出块。Dapp用户可以通过该节点监控子链运行情况

flush

子链的一个特殊操作,每条正常运行的子链每隔一段时间需要向母链进行状态刷新,并且同时完成:Scs矿工的收益发放;有币子链和母链之间的货币充提等操作

子链要点

概述

子链是在母链之上的独立的区块链系统,子链中的每个节点称为scs,scs的通讯通过母链,并定期向母链进行数据背书。子链上可以单独跑业务逻辑,母链无需知道。

母链上可以跑多条子链,墨客采用分片技术,随机将scs分配给不同的子链。

子链需要依赖于母链来运行,因此,运行一条子链需要M个母链节点(vnode)和N个子链节点(scs)。

普通子链节点scs可以称作子链矿工,其主要负责子链的出块,是维持一条子链稳定安全运行的根本。

子链节点下的母链节点,需要选取一部分注册成vnode代理的节点,vnode代理节点的作用是用于维护子链稳定运行,vnode代理节点同样有一个代理矿工池

部署子链控制合约时需要指定以上两个池子的地址。

子链合约部署完后,即可调用registeropen来召唤池子里的scs注册;当数量达到预期后,就可以调用registerclose来初始化子链区块,让子链开始运行。

子链需要每隔一段时间发起flush操作,将关键状态写入母链进行背书。除此之外,flush还将完成节点收益分配和有币区块链的母子链充提操作。

可以调用子链控制合约里的registerasmonitor来将一个子链节点注册成一个侦听节点。侦听节点只负责信息查询,不参与普通子链节点的服务,适合dapp用户部署来监控子链运行状态。

如果普通子链节点状态不稳定或弄虚作假,在flush的时候,有几率被没收押金,强制退出这条子链的服务。这时,可以调用子链控制合约的registeradd方法,将scs池子里的其他scs作为备用节点来为这条子链服务。

部署子链方可以调用子链控制合约的close方法关闭这条子链。此时会进入清算状态,待所有子链收益结清后,即关闭子链。请注意,子链业务逻辑清算不包含。

子链节点本身可以调用子链控制合约的withdraw方法来结束为某一子链服务,并得到返还的押金。

子链中的MOAC押金和消耗

为了使子链安全稳定的运行,MOAC引入的押金机制,主要体现在以下几个方面:

1、每个子链节点在第一次启动后,将会有一个唯一的墨客钱包地址,在部署子链前,需要在向这个地址打入1个MOAC作为运行费用;

2、每个子链节点注册进入子链矿工池时,需要向矿工池缴纳一定的押金,最小值由子链控制合约设置,最大值不限。子链节点每被选中一次,将会扣除一定数额的押金,当押金被扣完后,该节点将不会再参与新的子链,退出子链时,可以调用方法取回押金;

3、当一个子链节点注册成一个监听节点时,需要缴纳一定的押金;当退出子链时可以取回押金;

4、押金一般不会扣除,但在flush时,如果有企图作弊的节点,将会按照规则踢出子链,并扣除押金,不再返还;

子链中的MOAC消耗:

首先,调用子链方法不会消耗任何gas,但是,dapp运营方需要向子链控制合约地址打入一定量的MOAC维持子链运行,这部分MOAC将会消耗在给子链矿工费用和主链充提gas返还上。

子链部署的注意点

1、子链scs的vnode需要互相addPeer以保证通讯畅通。同时,这些vnode建议尽量add外面的节点以保证主链高度一致。

2、子链scs的时钟请同步互联网标准时间。

部署子链前的准备工作

Vnode节点

墨客主网节点 版本来源: https://github.com/MOACChain/moac-core/releases/

此文档采用的版本: 1.0.5 环境:windows testnet浏览器: http://47.75.144.55:3000/home

在测试环境testnet启动节点:
moac-windows-4.0-amd64.exe –testnet –rpc –rpcapi “chain3,mc,net,db,personal,admin,miner”

验证:

windows command 执行 moac-windows-4.0-amd64.exe attach \\.\pipe\moac.ipc
运行concole命令 mc.blockNumber 检查是否同步到最新区块

各类账号

可以运行concole命令 personal.newAccount() 创建账号; mc.accounts查看账号;

按序号查询余额:mc.getBalance(mc.accounts[0])

测试环境的公共提币地址:http://119.28.13.213:3000/

注意:后续消耗gas的操作都需要执行personal.unlockAccount(mc.accounts[0]) 对应账号进行解锁

准备账号列表:(示例地址参考后续的命令操作)

子链操作账号:进行创建合约,发起交易等基本操作: 0x87e369172af1e817ebd8d63bcd9f685a513a6736
主链vnode收益账号:    0xf103bc1c054babcecd13e7ac1cf34f029647b08c
子链scs收益账号:0xa934198916cd993c73c1aa6e0c0e7b21ce7c735b  0x2e7c076dbf6e61207a0ddb1b942ef7da8fd139f0

chain3 的 nodejs 环境

安装: npm install chain3

验证:

> chain3 = require('chain3');
> chain3 = new chain3();
> chain3.setProvider(new chain3.providers.HttpProvider('http://localhost:8545'));
> chain3.mc.blockNumber  检查是否获得当前区块

子链的部署方法

部署Vnode矿池合约

首先部署vnode矿池合约,VnodeProtocolBase,如果加入现成的vnode矿池,则可以忽略此步骤。

加入矿池的代理Vnode节点被用于提供子链调用服务和子链历史数据中转服务的节点。

以下为nodejs部署示例:最低保证金为 2 moac

> chain3 = require('chain3')
> solc = require('solc')
> chain3 = new chain3();
> chain3.setProvider(new chain3.providers.HttpProvider('http://localhost:8545'));
> solfile = 'VnodeProtocolBase.sol';
> contract = fs.readFileSync(solfile, 'utf8');
> output = solc.compile(contract, 1);
> abi = output.contracts[':VnodeProtocolBase'].interface;
> bin = output.contracts[':VnodeProtocolBase'].bytecode;
> VnodeProtocolBaseContract = chain3.mc.contract(JSON.parse(abi));
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> VnodeProtocolBase = VnodeProtocolBaseContract.new( 2, { from: chain3.mc.accounts[0],  data: '0x' + bin,  gas: '5000000'});
> chain3.mc.getTransactionReceipt(VnodeProtocolBase.transactionHash).contractAddress

部署完毕后,获得 vnode矿池合约地址 0x22f141dcc59850707708bc90e256318a5fe0b928

注意:gas 不要设置太大, 不然会触发错误 exceeds block gas limit undefined

vnode 设置代理并加入矿池

修改vnode目录配置文件vnodeconfig.json: VnodeBeneficialAddress里设置收益账号: 0xf103bc1c054babcecd13e7ac1cf34f029647b08c

这个账号也作为vnode的address,矿池中对应这个vnode的唯一编号

调用vnode矿池合约register方法加入矿池

参数:

from: 子链测试账号
value:押金,必须大于矿池合约的设置值
to: vnode矿池合约地址
data: register(address,string)

关于data传递调用register参数说明:

根据ABI chain3.sha3("register(address,string)") = 0x32434a2e90725ed590daff07a244305001c58c49f7bef73ce5e7249acf69f561
        取前4个字节 0x32434a2e
第一个参数address 传  vnodeconfig.json的VnodeBeneficialAddress  (前面补24个0, 凑足32个字节)
        000000000000000000000000f103bc1c054babcecd13e7ac1cf34f029647b08c
第二个参数string传 vnode提供给子链的调用地址 192.168.10.209:50062
端口号对应vnodeconfig.json的VnodeServiceCfg
        string数据类型 + string数据长度 + string内容
        string数据类型: 0000000000000000000000000000000000000000000000000000000000000040
        string内容: data = new Buffer('192.168.10.209:50062', 'utf8').toString('hex');
                后面补0, 凑足32个字节   3139322e3136382e31302e3230393a3530303632000000000000000000000000
        string数据长度:     3139322e3136382e31302e3230393a3530303632        20字节
                0000000000000000000000000000000000000000000000000000000000000014
        data = '0x32434a2e000000000000000000000000f103bc1c054babcecd13e7ac1cf34f029647b08c000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000143139322e3136382e31302e3230393a3530303632000000000000000000000000'

调用示例:

> amount = chain3.toSha(5,'mc')
> data = '0x32434a2e000000000000000000000000f103bc1c054babcecd13e7ac1cf34f029647b08c000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000143139322e3136382e31302e3230393a3530303632000000000000000000000000';
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> chain3.mc.sendTransaction({ from: chain3.mc.accounts[0], value:amount, to: '0x22f141dcc59850707708bc90e256318a5fe0b928', gas: "5000000", gasPrice: chain3.mc.gasPrice, data: data });

验证: 访问Vnode矿池合约的vnodeCount

> chain3.mc.getStorageAt("0x22f141dcc59850707708bc90e256318a5fe0b928",0x02)   // 注意vnodeCount变量在合约中变量定义的位置(16进制)

部署子链矿池

目前针对不同的共识协议,可以创建对应的子链矿池,接受对应SCS的注册,并缴纳保证金,进入矿池后,成为子链的候选节点

如果加入现成的子链矿池,则可以忽略此步骤

部署SubChainProtocolBase.sol示例: 共识:POR 最低保证金: 2moac

> chain3 = require('chain3')
> solc = require('solc')
> chain3 = new chain3();
> chain3.setProvider(new chain3.providers.HttpProvider('http://localhost:8545'));
> solfile = 'SubChainProtocolBase.sol';
> contract = fs.readFileSync(solfile, 'utf8');
> output = solc.compile(contract, 1);
> abi = output.contracts[':SubChainProtocolBase'].interface;
> bin = output.contracts[':SubChainProtocolBase'].bytecode;
> subchainprotocolbaseContract = chain3.mc.contract(JSON.parse(abi));
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> subchainprotocolbase = subchainprotocolbaseContract.new( "POR",  2, { from: chain3.mc.accounts[0],  data: '0x' + bin,  gas: '5000000'});
> chain3.mc.getTransactionReceipt(subchainprotocolbase.transactionHash).contractAddress

部署完毕后,获得子链矿池合约地址 0xe42f4f566aedc3b6dd61ea4f70cc78d396130fac

设置启动scs

这里我们设置两个scs节点

确认 userconfig.json配置

VnodeServiceCfg为代理vnode地址: 192.168.10.209:50062
Beneficiary为收益账号:
        0xa934198916cd993c73c1aa6e0c0e7b21ce7c735b
        0x2e7c076dbf6e61207a0ddb1b942ef7da8fd139f0

分别通过命令启动 scsserver-windows-4.0-amd64 –password “123456” (生成scs keystore的密码)

然后在生成的keystore文件中分别获得 scs 地址

d4057328a35f34507dbcd295d43ed0cccf9c368a
0x3e21ba36b396936c6cc9adc3674655b912e5fa54

最后给scs转入moac以支付必要的交易费用

> amount = 20;
> scsaddr = '0xd4057328a35f34507dbcd295d43ed0cccf9c368a';
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> chain3.mc.sendTransaction( { from: chain3.mc.accounts[0], value:chain3.toSha(amount,'mc'), to: scsaddr, gas: "2000000", gasPrice: chain3.mc.gasPrice, data: ''});
> scsaddr = '0x3e21ba36b396936c6cc9adc3674655b912e5fa54';
> chain3.mc.sendTransaction( { from: chain3.mc.accounts[0], value:chain3.toSha(amount,'mc'), to: scsaddr, gas: "2000000", gasPrice: chain3.mc.gasPrice, data: ''});

可以通过查询余额进行验证

> chain3.mc.getBalance('0xd4057328a35f34507dbcd295d43ed0cccf9c368a')
> chain3.mc.getBalance('0x3e21ba36b396936c6cc9adc3674655b912e5fa54')

将scs加入子链矿池

调用子链矿池合约register方法加入矿池

参数:

from: 子链测试账号
value:押金,必须大于矿池合约的设置值
to: 子链矿池合约地址
data: register(address)

关于data传递调用register参数说明:

根据ABI chain3.sha3("register(address)") = 0x4420e4869750c98a56ac621854d2d00e598698ac87193cdfcbb6ed1164e9cbcd
        取前4个字节 0x4420e486
参数address传scs 地址    d4057328a35f34507dbcd295d43ed0cccf9c368a  (前面补24个0, 凑足32个字节)
        000000000000000000000000d4057328a35f34507dbcd295d43ed0cccf9c368a
data = '0x4420e486000000000000000000000000d4057328a35f34507dbcd295d43ed0cccf9c368a'

调用示例:

> amount = chain3.toSha(5,'mc')
> data = '0x4420e486000000000000000000000000d4057328a35f34507dbcd295d43ed0cccf9c368a';
> chain3.mc.sendTransaction({ from: chain3.mc.accounts[0], value:amount, to: '0xe42f4f566aedc3b6dd61ea4f70cc78d396130fac', gas: "5000000", gasPrice: chain3.mc.gasPrice, data: data });

验证: 访问子链矿池合约的scsCount

> chain3.mc.getStorageAt("0xe42f4f566aedc3b6dd61ea4f70cc78d396130fac",0x02)     // 注意scsCount变量在合约中变量定义的位置(16进制)

同上将另一个scs(0x3e21ba36b396936c6cc9adc3674655b912e5fa54)也加入子链矿池

部署子链合约

现在我们可以部署一个子链合约,并准备将两个scs

部署SubChainBase.sol示例:

> chain3 = require('chain3')
> solc = require('solc')
> chain3 = new chain3();
> chain3.setProvider(new chain3.providers.HttpProvider('http://localhost:8545'));
> input = {'': fs.readFileSync('SubChainBase.sol', 'utf8'), 'SubChainProtocolBase.sol': fs.readFileSync('SubChainProtocolBase.sol', 'utf8')};
> output = solc.compile({sources: input}, 1);
> abi = output.contracts[':SubChainBase'].interface;
> bin = output.contracts[':SubChainBase'].bytecode;
> proto = '0xe42f4f566aedc3b6dd61ea4f70cc78d396130fac' ;    // 子链矿池合约
> vnodeProtocolBaseAddr = '0x22f141dcc59850707708bc90e256318a5fe0b928' ;       // Vnode矿池合约
> min = 1 ;                     // 子链需要SCS的最小数量
> max = 10 ;            // 子链需要SCS的最大数量
> thousandth = 1 ;                      // 千分之几
> flushRound = 40 ;             // 子链刷新周期  单位是主链block生成对应数量的时间
> SubChainBaseContract = chain3.mc.contract(JSON.parse(abi));
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> SubChainBase = SubChainBaseContract.new( proto, vnodeProtocolBaseAddr, min, max, thousandth, flushRound,{ from: chain3.mc.accounts[0],  data: '0x' + bin,  gas:'9000000'} , function (e, contract){console.log('Contract address: ' + contract.address + ' transactionHash: ' + contract.transactionHash); });

部署完毕后, 获得子链合约地址 0x1195cd9769692a69220312e95192e0dcb6a4ec09

子链开放注册

首先子链合约需要最终提供gas费给scs,需要给子链控制合约发送一定量的moac,调用合约里的函数addFund

根据ABI chain3.sha3("addFund()") = 0xa2f09dfa891d1ba530cdf00c7c12ddd9f6e625e5368fff9cdf23c9dc0ad433b1
        取前4个字节 0xa2f09dfa
> amount = 20;
> subchainaddr = '0x1195cd9769692a69220312e95192e0dcb6a4ec09';
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> chain3.mc.sendTransaction( { from: chain3.mc.accounts[0], value:chain3.toSha(amount,'mc'), to: subchainaddr, gas: "2000000", gasPrice: chain3.mc.gasPrice, data: '0xa2f09dfa'});

可以通过查询余额进行验证

> chain3.mc.getBalance('0x1195cd9769692a69220312e95192e0dcb6a4ec09')

然后调用 调用合约里的函数registerOpen 开放注册 (按子链矿池合约中SCS注册先后排序进行选取)

根据ABI chain3.sha3("registerOpen()") = 0x5defc56ce78f178d760a165a5528a8e8974797e616a493970df1c0918c13a175
        取前4个字节 0x5defc56c
> subchainaddr = '0x1195cd9769692a69220312e95192e0dcb6a4ec09';
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> chain3.mc.sendTransaction( { from: chain3.mc.accounts[0], value:0, to: subchainaddr, gas: "2000000", gasPrice: chain3.mc.gasPrice, data: '0x5defc56c'});
验证: 访问子链合约的 registerFlag 为 1 , 等待scs注册 (vnode 一个 flush周期后 ) , 访问子链合约的 nodeCount
> chain3.mc.getStorageAt(subchainaddr,0x14) // 注意registerFlag变量在合约中变量定义的位置(16进制) > chain3.mc.getStorageAt(subchainaddr,0x0e) // 注意nodeCount变量在合约中变量定义的位置(16进制)

子链关闭注册

等到两个scs都注册完毕后,即注册SCS数目大于等于子链要求的最小数目时,调用子链合约里的函数 registerClose关闭注册

根据ABI chain3.sha3("registerClose()") = 0x69f3576fc10c82561bd84b0045ee48d80d59a866174f2513fdef43d65702bf70
        取前4个字节 0x69f3576f
> subchainaddr = '0x1195cd9769692a69220312e95192e0dcb6a4ec09';
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> chain3.mc.sendTransaction( { from: chain3.mc.accounts[0], value:0, to: subchainaddr, gas: "2000000", gasPrice: chain3.mc.gasPrice, data: '0x69f3576f'});
验证: 访问子链合约的 registerFlag 为 0
> chain3.mc.getStorageAt(subchainaddr,0x14) // 注意registerFlag变量在合约中变量定义的位置(16进制)

SCS自身完成初始化并开始子链运行,可观察scs的concole界面,scs开始出块即成功完成部署子链。

子链业务逻辑的部署

同主链相同,业务逻辑的实现也通过智能合约的方式。

DAPP智能合约的部署

DAPP智能合约也通过主链的sendTransaction发送交易到 proxy vnode 的方式进行部署。

参数:

to: 子链控制合约subchainbase的地址
gas: 不需要消耗gas费用,传值:0
shardingflag:表示操作子链, 传值:0x1
via: 对应 proxy vnode 的收益地址

准备一个简单的合约 deorder.sol, 部署示例

> chain3 = require('chain3')
> solc = require('solc')
> chain3 = new chain3();
> chain3.setProvider(new chain3.providers.HttpProvider('http://localhost:8545'));
> solfile = 'deorder.sol';
> contract = fs.readFileSync(solfile, 'utf8');
> output = solc.compile(contract, 1);
> abi = output.contracts[':DeOrder'].interface;
> bin = output.contracts[':DeOrder'].bytecode;
> subchainaddr = '0x1195cd9769692a69220312e95192e0dcb6a4ec09';
> via = '0xf103bc1c054babcecd13e7ac1cf34f029647b08c';
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> chain3.mc.sendTransaction({from: chain3.mc.accounts[0], value:0, to: subchainaddr, gas:0, shardingFlag: "0x1", data: '0x' + bin, nonce: 0, via: via, });
验证:
合约部署成功后,Nonce值应该是1 可调用monitor的rpc接口ScsRPCMethod.GetNonce进行检查,具体详见子链接口调用部分。

DAPP智能合约的调用

DAPP智能合约的调用也通过主链的sendTransaction发送交易到 proxy vnode 的方式进行。

例如 deorder.sol有个方法 createOrder(string ordernum)

参数:

to: 子链控制合约subchainbase的地址
nonce:调用monitor的rpc接口ScsRPCMethod.GetNonce获得
gas: 0 不需要消耗gas费用
shardingflag: 0x1  表示操作子链
via: 对应 proxy vnode 的收益地址
data: chain3.sha3("createOrder(string)") 取前4个字节 0x03b7c764,加上传值凑足32个字节

调用示例:

> nonce = 1
> data = '0x03b7c7640000000000000000000000000000000000000000000000000000000001000001'
> subchainaddr = '0x1195cd9769692a69220312e95192e0dcb6a4ec09';
> via = '0xf103bc1c054babcecd13e7ac1cf34f029647b08c';
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> chain3.mc.sendTransaction( { nonce: nonce, from: chain3.mc.accounts[0], value:0, to: subchainaddr, gas:0, shardingFlag:'0x1', data: data, via: via,});
验证:
每次操作成功后,Nonce会自动增加1 或者直接调用monitor的rpc接口ScsRPCMethod.GetContractInfo获得合约变量的方式进行验证。

子链的监听及子链接口调用

SCS Monitor

Monitor是一种特殊的子链SCS节点,其主要可以用于监控子链的状态和数据。

Monitor不参与子链的交易共识,只是同步区块数据,提供数据查询

子链启动的方式与scs区别在于参数不同,主要定义了rpc接口的访问控制

scsserver-windows-4.0-amd64 --password "123456" --rpcdebug --rpcaddr 0.0.0.0 --rpcport 2345 --rpccorsdomain "*"

子链运行后,Monitor可以调用子链控制合约subchainbase中的registerAsMonitor方法进行注册

调用registerAsMonitor参数说明:

根据ABI chain3.sha3("registerAsMonitor(address)") = 0x4e592e2f6fc52405522577d357d824c923989a62e4916d9b689311d8b2a6192c
        取前4个字节 0x4e592e2f
参数address传scs keystore文件中的地址 (前面补24个0, 凑足32个字节)
data = '0x4e592e2f000000000000000000000000d135afa5c8d96ba11c40cf0b52952d54bce57363'

注意registerAsMonitor不同版本参数
> data = contractInstance.registerAsMonitor.getData('0xd135afa5c8d96ba11c40cf0b52952d54bce57363','127.0.0.1')

调用示例:

> amount = chain3.toSha(1,'mc')
> subchainaddr = '0x1195cd9769692a69220312e95192e0dcb6a4ec09';
> data = '0x4e592e2f000000000000000000000000d135afa5c8d96ba11c40cf0b52952d54bce57363';
> chain3.mc.sendTransaction({ from: chain3.mc.accounts[0], value:amount, to: subchainaddr, gas: "5000000", gasPrice: chain3.mc.gasPrice, data: data });

验证: 观察SCS monitor concole界面开始同步子链区块,或者调用子链合约的getMonitorInfo方法

> contractInstance = SubChainBaseContract.at('0xb877bf4e4cc94fd9168313e00047b77217760930')

> contractInstance.getMonitorInfo.call()

子链RPC接口

根据子链启动的参数,用户可以访问对应端口,调用接口获取子链相关数据

以调用接口GetScsId为列,获得当前的scs编号,即scs keystore文件中的地址。

关于rpc http的接口访问,可以用类似postman之类的工具进行快捷测试

header设置:
        Content-Type = application/json
        Accept = application/json

Body设置:
        {"jsonrpc":"2.0","id":0,"method":"ScsRPCMethod.GetScsId","params":{}}

post返回结果:
        {
                "jsonrpc": "2.0",
                "id": 0,
                "result": "0xd135afa5c8d96ba11c40cf0b52952d54bce57363"
        }

同时也可以通过nodejs的方式调用

> request = require('request');
> url  = "http://127.0.0.1:2345/rpc";
> data = {"jsonrpc":"2.0","id":0,"method":"ScsRPCMethod.GetScsId","params":{}};
> request({ url: url, method: "POST", json: true, body: data, headers: {"Content-Type": 'application/json', "Accept": 'application/json'}}, function(error, response, result) {if (!error && response.statusCode == 200) {console.log(result)}});

以下是几个常用的RPC接口及调用body示例

GetNonce:获得子链的nonce,这是调用子链DAPP合约的必要参数之一,每当子链交易发送后会自动加1

SubChainAddr: 子链合约地址
Sender:查询账号, 每个账号在子链有不同的nonce
Body: {"jsonrpc":"2.0","id":0,"method":"ScsRPCMethod.GetNonce",
                        "params":{"SubChainAddr":"0x1195cd9769692a69220312e95192e0dcb6a4ec09",
                                "Sender":"0x87e369172af1e817ebd8d63bcd9f685a513a6736"
                         }
          }

GetBlockNumber:获得当前子链的区块高度

SubChainAddr: 子链合约地址
Body: {"jsonrpc":"2.0","id":0,"method":"ScsRPCMethod.GetBlockNumber",
                "params":{"SubChainAddr":"0x1195cd9769692a69220312e95192e0dcb6a4ec09"}
          }

GetBlock: 获得当前子链的指定的区块信息

SubChainAddr: 子链合约地址
Sender:查询账号
Body: {"jsonrpc":"2.0","id":0,"method":"ScsRPCMethod.GetBlock",
                "params":{"number":1000,"SubChainAddr":"0x1195cd9769692a69220312e95192e0dcb6a4ec09"}
          }

GetSubChainInfo:获得当前子链的信息

SubChainAddr: 子链合约地址
Body: {"jsonrpc":"2.0","id":0,"method":"ScsRPCMethod.GetSubChainInfo",
                "params":{"SubChainAddr":"0x1195cd9769692a69220312e95192e0dcb6a4ec09"}
          }

GetBalance:获得对应账号在子链中的余额

SubChainAddr: 子链合约地址
Sender:查询账号
Body: {"jsonrpc":"2.0","id":0,"method":"ScsRPCMethod.GetBalance",
                "params":{"SubChainAddr":"0x1195cd9769692a69220312e95192e0dcb6a4ec09",
                        "Sender":"0x87e369172af1e817ebd8d63bcd9f685a513a6736"
                        }
          }

GetDappState:获得子链DAPP合约的状态

SubChainAddr: 子链合约地址
Sender:子链合约地址创建者地址
Body: {"jsonrpc":"2.0","id":0,"method":"ScsRPCMethod.GetDappState",
                "params":{"SubChainAddr":"0x1195cd9769692a69220312e95192e0dcb6a4ec09",
                        "Sender":"0x87e369172af1e817ebd8d63bcd9f685a513a6736"
                 }
          }

getContractInfo:获得子链DAPP智能合约全局变量

SubChainAddr: 子链合约地址
Reqtype:  查询类型 0: 查看合约全部变量 , 1: 查看合约某一个数组变量 , 2: 查看合约某一个mapping变量 , 3: 查看合约某一个结构体变量, 4: 查看合约某一简单类型变量(单倍长度存储的变量), 5: 查看合约某一变长变量(如string、bytes)
Storagekey: 十六进制字符串,查询的变量在合约里面的index ,查询全部变量时可以不填
Position: 十六进制字符串,当Reqtype==1时,Position为数组维度(从0开始);当Reqtype==2时,Position为mapping下标
Structformat:针对结构体变量,1:single(简单类型变量单倍长度存储的变量), 2:list(简单类型数组变量)3:string变长变量(如string、bytes),若结构变量为ContractInfoReq,Structformat = []byte{‘1’,’3’,’3’,’3’}

获取合约 index 1 的 address 对应 Body:
{"jsonrpc":"2.0","id":0,"method":"ScsRPCMethod.GetContractInfo",
"params":{"subChainAddr":"0x1195cd9769692a69220312e95192e0dcb6a4ec09",
        "Request":[{"Reqtype":4,
                  "Storagekey":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
                  "Position":[],
                  "Structformat":[]}
                  ]
        }
}

子链节点的更替

关闭子链

母子链货币交互简介

墨客支持有币子链,并且提供母链货币和子链原生币之间的兑换(充提)。

当前,墨客提供三种类型的母子链货币交互,以下一一介绍

母链MOAC和子链原生币交互

这个是最基础的一种货币兑换。使用者可以在主链上充值MOAC,然后最早在下一个flush周期在子链上获取子链原生币。同理,使用者可以提出子链原生币,并最早在下一个flush周期获得主链MOAC。

子链部署准备

参考子链部署章节,完成部署子链的准备工作。

子链操作账号: 0x87e369172af1e817ebd8d63bcd9f685a513a6736
vnode矿池合约地址: 0x22f141dcc59850707708bc90e256318a5fe0b928
vnode代理地址: 0xf103bc1c054babcecd13e7ac1cf34f029647b08c    192.168.10.209:50062
子链矿池合约地址: 0xe42f4f566aedc3b6dd61ea4f70cc78d396130fac
scs0: 0x075447a6df1fde4f39243bc67a945312ff36c193  确保启动并加入子链矿池
scs1: 0x7932f827c90c5f06c0177f642a07edfa73ee3044  确保启动并加入子链矿池
scs monitor: 0xa5966600efb221097ce6a8ba1dc6eb1d5b43ef83

erc20 部署

这类似一个管理合约,负责和子链货币之间的转换

参考官方示例的erc20合约DirectExchangeToken.sol 调用示例: 注意参数 tokensupply为erc20 的token总量,exchangerate为moac与子链token的兑换比率

> chain3 = require('chain3')
> solc = require('solc')
> chain3 = new chain3();
> chain3.setProvider(new chain3.providers.HttpProvider('http://localhost:8545'));
> input = {'': fs.readFileSync('DirectExchangeToken.sol', 'utf8'), 'StandardToken.sol': fs.readFileSync('StandardToken.sol', 'utf8'), 'ERC20.sol': fs.readFileSync('ERC20.sol', 'utf8'), 'SafeMath.sol': fs.readFileSync('SafeMath.sol', 'utf8')};
> output = solc.compile({sources: input}, 1);
> abi = output.contracts[':DirectExchangeToken'].interface;
> bin = output.contracts[':DirectExchangeToken'].bytecode;
> tokensupply = 100000000;      // total supply
> exchangerate = 100;
> directexchangetokenContract = chain3.mc.contract(JSON.parse(abi));
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> dtoken = directexchangetokenContract.new( tokensupply, exchangerate,{ from: chain3.mc.accounts[0],  data: '0x' + bin,  gas:'9000000'} , function (e, contract){console.log('Contract address: ' + contract.address + ' transactionHash: ' + contract.transactionHash); });

部署完毕后, 获得erc20合约地址 0x5fcb383cf80a9d2961f4d8ba0a4b9e4a34157da7

subchainbase 部署

注意这个子链合约在官方基础上进行了修改,增加了setToken和MintToken相关的若干方法。 部署SubChainBase.sol示例:

> chain3 = require('chain3')
> solc = require('solc')
> chain3 = new chain3();
> chain3.setProvider(new chain3.providers.HttpProvider('http://localhost:8545'));
> input = {'': fs.readFileSync('SubChainBase.sol', 'utf8'), 'SubChainProtocolBase.sol': fs.readFileSync('SubChainProtocolBase.sol', 'utf8')};
> output = solc.compile({sources: input}, 1);
> abi = output.contracts[':SubChainBase'].interface;
> bin = output.contracts[':SubChainBase'].bytecode;
> proto = '0xe42f4f566aedc3b6dd61ea4f70cc78d396130fac' ;    // 子链矿池合约
> vnodeProtocolBaseAddr = '0x22f141dcc59850707708bc90e256318a5fe0b928' ;       // Vnode矿池合约
> min = 1 ;                     // 子链需要SCS的最小数量
> max = 10 ;            // 子链需要SCS的最大数量
> thousandth = 1 ;                      // 千分之几
> flushRound = 40 ;             // 子链刷新周期  单位是主链block生成对应数量的时间(10秒一个block)
> SubChainBaseContract = chain3.mc.contract(JSON.parse(abi));
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> SubChainBase = SubChainBaseContract.new( proto, vnodeProtocolBaseAddr, min, max, thousandth, flushRound,{ from: chain3.mc.accounts[0],  data: '0x' + bin,  gas:'9000000'} , function (e, contract){console.log('Contract address: ' + contract.address + ' transactionHash: ' + contract.transactionHash); });

部署完毕后, 获得子链合约地址 0xe9463e215315d6f1e5387a161868d7d0a4db89e8

ERC20 初始化

在子链的register open前先调用 subchainbase 的 setToken 方法
参数: DirectExchangeToken的合约地址
根据参数DirectExchangeToken的地址,实例化合约内部的DirectExchangeToke对象, 并将 DirectExchangeToken合约的totalsupply复制给合约变量 BALANCE

调用示例:

根据ABI chain3.sha3("setToken(address)") = 0x144fa6d7f374f07750ac69fe5e8b4f8a614ab02482bcb9f0fa3f2c98943860c6
        取前4个字节 0x144fa6d7
        参数传erc20合约地址  5fcb383cf80a9d2961f4d8ba0a4b9e4a34157da7  (前面补24个0, 凑足32个字节)
        0x144fa6d70000000000000000000000005fcb383cf80a9d2961f4d8ba0a4b9e4a34157da7
> subchainaddr = '0xe9463e215315d6f1e5387a161868d7d0a4db89e8';
> data = '0x144fa6d70000000000000000000000005fcb383cf80a9d2961f4d8ba0a4b9e4a34157da7'
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> chain3.mc.sendTransaction( { from: chain3.mc.accounts[0], value:0, to: subchainaddr, gas: "2000000", gasPrice: chain3.mc.gasPrice, data: data});

验证:

访问子链合约的 BALANCE 为 ERC20的 totalsupply

> chain3.mc.getStorageAt(subchainaddr,0x30)   // 注意BALANCE变量在合约中变量定义的位置(16进制)

然后调用DirectExchangeToken 的 updateOwner 方法 将 合约owner由发布者 改为 subchainbase地址 这样子链合约就可以操控ERC20合约,并进行方法调用。

根据ABI chain3.sha3('updateOwner(address)') = 0x880cdc3156e7a1d7441d29b6ec3cab090473e3e84f7cc83d0bdcd0a696e8064b
        取前4个字节 0x880cdc31
        参数传subchainbase合约地址  e9463e215315d6f1e5387a161868d7d0a4db89e8  (前面补24个0, 凑足32个字节)
        0x880cdc31000000000000000000000000e9463e215315d6f1e5387a161868d7d0a4db89e8
> erc20addr = '0x5fcb383cf80a9d2961f4d8ba0a4b9e4a34157da7';
> data = '0x880cdc31000000000000000000000000e9463e215315d6f1e5387a161868d7d0a4db89e8'
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> chain3.mc.sendTransaction( { from: chain3.mc.accounts[0], value:0, to: erc20addr, gas: "2000000", gasPrice: chain3.mc.gasPrice, data: data});

验证:

访问erc20合约的 owner 是否由部署账号 0x87e369172af1e817ebd8d63bcd9f685a513a6736 改为 子链合约地址 0xe9463e215315d6f1e5387a161868d7d0a4db89e8

> chain3.mc.getStorageAt(erc20addr,0x03)   // 注意owner变量在合约中变量定义的位置(16进制)

子链注册scs

子链开放注册

调用合约里的函数addFund

根据ABI chain3.sha3("addFund()") = 0xa2f09dfa891d1ba530cdf00c7c12ddd9f6e625e5368fff9cdf23c9dc0ad433b1
        取前4个字节 0xa2f09dfa
> amount = 20;
> subchainaddr = '0xe9463e215315d6f1e5387a161868d7d0a4db89e8';
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> chain3.mc.sendTransaction( { from: chain3.mc.accounts[0], value:chain3.toSha(amount,'mc'), to: subchainaddr, gas: "2000000", gasPrice: chain3.mc.gasPrice, data: '0xa2f09dfa'});

可以通过查询余额进行验证

> chain3.mc.getBalance('0xe9463e215315d6f1e5387a161868d7d0a4db89e8')

然后调用 调用合约里的函数registerOpen 开放注册 (按子链矿池合约中SCS注册先后排序进行选取)

根据ABI chain3.sha3("registerOpen()") = 0x5defc56ce78f178d760a165a5528a8e8974797e616a493970df1c0918c13a175
        取前4个字节 0x5defc56c
> subchainaddr = '0xe9463e215315d6f1e5387a161868d7d0a4db89e8';
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> chain3.mc.sendTransaction( { from: chain3.mc.accounts[0], value:0, to: subchainaddr, gas: "2000000", gasPrice: chain3.mc.gasPrice, data: '0x5defc56c'});

验证:

访问子链合约的 nodeCount

> chain3.mc.getStorageAt(subchainaddr,0x0e)  // 注意nodeCount变量在合约中变量定义的位置(16进制)

等到两个scs都注册完毕后,即注册SCS数目大于等于子链要求的最小数目时,调用子链合约里的函数 registerClose关闭注册

根据ABI chain3.sha3("registerClose()") = 0x69f3576fc10c82561bd84b0045ee48d80d59a866174f2513fdef43d65702bf70
        取前4个字节 0x69f3576f
> subchainaddr = '0xe9463e215315d6f1e5387a161868d7d0a4db89e8';
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> chain3.mc.sendTransaction( { from: chain3.mc.accounts[0], value:0, to: subchainaddr, gas: "2000000", gasPrice: chain3.mc.gasPrice, data: '0x69f3576f'});

验证:

访问子链合约的 registerFlag 为 0

> chain3.mc.getStorageAt(subchainaddr,0x14)     // 注意registerFlag变量在合约中变量定义的位置(16进制)

同时观察scs的concole界面,scs开始出块即成功完成部署子链。

dapp合约部署

部署 dapp合约,dechat是一个官方示例,注意合约有两个参数,分别是版主的账号和开发者的账号

部署示例:

> chain3 = require('chain3')
> solc = require('solc')
> chain3 = new chain3();
> chain3.setProvider(new chain3.providers.HttpProvider('http://localhost:8545'));
> solfile = 'dechat1.0.5.sol';
> contract = fs.readFileSync(solfile, 'utf8');
> output = solc.compile(contract, 1);
> abi = output.contracts[':DeChat'].interface;
> bin = output.contracts[':DeChat'].bytecode;
> bin += '000000000000000000000000' + '87e369172af1e817ebd8d63bcd9f685a513a6736';  // 版主的账号
> bin += '000000000000000000000000' + '87e369172af1e817ebd8d63bcd9f685a513a6736';  // 开发者的账号
> amount = chain3.toSha(100000000,'mc') // 要与部署DirectExchangeToken合约地址的amount参数一致
> subchainaddr = '0xe9463e215315d6f1e5387a161868d7d0a4db89e8';
> via = '0xf103bc1c054babcecd13e7ac1cf34f029647b08c';
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> chain3.mc.sendTransaction({from: chain3.mc.accounts[0], value:0, to: subchainaddr, gas:0, shardingFlag: "0x1", data: '0x' + bin, nonce: 0, via: via, });
验证:
合约部署成功后,Nonce值应该是1

dapp 充值

调用 subchainbase 的 buyMintToken方法充值, 用户账号为发出sendTransaction的账号 数量为sendTransaction的amount参数
buyMintToken方法首先调用 DirectExchangeToke对象 的 buyMintToken 方法 按交易记录的amount 扣除DirectExchangeToke合约本身的token数量,增加用户账号对应token的数量
然后调用 transfer 给DirectExchangeToken合约地址 转 对应的moac
再调用 DirectExchangeToke对象 的 requestEnterMicrochain方法 (传subchainbase地址和对应token数量),减去用户账号地址的对应token数量, 增加DirectExchangeToke合约本身的token总量
最后将用户账号和对应token数量,加入推送结构体发至子链,等待一轮flush后,充值会进入到子链

调用示例:

根据ABI chain3.sha3("buyMintToken()") = 0x6bbded701cd78dee9626653dc2b2e76d3163cc5a6f81ac3b8e69da6a057824cb
        取前4个字节 0x6bbded70
> amount = 100;
> subchainaddr = '0xe9463e215315d6f1e5387a161868d7d0a4db89e8';
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> chain3.mc.sendTransaction( { from: chain3.mc.accounts[0], value: chain3.toSha(amount,'mc'), to: subchainaddr, gas: "2000000", gasPrice: chain3.mc.gasPrice, data: '0x6bbded70'});
验证:
检查账号的moac是否减少: > chain3.mc.getBalance(chain3.mc.accounts[0])
检查子链的token是否增加: 调用monitor的方法 ScsRPCMethod.GetBalance 获得子链token

dapp 提币

调用 dapp合约 的 redeemFromMicroChain方法, 用户账号为发出sendTransaction的账号 数量为sendTransaction的amount参数
redeemFromMicroChain方法将用户账号和对应token数量加入推送结构体redeem,等待一轮flush后,自动会调用子链合约的redeemFromMicroChain方法
然后子链合约自动调用DirectExchangeToke对象的 redeemFromMicroChain 方法,扣除DirectExchangeToke合约本身的token数量,增加用户账号对应token的数量
最后自动调用subchainbase的sellMintToken 方法,自动调用DirectExchangeToke对象的 sellMintToken 兑换moac

调用示例:

根据ABI chain3.sha3("redeemFromMicroChain()") = 0x89739c5bf1ef36273bf0e7aeb59ffe71213a58e1f01965e75662cb21b03abb13
取前4个字节 0x89739c5b
> nonce = 1       // 调用ScsRPCMethod.GetNonce获得
> subchainaddr = '0x1195cd9769692a69220312e95192e0dcb6a4ec09';
> via = '0xf103bc1c054babcecd13e7ac1cf34f029647b08c';
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> chain3.mc.sendTransaction( { nonce: nonce, from: chain3.mc.accounts[0], value:0, to: subchainaddr, gas:0, shardingFlag:'0x1', data: '0x89739c5b', via: via,});
验证:
检查账号的moac是否增加: > chain3.mc.getBalance(chain3.mc.accounts[0])
检查子链的token是否减少: 调用monitor的方法 ScsRPCMethod.GetBalance 获得子链token

母链ERC20和子链原生币交互

这是非常通用的一种货币兑换。使用者可以使用预先已经部署好的ERC20,或者当场部署一个主链ERC20,和子链的原生币进行兑换。

子链部署准备

参考子链部署章节,完成部署子链的准备工作。

子链操作账号: 0x87e369172af1e817ebd8d63bcd9f685a513a6736
vnode矿池合约地址: 0x22f141dcc59850707708bc90e256318a5fe0b928
vnode代理地址: 0xf103bc1c054babcecd13e7ac1cf34f029647b08c    192.168.10.209:50062
子链矿池合约地址: 0xe42f4f566aedc3b6dd61ea4f70cc78d396130fac
scs0:   0xd81043d85c9c959d2925958c54c1a49c7bfd1fc8  确保启动并加入子链矿池
scs1:   0xe767059d768fcef12e527fab63fda68cc13e24b3  确保启动并加入子链矿池
scs monitor:    0x0964e5d73d6a40f2fc707aa3e1361028a34923f0

erc20 部署

默认一个标准的erc20合约,通过allowance,transferFrom,balanceOf,transfer等标准的方法支持货币的转移。

参考官方示例的erc20合约erc20.sol,默认decimals为6,totalSupply为100000000乘以10的6次方。 调用示例:

> chain3 = require('chain3')
> solc = require('solc')
> chain3 = new chain3();
> chain3.setProvider(new chain3.providers.HttpProvider('http://localhost:8545'));
> solfile = 'erc20.sol';
> contract = fs.readFileSync(solfile, 'utf8');
> output = solc.compile(contract, 1);
> abi = output.contracts[':TestCoin'].interface;
> bin = output.contracts[':TestCoin'].bytecode;
> erc20Contract = chain3.mc.contract(JSON.parse(abi));
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> dtoken = erc20Contract.new( { from: chain3.mc.accounts[0],  data: '0x' + bin,  gas:'9000000'} , function (e, contract){console.log('Contract address: ' + contract.address + ' transactionHash: ' + contract.transactionHash); });

部署完毕后, 获得erc20合约地址 0x5042086887a86151945d2c2bb60628addf49d48c

验证: 调用合约balanceOf方法查询部署者的余额,应该是10的14次方

> contractInstance = erc20Contract.at(‘0x5042086887a86151945d2c2bb60628addf49d48c’) > contractInstance.balanceOf.call(‘0x87e369172af1e817ebd8d63bcd9f685a513a6736’)

subchainbase 部署

注意这个子链合约在官方基础上进行了修改,增加了erc20合约地址和兑换比例的参数 部署SubChainBase.sol示例:

> chain3 = require('chain3')
> solc = require('solc')
> chain3 = new chain3();
> chain3.setProvider(new chain3.providers.HttpProvider('http://localhost:8545'));
> input = {'': fs.readFileSync('SubChainBase.sol', 'utf8'), 'SubChainProtocolBase.sol':fs.readFileSync('SubChainProtocolBase.sol', 'utf8')};
> output = solc.compile({sources: input}, 1);
> abi = output.contracts[':SubChainBase'].interface;
> bin = output.contracts[':SubChainBase'].bytecode;
> proto = '0xe42f4f566aedc3b6dd61ea4f70cc78d396130fac' ;    // 子链矿池合约
> vnodeProtocolBaseAddr = '0x22f141dcc59850707708bc90e256318a5fe0b928' ;       // Vnode矿池合约
> ercAddr = '0x5042086887a86151945d2c2bb60628addf49d48c';     // erc20合约地址
> ercRate = 100;    // 兑换比率
> min = 1 ;                     // 子链需要SCS的最小数量
> max = 10 ;            // 子链需要SCS的最大数量
> thousandth = 1 ;                      // 千分之几
> flushRound = 40 ;             // 子链刷新周期  单位是主链block生成对应数量的时间(10秒一个block)
> SubChainBaseContract = chain3.mc.contract(JSON.parse(abi));
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> SubChainBase = SubChainBaseContract.new( proto, vnodeProtocolBaseAddr, ercAddr, ercRate, min, max, thousandth, flushRound,{ from: chain3.mc.accounts[0],  data: '0x' + bin,  gas:'9000000'} , function (e, contract){console.log('Contract address: ' + contract.address + ' transactionHash: ' + contract.transactionHash); });

部署完毕后, 获得子链合约地址 0xb877bf4e4cc94fd9168313e00047b77217760930

验证:

访问子链合约的 BALANCE 为 ERC20的 totalsupply

> subchainaddr = '0xb877bf4e4cc94fd9168313e00047b77217760930';
> chain3.mc.getStorageAt(subchainaddr,0x30)   // 注意BALANCE变量在合约中变量定义的位置(16进制) 1ed09bead87c0378d8e6400000000
应该是10的34次方  (14 + 2 + 18)

子链注册scs

子链开放注册

调用合约里的函数addFund

根据ABI chain3.sha3("addFund()") = 0xa2f09dfa891d1ba530cdf00c7c12ddd9f6e625e5368fff9cdf23c9dc0ad433b1
        取前4个字节 0xa2f09dfa
> amount = 20;
> subchainaddr = '0xb877bf4e4cc94fd9168313e00047b77217760930';
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> chain3.mc.sendTransaction( { from: chain3.mc.accounts[0], value:chain3.toSha(amount,'mc'), to: subchainaddr, gas: "2000000", gasPrice: chain3.mc.gasPrice, data: '0xa2f09dfa'});

可以通过查询余额进行验证

> chain3.mc.getBalance('0xb877bf4e4cc94fd9168313e00047b77217760930')

然后调用 调用合约里的函数registerOpen 开放注册 (按子链矿池合约中SCS注册先后排序进行选取)

根据ABI chain3.sha3("registerOpen()") = 0x5defc56ce78f178d760a165a5528a8e8974797e616a493970df1c0918c13a175
        取前4个字节 0x5defc56c
> subchainaddr = '0xb877bf4e4cc94fd9168313e00047b77217760930';
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> chain3.mc.sendTransaction( { from: chain3.mc.accounts[0], value:0, to: subchainaddr, gas: "2000000", gasPrice: chain3.mc.gasPrice, data: '0x5defc56c'});

验证:

访问子链合约的 nodeCount

> chain3.mc.getStorageAt(subchainaddr,0x0e)  // 注意nodeCount变量在合约中变量定义的位置(16进制)

等到两个scs都注册完毕后,即注册SCS数目大于等于子链要求的最小数目时,调用子链合约里的函数 registerClose关闭注册

根据ABI chain3.sha3("registerClose()") = 0x69f3576fc10c82561bd84b0045ee48d80d59a866174f2513fdef43d65702bf70
        取前4个字节 0x69f3576f
> subchainaddr = '0xb877bf4e4cc94fd9168313e00047b77217760930';
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> chain3.mc.sendTransaction( { from: chain3.mc.accounts[0], value:0, to: subchainaddr, gas: "2000000", gasPrice: chain3.mc.gasPrice, data: '0x69f3576f'});

验证:

访问子链合约的 registerFlag 为 0

> chain3.mc.getStorageAt(subchainaddr,0x14)     // 注意registerFlag变量在合约中变量定义的位置(16进制)

同时观察scs的concole界面,scs开始出块即成功完成部署子链。

dapp合约部署

部署 dapp合约,dechat是一个官方示例,注意合约有两个参数,分别是版主的账号和开发者的账号

部署示例:

> chain3 = require('chain3')
> solc = require('solc')
> chain3 = new chain3();
> chain3.setProvider(new chain3.providers.HttpProvider('http://localhost:8545'));
> solfile = 'dechat1.0.5.sol';
> contract = fs.readFileSync(solfile, 'utf8');
> output = solc.compile(contract, 1);
> abi = output.contracts[':DeChat'].interface;
> bin = output.contracts[':DeChat'].bytecode;
> bin += '000000000000000000000000' + '87e369172af1e817ebd8d63bcd9f685a513a6736';  // 版主的账号
> bin += '000000000000000000000000' + '87e369172af1e817ebd8d63bcd9f685a513a6736';  // 开发者的账号
> amount = chain3.toSha(100000000,'mc') // dapp合约地址的token数量,与erc20的amount一致
> subchainaddr = '0xb877bf4e4cc94fd9168313e00047b77217760930';
> via = '0xf103bc1c054babcecd13e7ac1cf34f029647b08c';
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> chain3.mc.sendTransaction({from: chain3.mc.accounts[0], value:amount, to: subchainaddr, gas:0, shardingFlag: "0x1", data: '0x' + bin, nonce: 0, via: via, });
验证:
在 scs 的_logs目录下搜索日志文件,查找”created contract address”,找到dapp合约地址:6ab296062d8a147297851719682fb5ffe081f1d3
调用monitor的方法 ScsRPCMethod.GetBalance 查询对应dapp合约地址的余额,应该等于erc20总量。

dapp 充值

调用 subchainbase 的 buyMintToken方法充值, 用户账号为发出sendTransaction的账号 ,参数分别为子链合约地址和token数量。
buyMintToken方法首先调用erc20合约的allowance检查授权,并调用transferFrom方法将token从用户账号地址转到合约地址
然后自动调用requestEnterMicrochain方法,将用户账号和对应token数量,加入推送结构体发至子链,等待一轮flush后,充值会进入到子链

调用示例:

> subchainaddr = '0xb877bf4e4cc94fd9168313e00047b77217760930';
> data = contractInstance.buyMintToken.getData(subchainaddr, 100)
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> chain3.mc.sendTransaction( { from: chain3.mc.accounts[0], value: 0, to: subchainaddr, gas: "2000000", gasPrice: chain3.mc.gasPrice, data: data});
验证:
检查账号的erc20 token是否减少: 调用erc20合约的balanceOf方法
检查子链的token是否增加: 调用monitor的方法 ScsRPCMethod.GetBalance 获得子链token

dapp 提币

调用 dapp合约 的 redeemFromMicroChain方法, 用户账号为发出sendTransaction的账号 数量为sendTransaction的amount参数
redeemFromMicroChain方法将用户账号和对应token数量加入推送结构体redeem,等待一轮flush后,自动会调用子链合约的redeemFromMicroChain方法
调用erc20合约的transfer给用户账号转对应的token数量

调用示例:

根据ABI chain3.sha3("redeemFromMicroChain()") = 0x89739c5bf1ef36273bf0e7aeb59ffe71213a58e1f01965e75662cb21b03abb13
取前4个字节 0x89739c5b
> nonce = 1       // 调用ScsRPCMethod.GetNonce获得
> subchainaddr = '0xb877bf4e4cc94fd9168313e00047b77217760930';
> via = '0xf103bc1c054babcecd13e7ac1cf34f029647b08c';
> chain3.personal.unlockAccount(chain3.mc.accounts[0], '123456');
> chain3.mc.sendTransaction( { nonce: nonce, from: chain3.mc.accounts[0], value:0, to: subchainaddr, gas:0, shardingFlag:'0x1', data: '0x89739c5b', via: via,});
验证:
检查账号的erc20 token是否增加: 调用erc20合约的balanceOf方法
检查子链的token是否减少: 调用monitor的方法 ScsRPCMethod.GetBalance 获得子链token

ATO方式

TODO