Skip to content

Verifier Service - Block Diagram

This document provides a detailed block diagram of the verifier service components and how data flows between them.

High-Level Architecture

┌─────────────────────────────────────────────────────────────────────────────
│                           VERIFIER SERVICE
│
│                        ┌────────────────┐
│                        │  Coordinator   │
│                        │  (External)    │
│                        └────────┬───────┘
│                                 │
│                                 │ Multiple gRPC Streams
│                                 │ (Load balanced)
│                    ┌────────────┼────────────┐
│                    │            │            │
│                    ▼            ▼            ▼
│         ┌──────────────┐  ┌──────────────┐  ┌──────────────┐
│         │  Verifier    │  │  Verifier    │  │  Verifier    │
│         │  Server      │  │  Server      │  │  Server      │
│         │  Instance 1  │  │  Instance 2  │  │  Instance N  │
│         └──────┬───────┘  └──────┬───────┘  └──────┬───────┘
│                │                 │                 │
│                │ New Stream      │ New Stream      │ New Stream
│                │ Creates         │ Creates         │ Creates
│                ▼                 ▼                 ▼
│         ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│         │ Parallel        │ │ Parallel        │ │ Parallel        │
│         │ Executor        │ │ Executor        │ │ Executor        │
│         │ (Stream 1)      │ │ (Stream 2)      │ │ (Stream N)      │
│         │                 │ │                 │ │                 │
│         │ - Verifier      │ │ - Verifier      │ │ - Verifier      │
│         │ - N Workers     │ │ - N Workers     │ │ - N Workers     │
│         │ - Batcher       │ │ - Batcher       │ │ - Batcher       │
│         └─────────────────┘ └─────────────────┘ └─────────────────┘
│
│
└─────────────────────────────────────────────────────────────────────────────

Detailed Component Diagram with Data Flow

                    ┌───────────────────┐
                    │   Coordinator     │
                    │     Service       │
                    └─────────┬─────────┘
                              │
                              │ Bidirectional gRPC Stream
                              │
