Skip to content

Commit 774fc55

Browse files
committed
Fixes
1 parent 3eae224 commit 774fc55

24 files changed

+2844
-0
lines changed

sentinel/.changeset/.gitkeep

Whitespace-only changes.

sentinel/.changeset/v1.50.1.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: 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: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// File: blockchain_client_wrapper/seth_wrapper.go
2+
package sentinel
3+
4+
import (
5+
"context"
6+
"math/big"
7+
8+
"github.com/ethereum/go-ethereum"
9+
"github.com/smartcontractkit/chainlink-testing-framework/sentinel/internal"
10+
"github.com/smartcontractkit/chainlink-testing-framework/seth"
11+
)
12+
13+
// SethClientWrapper wraps a Seth client to implement the BlockchainClient interface.
14+
type SethClientWrapper struct {
15+
client *seth.Client
16+
}
17+
18+
// NewSethClientWrapper wraps an existing Seth client.
19+
func NewSethClientWrapper(client *seth.Client) internal.BlockchainClient {
20+
return &SethClientWrapper{client: client}
21+
}
22+
23+
// Ensure SethClientWrapper implements BlockchainClient.
24+
var _ internal.BlockchainClient = (*SethClientWrapper)(nil)
25+
26+
// BlockNumber retrieves the latest block number.
27+
func (s *SethClientWrapper) BlockNumber(ctx context.Context) (uint64, error) {
28+
return s.client.Client.BlockNumber(ctx)
29+
}
30+
31+
// BlockByNumber retrieves a block by number.
32+
func (s *SethClientWrapper) FilterLogs(ctx context.Context, query internal.FilterQuery) ([]internal.Log, error) {
33+
fromBlock := new(big.Int).SetUint64(query.FromBlock)
34+
toBlock := new(big.Int).SetUint64(query.ToBlock)
35+
36+
// Convert internal.FilterQuery to ethereum.FilterQuery
37+
ethQuery := ethereum.FilterQuery{
38+
FromBlock: fromBlock,
39+
ToBlock: toBlock,
40+
Addresses: query.Addresses,
41+
Topics: query.Topics,
42+
}
43+
44+
// Fetch logs using Seth's client
45+
ethLogs, err := s.client.Client.FilterLogs(ctx, ethQuery)
46+
if err != nil {
47+
return nil, err
48+
}
49+
50+
// Convert []types.Log to []internal.Log
51+
internalLogs := make([]internal.Log, len(ethLogs))
52+
for i, ethLog := range ethLogs {
53+
internalLogs[i] = internal.Log{
54+
Address: ethLog.Address,
55+
Topics: ethLog.Topics,
56+
Data: ethLog.Data,
57+
BlockNumber: ethLog.BlockNumber,
58+
TxHash: ethLog.TxHash,
59+
Index: ethLog.Index,
60+
}
61+
}
62+
63+
return internalLogs, nil
64+
}

sentinel/chain_poller/README.md

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) 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: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// File: chain_poller/chain_poller.go
2+
package chain_poller
3+
4+
import (
5+
"context"
6+
"errors"
7+
8+
"github.com/rs/zerolog"
9+
"github.com/smartcontractkit/chainlink-testing-framework/sentinel/internal"
10+
)
11+
12+
// ChainPollerConfig holds the configuration for the ChainPoller.
13+
type ChainPollerConfig struct {
14+
BlockchainClient internal.BlockchainClient
15+
Logger zerolog.Logger
16+
ChainID int64
17+
}
18+
19+
// ChainPoller is responsible for polling logs from the blockchain.
20+
type ChainPoller struct {
21+
Config ChainPollerConfig
22+
}
23+
24+
// NewChainPoller initializes a new ChainPoller.
25+
func NewChainPoller(cfg ChainPollerConfig) (*ChainPoller, error) {
26+
if cfg.BlockchainClient == nil {
27+
return nil, errors.New("blockchain client cannot be nil")
28+
}
29+
if cfg.Logger.GetLevel() == zerolog.NoLevel {
30+
return nil, errors.New("logger cannot be nil")
31+
}
32+
33+
cfg.Logger = cfg.Logger.With().Str("component", "ChainPoller").Logger().With().Int64("ChainID", cfg.ChainID).Logger()
34+
35+
return &ChainPoller{
36+
Config: cfg,
37+
}, nil
38+
}
39+
40+
// Poll fetches logs from the blockchain based on the provided filter queries.
41+
func (cp *ChainPoller) Poll(ctx context.Context, filterQueries []internal.FilterQuery) ([]internal.Log, error) {
42+
var allLogs []internal.Log
43+
44+
for _, query := range filterQueries {
45+
logs, err := cp.Config.BlockchainClient.FilterLogs(ctx, query)
46+
if err != nil {
47+
cp.Config.Logger.Error().Err(err).Interface("query", query).Msg("Failed to filter logs")
48+
continue
49+
}
50+
allLogs = append(allLogs, logs...)
51+
}
52+
53+
return allLogs, nil
54+
}
55+
56+
var _ ChainPollerInterface = (*ChainPoller)(nil)

0 commit comments

Comments
 (0)