【区块链技术与应用】(五)
引言
这周恰逢期中,时间仓促,代码上有许多地方可以优化,但也只能留到之后的几次作业上了。
阅读建议:参考和链码样例为写链码前用样例试手内容,与作业相关的内容是“资产管理”之后的代码。
代码参考及学习资料在“参考”一栏中。
参考
https://blog.csdn.net/zekdot/article/details/120397660?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-1-120397660-blog-125920006.pc_relevant_3mothn_strategy_and_data_recovery&spm=1001.2101.3001.4242.2&utm_relevant_index=4
https://blog.csdn.net/weixin_49422491/article/details/125380911
https://blog.csdn.net/weixin_44676392/article/details/87938176
https://blog.csdn.net/weixin_44676392/article/details/87938451
https://blog.csdn.net/qq_41988893/article/details/119706443
fabric提供了fabric-contract-api-go和fabric-chaincode-go两个包来编写链码, 这里以fabric-contract-api-go为例进行链码编写
链码样例
1.声明合约
package mainimport ("errors""fmt""github.com/hyperledger/fabric-contract-api-go/contractapi"
)type SimpleContract struct {contractapi.Contract
}
2.编写合约函数
规则:
1、第一个参数必须是*contractapi.TransactionContext类型
2、函数最多返回两个值,第二个值必须是error类型
// 添加数据
func (sc *SimpleContract) Create(ctx contractapi.TransactionContextInterface, key string, value string) error {existing, err := ctx.GetStub().GetState(key)if err != nil {return errors.New("Unable to interact with world state")}if existing != nil {return fmt.Errorf("Cannot create world state pair with key %s. Already exists", key)}err = ctx.GetStub().PutState(key, []byte(value))if err != nil {return errors.New("Unable to interact with world state")}return nil
}// 读取数据
func (sc *SimpleContract) Read(ctx contractapi.TransactionContextInterface, key string) (string, error) {existing, err := ctx.GetStub().GetState(key)if err != nil {return "", errors.New("Unable to interact with world state")}if existing == nil {return "", fmt.Errorf("Cannot read world state pair with key %s. Does not exist", key)}return string(existing), nil
}
3.创建并启动链码
package mainimport ("github.com/hyperledger/fabric-contract-api-go/contractapi"
)func main() {simpleContract := new(SimpleContract)cc, err := contractapi.NewChaincode(simpleContract)if err != nil {panic(err.Error())}if err := cc.Start(); err != nil {panic(err.Error())}
}
完整合约代码:
package mainimport ("errors""fmt""github.com/hyperledger/fabric-contract-api-go/contractapi"
)
type User struct {}
type SimpleContract struct {contractapi.Contract
}//用户定义Struct// 添加数据
func (sc *SimpleContract) Create(ctx contractapi.TransactionContextInterface, key string, value string) error {existing, err := ctx.GetStub().GetState(key)if err != nil {return errors.New("Unable to interact with world state")}if existing != nil {return fmt.Errorf("Cannot create world state pair with key %s. Already exists", key)}err = ctx.GetStub().PutState(key, []byte(value))if err != nil {return errors.New("Unable to interact with world state")}return nil
}// 读取数据
func (sc *SimpleContract) Read(ctx contractapi.TransactionContextInterface, key string) (string, error) {existing, err := ctx.GetStub().GetState(key)if err != nil {return "", errors.New("Unable to interact with world state")}if existing == nil {return "", fmt.Errorf("Cannot read world state pair with key %s. Does not exist", key)}return string(existing), nil
}func main() {simpleContract := new(SimpleContract)cc, err := contractapi.NewChaincode(simpleContract)if err != nil {panic(err.Error())}if err := cc.Start(); err != nil {panic(err.Error())}
}
一个简单的智能合约就编写完了。
特别注意,这不是一个完整的链码程序,中间缺少了用户定义的struct.
test-network网络测试
启动fabric网络
进入fabric-sample的test-network目录
$ cd fabric-samples/test-network
运行
./network.sh up
启动网络
magpie@Goserver:~/fabric-samples01/test-network$ ./network.sh up
Starting nodes with CLI timeout of '5' tries and CLI delay of '3' seconds and using database 'leveldb' with crypto from 'cryptogen'
LOCAL_VERSION=2.4.6
DOCKER_IMAGE_VERSION=2.4.6
/home/magpie/fabric-samples01/test-network/../bin/cryptogen
Generating certificates using cryptogen tool
Creating Org1 Identities
+ cryptogen generate --config=./organizations/cryptogen/crypto-config-org1.yaml --output=organizations
org1.example.com
+ res=0
Creating Org2 Identities
+ cryptogen generate --config=./organizations/cryptogen/crypto-config-org2.yaml --output=organizations
org2.example.com
+ res=0
Creating Orderer Org Identities
+ cryptogen generate --config=./organizations/cryptogen/crypto-config-orderer.yaml --output=organizations
+ res=0
Generating CCP files for Org1 and Org2
/home/magpie/fabric-samples01/test-network/../bin/configtxgen
Generating Orderer Genesis block
+ configtxgen -profile TwoOrgsOrdererGenesis -channelID system-channel -outputBlock ./system-genesis-block/genesis.block
2022-10-16 06:15:53.999 UTC [common.tools.configtxgen] main -> INFO 001 Loading configuration
2022-10-16 06:15:54.026 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 002 orderer type: etcdraft
2022-10-16 06:15:54.026 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 003 Orderer.EtcdRaft.Options unset, setting to tick_interval:"500ms" election_tick:10 heartbeat_tick:1 max_inflight_blocks:5 snapshot_interval_size:16777216
2022-10-16 06:15:54.026 UTC [common.tools.configtxgen.localconfig] Load -> INFO 004 Loaded configuration: /home/magpie/fabric-samples01/test-network/configtx/configtx.yaml
2022-10-16 06:15:54.028 UTC [common.tools.configtxgen] doOutputBlock -> INFO 005 Generating genesis block
2022-10-16 06:15:54.028 UTC [common.tools.configtxgen] doOutputBlock -> INFO 006 Writing genesis block
+ res=0
[+] Running 7/7⠿ Volume "docker_orderer.example.com" Created 0.0s⠿ Volume "docker_peer0.org1.example.com" Created 0.0s⠿ Volume "docker_peer0.org2.example.com" Created 0.0s⠿ Container peer0.org1.example.com Started 3.4s⠿ Container peer0.org2.example.com Started 1.3s⠿ Container orderer.example.com Started 2.3s⠿ Container cli Started 4.1s
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
70d6427003ae hyperledger/fabric-tools:latest "/bin/bash" 4 seconds ago Up Less than a second cli
0f2f91968493 hyperledger/fabric-peer:latest "peer node start" 4 seconds ago Up 3 seconds 0.0.0.0:9051->9051/tcp, :::9051->9051/tcp, 7051/tcp, 0.0.0.0:9445->9445/tcp, :::9445->9445/tcp peer0.org2.example.com
615cea63009c hyperledger/fabric-orderer:latest "orderer" 4 seconds ago Up 2 seconds 0.0.0.0:7050->7050/tcp, :::7050->7050/tcp, 0.0.0.0:9443->9443/tcp, :::9443->9443/tcp orderer.example.com
1db85f663965 hyperledger/fabric-peer:latest "peer node start" 4 seconds ago Up 1 second 0.0.0.0:7051->7051/tcp, :::7051->7051/tcp, 0.0.0.0:9444->9444/tcp, :::9444->9444/tcp peer0.org1.example.com
d10bd7ff864d hyperledger/explorer:latest "docker-entrypoint.s…" 34 minutes ago Exited (1) 33 minutes ago explorer.mynetwork.com
2ad7a1e8464e hyperledger/explorer-db:latest "docker-entrypoint.s…" 34 minutes ago Up 34 minutes (healthy) 5432/tcp explorerdb.mynetwork.com
efd328836573 portainer/portainer-ce "/portainer" 3 days ago Up About an hour 0.0.0.0:8000->8000/tcp, :::8000->8000/tcp, 0.0.0.0:9000->9000/tcp, :::9000->9000/tcp, 9443/tcp portainer
最终出现以上输出日志则表示网络启动成功,每个加入Fabric网络的Node和User都需要隶属于某个组织,以上网络中包含了两个平行组织—peer0.org1.example.com和
peer0.org2.example.com,它还包括一个作为ordering service维护网络的
orderer.example.com`。
创建channel
上节已经在机器上运行了peer节点和orderer节点,现在可以使用network.sh为Org1和Org2之间创建channel。channel是特定网络成员之间的私有通道,只能被属于该通道的组织使用,并且对网络的其他成员是不可见的。每个channel都有一个单独的区块链账本,属于该通道的组织可以让其下peer加入该通道,以让peer能够存储channel上的帐本并验证账本上的交易。
使用以下命令创建自定义通道testchannel:
$ ./network.sh createChannel -c testchannel
部署chaincode
部署链码前,建议到链码子目录下执行go mod tidy,检查链码调用的包存在。同时,可能需要sudo apt install jq。创建通道后,您可以开始使用智能合约与通道账本交互。智能合约包含管理区块链账本上资产的业务逻辑,由成员运行的应用程序网络可以在账本上调用智能合约创建,更改和转让这些资产。可以通过./network.sh deployCC
命令部署智能合约,但本过程可能会出现很多问题。使用以下命令部署chaincode:
$ ./network.sh deployCC -c testchannel -ccn basic -ccp ../asset-transfer-basic/chaincode-go -ccl go
此命令执行后可能会出现错误:scripts/deployCC.sh: line 114: log.txt: Permission denied
,很明显这是权限不足所致,加上sudo试试:
$sudo ./network.sh deployCC -c testchannel -ccn basic -ccp ../asset-transfer-basic/chaincode-go -ccl go
chaincode
code1
package mainimport ("encoding/json""fmt""log""github.com/hyperledger/fabric-contract-api-go/contractapi"
)// SmartContract provides functions for managing an Asset
type SmartContract struct {contractapi.Contract
}// Asset describes basic details of what makes up a simple asset
type Asset struct {ID string `json:"ID"`Color string `json:"color"`Size int `json:"size"`Owner string `json:"owner"`AppraisedValue int `json:"appraisedValue"`
}// InitLedger adds a base set of assets to the ledger
func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {assets := []Asset{{ID: "asset1", Color: "blue", Size: 5, Owner: "cuteAlgernon", AppraisedValue: 300},{ID: "asset2", Color: "red", Size: 5, Owner: "Biosheep", AppraisedValue: 1000},{ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500},{ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600},{ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700},{ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800},}for _, asset := range assets {assetJSON, err := json.Marshal(asset)if err != nil {return err}err = ctx.GetStub().PutState(asset.ID, assetJSON)if err != nil {return fmt.Errorf("failed to put to world state. %v", err)}}return nil
}// CreateAsset issues a new asset to the world state with given details.
func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {exists, err := s.AssetExists(ctx, id)if err != nil {return err}if exists {return fmt.Errorf("the asset %s already exists", id)}asset := Asset{ID: id,Color: color,Size: size,Owner: owner,AppraisedValue: appraisedValue,}assetJSON, err := json.Marshal(asset)if err != nil {return err}return ctx.GetStub().PutState(id, assetJSON)
}// ReadAsset returns the asset stored in the world state with given id.
func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {assetJSON, err := ctx.GetStub().GetState(id)if err != nil {return nil, fmt.Errorf("failed to read from world state: %v", err)}if assetJSON == nil {return nil, fmt.Errorf("the asset %s does not exist", id)}var asset Asseterr = json.Unmarshal(assetJSON, &asset)if err != nil {return nil, err}return &asset, nil
}// UpdateAsset updates an existing asset in the world state with provided parameters.
func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {exists, err := s.AssetExists(ctx, id)if err != nil {return err}if !exists {return fmt.Errorf("the asset %s does not exist", id)}// overwriting original asset with new assetasset := Asset{ID: id,Color: color,Size: size,Owner: owner,AppraisedValue: appraisedValue,}assetJSON, err := json.Marshal(asset)if err != nil {return err}return ctx.GetStub().PutState(id, assetJSON)
}// DeleteAsset deletes an given asset from the world state.
func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error {exists, err := s.AssetExists(ctx, id)if err != nil {return err}if !exists {return fmt.Errorf("the asset %s does not exist", id)}return ctx.GetStub().DelState(id)
}// AssetExists returns true when asset with given ID exists in world state
func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {assetJSON, err := ctx.GetStub().GetState(id)if err != nil {return false, fmt.Errorf("failed to read from world state: %v", err)}return assetJSON != nil, nil
}// TransferAsset updates the owner field of asset with given id in world state.
func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string) error {asset, err := s.ReadAsset(ctx, id)if err != nil {return err}asset.Owner = newOwnerassetJSON, err := json.Marshal(asset)if err != nil {return err}return ctx.GetStub().PutState(id, assetJSON)
}// GetAllAssets returns all assets found in world state
func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) {// range query with empty string for startKey and endKey does an// open-ended query of all assets in the chaincode namespace.resultsIterator, err := ctx.GetStub().GetStateByRange("", "")if err != nil {return nil, err}defer resultsIterator.Close()var assets []*Assetfor resultsIterator.HasNext() {queryResponse, err := resultsIterator.Next()if err != nil {return nil, err}var asset Asseterr = json.Unmarshal(queryResponse.Value, &asset)if err != nil {return nil, err}assets = append(assets, &asset)}return assets, nil
}func main() {assetChaincode, err := contractapi.NewChaincode(&SmartContract{})if err != nil {log.Panicf("Error creating asset-transfer-basic chaincode: %v", err)}if err := assetChaincode.Start(); err != nil {log.Panicf("Error starting asset-transfer-basic chaincode: %v", err)}
}
code2-1资产管理
package atcc
// 导入必要的依赖
import ("fmt""encoding/json""github.com/hyperledger/fabric-contract-api-go/contractapi"
)type SmartContract struct {contractapi.Contract
}
// 定义资产的数据结构,并使用注解的方式来辅助序列化,marshal函数会使用字母序对key进行排序,这样可以
// 保证其序列化之后具有唯一性,即不会出现导出的json字符串中ID字段在Color字段前面这种情况,
// 这样做的主要原因是为了保证输入输出的唯一性,防止背书验证的时候失败。
type Asset struct {AppraisedValue int `json:"AppraisedValue"`Color string `json:"Color"`ID string `json:"ID"`Owner string `json:"Owner"`Size int `json:"Size"`
}
// 使用数据对链码进行初始化。
func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {assets := []Asset{{ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300},{ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400},{ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500},{ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600},{ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700},{ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800},}for _, asset := range assets {// 序列化资产assetJSON, err := json.Marshal(asset)if err != nil {return err}// 按照id存储序列化后的资产err = ctx.GetStub().PutState(asset.ID, assetJSON)if err != nil {return fmt.Errorf("failed to put to world state. %v", err)}}return nil
}
// 通过传入参数来创建一个账本上不存在的资产,这里有在后面实现的方法AssetExists来检查是否存在某个key为id的资产。
func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {exists, err := s.AssetExists(ctx, id)if err != nil {return err}if exists {return fmt.Errorf("the asset %s already exists", id)}asset := Asset{ID: id,Color: color,Size: size,Owner: owner,AppraisedValue: appraisedValue,}assetJSON, err := json.Marshal(asset)if err != nil {return err}return ctx.GetStub().PutState(id, assetJSON)
}
// 从账本中读取资产,调用GetState来实现
func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {assetJSON, err := ctx.GetStub().GetState(id)if err != nil {return nil, fmt.Errorf("failed to read from world state: %v", err)}if assetJSON == nil {return nil, fmt.Errorf("the asset %s does not exist", id)}var asset Asseterr = json.Unmarshal(assetJSON, &asset)if err != nil {return nil, err}return &asset, nil
}
// 更新资产,这里实现逻辑是根据传入参数创建一个新的资产并序列化,然后覆盖原来的资产。
func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {exists, err := s.AssetExists(ctx, id)if err != nil {return err}if !exists {return fmt.Errorf("the asset %s does not exist", id)}// overwriting original asset with new assetasset := Asset{ID: id,Color: color,Size: size,Owner: owner,AppraisedValue: appraisedValue,}assetJSON, err := json.Marshal(asset)if err != nil {return err}return ctx.GetStub().PutState(id, assetJSON)
}
// 删除资产,直接调用DelState函数来实现删除。
func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error {exists, err := s.AssetExists(ctx, id)if err != nil {return err}if !exists {return fmt.Errorf("the asset %s does not exist", id)}return ctx.GetStub().DelState(id)
}
// 检查id对应的资产是否存在,判断能不能读取出value即可。
func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {assetJSON, err := ctx.GetStub().GetState(id)if err != nil {return false, fmt.Errorf("failed to read from world state: %v", err)}return assetJSON != nil, nil
}
// 资产转移,实质是修改资产结构体的owner字段。
func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string) error {asset, err := s.ReadAsset(ctx, id)if err != nil {return err}asset.Owner = newOwnerassetJSON, err := json.Marshal(asset)if err != nil {return err}return ctx.GetStub().PutState(id, assetJSON)
}
// 读取全部资产,调用GetStateByRange函数来获取账本上的全部记录。
func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) {// range query with empty string for startKey and endKey does an// open-ended query of all assets in the chaincode namespace.resultsIterator, err := ctx.GetStub().GetStateByRange("", "")if err != nil {return nil, err}defer resultsIterator.Close()var assets []*Assetfor resultsIterator.HasNext() {queryResponse, err := resultsIterator.Next()if err != nil {return nil, err}var asset Asseterr = json.Unmarshal(queryResponse.Value, &asset)if err != nil {return nil, err}assets = append(assets, &asset)}return assets, nil
}
code2-2
assetsManager.go
package main
import ("log""github.com/hyperledger/fabric-contract-api-go/contractapi""main/atcc"
)func main() {assetChaincode, err := contractapi.NewChaincode(&atcc.SmartContract{})if err != nil {log.Panicf("Error creating atcc chaincode: %v", err)}if err := assetChaincode.Start(); err != nil {log.Panicf("Error starting atcc chaincode: %v", err)}
}
构建链码
go mod tidy
go mod vendor
使用chaincode
初始化账本
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$ORDERER_CA" -C $CHANNEL_NAME -n ${CC_NAME} --peerAddresses localhost:7051 --tlsRootCertFiles organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt --isInit -c '{"function":"InitLedger","Args":[]}'
获取当前资产
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$ORDERER_CA" -C $CHANNEL_NAME -n ${CC_NAME} --peerAddresses localhost:7051 --tlsRootCertFiles organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"function":"GetAllAssets","Args":[]}'
总结
随着实验次数的增多,肉眼可见的是文件夹越来越混乱——比如这次的网络,需要在前几次实验中找到内容,而每次实验都涉及到文件创建、下载、删除,有时哪怕一次实验,都会鼓捣出很多奇奇怪怪的文件夹,有时会出现这样一种状况:我知道网络是通的,但不知道是那个文件能够跑通,毕竟,如果按照教程顺利过关,是对具体内容没有深刻印象的,直到需要再次使用的时候。
链码编写(改写)难度不算特别大,但如果 涉及到网络上跑通,再加之账本的增删改查,如果找不到之前实验的基础内容,无异于再做一次。
【区块链技术与应用】(五)相关推荐
- 区块链技术与应用实验报告(实验五)
文章目录 区块链技术与应用实验报告(实验五) 关于作者 作者介绍 一.实验目的 二.实验原理简介 三.实验环境 四.实验步骤 1.解压缩即可完成安装. 2. 生成快捷方式 3.修改 bitcoin-q ...
- 区块链技术指南学习(五)双花
区块链技术通过区块链接形成的时间戳技术加上验证比特币是否满足UTXO( 未花费交易) 和数字签名, 有效避免了双重支付的问题. (也就是说给虚拟货币以现实版的唯一性和不可逆性,只需要时间戳技术.那么问 ...
- 区块链快速入门(五)——区块链技术的演化
一.区块链技术的发展 比特币区块链面向转账场景,支持简单的脚本计算.如果引入更多复杂的计算逻辑,将能支持更多应用场景,即智能合约(Smart Contract).智能合约可以提供除了货币交易功能外更灵 ...
- 区块链技术之以太坊ETH白皮书
链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载. 基于中本聪区块链比特币1.0之后,天才少年Vitalik Buterin(V神)在2013年年末发布了以太坊白皮书,其实 ...
- 一篇文章让你了解区块链技术的发展阶段
链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载. 区块链是由一系列技术实现的全新去中心化经济组织模式,2009年诞生于比特币系统的构建,2017年成为全球经济热点,但区块 ...
- 揭秘高盛区块链报告:区块链技术在五大领域从理论走向实践 | 附报告下载
来源:亿欧 概要:目前对区块链潜在作用讨论的焦点在于使用分布式账本建立去中心化市场,并削弱现有中间商的控制权,但区块链的潜力比这种单薄的说法来得更加细致也更加深远. 最近,区块链很火,资本市场沾上&q ...
- 什么是区块链技术?初学者指南
本文翻译自:https://blockgeeks.com/guides/what-is-blockchain-technology/ 如果涉及侵权,还望告知! 区块链技术是新的互联网吗? 区块链无疑是 ...
- BlockChain:《区块链技术在医疗领域应用分析》—中投顾问《2016-2020年区块链技术深度调研及投资前景预测报告》听课笔记
BlockChain:<区块链技术在医疗领域应用分析>听课笔记 导读 医疗行业接纳区块链技术的时间相对来说比较晚,不过并没错过应用区块链的时机.区块链技术正在改变全球医疗行业,逐 ...
- 如何学习区块链技术?
2018年春节最火热的概念应该就是区块链了,从百度的莱茨狗和网易星球刷屏朋友圈,到3点钟区块链无眠群的大火,大佬们纷纷进军区块链,不了解区块链好像错过一个时代. 这里把学习区块链过程中的一些资料进行索 ...
- 如何使用区块链技术进行项目开发
区块链是目前一个比较热门的新概念,蕴含了技术与金融两层概念.从技术角度来看,这是一个牺牲一致性效率且保证最终一致性的的分布式的数据库,当然这是比较片面的.从经济学的角度来看,这种容错能力很强的点对点网 ...
最新文章
- MongoDB获得短暂的
- Java SPI机制分析
- 谈慎独2017-12-19
- JavaScript正则表达式-基础入门
- C++一天一个程序(四)
- 四 akka学习 四种多线程的解决方案
- matlab中modred,计算机仿真技术(中南大学)3系统模型及转换.ppt
- svn 服务器创建文件夹,svn服务器创建文件夹
- 移动跨平台框架ReactNative活动指示器组件【11】
- SCI英语论文长难句攻略
- 原创程序|基于GDAL的遥感影像批量处理工具介绍(三)
- 如何像「西瓜足迹」一样,将用户导流到公众号上变现?
- 在美国纽约哪里有西联汇款?本文提供美国纽约法拉盛西联汇款网点代理地址
- 【无关技术·朋友圈朝花朝拾】月相
- 使用ale-import-roms导入atari的rom时RuntimeError问题解决办法
- 会员管理小程序实战开发教程-消费记录功能
- 获取时间段内所有周次及其起讫日期
- java什么是网络接口_Java语言:什么叫面向接口编程(来自网络)
- python空气质量指数计算_空气质量指数数据分析可视化
- 如何用python实现电商订单接口API
热门文章
- 后端面经(已收到腾讯实习offer)
- python白名单验证是什么意思_luminati python+selenium使用方式(白名单和账号密码验证方式)...
- 问题-fuxi job failed ,cased by:Invalid decimal format.
- 马尔科夫链原理介绍【通俗易懂】
- Unet——pytorch
- linux quota硬盘,Linux系统中quota磁盘命令的相关使用解析
- socket阻塞和非阻塞有哪些影响
- R与Excel之数据分析
- java se版本_补装老版本的Java SE
- 论文翻译-Three Stream 3D CNN with SE Block for Micro- Expression Recognition