Skip to content

Commit e693389

Browse files
C0rWinwlahti
authored andcommitted
[FAB-5599] Blocks events client example
Since there was a new API introduced to deliver both filtered blocks with minimal information per requirements from FAB-5481 and normal blocks, which is going to replace events producer functionaly. This commit add an example of how clients could leverage new service APIs to receive an updates of new blocks committed. Change-Id: I94984eab966365272706520615ebab5514e44f38 Signed-off-by: Artem Barger <bartem@il.ibm.com> Signed-off-by: Will Lahti <wtlahti@us.ibm.com>
1 parent e821875 commit e693389

File tree

3 files changed

+388
-0
lines changed

3 files changed

+388
-0
lines changed

examples/events/block-listener/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,22 @@
1+
# Deprecation
2+
Please note that events API available in Hyperledger Fabric v1.0.0 will be deprecated in favour of new
3+
events delivery API
4+
5+
```proto
6+
service Deliver {
7+
// deliver first requires an Envelope of type ab.DELIVER_SEEK_INFO with Payload data as a marshaled orderer.SeekInfo message,
8+
// then a stream of block replies is received.
9+
rpc Deliver (stream common.Envelope) returns (stream DeliverResponse) {
10+
}
11+
// deliver first requires an Envelope of type ab.DELIVER_SEEK_INFO with Payload data as a marshaled orderer.SeekInfo message,
12+
// then a stream of **filtered** block replies is received.
13+
rpc DeliverFiltered (stream common.Envelope) returns (stream DeliverResponse) {
14+
}
15+
}
16+
```
17+
18+
Please explore `eventsclient` example for demonstration of using new APIs.
19+
120
# What is block-listener
221
block-listener.go connects to a peer in order to receive block and chaincode
322
events (if there are chaincode events being sent).
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Events client
2+
This sample client demonstrates how to connect to a peer to receive block
3+
events. Block events come in the form of either full blocks as they have been
4+
committed to the ledger or filtered blocks (a minimal set of information about
5+
the block) which includes the transaction ids, transaction statuses, and any
6+
chaincode events associated with the transaction.
7+
8+
# Events service interface
9+
Starting with v1.1, two new event services are available:
10+
11+
```proto
12+
service Deliver {
13+
// deliver first requires an Envelope of type ab.DELIVER_SEEK_INFO with Payload data as a marshaled orderer.SeekInfo message,
14+
// then a stream of block replies is received.
15+
rpc Deliver (stream common.Envelope) returns (stream DeliverResponse) {
16+
}
17+
// deliver first requires an Envelope of type ab.DELIVER_SEEK_INFO with Payload data as a marshaled orderer.SeekInfo message,
18+
// then a stream of **filtered** block replies is received.
19+
rpc DeliverFiltered (stream common.Envelope) returns (stream DeliverResponse) {
20+
}
21+
}
22+
```
23+
24+
This sample demonstrates connecting to both of these services.
25+
26+
# General use
27+
```sh
28+
cd fabric/examples/events/eventsclient
29+
go build
30+
```
31+
You will see the executable **eventsclient** if there are no compilation errors.
32+
33+
Next, to start receiving block events from a peer with TLS enabled, run the
34+
following command:
35+
36+
```sh
37+
CORE_PEER_LOCALMSPID=<msp-id> CORE_PEER_MSPCONFIGPATH=<path to MSP folder> ./eventsclient -channelID=<channel-id> -filtered=<true or false> -tls=true -clientKey=<path to the client key> -clientCert=<path to the client TLS certificate> -rootCert=<path to the server root CA certificate>
38+
```
39+
40+
If the peer is not using TLS you can run:
41+
42+
```bash
43+
CORE_PEER_LOCALMSPID=<msp-id> CORE_PEER_MSPCONFIGPATH=<path to MSP folder> ./eventsclient -channelID=<channel-id> -filtered=<true or false> -tls=false
44+
```
45+
46+
The peer will begin delivering block events and print the output to the console.
47+
48+
# Example with the e2e_cli example
49+
The events client sample can be used with TLS enabled or disabled. By default,
50+
the e2e_cli example will have TLS enabled. In order to allow the events client
51+
to connect to peers created by the e2e_cli example with TLS enabled, the easiest
52+
way would be to map `127.0.0.1` to the hostname of the peer that you are
53+
connecting to, such as `peer0.org1.example.com`. For example on \*nix based
54+
systems this would be an entry in `/etc/hosts` file.
55+
56+
If you would prefer to disable TLS, you may do so by setting
57+
CORE_PEER_TLS_ENABLED=***false*** in ``docker-compose-cli.yaml`` and
58+
``base/peer-base.yaml`` as well as
59+
ORDERER_GENERAL_TLS_ENABLED=***false*** in``base/docker-compose-base.yaml``.
60+
61+
Next, run the [e2e_cli example](https://github.com/hyperledger/fabric/tree/master/examples/e2e_cli).
62+
63+
Once the "All in one" command:
64+
```sh
65+
./network_setup.sh up
66+
```
67+
has completed, attach the event client to peer peer0.org1.example.com by doing
68+
the following:
69+
70+
* If TLS is enabled:
71+
* to receive full blocks:
72+
```sh
73+
CORE_PEER_LOCALMSPID=Org1MSP CORE_PEER_MSPCONFIGPATH=$GOPATH/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/peerOrganizations/org1.example.com/peers/peer0.Org1.example.com/msp ./eventsclient -server=peer0.org1.example.com:7051 -channelID=mychannel -filtered=false -tls=true -clientKey=$GOPATH/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/peerOrganizations/org1.example.com/users/Admin@Org1.example.com/tls/client.key -clientCert=$GOPATH/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/peerOrganizations/org1.example.com/users/Admin@Org1.example.com/tls/client.crt -rootCert=$GOPATH/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/peerOrganizations/org1.example.com/users/Admin@Org1.example.com/tls/ca.crt
74+
```
75+
76+
* to receive filtered blocks:
77+
```sh
78+
CORE_PEER_LOCALMSPID=Org1MSP CORE_PEER_MSPCONFIGPATH=$GOPATH/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/peerOrganizations/org1.example.com/peers/peer0.Org1.example.com/msp ./eventsclient -server=peer0.org1.example.com:7051 -channelID=mychannel -filtered=true -tls=true -clientKey=$GOPATH/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/peerOrganizations/org1.example.com/users/Admin@Org1.example.com/tls/client.key -clientCert=$GOPATH/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/peerOrganizations/org1.example.com/users/Admin@Org1.example.com/tls/client.crt -rootCert=$GOPATH/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/peerOrganizations/org1.example.com/users/Admin@Org1.example.com/tls/ca.crt
79+
```
80+
81+
* If TLS is disabled:
82+
* to receive full blocks:
83+
```sh
84+
CORE_PEER_LOCALMSPID=Org1MSP CORE_PEER_MSPCONFIGPATH=$GOPATH/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/peerOrganizations/org1.example.com/peers/peer0.Org1.example.com/msp ./eventsclient -server=peer0.org1.example.com:7051 -channelID=mychannel -filtered=false -tls=false
85+
```
86+
87+
* to receive filtered blocks:
88+
```sh
89+
CORE_PEER_LOCALMSPID=Org1MSP CORE_PEER_MSPCONFIGPATH=$GOPATH/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/peerOrganizations/org1.example.com/peers/peer0.Org1.example.com/msp ./eventsclient -server=peer0.org1.example.com:7051 -channelID=mychannel -filtered=true -tls=false
90+
```
91+
92+
<a rel="license" href="http://creativecommons.org/licenses/by/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by/4.0/88x31.png" /></a><br />This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International License</a>.
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package main
8+
9+
import (
10+
"context"
11+
"flag"
12+
"fmt"
13+
"io/ioutil"
14+
"math"
15+
"os"
16+
"strings"
17+
"time"
18+
19+
"github.com/hyperledger/fabric/common/crypto"
20+
"github.com/hyperledger/fabric/common/flogging"
21+
"github.com/hyperledger/fabric/common/localmsp"
22+
genesisconfig "github.com/hyperledger/fabric/common/tools/configtxgen/localconfig"
23+
"github.com/hyperledger/fabric/common/tools/protolator"
24+
"github.com/hyperledger/fabric/common/util"
25+
"github.com/hyperledger/fabric/core/comm"
26+
config2 "github.com/hyperledger/fabric/core/config"
27+
"github.com/hyperledger/fabric/msp"
28+
common2 "github.com/hyperledger/fabric/peer/common"
29+
"github.com/hyperledger/fabric/protos/common"
30+
"github.com/hyperledger/fabric/protos/orderer"
31+
"github.com/hyperledger/fabric/protos/peer"
32+
"github.com/hyperledger/fabric/protos/utils"
33+
"github.com/spf13/viper"
34+
)
35+
36+
var (
37+
channelID string
38+
serverAddr string
39+
clientKeyPath string
40+
clientCertPath string
41+
serverRootCAPath string
42+
seek int
43+
quiet bool
44+
filtered bool
45+
tlsEnabled bool
46+
mTlsEnabled bool
47+
48+
oldest = &orderer.SeekPosition{Type: &orderer.SeekPosition_Oldest{Oldest: &orderer.SeekOldest{}}}
49+
newest = &orderer.SeekPosition{Type: &orderer.SeekPosition_Newest{Newest: &orderer.SeekNewest{}}}
50+
maxStop = &orderer.SeekPosition{Type: &orderer.SeekPosition_Specified{Specified: &orderer.SeekSpecified{Number: math.MaxUint64}}}
51+
)
52+
53+
const (
54+
OLDEST = -2
55+
NEWEST = -1
56+
57+
ROOT = "core"
58+
)
59+
60+
var logger = flogging.MustGetLogger("eventsclient")
61+
62+
// deliverClient abstracts common interface
63+
// for deliver and deliverfiltered services
64+
type deliverClient interface {
65+
Send(*common.Envelope) error
66+
Recv() (*peer.DeliverResponse, error)
67+
}
68+
69+
// eventsClient client to get connected to the
70+
// events peer delivery system
71+
type eventsClient struct {
72+
client deliverClient
73+
signer crypto.LocalSigner
74+
tlsCertHash []byte
75+
}
76+
77+
func (r *eventsClient) seekOldest() error {
78+
return r.client.Send(r.seekHelper(oldest, maxStop))
79+
}
80+
81+
func (r *eventsClient) seekNewest() error {
82+
return r.client.Send(r.seekHelper(newest, maxStop))
83+
}
84+
85+
func (r *eventsClient) seekSingle(blockNumber uint64) error {
86+
specific := &orderer.SeekPosition{Type: &orderer.SeekPosition_Specified{Specified: &orderer.SeekSpecified{Number: blockNumber}}}
87+
return r.client.Send(r.seekHelper(specific, specific))
88+
}
89+
90+
func (r *eventsClient) seekHelper(start *orderer.SeekPosition, stop *orderer.SeekPosition) *common.Envelope {
91+
env, err := utils.CreateSignedEnvelopeWithTLSBinding(common.HeaderType_DELIVER_SEEK_INFO, channelID, r.signer, &orderer.SeekInfo{
92+
Start: start,
93+
Stop: stop,
94+
Behavior: orderer.SeekInfo_BLOCK_UNTIL_READY,
95+
}, 0, 0, r.tlsCertHash)
96+
if err != nil {
97+
panic(err)
98+
}
99+
return env
100+
}
101+
102+
func (r *eventsClient) readEventsStream() {
103+
for {
104+
msg, err := r.client.Recv()
105+
if err != nil {
106+
logger.Info("Error receiving:", err)
107+
return
108+
}
109+
110+
switch t := msg.Type.(type) {
111+
case *peer.DeliverResponse_Status:
112+
logger.Info("Got status ", t)
113+
return
114+
case *peer.DeliverResponse_Block:
115+
if !quiet {
116+
logger.Info("Received block: ")
117+
err := protolator.DeepMarshalJSON(os.Stdout, t.Block)
118+
if err != nil {
119+
fmt.Printf(" Error pretty printing block: %s", err)
120+
}
121+
} else {
122+
logger.Info("Received block: ", t.Block.Header.Number)
123+
}
124+
case *peer.DeliverResponse_FilteredBlock:
125+
if !quiet {
126+
logger.Info("Received filtered block: ")
127+
err := protolator.DeepMarshalJSON(os.Stdout, t.FilteredBlock)
128+
if err != nil {
129+
fmt.Printf(" Error pretty printing filtered block: %s", err)
130+
}
131+
} else {
132+
logger.Info("Received filtered block: ", t.FilteredBlock.Number)
133+
}
134+
}
135+
}
136+
}
137+
138+
func (r *eventsClient) seek(s int) error {
139+
var err error
140+
switch seek {
141+
case OLDEST:
142+
err = r.seekOldest()
143+
case NEWEST:
144+
err = r.seekNewest()
145+
default:
146+
err = r.seekSingle(uint64(seek))
147+
}
148+
return err
149+
}
150+
151+
func main() {
152+
initConfig()
153+
initMSP()
154+
readCLInputs()
155+
156+
if seek < OLDEST {
157+
logger.Info("Invalid seek value")
158+
flag.PrintDefaults()
159+
return
160+
}
161+
162+
clientConfig := comm.ClientConfig{
163+
KaOpts: comm.DefaultKeepaliveOptions(),
164+
SecOpts: &comm.SecureOptions{},
165+
Timeout: 5 * time.Minute,
166+
}
167+
168+
if tlsEnabled {
169+
clientConfig.SecOpts.UseTLS = true
170+
rootCert, err := ioutil.ReadFile(serverRootCAPath)
171+
if err != nil {
172+
logger.Info("error loading TLS root certificate", err)
173+
return
174+
}
175+
clientConfig.SecOpts.ServerRootCAs = [][]byte{rootCert}
176+
if mTlsEnabled {
177+
clientConfig.SecOpts.RequireClientCert = true
178+
clientKey, err := ioutil.ReadFile(clientKeyPath)
179+
if err != nil {
180+
logger.Info("error loading client TLS key from", clientKeyPath)
181+
return
182+
}
183+
clientConfig.SecOpts.Key = clientKey
184+
185+
clientCert, err := ioutil.ReadFile(clientCertPath)
186+
if err != nil {
187+
logger.Info("error loading client TLS certificate from path", clientCertPath)
188+
return
189+
}
190+
clientConfig.SecOpts.Certificate = clientCert
191+
}
192+
}
193+
194+
grpcClient, err := comm.NewGRPCClient(clientConfig)
195+
if err != nil {
196+
logger.Info("Error creating grpc client:", err)
197+
return
198+
}
199+
conn, err := grpcClient.NewConnection(serverAddr, "")
200+
if err != nil {
201+
logger.Info("Error connecting:", err)
202+
return
203+
}
204+
205+
var client deliverClient
206+
if filtered {
207+
client, err = peer.NewDeliverClient(conn).DeliverFiltered(context.Background())
208+
} else {
209+
client, err = peer.NewDeliverClient(conn).Deliver(context.Background())
210+
}
211+
212+
if err != nil {
213+
logger.Info("Error connecting:", err)
214+
return
215+
}
216+
217+
events := &eventsClient{
218+
client: client,
219+
signer: localmsp.NewSigner(),
220+
}
221+
222+
if mTlsEnabled {
223+
events.tlsCertHash = util.ComputeSHA256(grpcClient.Certificate().Certificate[0])
224+
}
225+
226+
events.seek(seek)
227+
if err != nil {
228+
logger.Info("Received error:", err)
229+
return
230+
}
231+
232+
events.readEventsStream()
233+
}
234+
235+
func readCLInputs() {
236+
flag.StringVar(&serverAddr, "server", "localhost:7051", "The RPC server to connect to.")
237+
flag.StringVar(&channelID, "channelID", genesisconfig.TestChainID, "The channel ID to deliver from.")
238+
flag.BoolVar(&quiet, "quiet", false, "Only print the block number, will not attempt to print its block contents.")
239+
flag.BoolVar(&filtered, "filtered", true, "Whenever to read filtered events from the peer delivery service or get regular blocks.")
240+
flag.BoolVar(&tlsEnabled, "tls", false, "TLS enabled/disabled")
241+
flag.BoolVar(&mTlsEnabled, "mTls", false, "Mutual TLS enabled/disabled (whenever server side validates clients TLS certificate)")
242+
flag.StringVar(&clientKeyPath, "clientKey", "", "Specify path to the client TLS key")
243+
flag.StringVar(&clientCertPath, "clientCert", "", "Specify path to the client TLS certificate")
244+
flag.StringVar(&serverRootCAPath, "rootCert", "", "Specify path to the server root CA certificate")
245+
flag.IntVar(&seek, "seek", OLDEST, "Specify the range of requested blocks."+
246+
"Acceptable values:"+
247+
"-2 (or -1) to start from oldest (or newest) and keep at it indefinitely."+
248+
"N >= 0 to fetch block N only.")
249+
flag.Parse()
250+
}
251+
252+
func initMSP() {
253+
// Init the MSP
254+
var mspMgrConfigDir = config2.GetPath("peer.mspConfigPath")
255+
var mspID = viper.GetString("peer.localMspId")
256+
var mspType = viper.GetString("peer.localMspType")
257+
if mspType == "" {
258+
mspType = msp.ProviderTypeToString(msp.FABRIC)
259+
}
260+
err := common2.InitCrypto(mspMgrConfigDir, mspID, mspType)
261+
if err != nil { // Handle errors reading the config file
262+
panic(fmt.Sprintf("Cannot run client because %s", err.Error()))
263+
}
264+
}
265+
266+
func initConfig() {
267+
// For environment variables.
268+
viper.SetEnvPrefix(ROOT)
269+
viper.AutomaticEnv()
270+
replacer := strings.NewReplacer(".", "_")
271+
viper.SetEnvKeyReplacer(replacer)
272+
273+
err := common2.InitConfig(ROOT)
274+
if err != nil { // Handle errors reading the config file
275+
panic(fmt.Errorf("fatal error when initializing %s config : %s", ROOT, err))
276+
}
277+
}

0 commit comments

Comments
 (0)