Skip to content

Commit 8af03e1

Browse files
committed
additional documentation
Signed-off-by: Angelo De Caro <adc@zurich.ibm.com>
1 parent 9a1c684 commit 8af03e1

21 files changed

Lines changed: 341 additions & 95 deletions

File tree

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,8 @@ lint-auto-fix:
197197
install-linter-tool:
198198
@echo "Installing golangci Linter"
199199
@curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(HOME)/go/bin v2.10.1
200+
201+
.PHONY: fmt
202+
fmt: ## Run gofmt on the entire project
203+
@echo "Running gofmt..."
204+
@gofmt -l -s -w .

docs/services/network.md

Lines changed: 126 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,132 @@ The network service architecture is depicted below:
77

88
![network_service.png](../imgs/network_service.png)
99

10+
## Overview
11+
12+
The Network Service is a critical component of the Fabric Token SDK that abstracts the complexities of the underlying Distributed Ledger Technology (DLT). It serves several key purposes:
13+
14+
- **Unified Interface**: Regardless of the backend (Fabric, Fabric-X, etc.), it provides a common set of APIs for transaction management and ledger interaction.
15+
- **Transaction Lifecycle Management**: Handles the submission (broadcasting) of transactions and provides mechanisms to wait for their finality (commitment and validity).
16+
- **Ledger Querying (QE)**: Allows services to retrieve the current state of tokens and other ledger entries through a specialized Query Engine.
17+
- **Public Parameters (PP) Management**: Monitors the ledger for updates to the system's public parameters and ensures the SDK is always using the latest version to maintain cryptographic integrity.
18+
- **Identity & Membership**: Interfaces with the local Membership Service Provider (MSP) to provide identities for signing and transaction creation.
19+
20+
The service uses a **Driver-based architecture**, allowing for different implementations to be plugged in based on the specific requirements and features of the target network.
21+
1022
## Fabric
1123

