名词
QC QC(QuorumCert),NoxBFT算法中,主节点收到quorum(法定人数
)个节点对同一个提案的投票消息(带节点签名)后,将其合成一个QC证书
TC TC(TimeoutCert),NoxBFT算法中,节点收到quorum个节点超时消息(带节点签名)后,将其合成一个TC证书
quorum BFT算法中达成共识所需要的投票节点个数
架构
[img E2E68D50-52B6-462C-845D-EF1746984EBD]
PBFT
[img BA857096-884C-415A-9B75-48000458E6C3] 1.客户端Client将交易发送到区块链中的任意节点;
2.Replica节点接收到交易之后转发给Primary节点,Primary自身也能直接接收交易消息;
3.Primary会将收到的交易进行打包,生成batch进行验证,剔除其中的非法交易;
4.(三阶段第一阶段)Primary将验证通过的batch构造PrePrepare消息广播给其他节点,这里只广播批量交易的哈希值;
5.(三阶段第二阶段)Replica接收来自Primary的PrePrepare消息之后构造Prepare消息发送给其他Replica节点,表明该节点接收到来自主节点的PrePrepare消息并认可主节点的batch排序;
6.(三阶段第三阶段)Replica接收到2f个节点的Prepare消息之后对batch的消息进行合法性验证,验证通过之后向其他节点广播Commit消息,表示自己同意了Primary节点的验证结果;
7.Replica节点接收到2f+1个Commit之后执行batch中的交易并同主节点的执行结果进行验证,验证通过将会写入本地账本,并通过checkpoint检查点来进行结果校验的步骤,检查点规则可配置。
视图更换
Viewchange(视图更换)是指因原Primary节点失效而Replica节点参与新Primary节点选举的过程,该机制是保证整个共识算法健壮性的关键。 [img 59DD7CA9-6ED9-44B5-B2FA-DA546F7E75B0]
1.Replica节点检测到主节点有以上异常情况或者接收来自其他f+1个节点的ViewChange消息之后会向全网广播ViewChange消息;
2.当新主节点收到N-f个ViewChange消息时,会发送NewView消息;
3.Replica节点接收到NewView消息之后进行消息的验证和对比,验证View的切换信息相同之后正式更换ViewChange并打印FinishVC消息,从而完成整个ViewChange流程。
节点增删
Replica节点检测到主节点有以上异常情况或者接收来自其他f+1个节点的ViewChange消息之后会向全网广播ViewChange消息;
当新主节点收到N-f个ViewChange消息时,会发送NewView消息;
Replica节点接收到NewView消息之后进行消息的验证和对比,验证View的切换信息相同之后正式更换ViewChange并打印FinishVC消息,从而完成整个ViewChange流程。
[img 886118FA-01D5-4B6B-996A-90FF6BF63655]
1.首先,新的节点需要得到证书颁发机构颁发的证书,然后向联盟中的所有节点发送请求;
2.各个节点确认同意后会向联盟中的其他节点进行全网广播,当一个节点得到2f+1个同意加入的回复后会与新的节点建立连接;
3.随后,当新的节点和N-f(N为区块链联盟节点总数)个节点建立连接后就可以执行主动恢复算法,同步区块链联盟成员的最新状态;
4.随后,新节点再向主节点请求加入常规共识流程。最后,主节点确认过新节点的请求后会定义在哪个块号后需要改变节点总数N来共识(确保新节点的加入不会影响原有的共识,因为新节点的加入会导致全网共识N的改变,意味着f值可能改变)。
NoxBFT
基于HotStuff算法,实现了一种可扩展、高性能的新型的共识算法—NoxBFT。NoxBFT算法在保留原有RBFT算法高效性与鲁棒性的前提下,从三个方面实现了支持以千为数量级的大规模节点组网共识。首先,NoxBFT通过星型网络拓扑结构将网络复杂度将降低至n;其次,NoxBFT通过聚合签名实现了签名的快速验证,同时还支持动态扩展。
NoxBFT算法的共识流程主要是Proposal提案阶段与Vote投票阶段的循环:
[img 8CA83451-A590-4DD1-BD68-B4B02B333CCB]
1.Transaction&Broadcast:任意节点收到交易之后,首先将其存入到本地mempool中,随后将其广播给其他所有节点,收到广播的节点也会将其存入到各自的mempool中。每个节点在接收到交易后,都会进行交易的去重判断,剔除重复交易之后才能进入到节点的mempool中;
2.Proposal:当前轮次的主节点负责进行打包,从mempool中取出若干笔符合要求的交易打包成一个batch,并附带上一轮的QC封装成一个proposal,广播给其他节点;
3.Vote:所有的节点在监听到提案消息后,都会验证proposal的合法性(safety rules),验证通过后,首先检查该proposal中的QC证书(QuorumCert)是否达到了3-chain安全性提交规则(commit rules),达到后则直接提交区块,等待区块执行完成之后将其中的交易从mempool中移除(CommitTxs)。最后,节点会将投票(vote)信息发送至下一轮的主节点。需要注意的是,每个节点的投票中都会附带上节点签名; Proposal:下一轮的主节点收到quorum个vote后,聚合成一个QC,并开始下一轮打包,并重复步骤2与步骤3,一直到出现超时的情况。
超时轮换
当主节点由于网络原因或者其他因素导致从节点无法按期收到Proposal进行投票时,NoxBFT就会触发超时机制,通过Pacemaker活性模块让全网快速地进入到下一个round继续共识。
[img 8945397F-EDA4-4F8F-86E1-990CBCDE2CC5]
1.Transaction&Broadcast&Proposal:所有共识节点接收交易并且广播交易,当前的主节点正常的进行打包并广播proposal;
2.Round Timeout:由于网络原因,导致主节点proposal并没有及时地发送到从节点,因此从节点不会对本轮次进行投票;
3.Broadcast TimeoutMsg:所有节点都无法按期收到本轮的Proposal,导致超时,全网广播TimeoutVote消息,其中会附带上本节点当前所处的轮次号以及节点的签名;
4.Proposal:下一轮的主节点在一定时间内收到 quorum个TimeoutVote消息,构造成TC(Timeout cert),并从mempool中取出若干笔合法交易打包成batch,即可将TC与batch封装成一个新的提案proposal进行广播。
RAFT
与不限制共识成员的公链不同,联盟链中所有参与节点的身份都是已知的,每个节点有很高的可信度,故在某些可信度高的业务场景下可采用不容拜占庭节点的传统共识算法,基于此,平台同时支持Raft共识算法。
Raft共识机制中,节点共分为三种角色:
趣链区块链平台(以下简称“平台”)的账本数据主要包含2个部分:
散列算法
哈希是一种散列函数,把任意长度的输入通过哈希算法,变换成固定长度的输出(哈希值),哈希值的空间通常远小于输入的空间,并且哈希函数具有不可逆性,根据哈希值无法反推输入原文的内容。
对称加密
对称加密主要应用于数据传输方面,以弥补非对称加密算法在效率上的不足。
平台在通信双方协商出一个机密共享密钥后,再基于对称加密算法保证节点间的密文传输,使得计算上破解传输内容的难度更高,从而保证平台消息传输的高安全性。
对称加密也称常规加密,私钥、或者单钥加密,一个完整的对称加密方案由五个部分组成:
非对称加密
应用主要包含数字签名、密钥协商和公钥加密。
平台采用了SM2等非对称加密对交易进行签名,生成相应的国密证书以此来保证平台的身份安全。
在网络通信过程中,使用会话密钥对传输的信息进行加密,可以防止黑客窃听机密消息进行欺诈等行为。区块链平台通过实现SM2等非对称加密算法的密钥协商协议完成会话密钥的建立和网络中用户之间的相互认证,保证通信双方可以在不安全的公共媒体上创建共享的机密协议,而不必事先交换任何私有信息。
传输层安全
除了上述提到的密钥协商与密文传输以外,节点间还通过传输层安全TLS(TransportLayer Security)来保证通信安全。TLS 能够在传输层保障信息传输的安全性,是目前较为通用的网络传输实施标准,在几乎所有的网络安全传输中都采用了该技术,比如google、淘宝、百度、微信等。
传输层安全是Hyperchain默认开启的功能,采用TLSCA签发的证书进行安全通信,即在传输网络传输过程中需要验证传输层安全协议证书的安全性,验证通过即可以进行正常网络通信,反之则无法进行网络通信。目前TLSCert也已完全支持国密。
密钥管理
平台可以采用通过国家密码管理局认证的用户智能密码钥匙完成密钥管理功能。智能密码钥匙用于保管用户私钥和生成SM2签名。
随机比特生成器
平台采用硬件密码卡完成上述非对称加密算法中需要使用的随机数的生成。密码卡用于生成非对称加密算法中所需要的安全随机数和保管平台私钥。
数据归档
提供数据归档功能,将一部分旧的线上数据移到线下转存,以解决区块链数据的存储问题。
完整的数据归档过程包括:
docker启动solo模式
docker pull hyperchaincn/solo:v2.0.0
docker run -d -p 8081:8081 hyperchaincn/solo:v2.0.0
java内容
resources中存放文件夹hvm-jar
Pom.xml中添加依赖:
<dependencies>
<dependency>
<groupId>cn.hyperchaingroupId>
<artifactId>litesdkartifactId>
<version>1.0.3version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>RELEASEversion>
dependency>
dependencies>
java代码:
package cn.timechainer.hyperchainSDK.testSDK;
import cn.hyperchain.sdk.account.Account;
import cn.hyperchain.sdk.account.Algo;
import cn.hyperchain.sdk.common.utils.Decoder;
import cn.hyperchain.sdk.common.utils.FileUtil;
import cn.hyperchain.sdk.exception.RequestException;
import cn.hyperchain.sdk.provider.DefaultHttpProvider;
import cn.hyperchain.sdk.provider.ProviderManager;
import cn.hyperchain.sdk.request.Request;
import cn.hyperchain.sdk.response.ReceiptResponse;
import cn.hyperchain.sdk.response.TxHashResponse;
import cn.hyperchain.sdk.response.block.BlockNumberResponse;
import cn.hyperchain.sdk.response.block.BlockResponse;
import cn.hyperchain.sdk.response.node.NodeResponse;
import cn.hyperchain.sdk.response.node.NodeStateResponse;
import cn.hyperchain.sdk.response.tx.*;
import cn.hyperchain.sdk.service.*;
import cn.hyperchain.sdk.service.params.FilterParam;
import cn.hyperchain.sdk.service.params.MetaDataParam;
import cn.hyperchain.sdk.transaction.Transaction;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
class TestSDK {
public static String DEFAULT_URL = "192.168.182.9:8081";
public static final int nodeID1 = 1; //传入nodeID可以有多个
public static final String txHash = "0x6892cdc15f5c05606164a476caadd693e86df01c8c6fafea20c9a480d8fb2aea"; //传入nodeID可以有多个
public static final String blockHash = "0x0fd3426a6b47a7d672c9785766eb6919d6268c2434674e4098d1e0e7dee2f315";
public static final int index = 0; //区块内的交易索引值
public static final int blockNumber = 0x10; //区块号
public static final String from_block = "0x10";
public static final String to_block = "0x60";
public static final String address = "0x08deebc2c7db8a9166067f6bf8742f964973b2d0";
public static final String txIndex = "0x60";
public static void main(String[] args) throws IOException, RequestException {
System.out.println("start HyperChain.");
// create a provider:管理与节点的连接
//Builder(readTimeout, writeTimeout, connectTimeout), setUrl.https(证书)
DefaultHttpProvider defaultHttpProvider = new DefaultHttpProvider.Builder().setUrl(DEFAULT_URL).build();
System.out.println("创建一个httpprovider");
// 每个节点需要一个HttpProvider, Manager负责管理这些HttpProvider
ProviderManager providerManager = ProviderManager.createManager(defaultHttpProvider);
System.out.println("创建一个providerManager");
//测试进行一笔交易
TestSDK.startTxService(providerManager);
//创建一个TxService原来获取交易信息
TxService txService = ServiceManager.getTxService(providerManager);
//测试txService
TestSDK.transactionService(txService);
//创建一个BlockService原来获取交易信息
BlockService blockService = ServiceManager.getBlockService(providerManager);
//测试blockService
TestSDK.blockService(blockService);
//创建一个NodeService原来获取节点信息
NodeService nodeService = ServiceManager.getNodeService(providerManager);
//测试blockService
TestSDK.nodeService(nodeService);
//创建一个MQService
MQService mqService = ServiceManager.getMQService(providerManager);
}
private static void startTxService(ProviderManager providerManager) throws RequestException, IOException {
System.out.println("开始进行交易");
// 创建用户:先创建service,再创建accout
AccountService accountService = ServiceManager.getAccountService(providerManager);
Account account = accountService.genAccount(Algo.SMRAW); //ECRAW和SMRAM不需要设置passwd
System.out.println("创建一个account");
System.out.println(account);
// 创建交易体:创建交易体时需要指定要部署的jar包(封装成流)。
InputStream payload = FileUtil.readFileAsStream("hvm-jar/contractcollection-1.0-SNAPSHOT.jar");
Transaction transaction = new Transaction.HVMBuilder(account.getAddress()).deploy(payload).build();
System.out.println("创建一个交易实体");
transaction.sign(accountService.fromAccountJson(account.toJson()));
// 创建请求
ContractService contractService = ServiceManager.getContractService(providerManager);
Request<TxHashResponse> contractRequest = contractService.deploy(transaction);
System.out.println("创建一个交易请求");
//发送交易体
ReceiptResponse receiptResponse = contractRequest.send().polling();
System.out.println("发送交易请求,并获取交易响应凭证");
System.out.println(receiptResponse);
System.out.println("未解码的交易结果");
System.out.println(receiptResponse.getRet());
//解码交易响应凭证
System.out.println("获取交易响应凭证解码结果");
System.out.println(Decoder.decodeHVM(receiptResponse.getRet(), String.class));
}
public static void transactionService(TxService txService) throws RequestException {
System.out.println("开始进行区块查询相关操作");
// System.out.println(getTxResponseByTxHash(txService));
// System.out.println(getTxResponseByBlockHash(txService));
// System.out.println(getTxResponseDiscardTransactions(txService));
// System.out.println(getTxResponseTransactionsCount(txService));
// System.out.println(getTxResponseTransactionReceipt(txService));
// System.out.println(getTxResponseBlockTxCountByHash(txService));
// System.out.println(getTxResponseTransactionCountByContractAddr(txService));
// System.out.println(getTxResponseBatchByHash(txService));
// System.out.println(getTxResponseTxCountByTime(txService));
// System.out.println(getTxResponseByFilter(txService));
System.out.println(getTxResponseTxVersioin(txService));
}
public static String getTxResponseByTxHash(TxService txService) throws RequestException {
System.out.println("创建通过交易Hash查询交易的请求:发送请求,并获取响应结果,最后输出");
//设置是否隐私交易为false,输出结果与不加该参数一致
Request<TxResponse> txResponse = txService.getTxByHash(txHash, false ,nodeID1);
return txResponse.send().toString();
}
public static String getTxResponseBatchByHash(TxService txService) throws RequestException {
System.out.println("根据区块hash获取区块交易数量");
ArrayList<String> txHashList = new ArrayList<>();
txHashList.add(txHash);
// 还可以根据需要的数量进行查询getBlockTxCountByNumber
Request<TxResponse> txResponse = txService.getBatchTxByHash(txHashList, nodeID1);
return txResponse.send().toString();
}
public static String getTxResponseByBlockHash(TxService txService) throws RequestException {
System.out.println("创建通过区块Hash查询交易的请求:发送请求,并获取响应结果,最后输出");
//设置是否隐私交易为false,输出结果与不加该参数一致
Request<TxResponse> txResponse = txService.getTxByBlockHashAndIndex(blockHash , index, nodeID1);
return txResponse.send().toString();
}
public static String getTxResponseDiscardTransactions(TxService txService) throws RequestException {
//可以按时间进行查询
System.out.println("获取所有非法交易");
Request<TxResponse> txResponse = txService.getDiscardTx(nodeID1);
return txResponse.send().toString();
}
public static String getTxResponseTransactionsCount(TxService txService) throws RequestException {
System.out.println("获取链上的所有交易量");
Request<TxCountWithTSResponse> txResponse = txService.getTransactionsCount(nodeID1);
return txResponse.send().toString();
}
public static String getTxResponseTransactionReceipt(TxService txService) throws RequestException {
System.out.println("根据Tx hash获取交易回执信息");
Request<ReceiptResponse> txResponse = txService.getTransactionReceipt(txHash, nodeID1);
return txResponse.send().toString();
}
public static String getTxResponseBlockTxCountByHash(TxService txService) throws RequestException {
System.out.println("根据区块hash获取区块交易数量");
// 还可以根据需要的数量进行查询getBlockTxCountByNumber
Request<TxCountResponse> txResponse = txService.getBlockTxCountByHash(blockHash, nodeID1);
return txResponse.send().toString();
}
public static String getTxResponseTransactionCountByContractAddr(TxService txService) throws RequestException {
System.out.println("根据合约地址查询区块间的交易数量");
Request<TxResponse> txResponse = txService.getTransactionsCountByContractAddr(from_block, to_block, address, false, nodeID1);
return txResponse.send().toString();
}
public static String getTxResponseNextPageTransactions(TxService txService) throws RequestException {
final String blkNumber = "100"; //从该区块开始计数
final String txIndex = "0"; //起始交易在blkNumber号区块的位置偏移量
final String minBlkNumber = "96"; //截止计数的最小区块号
final String maxBlkNumber = "100"; //截止计数的最大区块号
final String separated = "0"; //标示要跳过的交易条数, 用于跳页查询
final String pageSize = "10"; //要返回的交易条数
final boolean containCurrent = true; //true表示返回的结果中包括blkNumber区块中位置为txIndex的交易,如果该条交易不是合约地址为address的交易,则不算入。
System.out.println("查询下一页交易");
//查询上一页交易
Request<TxResponse> txResponse = txService.getNextPageTransactions(blkNumber, txIndex, minBlkNumber, maxBlkNumber, separated, pageSize, containCurrent, address, nodeID1);
return txResponse.send().toString();
}
public static String getTxResponseTxCountByTime(TxService txService) throws RequestException {
System.out.println("获取指定时间内的交易数量");
final BigInteger startTime = new BigInteger("1609739137600000000");
final BigInteger endTime = new BigInteger("1609739137733697845");
// 还可以根据需要的数量进行查询getBlockTxCountByNumber
Request<TxCountResponse> txResponse = txService.getTxsCountByTime(startTime, endTime, nodeID1);
return txResponse.send().toString();
}
public static String getTxResponseByFilter(TxService txService) throws RequestException {
final int mode = 1; //0表示与filter完全稳和的与查询,1表示部分稳和的或查询
final boolean detail = false; //是否显示查询细节
MetaDataParam metaData = null;
FilterParam filter = null;
System.out.println("根据filter获取满足条件的交易");
// 还可以根据需要的数量进行查询getBlockTxCountByNumber
Request<TxLimitResponse> txResponse = txService.getTxsByFilter(mode, detail, metaData, filter, nodeID1);
return txResponse.send().toString();
}
public static String getTxResponseTxVersioin(TxService txService) throws RequestException {
System.out.println("获取平台当前的交易版本号");
// 还可以根据需要的数量进行查询getBlockTxCountByNumber
Request<TxVersionResponse> txResponse = txService.getTxVersion(nodeID1);
return txResponse.send().toString();
}
private static void blockService(BlockService blockService) throws RequestException {
System.out.println("开始进行block service相关操作");
// System.out.println(getBlockResponseByBlockNumber(blockService));
// System.out.println(getBlockResponseLatestBlock(blockService));
// System.out.println(getBlockResponseChainHeight(blockService));
System.out.println(getBlockResponseGenesisBlock(blockService));
}
private static String getBlockResponseByBlockNumber(BlockService blockService) throws RequestException {
// 根据区块号进行批量查询:getBatchBlocksByNum(blockNumberList,...)
System.out.println("根据区块号,获取结果");
//还可以根据区块hash进行查询:getBlockByHash; 根据hash批量查询:getBatchBlockByHash(blockHashList,...)
//还可以根据区块的区间进行查询 :getBlocks(from, to,...)
final boolean isPlain = true; //默认为false,返回的数据包括区块内的交易
Request<BlockResponse> blockResponse = blockService.getBlockByNum(from_block, isPlain, nodeID1);
return blockResponse.send().toString();
}
private static String getBlockResponseLatestBlock(BlockService blockService) throws RequestException {
System.out.println("获取最新区块的数据");
Request<BlockResponse> blockResponse = blockService.getLatestBlock(nodeID1);
return blockResponse.send().toString();
}
private static String getBlockResponseGenesisBlock(BlockService blockService) throws RequestException {
System.out.println("查询最新的区块号,即链高");
Request<BlockNumberResponse> blockResponse = blockService.getGenesisBlock(nodeID1);
return blockResponse.send().toString();
}
private static void nodeService(NodeService nodeService) throws RequestException {
System.out.println("开始进行node service相关操作");
System.out.println(getNodeResponseNodes(nodeService));
System.out.println(getNodeResponseNodeStates(nodeService));
}
private static List<NodeResponse.Node> getNodeResponseNodes(NodeService nodeService) throws RequestException {
System.out.println("查询所有节点信息");
Request<NodeResponse> nodeResponse = nodeService.getNodes(nodeID1);
return nodeResponse.send().getResult();
}
private static List<NodeStateResponse.NodeState> getNodeResponseNodeStates(NodeService nodeService) throws RequestException {
System.out.println("查询所有节点信息");
Request<NodeStateResponse> nodeResponse = nodeService.getNodeStates(nodeID1);
return nodeResponse.send().getResult();
}
}