Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 207 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,65 +1,229 @@
# Federated Signer Node
# federated-signer-node

`federated-signer-node` is the **listener / federated-signer-node** service used by the current `bridge-utexo` federated signing stack.

The code still uses some old naming such as `listener`, `tricorn`, and `FederatedSignerNode` in proto packages. Treat that as protocol compatibility and legacy naming, not as a different product model.

## Current role in the stack

One running instance of this repository represents **one federation participant**.

It sits between:
- the **federated-signer orchestrator** in `bridge-utexo`
- the local **Parent Adapter**
- the local **Nitro Enclave**
- optionally an **EVM node**
- optionally the **rgb-multisig-bridge** hub

```text
Bridge
|
v
Federated-signer orchestrator
|
v
Federated-signer-node / listener
| \
| \
v v
Parent Adapter EVM node
|
v
Nitro Enclave

Optional on the same node:
federated-signer-node <-> rgb-multisig-bridge
```

Listener / Signer Node for **Tricorn Federated Signing** (one instance per federation partner, typically on EC2). Accepts gRPC from the **Orchestrator** (`FederatedSignerNode`), validates bridge data, enriches `SignRequest.data`, and forwards to the **Enclave** via the **Parent Adapter** (`EnclaveService` on `listener-enclave.proto`).
## What the service actually does

## Proto layout (Tricorn communication map)
This repository now contains **three runtime capabilities** that can be enabled independently by config.

| Proto file | Channel | Go package (generated) |
|------------|---------|--------------------------|
| `proto/signer/signer.proto` | Shared messages | `signerpb` |
| `proto/bridge-orchestrator.proto` | Bridge → Orchestrator `FederatedSigner` | `pb_bridge_orchestrator` |
| `proto/orchestrator-listener.proto` | Orchestrator → Listener `FederatedSignerNode` | `pb_orchestrator_listener` |
| `proto/listener-enclave.proto` | Listener → Parent Adapter → Enclave `EnclaveService` | `pb_listener_enclave` |
| `proto/enriched/enriched-payload.proto` | Enriched / bootstrap `SignRequest.data` (no RPC) | `enrichedpb` |
| `proto/orchestrator-listener-health.proto` | Same port as Listener; ops health | `listenerhealthpb` |
### 1. EVM event indexing

Regenerate Go after editing: `bash scripts/gen-proto.sh`.
Code:
- [listener/evm_listener.go](listener/evm_listener.go)
- [listener/finality_tracker.go](listener/finality_tracker.go)

## First Task: Service Scaffolding + EVM Event Listener + SQLite
Role:
- polls the configured EVM bridge contract
- persists detected `FundsIn` events into SQLite
- tracks the last scanned block
- marks rows finalized using the chain finalized block

### Features
This is used by the signing path that must prove an EVM `FundsIn` really happened and is finalized before the enclave signs the outbound PSBT flow.

- **EVM event listener**: WebSocket subscription to Geth, parses `FundsIn`, `FundsInNative`, `FundsInBurn` from Bridge contract
- **SQLite**: `evm_events` table with migrations
- **Block finality tracker**: Polls `eth_getBlockByNumber("finalized")`, marks events as finalized
- **Graceful shutdown**: Context cancellation on SIGINT/SIGTERM
### 2. Orchestrator-facing gRPC node

### Project Structure
Code:
- [cmd/listener/main.go](cmd/listener/main.go)
- [internal/federatedsignergrpc/service.go](internal/federatedsignergrpc/service.go)
- [internal/federatedsignergrpc/grpc.go](internal/federatedsignergrpc/grpc.go)
- [internal/listenerenclaveclient](internal/listenerenclaveclient)