12-
The Fabric-based network implementation utilizes the Fabric Smart Client for configuration and operations, including chaincode queries and transaction broadcasting.
13-
14-
During bootstrap, the Token SDK processes the TMS defined in the configuration.
15-
For each TMS, the network provider retrieves the network instance corresponding to the `network` and `channel` specified in the TMS ID.
16-
Failure to retrieve the network instance results in a bootstrap failure.
17-
Upon success, the `Connect` function on the `Network` instance is invoked with the target namespace.
18-
This function establishes a connection to the backend, enabling the Token SDK to receive updates on public parameters and transaction finality.
19-
20-
When `Connect` is called, the Fabric network implementation establishes two `Fabric Delivery` streams to receive committed blocks:
21-
- One stream is used to analyze transactions that update the public parameters.
22-
More specifically, for each transaction in a block, the parser checks if the RW set contains a write whose key is the `setup key`.
23-
The setup key is set as `\x00seU+0000`. If such a key is found in a valid transaction, then the listener added upon calling `Connect` does the following:
24-
- It invokes the `Update` function of the TMS provider passing the TMS ID and the byte representation of the new public parameters.
25-
The function works as follows: if a TMS instance with the passed ID does not exist, it creates one.
26-
If a TMS with that ID already exists, then:
27-
- A new instance of TMS is created with the new public parameters.
28-
- If the previous step succeeds, then the `Done` function on the old TMS instance is invoked to release all allocated resources.
29-
- If the above step succeeds, then the public parameters are appended to the `PublicParameters` table.
30-
- The other stream is dedicated to transaction finality. Services can add listeners to the `Network` instance to listen for the finality of specific transactions.
31-
The `ttx` service and the `audit` service add a listener when a transaction has reached the point of being ready to be submitted to the ordering service.
32-
(For more information, look at the sections dedicated to these services). Both services use the same listener.
33-
This listener performs the following actions upon notification of the finality of a transaction:
34-
- If the transaction's status is valid, then the token request's hash contained in the transaction is matched against the hash of the token request stored in the database.
35-
If they match, then the `Tokens` table is updated by inserting the new tokens and marking the spent tokens as deleted.
36-
The corresponding token request in the `Requests` table is marked as `Valid` with a change of the status field.
37-
- If the transaction's status is invalid, then the corresponding token request in the `Requests` table is marked as `Invalid` or `Deleted`.
38-
In all other cases, an error is returned.
24+
The Fabric-based network implementation utilizes the Fabric Smart Client (FSC) to interact with the underlying Hyperledger Fabric network. It leverages FSC's configuration, transaction management, and communication layers to provide a robust backend for the Token SDK.
25+
26+
### Lifecycle and Bootstrap
27+
During the Token SDK bootstrap process, the system initializes a `Network` instance for each TMS (Token Management Service) defined in the configuration. The mapping is determined by the `network` and `channel` fields in the TMS identifier. If the specified network cannot be initialized (e.g., due to missing FSC configuration for that network), the bootstrap process will fail.
28+
29+
Upon successful initialization, the `Connect` function is invoked for the target namespace. This step is crucial as it:
30+
- Registers listeners for **Public Parameters** updates.
31+
- Initializes the **Endorsement Service** for the specific namespace.
32+
- Sets up the **Finality** and **Lookup** managers.
33+
34+
### Public Parameters Monitoring
35+
The Fabric driver monitors the ledger for updates to a specific "setup key" (usually `\x00seU+0000`). It uses a `PermanentLookupListener` that triggers whenever a valid transaction writes to this key. When an update is detected:
36+
1. The **TMS Provider** is updated with the new parameters. If a TMS instance already exists, it is replaced by a new one initialized with the updated cryptographic material, and the old instance is gracefully decommissioned.
37+
2. The new public parameters are persisted in the local **Tokens Database** to ensure consistency across restarts.
38+
39+
### Finality Management
40+
The Fabric driver supports two primary modes for monitoring transaction finality, configurable via `token.finality.type`:
41+
42+
- **Delivery Mode (`delivery`)**: This is the default mode for Fabric. It establishes a dedicated block delivery stream from the peer. The driver parses incoming blocks, processes read-write sets, and notifies registered listeners when a specific transaction ID is committed and validated. It includes advanced features like:
43+
- **Parallel Processing**: Blocks and transactions can be processed in parallel to improve throughput.
44+
- **LRU Caching**: Uses a Least Recently Used cache to track recently processed blocks and prevent redundant work.
45+
- **Notification Mode (`notification`)**: In this mode, the driver relies on event notifications from the underlying network service rather than pulling the entire block stream.
46+
47+
Regardless of the mode, the `ttx` and `audit` services utilize these listeners to update the local token vault and request status (e.g., marking a request as `Valid` or `Invalid`) once a transaction reaches finality on the ledger.
48+
49+
## FabricX
50+
51+
The `fabricx` driver is a specialized implementation designed for the Fabric-X network. It shares the same overall goals as the standard Fabric driver but introduces several implementation-specific optimizations and behaviors.
52+
53+
### Async Finality Processing
54+
FabricX handles transaction finality notifications asynchronously using an internal `EventQueue`. This queue is serviced by a pool of workers (by default, 10 workers with a queue size of 1000). This decoupled architecture ensures that the main network event loop remains non-blocking even when processing a high volume of finality notifications or performing complex transaction checks.
55+
56+
### Robust Transaction Submission
57+
The transaction submission process in FabricX involves a multi-step preparation phase:
58+
1. **Transaction ID Calculation**: Computes a unique ID based on a nonce and the creator's identity.
59+
2. **Namespace Marshaling**: Uses ASN1 marshaling for the target namespace (`TxNamespace`) before signing. This ensures the transaction structure meets the specific requirements of the Fabric-X MSP and ledger.
60+
3. **Broadcasting & Confirmation**: Once signed, the transaction is broadcast to the network. The broadcaster includes retry logic specifically for `io.EOF` errors, which often occur during network startup or transient connectivity issues.
61+
62+
### Public Parameters Versioning
63+
Unlike the standard Fabric driver, FabricX employs a `VersionKeeper` to manage the lifecycle of public parameters.
64+
- **Initialization**: The first time public parameters are updated, the version is initialized (the counter does not increment).
65+
- **Updates**: Subsequent updates to the public parameters increment an atomic version counter.
66+
- **Setup**: The TMS deployment process writes both the raw public parameters and their SHA256 hash to the ledger using specific setup keys defined by the translator.
67+
68+
### Query Engine (QE) and Token Detection
69+
The Query Engine in FabricX is responsible for retrieving the state of tokens from the ledger. For non-graph-hiding drivers, it determines if a token is spent by checking for the absence of its key in the ledger (a `nil` raw value). It supports batch retrieval of states to minimize network round-trips.
70+
71+
### Finality Retries
72+
During the initial connection phase, FabricX implements a specific retry strategy for retrieving finality information. If block 0 is not yet committed (a common scenario during network cold-starts), the driver will retry the operation (up to 5 times with a 2-second delay) to ensure a stable connection is established.
73+
74+
## Configuration
75+
76+
The Network Service and its drivers can be fine-tuned through the application configuration. Below are the key configuration parameters and examples for both Fabric and FabricX.
77+
78+
### TMS Configuration
79+
Each Token Management Service must be mapped to a network and channel.
80+
81+
```yaml
82+
token:
83+
enabled: true
84+
tms:
85+
my-tms-id:
86+
network: fabric-network-name # Matches fsc.networks configuration
87+
channel: my-channel
88+
namespace: my-chaincode-id
89+
```
90+
91+
### Fabric Finality Configuration
92+
These settings control the behavior of the Fabric driver's finality manager.
93+
94+
```yaml
95+
token:
96+
finality:
97+
# Mode: "delivery" (default) or "notification"
98+
type: delivery
99+
committer:
100+
maxRetries: 3
101+
retryWaitDuration: 5s
102+
delivery:
103+
# Number of parallel workers for mapping transactions
104+
mapperParallelism: 10
105+
# Number of parallel workers for processing blocks
106+
blockProcessParallelism: 10
107+
# Size of the LRU cache for block tracking
108+
lruSize: 30
109+
# Wait duration before timing out a delivery listener
110+
listenerTimeout: 10s
111+
```
112+
113+
### FabricX Specific Configuration
114+
FabricX introduces additional settings for its asynchronous event queue and lookup service.
115+
116+
```yaml
117+
token:
118+
finality:
119+
# FabricX defaults to "notification" mode
120+
type: notification
121+
notification:
122+
# Number of worker goroutines for the async queue
123+
workers: 10
124+
# Size of the event buffer
125+
queueSize: 1000
126+
127+
fabricx:
128+
lookup:
129+
permanent:
130+
# Polling interval for permanent lookups (e.g., public params)
131+
interval: 1m
132+
once:
133+
# Max time allowed for a one-time lookup
134+
deadline: 5m
135+
# Polling interval for one-time lookups
136+
interval: 2s
137+
```
39138

