来源:简易UTXO与账户模型的数字资产智能合约Hydruscoin -【已失效】
开源:开源库 hydruscoin【已失效】,作者:mint.zhao.chiu@gmail.com
转载:简易UTXO与账户模型的数字资产智能合约Hydruscoin – 区块链大学
参考:srderson/hyperledger-fabric-utxo-client-java
Hyperledger Fabric 是 Linux基金会 开源的区块链平台实现,由IBM主导开发。对广大智能合约开发者来说,我们可以摒弃Fabric底层实现不谈,只需要关注上层智能合约开发包chaincode就可以了。但是由于区块链技术概念较新,更不用说基于区块链技术进行智能合约开发了,能借鉴的文章、demo都很少。我结合自身实践和官方chaincode例子,实现了一个类似于bitcoin的数字货币。现已开源【已失效】,下面为大家简要讲解一下开发思路:
Chinacode初识
什么样的一个应用程序能被称作是一个chaincode?
type SimpleChaincode struct {
}
// Init callback representing the invocation of a chaincode
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) ([]byte, error) {
return nil, nil
}
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) ([]byte, error) {
return nil, nil
}
// Query callback representing the query of a chaincode
func (t *SimpleChaincode) Query(stub shim.ChaincodeStubInterface) ([]byte, error) {
return nil, nil
}
func main() {
err := shim.Start(new(SimpleChaincode))
if err != nil {
panic(err)
}
}
只需要 cc 对象实现Chaincode 接口,并在main函数中通过shim.Start(cc)启动,我们就可以说这是一个chaincode(智能合约)应用程序。
注:只要返回错误,接口所进行写入操作都会自动回滚
主要功能
- coinbase交易
- 数字货币转账
- 账户查询
- 交易查询
- 数字货币简单统计
实现init接口
Init 主要是用于chaincode注册,在chaincode整个生命周期中,只执行一次。主要目的是初始化一些变量。
在这个例子中,hydruscoin只是初始化了一个统计对象,代码如下:
// construct a new store
store := MakeChaincodeStore(stub)
// deploy hydruscoin chaincode only need to set coin stater
if err := store.InitCoinInfo(); err != nil {
return nil, err
}
InitCoinInfo()代码如下:
func (s *ChaincodeStore) InitCoinInfo() error {
coinInfo := &HydruscoinInfo{
CoinTotal: 0,
AccountTotal: 0,
TxoutTotal: 0,
TxTotal: 0,
Placeholder: "placeholder",
}
return s.PutCoinInfo(coinInfo)
}
func (s *ChaincodeStore) PutCoinInfo(coinfo *HydruscoinInfo) error {
coinBytes, err := proto.Marshal(coinfo)
if err != nil {
return err
}
if err := s.stub.PutState(coinInfoKey, coinBytes); err != nil {
return err
}
return nil
}
实现Invoke接口
Invoke 接口是 Chaincode 设计的重中之重,是智能合约与外部进行数据交换的主要通道。通过给 Invoke 接口传递不同的function,配以不同的args ,整个智能合约就活灵活现起来。
在这个例子里,Invoke 主要有两个功能:
- coinbase(产生数字货币),
- transfer(转移数字货币)。
// Invoke function
const (
IF_COINBASE string = "invoke_coinbase"
IF_TRANSFER string = "invoke_transfer"
)
// Invoke
func (coin *Hydruscoin) Invoke(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
// construct a new store
store := MakeChaincodeStore(stub)
switch function {
case IF_COINBASE:
return coin.coinbase(store, args)
case IF_TRANSFER:
return coin.transfer(store, args)
default:
return nil, ErrUnsupportedOperation
}
}
子功能1:Invoke Coinbase
coinbase 一词源自比特币,意指凭空产生数字货币,即货币发行,央行印钞一个道理。代码看着有些复杂,我简单梳理一下逻辑关系。
- 解码经过base64编码的交易TX
- 反序列化proto message至交易对象
- 循环验证交易TX的交易输入TX_IN,如果不是coinbase交易,则返回错误
- 循环验证交易TX的交易输出TX_OUT
4a. 读取交易输出TX_OUT相关地址的账号信息,如果错误,默认为账户不存在,直接新建一个
4b. 将交易输出TX_OUT的额度转移到账户余额里,并保存该交易输出
4c. 将更新写入区块链中 - 循环交易输出的同时也将同步更新统计对象
- 单独存储该交易至链上
子功能2:Invoke Transfer
transfer即所谓的转账。业务简单流程如下:
- 解码经过base64编码的交易TX
- 反序列化proto message至交易对象
- 验证交易发起者founder的身份信息
- 循环验证交易TX的交易输入TX_IN
4a. 验证交易发起者founder是否拥有此交易输出(ps:交易输入即之前交易的输出)
4b. 验证此交易输出TX_OUT是否是可花费的
4c. 如果都验证成功,删除之前的交易输出TX_OUT,更新交易发起者founder的账户信息 - 循环验证交易TX的交易输出TX_OUT
5a. 查询交易输出TX_OUT的接收者账户信息
5b. 根据交易输出TX_OUT,更新接收者账户 - 循环验证的同时同步更新统计对象
- 全局验证交易TX的正确性
- 单独存储该交易至链上
实现Query接口
Query 是 Chaincode 的查询接口,通过该接口可以查询存储在区块链上的数据的最终状态。
注意:在Query中任何对数据的更改都是不被允许的
我现在实现了4个简单的查询功能:查询账户信息,查询账户集信息,查询交易信息,查询统计信息。
// Query function
const (
QF_ADDR = "query_addr"
QF_ADDRS = "query_addrs"
QF_TX = "query_tx"
QF_COIN = "query_coin"
)
// Query
func (coin *Hydruscoin) Query(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
// construct a new store
store := MakeChaincodeStore(stub)
switch function {
case QF_ADDR: return coin.queryAddr(store, args)
case QF_ADDRS: return coin.queryAddrs(store, args)
case QF_TX: return coin.queryTx(store, args)
case QF_COIN: return coin.queryCoin(store, args)
default: return nil, ErrUnsupportedOperation
}
}
Query Addr/Addrs
在现在的设计中,任何人都可以查询任意一个账户的信息,只要你知道账户的地址。这样的设计肯定是有问题的,但是我们不就是个demo么?
func (coin *Hydruscoin) queryAddrs(store Store, args []string) ([]byte, error) {
results := &QueryAddrResults{
Results: make([]*QueryAddrResult, 0),
}
for _, arg := range args {
addr := arg
queryResult := new(QueryAddrResult)
account, err := store.GetAccount(addr)
if err != nil {
return nil, err
}
queryResult.Account = account
results.Results = append(results.Results, queryResult)
logger.Debugf("query addr[%s] result: %+v", addr, queryResult)
}
return proto.Marshal(results)
}
可以看到,仅仅是根据输入的地址信息,去区块链上查找账户信息,未作任何处理。
Query TX
在之前的Coinbase 和Transfer 中,最后都将交易信息存储在了链上。只要你知道交易TX 的Hash值,那么你就能通过该接口查询到交易的具体信息。
// GetTx returns a transaction for the given hash
func (s *ChaincodeStore) GetTx(key string) (*TX, bool, error) {
data, err := s.stub.GetState(key)
if err != nil {
return nil, false, fmt.Errorf("Error getting state from stub: %s", err)
}
if data == nil || len(data) == 0 {
return nil, false, nil
}
tx, err := ParseTXBytes(data)
if err != nil {
return nil, false, err
}
return tx, true, nil
}
Query Coin
这个不用多说,就是获取统计对象。
func (coin *Hydruscoin) queryCoin(store Store, args []string) ([]byte, error) {
if len(args) != 0 {
return nil, ErrInvalidArgs
}
coinInfo, err := store.GetCoinInfo()
if err != nil {
logger.Errorf("Error get coin info: %v", err)
return nil, err
}
logger.Debugf("query lepuscoin info: %+v", coinInfo)
return proto.Marshal(coinInfo)
}
暂存问题
- 未对交易进行签名:在 demo 阶段,暂时还未实现。
srderson/hyperledger-fabric-utxo-client-java
OBC UTXO Chaincode Client
This demo client will read transactions from Bitcoin blocks on the local system and send the transactions to the UTXO example chaincode for processing. It utilizes the gRPC API for communication between the client and OBC peer.
Instructions
- Deploy the UTXO example chaincode
- Clone this project
git clone https://github.com/srderson/obc-utxo-client-java.git
cd obc-utxo-client-java
- Edit the config.properties file in ./src/main/java/com/github/openblockchain/obcpeer/utxo
- Build
./gradlew build
- Copy the config.properties file ./src/main/java/com/github/openblockchain/obcpeer/utxo/config.properties to ./build/classes/main/com/github/openblockchain/obcpeer/utxo/config.properties
- Start the client
./gradlew run
hyperledger的examples(chaincode/go/utxo)
https://github.com/hyperledger-archives/fabric/tree/master/examples/chaincode/go/utxo
UTXO Chaincode
The UTXO example chaincode contains a single invocation function named execute . This function accepts BASE64 encoded transactions from the Bitcoin network. This chaincode will parse the transactions and pass the transaction components to the Bitcoin libconsensus C library for script verification.
The purpose of this chaincode is to
- Demonstrate how the world state can be used to store and process unspent transaction outputs (UTXO).
- Demonstrate how to include and use a C library from within a chaincode.
A client for exercising this chaincode is avilable at https://github.com/srderson/hyperledger-fabric-utxo-client-java.
The following are instructions for building and deploying the UTXO chaincode in Hypereledger Fabric. All commands should be run with vagrant.