1
1
use crate :: config:: { CHANNEL_CAPACITY , Config } ;
2
+ use crate :: relayer_session:: RelayerSender ;
2
3
use anyhow:: { Context , Result , bail} ;
3
4
use ed25519_dalek:: { Signer , SigningKey } ;
4
- use futures_util:: stream:: { SplitSink , SplitStream } ;
5
- use futures_util:: { SinkExt , StreamExt } ;
6
- use http:: HeaderValue ;
7
5
use protobuf:: well_known_types:: timestamp:: Timestamp ;
8
6
use protobuf:: { Message , MessageField } ;
9
7
use pyth_lazer_publisher_sdk:: publisher_update:: { FeedUpdate , PublisherUpdate } ;
@@ -13,93 +11,34 @@ use pyth_lazer_publisher_sdk::transaction::{
13
11
Ed25519SignatureData , LazerTransaction , SignatureData , SignedLazerTransaction ,
14
12
} ;
15
13
use solana_keypair:: read_keypair_file;
16
- use std:: time:: Duration ;
17
- use tokio:: net:: TcpStream ;
18
14
use tokio:: {
19
15
select,
20
16
sync:: mpsc:: { self , Receiver , Sender } ,
21
17
time:: interval,
22
18
} ;
23
- use tokio_stream:: StreamMap ;
24
- use tokio_tungstenite:: tungstenite:: client:: IntoClientRequest ;
25
- use tokio_tungstenite:: {
26
- MaybeTlsStream , WebSocketStream , connect_async_with_config,
27
- tungstenite:: Message as TungsteniteMessage ,
28
- } ;
29
- use tracing:: { error, instrument} ;
30
- use url:: Url ;
31
-
32
- struct RelayerSender {
33
- ws_senders : Vec < SplitSink < WebSocketStream < MaybeTlsStream < TcpStream > > , TungsteniteMessage > > ,
34
- }
19
+ use tracing:: error;
35
20
36
- impl RelayerSender {
37
- async fn send_price_update (
38
- & mut self ,
39
- signed_lazer_transaction : & SignedLazerTransaction ,
40
- ) -> Result < ( ) > {
41
- tracing:: debug!( "price_update: {:?}" , signed_lazer_transaction) ;
42
- let buf = signed_lazer_transaction. write_to_bytes ( ) ?;
43
- for sender in self . ws_senders . iter_mut ( ) {
44
- sender. send ( TungsteniteMessage :: from ( buf. clone ( ) ) ) . await ?;
45
- sender. flush ( ) . await ?;
46
- }
47
- Ok ( ( ) )
48
- }
49
- }
50
-
51
- async fn connect_to_relayer (
52
- mut url : Url ,
53
- token : & str ,
54
- ) -> Result < (
55
- SplitSink < WebSocketStream < MaybeTlsStream < TcpStream > > , TungsteniteMessage > ,
56
- SplitStream < WebSocketStream < MaybeTlsStream < TcpStream > > > ,
57
- ) > {
58
- tracing:: info!( "connecting to the relayer at {}" , url) ;
59
- url. set_path ( "/v1/transaction" ) ;
60
- let mut req = url. clone ( ) . into_client_request ( ) ?;
61
- let headers = req. headers_mut ( ) ;
62
- headers. insert (
63
- "Authorization" ,
64
- HeaderValue :: from_str ( & format ! ( "Bearer {}" , token) ) ?,
65
- ) ;
66
- let ( ws_stream, _) = connect_async_with_config ( req, None , true ) . await ?;
67
- Ok ( ws_stream. split ( ) )
68
- }
69
-
70
- async fn connect_to_relayers (
71
- config : & Config ,
72
- ) -> Result < (
73
- RelayerSender ,
74
- Vec < SplitStream < WebSocketStream < MaybeTlsStream < TcpStream > > > > ,
75
- ) > {
76
- let mut relayer_senders = Vec :: new ( ) ;
77
- let mut relayer_receivers = Vec :: new ( ) ;
78
- for url in config. relayer_urls . clone ( ) {
79
- let ( relayer_sender, relayer_receiver) =
80
- connect_to_relayer ( url, & config. authorization_token ) . await ?;
81
- relayer_senders. push ( relayer_sender) ;
82
- relayer_receivers. push ( relayer_receiver) ;
83
- }
84
- let sender = RelayerSender {
85
- ws_senders : relayer_senders,
86
- } ;
87
- tracing:: info!( "connected to relayers: {:?}" , config. relayer_urls) ;
88
- Ok ( ( sender, relayer_receivers) )
89
- }
90
-
91
- #[ derive( Debug , Clone ) ]
21
+ #[ derive( Clone ) ]
92
22
pub struct LazerPublisher {
93
23
sender : Sender < FeedUpdate > ,
94
24
}
95
25
96
26
impl LazerPublisher {
97
27
pub async fn new ( config : & Config ) -> Self {
28
+ let relayer_senders = futures:: future:: join_all (
29
+ config
30
+ . relayer_urls
31
+ . iter ( )
32
+ . map ( async |url| RelayerSender :: new ( url, & config. authorization_token ) . await ) ,
33
+ )
34
+ . await ;
35
+
98
36
let ( sender, receiver) = mpsc:: channel ( CHANNEL_CAPACITY ) ;
99
37
let mut task = LazerPublisherTask {
100
38
config : config. clone ( ) ,
101
39
receiver,
102
40
pending_updates : Vec :: new ( ) ,
41
+ relayer_senders,
103
42
} ;
104
43
tokio:: spawn ( async move { task. run ( ) . await } ) ;
105
44
Self { sender }
@@ -116,43 +55,11 @@ struct LazerPublisherTask {
116
55
config : Config ,
117
56
receiver : Receiver < FeedUpdate > ,
118
57
pending_updates : Vec < FeedUpdate > ,
58
+ relayer_senders : Vec < RelayerSender > ,
119
59
}
120
60
121
61
impl LazerPublisherTask {
122
- pub async fn run ( & mut self ) {
123
- let mut failure_count = 0 ;
124
- let retry_duration = Duration :: from_secs ( 1 ) ;
125
-
126
- loop {
127
- match self . run_relayer_connection ( ) . await {
128
- Ok ( ( ) ) => {
129
- tracing:: info!( "lazer_publisher graceful shutdown" ) ;
130
- return ;
131
- }
132
- Err ( e) => {
133
- failure_count += 1 ;
134
- tracing:: error!(
135
- "lazer_publisher failed with error: {:?}, failure_count: {}; retrying in {:?}" ,
136
- e,
137
- failure_count,
138
- retry_duration
139
- ) ;
140
- tokio:: time:: sleep ( retry_duration) . await ;
141
- }
142
- }
143
- }
144
- }
145
-
146
- #[ instrument( skip( self ) , fields( component = "lazer_publisher" ) ) ]
147
- pub async fn run_relayer_connection ( & mut self ) -> Result < ( ) > {
148
- // Establish relayer connections
149
- // Relayer will drop the connection if no data received in 5s
150
- let ( mut relayer_sender, relayer_receivers) = connect_to_relayers ( & self . config ) . await ?;
151
- let mut stream_map = StreamMap :: new ( ) ;
152
- for ( i, receiver) in relayer_receivers. into_iter ( ) . enumerate ( ) {
153
- stream_map. insert ( self . config . relayer_urls [ i] . clone ( ) , receiver) ;
154
- }
155
-
62
+ fn load_signing_key ( & self ) -> Result < SigningKey > {
156
63
// Read the keypair from the file using Solana SDK because it's the same key used by the Pythnet publisher
157
64
let publish_keypair = match read_keypair_file ( & self . config . publish_keypair_path ) {
158
65
Ok ( k) => k,
@@ -162,12 +69,23 @@ impl LazerPublisherTask {
162
69
publish_keypair_path = self . config. publish_keypair_path. display( ) . to_string( ) ,
163
70
"Reading publish keypair returned an error. " ,
164
71
) ;
165
- bail ! ( "Reading publish keypair returned an error. " ) ;
72
+ bail ! ( "Reading publish keypair returned an error." ) ;
166
73
}
167
74
} ;
168
75
169
- let signing_key = SigningKey :: from_keypair_bytes ( & publish_keypair. to_bytes ( ) )
170
- . context ( "Failed to create signing key from keypair" ) ?;
76
+ SigningKey :: from_keypair_bytes ( & publish_keypair. to_bytes ( ) )
77
+ . context ( "Failed to create signing key from keypair" )
78
+ }
79
+
80
+ pub async fn run ( & mut self ) {
81
+ let signing_key = match self . load_signing_key ( ) {
82
+ Ok ( signing_key) => signing_key,
83
+ Err ( e) => {
84
+ tracing:: error!( "Failed to load signing key: {e:?}" ) ;
85
+ // Can't proceed on key failure
86
+ panic ! ( "Failed to load signing key: {e:?}" ) ;
87
+ }
88
+ } ;
171
89
172
90
let mut publish_interval = interval ( self . config . publish_interval_duration ) ;
173
91
loop {
@@ -176,34 +94,15 @@ impl LazerPublisherTask {
176
94
self . pending_updates. push( feed_update) ;
177
95
}
178
96
_ = publish_interval. tick( ) => {
179
- if let Err ( err) = self . publish ( & signing_key, & mut relayer_sender ) . await {
97
+ if let Err ( err) = self . batch_transaction ( & signing_key) . await {
180
98
error!( "Failed to publish updates: {}" , err) ;
181
99
}
182
100
}
183
- // Handle messages from the relayers, such as errors if we send a bad update
184
- mapped_msg = stream_map. next( ) => {
185
- match mapped_msg {
186
- Some ( ( relayer_url, Ok ( msg) ) ) => {
187
- tracing:: debug!( "Received message from relayer at {relayer_url}: {msg:?}" ) ;
188
- }
189
- Some ( ( relayer_url, Err ( e) ) ) => {
190
- tracing:: error!( "Error receiving message from at relayer {relayer_url}: {e:?}" ) ;
191
- }
192
- None => {
193
- tracing:: error!( "relayer connection closed" ) ;
194
- bail!( "relayer connection closed" ) ;
195
- }
196
- }
197
- }
198
101
}
199
102
}
200
103
}
201
104
202
- async fn publish (
203
- & mut self ,
204
- signing_key : & SigningKey ,
205
- relayer_sender : & mut RelayerSender ,
206
- ) -> Result < ( ) > {
105
+ async fn batch_transaction ( & mut self , signing_key : & SigningKey ) -> Result < ( ) > {
207
106
if self . pending_updates . is_empty ( ) {
208
107
return Ok ( ( ) ) ;
209
108
}
@@ -238,12 +137,14 @@ impl LazerPublisherTask {
238
137
payload : Some ( buf) ,
239
138
special_fields : Default :: default ( ) ,
240
139
} ;
241
- if let Err ( e) = relayer_sender
242
- . send_price_update ( & signed_lazer_transaction)
243
- . await
244
- {
245
- tracing:: error!( "Error publishing update to Lazer relayer: {e:?}" ) ;
246
- bail ! ( "Failed to publish update to Lazer relayer: {e:?}" ) ;
140
+ for relayer_sender in self . relayer_senders . iter ( ) {
141
+ if let Err ( e) = relayer_sender
142
+ . sender
143
+ . send ( signed_lazer_transaction. clone ( ) )
144
+ . await
145
+ {
146
+ error ! ( "Error sending transaction to Lazer relayer session: {e:?}" ) ;
147
+ }
247
148
}
248
149
249
150
self . pending_updates . clear ( ) ;
0 commit comments