token/services/network/fabricx/config/config.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ const (
3131
Notification ManagerType = "notification"
3232
)
3333

34-
// NewListenerManagerConfig returns a new listener manager configuration.
34+
// NewListenerManagerConfig returns a new listener manager configuration instance
35+
// that wraps the provided Configuration interface to retrieve settings.
3536
func NewListenerManagerConfig(configuration Configuration) *serviceListenerManagerConfig {
3637
return &serviceListenerManagerConfig{c: configuration}
3738
}
@@ -41,7 +42,8 @@ type serviceListenerManagerConfig struct {
4142
c Configuration
4243
}
4344

44-
// Type returns the manager type.
45+
// Type returns the manager type from the configuration using the Type key.
46+
// If the configured value is empty, it defaults to the Notification manager type.
4547
func (c *serviceListenerManagerConfig) Type() ManagerType {
4648
if v := ManagerType(c.c.GetString(Type)); len(v) > 0 {
4749
return v

token/services/network/fabricx/driver.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ import (
3939
"go.opentelemetry.io/otel/trace"
4040
)
4141

42-
// NewDriver returns a new Driver instance.
42+
// NewDriver returns a new Driver instance for the FabricX network.
43+
// It initializes core services including the query executor provider, listener managers,
44+
// and endorsement service provider. It also validates that the finality type
45+
// is set to "notification" and initializes the event queue for finality notifications.
4346
func NewDriver(
4447
fnsProvider *fabric2.NetworkServiceProvider,
4548
tokensManager *tokens.ServiceManager,
@@ -140,7 +143,10 @@ type Driver struct {
140143
queryExecutorProvider *qe.ExecutorProvider
141144
}
142145

143-
// New returns a new Network instance for the given network and channel.
146+
// New returns a new Network instance for the specified network and channel.
147+
// It retrieves the Fabric network service and channel, initializes the necessary
148+
// query executors (token, spent token, and state) for that context,
149+
// and sets up finality and lookup listener managers.
144150
func (d *Driver) New(network, channel string) (driver.Network, error) {
145151
fns, err := d.fnsProvider.FabricNetworkService(network)
146152
if err != nil {

token/services/network/fabricx/endorsement/esp.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ type ServiceProvider struct {
4141
lazy.Provider[token2.TMSID, endorsement.Service]
4242
}
4343

44-
// NewServiceProvider returns a new ServiceProvider instance.
44+
// NewServiceProvider returns a new ServiceProvider instance for FabricX endorsement.
45+
// It uses a lazy provider to load endorsement services for different TMS IDs
46+
// based on their configuration and requirements.
4547
func NewServiceProvider(
4648
configService common.Configuration,
4749
viewManager ViewManager,
@@ -80,6 +82,10 @@ type loader struct {
8082
fabricProvider *fabric.NetworkServiceProvider
8183
}
8284

85+
// load creates and returns an endorsement.Service for the specified TMS ID.
86+
// It retrieves the necessary configuration, initializes an FSC endorsement service,
87+
// and sets up a translator factory that uses the current public parameters version
88+
// from the version keeper.
8389
func (l *loader) load(tmsID token2.TMSID) (endorsement.Service, error) {
8490
configuration, err := l.configService.ConfigurationFor(tmsID.Network, tmsID.Channel, tmsID.Namespace)
8591
if err != nil {
@@ -120,7 +126,8 @@ func key(tmsID token2.TMSID) string {
120126
type NamespaceTxProcessor struct {
121127
}
122128

123-
// EnableTxProcessing does nothing because for FabricX the endorser is stateless
129+
// EnableTxProcessing is a no-op implementation because for FabricX
130+
// the endorser service is stateless and does not require pre-processing.
124131
func (n *NamespaceTxProcessor) EnableTxProcessing(tmsID token2.TMSID) error {
125132
return nil
126133
}

token/services/network/fabricx/endorsement/rwset.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,27 +29,39 @@ type RWSetWrapper struct {
2929
ppVersion uint64
3030
}
3131

32-
// NewRWSetWrapper returns a new RWSetWrapper instance.
32+
// NewRWSetWrapper returns a new RWSetWrapper instance that binds a read-write set
33+
// to a specific namespace, transaction ID, and public parameters version.
3334
func NewRWSetWrapper(RWSet rwSet, namespace translator.Namespace, txID translator.TxID, ppVersion uint64) *RWSetWrapper {
3435
return &RWSetWrapper{RWSet: RWSet, Namespace: namespace, TxID: txID, ppVersion: ppVersion}
3536
}
3637

38+
// SetState writes the given key-value pair to the wrapped read-write set
39+
// using the configured namespace.
3740
func (w *RWSetWrapper) SetState(key translator.Key, value translator.Value) error {
3841
return w.RWSet.SetState(w.Namespace, key, value)
3942
}
4043

44+
// GetState reads the value for the given key from the wrapped read-write set
45+
// using the configured namespace.
4146
func (w *RWSetWrapper) GetState(key translator.Key) (translator.Value, error) {
4247
return w.RWSet.GetState(w.Namespace, key)
4348
}
4449

50+
// DeleteState marks the given key for deletion in the wrapped read-write set
51+
// using the configured namespace.
4552
func (w *RWSetWrapper) DeleteState(key translator.Key) error {
4653
return w.RWSet.DeleteState(w.Namespace, key)
4754
}
4855

56+
// StateMustNotExist adds a read dependency to the read-write set asserting
57+
// that the given key does not exist in the configured namespace (version is nil).
4958
func (w *RWSetWrapper) StateMustNotExist(key translator.Key) error {
5059
return w.RWSet.AddReadAt(w.Namespace, key, nil)
5160
}
5261

62+
// StateMustExist adds a read dependency to the read-write set asserting
63+
// that the given key exists. If version is VersionZero (0), it asserts existence at version 0.
64+
// If version is Latest, it asserts existence at the current public parameters version.
5365
func (w *RWSetWrapper) StateMustExist(key translator.Key, version translator.KeyVersion) error {
5466
switch version {
5567
case translator.VersionZero:

0 commit comments

Comments
 (0)