Download and install
https://github.com/USTB-InternetSecurityLab/Shellchain
项目代码量17余万行,基于multichain开发,最近的分支: springboot-contract
VSCode配置config.json举例:
1 | { |
Experiment 1: 单机实验
start
创建一条新链,这里的链名为chain1:
1 | wsd@ubuntu_1:~$ shellchain-util create chain1 |
查看链初始化数据,启动链
1 | wsd@ubuntu_1:~$ cat ~/.shellchain/chain1/params.dat |
关闭链
Raw transactions: 原始交易
查看有授权资格的账户并创建一笔用于转账的资产:
1 | listpermissions issue |
(If multiple addresses are displayed, use the getaddresses true command to see which addresses belong to the local wallet.)
返回资产创建的交易id
创建新的地址用于转账:
1 | getnewaddress |
给予新地址交易权限:
1 | grant 1CQ9gPjQLUnebtZh73hZv7uNDx1ihtQRUxVt5L receive,send |
返回交易授权地址
接下来我们看一看交易格式:gettransaction c20cc3707c00402ba1365a49c373483b5ae55e42055e7717d65d61bfc77243df
1 | { |
1 | ./src/multichain-cli chain1 decoderawtransaction 0100000001013e4affbd34ee58e69c34f910ba77f9bf363577fb93bbe1a2189ed656ce2640020000006a4730440220095458a6032aa1d445500b3a1321f4e4ee58c00d267b89b1c2b3d0bb427b36cc02202b4f3020dd094055a83cf859ebe0621c2c64f2ac015cf16ea331de55e168d7a7012103be0316aba83fd1a42cee503b706072c1d0db81990d3ad5589ad56eeb407d489affffffff0200000000000000002f76a9145457615fc1c97532c1927ad664d49f373c6d6e6d88ac1473706b700600000000000000ffffffff05265a5f7500000000000000001976a9142236a285f892a2049c22dacae972fb0ecb4e2a7688ac00000000027b7d00000000 |
1 | { |
./src/multichain-cli chain1 getwallettransaction 20cc3707c00402ba1365a49c373483b5ae55e42055e7717d65d61bfc77243df
1 | { |
列出该地址下 0 到 9999 范围内未被使用的资产(UTXO):
1 | listunspent 0 9999 '["15dCVacyUcLD22yX3EeoYAecfwqdRZhfedTPT7"]' |
获取资产所在的交易id以及vout序号,通过 gettxout
命令再次检查:
现在可以通过命令行创建一个转账20单位asset1的原始交易:
1 | createrawtransaction '[{"txid":"4026ce56d69e18a2e1bb93fb773536bff977ba10f9349ce658ee34bdff4a3e01","vout":0}]' '{"1CQ9gPjQLUnebtZh73hZv7uNDx1ihtQRUxVt5L":{"asset1":20}}' |
可以使用 decoderawtransaction
对得到的hex串进行解析。
由于每个交易的txin段和txout段要求的交易数量总数应该相等,在转出一笔20单位的out之后还需要补充一笔80单位的out字段:
1 | appendrawchange [hex-blob] 15dCVacyUcLD22yX3EeoYAecfwqdRZhfedTPT7 |
执行成功后得到一串更长的hex,解析此串可以得到完整的txin、txout字段。可以继续添加一些16进制信息(如 abcd1234 )作为data字段:
1 | appendrawdata [paste-longer-hex-blob] abcd1234 |
Round-robin consensus
Definition for Round-robin consensus: Multiple nodes play a key role in validating and voting for transactions, doesn’t depend on a single participant for block validation.
In this secion we’ll start collaborative block validation between the nodes. Note that, in the case of a permissioned Multichain blockchain, consensus is based on block signature and a customizable round-robin consensus scheme, rather than proof-of-work as in bitcoin.
1 | wsd@ubuntu_1:~$ multichain-cli chain1 grant 1R6jUNxTqx6bJetkv5ZkLXLub1nsNwC95gurf6 mine |
(Even though the permission is called mine note that there is no real “mining” taking place, in the sense of proof-of-work.)
1 | wsd@ubuntu_2:~$ multichain-cli chain1 listpermissions mine |
1 | wsd@ubuntu_1:~$ multichain-cli chain1 setruntimeparam miningturnover 1 |
Now wait for a couple of minutes, so that a few blocks are added. (This assumes you left the block time on the default of 15 seconds.) Check the creators of the last few blocks:
1 | wsd@ubuntu_1:~$ multichain-cli chain1 listblocks -10 |
The address of the validator of each block is in the miner field of each element of the response.
Experiment 2: 双机实验
1 | wsd@ubuntu_2:~$ multichaind chain1@192.168.122.168:2911 |
1 | wsd@ubuntu_1:~$ multichain-cli chain1 grant 1R6jUNxTqx6bJetkv5ZkLXLub1nsNwC95gurf6 connect |
In case that the interactive mode of multichain-cli doesn’t support backspace, you can wrap the cli command with multichain-cli chain1
.
Streams: 流
流可以用来存储和传递一般信息
1 | create stream stream1 true |
发布一个内容项上去,口令为 key1 (注意内容格式必须为16进制串,不然会报错:-8,Item data should be hexadecimal string)
1 | publish stream1 key1 1234abcd |
返回的是交易ID
检查该链上所有的流
1 | liststreams |
至少可以看到stream1和默认的root两个流
订阅stream1:
1 | liststreamitems stream1 |
继续发布一些内容项到流:
1 | publish stream1 key1 1234567890 |
获取流中的内容
1 | liststreamitems stream1 # should show 3 items |
You should see 3 items in the output because the first server has published two strings to key1, and published one string to key2.
1 | wsd@ubuntu_1:~$ multichain-cli chain1 liststreamkeys stream1 # 2 keys |
1 | wsd@ubuntu_1:~$ multichain-cli chain1 liststreamkeyitems stream1 key1 # 2 items with the key |
1 | wsd@ubuntu_1:~$ multichain-cli chain1 liststreampublisheritems stream1 1R6jUNxTqx6bJetkv5ZkLXLub1nsNwC95gurf6 # two items by this publisher |
Assets
1 | wsd@ubuntu_1:~$ multichain-cli chain1 listpermissions issue |
copy the address in the list so that an asset with 1000 units can be created on this node.
1 | wsd@ubuntu_1:~$ multichain-cli chain1 issue 1LWwPw3x9ocBkqTLkFGdqJqK3y6S8T3aLc7ZMg asset1 1000 0.01 |
On both servers, you can verify asset1.
1 | wsd@ubuntu_1:~$ multichain-cli chain1 gettotalbalances |
If the addresses are not enough( <= 2), run getnewaddress
to create new address for receiving assets:
(If multiple addresses are displayed, use the getaddresses true command to see which addresses belong to the local wallet.)
Now we’re going to build a new transaction which sends some of the newly created assets from the current holding addresses to the newly created addresses:
1 | ./src/multichain-cli chain1 listunspent 0 9999 '["15dCVacyUcLD22yX3EeoYAecfwqdRZhfedTPT7"]' |
Now try to send 50 units of the assets from first server’s wallet to second server’s.
1 | wsd@ubuntu_1:~$ multichain-cli chain1 sendasset 1R6jUNxTqx6bJetkv5ZkLXLub1nsNwC95gurf6 asset1 100 |
An error showing ‘Destination address doesn’t have receive permission’ will appear. So it’s time to add receive permissions
1 | wsd@ubuntu_1:~$ multichain-cli chain1 grant 1R6jUNxTqx6bJetkv5ZkLXLub1nsNwC95gurf6 receive |
try sending asset again
1 | wsd@ubuntu_1:~$ multichain-cli chain1 sendasset 1R6jUNxTqx6bJetkv5ZkLXLub1nsNwC95gurf6 asset1 100 |
Now check the asset balances on each server, including transactions with zero confirmations.
You can also view the transaction on each node and see how it affected their balances:
1 | wsd@ubuntu_1:~$ multichain-cli chain1 listwallettransactions 1 |
Issuing assets
发布资产
Combining assets and streams: 流与资产的结合
本部分将创建一笔125单位的转账交易,并写入附带信息到流中。
1 | wsd@ubuntu_1:~$ multichain-cli chain1 sendwithdata 1R6jUNxTqx6bJetkv5ZkLXLub1nsNwC95gurf6 '{"asset1":125}' '{"for":"stream1","key":"transfer","data":"486921"}' |
Now this transaction can be examined on the second server as below:
1 | wsd@ubuntu_2:~$ multichain-cli chain1 getwallettransaction 60e98d2eb2133e1757af056e99207255a00f0d4845e6451e319774dafe20b560 |
In the output from this command, you should see the balance field showing the incoming “qty” is 125 units of asset1 and the items field containing the stream item that was added. You can also view this item directly within the stream:
1 | wsd@ubuntu_1:~$ multichain-cli chain1 liststreamitems stream1 |
源码阅读
发布交易的函数是 SendMoneyToSeveralAddresses(addresses, nAmount,wtx, lpScript, CScript(),addresses,txInfo.formatTx(),txInfo.ntype); 几个参数的解释分别为,接收方地址列表addresses,转账数额nAmount,接收交易指针wtx,脚本解释器指针lpScript,新建脚本解释器对象CScript(),发送方地址列表addresses,传入的交易信息txInfo.formatTx(),传入的交易类型txInfo.ntype
获取交易ID: string txid= wtx.GetHash().GetHex();
获取userResult:txInfo.userResult.c_str()
//获取使用方地址,并判断该地址是否存在以及拥有接收权力
vector<CTxDestination> addresses;
contractInformation txInfo = contractInformation();
//交易的部分参数初始化
CAmount nAmount=0;
CWalletTx wtx;
mc_Script *lpScript=mc_gState->m_TmpBuffers->m_RpcScript3;
lpScript->Clear();
更新multichain源仓库代码
shellchain.conf文件名的产生写在 chainparams/buildgenesis.cpp 里的 mc_GenerateConfFiles 函数中
rpc/rpchelp.cpp 里添加帮助文档
rpc/rpclist.cpp 里添加了cli命令和函数名的对应
rpc/rpcrawtransaction.cpp TxToJSON函数添加自定义交易字段到json的解析,在decoderawtransaction函数中会用到
rpc/rpcserver.h 中添加对于自定义交易函数的声明
rpc/rpcwalletutils.cpp SendMoneyToSeveralAddresses 中添加对 opcustom 的脚本解释器检查:
1 | std:vector<CScript> scriptOpReturns; |
rpc/rpcwalletsend.cpp 添加主体客体注册rpc函数和发送自定义交易函数
rpc/rpcclient.cpp vRPCConvertParams 中添加需要字符串转json的参数index,方法的第一个参数index为0
script/script.cpp GetOpName中和script.h opcodetype中 加入状态码 OP_CUSTOMDATA
script/standard.cpp IsStandardCustomData 加入对自定义脚本的检查
secp256k1/ecdsa_impl.h 中添加对于 256 位椭圆曲线的加解密函数,头文件在 ecdsa.h 中
secp256k1/ecmult_impl.h 中添加椭圆曲线点乘的操作
secp256k1/secp256k1.c 中添加加解密函数
secp256k1/tests.c 添加加解密测试
utils/serialize.h 中添加对于json参数的序列化和反序列化 stream 操作
utils/util.cpp 中添加对于 .shellchain 目录的创建
wallet/wallet_ismine.h 中定义 enum customdatatype
wallet/walletcoins.cpp 中BuildAssetTransaction函数、CreateAssetGroupingTransaction函数添加生成交易是自定义字段的处理
wallet/wallettxs.cpp 第 2051 行 for(i=0;i<(int)tx.vout.size();i++) // Checking that tx has long OP_RETURN,此处可能需要添加对op_custom的支持,避免字段过大对性能的影响
其中CustomTxNotify函数没搞清功能是啥
net/rest.cpp 负责提供rest接口
structs/amount.h CAmount 类型即 int64_t类型
阅读 createrawtransaction 1416-1576
交易的序列化存储与反序列化恢复
序列化采用将对象写入流的方式,在bitcoinconsensus.cpp 中定义了用于序列化的流 TxInputStream
,仅在函数bitcoinconsensus_verify_script 中调用。
1 | /** A class that deserializes a single CTransaction one time. */ |
nTypeIn 取值类型为 enum{SER_NETWORK=1,SER_DISK=2,SER_GETHASH=4,} 中的一种,用于具体实现某种序列化操作
RPC请求接收
首先,server在启动的时候调用了 StartRPCThreads(string& strError)
函数,在该函数中调用
1 | RPCListen(acceptor, *rpc_io_service, *rpc_ssl_context, fUseSSL); |
RPCListen 的函数体:
1 | template <typename Protocol> |
该函数接收泛型Protocol,用在acceptor中,
而调用该函数的accepter声明只有一处,位于 rpcserver.cpp
的 StartRPCThreads
函数中,声明如下:
1 | boost::shared_ptr<ip::tcp::acceptor> acceptor(new ip::tcp::acceptor(*rpc_io_service)); |
boost::shared_ptr 是一个可以共享所有权的智能指针
ip::tcp::acceptor 重命名于 boost::asio::basic_socket_acceptorboost::asio::ip::tcp
所以泛型Protocol在这里就是boost::asio::ip::tcp
接下来重点解释一下 acceptor->async_accept()函数。
该函数用来异步接收一个rpc请求到socket,并获取远程tcp端口。函数声明为:
1 | template <typename Executor1, BOOST_ASIO_COMPLETION_TOKEN_FOR(void (boost::system::error_code)) AcceptHandler BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)> |
其中第一个参数 peer 就是连接将被接收进的socket,第二个参数 peer_endpoint 指与远程tcp连接对应的端口对象,第三个参数 handler 指接收完成后对应调用的函数对象。
boost::bind 返回值就是一个函数对象,第一个参数是函数声明指针,后面的对应该函数调用参数。RPCAcceptHandler
接收6个参数,前5个参数在这里都确定了,第6个参数 error_code 每次实际调用都不同,这里 _1
表示实际调用时的参数列表的第一个参数,即传递进来的 error_code。
查看 LogPrintf
进入chain的存储目录
1 | tail -f debug.log |
在配置文件中添加自定义配置
读取配置文件的代码写在 utils/utilwrapper.cpp
的 mc_ReadConfigFile
函数中:
1 | // 通过一个for循环从streamConfig的文件流中不断读取key=val的配置 |
配置内容写入 mapSettingsRet
和 mapMultiSettingsRet
DIY:魔改代码
自定义交易格式
主要步骤:
- 定义合约字段 MutableContractInfo,继承于 CObjectInfo
- 添加编译选项,编译测试
- 在 transaction.h 的交易定义中添加 contract_in, contract_out 字段类型
添加字段的序列化与反序列化操作,在rpcutils.h中定义,在rpcutils.cpp中实现 - 在发送交易的链条中添加合约字段和交易类型字段
1.定义合约字段
contracts文件夹中新建 contractinfo.h
1 | #ifndef _CONTRACT_INFO_H |
合约地址储存在BerkeleyDB中,其中记录发布方账户地址fromAddress,每次执行合约的交易号及它们的索引,合约名称、SPESC代码、Java程序代码
在 contractinfo.cc 中编写声明的函数实现
2. 添加编译选项
在 src/Makefile.am 中的 libbitcoin_wallet_a_SOURCES
选项中添加 contracts/contractinfo.cpp
, contracts/contractinfo.h
通过 autogen.sh
, configure CFLAGS="-g -O0" CXXFLAGS="-g -O0" JFALGS="-g -O0" FFLAGS="-g -O0" CPPFLAGS="-g -O0" --enable-debug
, make
三步进行测试
3. 添加交易自定义字段
在 CTransaction 和 CMutableTransaction 下添加定义
由于根据公钥是可以获得地址的,所以out端记录合约公钥
4. 修改发送交易的调用函数参数
SendMoneyToSeveralAddress 广播交易 > walletcoins.cpp 的 BuildAssetTransaction 函数中创建交易字段 vin_contract 和 vout_contract
5. BuildAssetTransaction
通过获取json串中的 contract_in 和 contract_out 字段,在临时交易txNew中添加ContractIn和ContractOut
交易大概长下面这样:
1 | { |
矿工接收请求后
- 接收到缓冲池MemoryPool中,重写 AcceptToMemoryPool 函数,添加
1
2
3
4
5
6
7
8
9
10#ifdef __ENABLE_CONTRACT
if(tx.nType > 99 && tx.nType < 200) {
if(!AcceptContract(tx,reason))
{
return state.DoS(0,
error("AcceptToMemoryPool: : AcceptContract failed %s : %s", hash.ToString(),reason),
REJECT_NONSTANDARD, reason);
}
}
#endif
在 protocol/multichaintx.cpp 中添加 AcceptContract 函数的实现
1 | bool AcceptContract(const CTransaction& tx, string& reason) |
- 拓展交易检查函数
CheckInputs
6. 拓展交易检查函数
main.cpp 下的 CheckInputs
函数
7. 编写合约数据库
合约数据库以contractAddress作为key,
存储了发布方address,每次执行的执行方各交易号,SPESC合约信息,Java程序代码。
分布式环境下,合约代码可利用multichain stream功能传递。
合约数据库使用BerkeleyDB,是可以和Java端调用的BerkeleyDB结合的(非JE版本可互通,JE版本仅用于Java端),
8. 编写 rpc 接口函数支持
编写 rpccontract.cc
9. 添加对于 rawtransaction 的解析支持
rpcrawtransaction.cpp 中的 TxToJSON 函数中添加对于自定义交易的转json串功能
1 |
再运行一次命令
./multichain-cli shellchain executecontract 1Ln9Tp1FCnKwSHw75BYBs74fyvp4LoaxzcHiqP addT 10
1 | { |
添加自定义接口
主要步骤:
比如现在我现在添加4个有关合约的自定义接口:sendcontract
, sendcontractfrom
, getcontract
, getcontractfrom
rpcserver.h 里声明
1 | extern json_spirit::Value sendcontract(const json_spirit::Array& params, bool fHelp); |
rpclist.cpp 里定义map的cli命令
如果需要json转移,则在 rpcclient.cpp 中的 vRPCConvertParams 中定义
接口测试
1 | sendcontract '[{"txid":"4026ce56d69e18a2e1bb93fb773536bff977ba10f9349ce658ee34bdff4a3e01","vout":0}]' '{"1CQ9gPjQLUnebtZh73hZv7uNDx1ihtQRUxVt5L":{"asset1":20}}' '{"contractid":"abcd1234", "action":{"name":"publishservice","args":[1]}, "state":{"data":"somedata"}}' |
根据公钥 pkey 获取地址:
1 | info.fromAddress = CBitcoinAddress(pkey.GetID()).ToString(); |
根据地址获取公钥脚本:
GetScriptForDestination
根据 scriptPubKey 获取 pubkey
CMutableTransaction 转 CWalletTx
walletcoins.cpp 1808行: static_cast<CTransaction>(&wtxNew) = CTransaction(txNew);
walletcoins.cpp 2743行 CreateAndCommitOptimizeTransaction 可以利用一下
测试createcontract
1 | wsd@ubuntu-16-LTS-Server:~/JoToChain$ ./src/multichain-cli contractchain createcontract contract_name ~/contract_path |
Commit 过程中报错:Error: The transaction was rejected: 16: ConnectInputs failed: wrong-contract-deploy-output-information
报错: CommitTransaction() : Error: Transaction not valid: 16: ConnectInputs failed: contract-deploy-input: address or actionName is null。考虑原因可能是CreateTransaction的过程中没有加入contractInfo
walletcoins.cpp 1564行:txNew.info.add(Others); 存疑。是否有存在的必要?
multichaintx.cpp 里的 AcceptContract 函数用于 main.cpp 中,作用是再次检查合约格式,并加入到合约数据库内存池中。
1 | ./src/multichain-cli contractchain gettransaction 91c743e88e50be7b36843a108b2316af9d549a9fb7c94fa320d5bd3e5d28dfa2 |
解析hex
1 | ./src/multichain-cli contractchain decoderawtransaction 010000000172e4398b34b340c2d369c53bab6bd81bb3b2f2312ce5ffdfdab510216296f0b6000000006a473044022014153ed4e56ca6d2eeed9a58357fca9f35b3e39b70a949b771b7b8f752fdbab202206c51bf6af4120f1c32430bd10fe17b034d5f7fc7eabb66bcf4b39a6ed5d143530121027f51366ead7860f7a76ec288dcefa2c1af3b1e649f548a2d3999e73da1f5f6c0ffffffff737b2261646472657373223a2231447048653337346e706f37656f74714d7063575433445257376d3347647435373751724d5a222c2274786964223a22222c22616374696f6e4e616d65223a225f696e6974222c22616374696f6e506172616d73223a22222c227061727479536967223a22227d0200000000000000001976a9145ed1c347df506af7d9cb66877bc2b2f58789909288ac027b7d00000000000000001976a914a06beee2be7d64b78fe0af0fa3e22ac64a85ec3888ac667b22636f6e747261637444617461223a22222c2270617274795075626b6579223a22303262393564633664623037333363336264373562653164353938343730626439666463336330303465333938316539323435316138353535313163343964326166227d0000000064000000 |
测试 callcontract
1 | callcontract '{"address":"1XK3LDVyRStHaXThmJP1awHsrm2u8ovS8DXMbe", "txid":"78a50477408c82831c941e576fd41b55e7e7a73e7388375e6d576004ea12f98d", "partySig":"sign_partyA", "actionName":"publishservice", "actionParams":"service A, 1"}' '{"contractData":"abcdef1234567890", "partyPubkey":"partyA"}' '[{"txid":"4026ce56d69e18a2e1bb93fb773536bff977ba10f9349ce658ee34bdff4a3e01","vout":0}]' '{"1CQ9gPjQLUnebtZh73hZv7uNDx1ihtQRUxVt5L":{"asset1":20}}' |
1 | callcontract '{"address":"1DpHe374npo7eotqMpcWT3DRW7m3Gdt577QrMZ", "txid":"91c743e88e50be7b36843a108b2316af9d549a9fb7c94fa320d5bd3e5d28dfa2", "partySig":"sign_partyA", "actionName":"publishservice", "actionParams":"service A, 1"}' '{"contractData":"abcdef1234567890", "partyPubkey":"partyA"}' |
报错:Insufficient funds
Coin selection: No unspent outputs are available. Please send a transaction, with zero amount, to this node or address first and wait for its confirmation.
报错原因在 wallet/wallet.cpp SelectMultiChainCoins函数中:
1 | int preferred_row=in_preferred_row; |
该循环进行了两次attemp都没有返回true,最终返回了false
函数调用栈:
wallet/wallet.cpp 3124
wallet/walletcoins.cpp 1370
wallet/walletcoins.cpp 2187
wallet/walletcoins.cpp 2412
wallet/wallet.cpp 2691
rpc/rpcsmartcontract.cpp 376
原因:传入的 scriptPubkeys 为空?callcontractfrom
函数中加入合约公钥 scriptPubKeys.push_back(GetScriptForDestination(CBitcoinAddress(contractInfo.address).Get()));
wallet.cpp 2841行中会对in、out字段顺序进行洗牌算法,所以检测合约字段还应该使用 size()>0 的方式,用 index=size()-1 不靠谱。
setFromAddresses 里没有填充值
测试成功结果:
1 | { |
验证结点的合约可通过stream订阅通知
系列测试
1
1 | ./src/multichain-cli contractchain createcontract test_name1 test_path1 |
2
创建合约
1
2
3
4
5
6./src/multichain-cli contractchain createcontract test_name2 test_path2
{
"address" : "144Z8njQaUejZTviipyNaP5MirxVgYidW73xmK",
"txid" : "b13c36a69a49d413978e1a76ceaae77a40c2b909751b3877cb27b6151346990a"
}初始化合约
1
2
3./src/multichain-cli contractchain callcontract '{"address":"144Z8njQaUejZTviipyNaP5MirxVgYidW73xmK", "txid":"b13c36a69a49d413978e1a76ceaae77a40c2b909751b3877cb27b6151346990a", "partySig":"sign_partyA", "actionName":"publishservice", "actionParams":"service A, 1"}' '{"contractData":"abcdef1234567890", "partyPubkey":"partyA"}'
e14b40448c7961509f5a00abe366a804ba02e7b704d67d1d13f98a4aad61ea27创建资产
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15./src/multichain-cli contractchain listpermissions issue
[
{
"address" : "1NgYPRT6AQjSgRsiShStsiWsbfPQ15sugh3bXS",
"for" : null,
"type" : "issue",
"startblock" : 0,
"endblock" : 4294967295
}
]
./src/multichain-cli contractchain issue 1NgYPRT6AQjSgRsiShStsiWsbfPQ15sugh3bXS money2 100 0.01
1298afdfd8146c2e95cbbbc524f7d69e4540a8ccc27079ab08a61c53ee924d93合约转移资产
1
2
3
4
5
6
7./src/multichain-cli contractchain grant 144Z8njQaUejZTviipyNaP5MirxVgYidW73xmK receive,send
8a7c066b680b2620671605f6de5e9560c050c65c2fdfeb21fa9eda513c23d71c
./src/multichain-cli contractchain callcontract '{"address":"144Z8njQaUejZTviipyNaP5MirxVgYidW73xmK", "txid":"e14b40448c7961509f5a00abe366a804ba02e7b704d67d1d13f98a4aad61ea27", "partySig":"sign_partyA", "actionName":"payContract", "actionParams":"30"}' '{"contractData":"1234567890abcdef", "partyPubkey":"partyA"}' '[{"txid":"1298afdfd8146c2e95cbbbc524f7d69e4540a8ccc27079ab08a61c53ee924d93","vout":0}]' '{"144Z8njQaUejZTviipyNaP5MirxVgYidW73xmK":{"money2":30}}'
df6a461259f37ffff4d9625bae73c06d393aea11a738b6cfe02a796b81f2abd7
评论
shortname
for Disqus. Please set it in_config.yml
.