┌─────────────────────────────┼─────────────────────────────────────────────────
│ VERIFIER SERVICE            │
│                             │
│  ┌──────────────────────────▼──────────────────────────────────────────────┐
│  │  VERIFIER SERVER (verifier_server.go)                                   │
│  │                                                                         │
│  │  Verifier is STATELESS. Each stream maintains its own policy state.     │
│  │                                                                         │
│  │  ┌────────────────────────────────────────────────────────────────────┐ │
│  │  │  StartStream(stream Verifier_StartStreamServer)                    │ │
│  │  │  - Creates new parallelExecutor for this stream                    │ │
│  │  │  - Launches goroutines using errgroup:                             │ │
│  │  │    • handleInputs()                                                │ │
│  │  │    • handleOutputs()                                               │ │
│  │  │    • handleCutoff()                                                │ │
│  │  │    • handleChannelInput() × Parallelism (default: 4 workers)       │ │
│  │  └────────────────────────────────────────────────────────────────────┘ │
│  │                                                                         │
│  │  ┌────────────────────────────────────────────────────────────────────┐ │
│  │  │  handleInputs()                                                    │ │
│  │  │  ┌──────────────────────────────────────────────────────────────┐  │ │
│  │  │  │  1. Receive VerifierBatch from coordinator                   │  │ │
│  │  │  │     - Contains: Update + Requests[]                          │  │ │
│  │  │  │     - FIRST batch MUST include policy updates (piggybacked)  │  │ │
│  │  │  │       to initialize the stream                               │  │ │
│  │  │  │                                                              │  │ │
│  │  │  │  2. Update Policies (if Update present)                      │  │ │
│  │  │  │     executor.verifier.updatePolicies(batch.Update)           │  │ │
│  │  │  │     - Config updates: MSP Manager, LifecycleEndorsement      │  │ │
│  │  │  │     - Namespace policies: MSP/Threshold rules                │  │ │
│  │  │  │                                                              │  │ │
│  │  │  │  3. Forward Requests to Executor                             │  │ │
│  │  │  │     for each request in batch.Requests:                      │  │ │
│  │  │  │       input.Write(request) → executor.inputCh                │  │ │
│  │  │  └──────────────────────────────────────────────────────────────┘  │ │
│  │  └────────────────┬───────────────────────────────────────────────────┘ │
│  │                   │                                                     │
│  │                   │ inputCh (buffered channel)                          │
│  │                   │ capacity = ChannelBufferSize × Parallelism          │
│  │                   │                                                     │
│  │  ┌────────────────▼───────────────────────────────────────────────────┐ │
│  │  │  PARALLEL EXECUTOR (parallel_executor.go)                          │ │
│  │  │                                                                    │ │
│  │  │  ┌───────────────────────────────────────────────────────────────┐ │ │
│  │  │  │  handleChannelInput() [Worker 1..N]                           │ │ │
│  │  │  │  ┌─────────────────────────────────────────────────────────┐  │ │ │
│  │  │  │  │  Loop:                                                  │  │ │ │
│  │  │  │  │    1. Read TxWithRef from inputCh                       │  │ │ │
│  │  │  │  │    2. Call verifier.verifyRequest(tx)                   │  │ │ │
│  │  │  │  │    3. Determine if config TX (isConfig flag)            │  │ │ │
│  │  │  │  │    4. Write verificationOutput to outputSingleCh        │  │ │ │
│  │  │  │  └─────────────────────────────────────────────────────────┘  │ │ │
│  │  │  └────────────────┬──────────────────────────────────────────────┘ │ │
│  │  │                   │                                                │ │
│  │  │                   │ outputSingleCh (buffered channel)              │ │
│  │  │                   │ capacity = ChannelBufferSize × Parallelism     │ │
│  │  │                   │                                                │ │
│  │  │  ┌────────────────▼──────────────────────────────────────────────┐ │ │
│  │  │  │  handleCutoff()                                               │ │ │
│  │  │  │  ┌─────────────────────────────────────────────────────────┐  │ │ │
│  │  │  │  │  Batching Logic:                                        │  │ │ │
│  │  │  │  │                                                         │  │ │ │
│  │  │  │  │  outputBuffer []*TxStatus                               │  │ │ │
│  │  │  │  │                                                         │  │ │ │
│  │  │  │  │  Loop:                                                  │  │ │ │
│  │  │  │  │    select:                                              │  │ │ │
│  │  │  │  │      case <-time.After(BatchTimeCutoff):                │  │ │ │
│  │  │  │  │        if len(outputBuffer) > 0:                        │  │ │ │
│  │  │  │  │          emit batch                                     │  │ │ │
│  │  │  │  │                                                         │  │ │ │
│  │  │  │  │      case output := <-outputSingleCh:                   │  │ │ │
│  │  │  │  │        append to outputBuffer                           │  │ │ │
│  │  │  │  │        if output.isConfig:                              │  │ │ │
│  │  │  │  │          emit immediately (no batching)                 │  │ │ │
│  │  │  │  │        else if len(buffer) >= BatchSizeCutoff:          │  │ │ │
│  │  │  │  │          emit batch                                     │  │ │ │
│  │  │  │  └─────────────────────────────────────────────────────────┘  │ │ │
│  │  │  └────────────────┬──────────────────────────────────────────────┘ │ │
│  │  │                   │                                                │ │
│  │  │                   │ outputCh (unbuffered channel)                  │ │
│  │  │                   │                                                │ │
│  │  └───────────────────┼────────────────────────────────────────────────┘ │
│  │                      │                                                  │
│  │  ┌───────────────────▼────────────────────────────────────────────────┐ │
│  │  │  handleOutputs()                                                   │ │
│  │  │  ┌──────────────────────────────────────────────────────────────┐  │ │
│  │  │  │  Loop:                                                       │  │ │
│  │  │  │    1. Read batch from outputCh                               │  │ │
│  │  │  │    2. Update metrics (VerifierServerOutTxs)                  │  │ │
│  │  │  │    3. Send TxStatusBatch to coordinator via stream.Send()    │  │ │
│  │  │  └──────────────────────────────────────────────────────────────┘  │ │
│  │  └────────────────────────────────────────────────────────────────────┘ │
│  └─────────────────────────────────────────────────────────────────────────┘
│
│  ┌─────────────────────────────────────────────────────────────────────────┐
│  │  VERIFIER CORE (verify.go)                                              │
│  │                                                                         │
│  │  ┌────────────────────────────────────────────────────────────────────┐ │
│  │  │  verifier struct                                                   │ │
│  │  │  - verifiers: atomic.Pointer[map[string]*NsVerifier]               │ │
│  │  │  - bundle: *channelconfig.Bundle (MSP Manager)                     │ │
│  │  └────────────────────────────────────────────────────────────────────┘ │
│  │                                                                         │
│  │  ┌────────────────────────────────────────────────────────────────────┐ │
│  │  │  updatePolicies(update *VerifierUpdates)                           │ │
│  │  │  ┌──────────────────────────────────────────────────────────────┐  │ │
│  │  │  │  1. Update Bundle (if Config present)                        │  │ │
│  │  │  │     - Parse config envelope                                  │  │ │
│  │  │  │     - Create new channelconfig.Bundle                        │  │ │
│  │  │  │     - Extract MSP Manager                                    │  │ │
│  │  │  │                                                              │  │ │
│  │  │  │  2. Create New Verifiers                                     │  │ │
│  │  │  │     if Config:                                               │  │ │
│  │  │  │       - Parse LifecycleEndorsement policy                    │  │ │
│  │  │  │       - Create verifier for MetaNamespaceID                  │  │ │
│  │  │  │     if NamespacePolicies:                                    │  │ │
│  │  │  │       - For each policy item:                                │  │ │
│  │  │  │         • Parse namespace policy                             │  │ │
│  │  │  │         • Create NsVerifier                                  │  │ │
│  │  │  │                                                              │  │ │
│  │  │  │  3. Merge with Existing Verifiers                            │  │ │
│  │  │  │     - Keep existing verifiers not in update                  │  │ │
│  │  │  │     - Update MSP Manager for signature policies              │  │ │
│  │  │  │     - Store new verifiers map atomically                     │  │ │
│  │  │  └──────────────────────────────────────────────────────────────┘  │ │
│  │  └────────────────────────────────────────────────────────────────────┘ │
│  │                                                                         │
│  │  ┌────────────────────────────────────────────────────────────────────┐ │
│  │  │  verifyRequest(tx *TxWithRef) → *TxStatus                          │ │
│  │  │  ┌──────────────────────────────────────────────────────────────┐  │ │
│  │  │  │  Load current verifiers map (atomic read)                    │  │ │
│  │  │  │                                                              │  │ │
│  │  │  │  For each namespace in tx.Content.Namespaces:                │  │ │
│  │  │  │    1. Skip ConfigNamespaceID (verified by orderer)           │  │ │
│  │  │  │                                                              │  │ │
│  │  │  │    2. Lookup namespace verifier                              │  │ │
│  │  │  │       if not found:                                          │  │ │
│  │  │  │         return ABORTED_SIGNATURE_INVALID                     │  │ │
│  │  │  │                                                              │  │ │
│  │  │  │    3. Verify namespace signatures                            │  │ │
│  │  │  │       nsVerifier.VerifyNs(txID, tx, nsIndex)                 │  │ │
│  │  │  │       if error:                                              │  │ │
│  │  │  │         return ABORTED_SIGNATURE_INVALID                     │  │ │
│  │  │  │                                                              │  │ │
│  │  │  │  Return COMMITTED if all verifications pass                  │  │ │
│  │  │  └──────────────────────────────────────────────────────────────┘  │ │
│  │  └────────────────────────────────────────────────────────────────────┘ │
│  └─────────────────────────────────────────────────────────────────────────┘
│
│  ┌─────────────────────────────────────────────────────────────────────────┐
│  │  POLICY MANAGEMENT (policy/policy.go)                                   │
│  │                                                                         │
│  │  ┌────────────────────────────────────────────────────────────────────┐ │
│  │  │  CreateNamespaceVerifier(pd, idDeserializer)                       │ │
│  │  │  - Validates namespace ID (length, characters)                     │ │
│  │  │  - Unmarshals NamespacePolicy proto                                │ │
│  │  │  - Creates NsVerifier with policy and MSP Manager                  │ │
│  │  └────────────────────────────────────────────────────────────────────┘ │
│  │                                                                         │
│  │  ┌────────────────────────────────────────────────────────────────────┐ │
│  │  │  ParseLifecycleEndorsementPolicy(bundle)                           │ │
│  │  │  - Extracts LifecycleEndorsement policy from channel config        │ │
│  │  │  - Creates NsVerifier for MetaNamespaceID                          │ │
│  │  └────────────────────────────────────────────────────────────────────┘ │
│  └─────────────────────────────────────────────────────────────────────────┘
│
└─────────────────────────────────────────────────────────────────────────────

