区块链入坑[1]--浅析Smart Contract,BlockChain的概念与ChainCode的编写

区块链入坑[1]--浅析Smart Contract,BlockChain的概念与ChainCode的编写

  • 1.浅析基于区块链的智能合约(Smart Contract)
    • 智能合约
    • 基于区块链的智能合约
  • 2.链码以及案例sacc (ChainCode)
    • yaml文件&区块链的结构
    • 使用开发模式测试链码
  • 3.自己的代码

1.浅析基于区块链的智能合约(Smart Contract)

智能合约

在了解智能合约和传统合约之前我们先来了解合约(Contract)这个词本身,“合同(或合约),是双方当事人基于意思表示合致而成立的法律行为,为私法自治的主要表现。”在现在的日常生活中合约常常是由人或者指定的机构来代为执行的,这就是传统合约。

“智能合约(Smart contract)"这个概念由计算机科学家、加密学家尼克萨博(NickSzabo) 在1993年提出并且在1994年完成了《智能合约》论文。该论文成为了智能合约的开山之作。作为一位通过给比特币打下基础而受到广泛赞誉的密码学家,尼克萨博为智能合约下的定义:“A smart contract is a set of promises defined in digital form, including agreements on which contract participants can execute these commitments.。

传统合约其实也在演化,尤其是网购这种行为已经智能合约,所谓的"智能"就是脱离了人和机构,交由计算机去管理和执行的自动行为。 以网购为例,我们来了解智能合约的显著特征。下面假设 买家A通过淘宝Alipay卖家B买价值为100$的东西,那么计算机所执行的操作逻辑如下(LaTeX作图):
区块链入坑[1]--浅析Smart Contract,BlockChain的概念与ChainCode的编写_第1张图片
本质上是如下的单一关系(Word作图0.0)
区块链入坑[1]--浅析Smart Contract,BlockChain的概念与ChainCode的编写_第2张图片
在这其中,支付宝(Alipay)是合约状态的判定者,钱的由支付宝保管,交易受支付宝监管。支付宝是合约的判定者和执行者,双方的资产转移,查询也全部由支付宝完成。

交易的状态判定和资产转移全部交给计算机重复地自动地执行,这就是典型的智能合约

这样的智能合约有很多隐患。我们不妨将合约判定者推广到支付宝以外的各个银行,交易机构甚至是日常生活中,只要中介机构的判断一方出现问题,你并没有收到东西,但是中介机构作假说判定卖家已经完成交易,你的钱已经被转走,那就可以破坏整个交易的公平性。或者交易机构的处理不够及时也能够造成损失。

之前所提到的大佬,加密学家萨博 就认为这种仅仅依靠第三方担保的信用机制是不可靠的,
经过一番思考他认为所有的交易合同都没有必要通过第三方完成,只要合同的条款可以用编程语言来表达,让计算机自动处理就行了。这样就避免了合同执行中的尔虞我诈,还节约了大量的交易成本。
然而这个想法也只是一个雏形,在他所处的90年代还没有数字资产,也没有很强大的计算机加密手段提出。这使得他的这种思想并不能很好地实践,而仅仅是用在了构建自动售货机、自动售票机的交易模式上。他缺少四样东西:

  1. 分布式的网络结构,能够避免第三方机构一家独大的网络结构。
  2. 在上述网络结构中,用以流通的数字话的,能够在计算机中记录的货币。
  3. 对上述数字化的货币唯一地,透明的加密手段。
  4. 网络结构中还要有传统的背书机构来记录全部交易。

而区块链的出现,使得萨博在90年代提出的超越时代的构想有了变为现实的能力。

基于区块链的智能合约

我们来想想区块链的特征:

  • DApp去中心化,使得参与交易者可以自证而不依赖第三方中介机构来背书。
  • 唯一性的数字货币。
  • 交易不可篡改,哈希以及系列编码手段使得交易记录唯一且不可被篡改。
  • 共识信任,通过技术来背书而不依赖机构。
  • 开放性以及过程透明,除了加密过的信息,其余的数据和应用都是可追溯,公开透明的。

Σ(っ °Д °;)っ这,这不就是…

是的,你会发现,这些特性完美地契合了萨博在90s的构想 (๑•̀ㅂ•́)و✧。
智能合约一旦上链,就没有了像之前依赖一方信任机构时各种各样的安全风险,并且变得透明,可追溯。

