hyperledger-fabricblockchainhyperledgerhyperledger-fabric-sdk-java

Endorsement from multiple organisations from hyperledger fabric JAVA SDK


I am using Hyperledger Fabric v1.4 first-network example to setup a blockchain network with two organisations and four peers. Here is a screenshot of the docker processes running with the setup.

The first network example uses an endorsement policy of AND ('Org1MSP.peer','Org2MSP.peer') and the default chaincode is chaincode_example02

To communicate with the network I am using the JAVA SDK v1.4.1. I am able to register create HF client and channels etc and also able to create users and query (read) blockchain without any issue.

Problem I am facing is when trying to update the blockchain using the 'invoke' function.

Here is the stacktrace I see in the JAVA side

2019-07-17 23:34:41,811 INFO  [http-nio-8080-exec-6] com.invincible.ngi.service.UtilityService: New channel initialized:mychannel
2019-07-17 23:34:41,812 INFO  [http-nio-8080-exec-6] com.invincible.ngi.service.UtilityService: Order added to the channel:orderer.example.com
2019-07-17 23:34:41,813 INFO  [http-nio-8080-exec-6] com.invincible.ngi.service.UtilityService: Peer added to the channel:peer0.org1.example.com
2019-07-17 23:34:43,570 INFO  [http-nio-8080-exec-6] org.hyperledger.fabric.sdk.Channel: Channel Channel{id: 6, name: mychannel} eventThread started shutdown: false  thread: null 
2019-07-17 23:34:46,696 ERROR [http-nio-8080-exec-6] com.invincible.ngi.service.QueryService: org.hyperledger.fabric.sdk.exception.TransactionEventException: Received invalid transaction event. Transaction ID 753436574ea481148f9d2da7d793f0ff1630c0c4b3106995240cf8b73aa1f1db status 10
java.util.concurrent.ExecutionException: org.hyperledger.fabric.sdk.exception.TransactionEventException: Received invalid transaction event. Transaction ID 753436574ea481148f9d2da7d793f0ff1630c0c4b3106995240cf8b73aa1f1db status 10
    at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
    at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895)
    at com.invincible.ngi.service.QueryService.updateBlockChain(QueryService.java:56)
    at com.invincible.ngi.resource.QueryResource.updateQuery(QueryResource.java:44)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:892)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1039)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.boot.actuate.web.trace.servlet.HttpTraceFilter.doFilterInternal(HttpTraceFilter.java:88)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:114)
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:104)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:853)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1587)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:748)
Caused by: org.hyperledger.fabric.sdk.exception.TransactionEventException: Received invalid transaction event. Transaction ID 753436574ea481148f9d2da7d793f0ff1630c0c4b3106995240cf8b73aa1f1db status 10
    at org.hyperledger.fabric.sdk.Channel$TL.lambda$fire$2(Channel.java:6227)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    ... 1 common frames omitted
2019-07-17 23:34:46,703 ERROR [http-nio-8080-exec-6] org.apache.juli.logging.DirectJDKLog: Servlet.service() for servlet [dispatcherServlet] in context with path [/api] threw exception [Request processing failed; nested exception is java.util.concurrent.ExecutionException: org.hyperledger.fabric.sdk.exception.TransactionEventException: Received invalid transaction event. Transaction ID 753436574ea481148f9d2da7d793f0ff1630c0c4b3106995240cf8b73aa1f1db status 10] with root cause
org.hyperledger.fabric.sdk.exception.TransactionEventException: Received invalid transaction event. Transaction ID 753436574ea481148f9d2da7d793f0ff1630c0c4b3106995240cf8b73aa1f1db status 10
    at org.hyperledger.fabric.sdk.Channel$TL.lambda$fire$2(Channel.java:6227)
    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)

And this is what appears in each of the peer nodes