Policy Update Flow

┌─────────────────────────────────────────────────────────────────────────────┐
│                        POLICY UPDATE MECHANISM                              │
└─────────────────────────────────────────────────────────────────────────────┘

Coordinator sends VerifierBatch with Update:
  │
  ├─► Config Update (update.Config != nil)
  │   │
  │   ├─► Parse config envelope
  │   ├─► Create new channelconfig.Bundle
  │   ├─► Extract MSP Manager (identity deserializer)
  │   ├─► Parse LifecycleEndorsement policy
  │   └─► Create verifier for MetaNamespaceID
  │
  └─► Namespace Policy Update (update.NamespacePolicies != nil)
      │
      └─► For each PolicyItem:
          ├─► Validate namespace ID
          ├─► Unmarshal NamespacePolicy proto
          ├─► Determine policy type:
          │   ├─► MspRule: MSP-based signature policy
          │   └─► ThresholdRule: Threshold signature policy
          └─► Create NsVerifier for namespace

Merge with Existing Verifiers:
  │
  ├─► Keep existing verifiers not in update
  ├─► Update MSP Manager for signature policies (if Config update)
  └─► Store new verifiers map atomically

NOTE: Policy updates are applied just-in-time:
  - Sidecar waits for all preceding TXs to complete before submitting config TX
  - Verifier applies update immediately before processing next data TX
  - Ensures all subsequent TXs use the new configuration

