Delivery层初始化配置 首先需要和bttc层一样准备一个genesis配置，如果是同步现有测试网或者主网，可以官方仓库拿到 主网：https://github.com/bttcprotocol/launch/blob/master/mainnet-v1/without-sentry/delivery/config/genesis.json 测试网：https://github.com/bttcprotocol/launch/blob/master/testnet-1029/without-sentry/delivery/config/genesis.json 如果是启动私链也可以自己创建，deliveryd程序也提供了初始化测试链的功能，比如需要创建一个私链，3个验证者2个同步节点 ./deliveryd create-testnet --v 3 --n 2 --output-dir ./output --chain-id delivery-9528 执行完成后，会自动创建5个节点对应的配置文件，每份主要的配置如下 genesis.json 初始化链配置，主要关心以下字段 chain_id 链id app_state->accounts 初始化账户资金等信息 bor->spans->validator_set->validators 初始化验证者信息 bor->spans->validator_set->proposer 初始化提案者信息 bor->spans->selected_producers 初始化当前生产者信息 bor->spans->bor_chain_id bttc层链id chainmanager->chain_params 链合约相关地址 checkpoint 配置相关参数 gov 治理相关参数 slashing 惩罚相关参数 staking 初始化相关地址抵押量 对于私链，在初始化数据基础上，主要关注chain_id和chain_params，其余地址相应数据使用默认即可 config.toml 链基础配置，主要关心参数如下 fast_sync：是否开始快速同步 db_dir: 数据的存放位置 genesis_file：genesis.json存放位置 priv_validator_key_file：验证者私钥文件位置 priv_validator_state_file：验证者状态文件位置 node_key_file：节点密钥存放位置 persistent_peers：持久链接的peer地址，类同于eth的staticnode private_peer_ids: 私有的节点id, 用于隐私接入，比如哨兵节点 登录验证人节点. 运行 deliveryd tendermint show-node-id. 示例: private_peer_ids = "e2c6a611e449b61f2266f0054a315fad6ce607ba" delivery-config.toml RPC和 REST配置，主要关心参数如下 eth_rpc_url：eth链RPC地址 bsc_rpc_url：bsc链RPC地址 bttc_rpc_url：bttc层RPC地址 tron_rpc_url：tron链RPC地址 测试网：47.252.19.181:50051 tron_grid_url：tron链grid地址 测试网：https://test-tronevent.bt.io amqp_url：AMQP地址，在bridge过程中会用于任务的消息队列 node_key.json 节点的密钥信息，可以使用工具自动生成 priv_validator_key.json 节点验证者的密钥信息，可以使用工具自动生成 对于单节点测试，也可以直接执行初始化 ./deliveryd init --chain-id delivery-9528 --home ./
侧链 优点 实现上相对简单易用 缺点 但需要在信任侧链的基础之上，如果侧链失败（意味着共识机制受到损害），您可能会损失所有资金。 相对解决 通常是只跨小额临时使用的代币，将风险人为控制降低。 plasma 优点 plasma在设计上比侧链更安全。（用户可以使用区块根来表明他们在 Plasma 链上收到了资金，如果 Plasma 链共识机制停止创建区块，用户可以使用区块根向以太坊提出索赔） 缺点 不能真正进行与侧链相同的复杂操作。不做信任假设来保证资金安全，所以我们总是不得不假设 Plasma 链共识机制随时可能失败，需要围绕它进行设计。这对 Plasma 链上可能发生的事情增加了额外的限制。主要缺点是你不能真正进行与侧链相同的复杂操作 参考 https://docs.plasma.group/en/latest/src/plasma/sidechains.html
由于在调研Algorand共识，所以最近阅读和测试相关的链实现，此篇将主要讲解kaleido的私链部署测试。 由于相关项目基本已经很久不更新，文档社区匮乏，只能阅读代码，反向推出运行参数和步骤。 编译源代码 参考《kaleido 编译与测试》，执行编译相关子程序 make all 最终kaleido\build\bin会生成genesis程序，下面会用到 genesis.json创建 启动私链，首先需要准备bios节点配置 阅读相关代码（github） 可以使用genesis程序进行生成，首次直接运行genesis后 Hint: You must make a minerkey before making genesis. 提示需要先生成minerkey，继续跟进 阅读相关代码（github） makeMinerKeyCommand = cli.Command{ Action: utils.MigrateFlags(makeMinerKey), Name: "makeminerkey", Usage: "Generates a miner key for mining", ArgsUsage: " ", Flags: []cli.Flag{ utils.DataDirFlag, utils.MinerStakeOwnerFlag, utils.MinerKeyCoinbaseFlag, utils.MinerKeyStartFlag, utils.MinerKeyLifespanFlag, }, Category: "MINER COMMANDS", Description: ` The makeMinerKey command generates a miner key for mining. If the key already exists, just return it. 可以使用kalgo加上参数makeminerkey进行生成，参考有限文档例子如下 mkdir $PWD/data export KALEIDO_HOME=$PWD/data echo $KALEIDO_HOME ./kalgo makeminerkey \ --miner.stakeowner 0x48F155527f25EB1d4cb2aa32b7e84692AA0025C0 \ --minerkey.coinbase 0x48F155527f25EB1d4cb2aa32b7e84692AA0025C0 \ --minerkey.start 1 0x48F155527f25EB1d4cb2aa32b7e84692AA0025C0修改为自己的地址 执行生成后，返回 MinerKey: 0x39fb25e90000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000006400000000000000000000000048f155527f25eb1d4cb2aa32b7e84692aa0025c003ada2cdce38f8fd0aafbaf5542a03a78b90b93ce594e0568760ba952e2b1f534566f9bde28e7b7d6ba8a369e0a4a6b419b12b86cb577c374cd257ef0a5a5645 Details: miner = 0x48F155527f25EB1d4cb2aa32b7e84692AA0025C0 coinbase = 0x48F155527f25EB1d4cb2aa32b7e84692AA0025C0 start = 1 end = 1000000 lifespan = 100 vrfVerifier = 0x03ada2cdce38f8fd0aafbaf5542a03a78b90b93ce594e0568760ba952e2b1f53 voteVerfier = 0x4566f9bde28e7b7d6ba8a369e0a4a6b419b12b86cb577c374cd257ef0a5a5645 GenesisStorage: 0xf542409778bcdead44d9b45f4b07b70b047acb90dcda73c55b6b72088ca2fefd = 0x48f155527f25eb1d4cb2aa32b7e84692aa0025c0000000640000000000000001 0xf542409778bcdead44d9b45f4b07b70b047acb90dcda73c55b6b72088ca2fefe = 0x03ada2cdce38f8fd0aafbaf5542a03a78b90b93ce594e0568760ba952e2b1f53 0xf542409778bcdead44d9b45f4b07b70b047acb90dcda73c55b6b72088ca2feff = 0x4566f9bde28e7b7d6ba8a369e0a4a6b419b12b86cb577c374cd257ef0a5a5645 以上仅为测试数据，对于自己相关私有数据请注意保管 由于cmd/genesis/main.go和consensus/clique/clique.go已写死了0x0e09768B2B2e7aa534243f8bf9AFdC145DdA8EDa为测试数据，所以我们可以修改对应代码，也可以拿测试地址生成minerkey，然后生成genesis.json然后再手动修改对应的地址和数据。 切记替换地址时，（github） contracts.MinerAddress: { Balance: common.Big0, Code: common.FromHex(contracts.MinerBinRuntime), Storage: map[common.Hash]common.Hash{ common.HexToHash("0x06ff3c55f357d4545a14dcc167670bf1dcc8bb45dcd90fa4a085a02a39da3a8a"): common.HexToHash("0x45ec182edc6774c9a2926172f1fd996e59b58ced000000640000000000000001"), common.HexToHash("0x06ff3c55f357d4545a14dcc167670bf1dcc8bb45dcd90fa4a085a02a39da3a8b"): common.HexToHash("0xf88a8d844c217531a38d6019ea671652340fe0d899996250bccce13af99933de"), common.HexToHash("0x06ff3c55f357d4545a14dcc167670bf1dcc8bb45dcd90fa4a085a02a39da3a8c"): common.HexToHash("0x6e8f4a7c7651766722dd7fb9d7a97cd28678a1cefb12631580a7ffe90a910b8f"), }, }, Storage中的数据，要替换成上面执行kalgo makeminerkey返回的数据中的GenesisStorage对应的数据，不然检查不通过（github）会报bad signature 此时按照常规eth启动步骤执行即可，细节不再单独讲解 ./kalgo --datadir ./data init genesis.json ./kalgo --config geth.toml --nodiscover ./kalgo attach ipc:./data/geth.ipc 添加新节点 同上为新节点创建minerkey mkdir $PWD/data export KALEIDO_HOME=$PWD/data echo $KALEIDO_HOME ./kalgo makeminerkey \ --miner.stakeowner 0xbb93FcC2bB90D46255c43DD398A1A372E12bE6Aa \ --minerkey.coinbase 0xbb93FcC2bB90D46255c43DD398A1A372E12bE6Aa \ --minerkey.start 1 返回 MinerKey: 0x39fb25e900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000064000000000000000000000000bb93fcc2bb90d46255c43dd398a1a372e12be6aa474d81ab830043ccdd099147b7b90f073d8b2bda52892d40e8601cfcfbb03a2e35c6ae655cecc87a4c977435e36c8d88ea2950e1cec425e42909b934adbc1de8 Details: miner = 0xbb93FcC2bB90D46255c43DD398A1A372E12bE6Aa coinbase = 0xbb93FcC2bB90D46255c43DD398A1A372E12bE6Aa start = 1 end = 1000000 lifespan = 100 vrfVerifier = 0x474d81ab830043ccdd099147b7b90f073d8b2bda52892d40e8601cfcfbb03a2e voteVerfier = 0x35c6ae655cecc87a4c977435e36c8d88ea2950e1cec425e42909b934adbc1de8 GenesisStorage: 0x3a624f62c38b6bf3cf93859680439fb3332269b77ec12e8e022bf7be4a569857 = 0xbb93fcc2bb90d46255c43dd398a1a372e12be6aa000000640000000000000001 0x3a624f62c38b6bf3cf93859680439fb3332269b77ec12e8e022bf7be4a569858 = 0x474d81ab830043ccdd099147b7b90f073d8b2bda52892d40e8601cfcfbb03a2e 0x3a624f62c38b6bf3cf93859680439fb3332269b77ec12e8e022bf7be4a569859 = 0x35c6ae655cecc87a4c977435e36c8d88ea2950e1cec425e42909b934adbc1de8 mkdir -p ./data/kalgo/minerkeys cp ~/.kaleido/kalgo/minerkeys/0xbb93FcC2bB90D46255c43DD398A1A372E12bE6Aa-0-1000000.bin ./data/kalgo/minerkeys ./kalgo makeminerkey \ --miner.stakeowner 0x10210572d6b4924Af7Ef946136295e9b209E1FA0 \ --minerkey.coinbase 0x10210572d6b4924Af7Ef946136295e9b209E1FA0 \ --minerkey.start 9500 mkdir -p ./data/kalgo/minerkeys cp ~/.kaleido/kalgo/minerkeys/0x10210572d6b4924Af7Ef946136295e9b209E1FA0-0-1000000.bin ./data/kalgo/minerkeys myaddr = '0x10210572d6b4924Af7Ef946136295e9b209E1FA0'; hash = eth.sendTransaction({ from: myaddr, to: '0x1000000000000000000000000000000000000002', gas: 200000, gasPrice: 20000000000, data: '0x39fb25e9000000000000000000000000000000000000000000000000000000000000251c000000000000000000000000000000000000000000000000000000000000006400000000000000000000000010210572d6b4924af7ef946136295e9b209e1fa04141f8e905b628d3657b151a62c1414c4cc4f554d24bc16971c8233b761879d09b8f95e47a6f9662b78198c45ff986153c1e3316ede5cbfae09c6f927d374bd2' }); admin.sleepBlocks(2); eth.getTransactionReceipt(hash); personal.importRawKey("8d91c6c7a494a4373...0715","j7OseEMVQ3Trna6gWdkp") personal.unlockAccount("0x10210572d6b4924Af7Ef946136295e9b209E1FA0","j7OseEMVQ3Trna6gWdkp",0) nohup ./kalgo --config geth.toml --networkid 1001 --unlock "0x10210572d6b4924Af7Ef946136295e9b209E1FA0" --password ./password --mine --miner.stakeowner "0x10210572d6b4924Af7Ef946136295e9b209E1FA0" --nodiscover --etherbase 0 --verbosity 4 2>> ./geth.log & personal.unlockAccount("0xf8329ea42489c2164f44a81deb3be22c2c456109","j7OseEMVQ3Trna6gWdkp",0) eth.sendTransaction({from: "0xf8329ea42489c2164f44a81deb3be22c2c456109", to: "0x10210572d6b4924Af7Ef946136295e9b209E1FA0", value: web3.toWei(25000000, 'ether')}) admin.addPeer("enode://e20d5cb1c726bb5f8bcc5c3b91255e7fcf459add7584cf6becbb99715f2f84334f14233843bb50ce68c71c4f1b6cfeaff3294cec85a488f363428476010651b1@172.31.204.223:32668?discport=0") admin.addPeer("enode://95b41257c7da4bd0a98e81cb636b833c36693a3ab76ff55987663455b186dac96437d785b7bb6c5d5335bae91402f227d99a395c1dfe31663a891eb6e5d90140@172.31.204.224:32668?discport=0") ./kalgo --datadir ./data init genesis.json ./kalgo --config geth.toml --nodiscover ./kalgo attach ipc:./data/geth.ipc 参考 https://docs.kaleidochain.io
在之前《隐私/匿名币》中说过Zcash 首次运用 zk-SNARKs 零知识证明技术验证交易有效性，原来的计划是先把基于以太坊的侧链开发完成，再做ZKP（Zero-Knowledge Proofs）的技术跟进，以解决匿名交易以及增强TPS扩容方案（交易指纹上链，交易数据链下存储）。 今天先做一些技术调研和储备，方便后续技术评估时的一些技术方向的把控 技术相关 《什么是 zk-SNARK？》 《零知识证明：STARKs 与 SNARKs》 《zk-SNARKs 介绍（零知识证明概述以及如何将 zk-SNARK 集成到以太坊中）》 https://zokrates.github.io/ https://docs.ethhub.io/ethereum-roadmap/privacy/ https://starkware.co/ https://docs.ethhub.io/built-on-ethereum/infrastructure/aztec-protocol/ https://scrt.network/blog/introducing-secret-network https://ethresear.ch/t/zether-the-first-privacy-mechanism-designed-for-ethereum/5029 https://ethereum.stackexchange.com/questions/49781/can-zksnarks-be-implemented-on-the-ethereum-ecosystem https://github.com/starkware-libs/ https://hackmd.io/@zkteam/gnark https://docs.gnark.consensys.net/en/latest/ 开源代码 https://github.com/zkcrypto/bellman/ https://github.com/matter-labs/bellman https://github.com/zcash/librustzcash (Groth16) https://github.com/zcash/mpc https://github.com/scipr-lab/libsnark https://github.com/Zokrates/ZoKrates https://github.com/AztecProtocol https://github.com/arnaucube/go-snark-study https://github.com/vocdoni/go-snark https://github.com/ConsenSys/gnark (Groth16, PlonK) https://github.com/ConsenSys/gnark-crypto https://github.com/nikkolasg/playsnark （Groth16, PHGR13） https://github.com/ConsenSys/quorum
源代码：https://github.com/ethersphere/swap-swear-and-swindle Swarm目前只跑在Goerli测试网，核心的合约为ERC20SimpleSwap，源代码在master分支，对于后续功能合约 full Swap, Swear and Swindle相关代码在experimental分支 逻辑简述 目前Swarm的Goerli测试网部署的合约逻辑为 用户通过执行SimpleSwapFactory合约，部署ERC20SimpleSwap合约，并与发行的gBZZ代币交互 用户与Swarm交互 测试用户 0x10210572d6b4924Af7Ef946136295e9b209E1FA0 部署ERC20SimpleSwap合约 用户地址通过执行SimpleSwapFactory合约的deploySimpleSwap方法，完成部署ERC20SimpleSwap合约，通过查看交易的eventlog,部署的Chequebook合约地址为0x68f7ea9f3fae55e3e55fee0f0761b7df570fd212 deploySimpleSwap合约代码 function deploySimpleSwap(address issuer, uint defaultHardDepositTimeoutDuration) public returns (address) { address contractAddress = address(new ERC20SimpleSwap(issuer, ERC20Address, defaultHardDepositTimeoutDuration)); deployedContracts[contractAddress] = true; emit SimpleSwapDeployed(contractAddress); return contractAddress; } deploySimpleSwap执行同时完成了ERC20SimpleSwap的构造初始化 constructor(address _issuer, address _token, uint _defaultHardDepositTimeout) public { issuer = _issuer; token = ERC20(_token); defaultHardDepositTimeout = _defaultHardDepositTimeout; } 同时初始化了交互Token gBZZ代币 订金（为支票薄提供资金） 通过执行API进行为支票薄提供资金 http://localhost:1635/chequebook/deposit?amount=1000000000 执行的逻辑为用户地址0x10210572d6b4924af7ef946136295e9b209e1fa0向自己的Chequebook合约地址0x68f7ea9f3fae55e3e55fee0f0761b7df570fd212转对应数量的gBZZ，也可以理解成充值抵押 测试交易 https://goerli.etherscan.io/tx/0x81080bdc2ee4ede8898f003c4b74c9dcabdd6467e443f9ab8669f0c62434aaee 提现 将自己Chequebook合约中的gBZZ体现到用户地址 执行逻辑为执行自己的Chequebook合约地址0x68f7ea9f3fae55e3e55fee0f0761b7df570fd212中的withdraw方法。 合约代码 function withdraw(uint amount) public { /* 只有发行人可以做到这一点 */ require(msg.sender == issuer, "not issuer"); /* 确保我们不从保证金中提取任何东西 */ require(amount <= liquidBalance(), "liquidBalance not sufficient"); require(token.transfer(issuer, amount), "transfer failed"); } 测试交易 https://goerli.etherscan.io/tx/0x967fabac2da76d868a9f80d8a64baacbe2cb585519388a4494593705ef050421 分析ERC20SimpleSwap合约 // SPDX-License-Identifier: BSD-3-Clause pragma solidity =0.7.6; pragma abicoder v2; import "@openzeppelin/contracts/math/SafeMath.sol"; import "@openzeppelin/contracts/math/Math.sol"; import "@openzeppelin/contracts/cryptography/ECDSA.sol"; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/presets/ERC20PresetMinterPauser.sol"; /** @title支票簿合同，无豁免 @author The Swarm作者 @notice支票簿合同允许支票簿的发行者将支票发送到无限数量的交易对手。 此外，可以通过hardDeposits保证偿付能力 @dev作为发行人，如果发送的支票的累积价值高于所有存款的累积价值，则不应该发送任何支票 作为受益人，我们应始终考虑支票弹跳的可能性（当未分配hardDeposits时） */ contract ERC20SimpleSwap { using SafeMath for uint; event ChequeCashed( address indexed beneficiary, address indexed recipient, address indexed caller, uint totalPayout, uint cumulativePayout, uint callerPayout ); event ChequeBounced(); event HardDepositAmountChanged(address indexed beneficiary, uint amount); event HardDepositDecreasePrepared(address indexed beneficiary, uint decreaseAmount); event HardDepositTimeoutChanged(address indexed beneficiary, uint timeout); event Withdraw(uint amount); uint public defaultHardDepositTimeout; /* 跟踪每个受益人的硬存款（偿付能力的链上保证）的结构*/ struct HardDeposit { uint amount; /* 分配的硬存款金额 */ uint decreaseAmount; /* 减少量从请求减少时的金额中减去 */ uint timeout; /* 发行者必须等待超时秒数才能减少hardDeposit，0表示应用defaultHardDepositTimeout */ uint canBeDecreasedAt; /* 可以减少硬质沉积的时间点 */ } struct EIP712Domain { string name; string version; uint256 chainId; } bytes32 public constant EIP712DOMAIN_TYPEHASH = keccak256( "EIP712Domain(string name,string version,uint256 chainId)" ); bytes32 public constant CHEQUE_TYPEHASH = keccak256( "Cheque(address chequebook,address beneficiary,uint256 cumulativePayout)" ); bytes32 public constant CASHOUT_TYPEHASH = keccak256( "Cashout(address chequebook,address sender,uint256 requestPayout,address recipient,uint256 callerPayout)" ); bytes32 public constant CUSTOMDECREASETIMEOUT_TYPEHASH = keccak256( "CustomDecreaseTimeout(address chequebook,address beneficiary,uint256 decreaseTimeout)" ); // 该合同使用的EIP712域 function domain() internal pure returns (EIP712Domain memory) { uint256 chainId; assembly { chainId := chainid() } return EIP712Domain({ name: "Chequebook", version: "1.0", chainId: chainId }); } // 计算EIP712域分隔符。 这不能恒定，因为它取决于chainId function domainSeparator(EIP712Domain memory eip712Domain) internal pure returns (bytes32) { return keccak256(abi.encode( EIP712DOMAIN_TYPEHASH, keccak256(bytes(eip712Domain.name)), keccak256(bytes(eip712Domain.version)), eip712Domain.chainId )); } // 使用EIP712签名方案恢复签名 function recoverEIP712(bytes32 hash, bytes memory sig) internal pure returns (address) { bytes32 digest = keccak256(abi.encodePacked( "\x19\x01", domainSeparator(domain()), hash )); return ECDSA.recover(digest, sig); } /* 此支票簿针对其写支票的令牌 */ ERC20 public token; /* 将每个受益人与已支付给他们的款项相关联 */ mapping (address => uint) public paidOut; /* 支付总额 */ uint public totalPaidOut; /* 将每个受益人与他们的HardDeposit相关联 */ mapping (address => HardDeposit) public hardDeposits; /* 所有硬存款的总和 */ uint public totalHardDeposit; /* 合同的签发人，在施工中 */ address public issuer; /* 指示支票是否在过去退回 */ bool public bounced; /** @notice设置颁发者，令牌和defaultHardDepositTimeout。 只能被调用一次。 @param _issuer此支票簿的支票的发行者（需要作为“将支票簿设置为付款”的参数）。 _issuer必须是外部拥有的帐户，或者它必须支持调用函数cashCheque @param _token此支票簿使用的令牌 @param _defaultHardDepositTimeout持续时间（以秒为单位），默认情况下将用于减少hardDeposit分配 */ function init(address _issuer, address _token, uint _defaultHardDepositTimeout) public { require(_issuer != address(0), "invalid issuer"); require(issuer == address(0), "already initialized"); issuer = _issuer; token = ERC20(_token); defaultHardDepositTimeout = _defaultHardDepositTimeout; } /// @return 支票簿的余额 function balance() public view returns(uint) { return token.balanceOf(address(this)); } /// @return 余额中没有硬存款支付的部分 function liquidBalance() public view returns(uint) { return balance().sub(totalHardDeposit); } /// @return 余额中可用于特定受益人的部分 function liquidBalanceFor(address beneficiary) public view returns(uint) { return liquidBalance().add(hardDeposits[beneficiary].amount); } /** @dev内部函数负责检查issuerSignature，更新hardDeposit余额并进行转账。 由CashCheque和CashChequeBeneficary调用 @param受益人支票分配给的受益人。 受益人必须是外部拥有的帐户 @param接收者会收到累计付款与已付给受益人减去调用者的款项之间的差额 @paramcumulativePayout分配给受益人的累计支票数量 @param issuerSig如果发行人不是发件人，则发行人必须已在累计付款上明确授予受益人 */ function _cashChequeInternal( address beneficiary, address recipient, uint cumulativePayout, uint callerPayout, bytes memory issuerSig ) internal { /* 发行人必须通过作为主叫方或通过签名明确批准累计支付 */ if (msg.sender != issuer) { require(issuer == recoverEIP712(chequeHash(address(this), beneficiary, cumulativePayout), issuerSig), "invalid issuer signature"); } /* requestPayout是请求进行付款处理的金额 */ uint requestPayout = cumulativePayout.sub(paidOut[beneficiary]); /* 计算实际支出 */ uint totalPayout = Math.min(requestPayout, liquidBalanceFor(beneficiary)); /* 计算硬存款使用量 */ uint hardDepositUsage = Math.min(totalPayout, hardDeposits[beneficiary].amount); require(totalPayout >= callerPayout, "SimpleSwap: cannot pay caller"); /* 如果有一些使用的硬存款，请更新hardDeposits */ if (hardDepositUsage != 0) { hardDeposits[beneficiary].amount = hardDeposits[beneficiary].amount.sub(hardDepositUsage); totalHardDeposit = totalHardDeposit.sub(hardDepositUsage); } /* 增加存储的payedOut金额以避免双倍支付 */ paidOut[beneficiary] = paidOut[beneficiary].add(totalPayout); totalPaidOut = totalPaidOut.add(totalPayout); /* 让全世界知道发行人对未兑现支票的承诺过高 */ if (requestPayout != totalPayout) { bounced = true; emit ChequeBounced(); } if (callerPayout != 0) { /* 如果指定，则转接到呼叫者 */ require(token.transfer(msg.sender, callerPayout), "transfer failed"); /* 进行实际付款 */ require(token.transfer(recipient, totalPayout.sub(callerPayout)), "transfer failed"); } else { /* 进行实际付款 */ require(token.transfer(recipient, totalPayout), "transfer failed"); } emit ChequeCashed(beneficiary, recipient, msg.sender, totalPayout, cumulativePayout, callerPayout); } /** @notice兑现非受益人的受益人支票，并通过发送方与发件人奖励这样做 @dev受益人必须能够生成签名（作为外部拥有的帐户）才能使用此功能 @param受益人支票分配给的受益人。受益人必须是外部拥有的帐户 @param接收者会收到累计付款与已付给受益人减去调用者的款项之间的差额 @paramcumulativePayout分配给受益人的累计支票数量 @param受益人Sig受益人必须已经明确批准才能由发件人兑现累计付款并发送给callerPayout @param issuerSig如果发行人不是发件人，则发行人必须已在累计付款上明确授予受益人 @param callerPayout当受益人还没有以太币时，他可以在callerPayout的帮助下激励其他人兑现支票 @param issuerSig如果发行人不是发件人，则发行人必须已在累计付款上明确授予受益人 */ function cashCheque( address beneficiary, address recipient, uint cumulativePayout, bytes memory beneficiarySig, uint256 callerPayout, bytes memory issuerSig ) public { require( beneficiary == recoverEIP712( cashOutHash( address(this), msg.sender, cumulativePayout, recipient, callerPayout ), beneficiarySig ), "invalid beneficiary signature"); _cashChequeInternal(beneficiary, recipient, cumulativePayout, callerPayout, issuerSig); } /** @注意兑现支票作为受益人 @param接收者会收到累计付款与已付给受益人减去调用者的款项之间的差额 @param的累计付款要求付款 @param issuerSig发行者必须已对收款人的累计付款给予明确批准 */ function cashChequeBeneficiary(address recipient, uint cumulativePayout, bytes memory issuerSig) public { _cashChequeInternal(msg.sender, recipient, cumulativePayout, 0, issuerSig); } /** @注意准备减少硬沉积 @dev减少hardDeposit必须分两步完成，以使受益人兑现任何未兑现的支票（并利用相关的硬存款） @param受益人，应减少其硬存款 @param reduction存款应减少的金额 */ function prepareDecreaseHardDeposit(address beneficiary, uint decreaseAmount) public { require(msg.sender == issuer, "SimpleSwap: not issuer"); HardDeposit storage hardDeposit = hardDeposits[beneficiary]; /* 如果减少的幅度超过存款，则不能减少 */ require(decreaseAmount <= hardDeposit.amount, "hard deposit not sufficient"); // 如果从未设置hardDeposit.timeout，则应用defaultHardDepositTimeout uint timeout = hardDeposit.timeout == 0 ? defaultHardDepositTimeout : hardDeposit.timeout; hardDeposit.canBeDecreasedAt = block.timestamp + timeout; hardDeposit.decreaseAmount = decreaseAmount; emit HardDepositDecreasePrepared(beneficiary, decreaseAmount); } /** @notice自调用prepareDecreaseHard存款以来等待了必要的时间后，减少了硬存款 @param受益人，应减少其硬存款 */ function decreaseHardDeposit(address beneficiary) public { HardDeposit storage hardDeposit = hardDeposits[beneficiary]; require(block.timestamp >= hardDeposit.canBeDecreasedAt && hardDeposit.canBeDecreasedAt != 0, "deposit not yet timed out"); /* this throws if decreaseAmount > amount */ //TODO: 如果prepareDecreaseHardDeposit和reducedHardDeposit之间有现金兑现，那么reducingHardDeposit将抛出并且无法减少硬存款。 hardDeposit.amount = hardDeposit.amount.sub(hardDeposit.decreaseAmount); /* 重置canBeDecreasedAt以避免两次减少 */ hardDeposit.canBeDecreasedAt = 0; /* 保持totalDeposit同步 */ totalHardDeposit = totalHardDeposit.sub(hardDeposit.decreaseAmount); emit HardDepositAmountChanged(beneficiary, hardDeposit.amount); } /** @注意增加硬保证金 @param受益人，应减少其硬存款 @param金额新的硬存款 */ function increaseHardDeposit(address beneficiary, uint amount) public { require(msg.sender == issuer, "SimpleSwap: not issuer"); /* 确保保证金不超过全球余额 */ require(totalHardDeposit.add(amount) <= balance(), "hard deposit exceeds balance"); HardDeposit storage hardDeposit = hardDeposits[beneficiary]; hardDeposit.amount = hardDeposit.amount.add(amount); // 我们没有明确设置hardDepositTimout，因为零意味着使用默认的HardDeposit超时 totalHardDeposit = totalHardDeposit.add(amount); /* 禁用任何未决的减少 */ hardDeposit.canBeDecreasedAt = 0; emit HardDepositAmountChanged(beneficiary, hardDeposit.amount); } /** @notice允许为每个受益人设置自定义的hardDepositDecreaseTimeout @dev，当必须保证偿付能力的时间长于defaultHardDepositDecreaseTimeout时，这是必需的 @param收款人的硬存款减少的受益人必须更改超时 @param hardDepositTimeout受益人新的hardDeposit.timeout @param受益人Sig受益人必须通过在新的reduceTimeout上签名来明确批准 */ function setCustomHardDepositTimeout( address beneficiary, uint hardDepositTimeout, bytes memory beneficiarySig ) public { require(msg.sender == issuer, "not issuer"); require( beneficiary == recoverEIP712(customDecreaseTimeoutHash(address(this), beneficiary, hardDepositTimeout), beneficiarySig), "invalid beneficiary signature" ); hardDeposits[beneficiary].timeout = hardDepositTimeout; emit HardDepositTimeoutChanged(beneficiary, hardDepositTimeout); } /// @notice提取以太币 /// @param要提取的金额 // solhint-disable-next-line no-simple-event-func-name function withdraw(uint amount) public { /* 只有发行人可以做到这一点 */ require(msg.sender == issuer, "not issuer"); /* 确保我们不从保证金中提取任何东西 */ require(amount <= liquidBalance(), "liquidBalance not sufficient"); require(token.transfer(issuer, amount), "transfer failed"); } function chequeHash(address chequebook, address beneficiary, uint cumulativePayout) internal pure returns (bytes32) { return keccak256(abi.encode( CHEQUE_TYPEHASH, chequebook, beneficiary, cumulativePayout )); } function cashOutHash(address chequebook, address sender, uint requestPayout, address recipient, uint callerPayout) internal pure returns (bytes32) { return keccak256(abi.encode( CASHOUT_TYPEHASH, chequebook, sender, requestPayout, recipient, callerPayout )); } function customDecreaseTimeoutHash(address chequebook, address beneficiary, uint decreaseTimeout) internal pure returns (bytes32) { return keccak256(abi.encode( CUSTOMDECREASETIMEOUT_TYPEHASH, chequebook, beneficiary, decreaseTimeout )); } } Cash Cheque Beneficiary https://goerli.etherscan.io/tx/0x6e255bcd2eb2a84d2a80e25368413f805a036d3cea18d43c81e96f0158a3084c
最近社区很多小伙伴来询问Chia分布式部署的方法，那我们今天就写一下Chia分布式的步骤以及测试环境验证 分布式的逻辑 其实前面已经写过部署的草稿了《Chia 分布式部署文档》，但是由于当时时间紧，对于没有开发基础的小伙伴理解上还是有些难度。 下面我们详细说下分布式拆分原理以及注意事项 下面是Chia架构模块结构 简单说每个模块都是单独的程序，都是通过TCP连接的，对于Chia Gui程序，默认运行是把各个模块都运行在本地，localhost连接的。对于分布式来说，就是把各个模块根据功能和负载等因素分不到不同机器。 模拟生产环境 由于plots存储量体量无上限，并且数据可重新生成，对于存储量和数据安全来说，优先选择存储量，所以单盘存储，不做raid。 对于专门做Chia挖矿的,一般的硬件配置是搞一部分配置稍微好的机器做P盘机（绘图机/work机..）{稍后会专门写一篇推荐生产P盘配置的文章},一些二手的存储机做存储（服务器前部分是磁盘阵列），P盘机硬件配置存储部分只有SSD临时绘图用，最终的plots存储到存储机《为Chia P盘机器提供存储服务》 此篇文章我们主要讲挖矿的分布式，对于P盘自动化，后面会有专门的程序和文章做讲解。 生产环境中，一般是把所有的机器放到一个IDC机房，或者自己来管理，所以模块内服务器都是内网连接，网络环境相对简单。 中控高度可控 对于系统化架构的设计大致如下，各模块彼此低耦合，对于一台机器只要装上中控子服务，配置好Chia中控调度地址，Chia中控调度服务会自动配置该机器的环境，并自动下发相应的任务，减少用户的学习成本。 也可以用于推广绘图算力和绘图存储， 用户只需要安装对应中控子服务（绘图/存储），Chia中控调度中心即可自动下发对应的任务，对于绘图任务类似POW，根据用户绘图量用户得到相应的奖励。 用户也可以选择存储任务，此时中控子服务会下发已经会好的图，用户无感知，只需要准备好足够容量的存储空间即可，子服务会自动安装好Harvester相关服务配置，中控调度只需要定时检查用户存储状态和网络环境，就可以根据工作量进行奖励发放。当然发放的可以别的链的对应代币，可以用于对冲推广代币等。 只是对调度服务的另一个使用场景吧， 后面社区也许会推出整体服务，暂时不确定 对于没有开发能力的，架构中的中控服务可以先用人工来代替 简化后架构如下 对于各模块的增改也先人工处理 实际测试 我们来演示最基础的分布式挖矿配置，一台挖矿服务器和两台存储服务器之前的配置，以及验证配置是否正确。 稍后继续补充
操作系统 Ubuntu 20.04 编译 sudo apt-get update sudo apt-get upgrade -y # Checkout the source and install git clone https://github.com/Chia-Network/chia-blockchain.git -b latest cd chia-blockchain sh install.sh . ./activate # The GUI requires you have Ubuntu Desktop or a similar windowing system installed. # You can not install and run the GUI as root sh install-gui.sh cd chia-blockchain-gui npm run electron & 升级 cd chia-blockchain chia stop -d all deactivate git fetch git checkout latest git pull # If you get RELEASE.dev0 then delete the package-lock.json in chia-blockchain-gui and install.sh again sh install.sh . ./activate chia init # The GUI requires you have Ubuntu Desktop or a similar windowing system installed. # You can not install and run the GUI as root sh install-gui.sh cd chia-blockchain-gui npm run electron & 参考 https://github.com/Chia-Network/chia-blockchain/wiki/INSTALL#ubuntudebian
下载 Chia GUI 程序 Windows下载地址: https://github.com/Chia-Network/chia-blockchain/releases/download/1.0.5/ChiaSetup-1.0.5.exe 然后安装完启动 启动程序 如果之前有创建过账户,则点击“自助记词中导入(24个词)”,如果没有的话,直接点击“创建一个新的私钥” 创建一个新的私钥 把助记词保存好,最好手抄到文件,存放到安全的地方 点击下一步,进入主程序 确认“连接状态”为“已连接”,对于“状态”刚开始运行时会显示“未同步”,稍微等等就好了. 开始P数据 点击“绘图” 点击“添加PLOT文件” 分别对各个选项做设置 1. 选择Plot文件大小 确认“选择Plot文件大小”选择的是“101.04GiB(k=32 ...)” 目前这个是主网最优的选项, 对于测试网可以选择“600MiB(k=25 ...)” 2. 选择要拓荒的数量 绘图数设置为 当前机器CPU 总核心数相等的数(即为2线程一个任务) 并选择“并发拓荒” 备注 如果想设置单线程跑一个任务,则点击“显示高级选项”,修改线程数量为1, 同时上一步中的绘图数设置为CPU核心数的2倍. 3. 选择临时目录 选择存放临时数据的存储目录 这个是P数据时计算需要的临时空间,这个存储需要较高的读写速度,不然严重影响P盘速度 单个任务大概需要304G空间. 4. 选择最终目录 选择最终P数据的生成位置,也就是单独的存储服务器的存储目录. 存储服务部署,可以查看《为Chia P盘机器提供存储服务》
逻辑简述 简单来说就是P盘机器专门用来生产plots, 单独的机器用来存储为收割机程序消费. 所以需要搭建存储服务,对于服务选择来说有nfs 和samba等,此片文件将使用samba来部署. 环境搭建 假设P盘机器(A)ip为:192.168.2.152 存储机器(B)ip为:192.168.2.142 B机器 安装samba sudo apt-get upgrade sudo dpkg -l | grep samba sudo apt-get install samba samba-common B机器生产环境为磁盘阵列,为了避免数据损坏,以及存储最大化,单盘使用. 为每个盘做raid0(因为服务器前部磁盘通常链接的板卡必须要做硬raid,所以即使单硬盘使用,也需要每个磁盘做raid0,不然不识别,设置在bios中) 下面在samba服务配置文件中,为每个磁盘做共享目录设置 设置samba配置 我们拿其中一个硬盘做举例,其余的盘操作相同 sudo vi /etc/samba/smb.conf 假设第一个磁盘挂载路径为 /mnt/md0. 把当前存储机登陆账户tom添加到samba服务 sudo pdbedit -a -u tom sudo pdbedit -L 因为主要是内网使用, 直接使用当前系统账户登陆了,也可以单独创建账户登陆,因为文件写入时和当前配置账户是同一个组,避免数据权限问题. 在文件最后添加 [md0] comment = md0 path = /mnt/md0 public = no browseable = yes writable = yes available = yes valid users = tom 其他的磁盘逻辑同上. 重启 Samba 服务 sudo service smbd restart 处理完成后,存储机的配置就完成了,为每个存储磁盘创建了共享. A机器 安装cifs samba sudo apt-get install cifs-utils samba 手动将md0 挂载到当前机器目录/home/tom/md0 查看B机器uid, 在B机器终端输入id sudo mount.cifs //192.168.2.142/md0 /home/tom/md0 --verbose -o username="tom",password="123456",uid=1000,iocharset=utf8,rw,dir_mode= 0777,file_mode=0777 此时B机器的md0共享目录就被挂载到/home/tom/md0,并且只需要当前登陆账户即可读写(非root) 开机启动挂载共享目录 sudo vi /etc/fstab 尾部添加 //192.168.2.142/md0 /home/tom/md0 cifs username="tom",passwo rd="123456",iocharset=utf8,rw,dir_mode=0777,file_mode=0777,uid=1000 0 0 至此对于单盘的添加就完成了,A机器的/home/tom/md0就是B机器的共享目录md0 也就是A机器添加Plot时,最终目录选择/home/tom/md0,生成好后直接就复制到了B机器的共享目录md0,也就是到了B机器的存储第一个硬盘里