On investigating I found that the issue is valid as the endorsement expects the transaction to be signed by at least one peer from each organisation. This is how I am initialising the channel.

    Channel channel = client.newChannel(ngiProperties.getChannel());
    logger.info("New channel initialized:" + ngiProperties.getChannel());
    Properties ordererProperties = new Properties();
    ordererProperties.setProperty("pemFile", ngiProperties.getOrdererServerCert());
    ordererProperties.setProperty("trustServerCertificate", ngiProperties.getOrdererTrustServerCertificate());
    ordererProperties.setProperty("hostnameOverride", ngiProperties.getOrdererHostnameOverride());
    ordererProperties.setProperty("sslProvider", ngiProperties.getOrdererSslProvider());
    ordererProperties.setProperty("negotiationType", ngiProperties.getOrdererNegotiationType());
    ordererProperties.put("grpc.NettyChannelBuilderOption.keepAliveTime", new Object[]{ngiProperties.getOrdererKeepAliveTime(), TimeUnit.MINUTES});
    ordererProperties.put("grpc.NettyChannelBuilderOption.keepAliveTimeout", new Object[]{ngiProperties.getOrdererKeepAliveTimeout(), TimeUnit.SECONDS});
    channel.addOrderer(client.newOrderer(ngiProperties.getOrdererHost(), ngiProperties.getOrdererGrpc(), ordererProperties));
    logger.info("Order added to the channel:" + ngiProperties.getOrdererHost()); // orderer.example.com
    Properties peerProperties = new Properties();
    peerProperties.setProperty("pemFile", ngiProperties.getPeerAServerCert());
    peerProperties.setProperty("trustServerCertificate", ngiProperties.getPeerATrustServerCertificate());
    peerProperties.setProperty("hostnameOverride", ngiProperties.getPeerAHostnameOverride());
    peerProperties.setProperty("sslProvider", ngiProperties.getPeerASslProvider());
    peerProperties.setProperty("negotiationType", ngiProperties.getPeerANegotiationType());
    channel.addPeer(client.newPeer(ngiProperties.getPeerAHost(), ngiProperties.getPeerAGrpc(), peerProperties));
    logger.info("Peer added to the channel:" + ngiProperties.getPeerAHost()); // peer0.org1.example.com
    channel.initialize();

Now what I realised is that if just add another peer from org2 in the channel, the issue gets resolved i.e., just by adding below piece of code before initialising the channel

    peerProperties = new Properties();
    peerProperties.setProperty("pemFile", ngiProperties.getPeerCServerCert());
    peerProperties.setProperty("trustServerCertificate", ngiProperties.getPeerCTrustServerCertificate());
    peerProperties.setProperty("hostnameOverride", ngiProperties.getPeerCHostnameOverride());
    peerProperties.setProperty("sslProvider", ngiProperties.getPeerCSslProvider());
    peerProperties.setProperty("negotiationType", ngiProperties.getPeerCNegotiationType());
    channel.addPeer(client.newPeer(ngiProperties.getPeerCHost(), ngiProperties.getPeerCGrpc(), peerProperties));
    logger.info("Peer added to the channel:" + ngiProperties.getPeerCHost()); // peer0.org2.example.com

With this here are a few questions I have

  1. If the validity of a transaction is achieved just by adding the required endorsing peers in the channel initialisation, what is the point of enforcing endorsement rule? If org1 somehow manages to get the peer details of of org2, org1 can commit transactions without having consent from org2?
  2. And what is the point of setting the user who is submitting the transaction to the HFClient? Where and how that user context and its enrolment validated by fabric in the transaction process?
  3. Ideally I would expect if I have AND ('Org1MSP.peer','Org2MSP.peer') as endorsement policy and have an UI to submit the transaction, the user context set in the HFClient should have the 'Org1MSP.peer' signature and the users in org2 with 'Org2MSP.peer' should get notified about the transation submitted. The transaction should be committed only when any user with signature 'Org2MSP.peer' signs it. And all these should happen irrespective of how many peers I have used to initialise the channel. Is my expectation valid? If so how to achieve it with fabric JAVA SDK?

Solution

  • If the validity of a transaction is achieved just by adding the required endorsing peers in the channel initialisation, what is the point of enforcing endorsement rule? If org1 somehow manages to get the peer details of of org2, org1 can commit transactions without having consent from org2?

    1. Endorsing peers are the peers where the chaincodes are installed. Now when your client sends the transaction proposal for endorsement,It first checks the endorsement policy on the fabric run-time. in your case it was AND('Org1MSP.peer','Org2MSP.peer') which means both of the endorsers should return the proposal response but you had just one peer on the channel,so it fails in the 1st step itself where it doesn't meet the endorsement policy rule. **The enforcement of endorsement policy provides a way to manage the tampering of data by and one peer and double spending.**a very detailed overview over here.

    2. As the network would be distributed I hardly thing any org can access the certs of any other Org.

    P.S transaction signing is an automated process,non of the org would be having people to sign transactions manually.

    Hope this helps.

    And what is the point of setting the user who is submitting the transaction to the HFClient? Where and how that user context and its enrolment validated by fabric in the transaction process?

    To know who is wanting to change the state of data in blockchain.for example when org1 wants to change the ownership of car then when the data is commited when would know who changed the ownership of the car.

    Ideally I would expect if I have AND ('Org1MSP.peer','Org2MSP.peer') as endorsement policy and have an UI to submit the transaction, the user context set in the HFClient should have the 'Org1MSP.peer' signature and the users in org2 with 'Org2MSP.peer' should get notified about the transation submitted. The transaction should be committed only when any user with signature 'Org2MSP.peer' signs it. And all these should happen irrespective of how many peers I have used to initialise the channel. Is my expectation valid? If so how to achieve it with fabric JAVA SDK?

    well the client just sends the transaction proposal i.e operation to be performed and metadata for it and signs it with its priavte key.The chaincode containers would be taking care of their own certs.There is no person in real who would be manually signing the transaction proposal,its an automated process executed by the endorsing peer.