https://blog.csdn.net/lakersssss24/article/details/125762826?spm=1001.2014.3001.5501
https://blog.csdn.net/lakersssss24/article/details/126434147
https://blog.csdn.net/lakersssss24/article/details/126671408?spm=1001.2101.3001.6650.3&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-3-126671408-blog-126434147.pc_relevant_3mothn_strategy_and_data_recovery&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-3-126671408-blog-126434147.pc_relevant_3mothn_strategy_and_data_recovery&utm_relevant_index=4

提前准备

sudo apt-get update  更新源 sudo apt-get install ssh 安装远程客户端sudo apt-get install curl 安装命令行工具sudo apt-get install git 安装gitsudo apt-get install gcc 安装gccsudo apt-get install vim 安装vim文件编辑器sudo apt-get install make 安装makesudo apt-get install net-tools 安装网络工具sudo apt-get install net-tools  安装mousepad 类似于windows的记事本
./bootstrap.sh

如何在Ubuntu 20.04上安装CouchDB

couchDB安装 https://blog.csdn.net/TU_Dresden/article/details/126864418

实验一

network

./network.sh up


./network.sh up createChannel -s couchdb

./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-go -ccl go


export PATH=${PWD}/../bin:$PATHexport FABRIC_CFG_PATH=$PWD/../config/export CORE_PEER_TLS_ENABLED=trueexport CORE_PEER_LOCALMSPID="Org1MSP" export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crtexport CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/mspexport CORE_PEER_ADDRESS=localhost:7051
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n basic --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt" -c '{"function":"InitLedger","Args":[""]}'


restart

./network.sh up createChannel -ca -s couchdb

gin模板

