Skip to content

Commit 89c6c5c

Browse files
add endorser sample application (#17)
A minimal example of a custom endorser service built with the Fabric-X SDK. Unlike classic chaincode, an endorser runs as a standalone gRPC service outside the peer. The sample shows how to implement the single `Executor` interface, wire it into the endorser server, and submit transactions through an included client CLI against a local test network. Signed-off-by: Arne Rutjes <arne123@gmail.com> Co-authored-by: Marcus Brandenburger <mbrandenburger@users.noreply.github.com>
1 parent 3c12356 commit 89c6c5c

21 files changed

Lines changed: 2380 additions & 4 deletions
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#
2+
# Copyright IBM Corp. All Rights Reserved.
3+
#
4+
# SPDX-License-Identifier: Apache-2.0
5+
#
6+
7+
name: Custom Endorser Tests
8+
9+
on:
10+
push:
11+
branches: ["main"]
12+
paths: ["custom-endorser/**"]
13+
pull_request:
14+
branches: ["main"]
15+
paths: ["custom-endorser/**"]
16+
workflow_dispatch:
17+
18+
jobs:
19+
test:
20+
name: Unit Tests
21+
runs-on: ubuntu-latest
22+
steps:
23+
- name: Checkout code
24+
uses: actions/checkout@v6
25+
26+
- name: Setup Go
27+
uses: actions/setup-go@v6
28+
with:
29+
go-version-file: custom-endorser/go.mod
30+
cache-dependency-path: custom-endorser/go.sum
31+
32+
- name: Run unit tests
33+
working-directory: custom-endorser
34+
run: make test
Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@
44
# SPDX-License-Identifier: Apache-2.0
55
#
66

7-
name: Sample Tests
7+
name: Token Sample Tests
88

99
on:
1010
push:
11-
branches: ["main"]
11+
branches: [ "main" ]
12+
paths: [ "tokens/**" ]
1213
pull_request:
13-
branches: ["main"]
14+
branches: [ "main" ]
15+
paths: [ "tokens/**" ]
1416
workflow_dispatch:
1517

18+
1619
jobs:
1720
test:
1821
name: Token Samples Tests

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,14 @@ A complete token management application demonstrating:
2626
- Docker Compose and Ansible deployment configurations
2727
- Support for both Fabric 3.x and Fabric-X environments
2828

29+
### [SDK Endorser](https://github.com/hyperledger/fabric-x-samples/tree/main/endorser)
30+
31+
A minimal example of a custom endorser service built with the Fabric-X SDK. Unlike classic
32+
chaincode, an endorser runs as a standalone gRPC service outside the peer. The sample shows how to
33+
implement the single `Executor` interface, wire it into the endorser server, and submit transactions
34+
through an included client CLI against a local test network.
2935

3036
## Coming soon
3137

3238
- [ ] [EVM Integration](https://github.com/hyperledger/fabric-x-evm) example
3339
- [ ] Base CRUD application with FSC [#1](https://github.com/hyperledger/fabric-x-samples/issues/1)
34-
- [ ] Base CRUD application with [client-sdk](https://github.com/hyperledger/fabric-x-sdk/).

custom-endorser/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
bin/
2+
testdata/crypto/

custom-endorser/Makefile

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
TOOLS_IMAGE ?= ghcr.io/hyperledger/fabric-x-tools:0.0.15
2+
TOOLS = docker run --rm -v "$(PWD):/workspace" -w /workspace $(TOOLS_IMAGE)
3+
4+
.PHONY: init-network
5+
init-network:
6+
rm -rf testdata/crypto
7+
@docker pull $(TOOLS_IMAGE)
8+
@$(TOOLS) cryptogen generate --config testdata/crypto-config.yaml --output testdata/crypto
9+
@$(TOOLS) configtxgen -configPath testdata --channelID mychannel --profile OrgsChannel --outputBlock testdata/crypto/sc-genesis-block.proto.bin
10+
11+
.PHONY: clean
12+
clean: stop
13+
@rm -rf bin testdata/crypto
14+
15+
.PHONY: start-network
16+
start-network:
17+
@docker compose up -d --wait test-committer
18+
@docker compose up fxconfig-init
19+
20+
.PHONY: stop-network
21+
stop-network:
22+
@docker compose down
23+
24+
.PHONY: test
25+
test:
26+
@go test -short ./...
27+
28+
.PHONY: build
29+
build:
30+
@go build -o bin/endorser ./cmd/endorser
31+
@go build -o bin/client ./cmd/client

custom-endorser/README.md

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# Custom Endorser Example
2+
3+
This example shows how to build a custom endorser application using the
4+
[fabric-x-sdk](https://github.com/hyperledger/fabric-x-sdk/). An endorser is like a peer executing chaincode in
5+
classic Fabric: it receives transaction proposals, reads and writes world state through a
6+
`SimulationStore`, and returns signed read/write sets. Unlike chaincode, it runs as a standalone
7+
gRPC service — outside the peer, in your own process.
8+
9+
You will build and run two endorser instances, submit transactions through the included client CLI,
10+
and see how to wire in your own `Executor` — the single interface you implement to add application
11+
logic.
12+
13+
## Prerequisites
14+
15+
- Go 1.26+
16+
- Docker (for the test network committer and orderer)
17+
18+
## Quick Start
19+
20+
### 1. Build
21+
22+
```shell
23+
make build # compiles bin/endorser and bin/client
24+
```
25+
26+
### 2. Generate crypto material and start the test network
27+
28+
```shell
29+
make init-network # generate TLS certs and MSP material (only once)
30+
make start-network # start committer + orderer in Docker
31+
```
32+
33+
### 3. Start the endorsers
34+
35+
Run each in a **separate terminal** — logs stream to stdout so you can see what's happening:
36+
37+
**Terminal 1**
38+
```shell
39+
./bin/endorser -c sampleconfig/endorser-org1.yaml
40+
```
41+
42+
**Terminal 2**
43+
```shell
44+
./bin/endorser -c sampleconfig/endorser-org2.yaml
45+
```
46+
47+
Each endorser logs `starting endorser` and then listens for proposals (endorser1 on `:9001`,
48+
endorser2 on `:9002`).
49+
50+
> [!NOTE]
51+
> Our "network" consists of two organizations, as defined in [testdata/crypto-config.yaml](./testdata/crypto-config.yaml).
52+
> The fact that we need two endorsers is defined by the endorsement policy in
53+
> fxconfig-init container: `--policy=AND('Org1MSP.member', 'Org2MSP.member')`. It means both
54+
> organizations have to sign the read/write set for it to be accepted on the ledger.
55+
56+
### 4. Send a transaction
57+
58+
```shell
59+
# write a value
60+
./bin/client -c sampleconfig/client.yaml invoke '{"Args":["set", "greeting", "hello world"]}'
61+
62+
# read it back
63+
./bin/client -c sampleconfig/client.yaml query '{"Args":["get", "greeting"]}'
64+
```
65+
66+
`invoke` collects endorsements from both endorsers and submits to the orderer.
67+
`query` collects endorsements and prints the response payload — it does not submit.
68+
69+
The transaction argument follows the Fabric peer CLI convention: a JSON `Args` array with the
70+
function name as the first element, or a `{"function": "...", "Args": [...]}` object.
71+
72+
### 5. Tear down
73+
74+
Stop the running endorsers by pressing CTRL+C. Then stop the test container.
75+
76+
```shell
77+
make stop-network # stop the Docker test network
78+
```
79+
80+
## Writing Your Own Executor
81+
82+
The `Executor` interface is the only thing you need to implement to add your own logic:
83+
84+
```go
85+
type Executor interface {
86+
Execute(ctx context.Context, newStore StoreFactory, inv endorsement.Invocation) (endorsement.ExecutionResult, error)
87+
}
88+
```
89+
90+
The included [`SampleExecutor`](./cmd/endorser/executor.go) is a simple key/value getter and setter
91+
— about 40 lines. It reads from and writes to the `SimulationStore`, which captures the read/write
92+
set that the endorser will sign.
93+
94+
To plug in your own executor, edit [`cmd/endorser/main.go`](./cmd/endorser/main.go) and add it to
95+
the `executors` map:
96+
97+
```go
98+
executors := map[string]service.Executor{
99+
"my-namespace": MyExecutor{},
100+
}
101+
```
102+
103+
Each key is a namespace. The namespace must be registered in the network configuration via
104+
`fxconfig` (see `testdata/fxconfig-docker.yaml` for the namespace used by this sample).
105+
106+
If the `SimulationStore` does not give you enough control, you can call `store.Result()` early and
107+
modify the resulting read/write set directly before returning `endorsement.Success(...)`.
108+
109+
## Project Structure
110+
111+
```
112+
├── cmd/
113+
│ ├── endorser/ # Endorser service entry point + SampleExecutor
114+
│ └── client/ # Developer client CLI
115+
├── config/ # Configuration structures
116+
├── sampleconfig/ # Sample config files (endorser1/2, client)
117+
├── service/ # Service implementation and integration tests
118+
├── testdata/ # Network config, crypto-config, and generated crypto material
119+
├── compose.yml # Docker Compose for committer + orderer
120+
├── Makefile
121+
├── go.mod
122+
└── README.md
123+
```
124+
125+
## Configuration
126+
127+
### Files
128+
129+
See the `sampleconfig/` folder for endorser and client configs, and `testdata/` for the network
130+
and crypto material used by the test environment.
131+
132+
### TLS Modes
133+
134+
| Mode | Description |
135+
| ------ | ------------------------------------------------------ |
136+
| `none` | No TLS — for local development without crypto material |
137+
| `tls` | One-sided TLS — server certificate only |
138+
| `mtls` | Mutual TLS — both sides present certificates |
139+
140+
### Environment Variables
141+
142+
Any config field can be overridden with the `ENDORSER_` prefix:
143+
144+
```shell
145+
ENDORSER_SERVER_ENDPOINT_PORT=8080 ./bin/endorser -c sampleconfig/endorser-org1.yaml
146+
```
147+
148+
## Core Dependencies
149+
150+
- [`fabric-x-sdk`](https://github.com/hyperledger/fabric-x-sdk) — endorsement building, block
151+
delivery, identity, versioned state
152+
- [`fabric-x-committer`](https://github.com/hyperledger/fabric-x-committer)`utils/connection`
153+
for gRPC server setup
154+
- [`fabric-x-common`](https://github.com/hyperledger/fabric-x-common) — config parsing

0 commit comments

Comments
 (0)