主頁 > 區塊鏈 > Hyperledger Fabric 智能合約開發及 fabric-sdk-go/fabric-gateway 使用示例

Hyperledger Fabric 智能合約開發及 fabric-sdk-go/fabric-gateway 使用示例

2022-07-12 17:45:34 區塊鏈

前言

在上個實驗 Hyperledger Fabric 多組織多排序節點部署在多個主機上 中,我們已經實作了多組織多排序節點部署在多個主機上,但到目前為止,我們所有的實驗都只是研究了聯盟鏈的網路配置方法(盡管這確實是重難點),而沒有考慮具體的應用開發,本文將在前面實驗的基礎上,首先嘗試使用 Go 語言開發了一個作業室聯盟鏈的專案資訊智能合約,并成功將其部署至聯盟鏈上;然后依據官方示例,使用 fabric-gateway 模塊實作了一個能夠管理專案資訊智能合約的客戶端;之后對比了 fabric-gateway 模塊和 fabric-sdk-* 模塊各自的優缺點,分析官方示例原始碼實作了通過 fabric-sdk-* 模塊管理整個聯盟鏈網路,一般語境下,本文默認智能合約等于鏈碼,

作業準備

本文作業

以三組織三排序節點的方式啟動 Hyperledger Fabric 網路,實驗共包含四個組織—— council 、 soft 、 web 、 hard , 其中 council 組織為網路提供 TLS-CA 服務,并且運行維護著三個 orderer 服務;其余每個組織都運行維護著一個 peer 節點、一個 admin 用戶和一個 user 用戶,網路結構為(實驗代碼已上傳至:https://github.com/wefantasy/FabricLearn 的 6_ContractGatewayAndSDK 下):

運行埠 說明
council.ifantasy.net 7050 council 組織的 CA 服務, 為聯盟鏈網路提供 TLS-CA 服務
orderer1.council.ifantasy.net 7051 council 組織的 orderer1 服務
orderer1.council.ifantasy.net 7052 council 組織的 orderer1 服務的 admin 服務
orderer2.council.ifantasy.net 7054 council 組織的 orderer2 服務
orderer2.council.ifantasy.net 7055 council 組織的 orderer2 服務的 admin 服務
orderer3.council.ifantasy.net 7057 council 組織的 orderer3 服務
orderer3.council.ifantasy.net 7058 council 組織的 orderer3 服務的 admin 服務
soft.ifantasy.net 7250 soft 組織的 CA 服務, 包含成員: peer1 、 admin1 、user1
peer1.soft.ifantasy.net 7251 soft 組織的 peer1 成員節點
web.ifantasy.net 7350 web 組織的 CA 服務, 包含成員: peer1 、 admin1 、user1
peer1.web.ifantasy.net 7351 web 組織的 peer1 成員節點
hard.ifantasy.net 7450 hard 組織的 CA 服務, 包含成員: peer1 、 admin1 、user1
peer1.hard.ifantasy.net 7451 hard 組織的 peer1 成員節點

實驗準備

本文網路結構直接將 Hyperledger Fabric無排序組織以Raft協議啟動多個Orderer服務、TLS組織運行維護Orderer服務 中創建的 4-2_RunOrdererByCouncil 復制為 6_ContractGatewayAndSDK 并修改(建議直接將本案例倉庫 FabricLearn 下的 6_ContractGatewayAndSDK 目錄拷貝到本地運行),文中大部分命令在 Hyperledger Fabric定制聯盟鏈網路工程實踐 中已有介紹因此不會詳細說明,默認情況下,所有命令皆在 6_ContractGatewayAndSDK 根目錄下執行,在開始后面的實驗前按照以下命令啟動基礎實驗網路:

  1. 設定DNS(如果未設定): ./setDNS.sh
  2. 設定環境變數: source envpeer1soft
  3. 啟動CA網路: ./0_Restart.sh

本實驗初始 docker 網路為:
初始 docker 網路

基礎環境

注冊用戶

直接運行根目錄下的 1_RegisterUser.sh 即可完成本實驗所需用戶的注冊,以往我們每個組織只有一個 peer 節點和一個 admin 節點,但這些節點都不適合為客戶端所用,因此基礎環境的改變主要包含了為每個組織新增一個 client 型別的用戶,以 soft 組織為例,其注冊用戶命令為:

echo "Working on soft"
export FABRIC_CA_CLIENT_TLS_CERTFILES=$LOCAL_CA_PATH/soft.ifantasy.net/ca/crypto/ca-cert.pem
export FABRIC_CA_CLIENT_HOME=$LOCAL_CA_PATH/soft.ifantasy.net/ca/admin
fabric-ca-client enroll -d -u https://ca-admin:[email protected]:7250
# client 型別用戶注冊
fabric-ca-client register -d --id.name user1 --id.secret user1 --id.type client -u https://soft.ifantasy.net:7250
fabric-ca-client register -d --id.name peer1 --id.secret peer1 --id.type peer -u https://soft.ifantasy.net:7250
fabric-ca-client register -d --id.name admin1 --id.secret admin1 --id.type admin -u https://soft.ifantasy.net:7250

組織證書構建

直接運行根目錄下的 2_EnrollUser.sh 即可完成本實驗所需證書的構建,每個組織主要增加了 client 型別用戶的證書構建每個注冊用戶單元組態檔 config.yaml ,以 soft 組織為例,其生成組織證書的命令為:

echo "Start Soft============================="
# 新增
echo "Enroll User1"
export FABRIC_CA_CLIENT_HOME=$LOCAL_CA_PATH/soft.ifantasy.net/registers/user1
export FABRIC_CA_CLIENT_TLS_CERTFILES=$LOCAL_CA_PATH/soft.ifantasy.net/assets/ca-cert.pem
export FABRIC_CA_CLIENT_MSPDIR=msp
fabric-ca-client enroll -d -u https://user1:[email protected]:7250

echo "Enroll Admin1"
export FABRIC_CA_CLIENT_HOME=$LOCAL_CA_PATH/soft.ifantasy.net/registers/admin1
export FABRIC_CA_CLIENT_TLS_CERTFILES=$LOCAL_CA_PATH/soft.ifantasy.net/assets/ca-cert.pem
export FABRIC_CA_CLIENT_MSPDIR=msp
fabric-ca-client enroll -d -u https://admin1:[email protected]:7250
mkdir -p $LOCAL_CA_PATH/soft.ifantasy.net/registers/admin1/msp/admincerts
cp $LOCAL_CA_PATH/soft.ifantasy.net/registers/admin1/msp/signcerts/cert.pem $LOCAL_CA_PATH/soft.ifantasy.net/registers/admin1/msp/admincerts/cert.pem

echo "Enroll Peer1"
export FABRIC_CA_CLIENT_HOME=$LOCAL_CA_PATH/soft.ifantasy.net/registers/peer1
export FABRIC_CA_CLIENT_TLS_CERTFILES=$LOCAL_CA_PATH/soft.ifantasy.net/assets/ca-cert.pem
export FABRIC_CA_CLIENT_MSPDIR=msp
fabric-ca-client enroll -d -u https://peer1:[email protected]:7250
# for TLS
export FABRIC_CA_CLIENT_MSPDIR=tls-msp
export FABRIC_CA_CLIENT_TLS_CERTFILES=$LOCAL_CA_PATH/soft.ifantasy.net/assets/tls-ca-cert.pem
fabric-ca-client enroll -d -u https://peer1soft:[email protected]:7050 --enrollment.profile tls --csr.hosts peer1.soft.ifantasy.net
cp $LOCAL_CA_PATH/soft.ifantasy.net/registers/peer1/tls-msp/keystore/*_sk $LOCAL_CA_PATH/soft.ifantasy.net/registers/peer1/tls-msp/keystore/key.pem
mkdir -p $LOCAL_CA_PATH/soft.ifantasy.net/registers/peer1/msp/admincerts
cp $LOCAL_CA_PATH/soft.ifantasy.net/registers/admin1/msp/signcerts/cert.pem $LOCAL_CA_PATH/soft.ifantasy.net/registers/peer1/msp/admincerts/cert.pem

mkdir -p $LOCAL_CA_PATH/soft.ifantasy.net/msp/admincerts
mkdir -p $LOCAL_CA_PATH/soft.ifantasy.net/msp/cacerts
mkdir -p $LOCAL_CA_PATH/soft.ifantasy.net/msp/tlscacerts
mkdir -p $LOCAL_CA_PATH/soft.ifantasy.net/msp/users
cp $LOCAL_CA_PATH/soft.ifantasy.net/assets/ca-cert.pem $LOCAL_CA_PATH/soft.ifantasy.net/msp/cacerts/
cp $LOCAL_CA_PATH/soft.ifantasy.net/assets/tls-ca-cert.pem $LOCAL_CA_PATH/soft.ifantasy.net/msp/tlscacerts/
cp $LOCAL_CA_PATH/soft.ifantasy.net/registers/admin1/msp/signcerts/cert.pem $LOCAL_CA_PATH/soft.ifantasy.net/msp/admincerts/cert.pem

cp $LOCAL_ROOT_PATH/config/config-msp.yaml $LOCAL_CA_PATH/soft.ifantasy.net/msp/config.yaml
# 新增
cp $LOCAL_ROOT_PATH/config/config-msp.yaml $LOCAL_CA_PATH/soft.ifantasy.net/registers/user1/msp/config.yaml
cp $LOCAL_ROOT_PATH/config/config-msp.yaml $LOCAL_CA_PATH/soft.ifantasy.net/registers/admin1/msp/config.yaml
cp $LOCAL_ROOT_PATH/config/config-msp.yaml $LOCAL_CA_PATH/soft.ifantasy.net/registers/peer1/msp/config.yaml
echo "End Soft============================="

為了配合使用每個用戶的單元組態檔,需要將所有用戶 msp 目錄下的 cacerts/council-ifantasy-net-7050.pem 檔案名修改為 cacerts/ca-cert.pem ,因此在 2_EnrollUser.sh 的末尾追加一行批量修改檔案名的命令來實作此目的:

# 按正則匹配并批量修改符合要求的檔案
find orgs/ -regex ".+cacerts.+.pem" -not -regex ".+tlscacerts.+" | rename 's/cacerts\/.+\.pem/cacerts\/ca-cert\.pem/'

配置通道

直接運行根目錄下的 3_Configtxgen.sh 即可完成本實驗所需通道配置,需要注意的是,為了使通道組織架構更加清晰,將通道組態檔 configtx.yaml 中各組織名稱從 orgnameMSP 改為了 orgname ,以 soft 組織為例,其組織通道配置如下:

- &soft
    Name: softMSP
    ID: softMSP
    MSPDir: ../orgs/soft.ifantasy.net/msp
    Policies:
        Readers:
            Type: Signature
            Rule: "OR('softMSP.admin', 'softMSP.peer', 'softMSP.client')"
        Writers:
            Type: Signature
            Rule: "OR('softMSP.admin', 'softMSP.client')"
        Admins:
            Type: Signature
            Rule: "OR('softMSP.admin')"
        Endorsement:
            Type: Signature
            Rule: "OR('softMSP.peer')"
    AnchorPeers:
        - Host: peer1.soft.ifantasy.net
            Port: 7251

智能合約開發

本節將參考官方示例智能合約 asset-transfer-basic 開發作業室聯盟鏈的 專案資源管理智能合約 ,其在官方示例的基礎上進行了依賴和結構上的簡化,本示例是基于 Go 語言的智能合約,因此建議先學習 Go 語言基礎概念和規范,不然自行定制可能會有一些 Bug ,

合約代碼

  1. 初始化目錄/檔案
    在實驗根目錄 6_ContractGatewayAndSDK 下創建目錄 contract 作為智能合約根目錄,并在其下創建智能合約檔案 project_contract.go ,后續代碼皆在 project_contract.go 中,
  2. 智能合約結構體
    type ProjectContract struct {
        contractapi.Contract
    }
    
    智能合約結構體一般是固定寫法,創建任意一個結構體然后繼承 contractapi.Contract 即可,當部署至鏈上后利用其繼承的 contractapi.Contract 的介面實作對合約操作,
  3. 專案資訊結構體
    type Project struct {
        ID           string `json:"ID"`             // 專案唯一ID
        Name         string `json:"Name"`           // 專案名稱
        Developer    string `json:"Developer"`      // 專案主要負責人
        Organization string `json:"Organization"`   // 專案所屬組織
        Category     string `json:"Category"`       // 專案所屬類別 
        Url          string `json:"Url"`            // 專案介紹地址
        Describes    string `json:"Describes"`      // 專案描述
    }
    
    專案資訊結構體主要定義了單個專案的基本資訊,類似于 Java 的 Entity 類、資料庫的單個表,
  4. 初始化智能合約資料
    func (s *ProjectContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
        projects := []Project{
            {ID: "FA8B31A55CD59DB352BCBF4D2AE791AD", Name: "作業室聯盟鏈管理系統", Developer: "Fantasy", Organization: "Web", Category: "blockchain", Url: "https://github.com/wefantasy/FabricLearn", Describes: "本專案虛擬了一個作業室聯盟鏈需求并將逐步實作,致力于提供一個易理解、可復現的Fabric學習專案,其中專案部署步驟的各個環節都清晰可見,并且將所有實驗打包為腳本使之能夠被快速復現在任何一臺主機上"},
        }
        for _, project := range projects {
            projectJSON, err := json.Marshal(project)
            if err != nil {
                return err
            }
            err = ctx.GetStub().PutState(project.ID, projectJSON)
            if err != nil {
                return fmt.Errorf("failed to put to world state. %v", err)
            }
        }
        return nil
    }
    
    在 Fabric 某個舊版本之前必須提供智能合約初始化函式,但在本實驗所用的 Fabric 2.4 則是可選項,在此僅僅是為了寫入預設實驗資料,Fabric 底層使用默認鍵值對(key-value)狀態資料庫 LevelDB 儲存資料,在操作體驗上十分像 redis 資料庫,
  5. 判斷專案資訊是否已存在
    func (s *ProjectContract) ProjectExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
        projectJSON, err := ctx.GetStub().GetState(id)
        if err != nil {
            return false, fmt.Errorf("failed to read from world state: %v", err)
        }
    
        return projectJSON != nil, nil
    }
    
  6. 寫入新專案資訊
    func (s *ProjectContract) CreateProject(ctx contractapi.TransactionContextInterface, id string, name string, developer string, organization string, category string, url string, describes string) error {
        exists, err := s.ProjectExists(ctx, id)
        if err != nil {
            return err
        }
        if exists {
            return fmt.Errorf("the project %s already exists", id)
        }
        project := Project{
            ID:           id,
            Name:         name,
            Developer:    developer,
            Organization: organization,
            Category:     category,
            Url:          url,
            Describes:    describes,
        }
        projectJSON, err := json.Marshal(project)
        if err != nil {
            return err
        }
        return ctx.GetStub().PutState(id, projectJSON)
    }
    
  7. 洗掉指定專案資訊
    func (s *ProjectContract) DeleteProject(ctx contractapi.TransactionContextInterface, id string) error {
        exists, err := s.ProjectExists(ctx, id)
        if err != nil {
            return err
        }
        if !exists {
            return fmt.Errorf("the project %s does not exist", id)
        }
    
        return ctx.GetStub().DelState(id)
    }
    
    Fabric 聯盟鏈作為區塊鏈的一種特殊形式,同樣具有可追溯特性,因此任何對資料的增刪改操作都是軟操作——留下操作記錄,
  8. 修改專案資訊
    func (s *ProjectContract) UpdateProject(ctx contractapi.TransactionContextInterface, id string, name string, developer string, organization string, category string, url string, describes string) error {
        exists, err := s.ProjectExists(ctx, id)
        if err != nil {
            return err
        }
        if !exists {
            return fmt.Errorf("the project %s does not exist", id)
        }
        project := Project{
            ID:           id,
            Name:         name,
            Developer:    developer,
            Organization: organization,
            Category:     category,
            Url:          url,
            Describes:    describes,
        }
        projectJSON, err := json.Marshal(project)
        if err != nil {
            return err
        }
        return ctx.GetStub().PutState(id, projectJSON)
    }
    
  9. 查詢專案資訊
    func (s *ProjectContract) ReadProject(ctx contractapi.TransactionContextInterface, id string) (*Project, error) {
        projectJSON, err := ctx.GetStub().GetState(id)
        if err != nil {
            return nil, fmt.Errorf("failed to read from world state: %v", err)
        }
        if projectJSON == nil {
            return nil, fmt.Errorf("the project %s does not exist", id)
        }
    
        var project Project
        err = json.Unmarshal(projectJSON, &project)
        if err != nil {
            return nil, err
        }
    
        return &project, nil
    }
    
  10. 查詢鏈上所有專案資訊
    func (s *ProjectContract) GetAllProjects(ctx contractapi.TransactionContextInterface) ([]*Project, error) {
        // GetStateByRange 查詢引數為兩個空字串時即查詢所有資料
        resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
        if err != nil {
            return nil, err
        }
        defer resultsIterator.Close()
    
        var projects []*Project
        for resultsIterator.HasNext() {
            queryResponse, err := resultsIterator.Next()
            if err != nil {
                return nil, err
            }
    
            var project Project
            err = json.Unmarshal(queryResponse.Value, &project)
            if err != nil {
                return nil, err
            }
            projects = append(projects, &project)
        }
    
        return projects, nil
    }
    
  11. 智能合約入口函式/主函式
    func main() {
        chaincode, err := contractapi.NewChaincode(&ProjectContract{})
        if err != nil {
            log.Panicf("Error creating project-manage chaincode: %v", err)
        }
    
        if err := chaincode.Start(); err != nil {
            log.Panicf("Error starting project-manage chaincode: %v", err)
        }
    }
    

至此,專案資訊管理智能合約核心代碼以撰寫完畢,完整 project_contract.go 檔案內容如下(需要注意的是合約入口必須屬于 main 包):

package main

import (
	"encoding/json"
	"fmt"
	"github.com/hyperledger/fabric-contract-api-go/contractapi"
	"log"
)

type ProjectContract struct {
	contractapi.Contract
}

type Project struct {
	ID           string `json:"ID"`             // 專案唯一ID
	Name         string `json:"Name"`           // 專案名稱
	Developer    string `json:"Developer"`      // 專案主要負責人
	Organization string `json:"Organization"`   // 專案所屬組織
	Category     string `json:"Category"`       // 專案所屬類別 
	Url          string `json:"Url"`            // 專案介紹地址
	Describes    string `json:"Describes"`      // 專案描述
}

// 初始化智能合約資料
func (s *ProjectContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
	projects := []Project{
		{ID: "FA8B31A55CD59DB352BCBF4D2AE791AD", Name: "作業室聯盟鏈管理系統", Developer: "Fantasy", Organization: "Web", Category: "blockchain", Url: "https://github.com/wefantasy/FabricLearn", Describes: "本專案虛擬了一個作業室聯盟鏈需求并將逐步實作,致力于提供一個易理解、可復現的Fabric學習專案,其中專案部署步驟的各個環節都清晰可見,并且將所有實驗打包為腳本使之能夠被快速復現在任何一臺主機上"},
	}
	for _, project := range projects {
		projectJSON, err := json.Marshal(project)
		if err != nil {
			return err
		}
		err = ctx.GetStub().PutState(project.ID, projectJSON)
		if err != nil {
			return fmt.Errorf("failed to put to world state. %v", err)
		}
	}
	return nil
}

// 寫入新專案
func (s *ProjectContract) CreateProject(ctx contractapi.TransactionContextInterface, id string, name string, developer string, organization string, category string, url string, describes string) error {
	exists, err := s.ProjectExists(ctx, id)
	if err != nil {
		return err
	}
	if exists {
		return fmt.Errorf("the project %s already exists", id)
	}

	project := Project{
		ID:           id,
		Name:         name,
		Developer:    developer,
		Organization: organization,
		Category:     category,
		Url:          url,
		Describes:    describes,
	}
	projectJSON, err := json.Marshal(project)
	if err != nil {
		return err
	}
	return ctx.GetStub().PutState(id, projectJSON)
}

// 讀取指定ID的專案資訊
func (s *ProjectContract) ReadProject(ctx contractapi.TransactionContextInterface, id string) (*Project, error) {
	projectJSON, err := ctx.GetStub().GetState(id)
	if err != nil {
		return nil, fmt.Errorf("failed to read from world state: %v", err)
	}
	if projectJSON == nil {
		return nil, fmt.Errorf("the project %s does not exist", id)
	}

	var project Project
	err = json.Unmarshal(projectJSON, &project)
	if err != nil {
		return nil, err
	}

	return &project, nil
}

// 更新專案資訊.
func (s *ProjectContract) UpdateProject(ctx contractapi.TransactionContextInterface, id string, name string, developer string, organization string, category string, url string, describes string) error {
	exists, err := s.ProjectExists(ctx, id)
	if err != nil {
		return err
	}
	if !exists {
		return fmt.Errorf("the project %s does not exist", id)
	}

	project := Project{
		ID:           id,
		Name:         name,
		Developer:    developer,
		Organization: organization,
		Category:     category,
		Url:          url,
		Describes:    describes,
	}
	projectJSON, err := json.Marshal(project)
	if err != nil {
		return err
	}

	return ctx.GetStub().PutState(id, projectJSON)
}

// 洗掉指定ID的專案資訊
func (s *ProjectContract) DeleteProject(ctx contractapi.TransactionContextInterface, id string) error {
	exists, err := s.ProjectExists(ctx, id)
	if err != nil {
		return err
	}
	if !exists {
		return fmt.Errorf("the project %s does not exist", id)
	}

	return ctx.GetStub().DelState(id)
}

// 判斷某專案是否存在
func (s *ProjectContract) ProjectExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
	projectJSON, err := ctx.GetStub().GetState(id)
	if err != nil {
		return false, fmt.Errorf("failed to read from world state: %v", err)
	}

	return projectJSON != nil, nil
}

// 讀取所有專案資訊
func (s *ProjectContract) GetAllProjects(ctx contractapi.TransactionContextInterface) ([]*Project, error) {
	// GetStateByRange 查詢引數為兩個空字串時即查詢所有資料
	resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
	if err != nil {
		return nil, err
	}
	defer resultsIterator.Close()

	var projects []*Project
	for resultsIterator.HasNext() {
		queryResponse, err := resultsIterator.Next()
		if err != nil {
			return nil, err
		}

		var project Project
		err = json.Unmarshal(queryResponse.Value, &project)
		if err != nil {
			return nil, err
		}
		projects = append(projects, &project)
	}

	return projects, nil
}

func main() {
	chaincode, err := contractapi.NewChaincode(&ProjectContract{})
	if err != nil {
		log.Panicf("Error creating project-manage chaincode: %v", err)
	}

	if err := chaincode.Start(); err != nil {
		log.Panicf("Error starting project-manage chaincode: %v", err)
	}
}

依賴下載

合約代碼撰寫完成后并不能直接部署到聯盟鏈上,需要將合約中 import 匯入的包下載到本地以供后面一起打包,本小節所有命令默認運行于 6_ContractGatewayAndSDK/contract 下,

  1. 初始化模塊
    go mod init github.com/wefantasy/FabricLearn/6_ContractGatewayAndSDK/contract
    
  2. 將所有依賴下載到本地
    go mod vendor
    

以上命令運行成功后,智能合約開發作業基本結束,此時 contract 目錄結構如下:

6_ContractGatewayAndSDK/contract
├── go.mod
├── go.sum
├── project_contract.go
└── vendor
    ├── github.com
    ├── golang.org
    ├── google.golang.org
    ├── gopkg.in
    └── modules.tx

合約部署測驗

如無特殊說明,以下命令默認運行于實驗根目錄 6_ContractGatewayAndSDK 下:

  1. 合約打包
     source envpeer1soft
     peer lifecycle chaincode package basic.tar.gz --path contract --lang golang --label basic_1
    
  2. 三組織安裝
     source envpeer1soft
     peer lifecycle chaincode install basic.tar.gz
     peer lifecycle chaincode queryinstalled
     source envpeer1web
     peer lifecycle chaincode install basic.tar.gz
     peer lifecycle chaincode queryinstalled
     source envpeer1hard
     peer lifecycle chaincode install basic.tar.gz
     peer lifecycle chaincode queryinstalled
    
  3. 三組織批準
     export CHAINCODE_ID=basic_1:0f1f1ffc8e3865a9179e70a3c56237482b3eb4dcecd30ab51ab01a6f5d3daeff
     source envpeer1soft
     peer lifecycle chaincode approveformyorg -o orderer1.council.ifantasy.net:7051 --tls --cafile $ORDERER_CA  --channelID testchannel --name basic --version 1.0 --sequence 1 --waitForEvent --init-required --package-id $CHAINCODE_ID
     peer lifecycle chaincode queryapproved -C testchannel -n basic --sequence 1
     source envpeer1web
     peer lifecycle chaincode approveformyorg -o orderer3.council.ifantasy.net:7057 --tls --cafile $ORDERER_CA  --channelID testchannel --name basic --version 1.0 --sequence 1 --waitForEvent --init-required --package-id $CHAINCODE_ID
     peer lifecycle chaincode queryapproved -C testchannel -n basic --sequence 1
     source envpeer1hard
     peer lifecycle chaincode approveformyorg -o orderer2.council.ifantasy.net:7054 --tls --cafile $ORDERER_CA  --channelID testchannel --name basic --version 1.0 --sequence 1 --waitForEvent --init-required --package-id $CHAINCODE_ID
     peer lifecycle chaincode queryapproved -C testchannel -n basic --sequence 1
    
    注意要將 CHAINCODE_ID 的值改為三組織安裝時輸出的連碼包 ID
  4. 提交并測驗
     source envpeer1soft
     peer lifecycle chaincode commit -o orderer2.council.ifantasy.net:7054 --tls --cafile $ORDERER_CA --channelID testchannel --name basic --init-required --version 1.0 --sequence 1 --peerAddresses peer1.soft.ifantasy.net:7251 --tlsRootCertFiles $CORE_PEER_TLS_ROOTCERT_FILE --peerAddresses peer1.web.ifantasy.net:7351 --tlsRootCertFiles $CORE_PEER_TLS_ROOTCERT_FILE
     peer chaincode invoke --isInit -o orderer1.council.ifantasy.net:7051 --tls --cafile $ORDERER_CA --channelID testchannel --name basic --peerAddresses peer1.soft.ifantasy.net:7251 --tlsRootCertFiles $CORE_PEER_TLS_ROOTCERT_FILE --peerAddresses peer1.web.ifantasy.net:7351 --tlsRootCertFiles $CORE_PEER_TLS_ROOTCERT_FILE -c '{"Args":["InitLedger"]}'
     sleep 5
     peer chaincode invoke -o orderer1.council.ifantasy.net:7051 --tls --cafile $ORDERER_CA --channelID testchannel --name basic --peerAddresses peer1.soft.ifantasy.net:7251 --tlsRootCertFiles $CORE_PEER_TLS_ROOTCERT_FILE --peerAddresses peer1.web.ifantasy.net:7351 --tlsRootCertFiles $CORE_PEER_TLS_ROOTCERT_FILE -c '{"Args":["GetAllProjects"]}'
    
    提交并測驗

fabric-gateway 客戶端示例

客戶端代碼

  1. 初始化目錄/檔案
    在實驗根目錄 6_ContractGatewayAndSDK 下創建目錄 contract-gateway 作為 fabric-gateway 客戶端的根目錄,并在其下創建聯盟鏈網路連接檔案 connect.go 和 客戶端主程式 app.go ,實驗最終目錄結構為:
    contract-gateway
    ├── app.go
    ├── connect.go
    ├── go.mod
    └── go.sum
    
  2. connect.go 寫入以下內容
    package main
    
    import (
        "crypto/x509"
        "fmt"
        "io/ioutil"
        "path"
        "github.com/hyperledger/fabric-gateway/pkg/identity"
        "google.golang.org/grpc"
        "google.golang.org/grpc/credentials"
    )
    
    const (
        mspID         = "softMSP"				// 所屬組織的MSPID
        cryptoPath    = "/root/FabricLearn/6_ContractGatewayAndSDK/orgs/soft.ifantasy.net"	// 中間變數
        certPath      = cryptoPath + "/registers/user1/msp/signcerts/cert.pem"		// client 用戶的簽名證書
        keyPath       = cryptoPath + "/registers/user1/msp/keystore/"		// client 用戶的私鑰路徑
        tlsCertPath   = cryptoPath + "/assets/tls-ca-cert.pem"			// client 用戶的 tls 通信證書
        peerEndpoint  = "peer1.soft.ifantasy.net:7251"			// 所連 peer 節點的地址
        gatewayPeer   = "peer1.soft.ifantasy.net"		// 網關 peer 節點名稱
    )
    
    // 創建指向聯盟鏈網路的 gRPC 連接.
    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
    }
    
    // 根據用戶指定的X.509證書為這個網關連接創建一個客戶端標識,
    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)
    }
    
    // 使用私鑰從訊息摘要生成數字簽名
    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
    }
    

    值得說明的是,不論是 gateway 客戶端還是 fabric-sdk 客戶端,一般都可以通過 client 、 admin 型別的用戶連接聯盟鏈網路,只是創建單獨的 client 型別的專用用戶連接網路更符合開發理念,

  3. app.go 寫入以下內容
    package main
    
    import (
        "bytes"
        "encoding/json"
        "fmt"
        "time"
        "github.com/hyperledger/fabric-gateway/pkg/client"
    )
    
    const (
        channelName   = "testchannel"	// 連接的通道
        chaincodeName = "basic"			// 連接的鏈碼
    )
    
    func main() {
        clientConnection := newGrpcConnection()
        defer clientConnection.Close()
    
        id := newIdentity()
        sign := newSign()
    
        gateway, err := client.Connect(
            id,
            client.WithSign(sign),
            client.WithClientConnection(clientConnection),
            client.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)
    
        fmt.Println("getAllAssets:")
        getAllAssets(contract)
    }
    func getAllAssets(contract *client.Contract) {
        fmt.Println("Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger")
    
        evaluateResult, err := contract.EvaluateTransaction("GetAllProjects")
        if err != nil {
            panic(fmt.Errorf("failed to evaluate transaction: %w", err))
        }
        result := formatJSON(evaluateResult)
    
        fmt.Printf("*** Result:%s\n", result)
    }
    
    func formatJSON(data []byte) string {
        var prettyJSON bytes.Buffer
        if err := json.Indent(&prettyJSON, data, " ", ""); err != nil {
            panic(fmt.Errorf("failed to parse JSON: %w", err))
        }
        return prettyJSON.String()
    }
    

客戶端演示

如無特殊說明,以下命令默認運行于實驗根目錄 contract-gateway 下:

  1. 初始化模塊
    go mod init github.com/wefantasy/FabricLearn/6_ContractGatewayAndSDK/contract-gateway
    
  2. 下載依賴
    go get
    
    此時實驗目錄結構為
  3. 運行客戶端
    go run .
    
    因為本目錄下同時有兩個 packagemain 的 go 檔案,所以要用 . 的方式運行,運行結果如下:
    運行gateway客戶端

fabric-sdk-go 客戶端示例

剛接觸 Fabric 你可能會很疑惑,有些案例使用 fabric-gateway 連接聯盟鏈、另一些案例通過 fabric-sdk-* 連接聯盟鏈,并且似乎都可以操縱網路,那么有什么區別呢? fabric-sdk-* 被定義為 Fabric 的低級 SDK ,主要為開發者提供賬本管理、通道管理、用戶管理等聯盟鏈管理的 API ,它的開發成本更高但功能豐富;而 fabric-gateway 被定義為 Fabric 的高級 SDK ,這里的高級主要體現在其抽象程度更高,主要為開發者提供賬本管理的 API ,它的開發成本更低但功能較少,因此建議優先學習 fabric-sdk-* 的使用,

連接組態檔

就像剛才說的, fabric-sdk-* 開發成本比較高,我覺得高出來的開發成本有一半都在連接組態檔的配置上,它讓我花費了至少半天的時間來排錯,而網上幾乎沒有能把連接組態檔講清楚的文章(也許是我沒有找到),只能通過官方示例代碼慢慢推匯出正確的配置方法,
從 fabric-sdk-* 官方示例 assetTransfer.go 中參考的 connection-org1.yaml 連接組態檔出發,可以定位到生成它的相關檔案為 ccp-generate.sh 和 ccp-template.yaml ,后者為連接組態檔的基準模板,前者使用 bash 命令將基準模板替換為具體連接組態檔,連接組態檔有 json 和 yaml 兩種格式,我覺得 yaml 語法更為簡潔,后續實驗以此為例,將 ccp-generate.sh 檔案中的函式展開后,可以很容易的得生成連接組態檔的程序,本節所有命令默認運行于 6_ContractGatewayAndSDK 目錄下,通過如下命令生成 soft 組織的連接組態檔:

  1. 創建模板檔案
    將官方模板 ccp-template.yaml 復制一份至我們專案的 6_ContractGatewayAndSDK/config/ccp-template.yaml 中,由于我們的命名規范與官方不同,且該模板通用性不高,因此將其內容改為如下:
    ---
    name: test-network-${ORG}
    version: 1.0.0
    client:
    organization: ${ORG}
    connection:
        timeout:
        peer:
            endorser: '300'
    organizations:
    ${ORG}:
        mspid: ${ORG}MSP
        peers:
        - peer1.${ORG}.ifantasy.net
        certificateAuthorities:
        - ${ORG}.ifantasy.net
    peers:
    peer1.${ORG}.ifantasy.net:
        url: grpcs://peer1.${ORG}.ifantasy.net:${P0PORT}
        tlsCACerts:
        pem: |
            ${PEERPEM}
        grpcOptions:
        ssl-target-name-override: peer1.${ORG}.ifantasy.net
        hostnameOverride: peer1.${ORG}.ifantasy.net
    certificateAuthorities:
    ${ORG}.ifantasy.net:
        url: https://${ORG}.ifantasy.net:${CAPORT}
        caName: ${ORG}.ifantasy.net
        tlsCACerts:
        pem: 
            - |
            ${CAPEM}
        httpOptions:
        verify: false
    

    這個模板可以跟我們專案很好的契合,需要特別注意的是其中組織名和組織ID必須與 configtx.yaml 檔案中相匹配,這是前面修改 configtx.yaml 的原因,不然很容易出錯,其中各個引數的含義可以對照下面的模板引數理解,

  2. 設定模板引數
    ORG=soft
    P0PORT=7251
    CAPORT=7250
    cryptoPath=$LOCAL_CA_PATH/soft.ifantasy.net
    PEERPEM=$cryptoPath/assets/tls-ca-cert.pem
    CAPEM=$cryptoPath/assets/ca-cert.pem
    
  3. 獲取 tls 證書和 ca 證書
    PP="`awk 'NF {sub(/\\n/, ""); printf "%s\\\\\\\n",$0;}' $PEERPEM`"
    CP="`awk 'NF {sub(/\\n/, ""); printf "%s\\\\\\\n",$0;}' $CAPEM`"
    
  4. 生成模板檔案
    sed -e "s/\${ORG}/$ORG/" \
            -e "s/\${P0PORT}/$P0PORT/" \
            -e "s/\${CAPORT}/$CAPORT/" \
            -e "s#\${PEERPEM}#$PP#" \
            -e "s#\${CAPEM}#$CP#" \
            config/ccp-template.yaml | sed -e $'s/\\\\n/\\\n          /g'  > connection-soft.yaml
    

依次執行上述命令,最后會將連接組態檔 connection-soft.yaml 輸出到實驗根目錄中,本例中其內容如下:

---
name: test-network-soft
version: 1.0.0
client:
  organization: soft
  connection:
    timeout:
      peer:
        endorser: '300'
organizations:
  soft:
    mspid: softMSP
    peers:
    - peer1.soft.ifantasy.net
    certificateAuthorities:
    - soft.ifantasy.net
peers:
  peer1.soft.ifantasy.net:
    url: grpcs://peer1.soft.ifantasy.net:7251
    tlsCACerts:
      pem: |
          -----BEGIN CERTIFICATE-----
          MIICHzCCAcWgAwIBAgIUbO4XSCy2KbQQN/E63zvkhUJfMzwwCgYIKoZIzj0EAwIw
          bDELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQK
          EwtIeXBlcmxlZGdlcjEPMA0GA1UECxMGRmFicmljMR0wGwYDVQQDExRjb3VuY2ls
          LmlmYW50YXN5Lm5ldDAeFw0yMjA2MTEwNTU3MDBaFw0zNzA2MDcwNTU3MDBaMGwx
          CzAJBgNVBAYTAlVTMRcwFQYDVQQIEw5Ob3J0aCBDYXJvbGluYTEUMBIGA1UEChML
          SHlwZXJsZWRnZXIxDzANBgNVBAsTBkZhYnJpYzEdMBsGA1UEAxMUY291bmNpbC5p
          ZmFudGFzeS5uZXQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQecDRTwml7bcaD
          nZdPiEYiTxFwHa+g2nw+mq+6KeMPW98WT3BPNErb1gw9BQa6GRcTypJ7Ga1lSqLS
          IFD+aypYo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAd
          BgNVHQ4EFgQUq3Q80AlYM9lGKHWVupCEjpyBb1kwCgYIKoZIzj0EAwIDSAAwRQIh
          AJashZ+Sob7DoOpYII22wDOPSV8updo1W9LNEAaxzMyTAiAokfgCVjtlX3EJnV+m
          qc5EBQCjA0AaX1HPNBTUII7T+Q==
          -----END CERTIFICATE-----
          
    grpcOptions:
      ssl-target-name-override: peer1.soft.ifantasy.net
      hostnameOverride: peer1.soft.ifantasy.net
certificateAuthorities:
  soft.ifantasy.net:
    url: https://soft.ifantasy.net:7250
    caName: soft.ifantasy.net
    tlsCACerts:
      pem: 
        - |
          -----BEGIN CERTIFICATE-----
          MIICGDCCAb+gAwIBAgIUXF3f1cgHiAMO03c/61iyFWAD/0AwCgYIKoZIzj0EAwIw
          aTELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQK
          EwtIeXBlcmxlZGdlcjEPMA0GA1UECxMGRmFicmljMRowGAYDVQQDExFzb2Z0Lmlm
          YW50YXN5Lm5ldDAeFw0yMjA2MTEwNTU3MDBaFw0zNzA2MDcwNTU3MDBaMGkxCzAJ
          BgNVBAYTAlVTMRcwFQYDVQQIEw5Ob3J0aCBDYXJvbGluYTEUMBIGA1UEChMLSHlw
          ZXJsZWRnZXIxDzANBgNVBAsTBkZhYnJpYzEaMBgGA1UEAxMRc29mdC5pZmFudGFz
          eS5uZXQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASP0Vs5wUaRzIyiXx2ygH6A
          IQyCLe6VhTxnNPmJhMUVOmO+iyLJqMUuQRRHIcCgiNGPR9cqd4ygcRJBvsG+sooY
          o0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4E
          FgQUkPhZPSjyHVdL5NkQED1Rdif7GdowCgYIKoZIzj0EAwIDRwAwRAIgfOt69wD8
          HEqroGm/zVFf/NiqivluaK5Yf3Ryn0C7p5ECID/KNGjbt5b53ivuL5slK5B+8eA2
          KGUN7ysBzX8hTzPj
          -----END CERTIFICATE-----
          
    httpOptions:
      verify: false

上述操作已打包至 5_GenConnectYaml.sh 中,也可以直接在根目錄下運行 5_GenConnectYaml.sh 來了生成連接組態檔,

客戶端代碼

  1. 初始化目錄/檔案
    在實驗根目錄 6_ContractGatewayAndSDK 下創建目錄 contract-sdk 作為 fabric-sdk 客戶端的根目錄,并在其下創建主程式 app.go ,將上節生成的 connection-soft.yaml 復制到該目錄下,最終目錄結構為:
     contract-sdk
     ├── app.go
     ├── connection-soft.yaml
     ├── go.mod
     ├── go.sum
     ├── keystore
     └── wallet
         └── appUser.id
    
  2. 向 app.go 寫入以下內容
     package main
    
     import (
         "fmt"
         "io/ioutil"
         "log"
         "os"
         "path/filepath"
    
         "github.com/hyperledger/fabric-sdk-go/pkg/core/config"
         "github.com/hyperledger/fabric-sdk-go/pkg/gateway"
     )
    
     func main() {
         log.Println("============ application-golang starts ============")
    
         err := os.Setenv("DISCOVERY_AS_LOCALHOST", "true")
         if err != nil {
             log.Fatalf("Error setting DISCOVERY_AS_LOCALHOST environemnt variable: %v", err)
         }
    
         wallet, err := gateway.NewFileSystemWallet("wallet")
         if err != nil {
             log.Fatalf("Failed to create wallet: %v", err)
         }
    
         err = populateWallet(wallet)
         // 除錯建議注釋這里
         // if !wallet.Exists("appUser") {
         // 	err = populateWallet(wallet)
         // 	if err != nil {
         // 		log.Fatalf("Failed to populate wallet contents: %v", err)
         // 	}
         // }
    
         ccpPath := filepath.Join(
             "connection-soft.yaml",
         )
    
         gw, err := gateway.Connect(
             gateway.WithConfig(config.FromFile(filepath.Clean(ccpPath))),
             gateway.WithIdentity(wallet, "appUser"),
         )
         if err != nil {
             log.Fatalf("Failed to connect to gateway: %v", err)
         }
         defer gw.Close()
         
         network, err := gw.GetNetwork("testchannel")
         if err != nil {
             log.Fatalf("Failed to get network: %v", err)
         }
         
         contract := network.GetContract("basic")
    
         log.Println("--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger")
         result, err := contract.EvaluateTransaction("GetAllProjects")
         if err != nil {
             log.Fatalf("Failed to evaluate transaction: %v", err)
         }
         log.Println(string(result))
    
         log.Println("--> Submit Transaction: DeleteProject, delete new project info with ID arguments")
         result, err = contract.SubmitTransaction("DeleteProject", "FA8B31A55CD59DB352BCBF4D2AE791AD")
         if err != nil {
             log.Fatalf("Failed to Submit transaction: %v", err)
         }
         log.Println(string(result))
     }
    
     func populateWallet(wallet *gateway.Wallet) error {
         log.Println("============ Populating wallet ============")
         credPath := filepath.Join(
             "..",
             "orgs",
             "soft.ifantasy.net",
             "registers",
             "user1",
             "msp",
         )
    
         certPath := filepath.Join(credPath, "signcerts", "cert.pem")
         // read the certificate pem
         cert, err := ioutil.ReadFile(filepath.Clean(certPath))
         if err != nil {
             return err
         }
    
         keyDir := filepath.Join(credPath, "keystore")
         // there's a single file in this dir containing the private key
         files, err := ioutil.ReadDir(keyDir)
         if err != nil {
             return err
         }
         if len(files) != 1 {
             return fmt.Errorf("keystore folder should have contain one file")
         }
         keyPath := filepath.Join(keyDir, files[0].Name())
         key, err := ioutil.ReadFile(filepath.Clean(keyPath))
         if err != nil {
             return err
         }
    
         identity := gateway.NewX509Identity("softMSP", string(cert), string(key))
    
         return wallet.Put("appUser", identity)
     }
    

客戶端演示

如無特殊說明,以下命令默認運行于實驗根目錄 contract-sdk 下:

  1. 初始化模塊
    go mod init github.com/wefantasy/FabricLearn/6_ContractGatewayAndSDK/contract-gateway
    
  2. 下載依賴
    go get
    
  3. 運行客戶端
    go run .
    
    運行SDK客戶端

Q&A

遇到錯誤:

QueryBlockConfig failed: no channel peers configured for channel [testchannel]

解決方法: 大概率是連接組態檔組織名稱啥的寫錯了,再次檢查組織組態檔與configtx.yaml中宣告的是否匹配,

遇到錯誤:

2022/06/10 15:55:44 Failed to get network: Failed to create new channel client: event service creation failed: could not get chConfig cache reference: QueryBlockConfig failed: QueryBlockConfig failed: target(s) required

解決方法: 可能是因為 wallet 目錄下的身份與所申明的身份不匹配,建議每次啟動前洗掉 wallet 目錄讓它重新生成,

遇到錯誤:

2022/06/10 16:08:13 Failed to Submit transaction: Failed to submit: error getting channel response for channel [testchannel]: no successful response received from any peer: access denied

解決方法: 此時檢查對應的 peer 節點容器日志若有 implicit policy evaluation failed 錯誤,則說明當前使用的身份權限不足,在實驗中使用 peer 型別的用戶身份則會導致此問題,建議使用 client 身份的用戶(admin 身份也行),

遇到錯誤:

2022/06/10 16:08:13 Failed to Submit transaction: Failed to submit: error getting channel response for channel [testchannel]: no successful response received from any peer: access denied

解決方法: 此時檢查對應的 peer 節點容器日志若有 implicit policy evaluation failed 錯誤,則說明當前使用的身份權限不足,在實驗中使用 peer 型別的用戶身份則會導致此問題,建議使用 client 身份的用戶(admin 身份也行),

參考

[1]: hyperledger-fabric. Fabric Contract APIs and Application APIs. readthedocs.io. [-]
[2]: barney2k7. What is the difference between fabric-chaincode-go and fabric-contract-api-go?. stackoverflow.com. [2020-05-08]
[3]: Nikos Karamolegkos. fabric-sdk-go vs fabric-gateway. When to use each one?. hyperledger.org. [2021-12-07]
[4]: kid1999 Karamolegkos. Fabric智能合約Go開發包簡單理解. github.io. [2021-06-26]

轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/498619.html

標籤:區塊鏈

上一篇:NBMiner42.1版本發布,完全解鎖30系LHR版本顯卡

下一篇:數字時代的“文藝復興”?起底數字藏品,讓人歡喜讓人愁

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • JAVA使用 web3j 進行token轉賬

    最近新學習了下區塊鏈這方面的知識,所學不多,給大家分享下。 # 1. 關于web3j web3j是一個高度模塊化,反應性,型別安全的Java和Android庫,用于與智能合約配合并與以太坊網路上的客戶端(節點)集成。 # 2. 準備作業 jdk版本1.8 引入maven <dependency> < ......

    uj5u.com 2020-09-10 03:03:06 more
  • 以太坊智能合約開發框架Truffle

    前言 部署智能合約有多種方式,命令列的瀏覽器的渠道都有,但往往跟我們程式員的風格不太相符,因為我們習慣了在IDE里寫了代碼然后打包運行看效果。 雖然現在IDE中已經存在了Solidity插件,可以撰寫智能合約,但是部署智能合約卻要另走他路,沒辦法進行一個快捷的部署與測驗。 如果團隊管理的區塊節點多、 ......

    uj5u.com 2020-09-10 03:03:12 more
  • 谷歌二次驗證碼成為區塊鏈專用安全碼,你怎么看?

    前言 谷歌身份驗證器,前些年大家都比較陌生,但隨著國內互聯網安全的加強,它越來越多地出現在大家的視野中。 比較廣泛接觸的人群是國際3A游戲愛好者,游戲盜號現象嚴重+國外賬號安全應用廣泛,這類游戲一般都會要求用戶系結名為“兩步驗證”、“雙重驗證”等,平臺一般都推薦用谷歌身份驗證器。 后來區塊鏈業務風靡 ......

    uj5u.com 2020-09-10 03:03:17 more
  • 密碼學DAY1

    目錄 ##1.1 密碼學基本概念 密碼在我們的生活中有著重要的作用,那么密碼究竟來自何方,為何會產生呢? 密碼學是網路安全、資訊安全、區塊鏈等產品的基礎,常見的非對稱加密、對稱加密、散列函式等,都屬于密碼學范疇。 密碼學有數千年的歷史,從最開始的替換法到如今的非對稱加密演算法,經歷了古典密碼學,近代密 ......

    uj5u.com 2020-09-10 03:03:50 more
  • 密碼學DAY1_02

    目錄 ##1.1 ASCII編碼 ASCII(American Standard Code for Information Interchange,美國資訊交換標準代碼)是基于拉丁字母的一套電腦編碼系統,主要用于顯示現代英語和其他西歐語言。它是現今最通用的單位元組編碼系統,并等同于國際標準ISO/IE ......

    uj5u.com 2020-09-10 03:04:50 more
  • 密碼學DAY2

    ##1.1 加密模式 加密模式:https://docs.oracle.com/javase/8/docs/api/javax/crypto/Cipher.html ECB ECB : Electronic codebook, 電子密碼本. 需要加密的訊息按照塊密碼的塊大小被分為數個塊,并對每個塊進 ......

    uj5u.com 2020-09-10 03:05:42 more
  • NTP時鐘服務器的特點(京準電子)

    NTP時鐘服務器的特點(京準電子) NTP時鐘服務器的特點(京準電子) 京準電子官V——ahjzsz 首先對時間同步進行了背景介紹,然后討論了不同的時間同步網路技術,最后指出了建立全球或區域時間同步網存在的問題。 一、概 述 在通信領域,“同步”概念是指頻率的同步,即網路各個節點的時鐘頻率和相位同步 ......

    uj5u.com 2020-09-10 03:05:47 more
  • 標準化考場時鐘同步系統推進智能化校園建設

    標準化考場時鐘同步系統推進智能化校園建設 標準化考場時鐘同步系統推進智能化校園建設 安徽京準電子科技官微——ahjzsz 一、背景概述隨著教育事業的快速發展,學校建設如雨后春筍,隨之而來的學校教育、管理、安全方面的問題成了學校管理人員面臨的最大的挑戰,這些問題同時也是學生家長所擔心的。為了讓學生有更 ......

    uj5u.com 2020-09-10 03:05:51 more
  • 位元幣入門

    引言 位元幣基本結構 位元幣基礎知識 1)哈希演算法 2)非對稱加密技術 3)數字簽名 4)MerkleTree 5)哪有位元幣,有的是UTXO 6)位元幣挖礦與共識 7)區塊驗證(共識) 總結 引言 上一篇我們已經知道了什么是區塊鏈,此篇說一下區塊鏈的第一個應用——位元幣。其實先有位元幣,后有的區塊 ......

    uj5u.com 2020-09-10 03:06:15 more
  • 北斗對時服務器(北斗對時設備)電力系統應用

    北斗對時服務器(北斗對時設備)電力系統應用 北斗對時服務器(北斗對時設備)電力系統應用 京準電子科技官微(ahjzsz) 中國北斗衛星導航系統(英文名稱:BeiDou Navigation Satellite System,簡稱BDS),因為是目前世界范圍內唯一可以大面積提供免費定位服務的系統,所以 ......

    uj5u.com 2020-09-10 03:06:20 more
最新发布
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:46:47 more
  • Hyperledger Fabric 使用 CouchDB 和復雜智能合約開發

    在上個實驗中,我們已經實作了簡單智能合約實作及客戶端開發,但該實驗中智能合約只有基礎的增刪改查功能,且其中的資料管理功能與傳統 MySQL 比相差甚遠。本文將在前面實驗的基礎上,將 Hyperledger Fabric 的默認資料庫支持 LevelDB 改為 CouchDB 模式,以實作更復雜的資料... ......

    uj5u.com 2023-04-16 07:28:31 more
  • .NET Core 波場鏈離線簽名、廣播交易(發送 TRX和USDT)筆記

    Get Started NuGet You can run the following command to install the Tron.Wallet.Net in your project. PM> Install-Package Tron.Wallet.Net 配置 public reco ......

    uj5u.com 2023-04-14 08:08:00 more
  • DKP 黑客分析——不正確的代幣對比率計算

    概述: 2023 年 2 月 8 日,針對 DKP 協議的閃電貸攻擊導致該協議的用戶損失了 8 萬美元,因為 execute() 函式取決于 USDT-DKP 對中兩種代幣的余額比率。 智能合約黑客概述: 攻擊者的交易:0x0c850f,0x2d31 攻擊者地址:0xF38 利用合同:0xf34ad ......

    uj5u.com 2023-04-07 07:46:09 more
  • Defi開發簡介

    Defi開發簡介 介紹 Defi是去中心化金融的縮寫, 是一項旨在利用區塊鏈技術和智能合約創建更加開放,可訪問和透明的金融體系的運動. 這與傳統金融形成鮮明對比,傳統金融通常由少數大型銀行和金融機構控制 在Defi的世界里,用戶可以直接從他們的電腦或移動設備上訪問廣泛的金融服務,而不需要像銀行或者信 ......

    uj5u.com 2023-04-05 08:01:34 more
  • solidity簡單的ERC20代幣實作

    // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; import "hardhat/console.sol"; //ERC20 同質化代幣,每個代幣的本質或性質都是相同 //ETH 是原生代幣,它不是ERC20代幣, ......

    uj5u.com 2023-03-21 07:56:29 more
  • solidity 參考型別修飾符memory、calldata與storage 常量修飾符C

    在solidity語言中 參考型別修飾符(參考型別為存盤空間不固定的數值型別) memory、calldata與storage,它們只能修飾參考型別變數,比如字串、陣列、位元組等... memory 適用于方法傳參、返參或在方法體內使用,使用完就會清除掉,釋放記憶體 calldata 僅適用于方法傳參 ......

    uj5u.com 2023-03-08 07:57:54 more
  • solidity注解標簽

    在solidity語言中 注釋符為// 注解符為/* 內容*/ 或者 是 ///內容 注解中含有這幾個標簽給予我們使用 @title 一個應該描述合約/介面的標題 contract, library, interface @author 作者的名字 contract, library, interf ......

    uj5u.com 2023-03-08 07:57:49 more
  • 評價指標:相似度、GAS消耗

    【代碼注釋自動生成方法綜述】 這些評測指標主要來自機器翻譯和文本總結等研究領域,可以評估候選文本(即基于代碼注釋自動方法而生成)和參考文本(即基于手工方式而生成)的相似度. BLEU指標^[^?88^^?^]^:其全稱是bilingual evaluation understudy.該指標是最早用于 ......

    uj5u.com 2023-02-23 07:27:39 more
  • 基于NOSTR協議的“公有制”版本的Twitter,去中心化社交軟體Damus

    最近,一個幽靈,Web3的幽靈,在網路游蕩,它叫Damus,這玩意詮釋了什么叫做病毒式營銷,滑稽的是,一個Web3產品卻在Web2的產品鏈上瘋狂傳銷,各方大佬紛紛為其背書,到底發生了什么?Damus的葫蘆里,賣的是什么藥? 注冊和簡單實用 很少有什么產品在用戶注冊環節會有什么噱頭,但Damus確實出 ......

    uj5u.com 2023-02-05 06:48:39 more