From 8d3ac0d5717a742b9b0724ce8e0a8efdb694f738 Mon Sep 17 00:00:00 2001 From: Jakub Dzikowski Date: Fri, 29 May 2026 15:38:33 +0200 Subject: [PATCH] Handle peer KEEPALIVE messages in chaincode message handler. The Node.js shim exited when the peer sent KEEPALIVE (type 18) on idle connections, which breaks long-running chaincode such as CCAAS. Echo KEEPALIVE back to the peer and align behaviour with the Go chaincode shim. Signed-off-by: Jakub Dzikowski --- libraries/fabric-shim/lib/handler.js | 7 +++++ libraries/fabric-shim/test/unit/handler.js | 35 ++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/libraries/fabric-shim/lib/handler.js b/libraries/fabric-shim/lib/handler.js index eac09843..3c882b25 100644 --- a/libraries/fabric-shim/lib/handler.js +++ b/libraries/fabric-shim/lib/handler.js @@ -35,6 +35,7 @@ const MSG_TYPE = { INIT: peer.ChaincodeMessage.Type.INIT, TRANSACTION: peer.ChaincodeMessage.Type.TRANSACTION, COMPLETED: peer.ChaincodeMessage.Type.COMPLETED, + KEEPALIVE: peer.ChaincodeMessage.Type.KEEPALIVE, }; /* @@ -295,6 +296,12 @@ class ChaincodeMessageHandler { stream.on('data', (msgpb) => { const msg = mapFromChaincodeMessage(msgpb); logger.debug(util.format('Received chat message from peer: %s, state: %s, type: %s', msg.txid, state, msg.type)); + + if (msg.type === MSG_TYPE.KEEPALIVE) { + stream.write(msgpb); + return; + } + if (state === STATES.Ready) { const type = msg.type; diff --git a/libraries/fabric-shim/test/unit/handler.js b/libraries/fabric-shim/test/unit/handler.js index a8c0526c..80780c0e 100644 --- a/libraries/fabric-shim/test/unit/handler.js +++ b/libraries/fabric-shim/test/unit/handler.js @@ -783,6 +783,41 @@ describe('Handler', () => { expect(handleTransactionSpy.firstCall.args).to.deep.equal([mapFromChaincodeMessage(readyMsg)]); }); + it ('should echo KEEPALIVE when in state ready and MSG_TYPE equals KEEPALIVE', () => { + const processStub = sinon.stub(process, 'exit'); + + eventReg.data(registeredMsg); + eventReg.data(establishedMsg); + + const keepaliveMsg = mapToChaincodeMessage({ + type: MSG_TYPE.KEEPALIVE + }); + + eventReg.data(keepaliveMsg); + + expect(mockStream.write.calledTwice).to.be.true; + expect(mockStream.write.secondCall.args).to.deep.equal([keepaliveMsg]); + expect(mockNewErrorMsg.notCalled).to.be.true; + expect(handleMsgResponseSpy.notCalled).to.be.true; + expect(handleInitSpy.notCalled).to.be.true; + expect(handleTransactionSpy.notCalled).to.be.true; + expect(processStub.notCalled).to.be.true; + + processStub.restore(); + }); + + it ('should echo KEEPALIVE when in state created and MSG_TYPE equals KEEPALIVE', () => { + const keepaliveMsg = mapToChaincodeMessage({ + type: MSG_TYPE.KEEPALIVE + }); + + eventReg.data(keepaliveMsg); + + expect(mockStream.write.calledTwice).to.be.true; + expect(mockStream.write.secondCall.args).to.deep.equal([keepaliveMsg]); + expect(mockNewErrorMsg.notCalled).to.be.true; + }); + it ('should end the process with value 1', () => { const processStub = sinon.stub(process, 'exit');