Skip to content

Commit 3eae224

Browse files
committed
Added sentinel
1 parent a6f5934 commit 3eae224

24 files changed

+985
-373
lines changed

lib/sentinel/README.md

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# **Sentinel**
2+
3+
## **Overview**
4+
5+
The **Sentinel** is a modular and extensible system for monitoring blockchain events and managing event subscriptions. Sentinel orchestrates multiple `ChainPollerService` instances, each tied to a specific blockchain network, to fetch logs, process events, and notify subscribers.
6+
7+
---
8+
9+
## **Key Features**
10+
11+
- **Multi-Chain Support**: Manage multiple blockchain networks (e.g., Ethereum, Polygon, Arbitrum) concurrently.
12+
- **Event Broadcasting**: Relay blockchain events to subscribers via a thread-safe subscription system.
13+
- **Flexible Subscriptions**: Dynamically subscribe and unsubscribe to events based on addresses and topics.
14+
- **Graceful Lifecycle Management**: Start, stop, and clean up resources across services effortlessly.
15+
16+
---
17+
18+
## **System Architecture**
19+
20+
### **How Components Interact**
21+
22+
```
23+
+----------------+
24+
| Sentinel |
25+
| (Coordinator) |
26+
+----------------+
27+
|
28+
+---------------------------------+--------------------------------+
29+
| | |
30+
+------------+ +-------------------+ +-------------------+
31+
| Chain ID 1 | | Chain ID 2 | | Chain ID 3 |
32+
| (Ethereum) | | (Polygon) | | (Arbitrum) |
33+
+------------+ +-------------------+ +-------------------+
34+
| | |
35+
+----------------+ +-------------------+ +-------------------+
36+
| ChainPollerSvc | | ChainPollerSvc | | ChainPollerSvc |
37+
| (Service 1) | | (Service 2) | | (Service 3) |
38+
+----------------+ +-------------------+ +-------------------+
39+
| | |
40+
+----------------+ +-------------------+ +-------------------+
41+
| ChainPoller | | ChainPoller | | ChainPoller |
42+
| (Log Fetching) | | (Log Fetching) | | (Log Fetching) |
43+
+----------------+ +-------------------+ +-------------------+
44+
| | |
45+
+----------------+ +-------------------+ +-------------------+
46+
| Subscription | | Subscription | | Subscription |
47+
| Manager | | Manager | | Manager |
48+
+----------------+ +-------------------+ +-------------------+
49+
| | |
50+
+----------------+ +-------------------+ +-------------------+
51+
| Blockchain | | Blockchain | | Blockchain |
52+
| (Ethereum) | | (Polygon) | | (Arbitrum) |
53+
+----------------+ +-------------------+ +-------------------+
54+
55+
```
56+
57+
### **Core Components**
58+
1. **Sentinel**:
59+
- Central coordinator managing multiple `ChainPollerService` instances.
60+
- Handles multi-chain subscriptions, lifecycle management, and configuration.
61+
62+
2. **ChainPollerService**:
63+
- Polls blockchain logs and broadcasts events to subscribers.
64+
- Integrates `ChainPoller` and `SubscriptionManager`.
65+
66+
3. **ChainPoller**:
67+
- Fetches logs from blockchain networks based on filter queries.
68+
69+
4. **SubscriptionManager**:
70+
- Tracks subscriptions to blockchain events.
71+
- Broadcasts logs to subscribers.
72+
73+
---
74+
75+
## **Usage**
76+
77+
### **Initialize Sentinel**
78+
Set up a `Sentinel` instance:
79+
```go
80+
import (
81+
"github.com/smartcontractkit/chainlink-testing-framework/sentinel"
82+
"github.com/smartcontractkit/chainlink-testing-framework/sentinel/chain_poller_service"
83+
)
84+
85+
logger := internal.NewDefaultLogger()
86+
sentinelInstance := sentinel.NewSentinel(sentinel.SentinelConfig{
87+
Logger: logger,
88+
})
89+
```
90+
91+
### **Add a Chain**
92+
Add a blockchain to monitor:
93+
```go
94+
config := chain_poller_service.ChainPollerServiceConfig{
95+
PollInterval: 100 * time.Millisecond,
96+
ChainPoller: chainPoller,
97+
Logger: logger,
98+
BlockchainClient: client,
99+
ChainID: 1,
100+
}
101+
102+
err := sentinelInstance.AddChain(config)
103+
if err != nil {
104+
panic("Failed to add chain: " + err.Error())
105+
}
106+
```
107+
108+
### **Subscribe to Events**
109+
Subscribe to blockchain events:
110+
```go
111+
address := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678")
112+
topic := common.HexToHash("0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef")
113+
114+
logCh, err := sentinelInstance.Subscribe(1, address, topic)
115+
if err != nil {
116+
panic("Failed to subscribe: " + err.Error())
117+
}
118+
119+
// Listen for logs
120+
go func() {
121+
for log := range logCh {
122+
fmt.Println("Received log:", log)
123+
}
124+
}()
125+
```
126+
127+
### **Unsubscribe**
128+
Unsubscribe from events:
129+
```go
130+
err = sentinelInstance.Unsubscribe(1, address, topic, logCh)
131+
if err != nil {
132+
panic("Failed to unsubscribe: " + err.Error())
133+
}
134+
```
135+
136+
### **Remove a Chain**
137+
Remove a blockchain from monitoring:
138+
```go
139+
err = sentinelInstance.RemoveChain(1)
140+
if err != nil {
141+
panic("Failed to remove chain: " + err.Error())
142+
}
143+
```
144+
145+
---
146+
147+
## **Testing**
148+
149+
### **Run Tests**
150+
Run tests for Sentinel and its components:
151+
```bash
152+
go test ./sentinel
153+
```
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# **Chain Poller**
2+
3+
## **Overview**
4+
5+
The **Chain Poller** is a lightweight utility in the [Sentinel](https://github.com/smartcontractkit/sentinel/lib) framework designed to fetch blockchain logs based on filter queries. It serves as a bridge between the blockchain client and higher-level services like the `ChainPollerService`.
6+
7+
---
8+
9+
## **Features**
10+
11+
- **Flexible Queries**: Fetch logs based on block ranges, addresses, and topics.
12+
- **Error Logging**: Captures errors during polling for troubleshooting.
13+
14+
---
15+
16+
## **Usage**
17+
18+
### **Initialization**
19+
Create a new `ChainPoller` with the required configuration:
20+
```go
21+
config := chain_poller.ChainPollerConfig{
22+
BlockchainClient: client,
23+
Logger: logger,
24+
ChainID: 1,
25+
}
26+
27+
chainPoller, err := chain_poller.NewChainPoller(config)
28+
if err != nil {
29+
panic("Failed to initialize Chain Poller: " + err.Error())
30+
}
31+
```
32+
33+
### **Polling**
34+
Fetch logs using filter queries:
35+
```go
36+
queries := []internal.FilterQuery{
37+
{
38+
FromBlock: 100,
39+
ToBlock: 200,
40+
Addresses: []common.Address{
41+
common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678"),
42+
},
43+
Topics: [][]common.Hash{
44+
{common.HexToHash("0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef")},
45+
},
46+
},
47+
}
48+
49+
logs, err := chainPoller.Poll(context.Background(), queries)
50+
if err != nil {
51+
logger.Error("Failed to fetch logs", "error", err)
52+
}
53+
fmt.Println("Fetched logs:", logs)
54+
```
55+
56+
---
57+
58+
## **API Reference**
59+
60+
### **`NewChainPoller(config ChainPollerConfig) (*ChainPoller, error)`**
61+
- Initializes a new Chain Poller with the specified blockchain client and logger.
62+
63+
### **`Poll(ctx context.Context, filterQueries []FilterQuery) ([]Log, error)`**
64+
- Fetches logs from the blockchain based on the given filter queries.
65+
66+
---
67+
68+
## **Testing**
69+
70+
Run the tests:
71+
```bash
72+
go test ./chain_poller
73+
```
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import (
55
"context"
66
"errors"
77

8-
"github.com/smartcontractkit/chainlink-testing-framework/sentinel/internal"
8+
"github.com/smartcontractkit/chainlink-testing-framework/lib/sentinel/internal"
99
)
1010

1111
// ChainPollerConfig holds the configuration for the ChainPoller.
1212
type ChainPollerConfig struct {
13-
BlockchainClient BlockchainClient
13+
BlockchainClient internal.BlockchainClient
1414
Logger internal.Logger
1515
ChainID int64
1616
}

sentinel/chain_poller/chain_poller_test.go renamed to lib/sentinel/chain_poller/chain_poller_test.go

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,12 @@ import (
1111
"github.com/stretchr/testify/mock"
1212
"github.com/stretchr/testify/require"
1313

14-
"github.com/smartcontractkit/chainlink-testing-framework/sentinel/chain_poller"
15-
"github.com/smartcontractkit/chainlink-testing-framework/sentinel/internal"
14+
"github.com/smartcontractkit/chainlink-testing-framework/lib/sentinel/chain_poller"
15+
"github.com/smartcontractkit/chainlink-testing-framework/lib/sentinel/internal"
1616
)
1717

18-
// MockBlockchainClient implements the BlockchainClient interface for testing.
19-
type MockBlockchainClient struct {
20-
mock.Mock
21-
}
22-
23-
func (m *MockBlockchainClient) BlockNumber(ctx context.Context) (uint64, error) {
24-
args := m.Called(ctx)
25-
return args.Get(0).(uint64), args.Error(1)
26-
}
27-
28-
func (m *MockBlockchainClient) FilterLogs(ctx context.Context, query internal.FilterQuery) ([]internal.Log, error) {
29-
args := m.Called(ctx, query)
30-
return args.Get(0).([]internal.Log), args.Error(1)
31-
}
32-
3318
func TestNewChainPoller_Success(t *testing.T) {
34-
mockClient := new(MockBlockchainClient)
19+
mockClient := new(internal.MockBlockchainClient)
3520
mockLogger := internal.NewMockLogger()
3621

3722
config := chain_poller.ChainPollerConfig{
@@ -61,7 +46,7 @@ func TestNewChainPoller_NilBlockchainClient(t *testing.T) {
6146
}
6247

6348
func TestNewChainPoller_NilLogger(t *testing.T) {
64-
mockClient := new(MockBlockchainClient)
49+
mockClient := new(internal.MockBlockchainClient)
6550

6651
config := chain_poller.ChainPollerConfig{
6752
BlockchainClient: mockClient,
@@ -76,7 +61,7 @@ func TestNewChainPoller_NilLogger(t *testing.T) {
7661
}
7762

7863
func TestChainPoller_Poll_SingleFilterQueryWithLogs(t *testing.T) {
79-
mockClient := new(MockBlockchainClient)
64+
mockClient := new(internal.MockBlockchainClient)
8065
mockLogger := internal.NewMockLogger()
8166

8267
config := chain_poller.ChainPollerConfig{
@@ -137,7 +122,7 @@ func TestChainPoller_Poll_SingleFilterQueryWithLogs(t *testing.T) {
137122
}
138123

139124
func TestChainPoller_Poll_MultipleFilterQueries(t *testing.T) {
140-
mockClient := new(MockBlockchainClient)
125+
mockClient := new(internal.MockBlockchainClient)
141126
mockLogger := internal.NewMockLogger()
142127

143128
config := chain_poller.ChainPollerConfig{
@@ -218,7 +203,7 @@ func TestChainPoller_Poll_MultipleFilterQueries(t *testing.T) {
218203
}
219204

220205
func TestChainPoller_Poll_NoLogs(t *testing.T) {
221-
mockClient := new(MockBlockchainClient)
206+
mockClient := new(internal.MockBlockchainClient)
222207
mockLogger := internal.NewMockLogger()
223208

224209
config := chain_poller.ChainPollerConfig{
@@ -258,7 +243,7 @@ func TestChainPoller_Poll_NoLogs(t *testing.T) {
258243
}
259244

260245
func TestChainPoller_Poll_FilterLogsError(t *testing.T) {
261-
mockClient := new(MockBlockchainClient)
246+
mockClient := new(internal.MockBlockchainClient)
262247
mockLogger := internal.NewMockLogger()
263248

264249
config := chain_poller.ChainPollerConfig{
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// File: chain_poller/interface.go
2+
package chain_poller
3+
4+
import (
5+
"context"
6+
7+
"github.com/smartcontractkit/chainlink-testing-framework/lib/sentinel/internal"
8+
)
9+
10+
// ChainPollerInterface defines the methods that ChainPoller must implement.
11+
type ChainPollerInterface interface {
12+
Poll(ctx context.Context, filterQueries []internal.FilterQuery) ([]internal.Log, error)
13+
}

0 commit comments

Comments
 (0)