Skip to content

Commit c787016

Browse files
committed
Merge branch 'main' of github.com:smartcontractkit/chainlink-testing-framework into properJSON
2 parents 29ed5aa + 64f30d9 commit c787016

25 files changed

+2851
-0
lines changed

book/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
- [Debug Loki errors](./libs/wasp/how-to/debug_loki_errors.md)
8282
- [Havoc](./libs/havoc.md)
8383
- [K8s Test Runner](k8s-test-runner/k8s-test-runner.md)
84+
- [Sentinel](./libs/sentinel.md)
8485

8586
---
8687

book/src/libs/sentinel.md

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
# Sentinel
2+
3+
**🚧 Beta Notice:** Sentinel is currently in **Beta** mode. The API is **subject to change**, and users are advised to stay updated with the latest releases and documentation.
4+
5+
## Table of Contents
6+
7+
- [Overview](#overview)
8+
- [Key Features](#key-features)
9+
- [System Architecture](#system-architecture)
10+
- [How Components Interact](#how-components-interact)
11+
- [Core Components](#core-components)
12+
- [Usage](#usage)
13+
- [Initialize Sentinel](#initialize-sentinel)
14+
- [Add a Chain](#add-a-chain)
15+
- [Subscribe to Events](#subscribe-to-events)
16+
- [Unsubscribe](#unsubscribe)
17+
- [Remove a Chain](#remove-a-chain)
18+
- [API Reference](#api-reference)
19+
- [Sentinel](#sentinel)
20+
- [Testing](#testing)
21+
- [Run Tests](#run-tests)
22+
23+
## Overview
24+
25+
Sentinel is a centralized orchestrator that manages multiple blockchain poller services, each responsible for a specific blockchain network (e.g., Ethereum, Optimism, Arbitrum). It provides a unified interface for subscribing to blockchain events, ensuring efficient log polling and event broadcasting to subscribers.
26+
27+
## Key Features
28+
29+
- **Multi-Chain Support**: Manage multiple blockchain networks concurrently.
30+
- **Event Broadcasting**: Relay blockchain events to subscribers via a thread-safe subscription system.
31+
- **Flexible Subscriptions**: Dynamically subscribe and unsubscribe to events based on addresses and topics.
32+
- **Graceful Lifecycle Management**: Start, stop, and clean up resources across services effortlessly.
33+
- **Comprehensive Testing**: Ensures reliability through extensive unit and integration tests.
34+
- **Scalable Architecture**: Designed to handle polling multiple chains with multiple users subscribed to multiple events.
35+
36+
## System Architecture
37+
38+
### How Components Interact
39+
40+
```mermaid
41+
graph TD
42+
Sentinel["Sentinel<br/>(Coordinator)"]
43+
44+
subgraph Ethereum
45+
ChainPollerSvc_Ethereum["ChainPollerSvc<br/>(Ethereum)"]
46+
ChainPoller_Ethereum["ChainPoller<br/>(Log Fetching)"]
47+
SubscriptionManager_Ethereum["Subscription Manager"]
48+
ChainPollerSvc_Ethereum --> ChainPoller_Ethereum
49+
ChainPollerSvc_Ethereum --> SubscriptionManager_Ethereum
50+
ChainPoller_Ethereum --> Blockchain_Ethereum["Blockchain<br/>(Ethereum)"]
51+
end
52+
53+
subgraph Polygon
54+
ChainPollerSvc_Polygon["ChainPollerSvc<br/>(Polygon)"]
55+
ChainPoller_Polygon["ChainPoller<br/>(Log Fetching)"]
56+
SubscriptionManager_Polygon["Subscription Manager"]
57+
ChainPollerSvc_Polygon --> ChainPoller_Polygon
58+
ChainPollerSvc_Polygon --> SubscriptionManager_Polygon
59+
ChainPoller_Polygon --> Blockchain_Polygon["Blockchain<br/>(Polygon)"]
60+
end
61+
62+
subgraph Arbitrum
63+
ChainPollerSvc_Arbitrum["ChainPollerSvc<br/>(Arbitrum)"]
64+
ChainPoller_Arbitrum["ChainPoller<br/>(Log Fetching)"]
65+
SubscriptionManager_Arbitrum["Subscription Manager"]
66+
ChainPollerSvc_Arbitrum --> ChainPoller_Arbitrum
67+
ChainPollerSvc_Arbitrum --> SubscriptionManager_Arbitrum
68+
ChainPoller_Arbitrum --> Blockchain_Arbitrum["Blockchain<br/>(Arbitrum)"]
69+
end
70+
71+
Sentinel --> Ethereum
72+
Sentinel --> Polygon
73+
Sentinel --> Arbitrum
74+
```
75+
76+
### Core Components
77+
78+
1. **Sentinel**:
79+
- **Role**: Central coordinator managing multiple `ChainPollerService` instances.
80+
- **Visibility**: External
81+
- **Responsibilities**:
82+
- Handles adding and removing blockchain chains.
83+
- Manages global subscriptions.
84+
- Orchestrates communication between components.
85+
86+
2. **ChainPollerService**:
87+
- **Role**: Manages the polling process for a specific blockchain.
88+
- **Visibility**: Internal
89+
- **Responsibilities**:
90+
- Polls blockchain logs based on filter queries.
91+
- Integrates internal `ChainPoller` and `SubscriptionManager`.
92+
- Broadcasts fetched logs to relevant subscribers.
93+
94+
3. **ChainPoller**:
95+
- **Role**: Fetches logs from blockchain networks.
96+
- **Visibility**: Internal
97+
- **Responsibilities**:
98+
- Interacts with the blockchain client to retrieve logs.
99+
- Processes filter queries to fetch relevant logs.
100+
101+
4. **SubscriptionManager**:
102+
- **Role**: Manages event subscriptions for a specific chain.
103+
- **Visibility**: Internal
104+
- **Responsibilities**:
105+
- Tracks subscriptions to blockchain events.
106+
- Ensures thread-safe management of subscribers.
107+
- Broadcasts logs to all relevant subscribers.
108+
109+
## Usage
110+
111+
### Initialize Sentinel
112+
113+
Set up a `Sentinel` instance:
114+
115+
```go
116+
package main
117+
118+
import (
119+
"github.com/rs/zerolog"
120+
"os"
121+
122+
"github.com/smartcontractkit/chainlink-testing-framework/sentinel"
123+
)
124+
125+
func main() {
126+
// Initialize logger
127+
logger := zerolog.New(os.Stdout).With().Timestamp().Logger()
128+
129+
// Initialize Sentinel
130+
sentinelCoordinator := sentinel.NewSentinel(sentinel.SentinelConfig{
131+
Logger: &logger,
132+
})
133+
defer sentinelCoordinator.Close()
134+
}
135+
```
136+
137+
### Add a Chain
138+
139+
Add a blockchain to monitor:
140+
141+
```go
142+
package main
143+
144+
import (
145+
"time"
146+
147+
"github.com/ethereum/go-ethereum/ethclient"
148+
"github.com/smartcontractkit/chainlink-testing-framework/sentinel/blockchain_client_wrapper"
149+
"github.com/smartcontractkit/chainlink-testing-framework/sentinel/sentinel"
150+
)
151+
152+
func main() {
153+
// Initialize logger and Sentinel as shown above
154+
155+
// Setup blockchain client (e.g., Geth)
156+
client, err := ethclient.Dial("https://mainnet.infura.io/v3/YOUR-PROJECT-ID")
157+
if err != nil {
158+
panic("Failed to connect to blockchain client: " + err.Error())
159+
}
160+
wrappedClient := blockchain_client_wrapper.NewGethClientWrapper(client)
161+
162+
// Add a new chain to Sentinel
163+
err = sentinelCoordinator.AddChain(sentinel.AddChainConfig{
164+
ChainID: 1, // Ethereum Mainnet
165+
PollInterval: 10 * time.Second,
166+
BlockchainClient: wrappedClient,
167+
})
168+
if err != nil {
169+
panic("Failed to add chain: " + err.Error())
170+
}
171+
}
172+
```
173+
174+
### Subscribe to Events
175+
176+
Subscribe to blockchain events:
177+
178+
```go
179+
package main
180+
181+
import (
182+
"fmt"
183+
184+
"github.com/ethereum/go-ethereum/common"
185+
"github.com/smartcontractkit/chainlink-testing-framework/sentinel/api"
186+
)
187+
188+
func main() {
189+
// Initialize logger, Sentinel, and add a chain as shown above
190+
191+
// Define the address and topic to subscribe to
192+
address := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678")
193+
topic := common.HexToHash("0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef")
194+
195+
// Subscribe to the event
196+
logCh, err := sentinelCoordinator.Subscribe(1, address, topic)
197+
if err != nil {
198+
panic("Failed to subscribe: " + err.Error())
199+
}
200+
defer sentinelCoordinator.Unsubscribe(1, address, topic, logCh)
201+
202+
// Listen for logs in a separate goroutine
203+
go func() {
204+
for log := range logCh {
205+
fmt.Printf("Received log: %+v\n", log)
206+
}
207+
}()
208+
}
209+
```
210+
211+
### Unsubscribe
212+
213+
Unsubscribe from events:
214+
215+
```go
216+
package main
217+
218+
func main() {
219+
// Initialize logger, Sentinel, add a chain, and subscribe as shown above
220+
221+
// Assume logCh is the channel obtained from Subscribe
222+
err = sentinelCoordinator.Unsubscribe(1, address, topic, logCh)
223+
if err != nil {
224+
panic("Failed to unsubscribe: " + err.Error())
225+
}
226+
}
227+
```
228+
229+
### Remove a Chain
230+
231+
Remove a blockchain from monitoring:
232+
233+
```go
234+
package main
235+
236+
func main() {
237+
// Initialize logger, Sentinel, add a chain, and subscribe as shown above
238+
239+
// Remove the chain
240+
err = sentinelCoordinator.RemoveChain(1)
241+
if err != nil {
242+
panic("Failed to remove chain: " + err.Error())
243+
}
244+
}
245+
```
246+
247+
## API Reference
248+
249+
### Sentinel
250+
251+
- **`NewSentinel(config SentinelConfig) *Sentinel`**
252+
Initializes a new Sentinel instance.
253+
254+
- **`AddChain(config AddChainConfig) error`**
255+
Adds a new blockchain chain to Sentinel.
256+
257+
- **`RemoveChain(chainID int64) error`**
258+
Removes an existing chain from Sentinel.
259+
260+
- **`Subscribe(chainID int64, address common.Address, topic common.Hash) (chan api.Log, error)`**
261+
Subscribes to a specific event on a given chain.
262+
263+
- **`Unsubscribe(chainID int64, address common.Address, topic common.Hash, ch chan api.Log) error`**
264+
Unsubscribes from a specific event.
265+
266+
## Testing
267+
268+
### Run Tests
269+
270+
Run the comprehensive test suite using:
271+
272+
```bash
273+
go test -race ./... -v
274+
```

go.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ flowchart LR
2222
click chainlink-testing-framework/lib href "https://github.com/smartcontractkit/chainlink-testing-framework"
2323
chainlink-testing-framework/lib/grafana
2424
click chainlink-testing-framework/lib/grafana href "https://github.com/smartcontractkit/chainlink-testing-framework"
25+
chainlink-testing-framework/sentinel --> chainlink-testing-framework/lib
26+
click chainlink-testing-framework/sentinel href "https://github.com/smartcontractkit/chainlink-testing-framework"
2527
chainlink-testing-framework/seth --> seth
2628
click chainlink-testing-framework/seth href "https://github.com/smartcontractkit/chainlink-testing-framework"
2729
chainlink-testing-framework/tools/citool --> chainlink-testing-framework/lib
@@ -47,6 +49,7 @@ flowchart LR
4749
chainlink-testing-framework/havoc
4850
chainlink-testing-framework/lib
4951
chainlink-testing-framework/lib/grafana
52+
chainlink-testing-framework/sentinel
5053
chainlink-testing-framework/seth
5154
chainlink-testing-framework/tools/citool
5255
chainlink-testing-framework/tools/envresolve

sentinel/.changeset/.gitkeep

Whitespace-only changes.

sentinel/.changeset/v0.1.0.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Sentinel init

sentinel/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Sentinel
2+
3+
Sentinel is a centralized orchestrator that manages multiple blockchain poller services, each responsible for a specific blockchain network (e.g., Ethereum, Polygon, Arbitrum). It provides a unified interface for subscribing to blockchain events, ensuring efficient log polling and event broadcasting to subscribers.
4+
5+
[![Documentation](https://img.shields.io/badge/Documentation-MDBook-blue?style=for-the-badge)](https://smartcontractkit.github.io/chainlink-testing-framework/libs/sentinel.html)

sentinel/api/blockchain_client.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// File: api/blockchain_client.go
2+
package api
3+
4+
import (
5+
"context"
6+
)
7+
8+
// BlockchainClient defines the required methods for interacting with a blockchain.
9+
type BlockchainClient interface {
10+
BlockNumber(ctx context.Context) (uint64, error)
11+
FilterLogs(ctx context.Context, query FilterQuery) ([]Log, error)
12+
}

sentinel/api/types.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// File: api/types.go
2+
package api
3+
4+
import "github.com/ethereum/go-ethereum/common"
5+
6+
// FilterQuery represents the parameters to filter logs/events.
7+
type FilterQuery struct {
8+
FromBlock uint64
9+
ToBlock uint64
10+
Topics [][]common.Hash
11+
Addresses []common.Address
12+
}
13+
14+
// Log represents a single log event fetched from the blockchain.
15+
type Log struct {
16+
Address common.Address
17+
Topics []common.Hash
18+
Data []byte
19+
BlockNumber uint64
20+
TxHash common.Hash
21+
Index uint
22+
}

0 commit comments

Comments
 (0)