同样对上述买家A卖家B的交易,如果网购使用基于区块链的智能合约来完成,则你会发现缺少了Alipay,取而代之的是各式节点,流程图如下:(在线作图0.0)
区块链入坑[1]--浅析Smart Contract,BlockChain的概念与ChainCode的编写_第3张图片
买家卖家双方,应用程序和管理员全部通过Peer节点来同步信息,Peer节点一手拿着账本(Ledger),一手拿着合约(ChainCode)。上图只是简单地描述整个过程,事实上一个Application/SDK会连接多个Peer节点和Order节点,网络图也更加复杂,并且Peer节点所持有的账本也有可能会是多项。

  1. Peer节点知道全部的规则并且管理所有参与者的资产。但是Peer在收到买卖双方信息之后并不直接记录到账本里面,而是返回一个结果响应并且保持账本数据。
  2. 当买卖双方收到足够的结果响应或者达到足够的时长之后,这些批量的数据将会打包发给Orderer,Orderer来对收到的数据进行排序,所有交易的排序是严格的,Orderer生成的区块也是不可篡改的,具有唯一性。生成区块之后将会分发给各个Peer。
  3. 各个Peer对照自己记录的结果响应和从[2]收到的区块,如果验证通过,那么再去检查账本的状态与合约是否一致:一致则交易有效,Peer节点更新账本状态;不一致则交易无效但是失败的交易还是会被保存,因为失败的交易所对应的区块也是唯一的,需要保存下来以保证基本的交易是否合法。

从上述的过程中就可以明白只要大多数的节点没有崩坏,那么交易就是安全的,而不像传统节点那样依赖中介机构一方的数据;并且由于Orderer和Peer节点定时以及定量地去检查交易的状态,使得交易的执行也是及时的。智能合约中更多地是P2P的交易,这使得很多交易可以透明,安全地完成而不需要第三方去监管。

2.链码以及案例sacc (ChainCode)

从上述对于上链过后的智能合约的简单描述中也可以知晓ChainCode负责的是P2P用户之间已经协商好的规则,也就是合约本身的内容,这个关键的规则应该被提前写入智能合约中。那么我们来分析ChainCode应该去完成的基本内容:

  1. 首先ChainCode应该有一个Init函数来初始化交易双方的资产和状态,或者Init函数只是返回初始化成功的信息。
  2. ChainCode应该有add,delete或者类似的能够增删账户数据的函数。
  3. ChainCode应该有query函数来询问现有账户的数据。
  4. 此外各个函数的报错信息应该完善。

yaml文件&区块链的结构

在区块链的开放当中我们使用的是yaml文件,百科出门右转度娘-YAML。
yaml =“Yet Another Markup Language” 从英文解释可以看出yaml仍然是一种标志语言。它要比json方便,同时简洁而强大。话不多说,直接上fabric中的自带yaml配置文件docker-compose-simple.yaml(节选其中Orderer部分):

  orderer:
    container_name: orderer
    image: hyperledger/fabric-orderer
    environment:
      - FABRIC_LOGGING_SPEC=debug
      - ORDERER_GENERAL_LISTENADDRESS=orderer
      - ORDERER_GENERAL_GENESISMETHOD=file
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric
    ports:
      - 7050:7050
  1. yaml中对象使用来表示,并且可以复用
  2. yaml中数组结构使用-来表示,子成员则在之后缩进

那么上述文件对应的JS代码应该如下:

order: 
{
container_name:'orderer'
image:'hyperledger/fabric-orderer'}
environment:['FABRIC_LOGGING_SPEC=debug',
      		'ORDERER_GENERAL_LISTENADDRESS=orderer',
      		'ORDERER_GENERAL_GENESISMETHOD=file']
working_dir: '/opt/gopath/src/github.com/hyperledger/fabric'
ports:'7050:7050'
}

docker-compose-simple.yaml中,我们可以看到基本的结构为
Orderer,Peer,Cli,ChainCode四个部分。
也可以打开docker-compose-base.yaml查看记录的区块链的基础结构为
两个Org联盟Org1和Org2,各个联盟里面有包含两个Peer节点Peer1和Peer2,同时还各自包含一个Orderer。
类似的文件有很多,例如负责通信的docker-compose-base.ca和各个connection文件,基本上fabric里所有的关于结构的配置文件全部都是yaml完成的。在跑源码之前可以先去查看这些文件。

