- 第 1 章 区块链
- 第 2 章 以太坊
- 第 3 章 以太坊私链入门
- 第 4 章 以太坊网络
- 第 5 章 geth v1.8.16 命令详解
- 第 6 章 Wallet
- 第 7 章 Token
- 第 8 章 智能合约语言 Solidity v0.5.0
- 第 9 章 Truffle v4.1.8 开发框架
- 第 10 章 web3.js - 1.0.0
- 第 11 章 web3j v3.4.0 - Jave Client
- 11.2. 启动以太坊
- 11.3. Maven pom.xml 文件
- 11.4. Java 与 Solidity 数据类型映射关系
- 11.5. 常量
- 11.6. 连接到服务器获取版本号
- 11.7. 获得以太坊状态信息
- 11.8. 单位转换
- 11.9. 账号管理
- 11.10. Credentials
- 11.11. 交易
- 11.12. 钱包
- 11.13. 智能合约
- 11.14. ERC20合约
- 11.15. Infura
- 11.16. 助记词
- 11.17. 过滤器 (Filter)
- 11.18. Subscription
- 11.19. 解锁账号
- 11.20. IBAN (International Bank Account Number)
- 11.21. Springboot with Ethereum (web3j)
- 第 12 章 web3.py - A python interface for interacting with the Ethereum blockchain and ecosystem.
- 第 14 章 Ethereum Developer APIs
- 第 15 章 infura
- 第 16 章 以太坊案例
- 第 17 章 FAQ
- 17.3. Error: authentication needed: password or unlock
- 17.4. 新增节点后不生效
- 17.5. Unhandled rejection Error: Returned error: The method personal_unlockAccount does not exist/is not available
- 17.6. Error: exceeds block gas limit
- 17.7. Migrations.sol:11:3: Warning: Defining constructors as functions with the same name as the contract is deprecated. Use "constructor(…) { … }" instead.
- 17.8. Exception in thread "main" rx.exceptions.OnErrorNotImplementedException: Invalid response received: okhttp3.internal.http.RealResponseBody@6c25e6c4
- 17.9. 旧版本 Remix(browser-solidity) 本地安装
- 第 18 章 Hyperledger Fabric v2.0.0
- 第 19 章 Hyperledger Fabric 运维
- 第 20 章 Chaincode 链码(智能合约)
- 第 21 章 Hyperledger Fabric Client SDK for Node.js
- 第 22 章 fabric-sdk-java
- 第 24 章 已知 Hyperledger 落地案例
- 第 25 章 Fabric Command
- 第 26 章 Fabric FAQ
- 第 27 章 IPFS(InterPlanetary File System,星际文件系统)
- 第 28 章 IPFS 命令
- 第 29 章 IPFS WebUI
- 第 30 章 IPFS 集群配置
- 第 31 章 IPFS API
- 第 32 章 IPFS Faq
- 第 33 章 EOS
- 第 34 章 EOS 安装
- 第 35 章 CLEOS
- 第 36 章 智能合约开发
- 第 37 章 EOS Dapp 开发
- 第 38 章 FAQ
- 第 39 章 BaaS (Blockchain as a Service) 平台
- 第 40 章 BitCoin
- 第 41 章 其他区块链相关
- 附录 1. 附录
1.21. 区块链探索
1.21. 区块链探索
1.21.1. 以太坊物流场景解决方案
网上谈关于物流行业区块链的文章很多,但是你会发现找遍互联网也找不到具体怎样将物流落地到区块链的文章,于是我只能自己捣鼓。
背景,使用区块链记录物流信息,实现信息朔源。
我想法是,将物流信息放到区块链中,实现物流中转信息的添加,当用户签收后合约关闭,不再允许增加新信息。
首先,每个物流单一张合约
其次,以太坊账号代表转运站,或者用户,这里我们使用5个账号分别代表不同的角色。
pragma solidity ^0.4.20; contract Logistics { enum State { New, Reviewed, Pending, Shipping, Received } struct Node { address owner; // 中转站 string date; // 转运日期 State status; // 状态 string message; // 留言信息 } mapping (uint => Node) stations; uint number = 1; string name; //商品名称 bool close = false; //合约状态 function Logistics(string _name) public { name = _name; } function getName() public view returns(string){ return name; } // 增加物流中转信息 function put(address _owner,string _date, State _status, string _message ) public{ if(close == false){ Node memory node = Node(_owner,_date,_status,_message); stations[number] = node; number = number + 1; } if (_status == State.Received) { close = true; } } // 获得中转信息 function get(uint _number) public view returns(address, string, State, string) { require(_number < number); Node memory node = stations[_number]; return (node.owner, node.date, node.status, node.message); } // 或者转中站数量 function getNode() public view returns(uint){ return number; } }
保存合约到 Truffle 的 contracts/Logistics.sol
部署代码
neo@MacBook-Pro ~/ethereum/truffle % cat migrations/1_initial_migration.js var Logistics = artifacts.require("./Logistics.sol"); module.exports = function(deployer) { deployer.deploy(Logistics,"Mackbook"); };
Mackbook 就是商品名称。
编译部署合约
neo@MacBook-Pro ~/ethereum/truffle % truffle compile --all Compiling ./contracts/Logistics.sol... Writing artifacts to ./build/contracts neo@MacBook-Pro ~/ethereum/truffle % truffle migrate --reset Using network 'development'. Running migration: 1_initial_migration.js Replacing Logistics... ... 0x14b6b6bfb84383b8325f5e97a6b7a5c1d1f5c2e162a4bd201b93a9d30cd75d8e Logistics: 0x1cff61b8259f05f4bbf7aa4f769321e5fa70b22d Saving successful migration to network... ... 0x26d544c8db7b1cf06034963e5f5bea7b28d11e7295a018f1b80a7555c38f26e7 Saving artifacts...
启动开发环境
neo@MacBook-Pro ~/ethereum/truffle % truffle develop Truffle Develop started at http://localhost:9545/ Accounts: (0) 0x627306090abab3a6e1400e9345bc60c78a8bef57 (1) 0xf17f52151ebef6c7334fad080c5704d77216b732 (2) 0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef (3) 0x821aea9a577a9b44299b9c15c88cf3087f3b5544 (4) 0x0d1d4e623d10f9fba5db95830f7d3839406c6af2 (5) 0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e (6) 0x2191ef87e392377ec08e7c08eb105ef5448eced5 (7) 0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5 (8) 0x6330a553fc93768f612722bb8c2ec78ac90b3bbc (9) 0x5aeda56215b167893e80b4fe645ba6d5bab767de Private Keys: (0) c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3 (1) ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f (2) 0dbbe8e4ae425a6d2687f1a7e3ba17bc98c673636790f1b8ad91193c05875ef1 (3) c88b703fb08cbea894b6aeff5a544fb92e78a18e19814cd85da83b71f772aa6c (4) 388c684f0ba1ef5017716adb5d21a053ea8e90277d0868337519f97bede61418 (5) 659cbb0e2411a44db63778987b1e22153c086a95eb6b18bdf89de078917abc63 (6) 82d052c865f5763aad42add438569276c00d3d88a2d062d36b2bae914d58b8c8 (7) aa3680d5d48a8283413f7a108367c7299ca73f553735860a87b08f39395618b7 (8) 0f62d96d6675f32685bbdb8ac13cda7c23436f63efbb9d07700d8669ff12b7c4 (9) 8d5366123cb560bb606379f90a0bfd4769eecc0557f1b362dcae9012b548b1e5 Mnemonic: candy maple cake sugar pudding cream honey rich smooth crumble sweet treat truffle(develop)>
开发环境会创建10个账号用户测试。我们需要使用前5个账号,每个账号代表一个转运站,或者用户
进入控制台验证合约
var contract; Logistics.deployed().then(function(instance){contract=instance;}); contract.getName(); contract.put("0x627306090abab3a6e1400e9345bc60c78a8bef57","2018-02-20",0,"寄包裹"); contract.get(1); contract.put("0xf17f52151ebef6c7334fad080c5704d77216b732","2018-02-21",1,"包裹揽件"); contract.get(2); contract.put("0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef","2018-02-22",2,"运输处理中"); contract.get(3); contract.put("0x821aea9a577a9b44299b9c15c88cf3087f3b5544","2018-02-23",3,"运输处理中"); contract.get(4); contract.put("0x0d1d4e623d10f9fba5db95830f7d3839406c6af2","2018-02-24",4,"包裹收到"); contract.get(5); contract.getNode(); contract.put("0x0d1d4e623d10f9fba5db95830f7d3839406c6af2","2018-02-22",5,"已经收到包裹,合约关闭,不允许在修改"); contract.get(6);
操作演示如下
truffle(development)> var contract; undefined truffle(development)> Logistics.deployed().then(function(instance){contract=instance;}); undefined truffle(development)> contract.getName(); 'Mackbook' truffle(development)> contract.put("0x627306090abab3a6e1400e9345bc60c78a8bef57","2018-02-20",0,"寄包裹"); { tx: '0x74992b7cccb214600ac2f1257486053202736714cf7e9e69fb62cba692bc6592', receipt: { transactionHash: '0x74992b7cccb214600ac2f1257486053202736714cf7e9e69fb62cba692bc6592', transactionIndex: 0, blockHash: '0xc838fb9c5352544f4d743b170d146a9ef1b1ef6a30019c33e2a77df24e808964', blockNumber: 86, gasUsed: 98633, cumulativeGasUsed: 98633, contractAddress: null, logs: [], status: 1 }, logs: [] } truffle(development)> contract.get(1); [ '0x627306090abab3a6e1400e9345bc60c78a8bef57', '2018-02-20', BigNumber { s: 1, e: 0, c: [ 0 ] }, '寄包裹' ] truffle(development)> contract.put("0xf17f52151ebef6c7334fad080c5704d77216b732","2018-02-21",1,"包裹揽件"); { tx: '0x3f8dcd5f0d9a9ec60942e6a1c73556dfcfde59354fc24474ffc8e32b9b00ac61', receipt: { transactionHash: '0x3f8dcd5f0d9a9ec60942e6a1c73556dfcfde59354fc24474ffc8e32b9b00ac61', transactionIndex: 0, blockHash: '0x96c889cae1001265bcdf32c808770a7f9f0c325467912524c10100bc04cf8271', blockNumber: 87, gasUsed: 113889, cumulativeGasUsed: 113889, contractAddress: null, logs: [], status: 1 }, logs: [] } truffle(development)> contract.get(2); [ '0xf17f52151ebef6c7334fad080c5704d77216b732', '2018-02-21', BigNumber { s: 1, e: 0, c: [ 1 ] }, '包裹揽件' ] truffle(development)> contract.put("0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef","2018-02-22",2,"运输处理中"); { tx: '0x1ebe589e6b63479f9542ba67650d63757ca45ac38cb43d395b5bc2a573d0363b', receipt: { transactionHash: '0x1ebe589e6b63479f9542ba67650d63757ca45ac38cb43d395b5bc2a573d0363b', transactionIndex: 0, blockHash: '0x83edf5fc1e38062dafc49a21b3d9a1fa0f9ddfb0f2e749b2b1945d03360a5209', blockNumber: 88, gasUsed: 114081, cumulativeGasUsed: 114081, contractAddress: null, logs: [], status: 1 }, logs: [] } truffle(development)> contract.get(3); [ '0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef', '2018-02-22', BigNumber { s: 1, e: 0, c: [ 2 ] }, '运输处理中' ] truffle(development)> contract.put("0x821aea9a577a9b44299b9c15c88cf3087f3b5544","2018-02-22",3,"运输处理中"); { tx: '0x44b2bf7853e6b4c86f732bb8f1bcee17f00e0f850530e359753b4d7c55c35b4d', receipt: { transactionHash: '0x44b2bf7853e6b4c86f732bb8f1bcee17f00e0f850530e359753b4d7c55c35b4d', transactionIndex: 0, blockHash: '0x7e79ca2570f5045f4c226805866803f898109d238518fa1e5abe6b4ee4c1c552', blockNumber: 89, gasUsed: 114081, cumulativeGasUsed: 114081, contractAddress: null, logs: [], status: 1 }, logs: [] } truffle(development)> contract.get(4); [ '0x821aea9a577a9b44299b9c15c88cf3087f3b5544', '2018-02-22', BigNumber { s: 1, e: 0, c: [ 3 ] }, '运输处理中' ] truffle(development)> contract.put("0x0d1d4e623d10f9fba5db95830f7d3839406c6af2","2018-02-22",4,"包裹收到"); { tx: '0xb2b0223dc7cc90744a97ea002ecd468796d7596e38f8bb105c9f2103da6dfa19', receipt: { transactionHash: '0xb2b0223dc7cc90744a97ea002ecd468796d7596e38f8bb105c9f2103da6dfa19', transactionIndex: 0, blockHash: '0xeb1051e80fe920fc166288036e6d27b38aca27144d2b636decade338f787371b', blockNumber: 90, gasUsed: 134156, cumulativeGasUsed: 134156, contractAddress: null, logs: [], status: 1 }, logs: [] } truffle(development)> contract.get(5); [ '0x0d1d4e623d10f9fba5db95830f7d3839406c6af2', '2018-02-22', BigNumber { s: 1, e: 0, c: [ 4 ] }, '包裹收到' ] truffle(development)> contract.getNode(); BigNumber { s: 1, e: 0, c: [ 6 ] } truffle(development)>
合一已经关闭,添加不会出错,但是没有数据进入区块中,使用 contract.get(6); 获取数据会抛出异常。
truffle(development)> contract.put("0x0d1d4e623d10f9fba5db95830f7d3839406c6af2","2018-02-22",3,"已经收到包裹,合约关闭,不允许在修改"); { tx: '0x72999fc308f2f3bc1f70fbc919c8b08594f177318dc4e57dd5ea590248e9a6cc', receipt: { transactionHash: '0x72999fc308f2f3bc1f70fbc919c8b08594f177318dc4e57dd5ea590248e9a6cc', transactionIndex: 0, blockHash: '0xa3d9bc835bd5de6067271baa7899c3aaada6088362371b5139f4fa7cbd9f4050', blockNumber: 91, gasUsed: 29360, cumulativeGasUsed: 29360, contractAddress: null, logs: [], status: 1 }, logs: [] } truffle(development)> contract.get(6); Error: VM Exception while processing transaction: revert at XMLHttpRequest._onHttpResponseEnd (/usr/local/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:509:1) at XMLHttpRequest._setReadyState (/usr/local/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:354:1) at XMLHttpRequestEventTarget.dispatchEvent (/usr/local/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:64:1) at XMLHttpRequest.request.onreadystatechange (/usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/httpprovider.js:128:1) at /usr/local/lib/node_modules/truffle/build/webpack:/~/truffle-provider/wrapper.js:134:1 at /usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/requestmanager.js:86:1 at Object.InvalidResponse (/usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/errors.js:38:1) truffle(development)>
这个合约还不是很完善,仅仅是作者的想法,是否在实际项目中可行,尚未知,区块链应用场景实例的文章还比较少,只能摸索前进。
1.21.2. 区块链防伪溯源应用场景
食品安全溯源
下面的方案,同样适合药品安全溯源
1.21.2.1. 背景
需求是通过区块链跟踪产品,实现产品产地,生产,流通等环节溯源。
需求归纳,需要实现下面几点:
产品具备通用的属性,例如名称,价格,重量,颜色,体积等等
生产销售链条跟踪
涉及环节,农产品的供应链是一个非常复杂的过程,涉及多方,农业局、卫生局、药监局、工商局、环保局等多个部门交织其中。
参与者角色,我们为每个环节的参与者分配一个以太坊账号,例如每个供应商一个账号,每个代理商一个账号。这样任何一方经手后都会使用自己的账号想合约中添加数据。
1.21.2.2. 如何实现
首先,要理解什么是物品,什么是数据。
其次,要明白物品跟数据的关系,物品怎么跟数据建立关系。
食品是物品,物品与数据并无关联,所谓溯源是指一连串的数据。数据是可以伪造的,我们可以伪造一连串溯源数据,然后上链。这些数据都是安全正常供应链数据伪造到。所以数据只是数据,与物品没有任何关系。
怎样把物品和数据建立关系呢?我们通常会借助中间载体。例如
- 贴二维码,RFID标签,NFC标签 。或者通过激光打码到物品上
- 装箱,装瓶,装盒子
- 提取生物特征信息
贴二维码,RFID和NFC标签是常规做法,将物品上贴上标签,标签带有唯一标示,标签与区块链上的数据建立关联。这样当读取标签的时候,就能调出区块链上的数据。
物品装进盒子,盒子上有唯一标示,盒子与区块链数据建立关联。
生物特征是指,例如人与数据建立连接,可以采集面部数据,眼部虹膜数据,指纹,牙齿,甚至DNA....... 然后用这些特征与数据建立关系。食品溯源通常使用贴标签和装盒子的方法,不会使用生物识别技术。
仍然存在的问题:
首先说说贴标签,这种方式并不能保证100%真实,揭开标签,转帖到其他商品上,理论上那个产品就是真实的。虽然有易碎纸技术,道高一尺魔高一丈,作假者有各种手段揭开标签。RFID/NFC理论上不可造假,实际上是可以了,只是作假成本的问题,是否值得。
在说说装箱,装瓶防伪以茅台为代表,虽然厂家可以使用一次性瓶子,盒子,箱子,制假者还是前面说的那句道高一尺魔高一丈。它们在茅台瓶子上打孔,采用真瓶装假酒的手法作假。
1.21.2.3. 安全问题
我将安全划分为六层,分别是:
+----------+-----------------------------+ | 实体层 | 物 | +----------+-----------------------------+ | 用户层 | 人 | +----------+-----------------------------+ | 网络层 | 网络 | +----------+-----------------------------+ | 应用层 | 操作系统,应用服务器 | +----------+-----------------------------+ | 业务逻辑层 | 功能,业务逻辑 | +----------+-----------------------------+ | 存储层 | 物理存储,硬盘 | +----------+-----------------------------+
并不是实施了区块链技术就安全无忧了,安全分为很多层,区块链只能做到网络层和存储层的安全。区块链无法解决用户层,应用层,逻辑层等安全问题,他只能保证存储在硬盘上的区块不被修改。
因为区块链仅仅能解决数据存储层的安全问题,不能保证上链的数据是真实的,上链前绝对不会被篡改;所以仅仅朔源,不考虑防伪是没有意义的,防伪仍然是重中之重。
1.21.2.4. 防伪问题
如何做防伪呢,这个领域很多公司已经探索多年,各种高科技应用,武装到牙齿,但仍没有解决假货问题。
区块链的出现很可能是一个突破,我们只需将现有成熟的防伪技术与区块链结合即可。
现在流行的访问技术太多了,我倾向于采用二维码,RFID,NFC技术,二维码与互联网紧密相连。
1.21.2.5. 性能问题
区块链目前的底层只适合做,低频高价值的业务。
区块链的读取性能通常是没有问题的,但是区块链的写入实际上无论你用多少个服务器节点都不能提升,因为写入区块需要做共识算法,这步操作,会在所有节点上进行,同时还需要加密运算,这些操作都是 CPU 密集型操作。所以写入操作是存在瓶颈的。
解决这个问题,我想出了几种方案:
性能解决方案
通过消息队列技术异步写入,将需要写入的区块放入队列,异步完成上链操作。
并行写入,我们可以建设多个区块链平台。多个平台同时服务于业务。
为了达到去中心化并行写入,我们将在客户端通过算法,匹配服务器。而不是在两个平台前面增加负载均衡。因为这样又回到了中心化系统。
1.21.2.6. 颗粒度问题
朔源的颗粒度问题,例如“红酒”的溯源,我们是将单位溯源做到箱呢?还是打,或是瓶呢?
我们用“四象限法则”分析
高价值 o | | o | 低频率 --------------+------------- 高频率 操作频率 | o | o | 低价值 物品价值
通过观察上面图,我们可以看到可以有四种情况,低频低价值,低频高价值,高频高价值,高频低价值
我认为对于低频高价值和高频高价值的业务,尽量做到最小颗粒度。
而对于低频低价值和高频低价值的业务,可以颗粒度更粗。
1.21.2.7. 存储规划
如果是高频低价值的业务,那么溯源数据源源将会不断的被添加到区块,以此同时区块的访问率极低。迟早会达到一个临界值。
所以你要规划存储,例如溯源数据的过期时间,对于 hyperledger 可以使用 DelState(key) 删除历史数据。
如果是高频高价值的业务是否要考虑永久保留数据呢?
这些问题都是需要考虑的。因为目前我们还不知道区块链的存储临界值。
1.21.2.8. 大数据问题
区块链替代不了数据库,它与数据库是互补关系。
对于低频的业务,通常传统数据库足以应付。那么对于高频操作的业务呢?暂时可能没有问题,但总有一天会遇到瓶颈。
综上所述,溯源项目数据库规划决不能少。同时还要考虑数据仓库和后期数据挖掘。因为用户使用微信或者我们的APP扫描二维码,我们可以获得很多有价值的数据。
手上没有 Vision 使用文本简单的绘制了一幅图
+------------------------+ | User -> QR Code | +------------------------+ | | V V +---------------+ +---------------+ +---------------+ | Search Engine |<-- | Microservice | | Microservice | +---------------+ +---------------+ +---------------+ | | +----------------------------------+ | | | | | V V V V +----------+ +------------+ +-------------+ | Database | | Big Data | | Blockchain | +----------+ +------------+ +-------------+ | MySQL | | Hadoop | | Hyperledger | | NoSQL | | Hive/Hbase | | Chaincode | +----------+ +------------+ +-------------+ | | ^ ^ | +------ ETL -----| | | | +----------- Message Queue ----------o
区块链之外的很多复杂的需求我们需要借助大数据系统和搜索技术。
区块链的弱点是无法做复杂的查询,这里我们会用到搜索引擎技术解决,实际上搜索引擎角色是给区块链做索引。
上图数据写入时,保存了四份,分别在搜索引擎,关系型数据库,数据仓库和区块的
具体怎么实现,有很多方式,这里就不讨论了,否则就跑题了。
1.21.2.9. BI商业智能
数据采集,大数据分析
溯源信息的查询是通过用户手机终端实现,有几种途径,微信扫二维码,APP扫二维码,微信小程序等等。
扫码的同时还可以收集用户数据,我们可以收集到很多有价值的数据,例如地理位置,手机号码,性别,年龄等等......
有了这些数据便可以挖掘出有价值的数据,甚至可以将数据提供给生产企业作参考。
传统销售数据只能跟踪到地域,也就是统计出地域销量,没法监控到最后一公里的数据,而我们主要是采集商品最后一公里的数据。
我们能做到用户消费后,呼叫中心立即跟进回访,还能在用户快用完商品是向用户推送促销信息,以及客服二次跟进。
大数据能做什么?
用户行为分析,用户的喜好,这些数据能为后面精准推送提供支持。
消费与地理分析的关系
年龄段与购买力的关系
区域产品的存量,例如:用户扫描了一次二维码,可能用户就已经使用了改产品。我们就知道该地区投放的1000件商品被消耗了意见。
性别与消费习惯
两次间隔消费时间
活跃用户和沉睡用户
1.21.2.10. 采集终端
溯源数据怎么录入呢?例如我们开发一个设备,二维码扫描枪,内置安卓系统。
我们不清楚他们的教育背景以及学习能力,所以设计原则是尽量傻瓜化,降低数据录入难度和学习难度,终端开机后互动教学,走一遍流程即可上手。
首先将溯源环节的每个节点通过后台事先写入数据库,接下来通过GIS地理信息系统匹配。
UUID -> 二维码 -> 设备扫描二维码激活-> 入数据库 -> 异步消息队列 -> 上链 > ---+ ^ | | | +------------------- 追加数据 -----------------+
终端会帮助用户欲录入信息,用户可以在信息基础上修改或者重写。同时终端支持图片,图像记录上传。
对于图片还能实现 EXIF 数据保存,包括图片描述信息,地理信息等等......
1.21.2.11. 多媒体数据
这里我们需要考虑是否需要记录多媒体数据,这里的多媒体指图像,声音,甚至3D扫描数据等等......
对于图片、音频与视频,我们可以将它集成到采集终端上,然后异步上传到去中心化的分布式文件系统中。
去中心化的分布式文件系统能实现,一张图片一个hash值,通过hash值访问图片,图片被同步到相邻节点实现去中心化,图片被修改hash值随之变化数据便无效。
1.21.2.12. 物流接口
使用物流单好通过物流公司提供的借口获得物流数据,然后写入到区块。
1.21.2.13. 如何激励用户
防伪技术做了,区块链溯源也做了,那么对于用户来说,他可能懒得去扫你的二维码,怎么办呢?
这里需要激励用户,怎样激励用户,我的方案是送代币。
首先代币不仅能够购买物品,还能交易,流通,形成一个小的商业闭环。其次目前代币已经泛滥 99% 可能是空气币,这里我们需要将代币的价值与物品对价,类似金本位/银本位。
怎样操作呢?例如一个代币等于一斤水果,无论代币怎样炒作,最终用户不想玩下去了,就来换水果,也可以是大米,食用油等等...
关于怎样使用代币来做积分系统请参考我的另一篇文章 《使用代币替代传统积分系统》 ,你可以在搜索引擎中找到
根据业务需要,可以发行布置一套币,例如水果币,流量币,话费币,每种币的功能不同,这些币可以在交易所中撮合交易,例如卖出水果币,换成流量币等等。
由于国家的法规问题,代币系统设计原则一定是代币只能用来购买商城中的物品,不能直接兑换成RMB,否则会触碰到国家的红线。但是通过交易所,币币之间兑换我们就控制不了了。
另外扫描二维码显示溯源防伪信息的同时我们有很多可以操作空间,可以获取用户地理位置,手机号码等等信息,为后面大数据分析埋点。
用户激励手段
分享激励
好评激励
用户等级激励
代币激励
用户排名,PK排行榜
成就勋章
身份标签,黄马甲:)
等等,手段众多,目的是让用户查询溯源信息,手机用户数据,鼓励代币消费等等.......
1.21.2.14. 上链
并不是所有数据都上链,哪些数据上链呢?
产地(出生、生长)、采购、加工(检疫、屠宰)、库存、运输、销售、配送等等......
1.21.2.15. 以太坊解决方案
我们设计一个简单的合约,模拟上面提到的解决方案
pragma solidity ^0.4.20; contract Trace { enum State { Origin, Factory, QA, Shipping, Received, Pending } string name; uint price; uint weight; bool lock = false; //合约锁 bool close = false; //合约状态 uint number = 1; uint attr_number = 1; mapping (address => string) guestbook; //客户留言本 struct Attribute { address owner; // 供应商 string name; // 属性的名字 string date; // 生产日期 string desc; // 描述信息 } mapping (uint => Attribute) attribute; struct Logistics { address owner; // 中转站 string date; // 转运日期 State status; // 状态 string message; // 留言信息 } mapping (uint => Logistics) stations; function Trace(string _name, uint _price, uint _weight) public { name = _name; price = _price; weight = _weight; } // 名称 function getName() public view returns(string){ return name; } // 价格 function getPrice() public view returns(uint){ return price; } // 重量 function getWeight() public view returns(uint){ return weight; } // 增加商品属性 function putAttribute(address _owner,string _name, string _date, string _desc ) public{ if(lock == false){ Attribute memory item = Attribute(_owner, _name,_date,_desc); attribute[attr_number] = item; attr_number = attr_number + 1; } } // 获得属性 function getAttribute(uint _attr_number) public view returns(address, string, string, string) { require(_attr_number < attr_number); Attribute memory item = attribute[_attr_number]; return (item.owner, item.name, item.date, item.desc); } // 增加物流中转信息 function putLogistics(address _owner,string _date, State _status, string _message ) public{ if(close == false){ Logistics memory node = Logistics(_owner,_date,_status,_message); stations[number] = node; number = number + 1; lock = true; } if (_status == State.Received) { close = true; } } // 获得中转信息 function getLogistics(uint _number) public view returns(address, string, State, string) { require(_number < number); Logistics memory node = stations[_number]; return (node.owner, node.date, node.status, node.message); } // 或者转中站数量 function getLogisticsCount() public view returns(uint){ return number; } // 客户留言 function addGuestbook(address _owner, string message) public{ guestbook[_owner] = message; } }
怎样使用这个合约呢?合约部署,需要输入三个参数,分别是名称,价格和装量
Trace(string _name, uint _price, uint _weight)
产品属性可以在出厂前设置,一旦出厂进入物流阶段就不允许在更改了。
1.21.2.15.1. 应用场景一
调用合约案例一,这是没有经过深加工的原产品案例。例如 Trace("山羊肉", 25, 50)
var contract; Trace.deployed().then(function(instance){contract=instance;}); contract.getName(); contract.putAttribute("0x627306090abab3a6e1400e9345bc60c78a8bef57","颜色", "", "黑色") contract.putAttribute("0x627306090abab3a6e1400e9345bc60c78a8bef57","产地", "", "内蒙古") contract.putAttribute("0x627306090abab3a6e1400e9345bc60c78a8bef57","出生", "2017-01-12", "XXX牧场") contract.putAttribute("0x627306090abab3a6e1400e9345bc60c78a8bef57","宰杀", "2018-02-12", "XXX宰杀") contract.putLogistics("0x627306090abab3a6e1400e9345bc60c78a8bef57","2018-02-20",0,"XXX牧场"); contract.putLogistics("0x627306090abab3a6e1400e9345bc60c78a8bef57","2018-02-20",1,"XXX屠宰公司"); contract.putLogistics("0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef","2018-02-22",2,"XXX检验检疫"); contract.putLogistics("0xf17f52151ebef6c7334fad080c5704d77216b732","2018-02-21",3,"XXX一级经销商"); contract.putLogistics("0x821aea9a577a9b44299b9c15c88cf3087f3b5544","2018-02-23",3,"XXX二级经销商"); contract.putLogistics("0x821aea9a577a9b44299b9c15c88cf3087f3b5544","2018-02-24",3,"XXX批发中心"); contract.putLogistics("0x821aea9a577a9b44299b9c15c88cf3087f3b5544","2018-02-25",3,"XXX超市"); contract.putLogistics("0x0d1d4e623d10f9fba5db95830f7d3839406c6af2","2018-02-26",4,"用户包裹收到"); contract.getNode(); // 获得物流经过的转运站数量
1.21.2.15.2. 应用场景二
调用合约案例二,这是深加工的产品案例。例如 Trace("牦牛肉干", 80, 500)
var contract; Trace.deployed().then(function(instance){contract=instance;}); contract.getName(); contract.putAttribute("0x627306090abab3a6e1400e9345bc60c78a8bef57","调和油", "2016-10-10", "银龙鱼牌") contract.putAttribute("0x627306090abab3a6e1400e9345bc60c78a8bef57","辣椒粉", "2016-10-30", "西藏XXX公司生产") contract.putAttribute("0x627306090abab3a6e1400e9345bc60c78a8bef57","生抽", "2016-01-12", "XXX生抽,XXX生产") contract.putAttribute("0x627306090abab3a6e1400e9345bc60c78a8bef57","山梨酸钾", "2017-02-12", "XXX生产") contract.putAttribute("0x627306090abab3a6e1400e9345bc60c78a8bef57","防腐剂", "2017-02-12", "XXX生产") contract.putAttribute("0x627306090abab3a6e1400e9345bc60c78a8bef57","牦牛肉", "2017-02-12", "XXX牧场") contract.putLogistics("0x627306090abab3a6e1400e9345bc60c78a8bef57","2018-02-20",0,"XXX牧场"); contract.putLogistics("0x627306090abab3a6e1400e9345bc60c78a8bef57","2018-02-20",1,"XXX公司生产"); contract.putLogistics("0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef","2018-02-22",2,"XXX通过QA、QC"); contract.putLogistics("0xf17f52151ebef6c7334fad080c5704d77216b732","2018-02-21",3,"XXX一级经销商"); contract.putLogistics("0x821aea9a577a9b44299b9c15c88cf3087f3b5544","2018-02-23",3,"XXX二级经销商"); contract.putLogistics("0x821aea9a577a9b44299b9c15c88cf3087f3b5544","2018-02-24",3,"XXX批发中心"); contract.putLogistics("0x821aea9a577a9b44299b9c15c88cf3087f3b5544","2018-02-25",3,"XXX超市"); contract.putLogistics("0x0d1d4e623d10f9fba5db95830f7d3839406c6af2","2018-02-26",4,"用户包裹收到"); contract.getNode(); // 获得物流经过的转运站数量
1.21.2.15.3. 用户留言
contract.addGuestbook("0x0d1d423e623d10f9d10f9d10f9d10f9d10f9fba5","东西好吃,下次还买,给好评");
1.21.2.16. Hyperledger 解决方案
由于家里在刷墙,服务器收起来了,没有开发环境,只能提供部分参考代码,无法提供合约完整代码,只是给大家一个思路,原理很上面以太坊的合约类似。
1.21.2.16.1. 溯源合约涉及
package main import "fmt" import "encoding/json" const ( Origin = iota // 0 Factory // 1 QA // 2 Shipping // 3 Received // 4 Pending // 5 Supermarket // 6 ) type structElement struct { Name string `json:"name"` Company string `json:"company"` Description string `json:"description"` } type structLogistics struct { Stations string `json:"stations"` // 中转站 Date string `json:"date"` // 转运日期 Status uint8 `json:"status"` // 状态 Message string `json:"message"` // 留言信息 } type Trace struct { Name string `json:"name"` Address string `json:"address"` Attribute map[string]string `json:"attribute"` Element []structElement `json:"element"` Logistics map[string]structLogistics `json:"logistics"` } func (trace *Trace) setName(_name string) { trace.Name = _name } func (trace *Trace) getName() string { return trace.Name } func (trace *Trace) putAttribute(_key string, _value string) { trace.Attribute[_key] = _value } func (trace *Trace) putLogistics(_key string, _value structLogistics) { trace.Logistics[_key] = _value } func main(){ trace := &Trace{ Name: "牦牛肉干", Address: "内蒙古呼和浩特", Attribute: map[string]string{}, Element: []structElement{structElement{Name:"塑料袋",Company: "XXX塑料制品有限公司", Description: "外包装"},structElement{Name:"辣椒粉",Company: "XXX调味品有限公司", Description: "采摘年份2016-10-10"},structElement{Name:"调和油",Company: "XXX调味品有限公司", Description: "生产日期2016-10-10"}}, Logistics: map[string]structLogistics{}} trace.putAttribute("Color","Red") trace.putAttribute("Size","10") trace.putAttribute("Weight","100kg") trace.putLogistics("1", structLogistics{"呼和浩特","2016-10-15", Origin, "牦牛收购"}) trace.putLogistics("2", structLogistics{"呼和浩特","2016-10-18", Factory, "牦牛宰杀"}) trace.putLogistics("3", structLogistics{"呼和浩特","2016-10-15", QA, "经过质检"}) trace.putLogistics("4", structLogistics{"北京市","2016-10-15", Shipping, "运输中"}) trace.putLogistics("5", structLogistics{"杭州市","2016-10-15", Shipping, "XXX冷库"}) trace.putLogistics("5", structLogistics{"深圳市","2016-10-15", Supermarket, "XXX超市"}) trace.putLogistics("5", structLogistics{"龙华区","2016-10-15", Received, "用户签收"}) traceJson, _ := json.Marshal(trace) fmt.Println(string(traceJson)) }
1.21.2.16.1.1. 食品安全朔源
trace := &Trace{ Name: "牦牛肉干", Address: "内蒙古呼和浩特", Attribute: map[string]string{}, Element: []structElement{structElement{Name:"塑料袋",Company: "XXX塑料制品有限公司", Description: "外包装"},structElement{Name:"辣椒粉",Company: "XXX调味品有限公司", Description: "采摘年份2016-10-10"},structElement{Name:"调和油",Company: "XXX调味品有限公司", Description: "生产日期2016-10-10"}}, Logistics: map[string]structLogistics{}} trace.putAttribute("Color","Red") trace.putAttribute("Size","10") trace.putAttribute("Weight","100kg") trace.putLogistics("1", structLogistics{"呼和浩特","2016-10-15", Origin, "牦牛收购"}) trace.putLogistics("2", structLogistics{"呼和浩特","2016-10-18", Factory, "牦牛宰杀"}) trace.putLogistics("3", structLogistics{"呼和浩特","2016-10-15", QA, "经过质检"}) trace.putLogistics("4", structLogistics{"北京市","2016-10-15", Shipping, "运输中"}) trace.putLogistics("5", structLogistics{"杭州市","2016-10-15", Shipping, "XXX冷库"}) trace.putLogistics("5", structLogistics{"深圳市","2016-10-15", Supermarket, "XXX超市"}) trace.putLogistics("5", structLogistics{"龙华区","2016-10-15", Received, "用户签收"})
1.21.2.16.1.2. 水平移植
这个方案可以水平移植到其他领域,例如 药品安全溯源
trace := &Trace{ Name: "强身大力丸", Address: "深圳是XXX制药有限公司", Attribute: map[string]string{}, Element: []structElement{ structElement{Name:"枸杞",Company: "宁夏XXX农业有限公司", Description: "采摘年份2016-10-10,10g"}, structElement{Name:"茯苓",Company: "河南XXX农业有限公司", Description: "采摘年份2016-10-10,20kg"}, structElement{Name:"XXX",Company: "XXX有限公司", Description: "生产日期2016-10-10"}, structElement{Name:"XXX",Company: "XXX有限公司", Description: "生产日期2016-10-10"}, ... ... structElement{Name:"塑料包装",Company: "XXX有限公司", Description: "生产日期2016-10-10"}, structElement{Name:"包装盒",Company: "XXX有限公司", Description: "生产日期2016-10-10"} }, Logistics: map[string]structLogistics{}} trace.putAttribute("Color","Red") trace.putAttribute("Size","10") ... ... trace.putAttribute("Weight","100kg") trace.putLogistics("1", structLogistics{"呼和浩特","2016-10-15", Origin, "原材料...."}) trace.putLogistics("2", structLogistics{"呼和浩特","2016-10-18", Factory, "生产...."}) trace.putLogistics("3", structLogistics{"呼和浩特","2016-10-15", QA, "经过质检"}) trace.putLogistics("3", structLogistics{"XXX市药品监督局","2016-10-15", QA, "经过质检"}) trace.putLogistics("4", structLogistics{"北京市","2016-10-15", Shipping, "运输中"}) trace.putLogistics("5", structLogistics{"杭州市","2016-10-15", Shipping, "XXX冷库"}) trace.putLogistics("5", structLogistics{"深圳市","2016-10-15", Supermarket, "XXX超市"}) trace.putLogistics("5", structLogistics{"龙华区","2016-10-15", Received, "用户签收"})
合约落地,还需要做一些调整已适应实际场景。但基本思路是通的。
1.21.2.16.2. 积分通正(代币)
我发现用以太坊思维,将以太坊代币合约搬到 hyperledger 上,一样可以实现代币的功能,这个代币除了不能上交易所,基本满足我们替代积分系统的需求,下面是我写了这样一个合约,在超级账本上实现类似以太坊的代币转账功能。
package main import ( "bytes" "encoding/json" "fmt" "strconv" "github.com/hyperledger/fabric/core/chaincode/shim" sc "github.com/hyperledger/fabric/protos/peer" ) // Define the Smart Contract structure type SmartContract struct { } type Token struct { Owner string `json:"Owner"` TotalSupply uint `json:"TotalSupply"` TokenName string `json:"TokenName"` TokenSymbol string `json:"TokenSymbol"` BalanceOf map[string]uint `json:"BalanceOf"` } func (token *Token) initialSupply(){ token.BalanceOf[token.Owner] = token.TotalSupply; } func (token *Token) transfer (_from string, _to string, _value uint){ if(token.BalanceOf[_from] >= _value){ token.BalanceOf[_from] -= _value; token.BalanceOf[_to] += _value; } } func (token *Token) balance (_from string) uint{ return token.BalanceOf[_from] } func (token *Token) burn(_value uint) { if(token.BalanceOf[token.Owner] >= _value){ token.BalanceOf[token.Owner] -= _value; token.TotalSupply -= _value; } } func (token *Token) burnFrom(_from string, _value uint) { if(token.BalanceOf[_from] >= _value){ token.BalanceOf[_from] -= _value; token.TotalSupply -= _value; } } func (token *Token) mint(_value uint) { token.BalanceOf[token.Owner] += _value; token.TotalSupply += _value; } func (s *SmartContract) Init(stub shim.ChaincodeStubInterface) sc.Response { return shim.Success(nil) } func (s *SmartContract) initLedger(stub shim.ChaincodeStubInterface) sc.Response { token := &Token{ Owner: "netkiller", TotalSupply: 10000, TokenName: "代币通正", TokenSymbol: "COIN", BalanceOf: map[string]uint{}} token.initialSupply() tokenAsBytes, _ := json.Marshal(token) stub.PutState("Token", tokenAsBytes) fmt.Println("Added", tokenAsBytes) return shim.Success(nil) } func (s *SmartContract) transferToken(stub shim.ChaincodeStubInterface, args []string) sc.Response { if len(args) != 3 { return shim.Error("Incorrect number of arguments. Expecting 2") } tokenAsBytes, _ := stub.GetState(args[0]) token := Token{} json.Unmarshal(tokenAsBytes, &token) token.transfer(args[1],args[2],args[3]) tokenAsBytes, _ = json.Marshal(token) stub.PutState(args[0], tokenAsBytes) return shim.Success(nil) } func (s *SmartContract) balanceToken(stub shim.ChaincodeStubInterface, args []string) sc.Response { if len(args) != 1 { return shim.Error("Incorrect number of arguments. Expecting 1") } tokenAsBytes, _ := stub.GetState(args[0]) token := Token{} json.Unmarshal(tokenAsBytes, &token) amount := token.balance(args[1]) return shim.Success(amount) } func (s *SmartContract) Invoke(stub shim.ChaincodeStubInterface) sc.Response { // Retrieve the requested Smart Contract function and arguments function, args := stub.GetFunctionAndParameters() // Route to the appropriate handler function to interact with the ledger appropriately if function == "balanceToken" { return s.balanceToken(stub, args) } else if function == "initLedger" { return s.initLedger(stub) } else if function == "transferToken" { return s.transferToken(stub, args) } return shim.Error("Invalid Smart Contract function name.") } // The main function is only relevant in unit test mode. Only included here for completeness. func main() { // Create a new Smart Contract err := shim.Start(new(SmartContract)) if err != nil { fmt.Printf("Error creating new Smart Contract: %s", err) } }
合约代码的测试
func main(){ token := &Token{ Owner: "netkiller", // 代币管理者 TotalSupply: 10000, // 代币发行总量 TokenName: "积分连", // 代币名称 TokenSymbol: "NEO", // 代币符号 NEO BalanceOf: map[string]uint{}} token.initialSupply() // 初始化代币 fmt.Println(token.balance("netkiller")) // 查询余额 token.transfer("netkiller","neo", 100) // 转账,这里账号使用用户ID,没有使用以太坊钱包那样的哈希值,因为哈希值不便于记忆。 fmt.Println(token.balance("netkiller")) fmt.Println(token.balance("neo")) }
我们可以建立很多套这样的比,例如水果币,蔬菜币,流量币...
开发一个小型交易所难度也不大,让用户在交易所中交易这些币。
1.21.2.17. 总结
区块链技术不能彻底解决食品的防伪和溯源问题,但是他能增加造假的难度。目前还不能解决源头造假的问题。
举例阳澄湖大闸蟹怎么用区块链造假,首先将外地蟹运往阳澄湖洗澡。将螃蟹打捞上来,期间记录过程,拍摄视频,然后贴上二维码,RFID,NFC标签,在装上有防伪的箱子(带有IoT技术,开箱需要扫码,中间运输过程中一旦开启时将上报公司,且箱子是一次性的,开启后无法在还原,只能报废),同时也给物流公司的集装箱上了电子锁,电子锁有记录GPS轨迹的功能,还有加速度传感器,被开启或中途长时间停车都会通过 IoT 技术通知公司。武装到牙齿了吧!!!
一切就绪后,准备防伪证书,视频,产品信息,将这些数据上链。发货,记录供应链数据,物流数据,GPS行车轨迹,中转站。最后到大商超,上架。所有数据收集完毕,更新区块链,追加供应链数据。
现在用户下载溯源APP或小程序,扫码或NFC可以看到溯源数据和整个流程的视频记录。哇真实,可信。同时扫码还有区块链积分的激励。
整个过程,除了大闸蟹是假的,其他环节全是真实的。最终所谓的区块链溯源仅仅成为了一种营销工具和手段。它并不能保证物品是真实性。
同理,黑龙江五常大米,福建武夷山茶叶也可以用类似方法造假。防伪技术解决了中间环节的安全问题。例如中间调包,但是源头控制不了。
真正解决食品安全问题需要靠法制和公民的道德。这是一个礼崩乐坏,底层互害的社会,日本不需要任何区块链溯源,即使是落后的泰国也不需要什么区块链的加持来保证食品安全。
1.21.3. 以太坊·电影院场景区块链应用探索
最近一直在思考区块链在各种场景下的落地问题。
下面是电影院场景区块链应用探索,这是我的一个设想,区块链如何在院线场景落地的一些思路。
为此我写一个这样的智能合约,实现了构造方法描述一部电影票价,坐位数量,可以实现订票,扣款,退票,还款等功能。
1.21.3.1. 合约文件
pragma solidity ^0.4.21; // author: netkiller // home: http://www.netkiller.cn // QQ:13721218 contract Movie { address public publisher; //电影院 string name; //影片名称 uint price; //票价 uint public seat; //坐位数量 mapping (address => uint) public audience; bool play = false; //电影是否已经开播,开播后不允许买票和退票。 //合约构造方法 function Movie(string _name, uint _price, uint _seat) public{ publisher = msg.sender; name = _name; price = _price; seat = _seat; } //获取剩余坐位数量 function getName() public view returns (string){ return name; } //有时需要开放预留坐位,调整坐位数量 function changeSeat(uint _seat) public { if (msg.sender != publisher) { return; } if (play == true){ return; } seat = _seat; } //获取剩余坐位数量 function getSeat() public view returns (uint){ return seat; } //买票方法,参数买票者,票数,买票后扣除用户以太币。 function buyTicket(address _audience, uint _ticket) public payable returns (bool success) { if (_ticket >= seat) { return false; } if (play == true){ return false; } uint amount = price * _ticket; //计算票价 if (this.balance >= amount) { _audience.transfer(_audience.balance - amount); publisher.transfer(publisher.balance + amount); audience[_audience] = _ticket; seat -= _ticket; } return true; } //退票 function refundTicket(address _audience, uint _ticket) public { if (msg.sender != publisher) { return; } if (play == true){ return; } uint amount = price * _ticket; if (audience[_audience] <= _ticket) { if (publisher.balance >= amount) { _audience.transfer(_audience.balance + amount); publisher.transfer(publisher.balance - amount); audience[_audience] -= _ticket; seat += _ticket; } } } //播放电影,锁定 function playMovie() public { play = true; } //销毁合约 function destroy() public{ if (msg.sender == publisher) { selfdestruct(publisher); } } }
1.21.3.2. 合约用法
例如现在要上映一部影片步骤是, 首先实例化合约,然后部署合约
Movie("黑客帝国", "25", 80)
var contract; Movie.deployed().then(function(instance){contract=instance;}); contract.getName(); //获得影片名字 contract.buyTicket("0x627306090abab3a6e1400e9345bc60c78a8bef57",1) // 购买 1 张票 contract.buyTicket("0x627306090abab3a6ebc60c78a8bef571400e9345",5) // 购买 5 张票 contract.buyTicket("0xf17f52151EbEF6C7334FAD080c5704D77216b732",2) // 购买 2 张票 ... ... contract.refundTicket("0x627306090abab3a6ebc60c78a8bef571400e9345", 2 ) // 退 2 张票 contract.audience.call().then(console.log); contract.playMovie() // 电影开播,锁定这个合约
1.21.4. 游戏领域区块链探索
如何将区块链嫁接到游戏领域,我做了很多思考,经过分析总结,发现下面几项内容非常适合上链。
上链内容
积分代币
如果说区块链应用于游戏领域,可能99%的人首先会想到是代币,的确游戏领域实施区块链连,代币必不可少。但是区块链不等于代币。
游戏装备
人物属性
关卡任务
下面我们要思考为什么需要将游戏数据放到区块链上,玩游戏的人都知道私服,私人架设游戏服务器,私服上玩游戏遇到最大的问题就是公平性。管理员可以随意调整服务器参数。
私服存在哪些问题呢?
修改游戏装备属性
修改生命与魔法值
关卡参数
人物属性
随意封账号
这是我们在私服上遇到的最大问题,那么官方服务器就公平吗?不一定,对于弱势的玩家只能相信游戏公司的承诺。
有了区块链技术,我们能做什么呢?例如我们将用户装备数据等数据上链,这样保证了装备永远属于玩家
区块链能做什么?
“点” 奖励采用代币实现,可以实现流通,兑换,消费等等......
爆出装备立即上链
用户等级属性上链
用户状态上链
关卡数据上链
了凸显公平性,我们采用公链,查询用户数据可以使用接口,也可以直接到公链上查询。
下面详细讲解具体怎么实现。
1.21.4.1. 游戏代币
传统币 Point (点) 仅仅是一个数字,数字存在数据库中,例如
Username | Point (Integer) ----------------------- Neo | 1000 Jam | 500
因为仅仅是一个数字,管理员可以随意修改,黑客也可随意修改,例如
update member set point = 1000000000000 where username = 'Neo'
瞬间就有 1000000000000 点。由于是在数据库中修改,没有日志,不知道谁操作的,可能是开发人员,可以是管理员,也可能是黑客。
如何消费“点呢”,例如消费 100 个点:
update member set point = point - 100 where username = 'Neo'
传统币“点”,只是一个数字做加法和减法运算,安全性主要依赖于开发团队的能(期望别出BUG),运维团队的能力(被别黑客攻击),以及DBA(数据库管理员)的节操。
审计全靠开发人员打印出的日志。
现在我们再看看数字货币,跟很多朋友聊天中发现,他们还没有理解什么是币,他们认为数字代币花掉就没了(消失了),然后通过挖矿不停的产生新币,这种理解完全错误。
数字币是这样运作的,首先发行时设置了币的总量例如 1000000,然后将所有代币存入发行者账号,例如 account 1
account | coin --------------------------------- account1 | 1000000 account2 | 0 account3 | 0 account4 | 0 account5 | 0
现在 account2 游戏在线1小时奖励 10 个币,系统从账号account1转账5个币给 account2
account | coin --------------------------------- account1 | 999990 account2 | 10 account3 | 0 account4 | 0 account5 | 0
以此类推,从 account1 转账给其他账号。
account | coin --------------------------------- account1 | 999960 account2 | 10 account3 | 10 account4 | 10 account5 | 10
现在 account3 消费 5个币买了装备。从 account3 转 5 个币到 account1
account | coin --------------------------------- account1 | 999965 account2 | 10 account3 | 5 account4 | 10 account5 | 10
现在你应该看懂了把,代币是流通的,总量是不变的。account1 账号负责币的发行,回收等等工作。
同时任何转账将产生区块,历史数据永久记录。
下面是一个高级代币合约,地址 https://github.com/ibook/NetkillerAdvancedToken
pragma solidity ^0.4.20; /******************************************/ /* Netkiller ADVANCED TOKEN */ /******************************************/ /* Author netkiller <netkiller@msn.com> */ /* Home http://www.netkiller.cn */ /* Version 2018-03-05 */ /* Version 2018-03-06 - Add Global lock */ /******************************************/ interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public; } contract NetkillerAdvancedToken { address public owner; // Public variables of the token string public name; string public symbol; uint8 public decimals = 2; // 18 decimals is the strongly suggested default, avoid changing it uint256 public totalSupply; uint256 public sellPrice; uint256 public buyPrice; // This creates an array with all balances mapping (address => uint256) public balanceOf; mapping (address => mapping (address => uint256)) public allowance; // This generates a public event on the blockchain that will notify clients event Transfer(address indexed from, address indexed to, uint256 value); // This notifies clients about the amount burnt event Burn(address indexed from, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); mapping (address => bool) public frozenAccount; /* This generates a public event on the blockchain that will notify clients */ event FrozenFunds(address target, bool frozen); bool lock = true; /** * Constrctor function * * Initializes contract with initial supply tokens to the creator of the contract */ function NetkillerAdvancedToken( uint256 initialSupply, string tokenName, string tokenSymbol ) public { owner = msg.sender; totalSupply = initialSupply * 10 ** uint256(decimals); // Update total supply with the decimal amount balanceOf[msg.sender] = totalSupply; // Give the creator all initial tokens name = tokenName; // Set the name for display purposes symbol = tokenSymbol; // Set the symbol for display purposes } modifier onlyOwner { require(msg.sender == owner); _; } modifier isLock { require(!lock); _; } function setLock(bool _lock) onlyOwner { lock = _lock; } function transferOwnership(address newOwner) onlyOwner public { owner = newOwner; } /* Internal transfer, only can be called by this contract */ function _transfer(address _from, address _to, uint _value) isLock internal { require (_to != 0x0); // Prevent transfer to 0x0 address. Use burn() instead require (balanceOf[_from] >= _value); // Check if the sender has enough require (balanceOf[_to] + _value > balanceOf[_to]); // Check for overflows require(!frozenAccount[_from]); // Check if sender is frozen require(!frozenAccount[_to]); // Check if recipient is frozen balanceOf[_from] -= _value; // Subtract from the sender balanceOf[_to] += _value; // Add the same to the recipient Transfer(_from, _to, _value); } /** * Transfer tokens * * Send `_value` tokens to `_to` from your account * * @param _to The address of the recipient * @param _value the amount to send */ function transfer(address _to, uint256 _value) public { _transfer(msg.sender, _to, _value); } /** * Transfer tokens from other address * * Send `_value` tokens to `_to` in behalf of `_from` * * @param _from The address of the sender * @param _to The address of the recipient * @param _value the amount to send */ function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { require(_value <= allowance[_from][msg.sender]); // Check allowance allowance[_from][msg.sender] -= _value; _transfer(_from, _to, _value); return true; } /** * Set allowance for other address * * Allows `_spender` to spend no more than `_value` tokens in your behalf * * @param _spender The address authorized to spend * @param _value the max amount they can spend */ function approve(address _spender, uint256 _value) public returns (bool success) { allowance[msg.sender][_spender] = _value; Approval(msg.sender, _spender, _value); return true; } /** * Set allowance for other address and notify * * Allows `_spender` to spend no more than `_value` tokens in your behalf, and then ping the contract about it * * @param _spender The address authorized to spend * @param _value the max amount they can spend * @param _extraData some extra information to send to the approved contract */ function approveAndCall(address _spender, uint256 _value, bytes _extraData) public returns (bool success) { tokenRecipient spender = tokenRecipient(_spender); if (approve(_spender, _value)) { spender.receiveApproval(msg.sender, _value, this, _extraData); return true; } } /** * Destroy tokens * * Remove `_value` tokens from the system irreversibly * * @param _value the amount of money to burn */ function burn(uint256 _value) onlyOwner public returns (bool success) { require(balanceOf[msg.sender] >= _value); // Check if the sender has enough balanceOf[msg.sender] -= _value; // Subtract from the sender totalSupply -= _value; // Updates totalSupply Burn(msg.sender, _value); return true; } /** * Destroy tokens from other account * * Remove `_value` tokens from the system irreversibly on behalf of `_from`. * * @param _from the address of the sender * @param _value the amount of money to burn */ function burnFrom(address _from, uint256 _value) onlyOwner public returns (bool success) { require(balanceOf[_from] >= _value); // Check if the targeted balance is enough require(_value <= allowance[_from][msg.sender]); // Check allowance balanceOf[_from] -= _value; // Subtract from the targeted balance allowance[_from][msg.sender] -= _value; // Subtract from the sender's allowance totalSupply -= _value; // Update totalSupply Burn(_from, _value); return true; } /// @notice Create `mintedAmount` tokens and send it to `target` /// @param target Address to receive the tokens /// @param mintedAmount the amount of tokens it will receive function mintToken(address target, uint256 mintedAmount) onlyOwner public { balanceOf[target] += mintedAmount; totalSupply += mintedAmount; Transfer(0, this, mintedAmount); Transfer(this, target, mintedAmount); } /// @notice `freeze? Prevent | Allow` `target` from sending & receiving tokens /// @param target Address to be frozen /// @param freeze either to freeze it or not function freezeAccount(address target, bool freeze) onlyOwner public { frozenAccount[target] = freeze; FrozenFunds(target, freeze); } /// @notice Allow users to buy tokens for `newBuyPrice` eth and sell tokens for `newSellPrice` eth /// @param newSellPrice Price the users can sell to the contract /// @param newBuyPrice Price users can buy from the contract function setPrices(uint256 newSellPrice, uint256 newBuyPrice) onlyOwner public { sellPrice = newSellPrice; buyPrice = newBuyPrice; } /// @notice Buy tokens from contract by sending ether function buy() payable public { uint amount = msg.value / buyPrice; // calculates the amount _transfer(this, msg.sender, amount); // makes the transfers } /// @notice Sell `amount` tokens to contract /// @param amount amount of tokens to be sold function sell(uint256 amount) public { require(this.balance >= amount * sellPrice); // checks if the contract has enough ether to buy _transfer(msg.sender, this, amount); // makes the transfers msg.sender.transfer(amount * sellPrice); // sends ether to the seller. It's important to do this last to avoid recursion attacks } function transfer(address _to, uint256 _value, bytes _data) public returns (bool) { require(_to != address(this)); transfer(_to, _value); require(_to.call(_data)); return true; } function transferFrom(address _from, address _to, uint256 _value, bytes _data) public returns (bool) { require(_to != address(this)); transferFrom(_from, _to, _value); require(_to.call(_data)); return true; } function approve(address _spender, uint256 _value, bytes _data) public returns (bool) { require(_spender != address(this)); approve(_spender, _value); require(_spender.call(_data)); return true; } }
这个代币合约实现了,增发,减持,全局锁,账号冻结/解冻 等等功能。
1.21.4.2. 玩家属性与游戏装备
下面的合约实现了游戏玩家上链,上链信息有玩家属性,例如肤色,眼睛,头发,血统等等。身上的穿戴物品包括武器等等。
pragma solidity ^0.4.20; contract Player { address public owner; string name; bool lock = false; //合约锁 uint number = 1; uint attr_number = 1; mapping (address => string) guestbook; //客户留言本 struct Attribute { string key; // 属性的名字 string value; // 属性值 } mapping (uint => Attribute) attribute; struct Wear { string name; // 装备名 string desciption; // 信息 string attribute; // 属性,存储json 数据。例如生命+10,魔法+5,冰冻系... } mapping (uint => Wear) wear; function Player(string _name) public { name = _name; } modifier onlyOwner { require(msg.sender == owner); _; } // 名称 function getName() public view returns(string){ return name; } function setLock(bool _lock) onlyOwner public { lock = _lock; } // 增加人物属性,例如肤色,眼睛,头发等等 function putAttribute(string _key, string _value) onlyOwner public{ if(lock == false){ Attribute memory item = Attribute(_key, _value); attribute[attr_number] = item; attr_number = attr_number + 1; } } // 获得属性 function getAttribute(uint _attr_number) public view returns(string, string) { require(_attr_number < attr_number); Attribute memory item = attribute[_attr_number]; return (item.key, item.value); } // 增加装备信息,穿戴物品,武器, function putWear(string _name, string _description, string _attribute ) onlyOwner public{ if(lock == false){ Wear memory node = Wear(_name,_description,_attribute); wear[number] = node; number = number + 1; lock = true; } } // 获得信息 function getWear(uint _number) public view returns(string, string, string) { require(_number < number); Wear memory item = wear[_number]; return (item.name, item.desciption, item.attribute); } // 数量 function getWearCount() public view returns(uint){ return number; } // 客户留言 function addGuestbook(address _owner, string message) onlyOwner public{ guestbook[_owner] = message; } }
1.21.4.3. 装备属性与规范
假设我们开发一个游戏平台,很多厂商可以在这个平台上出售游戏。
例如屠龙宝刀只有一把,但是实际情况只要能赚钱,游戏厂商可以卖给了10个玩家,甚至更多。
为了公平起见,对于稀有的装备管理,我们要求游戏厂商在平台上备案。包括装备属性,应该归谁所有等等
1.21.4.4. 物品合成计算
区块链还可用于物品合成计算或者叫炼金术等等
很早的时候玩《暗黑破坏神III》 里面已一个盒子,放入符文,可以根据公式合成其他属性的符文,我任务这个需求可以使用区块链来完成。
另外在我玩XBOX游戏《巫师3》 中的炼金术,铸造,药水合成等等,我逗人都可以使用区块链完成。
1.21.4.5. 实施步骤
如果着手一个游戏项目上链,我们需要怎么做呢?
上链步骤
收集需求,收集公司的内部上链需求,听取所有部门的建议和诉求。
收集内容例如,代币发行量多少?代币小数位数,代币名称,是否会增发,是否需要冻结,代币怎样流通,怎样回收
Dapp 的 UI 设计,各种功能流程
分析需求,因为需求来自各种部门,各种岗位等等,他们不一定从事需求分析工作,所以我们需求对他们的需求过滤,分析,然后给出初步的PRD文档(产品需求文档)
根据收集的需求设计合约和Dapp
根据需求设计Dapp
系统架构设计,软件架构设计,技术选型;需要考虑扩展性,灵活性,并发设计,数据安全,部署,后期监控,各种埋点(统计、监控)
准备环境,我们需要三个环境,开发,测试,生产(运营)。
项目启动
运维部门准备环境,开始建设监控系统
开发部门开发合约和Dapp
测试部门准备测试用例,测试环境
测试
Alpha 阶段,将合约部署到测试环境,测试合约的每个函数的工作逻辑,确保无误。因为合约一旦部署将不能更改,只能废弃使用新的合约,所以这个测试步骤必须重视。
Beta 阶段,将测试合约部署到测试网,例如 Ropsten ,可以邀请公司内部测试
部署生产环境
部署合约,将合约部署到主网
Dapp 部署到生产环境。
验收测试,在生产环境做最后验收测试
代币上交易所
1.21.5. 以太坊竞猜活动区块链探索
合约实现了报名,退出,参加人数控制,竞猜次数控制,公布答案,获奖名单等等功能
pragma solidity ^0.4.20; // Author netkiller<netkiller@msn.com> // Home http://www.netkiller.cn contract Guess { address public owner; string name; //活动名称 bool start = false; //合约锁 uint number; //参赛人数统计 uint public quota; //名额限定 mapping (address => string) public registrantsPaid; //参加活动 uint maxCounter = 3; //最大竞猜次数 mapping (address => uint) counter; //竞猜次数统计 string public question; //竞猜问题 mapping (uint => string) public options; //选项 // 答案结构 struct Answer { address player; uint answer; } mapping (uint => Answer) public answer; //答案 uint answerIndex = 0; //公布最终答案 uint public expose; //获奖名单 mapping (address => uint) winner; function Guess(string _name, uint _quota) public { name = _name; quota = _quota; number = 0; } modifier onlyOwner { require(msg.sender == owner); _; } // 获取活动名称 function getName() public view returns(string){ return name; } function setStatus(bool _start) onlyOwner public { start = _start; } function setQuestion(string _question) public { question = _question; } // 增加人物属性,例如肤色,眼睛,头发等等 function putOptions(uint _key, string _value) onlyOwner public{ if(start == false){ options[_key] = _value; } } function join(string _password) onlyOwner public returns (bool success) { require(start == ture); if (number >= quota) { return false; } registrantsPaid[msg.sender] = _password; number++; return true; } function changeQuota(uint _quota) onlyOwner public { quota = _quota; } function quit() onlyOwner public { require(start == false); //require (registrantsPaid[msg.sender] == _password); delete registrantsPaid[msg.sender]; number--; } function setGuess(uint _answer) public{ require(start == ture); if(maxCounter > counter[msg.sender]){ counter[msg.sender]++; answer[answerIndex] = Answer(msg.sender, _answer); answerIndex++; } } //揭秘答案 function setExpose(uint _expose) onlyOwner public { require(start == ture); expose = _expose; for(uint i=0;i<answerIndex;i++) { Answer memory ans = answer[i]; if(ans.answer == expose){ winner[ans.player] = ans.answer; } } } // 数量 function getCount() public view returns(uint){ return number; } }
1.21.6. 使用代币替代传统积分系统
首先我们使用代币是为了取代传统的积分机制。因为代币的“币”特性能够实现流通,交易等等,实现一个闭环生态系统。而传统的积分只能内部使用,无法流通,外接不承认加分的价值,积分无法交易,流通。
其次我们并非是为了ICO炒作,然后割韭菜,代币取代积分是刚性需求。
这里假设您已经看我我之前写的文章,知道什么是代币,并且能用钱包部署代币合约。例如现在合约已经部署到主网,已经可以使用钱包转账代币,甚至代币已经上了交易所,接下来我们要做什么呢?
接下来的工作是将代币和网站或者手机App打通,只有将代币整合到现有业务场景中代币才有意义。
1.21.6.1. 规划
1.21.6.1.1. 账号规划
我认为我们需要下面几种角色的账号
代币发行账号,负责发行代币,管理代币
收款账号,用户代币流通中的收款,这个账号应该由财务人员负责,相当于企业对公账号。
交易所账号,用来对接交易所
用户账号,普用户的账号,依赖接受代币,消费代币,交易代币。
1.21.6.1.2. 日志规划
日志
开户日志
绑定日志
送币日志
商城日志,代币转账日志
1.21.6.1.3. 监控规划
监控
监控内容
额度监控,监控账号额度变化
交易监控,监控任何一笔交易
异常监控
1.21.6.1.4. 代币构成规划
例如总量发行1亿,1亿代币怎样构成的?
代币构成
1000万是空投赠送给 XXX 个用户
3000万作为福利发给公司员工
3000万放在交易
3000万放在网站流通
1.21.6.2. 实施步骤
如果着手一个游戏项目上链,我们需要怎么做呢?
上链步骤
收集需求,收集公司的内部上链需求,听取所有部门的建议和诉求。
收集内容例如,代币发行量多少?代币小数位数,代币名称,是否会增发,是否需要冻结,代币怎样流通,怎样回收
Dapp 的 UI 设计,各种功能流程
分析需求,因为需求来自各种部门,各种岗位等等,他们不一定从事需求分析工作,所以我们需求对他们的需求过滤,分析,然后给出初步的PRD文档(产品需求文档)
根据收集的需求设计合约和Dapp
根据需求设计Dapp
系统架构设计,软件架构设计,技术选型;需要考虑扩展性,灵活性,并发设计,数据安全,部署,后期监控,各种埋点(统计、监控)
准备环境,我们需要三个环境,开发,测试,生产(运营)。
项目启动
运维部门准备环境,开始建设监控系统
开发部门开发合约和Dapp
测试部门准备测试用例,测试环境
测试
Alpha 阶段,将合约部署到测试环境,测试合约的每个函数的工作逻辑,确保无误。因为合约一旦部署将不能更改,只能废弃使用新的合约,所以这个测试步骤必须重视。
Beta 阶段,将测试合约部署到测试网,例如 Ropsten ,可以邀请公司内部测试
部署生产环境
部署合约,将合约部署到主网
Dapp 部署到生产环境。
验收测试,在生产环境做最后验收测试
代币上交易所
1.21.6.3. ERC20 代币合约
合约部署这里加就不介绍了,可以参考《Netkiller Blockchain 手札》
合约地址:https://raw.githubusercontent.com/ibook/TokenERC20/master/contracts/TokenERC20.sol
这个合约提供增发,冻结,所有权转移等等功能。
pragma solidity ^0.4.21; interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public; } contract TokenERC20 { address public owner; // Public variables of the token string public name; string public symbol; uint8 public decimals = 18; // 18 decimals is the strongly suggested default, avoid changing it uint256 public totalSupply; // This creates an array with all balances mapping (address => uint256) public balanceOf; mapping (address => mapping (address => uint256)) public allowance; // This generates a public event on the blockchain that will notify clients event Transfer(address indexed from, address indexed to, uint256 value); // This notifies clients about the amount burnt event Burn(address indexed from, uint256 value); mapping (address => bool) public frozenAccount; event FrozenFunds(address target, bool frozen); /** * Constrctor function * * Initializes contract with initial supply tokens to the creator of the contract */ function TokenERC20( uint256 initialSupply, string tokenName, string tokenSymbol ) public { owner = msg.sender; totalSupply = initialSupply * 10 ** uint256(decimals); // Update total supply with the decimal amount balanceOf[msg.sender] = totalSupply; // Give the creator all initial tokens name = tokenName; // Set the name for display purposes symbol = tokenSymbol; // Set the symbol for display purposes } modifier onlyOwner { require(msg.sender == owner); _; } /** * Internal transfer, only can be called by this contract */ function _transfer(address _from, address _to, uint _value) internal { // Prevent transfer to 0x0 address. Use burn() instead require(_to != 0x0); // Check if the sender has enough require(balanceOf[_from] >= _value); // Check for overflows require(balanceOf[_to] + _value > balanceOf[_to]); // Save this for an assertion in the future uint previousBalances = balanceOf[_from] + balanceOf[_to]; // Subtract from the sender balanceOf[_from] -= _value; // Add the same to the recipient balanceOf[_to] += _value; Transfer(_from, _to, _value); // Asserts are used to use static analysis to find bugs in your code. They should never fail assert(balanceOf[_from] + balanceOf[_to] == previousBalances); } function transfer(address _to, uint256 _value) public { require(!frozenAccount[msg.sender]); _transfer(msg.sender, _to, _value); } function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { require(!frozenAccount[msg.sender]); require(_value <= allowance[_from][msg.sender]); // Check allowance allowance[_from][msg.sender] -= _value; _transfer(_from, _to, _value); return true; } function approve(address _spender, uint256 _value) public returns (bool success) { allowance[msg.sender][_spender] = _value; return true; } function approveAndCall(address _spender, uint256 _value, bytes _extraData) public returns (bool success) { tokenRecipient spender = tokenRecipient(_spender); if (approve(_spender, _value)) { spender.receiveApproval(msg.sender, _value, this, _extraData); return true; } } function burn(uint256 _value) onlyOwner public returns (bool success) { require(balanceOf[msg.sender] >= _value); // Check if the sender has enough balanceOf[msg.sender] -= _value; // Subtract from the sender totalSupply -= _value; // Updates totalSupply Burn(msg.sender, _value); return true; } function burnFrom(address _from, uint256 _value) onlyOwner public returns (bool success) { require(balanceOf[_from] >= _value); // Check if the targeted balance is enough require(_value <= allowance[_from][msg.sender]); // Check allowance balanceOf[_from] -= _value; // Subtract from the targeted balance allowance[_from][msg.sender] -= _value; // Subtract from the sender's allowance totalSupply -= _value; // Update totalSupply Burn(_from, _value); return true; } function transfer(address _to, uint256 _value, bytes _data) public returns (bool) { require(_to != address(this)); transfer(_to, _value); require(_to.call(_data)); return true; } function transferFrom(address _from, address _to, uint256 _value, bytes _data) public returns (bool) { require(_to != address(this)); transferFrom(_from, _to, _value); require(_to.call(_data)); return true; } function approve(address _spender, uint256 _value, bytes _data) public returns (bool) { require(_spender != address(this)); approve(_spender, _value); require(_spender.call(_data)); return true; } function transferOwnership(address _owner) onlyOwner public { owner = _owner; } function mintToken(address target, uint256 mintedAmount) public onlyOwner { balanceOf[target] += mintedAmount; totalSupply += mintedAmount; Transfer(0, owner, mintedAmount); Transfer(owner, target, mintedAmount); } function freezeAccount(address target, bool freeze) public onlyOwner { frozenAccount[target] = freeze; FrozenFunds(target, freeze); } }
1.21.6.4. 打通用户注册
这里我们要实现新用户在网站上开户为其创建一个钱包账号。
对于现有的注册流程界面部分无需修改,只需在创建账号逻辑的环节增加一段代码,去以太坊创建账号。
web3.eth.personal.newAccount('!@superpassword').then(console.log);
创建账号后
注册成功 ------------------------- 用户名:netkiller 性别:男 ... ... ... 钱包账号:0x627306090abab3a6e1400e9345bc60c78a8bef57 [ 下载备份 ] * (提醒)请下载备份您的钱包文件,并请牢记你的密码,一旦丢失无法找回。
点击下载按钮后将对应账号文件提供给用户,文章通常在 keystore 类似这种格式 UTC--2018-02-10T09-37-49.558088000Z--5c18a33df2cc41a1beddc91133b8422e89f041b7
有了这个账号文件,用户可以导入到Ethereum Wallet 或者 MetaMask 中。
1.21.6.5. 现有用户怎么处理
新用户我们可以在用户注册的时候为其创建一个钱包账号。那么老用户呢?
老用户我们提供“绑定”功能,如果用户已有以太坊账号,就可以将以太坊账号与网站账号做绑定。
通常我们需要一个页面
当前用户名:netkiller
以太坊钱包:___________________________
手机验证码:______ [ 获取验证码]
[ 绑定 ] [取消]
填写钱包账号,然后点击获取验证码,然后输入验证码,点击 “提交” 完成账号的绑定。
如果你想在平台上提供转账等高级操作,你还需要让用户上传 UTC--2018-02-10T09-37-49.558088000Z--5c18a33df2cc41a1beddc91133b8422e89f041b7 文件,如果采用上传方案,就不需要绑定了,因为在文件中(json格式)address 就是账号。
当前用户名:netkiller 以太坊钱包:___________________________ [浏览] 手机验证码:______ [ 获取验证码] [ 上传 ] [取消]
1.21.6.6. 赠送代币
对于新开户,或者老用户绑定了钱包。我们通常要意思一下,就是送点币。
送币有两种方式,第一种是转账给用户,缺点是需要花店gas(气),第二种是采用空投方式,就是在用户查询余额的时候送币。
先来说说第一种,转账方式。
fs = require('fs'); const Web3 = require('web3'); const web3 = new Web3('http://localhost:8545'); web3.version const abi = fs.readFileSync('output/TokenERC20.abi', 'utf-8'); const contractAddress = "0x05A97632C197a0496bc939C4e666c2E03Cb95DD4"; const toAddress = "0x2C687bfF93677D69bd20808a36E4BC2999B4767C"; var coinbase; web3.eth.getCoinbase().then(function (address){ coinbase = address; console.log(address); }); const contract = new web3.eth.Contract(JSON.parse(abi), contractAddress, { from: coinbase , gas: 100000}); contract.methods.balanceOf('0x5c18a33DF2cc41a1bedDC91133b8422e89f041B7').call().then(console.log).catch(console.error); contract.methods.balanceOf('0x2C687bfF93677D69bd20808a36E4BC2999B4767C').call().then(console.log).catch(console.error); web3.eth.personal.unlockAccount(coinbase, "netkiller").then(console.log); contract.methods.transfer('0x2C687bfF93677D69bd20808a36E4BC2999B4767C', 100).send().then(console.log).catch(console.error); contract.methods.balanceOf('0x2C687bfF93677D69bd20808a36E4BC2999B4767C').call().then(console.log).catch(console.error);
第二种是空投币
uint totalSupply = 100000000 ether; // 总发行量 uint currentTotalAirdrop = 0; // 已经空投数量 uint airdrop = 1 ether; // 单个账户空投数量 // 存储是否空投过 mapping(address => bool) touched; // 修改后的balanceOf方法 function balanceOf(address _owner) public view returns (uint256 balance) { if (!touched[_owner] && currentTotalAirdrop < totalSupply) { touched[_owner] = true; currentTotalAirdrop += airdrop; balances[_owner] += airdrop; } return balances[_owner]; }
空投代币省了 Gas,但是带来一个问题,就是实际代币发行量成了 totalSupply * 2 ,因为创建合约的时候代币全部发给了 msg.sender ,空投只能使用增发币,无法去 msg.sender 扣除的空投数量。
空投币不好管理发行量。有一种做法,就是发行的时候分为2分,一份是 coinbase(msg.sender) 的 另一份是空投的。
1.21.6.7. 赚取代币
这里我们举例几个场景
发放代币的方法
电商平台可以通过活动,订单量等等条件将代币发放给用户
智能穿戴,例如鞋子,手环,可以根据用户的运动值这算成代币,发放给用户
游戏平台,用户在线时间,电子竞技赢得分数都可以这算成代币,发放给用户
1.21.6.8. 用户登录
第一个界面一定是,请输入用户名和密码,然后提交登录。
登录后进入用户信息页面
用户登录成功 ------------------------------ 当前用户名:netkiller ... ... 钱包账号:0x627306090abab3a6e1400e9345bc60c78a8bef57 ------------------------------ 当前余额: 1000 NEO
NEO是代币符号,假设叫NEO,这是我的英文名。实际上NEO已经被其他代币使用:(
获取账号余额代码
fs = require('fs'); const Web3 = require('web3'); const web3 = new Web3('http://localhost:8545'); const abi = fs.readFileSync('output/TokenERC20.abi', 'utf-8'); const contractAddress = "0x05A97632C197a0496bc939C4e666c2E03Cb95DD4"; const fromAddress = "0x5c18a33DF2cc41a1bedDC91133b8422e89f041B7"; //用户账号 const toAddress = "0x2C687bfF93677D69bd20808a36E4BC2999B4767C"; //收款账号 const contract = new web3.eth.Contract(JSON.parse(abi), contractAddress, { from: fromAddress , gas: 100000}); contract.methods.balanceOf(toAddress).call().then(console.log).catch(console.error);
1.21.6.9. 积分商城
这里是消费代币的地方,可以使用代币对兑换礼品,购买物品等等。
用户花出去代币去向应该是,用户收款的财务账号。
fs = require('fs'); const Web3 = require('web3'); const web3 = new Web3('http://localhost:8545'); const abi = fs.readFileSync('output/TokenERC20.abi', 'utf-8'); const contractAddress = "0x05A97632C197a0496bc939C4e666c2E03Cb95DD4"; const fromAddress = "0x5c18a33DF2cc41a1bedDC91133b8422e89f041B7"; //用户账号 const toAddress = "0x2C687bfF93677D69bd20808a36E4BC2999B4767C"; //收款账号 const contract = new web3.eth.Contract(JSON.parse(abi), contractAddress, { from: fromAddress , gas: 100000}); web3.eth.personal.unlockAccount(fromAddress, "netkiller").then(console.log); contract.methods.transfer(toAddress, 10).send().then(console.log).catch(console.error); //花费代币 10 contract.methods.balanceOf(toAddress).call().then(console.log).catch(console.error);
1.21.6.10. 代币报表
报表是用来展示网站数据变化的图标,这里只列出与代币有关的报表。
1.21.6.10.1. 曾币报表
用来展示每日,每周,每月.... 赠送,空投的代币量
1.21.6.10.2. 积分商城报表
进账财务数据,每日,每周,每月....
1.21.6.11. 代币交易
代币上交易所后,用户间就可以了。
我们使用另外一个交易所账号,参与代币交易,可以卖币(回收),买币(发行)等等操作,实现代币的闭环流通。
1.21.7. 区块链征信解决方案探索
翻看了无数的文章没有找到一篇关于谈征信在区块链上怎么落地的文章。也在各种区块链微信群和QQ群中问了一圈,也没有人知道怎么落地。
现在的情况是大家都知道区块链做征信没问题,区中心化,不可撰改,简直是征信系统而设计的。那么怎么使项目落地呢?没有一篇文章谈到这个问题。可能有些大公司已经实现了,处于技术保密,没有分享。
看来只能靠自己了,以太坊和超级账本一直在研究,最近一段时间研究以太坊比较多,但是发现征信这个系统是在不适合在以太坊上实现,于是有回到超级账本上。
回到超级账本上感觉有点不适应,超级账本没有 Token , 超级账本的合约实现与以太坊完全不同。两个系统是两种思维解决同一个区块链需求。
一看 hyperledger 就是当前IBM风格,hyperledger 特点,体系庞大,结构复杂,难以理解,运维复杂。简单的问题用复杂的方式思考,做出一个复杂的系统,可用性极差。IBM的产品特点是,你只能他们合作,一旦合作(上了船)就摔不掉,从他们小型机,到中间件产品,以及各种行业解决方案。IBM的系统出了问题,只有IBM的人才能解决。
随者 hyperledger 开源,我希望 hyperledger 的风格能脱离IBM的影子。
回到正题,研究了几个 hyperledger 提供的 Example 后,对怎么实现征信需求,有了一点思路。
首先 chaincode 合约并不复杂,由两个核心方法组织,分别是Init和Invoke。其次数据操作类似 map 数据结构, shim.ChaincodeStubInterface 接口提供了 get, put, del 等操作。
1.21.7.1. 需求分析与概要设计
证信系统信息查询问题,一怎样查询?二查询哪些信息?
区块不是关系型数据库,无法实现SQL那样的发杂查询,所以设计接口要尽量迎合区块链的,有些情况需要妥协,适应区块链的弱点和不足。
但是我们可以让数据库和区块链同时存在,相互弥补不足。
数据库部分我这就就不讲了,区块链的实现方式是,使用身份证码号查询,返回 json 数据。
1.21.7.2. 数据结构
首先我们定义一个结构体用来存储身份信息,征信信息远不止这几项,请根据你的实际情况定义即可
package main import "fmt" import "encoding/json" type Person struct { No string `json:"no"` Name string `json:"name"` Sex bool `json:"sex"` Age int `json:"age"` Address string `json:"address"` } func main(){ person := &Person{"430725198001190911","景峯",true,30,"Shenzhen,China"} personJson, _ := json.Marshal(person) fmt.Println(string(personJson)); person1 := &Person{ No: "430725198001190911", Name: "Neo Chen", Sex: true, Age: 35, Address: "Shenzhen, China"} json2, _ := json.Marshal(person1) fmt.Println(string(json2)) }
编译,运行,测试定义json是否正确。
neo@MacBook-Pro ~/golang/contract % rm -rf person && go build person.go && ./person {"no":"430725198001190911","name":"景峯","sex":true,"age":30,"address":"Shenzhen,China"} {"no":"430725198001190911","name":"Neo Chen","sex":true,"age":35,"address":"Shenzhen, China"}
最终我们只需要结构体复制到合约代码中。
type Person struct { No string `json:"no"` Name string `json:"name"` Sex bool `json:"sex"` Age int `json:"age"` Address string `json:"address"` }
1.21.7.3. 将征信资料写入区块链
通过下面的函数,将征信数据写入到区块链上。
func (s *SmartContract) createPerson(stub shim.ChaincodeStubInterface, args []string) sc.Response { if len(args) != 6 { return shim.Error("Incorrect number of arguments. Expecting 6") } var person = Person{No: args[1], Name: args[2], Sex: args[3], Age: args[4], Address: args[5]} personAsBytes, _ := json.Marshal(person) stub.PutState(args[0], personAsBytes) return shim.Success(nil) }
1.21.7.4. 查询区块数据
通过下面方法查询链上的征信资料。
func (s *SmartContract) queryPerson(stub shim.ChaincodeStubInterface, args []string) sc.Response { if len(args) != 1 { return shim.Error("Incorrect number of arguments. Expecting 1") } personAsBytes, _ := stub.GetState(args[0]) return shim.Success(personAsBytes) }
1.21.7.5. 删除区块
通过下面方法删除征信数据。
func (s *SmartContract) deletePerson(stub shim.ChaincodeStubInterface, args []string) sc.Response { if len(args) != 1 { return shim.Error("Incorrect number of arguments. Expecting 1") } personAsBytes, _ := stub.GetState(args[0]) err= stub.DelState(args[0]) if err != nil { return shim.Error("Failed to delete Student from DB, key is: "+args[0]) } return shim.Success(personAsBytes) }
1.21.8. Hyperledger fabric 银行应用探索
一直想写这篇文章,可是我个人对银行系统了解甚少,网上很多文章有多拿银行来举例,铺天盖地的文章,却没有一篇告诉你究竟如何落地。
其中不少文章中提到银行SWIFT系统,什么事 SWIFT 呢?
1.21.8.1. 电汇年代
这要从电汇说起,年轻时候上学,每个学期都有一笔学费,那时主要交通是铁路,携带现金非常不便,母亲将5000元人民币缝在我的贴身内裤边上。到了学校拆开线取出,味道好极了,呵呵。
后来从同学那里得知,可以使用邮局汇款,首先去邮局,拿一个特殊信封填好地址,然后将钱交给工作人员。一周以后信封皮会记挂号信到收款人手里,那个这个信封去指定邮局取款。
记得第二学期就出现电汇,银行提供的电汇服务比邮政的速度快,也比邮局更方便。
用户A ----- 汇票 -----> 用户B | | 申请 提款 | | V V 银行A ----- 划账 -----> 银行B
电汇是用户A银行提出申请,将钱交给A银行。银行马上通过网络通知B银行,用户B就可以提款。没有多久邮政的系统也换成这套系统了。
那个年代只有拨号网络,帧中继,ATM(是一种网络,不是ATM取款机) 等窄带网络,现在用宽带上网的90后无法想法那个网速。
1.21.8.2. 通存通取年代
ISDN,DDN 专线的出现,才有了稳定窄带通信,银行网点互联成为可能。
MasterCard万事达,Visa维萨卡率先进入中国。很快银行内部就能实现转账了,ATM机互联成为可能,这时结算全部使用 MasterCard万事达,Visa维萨卡。银行需向MasterCard万事达,Visa维萨机构支付费用。促使一些有实力银行研发自己的内部系统,但是这些系统仅限内部使用,如果银行卡上没有MasterCard/Visa 的只能本银行使用,无法跨行。没有实力的银行则会选择门槛稍低的 INTERLINK、PLUS 等机构合作。
98年在学校寝室,一位同学拿了一张牡丹万事达卡,全寝室传看,那个年代在哈尔滨提款机都不普及,只有较大的银行网点才有。
2000来深圳,那时有个深银联,深圳首先实现了跨行业务,2年后(2002年)银联出现,国家强推,所有银行开始支持银联。当时银行卡上同时有两个标示,MasterCard/Visa 和 银联。
同年招商银行率先推出网上银行。招商银行电脑部一度成为IT红黑榜上评价最高的公司,可惜没有跟上互联网的步伐......
目前新开的银行卡已经看不到 MasterCard/Visa 标志了,甚至香港的银行卡也默认使用银联,出国旅游耍刷 UnionPay 跨境支付非常方便。
1.21.8.3. 跨境汇款
跨境汇款你别想像银联一样,填写一个姓名一个账号,点击一下转账按钮这样的操作。每个国家的政策也不同,政策不允许这样操作。
跨境只能汇款,无法转账,又回到了电汇时代,只是不再主要纸质的汇票了
用户A ----- SWIFT CODE -----> 用户B | | 申请 提款 | | V V 银行A -------- SWIFT --------> 银行B
跨境汇款必须依赖 SWIFT 系统,由于我国的政策问题,个人很少涉及跨境业务,所以多数人对 SWIFT 不是很了解。 如果你在香港开一张银行卡例如汇丰银行,拿到银行给的信封里,就会有一个 SWIFT 码。
你会遇到这个问题,无法输入对方的名字,例如:
Nickname:netkiller English name: Neo chen Nippon name: ちんけいほう (音訳) Korean name: 천징봉 Thailand name: ภูมิภาพภูเขา Vietnam: Trần Cảnh Phong
所以就需要每个银行一个代码,每个账号一个代码,由于全世界银行太多,银行系统各种各样,每个国家的语言也不同,难以达成一致,联合国也没有能力统一标准。新建一套系统不太可能,所以80年代的标准仍然沿用至今。
使用 SWIFT 面临的问题
网络速度慢
手续费高
技术落后
不能实时到账
脆弱容易被攻击
SWIFT的诞生甚至早于数字时代,可以说是目前最好的跨境通讯和交易系统,但它的确需要与时俱进。
牵头做一个世界银联不太可能,世界各国银行无法想政府一样,一个红头文件,下面招办,行政手段推动。且业务差异大,系统复杂超乎想象,这个中心数据库谁来管理呢?
SWIFT早就意识到了这些问题,并宣布进军区块链,同时加入超级账本项目(Hyperledger Project)成为会员。可以肯定下一个版本的SWIFT灰使用区块链技术,一步一步逐渐取代就系统。
1.21.8.4. 区块链能做什么
区块链可以解决银行哪些痛点,先说说 SWIFT 2.0 (区块链SWIFT)我想SWIFT仍然会兼容现有的协议。SWIFT CODE协议仍然会保留。短时间不可能被取代SWIFT CODE因为这个体系已经使用很多年。
用户A ----- SWIFT CODE -----> 用户B | | 申请 提款 | | V V 银行A -------- 划账 --------> 银行B \ / \ / V V +---------------------------------+ | Hyperledger Fabric 盟链 | +---------------------------------+ | Smart Contract | +---------------------------------+
后端将会被区块链取代
另外银行的跨国业务将会走自己的区块链,不再依赖 SWIFT, 因为费用等问题。
用户A --------- 转账 ---------> 用户B | | 申请 提款 | | V V 英国渣打银行 ----- 划账 -----> 深圳渣打银行 \ / \ / V V +---------------------------------+ | Hyperledger Fabric 盟链 | +---------------------------------+ | Smart Contract | +---------------------------------+
1.21.8.5. 智能合约怎么落地
我对银行业务实在不了解,这里只能设想一下场景。下面是我之前写的一个Token合约,我将它应用到这个场景中
package main /* -------------------------------------------------- Author: netkiller <netkiller@msn.com> Home: http://www.netkiller.cn Data: 2018-03-20 11:00 PM -------------------------------------------------- CORE_PEER_ADDRESS=peer:7051 CORE_CHAINCODE_ID_NAME=token3:1.0 chaincode/token/token3 peer chaincode install -n token3 -v 1.0 -p chaincodedev/chaincode/token peer chaincode instantiate -C myc -n token3 -v 1.0 -c '{"Args":[""]}' -P "OR ('Org1MSP.member','Org2MSP.member')" peer chaincode invoke -C myc -n token3 -c '{"function":"createAccount","Args":["coinbase"]}' peer chaincode invoke -C myc -n token3 -v 1.0 -c '{"function":"showAccount","Args":["coinbase"]}' peer chaincode invoke -C myc -n token3 -c '{"function":"balanceAll","Args":["coinbase"]}' peer chaincode invoke -C myc -n token3 -c '{"function":"initCurrency","Args":["Netkiller Token","NKC","1000000","coinbase"]}' peer chaincode invoke -C myc -n token3 -c '{"function":"initCurrency","Args":["NEO Token","NEC","1000000","coinbase"]}' peer chaincode invoke -C myc -n token3 -c '{"function":"setLock","Args":["true"]}' peer chaincode invoke -C myc -n token3 -c '{"function":"setLock","Args":["false"]}' peer chaincode invoke -C myc -n token3 -c '{"function":"mintToken","Args":["NKC","5000","coinbase"]}' peer chaincode invoke -C myc -n token3 -c '{"function":"createAccount","Args":["netkiller"]}' peer chaincode invoke -C myc -n token3 -c '{"function":"transferToken","Args":["coinbase","netkiller","NKC","100"]}' peer chaincode invoke -C myc -n token3 -c '{"function":"balance","Args":["netkiller","NKC"]}' peer chaincode invoke -C myc -n token3 -c '{"function":"frozenAccount","Args":["netkiller","true"]}' -------------------------------------------------- */ import ( "encoding/json" "fmt" "strconv" "github.com/hyperledger/fabric/core/chaincode/shim" pb "github.com/hyperledger/fabric/protos/peer" ) type Msg struct{ Status bool `json:"Status"` Code int `json:"Code"` Message string `json:"Message"` } type Currency struct{ TokenName string `json:"TokenName"` TokenSymbol string `json:"TokenSymbol"` TotalSupply float64 `json:"TotalSupply"` } type Token struct { Lock bool `json:"Lock"` Currency map[string]Currency `json:"Currency"` } func (token *Token) transfer (_from *Account, _to *Account, _currency string, _value float64) []byte{ var rev []byte if (token.Lock){ msg := &Msg{Status: false, Code: 0, Message: "锁仓状态,停止一切转账活动"} rev, _ = json.Marshal(msg) return rev } if(_from.Frozen ) { msg := &Msg{Status: false, Code: 0, Message: "From 账号冻结"} rev, _ = json.Marshal(msg) return rev } if( _to.Frozen) { msg := &Msg{Status: false, Code: 0, Message: "To 账号冻结"} rev, _ = json.Marshal(msg) return rev } if(!token.isCurrency(_currency)){ msg := &Msg{Status: false, Code: 0, Message: "货币符号不存在"} rev, _ = json.Marshal(msg) return rev } if(_from.BalanceOf[_currency] >= _value){ _from.BalanceOf[_currency] -= _value; _to.BalanceOf[_currency] += _value; msg := &Msg{Status: true, Code: 0, Message: "转账成功"} rev, _ = json.Marshal(msg) return rev }else{ msg := &Msg{Status: false, Code: 0, Message: "余额不足"} rev, _ = json.Marshal(msg) return rev } } func (token *Token) initialSupply(_name string, _symbol string, _supply float64, _account *Account) []byte{ if _,ok := token.Currency[_symbol]; ok { msg := &Msg{Status: false, Code: 0, Message: "代币已经存在"} rev, _ := json.Marshal(msg) return rev } if _account.BalanceOf[_symbol] > 0 { msg := &Msg{Status: false, Code: 0, Message: "账号中存在代币"} rev, _ := json.Marshal(msg) return rev }else{ token.Currency[_symbol] = Currency{TokenName: _name, TokenSymbol: _symbol, TotalSupply: _supply} _account.BalanceOf[_symbol] = _supply msg := &Msg{Status: true, Code: 0, Message: "代币初始化成功"} rev, _ := json.Marshal(msg) return rev } } func (token *Token) mint(_currency string, _amount float64, _account *Account) []byte{ if(!token.isCurrency(_currency)){ msg := &Msg{Status: false, Code: 0, Message: "货币符号不存在"} rev, _ := json.Marshal(msg) return rev } cur := token.Currency[_currency] cur.TotalSupply += _amount; token.Currency[_currency] = cur _account.BalanceOf[_currency] += _amount; msg := &Msg{Status: true, Code: 0, Message: "代币增发成功"} rev, _ := json.Marshal(msg) return rev } func (token *Token) burn(_currency string, _amount float64, _account *Account) []byte{ if(!token.isCurrency(_currency)){ msg := &Msg{Status: false, Code: 0, Message: "货币符号不存在"} rev, _ := json.Marshal(msg) return rev } if(token.Currency[_currency].TotalSupply >= _amount){ cur := token.Currency[_currency] cur.TotalSupply -= _amount; token.Currency[_currency] = cur _account.BalanceOf[_currency] -= _amount; msg := &Msg{Status: false, Code: 0, Message: "代币回收成功"} rev, _ := json.Marshal(msg) return rev }else{ msg := &Msg{Status: false, Code: 0, Message: "代币回收失败,回收额度不足"} rev, _ := json.Marshal(msg) return rev } } func (token *Token) isCurrency(_currency string) bool { if _, ok := token.Currency[_currency]; ok { return true }else{ return false } } func (token *Token) setLock(_look bool) bool { token.Lock = _look return token.Lock } type Account struct { Name string `json:"Name"` Frozen bool `json:"Frozen"` BalanceOf map[string]float64 `json:"BalanceOf"` } func (account *Account) balance (_currency string) map[string]float64{ bal := map[string]float64{_currency:account.BalanceOf[_currency]} return bal } func (account *Account) balanceAll() map[string]float64{ return account.BalanceOf } // ----------- const TokenKey = "Token" // Define the Smart Contract structure type SmartContract struct { } func (s *SmartContract) Init(stub shim.ChaincodeStubInterface) pb.Response { token := &Token{Currency: map[string]Currency{}} tokenAsBytes, err := json.Marshal(token) err = stub.PutState(TokenKey, tokenAsBytes) if err != nil { return shim.Error(err.Error()) }else{ fmt.Printf("Init Token %s \n", string(tokenAsBytes)) } return shim.Success(nil) } func (s *SmartContract) Query(stub shim.ChaincodeStubInterface) pb.Response { function, args := stub.GetFunctionAndParameters() if function == "balance" { return s.balance(stub, args) } else if function == "balanceAll" { return s.balanceAll(stub, args) } else if function == "showAccount" { return s.showAccount(stub, args) } return shim.Error("Invalid Smart Contract function name.") } func (s *SmartContract) Invoke(stub shim.ChaincodeStubInterface) pb.Response { // Retrieve the requested Smart Contract function and arguments function, args := stub.GetFunctionAndParameters() // Route to the appropriate handler function to interact with the ledger appropriately if function == "initLedger" { return s.initLedger(stub, args) } else if function == "createAccount" { return s.createAccount(stub, args) } else if function == "initCurrency" { return s.initCurrency(stub, args) } else if function == "setLock" { return s.setLock(stub, args) } else if function == "transferToken" { return s.transferToken(stub, args) } else if function == "frozenAccount" { return s.frozenAccount(stub, args) } else if function == "mintToken" { return s.mintToken(stub, args) } else if function == "balance" { return s.balance(stub, args) } else if function == "balanceAll" { return s.balanceAll(stub, args) } else if function == "showAccount" { return s.showAccount(stub, args) } else if function == "showToken" { return s.showToken(stub, args) } return shim.Error("Invalid Smart Contract function name.") } func (s *SmartContract) createAccount(stub shim.ChaincodeStubInterface, args []string) pb.Response { if len(args) != 1 { return shim.Error("Incorrect number of arguments. Expecting 1") } key := args[0] name := args[0] existAsBytes,err := stub.GetState(key) fmt.Printf("GetState(%s) %s \n", key, string(existAsBytes)) if string(existAsBytes) != "" { fmt.Println("Failed to create account, Duplicate key.") return shim.Error("Failed to create account, Duplicate key.") } account := Account{ Name: name, Frozen: false, BalanceOf: map[string]float64{}} accountAsBytes, _ := json.Marshal(account) err = stub.PutState(key, accountAsBytes) if err != nil { return shim.Error(err.Error()) } fmt.Printf("createAccount %s \n", string(accountAsBytes)) return shim.Success(accountAsBytes) } func (s *SmartContract) initLedger(stub shim.ChaincodeStubInterface, args []string) pb.Response { return shim.Success(nil) } func (s *SmartContract) showToken(stub shim.ChaincodeStubInterface, args []string) pb.Response { tokenAsBytes,err := stub.GetState(TokenKey) if err != nil { return shim.Error(err.Error()) }else{ fmt.Printf("GetState(%s)) %s \n", TokenKey, string(tokenAsBytes)) } return shim.Success(tokenAsBytes) } func (s *SmartContract) initCurrency(stub shim.ChaincodeStubInterface, args []string) pb.Response { if len(args) != 4 { return shim.Error("Incorrect number of arguments. Expecting 4") } _name := args[0] _symbol:= args[1] _supply,_:= strconv.ParseFloat(args[2], 64) _account := args[3] coinbaseAsBytes,err := stub.GetState(_account) if err != nil { return shim.Error(err.Error()) } fmt.Printf("Coinbase before %s \n", string(coinbaseAsBytes)) coinbase := &Account{} json.Unmarshal(coinbaseAsBytes, &coinbase) token := Token{} existAsBytes,err := stub.GetState(TokenKey) if err != nil { return shim.Error(err.Error()) }else{ fmt.Printf("GetState(%s)) %s \n", TokenKey, string(existAsBytes)) } json.Unmarshal(existAsBytes, &token) result := token.initialSupply(_name,_symbol,_supply, coinbase) tokenAsBytes, _ := json.Marshal(token) err = stub.PutState(TokenKey, tokenAsBytes) if err != nil { return shim.Error(err.Error()) }else{ fmt.Printf("Init Token %s \n", string(tokenAsBytes)) } coinbaseAsBytes, _ = json.Marshal(coinbase) err = stub.PutState(_account, coinbaseAsBytes) if err != nil { return shim.Error(err.Error()) } fmt.Printf("Coinbase after %s \n", string(coinbaseAsBytes)) return shim.Success(result) } func (s *SmartContract) transferToken(stub shim.ChaincodeStubInterface, args []string) pb.Response { if len(args) != 4 { return shim.Error("Incorrect number of arguments. Expecting 4") } _from := args[0] _to := args[1] _currency := args[2] _amount,_ := strconv.ParseFloat(args[3], 32) if(_amount <= 0){ return shim.Error("Incorrect number of amount") } fromAsBytes,err := stub.GetState(_from) if err != nil { return shim.Error(err.Error()) } fmt.Printf("fromAccount %s \n", string(fromAsBytes)) fromAccount := &Account{} json.Unmarshal(fromAsBytes, &fromAccount) toAsBytes,err := stub.GetState(_to) if err != nil { return shim.Error(err.Error()) } fmt.Printf("toAccount %s \n", string(toAsBytes)) toAccount := &Account{} json.Unmarshal(toAsBytes, &toAccount) tokenAsBytes,err := stub.GetState(TokenKey) if err != nil { return shim.Error(err.Error()) } fmt.Printf("Token %s \n", string(toAsBytes)) token := Token{Currency: map[string]Currency{}} json.Unmarshal(tokenAsBytes, &token) result := token.transfer(fromAccount, toAccount, _currency, _amount) fmt.Printf("Result %s \n", string(result)) fromAsBytes, err = json.Marshal(fromAccount) if err != nil { return shim.Error(err.Error()) } err = stub.PutState(_from, fromAsBytes) if err != nil { return shim.Error(err.Error()) }else{ fmt.Printf("fromAccount %s \n", string(fromAsBytes)) } toAsBytes, err = json.Marshal(toAccount) if err != nil { return shim.Error(err.Error()) } err = stub.PutState(_to, toAsBytes) if err != nil { return shim.Error(err.Error()) }else{ fmt.Printf("toAccount %s \n", string(toAsBytes)) } return shim.Success(result) } func (s *SmartContract) mintToken(stub shim.ChaincodeStubInterface, args []string) pb.Response { if len(args) != 3 { return shim.Error("Incorrect number of arguments. Expecting 3") } _currency := args[0] _amount,_ := strconv.ParseFloat(args[1], 32) _account := args[2] coinbaseAsBytes,err := stub.GetState(_account) if err != nil { return shim.Error(err.Error()) }else{ fmt.Printf("Coinbase before %s \n", string(coinbaseAsBytes)) } coinbase := &Account{} json.Unmarshal(coinbaseAsBytes, &coinbase) tokenAsBytes,err := stub.GetState(TokenKey) if err != nil { return shim.Error(err.Error()) } fmt.Printf("Token before %s \n", string(tokenAsBytes)) token := Token{} json.Unmarshal(tokenAsBytes, &token) result := token.mint(_currency, _amount, coinbase) tokenAsBytes, err = json.Marshal(token) if err != nil { return shim.Error(err.Error()) } err = stub.PutState(TokenKey, tokenAsBytes) if err != nil { return shim.Error(err.Error()) } fmt.Printf("Token after %s \n", string(tokenAsBytes)) coinbaseAsBytes, _ = json.Marshal(coinbase) err = stub.PutState(_account, coinbaseAsBytes) if err != nil { return shim.Error(err.Error()) }else{ fmt.Printf("Coinbase after %s \n", string(coinbaseAsBytes)) } fmt.Printf("mintToken %s \n", string(tokenAsBytes)) return shim.Success(result) } func (s *SmartContract) setLock(stub shim.ChaincodeStubInterface, args []string) pb.Response { if len(args) != 1 { return shim.Error("Incorrect number of arguments. Expecting 2") } _look := args[0] tokenAsBytes,err := stub.GetState(TokenKey) if err != nil { return shim.Error(err.Error()) } // fmt.Printf("setLock - begin %s \n", string(tokenAsBytes)) token := Token{} json.Unmarshal(tokenAsBytes, &token) if(_look == "true"){ token.setLock(true) }else{ token.setLock(false) } tokenAsBytes, err = json.Marshal(token) if err != nil { return shim.Error(err.Error()) } err = stub.PutState(TokenKey, tokenAsBytes) if err != nil { return shim.Error(err.Error()) } fmt.Printf("setLock - end %s \n", string(tokenAsBytes)) return shim.Success(nil) } func (s *SmartContract) frozenAccount(stub shim.ChaincodeStubInterface, args []string) pb.Response { if len(args) != 2 { return shim.Error("Incorrect number of arguments. Expecting 2") } _account := args[0] _status := args[1] accountAsBytes,err := stub.GetState(_account) if err != nil { return shim.Error(err.Error()) } // fmt.Printf("setLock - begin %s \n", string(tokenAsBytes)) account := Account{} json.Unmarshal(accountAsBytes, &account) var status bool if(_status == "true"){ status = true; }else{ status = false } account.Frozen = status accountAsBytes, err = json.Marshal(account) if err != nil { return shim.Error(err.Error()) } err = stub.PutState(_account, accountAsBytes) if err != nil { return shim.Error(err.Error()) }else{ fmt.Printf("frozenAccount - end %s \n", string(accountAsBytes)) } return shim.Success(nil) } func (s *SmartContract) showAccount(stub shim.ChaincodeStubInterface, args []string) pb.Response { if len(args) != 1 { return shim.Error("Incorrect number of arguments. Expecting 1") } _account := args[0] accountAsBytes,err := stub.GetState(_account) if err != nil { return shim.Error(err.Error()) }else{ fmt.Printf("Account balance %s \n", string(accountAsBytes)) } return shim.Success(accountAsBytes) } func (s *SmartContract) balance(stub shim.ChaincodeStubInterface, args []string) pb.Response { if len(args) != 2 { return shim.Error("Incorrect number of arguments. Expecting 1") } _account := args[0] _currency := args[1] accountAsBytes,err := stub.GetState(_account) if err != nil { return shim.Error(err.Error()) }else{ fmt.Printf("Account balance %s \n", string(accountAsBytes)) } account := Account{} json.Unmarshal(accountAsBytes, &account) result := account.balance(_currency) resultAsBytes, _ := json.Marshal(result) fmt.Printf("%s balance is %s \n", _account, string(resultAsBytes)) return shim.Success(resultAsBytes) } func (s *SmartContract) balanceAll(stub shim.ChaincodeStubInterface, args []string) pb.Response { if len(args) != 1 { return shim.Error("Incorrect number of arguments. Expecting 1") } _account := args[0] accountAsBytes,err := stub.GetState(_account) if err != nil { return shim.Error(err.Error()) }else{ fmt.Printf("Account balance %s \n", string(accountAsBytes)) } account := Account{} json.Unmarshal(accountAsBytes, &account) result := account.balanceAll() resultAsBytes, _ := json.Marshal(result) fmt.Printf("%s balance is %s \n", _account, string(resultAsBytes)) return shim.Success(resultAsBytes) } // The main function is only relevant in unit test mode. Only included here for completeness. func main() { // Create a new Smart Contract err := shim.Start(new(SmartContract)) if err != nil { fmt.Printf("Error creating new Smart Contract: %s", err) } }
部署链码,然后实例化链码
peer chaincode install -n token3 -v 1.0 -p chaincodedev/chaincode/token peer chaincode instantiate -C myc -n token3 -v 1.0 -c '{"Args":[""]}' -P "OR ('Org1MSP.member','Org2MSP.member')"
首先初始化账号,需要将现有账号同步到区块链上,这是上链操作。账号分为两种,一个是 coinbase 银行的总账号,另外是用户账号
peer chaincode invoke -C myc -n token3 -c '{"function":"createAccount","Args":["coinbase"]}'
初始化外币,银行有多少外币存量,初始化到 coinbase 账号
peer chaincode invoke -C myc -n token3 -c '{"function":"initCurrency","Args":["Chain","RMB","1000000000","coinbase"]}' peer chaincode invoke -C myc -n token3 -c '{"function":"initCurrency","Args":["Japan","JPY","1000000000","coinbase"]}' peer chaincode invoke -C myc -n token3 -c '{"function":"initCurrency","Args":["USA","USD","1000000000","coinbase"]}'
为用户创建账号
peer chaincode invoke -C myc -n token3 -c '{"function":"createAccount","Args":["netkiller"]}' peer chaincode invoke -C myc -n token3 -c '{"function":"createAccount","Args":["neo"]}'
同步用户账号中的外币
peer chaincode invoke -C myc -n token3 -c '{"function":"transferToken","Args":["coinbase","netkiller","RMB","10000"]}' peer chaincode invoke -C myc -n token3 -c '{"function":"transferToken","Args":["coinbase","netkiller","USD","100"]}' peer chaincode invoke -C myc -n token3 -c '{"function":"transferToken","Args":["coinbase","neo","RMB","10000"]}' peer chaincode invoke -C myc -n token3 -c '{"function":"transferToken","Args":["coinbase","neo","USD","100"]}'
现在区块链上的用户资金已经跟数据库中的资金同步。
现在netkiller这个用户需要给neo转账 50 美金
peer chaincode invoke -C myc -n token3 -c '{"function":"transferToken","Args":["netkiller","neo","USD","50"]}'
现在 netkiller 账号中又 50美金,neo 账号中有 150美金。
peer chaincode invoke -C myc -n token3 -c '{"function":"balanceAll","Args":["netkiller"]}' peer chaincode invoke -C myc -n token3 -c '{"function":"balanceAll","Args":["neo"]}'
如果 neo 账号被法院申请冻结了,怎么办?可以使用下面方法设置冻结账号
peer chaincode invoke -C myc -n token3 -c '{"function":"frozenAccount","Args":["neo","true"]}'
此时 neo 账号被冻结,资金无法转入,转出。
1.21.8.6. 总结
以上仅仅是提供一个区块链学历案例,作者也设想了一个场景。但是真是情况怎样作者也不清楚。
上面通正合约没有考虑到汇率和中间价问题,需要进一步完善。
另外还需要一个守护进程订阅 Event 状态,每做一笔转账交易,event 会受到然后将这个操作同步到中心化数据库。
用户A ------------- 转账 ------------> 用户B | | 申请 提款 | | V V 英国渣打银行 --------- 划账 ---------> 深圳渣打银行 / \ / \ / \ / \ V V V V +----------+ +---------------------------------+ +----------+ | | | Hyperledger Fabric 盟链 | | | | Database | +---------------------------------+ | Database | | | | Smart Contract | | | +----------+ +---------------------------------+ +----------+ ^ o o ^ | | | | +----- Event -----+ +----- Event -----+
1.21.9. 区块链医院应用探索
1.21.9.1. 背景
当今医院面临的问题很多,产生很多医疗事故和医疗纠纷。问题主要体现在,医疗信息不透明,药品监管不力,电子病历不能共享,医疗影像资料一次性消费。
各医院的医院信息系统 HIS(Hospital Information System) 在一个局域网中运行,无法信息共享
还有药品和器械定价,采购,流通,溯源都需要监管。
在区块链没有出现之前,中心化的数据库无法解决这些问题,为什么呢?,谁来建设这个中心库,谁来管理,怎样使用,产生的费用谁来承担,等等无法达成一致。
区块链的出现迎刃而解,区块链是分布式共识数据库,加入医院盟链的医院都有各自的的节点,首先是公平性,都能达成一致。其次是安全性所有医院的节点达成共识后,才能存储数据。
最终实现医疗和健康信息共享,医疗流程透明化,医疗事故责任可追溯......
1.21.9.2. 药品和器械上链
1.21.9.2.1. 药品上链
使用区块链技术建立一个药品数据库。
将世面上所有销售的药品上链,上链内容包含,药品名称,说明书,价格,图片......
药品的生产,质检,物流等等溯源信息上链
药品数据库,价格透明
1.21.9.2.2. 器械上链
重复使用的器械,我们需要将使用过程上链,例如什么时间,什么地点,谁使用过,事后消毒过程等等
一次性使用的器械,一品一码对一次性使用的器械信息上链,使用状态,是否销毁等等。
1.21.9.3. 电子病历上链
目前的病历掌握在各个医院手上的,患者自己并不掌握,所以病人就没有办法获得自己的医疗记录和历史情况,这对患者就医会造成很大的困扰,因为医生无法详尽了解到你的病史记录。
虽然各医院的病例已经电子化,但是医院与医院之间的是不共享的。如果病人需要转院,因为医院因为隐私原因是不能直接把病历给转院的那家的,那么需要在当前医院把病历取出来,如果还需要保险报销的话,那么这个会更为麻烦。
建立医院盟链,病历上链一定是未来的趋势,通过区块链共享病例,能为患者和医生带来便利。在我们就医的过程中,如果医生能够获得更多的病例信息,有利于病情诊断。
病例一旦上链,电子病例不可篡改
1.21.9.3.1. 医学影像上链
例如 X 光片,CT,超声波等等医学影像资料,目前的现状是一次性使用,看一次病拍一次,看完丢掉,下次再拍。除非患者有意保存,否则医生很难看到一个时间段内的医学影像资料。
所以医学影像资料上链也很重要
1.21.9.4. 健康信息
另外现在又很多穿戴设备可以记录脉搏、运动数据、例如跑步,健身等等数据。
1.21.9.5. 出生证明
新生儿出生证明
1.21.9.6. 保险
1.21.9.6.1. 保险信息上链
就诊过程中医生经常询问患者,是否购买了商业保险以及社会保险,这样医生可以根据患者的承受能力为患者提供可承受的治疗方案。
1.21.9.6.2. 区块链解决出险理赔过程
传统出险,需要患者保存好单据,填写好出险单,提交病历,收据等资料交给保险公司。保险公司收到单据后审核,如果没有问题转账到患者银行卡上。
保险公司加入到盟链中,可以直接访问患者病历,无需患者再提交纸质单据。通过智能合约完成整个理赔过程,也降低了患者理赔操作难度。
1.21.9.7. 智能合约
暂无时间写
1.21.10. 艺术品区块链溯源防伪平台
1.21.10.1. 都有哪些角色参与其中
参与艺术品上链,鉴定,交易包含了下面几种角色。
平台至少有三种角色会
- 用户
- 机构
- 鉴定师
- 艺术家
所以我们需要为不同的角色提供不同的App应用。
用户端:功能包括防伪查询,链上资产的浏览,权益转让,资产拍卖,资产抵押,社区互动,分享,数字资产行情,钱包等等
机构端:负责信息收集,信息整理,数据提交,数据审查,资产上链,资产划拨等等
鉴定师:负责数字资产的鉴定,需要有相关资质。
1.21.10.2. 需要运用的防伪技术
防伪溯源涉及的技术栈
- 纸纹防伪(PaperPrint),纸纹防伪即纸纹防伪技术。它是一种基于提取和识别每张纸与生俱来的、独一无二且无法仿造、克隆的自然纤维纹理作为防伪特征(即纸纹)来实现防伪的新型防伪技术。
- 荧光防伪油墨,使用荧光油墨印刷技术,在特定波长的紫外线或者红外线下才能看到。荧光二维码,荧光印章,荧光指纹,荧光暗记,布满整个宣纸的荧光图案。我们可以为每个艺术家定制带有荧光图案的专属纸张。
- DNA防伪, 将艺术家的血液滴在书画上或者头发夹在宣纸中间。据说某中世纪著名画家将自己的精液和油画颜料混合 :)
- 特征识别防伪,类似我们手机的面部识别,记录物品的特征,例如使用电子显微镜平射纸文理,画面局部等等。油画还可以拍摄X光片。
- 3D 建模扫描,例如3D扫描仪,将物品的3D数据记录下来。
- 激光内雕,例如施华洛世奇将 logo 内雕在他的水晶制品中。
- QRcode 二维码,用于链上数据查询
- NFC(Near Field Communication) 有两个作用,一可以存储数据,二用来防伪,因为生产相同UID的NFC芯片难度极大,门槛很高。
- RFID(Radio Frequency Identification) 是 NFC 的一种,区别是不能存储数据,NFC 不能替代 RFID,RFID 可以实现资产盘点,以及安防。
- GPS 定位与地图,记录用户位置,资产位置,机构位置,鉴定师位置,还能实现次产跟踪,例如两次查询资产的GPS坐标,不在安全范围,将视为被盗,系统将通知机构或用户。
- 高清相机、高清视频设备
- 安防设备,CCTV监控,门禁等等
举例,书画怎样做防伪
以国画为例,我们首先找一个宣纸企业合作,在宣纸生产过程中,将NFC芯片夹在宣纸中间,然后我们使用荧光油墨在宣纸上印刷防伪图案。也可以采用人民币上的金属丝方案,最后拍摄纸纹。视频记录下艺术家的整个作画过程,然后再拍摄特征数据。最后使用激光打孔技术,在宣纸上打出防伪图案数据,针孔极小肉眼难以分辨,每个防伪数据都是唯一的。
NFC 标签可以使用易碎纸粘贴在艺术品上,缺点是寿命较短。使用PVC材料又容易撕下。
油画可以讲NFC芯片放在纺织布中,使用堆彩技术的画家可以讲NFC芯片覆盖(埋植)在颜料下面
1.21.10.3. 技术架构
1.21.10.3.1. 前端技术
由于 H5 技术无法满足我们的需求,例如相机,麦克风,NFC,定位...等等。我们重点放在 App 开发,H5 紧紧用于官网,区块链浏览器,资讯,等等。
由于使用了很多手机上技术,原生App更适合,而混合开发 React Native,Vue.js, Flutter 不在我们选择之列。
微信小程序可以考虑,但是如果涉及 Token 可能随时会被下架。
1.21.10.3.2. 微服务端
服务端设计为可以水平扩展,可以随时根据用户量,扩展服务器规模。
Nginx 负载均衡,HTTP2 (安卓 Okhttp 已经很好的支持 http2)
框架采用 Spring cloud
数据库开发使用 JPA
接口认证 Oauth2 + Jwt
1.21.10.3.3. 存储层
MongoDB
Redis
1.21.10.3.4. 消息队列层
Kafka
1.21.10.3.5. 搜索层
ELK(ElasticSearch, Logstash, Kibana)
搜索是非常重要的功能,因为区块链只能通过 hash 值取出链上的数据,虽然 Hyperledger Fabrc 在数据使用 CouchDB 时提供了 World State 的一些高级搜索功能,但是仍然不能满足我们的需求。
所以链上数据需要存储一份在搜索引擎中,搜索引擎的分词功能,可以提供快速精准的搜索服务。
搜索引擎的工作流程是:
User --> Phone App --> Nginx --> Spring cloud --> Elastsearch --> Hyperledger Fabric
1.21.10.3.6. 区块链
我们不做山寨链,我发现很多国内企业热衷于做山寨链,什么事山寨链呢,就是在现有的区块链(Ethereum, Hyperledger Fabric 或 EOS) 的基础上二次开发,首先开发山寨链需要大量的资金人力,私链是没有任何意义的,没有公信力。即使目前的现有区块链无法满足我们的需求,可以通过架构调节去适应他。
所以我们只用最成熟的产品:
Hyperledger Fabric 盟链:主要用于资产上链,链上资产查询
Ethereum 公链:用于 Token ,由于 Hyperledger Fabric 无法实现 Token,所以我们仍然需要以太坊。(作者写过一篇文章关于为什么Hyperledger Fabric 不能实现 Token,请兴趣自己在网上搜索)
EOS Token/资产上链:由于在我设计这个系统之时 EOS 还没有 Release 所以当时没有考虑 EOS。现在我们可以使用 EOS,甚至替换掉 Hyperledger Fabrc + Ethereum 方案。因为 EOS 即能实现资产上链,也能实现 Token。 这里我们将资产也在 EOS 上链一份,同时也支持 EOS 发的 Token。
IPFS 星际文件系统:用于存储多媒体数据,例如图片,视频。(注意:IPFS 暂时不支持流媒体,我的解决方案是上链同事复制一份到 nginx 中,并开启 mp4 流媒体功能)
注:虽然以太坊目前尝尝拥堵,但是很多应用场景仍是不可替代的。
1.21.10.3.7. 支持层
监控 Zabbix
1.21.10.4. RFID/NFC
1.21.10.4.1. RFID
RFID基本概念:
RFID(Radio Frequency Identification)的缩写,即射频识别,俗称电子标签。 RFID射频识别是一种非接触式的自动识别技术,它通过射频信号自动识别目标对象并获取相关数据,识别工作无须人工干预,可工作于各种恶劣环境。 RFID是一种简单的无线系统,只有两个基本器件,该系统用于控制、检测和跟踪物体。系统由一个询问器(或阅读器)和很多应答器(或标签)组成。
RFID包括: 低频125KHz 主要是动物管理 中频 一般指433MHz(这个频段一般也是有源的 也有做高速收费) 高频13.56MHz 公交卡 身份证都是这个频段。 超高频860-960MHz 主要用在物流和停车场管理。 微波2.45GHz ETC用这个频段的多
1.21.10.4.2. NFC
NFC基本概念:
NFC(Near Field Communication)缩写,即近距离无线通讯技术。由飞利浦公司和索尼公司共同开发的一项无线技术。NFC由非接触式射频识别及互联互通技术整合演变而来,可以在移动设备、消费类电子产品、PC和智能控件工具间进行近距离无线通信。NFC提供了一种简单、触控式的解决方案,可以让消费者简单直观地交换信息、访问内容与服务。 NFC技术特点: 1、 在13.56MHz频率运行距离在20公分内; 2、 传输速度可分106Kbits/sec,212 Kbits/sec,424 Kbits/sec; 3、 运作可分主动与被动模式。主动模式需使用电池,也需要独立发射模组;被动模式不需使用电池,但无法独立发射讯号; 4、 已成为ISO/IEC IS 18092国家标准、ETSI TS 102 190标准、EMCA-340标准。
目前主流手机都带有 NFC 是近场通信功能,安卓手机对 NFC 方案全开放,苹果手机暂时开放部分功能.
1.21.10.4.3. RFID/NFC 两种技术的差异
RFID/NFC 比较
- 距离,RFID远,NFC进
- RIFD由读卡器和标签组成,读卡器只能读取标签上的数据。NFC既可以做读卡器,也能提供标签服务,还能实现P2P点对点传输数据。
- 修改,RFID是只读的,NFC上的数据可以修改,例如公交卡
1.21.10.5. 资产投资与份额持有
传统艺术品投资门槛非常高,一是用户不知道从哪些渠道可以投资,二是艺术品价值过高,三是艺术品鉴定难。这导致了投资艺术品门槛过高。 Token 能实现份额化,实现人人参与,人人持有,P2P交易。
例如某机构上链一件艺术品,用户可以投资该艺术品的一定份额,可以转让他持有的权益。且交易去中心化,不受任何机构,管理者的制约。
下面的合约可以展示如何分割艺术品份额,最终达到链上资产的份额分割和持有与交易。
pragma solidity ^0.4.25; /** * @title SafeMath * @dev Math operations with safety checks that revert on error */ library SafeMath { /** * @dev Multiplies two numbers, reverts on overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b); return c; } /** * @dev Integer division of two numbers truncating the quotient, reverts on division by zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { require(b > 0); // Solidity only automatically asserts when dividing by 0 uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } /** * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend). */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { require(b <= a); uint256 c = a - b; return c; } /** * @dev Adds two numbers, reverts on overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a); return c; } /** * @dev Divides two numbers and returns the remainder (unsigned integer modulo), * reverts when dividing by zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { require(b != 0); return a % b; } } contract Ownable { address public owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); constructor() public { owner = msg.sender; } modifier onlyOwner() { require(msg.sender == owner); _; } function transferOwnership(address newOwner) public onlyOwner { require(newOwner != address(0)); emit OwnershipTransferred(owner, newOwner); owner = newOwner; } } contract NetkillerAssetsToken is Ownable { using SafeMath for uint256; string public name; string public symbol; uint public decimals; uint256 public totalSupply; mapping(address => mapping(string => uint256)) internal balances; mapping(string => address) internal tokens; event Transfer(address indexed _from, address indexed _to, string indexed _tokenId); event Burn(address indexed from, string _tokenId); constructor( string tokenName, string tokenSymbol, uint decimalUnits ) public { owner = msg.sender; name = tokenName; symbol = tokenSymbol; decimals = decimalUnits; totalSupply = 0; } function add(address _owner, string _tokenId) onlyOwner returns(bool status){ balances[_owner][_tokenId] = 100 * 10 ** uint256(decimals); tokens[_tokenId] = _owner; totalSupply = totalSupply.add(1); return true; } function balanceOf(address _owner, string _tokenId) constant returns(uint balance){ return balances[_owner][_tokenId]; } function ownerOf(string _tokenId) constant returns (address owner) { return tokens[_tokenId]; } function transfer(address _to, string _tokenId){ address _from = msg.sender; uint256 amount = balances[_from][_tokenId]; transfer(_to, amount, _tokenId); } function transfer(address _to, uint256 _value, string _tokenId){ require(msg.sender == ownerOf(_tokenId)); require(msg.sender != _to); require(_to != address(0)); address _from = msg.sender; uint256 amount = balances[_from][_tokenId]; require(amount >= _value); balances[_from][_tokenId] = balances[_from][_tokenId].sub(_value); balances[_to][_tokenId] = balances[_to][_tokenId].add(_value); tokens[_tokenId] = _to; emit Transfer(_from, _to, _tokenId); } function burn(address _owner, string _tokenId) onlyOwner public returns (bool success) { require(balances[_owner][_tokenId] > 0 && balances[_owner][_tokenId] == 100 * 10 ** uint256(decimals)); balances[_owner][_tokenId] = 0; tokens[_tokenId] = address(0); totalSupply = totalSupply.sub(1); emit Burn(msg.sender, _tokenId); return true; } }
由于 ERC721 不太符合我的需求,所以我结合 ERC20 和 ERC721 写出了我的合约。合约尽量保持了ERC20的使用习惯,函数定义尽量兼容 ERC20。
我们来看下面的构造方法,每个种类的物品一个合约,例如字画,陶瓷,青铜器。
constructor( string tokenName, string tokenSymbol, uint decimalUnits ) public { owner = msg.sender; name = tokenName; symbol = tokenSymbol; decimals = decimalUnits; totalSupply = 0; }
通过下面函数,添加资产到 Token,使链上资产与Token绑定。
function add(address _owner, string _tokenId) onlyOwner returns(bool status){ balances[_owner][_tokenId] = 100 * 10 ** uint256(decimals); tokens[_tokenId] = _owner; totalSupply = totalSupply.add(1); return true; }
balances[_owner][_tokenId] = 100 * 10 ** uint256(decimals); 初始化份额是 100 表示 100%
totalSupply = totalSupply.add(1); 物品件数加一。可以用于统计链上资产的数量。
下面函数是查询资产的持有人
function ownerOf(string _tokenId) constant returns (address owner) { return tokens[_tokenId]; }
下面函数是,权益转让和权益份额转让。
function transfer(address _to, string _tokenId){ address _from = msg.sender; uint256 amount = balances[_from][_tokenId]; transfer(_to, amount, _tokenId); } function transfer(address _to, uint256 _value, string _tokenId){ require(msg.sender == ownerOf(_tokenId)); require(msg.sender != _to); require(_to != address(0)); address _from = msg.sender; uint256 amount = balances[_from][_tokenId]; require(amount >= _value); balances[_from][_tokenId] = balances[_from][_tokenId].sub(_value); balances[_to][_tokenId] = balances[_to][_tokenId].add(_value); tokens[_tokenId] = _to; emit Transfer(_from, _to, _tokenId); }
接下来,我们就是可以开发 Dapp 钱包了,在钱包中实现资产的转移交易。
这个合约可以移植到 EOS 上,Hyperledger Fabric 不可以,因为 Fabric 没有锁的机制,会导致计算出错。
1.21.10.6. 资产上链的
我们希望资产上链适用于任何领域,后面也方便将业务拓展。所以我实现了一个万能合约。以不变应万变,Hyperledger Fabic 链码如下。
package main import ( "fmt" "github.com/hyperledger/fabric/core/chaincode/shim" pb "github.com/hyperledger/fabric/protos/peer" ) type SmartContract struct {} func (s *SmartContract) Init(stub shim.ChaincodeStubInterface) pb.Response { return shim.Success(nil) } func (s *SmartContract) Query(stub shim.ChaincodeStubInterface) pb.Response { return shim.Success(nil) } func (s *SmartContract) Invoke(stub shim.ChaincodeStubInterface) pb.Response { // Retrieve the requested Smart Contract function and arguments function, args := stub.GetFunctionAndParameters() // Route to the appropriate handler function to interact with the ledger appropriately if function == "create" { return s.create(stub, args) } else if function == "find" { return s.find(stub, args) } else if function == "update" { return s.update(stub, args) } else if function == "delete" { return s.delete(stub, args) } return shim.Error("Invalid Smart Contract function name.") } func (s *SmartContract) create(stub shim.ChaincodeStubInterface, args []string) pb.Response { if len(args) != 2 { return shim.Error("Incorrect number of arguments. Expecting 2") } _key := args[0] _data := args[1] if(_data == ""){ return shim.Error("Incorrect string of data") } existAsBytes,err := stub.GetState(_key) if string(existAsBytes) != "" { fmt.Println("Failed to create account, Duplicate key.") return shim.Error("Failed to create account, Duplicate key.") } err = stub.PutState(_key, []byte(_data)) if err != nil { return shim.Error(err.Error()) } fmt.Printf("create %s %s \n", _key, string(_data)) return shim.Success(nil) } func (s *SmartContract) find(stub shim.ChaincodeStubInterface, args []string) pb.Response { if len(args) != 1 { return shim.Error("Incorrect number of arguments. Expecting 1") } _key := args[0] _data, err := stub.GetState(_key) if err != nil { return shim.Error(err.Error()) } if string(_data) == "" { return shim.Error("The key isn't exist.") }else{ fmt.Printf("query %s %s \n", _key, string(_data)) } return shim.Success(_data) } func (s *SmartContract) update(stub shim.ChaincodeStubInterface, args []string) pb.Response { if len(args) != 2 { return shim.Error("Incorrect number of arguments. Expecting 2") } _key := args[0] _data := args[1] if(_data == ""){ return shim.Error("Incorrect string of data") } err := stub.PutState(_key, []byte(_data)) if err != nil { return shim.Error(err.Error()) }else{ fmt.Printf("update %s %s \n", _key, string(_data)) } return shim.Success(nil) } // Deletes an entity from state func (t *SmartContract) delete(stub shim.ChaincodeStubInterface, args []string) pb.Response { if len(args) != 1 { return shim.Error("Incorrect number of arguments. Expecting 1") } _key := args[0] // Delete the key from the state in ledger err := stub.DelState(_key) if err != nil { return shim.Error("Failed to delete state") } return shim.Success(nil) } func main() { err := shim.Start(new(SmartContract)) if err != nil { fmt.Printf("Error creating new Smart Contract: %s", err) } }
链码有四个函数,分别是创建,查找,更新,删除。
if function == "create" { return s.create(stub, args) // 创建 } else if function == "find" { return s.find(stub, args) // 查找 } else if function == "update" { return s.update(stub, args) // 更新 } else if function == "delete" { return s.delete(stub, args) // 删除 }
上链使用 create 方法,函数有两个参数,一个是 key, 另一个是数据。
key 使用 UUID 存储再数据库和链上,同时 UUID 对应通证的
data 是序列化 byte 数据。例如可以使用 json, hession, msgpack 等序列化后的数据。
err = stub.PutState(_key, []byte(_data))
这个链码考虑到前期产品上市,不确定性因素很多,需要更新和删除等等。后期我们可以在数据中设置一个 status 变量,当 status = false 就不在允许数据的删除和更新。
1.21.10.7. 原型设计
1.21.10.7.1. 注册与登录
注册
- 安装APP后,点击注册链接,进入注册界面。
- 用户类型选择选择 “机构”或者”鉴定师”。对于前台只有用户一种角色。
- 输入手机号码,然后获取验证码
- 输入密码
- 点击注册按钮,完成注册
登录,平台提供多重登录方式
- 密码登录
- 验证码登录
- 微信登录
- 指纹登录
- 面部识别登录
- 助记词登录
- 私钥登录
- 闪付卡登录(类似银行卡的 QuickPass 闪付,可以在 Post 机上做 Token 付款,转账等操作)
1.21.10.7.2. 用户角色
防伪查询
查看链上资产
资产交易
评论
分享
用户是权益持有人,当用户(用户端)委托机构负责自己的资产上链。权益持有人一次性获得次产权益100%份额,机构可以查看的机构名下的权益持有人。
1.21.10.7.3. 鉴定师角色
鉴定师是管理机构认证并颁发资质证书人员,艺术品溯源区块链领域,他主要的职责事艺术品鉴定。鉴定师可以挂靠到机构。
如何注册成为鉴定师?
首先进入APP -> 点击『注册 』->阅读条款->点击同意按钮。 输入手机号码,发送验证码,输入密码,重复输入密码,选择『鉴定师』,提交。
注册成功会自动登录,进入完善资料页面,鉴定师需要实名认证,上传身份证信息,鉴定师证书,等等资质文件。鉴定师需要仔细填写每一项,并保证资料的真实性。
提交资料后,等待管理机构审批,管理机构会仔细核对每项数据。如果被拒,需要鉴定师重新填写,再提交。
审批通过前,只能看到鉴定师信息页面。 审批通过后,可以看到,鉴定师信息,已签名资产,未处理资产。
怎样鉴定物品?
鉴定师登录APP后,进入『我的』可以看淡未处理资产菜单,进入菜单可以看到任务列表。点开一件物品,将鉴定结果提交上去,完成鉴定。鉴定师可以在[已签名资产]中查看自己鉴定过的物品。
1.21.10.7.4. 机构角色
机构主要负责资产上链,审查,资产托管
如何注册成为机构
- 首先进入APP -> 点击『注册 』->阅读条款->点击同意按钮。
- 输入手机号码,发送验证码,输入密码,重复输入密码,选择『机构』,提交数据。
- 注册成功后自动登录APP,进入完善机构信息页面,填写机构信息同时上传资质文件,选择区块链应用领域(目前只开通了艺术品领域)
- 提交资料后,等待管理员审批,管理员会仔细核对每项数据。如果被拒,需要鉴定师重新填写,再提交。
审批通过前:机构只能看到机构信息页面
审批通过后:机构拥有机构权限,机构信息,钱包,地址管理,安全,区块链属性配置,资产管理,权益持有人,评论审核,申请溯源标签
1.21.10.7.4.1. 地址管理
地址为收货地址,管理员会邮寄AI智能标签给机构。
进入“我的” -> “设置” -> “地址管理” -> 添加地址
1.21.10.7.4.2. 申请溯源标签
首先机构需要完善收货地址,至少添加一个收货地址。
然后进入『申请溯源标签』,进入“我的” -> “机构” -> “申请溯源标签” 。输入数量,选择类型,选择收货地址,系统会自动计算费用,提交后从钱包中扣取。
标签有以下几种类型
- 易碎纸
- PVC
- 捆扎带
- 玻璃管标签
- 可以根据机构的需求定制AI防伪溯源标签的大小,形状等等。
注意:暂时只能线上申请,线下付款。待钱包功能上线后同意采用 Token 结算。提交信息后,管理员收到信息会主动联系你,完成付款后将标签邮寄给机构的收货地址。
1.21.10.7.4.3. 数字资产上链
数字资产上链是将企业数字化资产上传到区块链上,相比数据库区块链是分布式共识,可以防止信息篡改。
准备工作
- 检查你的App是否是最新版本
- 链接WiFI活4G(需要信号稳定)
- 开启手机或者设备的NFC功能,GPS定位功能,授权摄像头,指纹认证
- 准备好物品信息
- 将溯源标签粘贴到艺术品上
- 信息录入可以使用蓝牙键盘链接手机或者管理员指定专用设备上,以便加快信息录入
- 有些输入可以提供语音输入,或者扫码录入。
资产录入
- 录入物品信息,下一步
- 上传多媒体资料,例如图片,视频,声音等等
- 如果物品有历史事件资料,可以追加这些信息到历史记录中。
- 绑定AI只能溯源防伪标签
- 绑定二维码,方便作品调出
- 设置定位信息 (可选)
- 指定一个或多个鉴定师鉴定物品。
- 指定权益持有人(暂时不可用,权益持有人来自用户端)
- 鉴定师鉴定物品,输入鉴定结果。
- 机构提交信息。
- 管理员审批
- 资产上链。
经过上面几个步骤完成数字资产上链。
注意:管理员没有审批前可以修改资产信息,管理员审批后链上数据无法修改。
1.21.10.7.4.4. 机构成员管理
在整个区块链溯源防伪的过程中,上链的工作量是最大的,所以非一人所为,必须团队完成。 机构可以添加自己的员工,为员工分配令牌,通过令牌登录后可以一同完成资产上链的数据录入。
1.21.10.7.4.5. 资产审核
分支机构可以对员工添加的资产信息逐一核对,核对后点击『提交』按钮,信息将提交至管理员。待管理员在此审批通过,数据便会上链。
无论是机构拒绝还是管理员拒绝,信息都需要重新填写。
信息的审核责任主要在机构,管理员审核通常是看物品是否符合国家法律,法规,政策等等方面。
1.21.10.7.4.6. 鉴定师隶属于机构
当鉴定师为机构鉴定物品后即成为该机构的鉴定师,机构可以查看的机构名下的鉴定师,可以理解为鉴定师挂靠该机构,该机构负责管理鉴定师。
鉴定师负责物品鉴定,并给出鉴定结论。一个物品可以有多名鉴定师同时鉴定。
用户也可以邀请鉴定师鉴定某肩艺术品。
1.21.10.7.4.7. 评论
用户(用户端)可以评论上链的数字资产,增加用户与机构,用户与用于的互动粘合。
防止用户随意提交无意义的信息,所以机构需要审核每一条评论信息。
1.21.10.7.4.8. 安全
1.21.10.7.4.8.1. 指纹认证
开启指纹后,添加物品,审批,等等需要确认的地方都需要指纹验证。
1.21.10.7.4.8.2. 艺术品跟踪
当发现查询作品的GPS坐标与作品所在位置的GPS坐标不在安全范围时,发送报警信息您的艺术品可能被盗,请尽快核实,如需协助,我方可以向公安机关提供:
- 物品所在位置
- 以及查询者的身份信息
1.21.10.7.4.8.3. 令牌管理
为员工指派令牌,机构注册的账号,只能登录一部设备,如果需要多人录入资产信息,就需要使用令牌登录。
管理员会收取一部分费用(Token)
1.21.10.7.5. 钱包
用户数字资产的交易
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论