Hyperledger fabric查询区块错误问题解决:“error Entry not found in index”

最近写了一个Hyperledger Fabric区块监控的程序,功能是应用程序监听区块生成事件,并查询新生成区块的信息。然而,当客户端收到Peer发来的blockEvent事件后,调用Channel对象的queryBlockByNumber()方法时,出现了“error Entry not found in index”错误。

一、错误描述

在调用queryBlockByNumber()方法时,向peer发送一个proposal,调用fabric的系统链码qscc,从下面的错误描述看出,由于链码执行出现错误,因此报了代码为500的错。

[2019/04/01 10:21:35.429] [pool-15-thread-1] [Sending proposal to peer1.crossborder.unionpayintl.com failed because of: gRPC failure=Status{code=UNKNOWN, description=chaincode error (status: 500, message: Failed to get block number 36566, error Entry not found in index), cause=null}]
java.lang.Exception: io.grpc.StatusRuntimeException: UNKNOWN: chaincode error (status: 500, message: Failed to get block number 36566, error Entry not found in index)
	at org.hyperledger.fabric.sdk.Channel.sendProposalToPeers(Channel.java:2241)
	at org.hyperledger.fabric.sdk.Channel.sendProposal(Channel.java:2155)
	at org.hyperledger.fabric.sdk.Channel.queryBlockByNumber(Channel.java:1680)
	at org.hyperledger.fabric.sdk.Channel.queryBlockByNumber(Channel.java:1586)
	at com.cup.blocklistener.hyperfabric.FabricServiceImpl.lambda$reconstructChannel$0(FabricServiceImpl.java:65)
	at org.hyperledger.fabric.sdk.Channel.lambda$null$0(Channel.java:2612)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
Caused by: io.grpc.StatusRuntimeException: UNKNOWN: chaincode error (status: 500, message: Failed to get block number 36566, error Entry not found in index)
	at io.grpc.Status.asRuntimeException(Status.java:526)
	at io.grpc.stub.ClientCalls$UnaryStreamToFuture.onClose(ClientCalls.java:427)
	at io.grpc.internal.ClientCallImpl.closeObserver(ClientCallImpl.java:419)
	at io.grpc.internal.ClientCallImpl.access$100(ClientCallImpl.java:60)
	at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl.close(ClientCallImpl.java:493)
	at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl.access$500(ClientCallImpl.java:422)
	at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInContext(ClientCallImpl.java:525)
	at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37)
	at io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:102)
	... 3 more

相对应的,peer端也有相应的错误日志打印,如下所示,即peer模拟执行交易失败

simulateProposal() resulted in chaincode response status 500 for txid: ...

二、查询流程分析

查询的流程涉及到SDK端、Peer端以及peer上运行的系统链码,下面进行分析:

SDK端

  1. 程序调用Channel对象的queryBlockByNumber()方法;
  2. Channel对象创建QuerySCCRequest请求,设置Fcb为"GetBlockByNumber",设置Args为通道的名字和区块号;
  3. 调用sendProposal方法将请求发给peer(通过Endorser对象的ProcessProposal方法进行grpc调用)。

Peer端

  1. core/endoser/endorser.go文件中的ProcessProposal方法处理请求;
  2. 对proposal进行一系列预处理;
  3. 调用simulateProposal方法模拟执行交易;;
  4. 调用callChaincode方法将proposal交给智能合约执行;
  5. 调用core/chaincode/chaincodeexec.go中的ExecuteChaincode方法执行交易;
  6. core/chaincode/exectransaction.go中的Execute方法执行交易,如果chaincode没有运行先调用launch方法启动chaincode;
  7. 然后调用theChaincodeSupport.Execute方法执行交易;
  8. 调用handler.sendExecuteMessage方法把proposal发给chaincode实例进行执行,由于是QueryScc请求,所以发给qscc合约进行执行。

Peer端的qscc系统链码

  1. core/scc/qscc/qscc.go根据fcn的名字调用GetBlockByNumber方法进行执行;
  2. 调用core/ledger包中的GetBlockByNumber接口从账本中获取区块;
  3. 调用core/ledger/kvledger包中的kvLedger对象的GetBlockByNumber方法,从kv账本中获取区块;
  4. 调用common/ledger/blkstorage包中BlockStore对象的RetrieveBlockByNumber方法从区块存储中获取区块;
  5. 调用common/ledger/blkstorage/fsblkstorage包中的fsBlockStore对象的RetrieveBlockByNumber方法;
  6. 调用common/ledger/blkstorage/fsblkstorage包中的blockfileMgr对象的retrieveBlockByNumber方法;
  7. 调用common/ledger/blkstorage/fsblkstorage包中的index接口中的getBlockLocByBlockNum方法从leveldb的index中获取区块位置。由于从index中获取区块不存在,所以返回"Entry not found in index"错误。
// common/ledger/blockstorage/fsblkstorage/blockindex.go文件
func (index *blockIndex) getBlockLocByBlockNum(blockNum uint64) (*fileLocPointer, error) {
	if _, ok := index.indexItemsMap[blkstorage.IndexableAttrBlockNum]; !ok {
		return nil, blkstorage.ErrAttrNotIndexed
	}
	b, err := index.db.Get(constructBlockNumKey(blockNum))
	if err != nil {
		return nil, err
	}
	// 在此处返回"Entry not found in index"错误
	if b == nil {
		return nil, blkstorage.ErrNotFoundInIndex
	}
	blkLoc := &fileLocPointer{}
	blkLoc.unmarshal(b)
	return blkLoc, nil
}

三、原因分析及解决方法

从上面的原因分析可以看出,抛出"Entry not found in index"错误的原因是在leveldb中无法找到对应的区块。而通过应用程序和peer端报的日志的时间可以看出,在区块写入leveldb完成之前,peer就会把blockEvent发给应用程序,而应用程序在进行查询发生在区块写入leveldb之前,因此,在leveldb的index中无法找到对应区块。猜测是由于运行时间过长,底层存储的数据量较大,影响了区块写入的速度。

分析了原因之后,解决方法就很显然了,要么是进行重试,要么就延时查询。由于对监控区块的实时性要求并不高,因此,我采用了Thread.sleep(2000)使线程在收到blockEvent后休眠2s再进行查询,然后上面的问题就再没有出现。


版权声明:本文为xuyuzhuang1991原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。