使用开发模式测试链码

我们需要开三个Terminal来完成client,chaincode的测试。
yaml文件记录了区块链中的Org,Peer,Orderer以及相关结构的具体信息所以我们先
Terminal 1中启动 docker-compose-simple.yaml

docker-compose -f docker-compose-simple.yaml up

//结果如下:
Creating orderer ... done
Creating peer    ... done
Creating cli       ... done
Creating chaincode ... done
Attaching to orderer, peer, cli, chaincode
peer         | 2019-05-04 10:48:34.210 UTC [viperutil] getKeysRecursively -> DEBU 001 Found map[string]interface{} value for peer.BCCSP
orderer      | 2019-05-04 10:48:33.111 UTC [localconfig] completeInitialization -> INFO 001 Kafka.Version unset, setting to 0.10.2.0
peer         | 2019-05-04 10:48:34.211 UTC [viperutil] unmarshalJSON -> DEBU 002 Unmarshal JSON: value cannot be unmarshalled: invalid character 'S' looking for beginning of value
peer         | 2019-05-04 10:48:34.211 UTC [viperutil] getKeysRecursively -> DEBU 003 Found real value for peer.BCCSP.Default se

记得一定要检查oderer,peer,cli,chaincode(这个对于不同的项目,结构不同,以自己的yaml中的结构为准)有没有初始化成功,因为会输出很长的文字,所以不太容易直接看是否允许成功,lz第一次的时候就是peer失败了,但是没有仔细看,结果后面一路报错QAQ。
使用docker ps查看进程:

docker ps

//结果如下:
CONTAINER ID         COMMAND        CREATED    STATUS       PORTS                                                             NAMES
c7b0d0a23a54        hyperledger/fabric-ccenv     "/bin/bash -c 'sleep…"                                                       chaincode
589f8cd7b518        hyperledger/fabric-tools     "/bin/bash -c ./scri…"                                                       cli
ceeaaf3e5913        hyperledger/fabric-peer      "peer node start --p…"     0.0.0.0:7051->7051/tcp, 0.0.0.0:7053->7053/tcp    peer
d340752b0409        hyperledger/fabric-orderer   "orderer"                   0.0.0.0:7050->7050/tcp                           orderer

Terminal 2中输入 以下命令进入chaincode端口:

docker exec -it chaincode bash

进入go脚本sacc的文件夹,编译我们的go脚本:

go build

执行刚刚编译好的sacc:(看到starting up…卡着不动就是正常运行了)

CORE_PEER_ADDRESS=peer:7052 CORE_CHAINCODE_ID_NAME=mycc:0 ./sacc

//结果如下:(starting up代表开始运作,之后这个端口会一直记录chaincode的状态)
2019-05-04 10:55:51.401 UTC [shim] SetupChaincodeLogging -> INFO 001 Chaincode log level not provided; defaulting to: INFO
2019-05-04 10:55:51.402 UTC [shim] SetupChaincodeLogging -> INFO 002 Chaincode (build level: ) starting up ...

这里时常会有报错:
Error starting SimpleAsset chaincode: error sending: rpc error: code = Unimplemented desc = unknown service protos.ChaincodeSupp
那是因为在1.1.0以及之后的版本当中不在使用7051端口,使用7051的同学可能会报错。

Terminal 3中,使用如下命令进入cli的端口

docker exec -it cli bash

使用[“A”,“1”]初始化键值对得到如下结果:

peer chaincode instantiate -n mycc -v 0 -c '{"Args":["A","100"]}' -C myc

//结果如下:
2019-05-04 11:14:16.661 UTC [chaincodeCmd] InitCmdFactory -> INFO 001 Retrieved channel (myc) orderer endpoint: orderer:7050
2019-05-04 11:14:16.665 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default escc
2019-05-04 11:14:16.666 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 003 Using default vscc

同样可以使用set函数来修改A的值:

peer chaincode invoke -n mycc -c '{"Args":["set", "A", "200"]}' -C myc

使用query函数来查询A的值:

peer chaincode query -n mycc -c '{"Args":["query","A"]}' -C myc

这个过程中还会有很多莫名其妙的错误,比如以下的error code 500错误

Error: could not assemble transaction, err Proposal response was not successful, error code 500

