最近写了一个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端
- 程序调用Channel对象的queryBlockByNumber()方法;
- Channel对象创建QuerySCCRequest请求,设置Fcb为"GetBlockByNumber",设置Args为通道的名字和区块号;
- 调用sendProposal方法将请求发给peer(通过Endorser对象的ProcessProposal方法进行grpc调用)。
Peer端
- core/endoser/endorser.go文件中的ProcessProposal方法处理请求;
- 对proposal进行一系列预处理;
- 调用simulateProposal方法模拟执行交易;;
- 调用callChaincode方法将proposal交给智能合约执行;
- 调用core/chaincode/chaincodeexec.go中的ExecuteChaincode方法执行交易;
- core/chaincode/exectransaction.go中的Execute方法执行交易,如果chaincode没有运行先调用launch方法启动chaincode;
- 然后调用theChaincodeSupport.Execute方法执行交易;
- 调用handler.sendExecuteMessage方法把proposal发给chaincode实例进行执行,由于是QueryScc请求,所以发给qscc合约进行执行。
Peer端的qscc系统链码
- core/scc/qscc/qscc.go根据fcn的名字调用GetBlockByNumber方法进行执行;
- 调用core/ledger包中的GetBlockByNumber接口从账本中获取区块;
- 调用core/ledger/kvledger包中的kvLedger对象的GetBlockByNumber方法,从kv账本中获取区块;
- 调用common/ledger/blkstorage包中BlockStore对象的RetrieveBlockByNumber方法从区块存储中获取区块;
- 调用common/ledger/blkstorage/fsblkstorage包中的fsBlockStore对象的RetrieveBlockByNumber方法;
- 调用common/ledger/blkstorage/fsblkstorage包中的blockfileMgr对象的retrieveBlockByNumber方法;
- 调用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再进行查询,然后上面的问题就再没有出现。