【区块链技术与应用】(五)

这篇具有很好参考价值的文章主要介绍了【区块链技术与应用】(五)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

区块链技术与应用,信息管理与信息系统,区块链,github,人工智能

引言

这周恰逢期中,时间仓促,代码上有许多地方可以优化,但也只能留到之后的几次作业上了。
阅读建议:参考和链码样例为写链码前用样例试手内容,与作业相关的内容是“资产管理”之后的代码。
代码参考及学习资料在“参考”一栏中。
区块链技术与应用,信息管理与信息系统,区块链,github,人工智能

参考

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 main

import (
    "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 main

import (
    "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 main

import (
    "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网络
  1. 进入fabric-sample的test-network目录

    $ cd fabric-samples/test-network
    
  2. 运行./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.compeer0.org2.example.com,它还包括一个作为ordering service维护网络的orderer.example.com`。

区块链技术与应用,信息管理与信息系统,区块链,github,人工智能
区块链技术与应用,信息管理与信息系统,区块链,github,人工智能

创建channel

上节已经在机器上运行了peer节点和orderer节点,现在可以使用network.sh为Org1和Org2之间创建channel。channel是特定网络成员之间的私有通道,只能被属于该通道的组织使用,并且对网络的其他成员是不可见的。每个channel都有一个单独的区块链账本,属于该通道的组织可以让其下peer加入该通道,以让peer能够存储channel上的帐本并验证账本上的交易。
使用以下命令创建自定义通道testchannel:

$ ./network.sh createChannel -c testchannel

区块链技术与应用,信息管理与信息系统,区块链,github,人工智能
区块链技术与应用,信息管理与信息系统,区块链,github,人工智能

部署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 main
 
import (
  "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 Asset
  err = 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 asset
  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)
}
 
// 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 = newOwner
  assetJSON, 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 []*Asset
  for resultsIterator.HasNext() {
    queryResponse, err := resultsIterator.Next()
    if err != nil {
      return nil, err
    }
 
    var asset Asset
    err = 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 Asset
  err = 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 asset
  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)
}
// 删除资产,直接调用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 = newOwner
  assetJSON, 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 []*Asset
  for resultsIterator.HasNext() {
    queryResponse, err := resultsIterator.Next()
    if err != nil {
      return nil, err
    }

    var asset Asset
    err = 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

区块链技术与应用,信息管理与信息系统,区块链,github,人工智能

使用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":[]}'

总结

随着实验次数的增多,肉眼可见的是文件夹越来越混乱——比如这次的网络,需要在前几次实验中找到内容,而每次实验都涉及到文件创建、下载、删除,有时哪怕一次实验,都会鼓捣出很多奇奇怪怪的文件夹,有时会出现这样一种状况:我知道网络是通的,但不知道是那个文件能够跑通,毕竟,如果按照教程顺利过关,是对具体内容没有深刻印象的,直到需要再次使用的时候。

链码编写(改写)难度不算特别大,但如果 涉及到网络上跑通,再加之账本的增删改查,如果找不到之前实验的基础内容,无异于再做一次。文章来源地址https://www.toymoban.com/news/detail-795480.html

到了这里,关于【区块链技术与应用】(五)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包