基础知识

信通院2019区块链白皮书: 区块链(Blockchain)是一种由多方共同维护,使用密码学保证 传输和访问安全,能够实现数据一致存储、难以篡改、防止抵赖的记 账技术,也称为分布式账本技术(Distributed Ledger Technology)。

  • 哈希算法:正向快速、你想困难、输入任意长度、输出固定长度、输入敏感、冲突避免。
  • 数字签名:非对称加密。私钥加密,公钥解密。私钥签名,公钥验签。

数字签名过程

  1. Bob有两把钥匙,一把叫公钥(public key),一把叫私钥(private key)。
  2. Bob的公钥可以公开供其他人使用,他只保留自己的私钥。公钥和私钥用来加解密数据,如果使用任意一把来加密数据,那么只有使用另外一把才能解密数据。
  3. 现在Bob决定给Pat写一份信,信件的内容不用加密,但是要保证Pat收到信件后,能够确认信件的确是Bob发出的,而不是别人冒充Bob发给Pat的。
  4. Bob将信件通过hash软件计算一下,得到一串消息摘要(有的文章也称之为“hash值”)。这一过程能够保证2点:①过程不可逆。即不能通过消息摘计算出信件的内容。②消息摘要不会重复。即如果信件有任何改动,再次hash计算出的消息摘要一定不会和改动前的消息摘要一致。
  5. 然后,Bob使用自己的私钥,将消息摘要加密。加密后的结果,我们称之为“数字签名”。现在,Bob就可以将信件连同数字签名一起发给Pat。
  6. Pat收到信件以后,会做2件事:①使用Bob的公钥将数字签名解密,如果顺利解密,说明的确是Bob签发的数字签名,不是别人签发的,因为Bob的私钥没有公开。②Pat使用hash软件对信件再次进行hash计算,和解密数字签名得到的消息摘要对比,如果一致,说明信件没有篡改,确实是Bob发出的。这就是数字签名的过程。它能够确保签名人发出的消息不被篡改,也能证明的确是签名人发出的消息。

交易

数据写入: 三要素: 发起人(id、公钥、地址)、行为(转账、合约调用、存正)、发起人签名 没有签名的交易无法在区块链p2p网络上传播、更无法被打包.

区块

区块的组成: 上一个区块的hash、交易根哈希、交易dag、读写集根hash

区块

为什么难以篡改? 参考上图的Merkle交易树。

区块链网路

区块链架构

  • 共识节点(consensus node):参与区块链网络中共识投票、交易执行、区块验证和记账的节点。

  • 同步节点 (sync node):或称见证节点,参与区块和交易同步、区块验证,交易执行,并记录完整账本数据,但不参与共识投票。

  • 轻节点 (light node):参与同步和校验区块头信息、验证交易存在性的节点。

  • SDK:帮助用户通过RPC和区块链网络进行连接,完成合约创建、调用、链管理等功能。

  • 区块链浏览器 (ChainMaker broswer):通过可视化界面为用户展示区块信息、交易信息、节点信息等区块链信息。

  • 管理平台 (management platform):通过可视化界面方便用户对链进行管理、信息浏览和资源监控等。

  • 合约IDE (contract IDE):智能合约在线开发环境,长安链所有合约支持语言均可在该IDE上开发和编译。

  • 命令行工具集 (ChainMaker CLI, cmc):使用户可以用命令行的方式对链进行部署和管理操作,例如证书生成、链配置、交易发送等。

交易

transaction

  1. 用户接入区块链rpc,发起交易
  2. 节点验证交易,广播交易
  3. 记账节点(共识节点)打包交易到新区块(此处需要共识算法多个节点共识一致)
  4. 广播区块,其他节点同步区块到本地数据库存储。

共识流程:
共识

  1. 提议候选区块。区块提议节点从交易池选取一批交易,并行调度执行得到结果,生成DAG,并将区块和DAG广播。
  2. 共识候选区块。基于链上的共识机制,对候选区块进行共识投票。
  3. 验证候选区块。在共识过程中,网络中其他节点针对提议的候选区块进行正确性验证。
  4. 执行候选区块。将完成共识投票的区块提交记录至账本,并从交易缓存中移除。

TBFT 是一种拜占庭容错的共识算法,可以在拜占庭节点数小于总数1/3的情况下,保证系统的安全运行。

TBFT