```
cmd/listener/ # Main entrypoint
internal/
config/ # Environment config
contracts/ # Bridge ABI (FundsIn events)
db/ # SQLite + migrations
listener/ # EVM listener, finality tracker
Role:
- exposes `FederatedSignerNode.Sign`
- exposes `FederatedSignerNode.PublicKey`
- exposes `ListenerHealth.Health` on the same port
- validates and enriches orchestrator bootstrap payloads
- forwards the enriched request to the enclave through the Parent Adapter

This is the main runtime mode for the current bridge stack.

### 3. RGB multisig hub cosigner

Code:
- [listener/rgbbridge/listener.go](listener/rgbbridge/listener.go)
- [listener/microservice/attach_rgb.go](listener/microservice/attach_rgb.go)

Role:
- connects to `rgb-multisig-bridge`
- syncs multisig operations from the hub
- responds as this cosigner
- stores RGB bridge audit state in SQLite
- can sign hub PSBTs either through `rgb-lib-go` or via the enclave

This path is compiled only with `-tags rgb`. The Docker build uses that tag by default.

## Signing flows

The listener does not sign arbitrary opaque bytes. It validates bridge-specific context first, then sends an **enriched** payload to the enclave.

### `TRANSACTION` flow: EVM -> RGB / PSBT signing

Input bootstrap:
- [SignPsbtBootstrap](proto/enriched/enriched-payload.proto)

Flow:
1. The orchestrator calls `FederatedSignerNode.Sign` with `data_type=TRANSACTION`.
2. The listener reads `tx_hash` and `operation_idx` from the bootstrap payload.
3. The listener checks that the EVM event exists in `evm_events` and is finalized.
4. The listener rejects `tx_hash` values already present in `processed_events`.
5. The listener fetches the PSBT from `rgb-multisig-bridge`.
6. The listener builds `EnrichedPsbtPayload`.
7. The listener calls `EnclaveService.Sign` through the Parent Adapter.
8. On success, the listener records the EVM tx hash in `processed_events`.

### `SWAP` flow: RGB -> EVM / calldata signing

Input bootstrap:
- [SignEvmBootstrap](proto/enriched/enriched-payload.proto)

Flow:
1. The orchestrator calls `FederatedSignerNode.Sign` with `data_type=SWAP`.
2. The listener validates the consignment.
3. The listener parses `fundsOut` calldata and checks token, recipient, amount, and commission.
4. If `ORCHESTRATOR_RGB_EVM_TRANSFER_DB_CHECK=true`, the listener requires a matching `rgb_bridge_events` row by consignment hash and cross-checks the amount.
5. The listener injects chain ID, proxy contract, consignment hash, and validated amounts into `EnrichedEvmPayload`.
6. The listener calls `EnclaveService.Sign`.
7. The enclave returns the EVM signature.

### `PublicKey` flow

`FederatedSignerNode.PublicKey` is just proxied to the enclave through the Parent Adapter.

### `Health` flow

`ListenerHealth.Health` reports:
- geth syncing state
- current and highest block
- last finalized block
- whether the enclave is reachable

## Persistence

The service uses **SQLite**, not PostgreSQL.

Code:
- [listener/database](listener/database)
- [listener/database/migrations](listener/database/migrations)

Main tables:
- `evm_events`: detected EVM bridge events
- `listener_blocks`: last scanned EVM block
- `processed_events`: EVM tx hashes already consumed by successful sign flow
- `rgb_bridge_events`: RGB hub operation audit trail and transfer linkage

## Proto map

| Proto file | Purpose |
|------------|---------|
| [proto/signer/signer.proto](proto/signer/signer.proto) | shared signing messages |
| [proto/orchestrator-listener.proto](proto/orchestrator-listener.proto) | orchestrator -> listener RPC |
| [proto/orchestrator-listener-health.proto](proto/orchestrator-listener-health.proto) | operational health RPC |
| [proto/listener-enclave.proto](proto/listener-enclave.proto) | listener -> parent adapter -> enclave RPC |
| [proto/enriched/enriched-payload.proto](proto/enriched/enriched-payload.proto) | bootstrap and enriched payload messages |
| [proto/bridge-orchestrator.proto](proto/bridge-orchestrator.proto) | bridge -> orchestrator RPC contract reference |

Regenerate Go code after proto edits:

```bash
bash scripts/gen-proto.sh
```

### Configuration
## Configuration

| Env | Default | Description |
|-----|---------|-------------|
| GETH_WS_ENDPOINT | ws://127.0.0.1:8545 | Geth WebSocket |
| BRIDGE_CONTRACT_ADDRESS | (required) | Bridge contract |
| PARENT_ADAPTER_GRPC | 127.0.0.1:5000 | Parent Adapter (`tricorn.EnclaveService`) |
| LISTEN_PORT | 5001 | Future gRPC port |
| LOG_LEVEL | info | debug, info, warn, error |
| DB_PATH | ./data/listener.db | SQLite path |
| FINALITY_POLL_INTERVAL_SEC | 12 | Finality poll interval |
Current example files:
- [configs/env_examples/.example.listener.env](configs/env_examples/.example.listener.env)
- [configs/env_examples/.example.listener.database.env](configs/env_examples/.example.listener.database.env)

Copy `.env.example` to `.env` and set `BRIDGE_CONTRACT_ADDRESS`.
Important current envs:
- `NODE_ADDRESS`: EVM RPC endpoint
- `CHAIN_ID`: EVM chain ID used in enriched EVM signing payload
- `BRIDGE_CONTRACT_ADDRESS`: EVM bridge contract watched by the listener
- `PROXY_CONTRACT_ADDRESS`: proxy contract used in RGB -> EVM signing checks
- `PARENT_ADAPTER_GRPC`: TCP address of the Parent Adapter
- `GRPC_LISTEN_ADDR`: listener gRPC address exposed to the orchestrator
- `EVM_LISTENER_ENABLED`: enables EVM indexing/finality tracking
- `ORCHESTRATOR_GRPC_ENABLED`: enables the gRPC listener node API
- `ORCHESTRATOR_RGB_EVM_TRANSFER_DB_CHECK`: requires RGB hub DB correlation for SWAP signing
- `RGB_MULTISIG_BRIDGE_ENABLED`: enables RGB hub cosigner mode

### Run
Important note:
- the committed examples are single-node examples
- the multi-node compose setup expects per-node files such as `.listener-node-1.env`, `.listener-node-2.env`, `.listener-node-3.env`
- those per-node files are environment-specific derivatives of the example listener config

From the module root:
## Running

### Local manual run

Without RGB-specific runtime:

```bash
go run ./cmd/listener
go run cmd/listener/main.go run -f
```

Or build and run:
With RGB hub support:

```bash
go build -o listener ./cmd/listener
./listener
go run -tags rgb cmd/listener/main.go run -f
```

### Docker

Primary Dockerfile:
- [deploy/listener.Dockerfile](deploy/listener.Dockerfile)

Current multi-node stack reference:
- [deploy/docker-compose.yml](deploy/docker-compose.yml)

That compose file models:
- 3 enclaves
- 3 parent adapters
- 3 listener nodes
- shared operational logging via Dozzle

Practical note:
- [deploy/local/docker-compose.yml](deploy/local/docker-compose.yml) is an older single-listener setup and may need adjustments before use with the current runtime

## Documentation stance

When updating docs for this repository, assume:
- this repo is the **federated-signer-node / listener** only
- the orchestrator lives in `bridge-utexo`
- the enclave and parent adapter are downstream dependencies, not implemented here
- the important current paths are:
- orchestrator-facing gRPC signing
- EVM event validation/finality
- RGB multisig bridge synchronization
37 changes: 31 additions & 6 deletions configs/env_examples/.example.listener.env
Original file line number Diff line number Diff line change
@@ -1,18 +1,43 @@
GETH_WS_ENDPOINT=ws://127.0.0.1:8545
NODE_ADDRESS=http://127.0.0.1:8545
CHAIN_ID=
BRIDGE_CONTRACT_ADDRESS=
PROXY_CONTRACT_ADDRESS=

PARENT_ADAPTER_GRPC=127.0.0.1:5000
# Background EnclaveService.PublicKey health probe interval (seconds); 0 disables.
PARENT_ADAPTER_GRPC_HEALTH_PROBE_SEC=30
# Debug-log remaining deadline per Parent Adapter unary RPC (true/false).
PARENT_ADAPTER_GRPC_LOG_DEADLINE=false
LISTEN_PORT=5001

GRPC_LISTEN_ADDR=:50051
LOG_LEVEL=info

EVENT_POLL_INTERVAL_SEC=12
FINALITY_POLL_INTERVAL_SEC=12
EVENT_LISTENING_LIMIT=1000

# SWAP (RGB→EVM): require a matching rgb_bridge_events row (hub consignment SHA-256 + amount). Set false if this listener has no RGB hub cosigner on the same DB.
# ORCHESTRATOR_RGB_EVM_TRANSFER_DB_CHECK=true
EVM_LISTENER_ENABLED=true
ORCHESTRATOR_GRPC_ENABLED=true
ORCHESTRATOR_RGB_EVM_TRANSFER_DB_CHECK=true

# --- RGB multisig hub (-tags rgb) optional ---
# RGB_HUB_SIGN_VIA_ENCLAVE=true
# RGB_ENCLAVE_NETWORK_ID=0
RGB_MULTISIG_BRIDGE_ENABLED=false
RGB_HUB_URL=
RGB_HUB_TOKEN=
RGB_MULTISIG_KEYS_JSON=
RGB_SIGNER_DATA_DIR=./data/rgb-signer
RGB_MULTISIG_POLL_INTERVAL_SEC=5
RGB_INDEXER_URL=
RGB_WALLET_DATA_DIR=./data/rgb-wallet
RGB_WALLET_XPUB_VAN=
RGB_WALLET_XPUB_COL=
RGB_WALLET_MASTER_FINGERPRINT=
# RGB_WALLET_MNEMONIC=
# RGB_WALLET_VANILLA_KEYCHAIN=1
RGB_WALLET_MAX_ALLOCATIONS_PER_UTXO=20
RGB_WALLET_SKIP_CONSISTENCY_CHECK=false
RGB_BITCOIN_NETWORK=signet

# Use the enclave instead of rgb-lib singlesig Wallet for hub PSBT signing.
RGB_HUB_SIGN_VIA_ENCLAVE=false
RGB_ENCLAVE_NETWORK_ID=0