Signature Verification Flow

┌─────────────────────────────────────────────────────────────────────────────┐
│                    SIGNATURE VERIFICATION PROCESS                           │
└─────────────────────────────────────────────────────────────────────────────┘

Transaction arrives with multiple namespaces:
  │
  └─► For each namespace in TX:
      │
      ├─► Skip ConfigNamespaceID
      │   └─► Config TXs verified by ordering service
      │
      ├─► Lookup namespace verifier
      │   ├─► If not found: ABORTED_SIGNATURE_INVALID
      │   └─► If found: proceed to verification
      │
      └─► Verify namespace signatures
          │
          ├─► Extract namespace data (ASN1 encoding)
          │   └─► Includes: TxID, NsID, NsVersion, Read/ReadWrites/BlindWrites
          │
          ├─► Get endorsements for this namespace
          │   └─► tx.Endorsements[nsIndex]
          │
          └─► Apply policy verification:
              │
              ├─► MSP-Based Policy (MspRule)
              │   ├─► Deserialize creator identities
              │   ├─► Verify signatures using MSP
              │   └─► Evaluate policy (AND/OR/NOutOf)
              │
              └─► Threshold Policy (ThresholdRule)
                  └─► Verify threshold signature (ECDSA/EdDSA/BLS)

Result:
  ├─► All namespaces valid: COMMITTED
  └─► Any namespace invalid: ABORTED_SIGNATURE_INVALID

Batching and Cutoff Logic

┌─────────────────────────────────────────────────────────────────────────────┐
│                         BATCHING MECHANISM                                  │
└─────────────────────────────────────────────────────────────────────────────┘

Configuration (ExecutorConfig):
  - BatchSizeCutoff: Minimum batch size (default: 50)
  - BatchTimeCutoff: Maximum wait time (default: 500ms)
  - Parallelism: Number of worker goroutines (default: 4)
  - ChannelBufferSize: Input/output buffer size (default: 50)

Batching Strategy:

  outputBuffer []*TxStatus
  │
  ├─► Regular Transaction Received
  │   ├─► Append to outputBuffer
  │   ├─► If len(outputBuffer) >= BatchSizeCutoff:
  │   │   └─► Emit batch immediately
  │   └─► Else: wait for more transactions or timeout
  │
  ├─► Config Transaction Received
  │   ├─► Append to outputBuffer
  │   └─► Emit batch immediately (no batching for config TXs)
  │
  └─► Timeout (BatchTimeCutoff elapsed)
      └─► If len(outputBuffer) > 0:
          └─► Emit batch (even if below BatchSizeCutoff)

Rationale:
  - Regular TXs: Batching reduces gRPC overhead and improves throughput
  - Timeout: Prevents indefinite waiting when load is low
  - Config TXs: Config TXs are processed without other TXs. Nothing to batch it with.