TBFT 的每轮共识可以分为5个步骤:

  1. NewRound: 共识投票的准备阶段,会初始化共识相关状态;
  2. Proposal: 提案阶段,leader节点会打包区块,并广播给follwer节点;
  3. Prevote: 预投票阶段,follower节点在收到proposal并验证proposal合法后,广播自己的prevote投票到其他节点;
  4. Precommit: 预提交阶段,节点收到 >2/3 针对proposal的prevote投票后,广播自己的precommit投票到其他节点;
  5. Commit: 提交阶段,节点收到 >2/3 针对proposal的precommit投票后,提交proposal中的区块到账本。

智能合约

一种以 “计算机语言编写”,由计算机 “自动验证和执行” 的代码化合同,是纸质合同的数字化形式 在区块链里,智能合约在所有节点的执行结果,保证相同。所以常用于对账户操作。

p2p 网络

网络模块主要负责如下功能实现:

  • 节点组网
  • 具有安全保障的节点间数据通讯
  • 节点网络身份认证
  • 消息广播及订阅(Pubsub)
  • 扩展支持节点自动发现、自动组网
  • 多链间网络消息数据隔离
  • 复杂网络环境场景解决方案的支持

长安链2.0版本的网络模块是基于libp2p的v0.6.4版本实现并改进的. P2P网络相关特性,可用下图一图汇总,包括:

  • 大规模节点组网;
  • 动态节点和连接管理;
  • 专有网络穿透连接;
  • 多链网络隔离。 p2p

网络节点自动发现、自动连接的组网方式,默认在线的每个节点都可以作为种子节点为其他节点提供网络发现服务,每个种子节点都会记录网内节点地址信息。当有新节点连接到某个种子节点时,新节点会向该种子节点查询网内其他可连接节点的地址,拿到其他节点地址后,新节点会主动尝试与这些节点建立连接;另外,种子节点在接受了新节点链接后,会通过网络发现服务将该新节点的地址通知给其他在线的种子节点,其他节点在获得该新节点地址后,也会主动尝试与该新节点建立连接。
更详细的组网方式,可以参考边缘计算的edgemesh网络原理分析的文档。

区块链的几个特点:

区块链是一个非实时响应的网络(不适合毫秒级响应的场景)
区块链是一个容量有限的网络(不适合大文件存储)
区块链是一个多方博弈的网络(一旦网络运行,升级协议、合约需要多方共识后统一操作)
区块链是一个透明网络(数据上链后会同步到全节点)
区块链是一个不可(难以)篡改的网络(信息上链不可删除、误操作不可回退)
区块链是一个信任算法的网络(密码学,智能合约)

应用场景

场景

节点搭建

部署管理台

管理平台主要由前端、后端及数据库三部分组成。通过docker的方式部署。
克隆代码之前需要先注册账号。
$ git clone -b v2.3.1 --depth=1 https://git.chainmaker.org.cn/chainmaker/management-backend.git $ docker-compose up

服务启动后,默认监听的是80端口,可以在浏览器直接打开,数据用户名密码:admin/a123456 查看服务启动情况: server

通过快速引导,生成四个节点,共识算法选择为:TBFT 配置完成后,会生成一个安装包,下载解压后,得到四个节点的配置,以下只展示一个节点的配置: config

查看链账户管理,可以看到四个节点。 node-list
每个节点有三种证书类型:

  • 组织证书:组织证书将作为准入该区块链的凭证,被添加到链配置文件中,并作为节点证书和用户证书的上级CA,为节点和用户颁发证书。
  • 节点证书:节点证书由所属组织ca颁发,作为节点的身份凭证,在区块链网络通讯中验证节点身份有效性。
  • 用户证书:用户证书由所属组织ca颁发,对上链交易进行签名,并根据签名信息验证交易的有效性。 后续对节点的操作,主要用到用户证书。

启动节点

启动四个节点: bash start.sh 查看节点运行状态: run

控制台管理节点

在区块链管理,订阅区块链,填入链id:edge(部署管理台-生成配置的时候填入),rpc端口,保存。 dingyue2 dingyue

区块链浏览器

$ git clone --depth=1 https://git.chainmaker.org.cn/chainmaker/chainmaker-explorer.git 启动:cd docker && docker-compose up -d 查看: explorer

将区块链连接到浏览器: link

访问:http://localhost:9996 home page

智能合约

智能合约的部署,可以通过管理台部署,也可以通过sdk,连接到node,调用rpc方法部署。 deploy 通过一个完整的例子,体验合约的编写、部署、调用。

编写