package main import ("fmt""github.com/gin-gonic/gin") type Stu struct {Name string `form:"name"`Id   string `form:"id"`Age  string `form:"age"`} func main() {r := gin.Default()var stu Stur1 := r.Group("/fabric2.4")r1.POST("/setstu", func(c *gin.Context) {//var stu Stuc.ShouldBind(&stu)c.JSON(200, stu)fmt.Println("stu:", stu)})r1.POST("/ok1", func(c *gin.Context) {c.JSON(200, "ok1")})r.Run(":8080") // 监听并在 0.0.0.0:8080 上启动服务 }

链码的只要功能部分:

package chaincode import ("encoding/json""fmt" "github.com/hyperledger/fabric-contract-api-go/contractapi") // SmartContract provides functions for managing an Assettype SmartContract struct {contractapi.Contract} // Asset describes basic details of what makes up a simple asset//Insert struct field in alphabetic order => to achieve determinism across languages// golang keeps the order when marshal to json but doesn't order automaticallytype Asset struct {AppraisedValue int    `json:"AppraisedValue"`Color          string `json:"Color"`ID             string `json:"ID"`Owner          string `json:"Owner"`Size           int    `json:"Size"`} // InitLedger adds a base set of assets to the ledgerfunc (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} 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 statefunc (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, and returns the old owner.func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string) (string, error) {asset, err := s.ReadAsset(ctx, id)if err != nil {return "", err} oldOwner := asset.Ownerasset.Owner = newOwner assetJSON, err := json.Marshal(asset)if err != nil {return "", err} err = ctx.GetStub().PutState(id, assetJSON)if err != nil {return "", err} return oldOwner, nil} // GetAllAssets returns all assets found in world statefunc (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}

查看虚拟机的IP和地址:

ifconfig

gin框架

package main import ("bytes""crypto/x509""encoding/json""fmt""github.com/gin-gonic/gin""github.com/hyperledger/fabric-gateway/pkg/client""github.com/hyperledger/fabric-gateway/pkg/identity""google.golang.org/grpc""google.golang.org/grpc/credentials""io/ioutil""path""time") const (mspID         = "Org1MSP"cryptoPath    = "./peerOrganizations/org1.example.com"certPath      = cryptoPath + "/users/User1@org1.example.com/msp/signcerts/cert.pem"keyPath       = cryptoPath + "/users/User1@org1.example.com/msp/keystore/"tlsCertPath   = cryptoPath + "/peers/peer0.org1.example.com/tls/ca.crt"peerEndpoint  = "192.168.136.130:7051"gatewayPeer   = "peer0.org1.example.com"channelName   = "mychannel"chaincodeName = "basic") type Asset struct {AppraisedValue int    `form:"appraisedValue" json:"appraisedValue" `Color          string `form:"color" json:"color"`ID             string `form:"id" json:"id"`Owner          string `form:"owner" json:"owner"`Size           int    `form:"size" json:"size"`} func main() {// The gRPC client connection should be shared by all Gateway connections to this endpointclientConnection := newGrpcConnection()defer clientConnection.Close() id := newIdentity()sign := newSign() // Create a Gateway connection for a specific client identitygateway, err := client.Connect(id,client.WithSign(sign),client.WithClientConnection(clientConnection),// Default timeouts for different gRPC callsclient.WithEvaluateTimeout(5*time.Second),client.WithEndorseTimeout(15*time.Second),client.WithSubmitTimeout(5*time.Second),client.WithCommitStatusTimeout(1*time.Minute),)if err != nil {panic(err)}defer gateway.Close()network := gateway.GetNetwork(channelName)contract := network.GetContract(chaincodeName) r := gin.Default()r1 := r.Group("/fabric2.4")r1.POST("/CreateAsset", func(c *gin.Context) {var asset Assetc.ShouldBind(&asset)c.JSON(200, asset)marshal, _ := json.Marshal(asset)fmt.Println(string(marshal))fmt.Println("asset:", asset)})r1.POST("/GetAllAssets", func(c *gin.Context) {result := getAllAssets(contract)c.JSON(200, result)})r.Run(":8080") // 监听并在 0.0.0.0:8080 上启动服务 } // Evaluate a transaction to query ledger state.func getAllAssets(contract *client.Contract) string {fmt.Println("Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger") evaluateResult, err := contract.EvaluateTransaction("GetAllAssets")if err != nil {panic(fmt.Errorf("failed to evaluate transaction: %w", err))}result := formatJSON(evaluateResult) fmt.Printf("*** Result:%s\n", result) return string(evaluateResult)} // newGrpcConnection creates a gRPC connection to the Gateway server.func newGrpcConnection() *grpc.ClientConn {certificate, err := loadCertificate(tlsCertPath)if err != nil {panic(err)} certPool := x509.NewCertPool()certPool.AddCert(certificate)transportCredentials := credentials.NewClientTLSFromCert(certPool, gatewayPeer) connection, err := grpc.Dial(peerEndpoint, grpc.WithTransportCredentials(transportCredentials))if err != nil {panic(fmt.Errorf("failed to create gRPC connection: %w", err))} return connection} // newIdentity creates a client identity for this Gateway connection using an X.509 certificate.func newIdentity() *identity.X509Identity {certificate, err := loadCertificate(certPath)if err != nil {panic(err)} id, err := identity.NewX509Identity(mspID, certificate)if err != nil {panic(err)} return id} func loadCertificate(filename string) (*x509.Certificate, error) {certificatePEM, err := ioutil.ReadFile(filename)if err != nil {return nil, fmt.Errorf("failed to read certificate file: %w", err)}return identity.CertificateFromPEM(certificatePEM)} // newSign creates a function that generates a digital signature from a message digest using a private key.func newSign() identity.Sign {files, err := ioutil.ReadDir(keyPath)if err != nil {panic(fmt.Errorf("failed to read private key directory: %w", err))}privateKeyPEM, err := ioutil.ReadFile(path.Join(keyPath, files[0].Name())) if err != nil {panic(fmt.Errorf("failed to read private key file: %w", err))} privateKey, err := identity.PrivateKeyFromPEM(privateKeyPEM)if err != nil {panic(err)} sign, err := identity.NewPrivateKeySign(privateKey)if err != nil {panic(err)} return sign} // Format JSON datafunc formatJSON(data []byte) string {var prettyJSON bytes.Bufferif err := json.Indent(&prettyJSON, data, " ", ""); err != nil {panic(fmt.Errorf("failed to parse JSON: %w", err))}return prettyJSON.String()}

链码

package chaincode import ("encoding/json""fmt" "github.com/hyperledger/fabric-contract-api-go/contractapi") // SmartContract provides functions for managing an Assettype SmartContract struct {contractapi.Contract} // Asset describes basic details of what makes up a simple asset//Insert struct field in alphabetic order => to achieve determinism across languages// golang keeps the order when marshal to json but doesn't order automaticallytype Asset struct {AppraisedValue int    `json:"AppraisedValue"`Color          string `json:"Color"`ID             string `json:"ID"`Owner          string `json:"Owner"`Size           int    `json:"Size"`} // InitLedger adds a base set of assets to the ledgerfunc (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} 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 statefunc (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, and returns the old owner.func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string) (string, error) {asset, err := s.ReadAsset(ctx, id)if err != nil {return "", err} oldOwner := asset.Ownerasset.Owner = newOwner assetJSON, err := json.Marshal(asset)if err != nil {return "", err} err = ctx.GetStub().PutState(id, assetJSON)if err != nil {return "", err} return oldOwner, nil} // GetAllAssets returns all assets found in world statefunc (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}

postman


测试自家网站:

本地查询:


自己的github:



实验二

gin

package main import ("bytes""crypto/x509""encoding/json""fmt""github.com/gin-gonic/gin""github.com/hyperledger/fabric-gateway/pkg/client""github.com/hyperledger/fabric-gateway/pkg/identity""google.golang.org/grpc""google.golang.org/grpc/credentials""io/ioutil""path""time") const (mspID         = "Org1MSP"cryptoPath    = "./peerOrganizations/org1.example.com"certPath      = cryptoPath + "/users/User1@org1.example.com/msp/signcerts/cert.pem"keyPath       = cryptoPath + "/users/User1@org1.example.com/msp/keystore/"tlsCertPath   = cryptoPath + "/peers/peer0.org1.example.com/tls/ca.crt"peerEndpoint  = "192.168.136.130:7051"gatewayPeer   = "peer0.org1.example.com"channelName   = "mychannel"chaincodeName = "basic") type Asset struct {AppraisedValue int    `form:"appraisedValue" json:"appraisedValue" `Color          string `form:"color" json:"color"`ID             string `form:"id" json:"id"`Owner          string `form:"owner" json:"owner"`Size           int    `form:"size" json:"size"`} func main() {// The gRPC client connection should be shared by all Gateway connections to this endpointclientConnection := newGrpcConnection()defer clientConnection.Close() id := newIdentity()sign := newSign() // Create a Gateway connection for a specific client identitygateway, err := client.Connect(id,client.WithSign(sign),client.WithClientConnection(clientConnection),// Default timeouts for different gRPC callsclient.WithEvaluateTimeout(5*time.Second),client.WithEndorseTimeout(15*time.Second),client.WithSubmitTimeout(5*time.Second),client.WithCommitStatusTimeout(1*time.Minute),)if err != nil {panic(err)}defer gateway.Close()network := gateway.GetNetwork(channelName)contract := network.GetContract(chaincodeName) r := gin.Default()r1 := r.Group("/fabric2.4.2")r1.POST("/Init", func(c *gin.Context) {initLedger(contract)c.JSON(200, "init ok!")})r.Run(":8080") // 监听并在 0.0.0.0:8080 上启动服务 } // Evaluate a transaction to query ledger state.func getAllAssets(contract *client.Contract) string {fmt.Println("Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger") evaluateResult, err := contract.EvaluateTransaction("GetAllAssets")if err != nil {panic(fmt.Errorf("failed to evaluate transaction: %w", err))}result := formatJSON(evaluateResult) fmt.Printf("*** Result:%s\n", result) return string(evaluateResult)} // newGrpcConnection creates a gRPC connection to the Gateway server.func newGrpcConnection() *grpc.ClientConn {certificate, err := loadCertificate(tlsCertPath)if err != nil {panic(err)} certPool := x509.NewCertPool()certPool.AddCert(certificate)transportCredentials := credentials.NewClientTLSFromCert(certPool, gatewayPeer) connection, err := grpc.Dial(peerEndpoint, grpc.WithTransportCredentials(transportCredentials))if err != nil {panic(fmt.Errorf("failed to create gRPC connection: %w", err))} return connection} // newIdentity creates a client identity for this Gateway connection using an X.509 certificate.func newIdentity() *identity.X509Identity {certificate, err := loadCertificate(certPath)if err != nil {panic(err)} id, err := identity.NewX509Identity(mspID, certificate)if err != nil {panic(err)} return id} func loadCertificate(filename string) (*x509.Certificate, error) {certificatePEM, err := ioutil.ReadFile(filename)if err != nil {return nil, fmt.Errorf("failed to read certificate file: %w", err)}return identity.CertificateFromPEM(certificatePEM)} // newSign creates a function that generates a digital signature from a message digest using a private key.func newSign() identity.Sign {files, err := ioutil.ReadDir(keyPath)if err != nil {panic(fmt.Errorf("failed to read private key directory: %w", err))}privateKeyPEM, err := ioutil.ReadFile(path.Join(keyPath, files[0].Name())) if err != nil {panic(fmt.Errorf("failed to read private key file: %w", err))} privateKey, err := identity.PrivateKeyFromPEM(privateKeyPEM)if err != nil {panic(err)} sign, err := identity.NewPrivateKeySign(privateKey)if err != nil {panic(err)} return sign} // Format JSON datafunc formatJSON(data []byte) string {var prettyJSON bytes.Bufferif err := json.Indent(&prettyJSON, data, " ", ""); err != nil {panic(fmt.Errorf("failed to parse JSON: %w", err))}return prettyJSON.String()}

New asset部分:
把下图代码:

改为这个:

r1.POST("/CreateAsset", func(c *gin.Context) {var asset Assetc.ShouldBind(&asset)c.JSON(200, asset)marshal, _ := json.Marshal(asset)CreateAsset(contract, asset)fmt.Println("存入成功!存入的数据是:", string(marshal))//fmt.Println("asset:", asset)})

函数:

func CreateAsset(contract *client.Contract, asset Asset) string {evaluateResult, err := contract.SubmitTransaction("CreateAsset", asset.ID, asset.Color, strconv.Itoa(asset.Size), asset.Owner, strconv.Itoa(asset.AppraisedValue))if err != nil {panic(fmt.Errorf("failed to evaluate transaction: %w", err))}result := formatJSON(evaluateResult) fmt.Printf("*** Result:%s\n", result) return string(evaluateResult)}


总结

本次实验内容内容不多,难度颇大。
其一在于作者默认搭好框架,所以需要按照前文做出适配。
其二是网络搭好之后的查询,如果看了前文,可以知道作者是修改了

https://github.com/hyperledger/fabric-samples/tree/main/asset-transfer-basic

部分的代码,也就是本文的gin框架部分。修改也很多,

然后就是代码,代码在我这里经常报错,然后为了解决一个bug往往会搞出更多的问题,尤其是网络接口的占用,就在写文档的现在,我的7051端口莫名被占用了,为了更好完成期末任务,决定找一个正确而清晰的文档从头开始配置。(很大的原因是这几次作业下来整个fabric文件夹臃肿不堪,fabric-samples就有好几个,隔着几周时间回去看,有能运行的,也有当初是坑,后来没删的,总之,归零重启更有效)

在做两个实验之前,我先按照作者的思路配置了环境,主要是启动网络。
其次是一种很新的工具,postman,现在还没有完全摸清这个软件的用途,目前仅做查询网址用途。

第二个查询部分没有把assets查出来,可能是某个方面出了bug,之后会重新启用虚拟机,然后从头开始做一遍。