Concurrency Model

┌─────────────────────────────────────────────────────────────────────────────┐
│                        CONCURRENCY ARCHITECTURE                             │
└─────────────────────────────────────────────────────────────────────────────┘

Per-Stream Goroutines (using errgroup):
  │
  ├─► handleInputs() [1 goroutine]
  │   └─► Receives from gRPC stream, writes to inputCh
  │
  ├─► handleChannelInput() [N goroutines, N = Parallelism]
  │   └─► Reads from inputCh, verifies, writes to outputSingleCh
  │
  ├─► handleCutoff() [1 goroutine]
  │   └─► Reads from outputSingleCh, batches, writes to outputCh
  │
  └─► handleOutputs() [1 goroutine]
      └─► Reads from outputCh, sends to gRPC stream

Channel Flow:
  inputCh → [Workers] → outputSingleCh → [Batcher] → outputCh → gRPC

Synchronization:
  - Channels provide synchronization between goroutines
  - Atomic pointer for verifiers map (lock-free reads)
  - errgroup ensures all goroutines complete before stream closes
  - Context cancellation propagates to all goroutines

Error Handling:
  - Any goroutine error cancels the entire stream
  - ErrUpdatePolicies: Invalid policy update (INVALID_ARGUMENT)
  - Other errors: Wrapped as CANCELLED

Metrics and Monitoring

┌─────────────────────────────────────────────────────────────────────────────┐
│                              METRICS                                        │
└─────────────────────────────────────────────────────────────────────────────┘

Throughput Metrics:
  - verifier_server_tx_in_total
    └─► Counter: Total transactions received from coordinator

  - verifier_server_tx_out_total
    └─► Counter: Total transaction statuses sent to coordinator

Active State Metrics:
  - verifier_server_grpc_active_streams
    └─► Gauge: Number of active gRPC streams

  - verifier_server_parallel_executor_active_requests
    └─► Gauge: Number of transactions currently being verified

Health Check:
  - gRPC health service (grpc_health_v1.Health)
    └─► Reports service health status

Configuration Parameters

┌─────────────────────────────────────────────────────────────────────────────┐
│                         CONFIGURATION                                       │
└─────────────────────────────────────────────────────────────────────────────┘

Server Configuration:
  - Server.Port: gRPC server port (default: 5001)
  - Server.TLS: TLS configuration for secure connections

Parallel Executor Configuration:
  - Parallelism: Number of verification workers (default: 4)
    └─► Higher values: More concurrent verification
    └─► Lower values: Less memory usage

  - BatchSizeCutoff: Minimum batch size (default: 50)
    └─► Higher values: Better throughput, higher latency
    └─► Lower values: Lower latency, more gRPC overhead

  - BatchTimeCutoff: Maximum wait time (default: 500ms)
    └─► Higher values: Better batching, higher latency
    └─► Lower values: Lower latency, less efficient batching

  - ChannelBufferSize: Buffer size per worker (default: 50)
    └─► Total capacity = ChannelBufferSize × Parallelism
    └─► Higher values: Better handling of load spikes
    └─► Lower values: Less memory usage, more backpressure

Key Design Decisions

1. Per-Stream Executor

Each gRPC stream gets its own parallelExecutor instance to ensure: - Responses are sent to the correct stream - No cross-stream interference - Clean resource cleanup on stream closure

2. Atomic Verifiers Map

The verifiers map uses atomic.Pointer for lock-free reads: - Workers can read the current verifiers without blocking - Policy updates are atomic (all-or-nothing) - Temporary inconsistency is acceptable (coordinator ensures ordering)

3. Config TX Priority

Config transactions bypass batching for immediate processing: - Ensures policy updates are applied as soon as possible - Prevents dependent data TXs from being delayed - Coordinator waits for config TX completion before sending dependent TXs

4. Just-In-Time Policy Updates

Policy updates are applied immediately before processing the next data TX: - Sidecar ensures all preceding TXs are complete before submitting config TX - Verifier applies update in handleInputs() before forwarding requests - Guarantees all subsequent TXs use the new configuration

5. Namespace Verification Independence

Each namespace in a transaction is verified independently: - Allows different namespaces to have different policies - Single invalid namespace invalidates the entire transaction - Supports multi-namespace transactions with heterogeneous policies