故事背景: 假设小明(xiaoming)最近在和他的2个兄弟(小米(xiaomi)和小刚(xiaogang))一起玩石头剪刀布的游戏, 赢的人可以继承老爷子上亿资产。 三个人现在都怕对方使诈。 在出招的最后一刻变卦赢得了比赛。 那如果这个时候我们用智能合约代码来决定胜负似乎就没有问题了。

编写合约:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;

contract Winner {
    mapping(string=>uint8) _mapActions;
    mapping(address=>bool) _AllAccounts;
    mapping(address=>uint8) _AccountsActions;
    bool start; 
    // 对应 部署管理台->用户证书 中的cmtestuser3
    address constant xiaomiAddr = 0x8ab4Ecd553ed5c841248ad49A033eAe80172E0Af; 

    // 对应 部署管理台->用户证书 中的cmtestuser2
    address constant xiaomingAddr = 0x8C46B617AeF8dDb1b81fD090Ff77f3B20B27Ca57;

    // 对应 部署管理台->用户证书 中的cmtestuser1
    address constant xiaogangAddr = 0x7970bBCfB211CD71862DA1d0601e7Ffb3Ab62Bfd;
    
    event log(address);
    // 这个是构造函数 在合约第一次创建时执行
    constructor() {
        _mapActions["scissors"] = 1; // 剪刀
        _mapActions["hammer"] = 2; // 锤子
        _mapActions["cloth"] = 3; // 布 
        
        _AllAccounts[xiaomiAddr] = true;  // xiaomi
        _AllAccounts[xiaomingAddr] = true; // xiaoming
        _AllAccounts[xiaogangAddr] = true; // xiaogang
        
        _AccountsActions[xiaomiAddr] = 0;
        _AccountsActions[xiaomingAddr] = 0;
        _AccountsActions[xiaogangAddr] = 0;
    }
    
    // 设置执行动作  要求只能是scissors hammer cloth 
    // 并且要求只能是上述要求的三个地址
    function setAction( string memory action) public  returns (bool) {
        emit log(msg.sender);
        emit log(address(msg.sender);
        if (_mapActions[action] == 0 ) {
            return false;
        }
    
        if (!_AllAccounts[address(msg.sender)]) {
            return false;
        }
        _AccountsActions[address(msg.sender)] = _mapActions[action];
        return true;
    }
    
    // 重置状态
    function reset() private {
        _AccountsActions[xiaomiAddr] = 0;
        _AccountsActions[xiaomingAddr] = 0;
        _AccountsActions[xiaogangAddr] = 0;
    }
    
    // 查看谁赢得3亿资产
    function whoIsWinner() public returns (string memory, bool) {
        if (
            _AccountsActions[xiaomiAddr] == 0 ||
            _AccountsActions[xiaomingAddr] == 0 ||
            _AccountsActions[xiaogangAddr] == 0
            ) {
                reset();
                return ("", false);
            }
        uint8  xiaomi = _AccountsActions[xiaomiAddr];
        uint8  xiaoming = _AccountsActions[xiaomingAddr];
        uint8  xiaogang = _AccountsActions[xiaogangAddr];
        if (xiaomi != xiaoming && xiaomi != xiaogang && xiaoming != xiaogang) {
            reset();
            return ("", false);
        }
        
        if (xiaomi == xiaoming) {
            if (winCheck(xiaomi, xiaogang)) {
                return ("xiaogang", true);
            }else{
                reset();
                return ("", false);
            }
        }
        if (xiaomi == xiaogang) {
            if (winCheck(xiaomi, xiaoming)) {
                return ("xiaoming", true);
            }else{
                reset();
                return ("", false);
            }
        } 
        if (xiaoming == xiaogang) {
            if (winCheck(xiaoming, xiaomi)) {
                return ("xiaomi", true);
            }else{
                reset();
                return ("", false);
            }
        }
        reset();
        return ("", false);
    }
    
    function winCheck(uint8 a, uint8 b ) private pure returns( bool) {
        if(a == 1 && b==3) {
            return true;
        }else if (a==2 && b==1) {
            return true;
        }else if (a==3 && b==2) {
            return true;
        }
        return false;
    }
    
}

合约编译:

1
2
3
liujh-h@Z370 ~ $  solc --abi --bin --hashes --overwrite -o . winner.sol
liujh-h@Z370 ~ $ ls
Winner.abi  Winner.bin  Winner.signatures winner.sol

部署合约:

部署合约选用go sdk调用rpc的方式,部署。 代码:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// 常量初始化
const (
	createContractTimeout = 5
	WinnerContractName    = "winnerrr"
	WinnerVersion         = "1.0.0"
	WinnerByteCodePath    = "/home/liujh-h/mywork/code/solidity/Winner.bin"
	WinnerABIPath         = "/home/liujh-h/mywork/code/solidity/Winner.abi"
)

// 定义使用方式:
// 第一个参数为用户,整形对应关系为: 1-cmtestuser1 2-cmtestuser2 3-cmtestuser3
// 第二个参数为行为,create-创建, winner-查询谁赢了3个亿资产, scissors/hammer/cloth 对应三个动作
func main() {
	index, _ := strconv.Atoi(os.Args[1])

	fmt.Println("====================== create client ======================")
	client, err := examples.CreateChainClientWithSDKConf(sdkConfigOrgClientPaths[index])
	if err != nil {
		log.Fatalln(err)
	}

	if len(os.Args) != 3 {
		log.Fatal("arg1: user, arg2: create/winner/scissors/hammer/cloth")
	}

	if os.Args[2] == "create" {
		fmt.Println("====================== 创建Winner合约 ======================")
		usernames := []string{examples.UserNameOrg1Admin1, examples.UserNameOrg2Admin1, examples.UserNameOrg3Admin1, examples.UserNameOrg4Admin1}
		testUserContractWinnerEVMCreate(client, true, true, usernames...)

	} else if os.Args[2] == "winner" {
		fmt.Println("====================== 获取Winner ======================")
		testUserContractWinnerEVMGetWinner(client, true)
	} else {
		fmt.Println("====================== 剪刀石头布 ======================")
		testUserContractWinnerEVMSetAction(client, os.Args[2], true)
	}
}

// 创建合约
func testUserContractWinnerEVMCreate(client *sdk.ChainClient, withSyncResult bool, isIgnoreSameContract bool, usernames ...string) {
	resp, err := createUserContract(client, WinnerContractName, WinnerVersion,
		WinnerByteCodePath, common.RuntimeType_EVM, nil, withSyncResult, usernames...)
	if !isIgnoreSameContract {
		if err != nil {
			log.Fatalln(err)
		}
	}

	fmt.Printf("CREATE EVM Winner contract resp: %+v\n", resp)
}

// 查询结果 调用whoIsWinner 方法
func testUserContractWinnerEVMGetWinner(client *sdk.ChainClient, withSyncResult bool) {
	abiJson, err := ioutil.ReadFile(WinnerABIPath)
	if err != nil {
		log.Fatalln(err)
	}

	myAbi, err := abi.JSON(strings.NewReader(string(abiJson)))
	if err != nil {
		log.Fatalln(err)
	}

	method := "whoIsWinner"
	dataByte, err := myAbi.Pack(method)
	if err != nil {
		log.Fatalln(err)
	}

	dataString := hex.EncodeToString(dataByte)

	kvs := []*common.KeyValuePair{
		{
			Key:   "data",
			Value: []byte(dataString),
		},
	}

	result, err := invokeUserContractWithResult(client, WinnerContractName, method, "", kvs, withSyncResult)
	if err != nil {
		log.Fatalln(err)
	}

	winner, err := myAbi.Unpack("whoIsWinner", result)
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Printf("get winner [%s]\n", winner)
}

// 石头剪刀布,具体动作
func testUserContractWinnerEVMSetAction(client *sdk.ChainClient, action string, withSyncResult bool) {
	abiJson, err := ioutil.ReadFile(WinnerABIPath)
	if err != nil {
		log.Fatalln(err)
	}

	myAbi, err := abi.JSON(strings.NewReader(string(abiJson)))
	if err != nil {
		log.Fatalln(err)
	}

	method := "setAction"
	dataByte, err := myAbi.Pack(method, action)
	if err != nil {
		log.Fatalln(err)
	}

	dataString := hex.EncodeToString(dataByte)

	kvs := []*common.KeyValuePair{
		{
			Key:   "data",
			Value: []byte(dataString),
		},
	}

	err = invokeUserContract(client, WinnerContractName, method, "", kvs, withSyncResult)
	if err != nil {
		log.Fatalln(err)
	}
}

合约调用

调用部署合约交易 create 查看页面执行结果: create-page 本次交易id为:17597575d8fc4f5ecabc6a678c29abaf7f4e40a7b8fe4f77870678ef3335d2cc

开始石头剪刀布行为: vote 查看页面执行结果:
vote-page

投票结束,查看结果: whoWin 查看页面执行结果:
vote-page

文档: 中国信通院2022年区块链白皮书
中国信通院2019年区块链白皮书