一方面可以调整函数的传入参数,事实上多数情况就是传入参数不符合定义;
另一方面实在找不到原因就可以使用重启大法删除容器,再重新开始:

docker rm `docker ps -a -q`

其中:

-a, --all      Show all containers (default shows just running)  全部容器
-q, --quiet    Only display numeric IDs	仅仅显示数字ID

3.自己的代码

实验课老师具体的要求还没有讲,所以先大致把可能会用到的函数写出来:
1.Invoke函数

func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
	fmt.Println("security Invoke")
	function, args := stub.GetFunctionAndParameters()
	if function == "add" {		//add fromID, toID, Hash, OwnerID
		return t.add(stub, args)
	} else if function == "delete" {	//delete by OwnerID
		return t.delete(stub, args) 
	} else if function == "queryByID" {		//get callback Info by OwnerID
		return t.queryByID(stub, args)
	} else if function == "transfer" {		//(Possible)trasaction from AtoB
		return t.transfer(stub, args)
	} else if function == "queryByOwnerID" {//just as what U read
		return t.queryByOwnerID(stub, args)
	} else if function == "queryAll" {		//query all Owners
		return t.queryAll(stub, args)
	} else if function == "totalSupply" {	//all Apply
		return t.totalSupply(stub, args)
	} else if function == "balanceOf" {		//just as what U read
		return t.balanceOf(stub, args)
	}
	return shim.Error("Invalid invoke function name.")
}

2.add函数

func (t *SimpleChaincode) add(stub shim.ChaincodeStubInterface, args []string) pb.Response {

	if len(args) != 4 {
		return shim.Error("Incorrect number of arguments. Expecting 4")
	}
	if len(args[0]) <= 0 {
		return shim.Error("1st argument must be a non-empty string")
	}
	if len(args[1]) <= 0 {
		return shim.Error("2nd argument must be a non-empty string")
	}
	if len(args[2]) <= 0 {
		return shim.Error("3rd argument must be a non-empty string")
	}
	if len(args[3]) <= 0 {
		return shim.Error("4th argument must be a non-empty string")
	}
	FromId := args[0]
	ToId := args[1]
	FileHash := args[2]
	OwnerID := args[3]

	ID := FromId + "!!" + FileHash + "!!" + ToId
	jsonstring, err := stub.GetState(ID)
	if err != nil {
		return shim.Error("Failed to get state")
	}
	if jsonstring != nil {
		return shim.Error("this security already exists")
	}
	tx := &TransactionResult{"transaction", ID, FromId, ToId, FileHash, OwnerID}
	secStr, err := json.Marshal(tx)

	fmt.Println("in add: ")
	fmt.Printf("%s\n", secStr)
	if err != nil {
		return shim.Error(err.Error())
	}
	err = stub.PutState(ID, secStr)
	if err != nil {
		return shim.Error(err.Error())
	}
	return shim.Success(secStr)
}

3.delete函数

func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	if len(args) != 1 {
		return shim.Error("Incorrect number of arguments. Expecting 1")
	}

	id := args[0]

	err := stub.DelState(id)
	if err != nil {
		return shim.Error("Failed to delete state")
	}

	return shim.Success(nil)
}

4.queryByID函数

func (t *SimpleChaincode) queryByID(stub shim.ChaincodeStubInterface, args []string) pb.Response {

	if len(args) != 1 {
		return shim.Error("Incorrect number of arguments. Expecting id of the security to query")
	}

	id := args[0]

	// Get the state from the ledger
	securityString, err := stub.GetState(id)
	if err != nil {
		jsonResp := "{\"Error\":\"Failed to get state for " + id + "\"}"
		return shim.Error(jsonResp)
	}

	if securityString == nil {
		jsonResp := "{\"Error\":\"Nil amount for " + id + "\"}"
		return shim.Error(jsonResp)
	}

	fmt.Printf("Query Response:%s\n", string(securityString))
	return shim.Success(securityString)
}

5.transfer函数

