目录
[1.1 安装FISCO BCOS节点](#1.1 安装FISCO BCOS节点)
[1.2 安装Go SDK依赖](#1.2 安装Go SDK依赖)
[2.1 创建投票合约 Voting.sol](#2.1 创建投票合约 Voting.sol)
[2.2 编译合约](#2.2 编译合约)
[三、Go SDK合约调用实现](#三、Go SDK合约调用实现)
[3.1 项目结构](#3.1 项目结构)
[3.2 Go SDK客户端封装](#3.2 Go SDK客户端封装)
[3.3 投票合约调用示例](#3.3 投票合约调用示例)
[3.4 配置文件 conf/config.toml](#3.4 配置文件 conf/config.toml)
FISCO BCOS是著名的国产的企业级区块链平台,支持Solidity智能合约,并且提供了多种语言的SDK,包括Go。

FISCO BCOS 3.x版本采用微服务模块化设计架构,总体上系统包含接入层、调度层、计算层、存储层和管理层五个方面。下面分别介绍每层的功能设计。
- 接入层 :接入层主要负责区块链连接的能力,包括提供P2P能力的"对外网关服务"和提供给SDK访问的"对内网关服务"。在联盟链的体系中,"对外网关服务"管理了机构对外连接的出入口,负责机构级别的安全认证。"对内网关服务"则提供给机构内的客户端(应用端)访问入口。两个网关服务都可以平行扩展、多活部署、负载均衡,满足高可用要求。
- 调度层 :调度层是区块链内核运转调度的"大脑中枢"系统,负责整个区块链系统运行调度,包括网络分发调度、交易池管理、共识机制、计算调度等模块。其中,网络分发模块主要是与接入层实现互联通信功能,处理消息分发逻辑;交易池管理主要负责交易的接收、签名验证、淘汰等功能;共识机制负责交易排序、区块打包以及对区块结果进行分布式共识,确保一致性;计算调度则完成交易验证(核心是智能合约的验证)的调度处理,实现并行验证,是整个系统吞吐量的关键。
- 计算层 :这里主要负责交易验证,需要将交易解码放入合约虚拟机中执行,得到交易执行结果。交易验证是整个区块链的核心,尤其是基于智能合约的区块链系统,交易验证的计算可能需要花费较大的CPU开销。因此,如何实现并行化交易验证,通过集群化模式实现交易验证计算的平行扩展是非常重要的。
- 存储层 :存储层负责落盘存储交易、区块、账本状态等数据,存储层重点关注如何支撑海量数据的存储,采用分布式存储集群的方式可实现存储容量可扩展。分布式存储业界已有许多稳定可复用的开源组件(如TiKV),这层将复用成熟组件。
-
管理层 :管理层是为整个区块链系统各模块实现可视化管理的平台,包括部署、配置、日志、网络路由等管理功能。FISCO BCOS 3.x系统架构基于开源微服务框架Tars构建,这层的能力复用成熟的Tars-Framework管理组件。
-
上面介绍了FISCO BCOS区块链平台,下面我将介绍如何基于FISCO BCOS部署一个投票智能合约,并使用Go SDK进行合约调用。
概要设计
一、步骤概览:
-
1、环境准备:安装必要的工具和依赖,包括FISCO BCOS节点、Go环境、Go SDK等。
-
2、编写Solidity投票智能合约。
-
3、编译智能合约,获取ABI和BIN。
-
4、部署智能合约到FISCO BCOS链上。
-
5、使用Go SDK编写合约调用代码。
二、详细步骤:
完整流程实现
-
1、环境准备
-
安装并启动FISCO BCOS区块链节点。可以参考官方文档:https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/installation.html
-
安装Go语言环境(1.14及以上版本)。
-
下载Go SDK:https://github.com/FISCO-BCOS/go-sdk
-
配置Go SDK,主要需要配置证书和连接节点的信息。
-
-
2、编写投票智能合约(Vote.sol)
以下是一个简单的投票合约示例,包含提案和投票功能。
javascript
pragma solidity ^0.4.24;
contract Voting {
// 投票者结构
struct Voter {
uint weight; // 权重,这里简单处理,默认1
bool voted; // 是否已投票
uint vote; // 所投提案的索引
}
// 提案结构
struct Proposal {
bytes32 name; // 提案名称
uint voteCount; // 得票数
}
address public chairperson; // 主持人
mapping(address => Voter) public voters; // 投票人地址到投票人的映射
Proposal[] public proposals; // 提案数组
// 构造函数,初始化提案列表
constructor(bytes32[] proposalNames) public {
chairperson = msg.sender;
voters[chairperson].weight = 1;
for (uint i = 0; i < proposalNames.length; i++) {
proposals.push(Proposal({
name: proposalNames[i],
voteCount: 0
}));
}
}
// 给投票人授权,只有主持人可以调用
function giveRightToVote(address voter) public {
require(msg.sender == chairperson, "Only chairperson can give right to vote.");
require(!voters[voter].voted, "The voter already voted.");
require(voters[voter].weight == 0);
voters[voter].weight = 1;
}
// 投票函数
function vote(uint proposal) public {
Voter storage sender = voters[msg.sender];
require(sender.weight != 0, "Has no right to vote");
require(!sender.voted, "Already voted.");
sender.voted = true;
sender.vote = proposal;
// 计票
proposals[proposal].voteCount += sender.weight;
}
// 计算获胜提案
function winningProposal() public view returns (uint winningProposal_) {
uint winningVoteCount = 0;
for (uint p = 0; p < proposals.length; p++) {
if (proposals[p].voteCount > winningVoteCount) {
winningVoteCount = proposals[p].voteCount;
winningProposal_ = p;
}
}
}
// 获取获胜提案名称
function winnerName() public view returns (bytes32 winnerName_) {
winnerName_ = proposals[winningProposal()].name;
}
}
-
3、编译智能合约
-
可以使用solc编译器或者在线编译器(如Remix)编译合约,得到ABI和BIN文件。
-
也可以使用FISCO BCOS提供的控制台工具来编译。
-
-
4、部署智能合约
- 使用Go SDK的合约部署功能,需要合约的BIN和ABI。
-
5、使用Go SDK进行合约调用
-
首先,需要初始化客户端,连接到FISCO BCOS节点。
-
然后,使用ABI和合约地址加载合约实例。
-
最后,调用合约的方法。
-
-
接下来详细介绍基于FISCO BCOS部署Solidity投票智能合约并使用Go SDK调用的完整流程。
一、环境准备
1.1 安装FISCO BCOS节点
bash
# 下载安装脚本
curl -LO https://github.com/FISCO-BCOS/FISCO-BCOS/releases/download/v3.4.0/build_chain.sh && chmod u+x build_chain.sh
# 构建单节点链
./build_chain.sh -l 127.0.0.1:4 -p 30300,20200,8545
# 启动节点
./nodes/127.0.0.1/start_all.sh
# 检查节点状态
./nodes/127.0.0.1/fisco-bcos -v
1.2 安装Go SDK依赖
bash
# 安装Go(版本>=1.17)
# 下载Go SDK
git clone https://github.com/FISCO-BCOS/go-sdk.git
cd go-sdk
# 配置证书(从节点复制)
cp -r nodes/127.0.0.1/sdk/* conf/
二、Solidity投票智能合约
2.1 创建投票合约 Voting.sol
javascript
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Voting {
// 投票者结构
struct Voter {
uint weight; // 投票权重
bool voted; // 是否已投票
uint vote; // 投票提案索引
address delegate; // 委托投票地址
}
// 提案结构
struct Proposal {
bytes32 name; // 提案名称
uint voteCount; // 得票数
}
address public chairperson; // 主持人
mapping(address => Voter) public voters; // 投票人映射
Proposal[] public proposals; // 提案列表
// 事件
event VoterRegistered(address voter);
event Voted(address voter, uint proposalIndex);
event Delegated(address from, address to);
event ProposalCreated(bytes32 name);
// 构造函数:初始化提案
constructor(bytes32[] memory proposalNames) {
chairperson = msg.sender;
// 主持人自动获得投票权
voters[chairperson].weight = 1;
// 创建提案
for (uint i = 0; i < proposalNames.length; i++) {
proposals.push(Proposal({
name: proposalNames[i],
voteCount: 0
}));
emit ProposalCreated(proposalNames[i]);
}
}
// 主持人给投票者授权
function giveRightToVote(address voter) public {
require(msg.sender == chairperson, "Only chairperson can give voting rights");
require(!voters[voter].voted, "The voter already voted");
require(voters[voter].weight == 0, "Voter already has right");
voters[voter].weight = 1;
emit VoterRegistered(voter);
}
// 委托投票
function delegate(address to) public {
Voter storage sender = voters[msg.sender];
require(!sender.voted, "You already voted");
require(to != msg.sender, "Self-delegation is disallowed");
// 查找最终委托地址
while (voters[to].delegate != address(0)) {
to = voters[to].delegate;
require(to != msg.sender, "Found loop in delegation");
}
sender.voted = true;
sender.delegate = to;
Voter storage delegate_ = voters[to];
if (delegate_.voted) {
// 如果被委托者已投票,则增加提案票数
proposals[delegate_.vote].voteCount += sender.weight;
} else {
// 否则增加被委托者的权重
delegate_.weight += sender.weight;
}
emit Delegated(msg.sender, to);
}
// 投票
function vote(uint proposal) public {
Voter storage sender = voters[msg.sender];
require(sender.weight != 0, "Has no right to vote");
require(!sender.voted, "Already voted");
sender.voted = true;
sender.vote = proposal;
// 计票
proposals[proposal].voteCount += sender.weight;
emit Voted(msg.sender, proposal);
}
// 计算获胜提案
function winningProposal() public view returns (uint winningProposal_) {
uint winningVoteCount = 0;
for (uint p = 0; p < proposals.length; p++) {
if (proposals[p].voteCount > winningVoteCount) {
winningVoteCount = proposals[p].voteCount;
winningProposal_ = p;
}
}
}
// 获取获胜提案名称
function winnerName() public view returns (bytes32 winnerName_) {
winnerName_ = proposals[winningProposal()].name;
}
// 获取提案数量
function getProposalsCount() public view returns (uint) {
return proposals.length;
}
// 获取提案详情
function getProposal(uint index) public view returns (bytes32 name, uint voteCount) {
require(index < proposals.length, "Invalid proposal index");
Proposal memory proposal = proposals[index];
return (proposal.name, proposal.voteCount);
}
// 获取投票者信息
function getVoter(address addr) public view returns (uint weight, bool voted, uint vote, address delegate) {
Voter memory voter = voters[addr];
return (voter.weight, voter.voted, voter.vote, voter.delegate);
}
}
2.2 编译合约
bash
# 安装solc编译器
npm install -g solc
# 编译合约
solcjs --bin --abi Voting.sol -o build/
三、Go SDK合约调用实现
3.1 项目结构
XML
voting-demo/
├── go.mod
├── main.go
├── contracts/
│ └── Voting.sol
├── build/
│ ├── Voting.abi
│ └── Voting.bin
└── conf/
├── ca.crt
├── sdk.crt
└── sdk.key
3.2 Go SDK客户端封装
Go
// client/client.go
package client
import (
"context"
"fmt"
"log"
"math/big"
"strings"
"github.com/FISCO-BCOS/go-sdk/client"
"github.com/FISCO-BCOS/go-sdk/conf"
"github.com/FISCO-BCOS/go-sdk/core/types"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
)
type FiscoClient struct {
config *conf.Config
client *client.Client
privateKey string
}
func NewFiscoClient(configFile, privateKey string) (*FiscoClient, error) {
configs, err := conf.ParseConfigFile(configFile)
if err != nil {
return nil, fmt.Errorf("parse config failed: %v", err)
}
if len(configs) == 0 {
return nil, fmt.Errorf("no config found")
}
config := configs[0]
cli, err := client.Dial(config)
if err != nil {
return nil, fmt.Errorf("dial client failed: %v", err)
}
return &FiscoClient{
config: config,
client: cli,
privateKey: privateKey,
}, nil
}
func (fc *FiscoClient) DeployContract(abiJSON, binHex string, params ...interface{}) (common.Address, *types.Transaction, error) {
parsedABI, err := abi.JSON(strings.NewReader(abiJSON))
if err != nil {
return common.Address{}, nil, err
}
input, err := parsedABI.Pack("", params...)
if err != nil {
return common.Address{}, nil, err
}
data := common.FromHex(binHex)
data = append(data, input...)
address, tx, _, err := fc.client.DeployContract(fc.privateKey, abiJSON, data)
if err != nil {
return common.Address{}, nil, err
}
return address, tx, nil
}
func (fc *FiscoClient) CallContract(address common.Address, abiJSON string, method string, params ...interface{}) ([]interface{}, error) {
parsedABI, err := abi.JSON(strings.NewReader(abiJSON))
if err != nil {
return nil, err
}
input, err := parsedABI.Pack(method, params...)
if err != nil {
return nil, err
}
msg := ethereum.CallMsg{
To: &address,
Data: input,
}
output, err := fc.client.CallContract(context.Background(), msg, nil)
if err != nil {
return nil, err
}
return parsedABI.Unpack(method, output)
}
func (fc *FiscoClient) SendTransaction(address common.Address, abiJSON string, method string, params ...interface{}) (*types.Transaction, error) {
parsedABI, err := abi.JSON(strings.NewReader(abiJSON))
if err != nil {
return nil, err
}
input, err := parsedABI.Pack(method, params...)
if err != nil {
return nil, err
}
tx, err := fc.client.SendTransaction(fc.privateKey, address, input)
if err != nil {
return nil, err
}
return tx, nil
}
func (fc *FiscoClient) GetTransactionReceipt(txHash common.Hash) (*types.Receipt, error) {
return fc.client.GetTransactionReceipt(context.Background(), txHash, false)
}
3.3 投票合约调用示例
Go
// main.go
package main
import (
"encoding/hex"
"fmt"
"io/ioutil"
"log"
"strings"
"github.com/FISCO-BCOS/go-sdk/core/types"
"github.com/ethereum/go-ethereum/common"
"voting-demo/client"
)
type VotingContract struct {
address common.Address
abi string
bin string
client *client.FiscoClient
}
func NewVotingContract(client *client.FiscoClient, abiPath, binPath string) (*VotingContract, error) {
abiData, err := ioutil.ReadFile(abiPath)
if err != nil {
return nil, err
}
binData, err := ioutil.ReadFile(binPath)
if err != nil {
return nil, err
}
// 清理bin文件中的换行符
binHex := strings.TrimSpace(string(binData))
return &VotingContract{
abi: string(abiData),
bin: binHex,
client: client,
}, nil
}
func (vc *VotingContract) Deploy(proposalNames []string) error {
// 将字符串转换为bytes32
var bytes32Names [][32]byte
for _, name := range proposalNames {
var bytes32Name [32]byte
copy(bytes32Name[:], name)
bytes32Names = append(bytes32Names, bytes32Name)
}
address, tx, err := vc.client.DeployContract(vc.abi, vc.bin, bytes32Names)
if err != nil {
return fmt.Errorf("deploy failed: %v", err)
}
vc.address = address
fmt.Printf("Contract deployed at: %s\n", address.Hex())
fmt.Printf("Transaction hash: %s\n", tx.Hash().Hex())
// 等待交易确认
receipt, err := vc.waitForTransaction(tx.Hash())
if err != nil {
return err
}
fmt.Printf("Contract deployed successfully. Gas used: %d\n", receipt.GasUsed)
return nil
}
func (vc *VotingContract) GiveVotingRight(voterAddress string) error {
address := common.HexToAddress(voterAddress)
tx, err := vc.client.SendTransaction(vc.address, vc.abi, "giveRightToVote", address)
if err != nil {
return err
}
receipt, err := vc.waitForTransaction(tx.Hash())
if err != nil {
return err
}
fmt.Printf("Voting right given to %s. Status: %d\n", voterAddress, receipt.Status)
return nil
}
func (vc *VotingContract) Vote(proposalIndex uint) error {
tx, err := vc.client.SendTransaction(vc.address, vc.abi, "vote", big.NewInt(int64(proposalIndex)))
if err != nil {
return err
}
receipt, err := vc.waitForTransaction(tx.Hash())
if err != nil {
return err
}
fmt.Printf("Voted for proposal %d. Status: %d\n", proposalIndex, receipt.Status)
return nil
}
func (vc *VotingContract) GetWinner() (string, error) {
results, err := vc.client.CallContract(vc.address, vc.abi, "winnerName")
if err != nil {
return "", err
}
if len(results) > 0 {
if winnerName, ok := results[0].([32]byte); ok {
// 移除末尾的空字节
return strings.TrimRight(string(winnerName[:]), "\x00"), nil
}
}
return "", fmt.Errorf("failed to get winner")
}
func (vc *VotingContract) GetProposalCount() (uint, error) {
results, err := vc.client.CallContract(vc.address, vc.abi, "getProposalsCount")
if err != nil {
return 0, err
}
if len(results) > 0 {
if count, ok := results[0].(*big.Int); ok {
return uint(count.Uint64()), nil
}
}
return 0, fmt.Errorf("failed to get proposal count")
}
func (vc *VotingContract) GetProposal(index uint) (string, uint, error) {
results, err := vc.client.CallContract(vc.address, vc.abi, "getProposal", big.NewInt(int64(index)))
if err != nil {
return "", 0, err
}
if len(results) >= 2 {
name := results[0].([32]byte)
voteCount := results[1].(*big.Int)
proposalName := strings.TrimRight(string(name[:]), "\x00")
return proposalName, uint(voteCount.Uint64()), nil
}
return "", 0, fmt.Errorf("failed to get proposal")
}
func (vc *VotingContract) GetVoterInfo(address string) (uint, bool, uint, string, error) {
addr := common.HexToAddress(address)
results, err := vc.client.CallContract(vc.address, vc.abi, "getVoter", addr)
if err != nil {
return 0, false, 0, "", err
}
if len(results) >= 4 {
weight := results[0].(*big.Int)
voted := results[1].(bool)
voteIndex := results[2].(*big.Int)
delegate := results[3].(common.Address)
return uint(weight.Uint64()), voted, uint(voteIndex.Uint64()), delegate.Hex(), nil
}
return 0, false, 0, "", fmt.Errorf("failed to get voter info")
}
func (vc *VotingContract) waitForTransaction(txHash common.Hash) (*types.Receipt, error) {
for {
receipt, err := vc.client.GetTransactionReceipt(txHash)
if err != nil {
return nil, err
}
if receipt != nil {
return receipt, nil
}
// 等待一段时间再重试
// 在实际应用中,建议使用更复杂的重试逻辑
// time.Sleep(1 * time.Second)
}
}
func main() {
// 初始化客户端
fiscoClient, err := client.NewFiscoClient("conf/config.toml", "your_private_key_here")
if err != nil {
log.Fatal(err)
}
// 创建投票合约实例
votingContract, err := NewVotingContract(fiscoClient, "build/Voting.abi", "build/Voting.bin")
if err != nil {
log.Fatal(err)
}
// 部署合约
fmt.Println("Deploying voting contract...")
proposals := []string{"Proposal A", "Proposal B", "Proposal C"}
err = votingContract.Deploy(proposals)
if err != nil {
log.Fatal(err)
}
// 给投票者授权
fmt.Println("\nGiving voting rights...")
voterAddress := "0xYourVoterAddressHere"
err = votingContract.GiveVotingRight(voterAddress)
if err != nil {
log.Fatal(err)
}
// 投票
fmt.Println("\nVoting...")
err = votingContract.Vote(0) // 投给第一个提案
if err != nil {
log.Fatal(err)
}
// 查询投票结果
fmt.Println("\nGetting voting results...")
winner, err := votingContract.GetWinner()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Winner: %s\n", winner)
// 查询提案数量
count, err := votingContract.GetProposalCount()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Total proposals: %d\n", count)
// 查询提案详情
for i := uint(0); i < count; i++ {
name, votes, err := votingContract.GetProposal(i)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Proposal %d: %s - %d votes\n", i, name, votes)
}
// 查询投票者信息
weight, voted, voteIndex, delegate, err := votingContract.GetVoterInfo(voterAddress)
if err != nil {
log.Fatal(err)
}
fmt.Printf("\nVoter Info:\nWeight: %d\nVoted: %v\nVote Index: %d\nDelegate: %s\n",
weight, voted, voteIndex, delegate)
}
3.4 配置文件 conf/config.toml
XML
[network]
peers=["127.0.0.1:20200"] # 节点RPC端口
[chain]
chainId=1
groupId=1
[cryptoMaterial]
certPath="conf"
# caCertPath = "conf/ca.crt"
# sslCert = "conf/sdk.crt"
# sslKey = "conf/sdk.key"
# enSslCert = "conf/gm/gmensdk.crt"
# enSslKey = "conf/gm/gmensdk.key"
[account]
keyFile="conf/sdk.key"
[retry]
connectionTimeout=10000
reconnectRetryInterval=10000
四、测试脚本
bash
# 编译Go程序
go mod init voting-demo
go mod tidy
go build -o voting-demo
# 运行程序
./voting-demo
五、Web3.js前端集成示例(可选)
javascript
// web3-voting.js
const Web3 = require('web3');
const fs = require('fs');
class VotingDApp {
constructor(config) {
this.web3 = new Web3(config.rpcUrl);
this.account = config.account;
this.privateKey = config.privateKey;
this.contractAddress = config.contractAddress;
this.contractABI = JSON.parse(fs.readFileSync(config.abiPath, 'utf8'));
this.contract = new this.web3.eth.Contract(
this.contractABI,
this.contractAddress
);
}
async giveRightToVote(voterAddress) {
const tx = {
from: this.account,
to: this.contractAddress,
gas: 3000000,
data: this.contract.methods.giveRightToVote(voterAddress).encodeABI()
};
const signedTx = await this.web3.eth.accounts.signTransaction(
tx,
this.privateKey
);
return await this.web3.eth.sendSignedTransaction(signedTx.rawTransaction);
}
async vote(proposalIndex) {
const tx = {
from: this.account,
to: this.contractAddress,
gas: 3000000,
data: this.contract.methods.vote(proposalIndex).encodeABI()
};
const signedTx = await this.web3.eth.accounts.signTransaction(
tx,
this.privateKey
);
return await this.web3.eth.sendSignedTransaction(signedTx.rawTransaction);
}
async getWinner() {
return await this.contract.methods.winnerName().call();
}
async getProposalCount() {
return await this.contract.methods.getProposalsCount().call();
}
}
六、部署和测试步骤总结
-
1、启动FISCO BCOS节点
-
2、编译Solidity合约:生成ABI和BIN文件
-
3、配置Go SDK:设置证书和连接信息
-
4、部署合约:使用Go SDK部署投票合约
-
5、测试合约功能:
-
给投票者授权
-
执行投票
-
查询投票结果
-
获取提案信息
-
注意事项
-
1、私钥安全:生产环境中不要硬编码私钥
-
2、交易确认:添加适当的交易确认等待逻辑
-
3、错误处理:完善错误处理和重试机制
-
4、Gas设置:根据合约复杂度调整Gas限制
-
5、链上数据:注意FISCO BCOS与以太坊的差异
这个完整的实现方案包含了从合约编写到Go SDK调用的全流程,您可以根据实际需求进行调整和扩展。