func (t *SimpleChaincode) transfer(stub shim.ChaincodeStubInterface, args []string) pb.Response {

	if len(args) != 2 {
		return shim.Error("Incorrect number of arguments. Expecting 2")
	}
	if len(args[0]) <= 0 {
		return shim.Error("1st argument must be a non-empty string")
	}
	if len(args[1]) <= 0 {
		return shim.Error("2nd argument must be a non-empty string")
	}
	id := args[0]
	newOwnerID := args[1]
	// Get the state from the ledger
	// TODO: will be nice to have a GetAllState call to ledger
	securityString, err := stub.GetState(id)
	if err != nil {
		return shim.Error("Failed to get state")
	}
	if securityString == nil {
		return shim.Error("this security doesn't exist")
	}
	security := &TransactionResult{}
	err = json.Unmarshal(securityString, &security) //unmarshal it aka JSON.parse()
	if err != nil {
		return shim.Error(err.Error())
	}
	security.OwnerID = newOwnerID //change the owner

	newSecurityString, _ := json.Marshal(security)
	err = stub.PutState(id, newSecurityString) //rewrite the marble
	if err != nil {
		return shim.Error(err.Error())
	}
	fmt.Println("- end transferMarble (success)")
	return shim.Success(nil)
}

6.queryByOwnerID函数

// query callback representing the query of a chaincode
func (t *SimpleChaincode) queryByOwnerID(stub shim.ChaincodeStubInterface, args []string) pb.Response {

	if len(args) != 1 {
		return shim.Error("Incorrect number of arguments. Expecting id of the security to query")
	}

	ownerID := args[0]

	queryJSON := fmt.Sprintf("{\"selector\":{\"docType\":\"security\",\"ownerID\":\"%s\"}}", ownerID)
	// Get the state from the ledger
	_, results, err := t.queryByJSON(stub, queryJSON)
	if err != nil {
		jsonResp := "{\"Error\":\"Failed to get state for " + ownerID + "\"}"
		return shim.Error(jsonResp)
	}

	fmt.Printf("Query Response:%s\n", string(string(results)))
	return shim.Success(results)
}

7.queryByJSON函数

func (t *SimpleChaincode) queryByJSON(stub shim.ChaincodeStubInterface, queryString string) (int, []byte, error) {

	fmt.Printf("- getQueryResultForQueryString queryString:\n%s\n", queryString)

	resultsIterator, err := stub.GetQueryResult(queryString)
	if err != nil {
		return 0, nil, err
	}
	defer resultsIterator.Close()

	// buffer is a JSON array containing QueryRecords
	count := 0
	var buffer bytes.Buffer
	buffer.WriteString("[")

	bArrayMemberAlreadyWritten := false
	for resultsIterator.HasNext() {
		count++
		queryResponse, err := resultsIterator.Next()
		if err != nil {
			return 0, nil, err
		}
		// Add a comma before array members, suppress it for the first array member
		if bArrayMemberAlreadyWritten == true {
			buffer.WriteString(",")
		}
		buffer.WriteString("{\"Key\":")
		buffer.WriteString("\"")
		buffer.WriteString(queryResponse.Key)
		buffer.WriteString("\"")

		buffer.WriteString(", \"Record\":")
		// Record is a JSON object, so we write as-is
		buffer.WriteString(string(queryResponse.Value))
		buffer.WriteString("}")
		bArrayMemberAlreadyWritten = true
	}
	buffer.WriteString("]")

	fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", buffer.String())

	return count, buffer.Bytes(), nil
}

8.totalSupply函数

// query callback representing the query of a chaincode
func (t *SimpleChaincode) totalSupply(stub shim.ChaincodeStubInterface, args []string) pb.Response {

	queryJSON := fmt.Sprintf("{\"selector\":{\"docType\":\"security\"}}")
	// Get the state from the ledger
	count, _, err := t.queryByJSON(stub, queryJSON)
	if err != nil {
		jsonResp := "{\"Error\":\"Failed to get total supply\"}"
		return shim.Error(jsonResp)
	}

	fmt.Printf("Query Response:%s\n", string(string(count)))
	return shim.Success([]byte(strconv.Itoa(count)))

}

什么?你竟然看到了最后,那就送你一只Python打印的皮卡丘好了

print('''

   へ     /|
  /\7  ∠_/
  / │   / /
 │ Z _,< /   /`ヽ
 │     ヽ   /  〉
  Y     `  /  /
 イ	● 、 ●  ⊂⊃〈  /
 ()  へ    | \〈
  >ー 、_  ィ  │ //
  / へ   / ノ<| \\
  ヽ_ノ  (_/  │//
  7       |/
  >―r ̄ ̄`ー―_ |

    ''')

o(*≧▽≦)ツ
期待我们的下次见面哦。

你可能感兴趣的:(区块链)