11use clap:: Parser ;
22use cli_ext:: OracleExt ;
33use exex:: ExEx ;
4- use futures:: FutureExt ;
4+ use futures:: { FutureExt , Stream } ;
55use network:: { proto:: OracleProtoHandler , OracleNetwork } ;
6- use offchain_data:: DataFeederStream ;
6+ use offchain_data:: { DataFeederError , DataFeederStream , DataFeeds } ;
77use oracle:: Oracle ;
88use reth:: chainspec:: EthereumChainSpecParser ;
9+ use reth_exex:: ExExContext ;
910use reth_network:: { protocol:: IntoRlpxSubProtocol , NetworkProtocols } ;
11+ use reth_node_api:: FullNodeComponents ;
1012use reth_node_ethereum:: EthereumNode ;
1113
1214mod cli_ext;
@@ -17,6 +19,35 @@ mod oracle;
1719
1820const ORACLE_EXEX_ID : & str = "exex-oracle" ;
1921
22+ /// Helper function to start the oracle stack.
23+ async fn start < Node : FullNodeComponents , D > (
24+ ctx : ExExContext < Node > ,
25+ tcp_port : u16 ,
26+ udp_port : u16 ,
27+ data_feeder : D ,
28+ ) -> eyre:: Result < ( Oracle < Node , D > , Node :: Network ) >
29+ where
30+ Node :: Network : NetworkProtocols ,
31+ D : Stream < Item = Result < DataFeeds , DataFeederError > > + Send + ' static ,
32+ {
33+ // Define the oracle subprotocol
34+ let ( subproto, proto_events, to_peers) = OracleProtoHandler :: new ( ) ;
35+ // Add it to the network as a subprotocol
36+ let net = ctx. network ( ) . clone ( ) ;
37+ net. add_rlpx_sub_protocol ( subproto. into_rlpx_sub_protocol ( ) ) ;
38+
39+ // The instance of the execution extension that will handle chain events
40+ let exex = ExEx :: new ( ctx) ;
41+
42+ // The instance of the oracle network that will handle discovery and gossiping of data
43+ let network = OracleNetwork :: new ( proto_events, tcp_port, udp_port) . await ?;
44+
45+ // The oracle instance that will orchestrate the network, the execution extensions,
46+ // the off-chain data stream, and the gossiping
47+ let oracle = Oracle :: new ( exex, network, data_feeder, to_peers) ;
48+ Ok ( ( oracle, net. clone ( ) ) )
49+ }
50+
2051fn main ( ) -> eyre:: Result < ( ) > {
2152 reth:: cli:: Cli :: < EthereumChainSpecParser , OracleExt > :: parse ( ) . run ( |builder, args| async move {
2253 let tcp_port = args. tcp_port ;
@@ -36,26 +67,8 @@ fn main() -> eyre::Result<()> {
3667 // Source: https://github.com/vados-cosmonic/wasmCloud/commit/440e8c377f6b02f45eacb02692e4d2fabd53a0ec
3768 tokio:: task:: spawn_blocking ( move || {
3869 tokio:: runtime:: Handle :: current ( ) . block_on ( async move {
39- // define the oracle subprotocol
40- let ( subproto, proto_events, to_peers) = OracleProtoHandler :: new ( ) ;
41- // add it to the network as a subprotocol
42- ctx. network ( ) . add_rlpx_sub_protocol ( subproto. into_rlpx_sub_protocol ( ) ) ;
43-
44- // the instance of the execution extension that will handle chain events
45- let exex = ExEx :: new ( ctx) ;
46-
47- // the instance of the oracle network that will handle discovery and
48- // gossiping of data
49- let network = OracleNetwork :: new ( proto_events, tcp_port, udp_port) . await ?;
50- // the off-chain data feed stream
5170 let data_feed = DataFeederStream :: new ( args. binance_symbols ) . await ?;
52-
53- // the oracle instance that will orchestrate the network, the execution
54- // extensions, the offchain data stream and the
55- // gossiping the oracle will always sign and
56- // broadcast data via the channel until a peer is
57- // subcribed to it
58- let oracle = Oracle :: new ( exex, network, data_feed, to_peers) ;
71+ let ( oracle, _net) = start ( ctx, tcp_port, udp_port, data_feed) . await ?;
5972 Ok ( oracle)
6073 } )
6174 } )
@@ -67,3 +80,108 @@ fn main() -> eyre::Result<()> {
6780 handle. wait_for_node_exit ( ) . await
6881 } )
6982}
83+
84+ #[ cfg( test) ]
85+ mod tests {
86+ use crate :: { offchain_data:: binance:: ticker:: Ticker , start} ;
87+ use futures:: { Stream , StreamExt } ;
88+ use reth_exex_test_utils:: test_exex_context;
89+ use reth_network:: { NetworkEvent , NetworkEventListenerProvider , NetworkInfo , Peers } ;
90+ use reth_network_api:: PeerId ;
91+ use reth_tokio_util:: EventStream ;
92+ use reth_tracing:: tracing:: info;
93+ use tokio_stream:: wrappers:: BroadcastStream ;
94+
95+ async fn wait_for_session ( mut events : EventStream < NetworkEvent > ) -> PeerId {
96+ while let Some ( event) = events. next ( ) . await {
97+ if let NetworkEvent :: SessionEstablished { peer_id, .. } = event {
98+ info ! ( "Session established with {}" , peer_id) ;
99+ return peer_id;
100+ }
101+ info ! ( "Unexpected event: {:?}" , event) ;
102+ }
103+
104+ unreachable ! ( )
105+ }
106+
107+ use crate :: offchain_data:: { DataFeederError , DataFeeds } ;
108+ use futures:: stream:: { self } ;
109+ use std:: pin:: Pin ;
110+
111+ fn mock_stream ( ) -> Pin < Box < dyn Stream < Item = Result < DataFeeds , DataFeederError > > + Send > > {
112+ let ticker = Ticker {
113+ event_type : "24hrTicker" . to_string ( ) ,
114+ event_time : 1698323450000 ,
115+ symbol : "BTCUSDT" . to_string ( ) ,
116+ price_change : "100.00" . to_string ( ) ,
117+ price_change_percent : "2.5" . to_string ( ) ,
118+ weighted_avg_price : "40200.00" . to_string ( ) ,
119+ prev_close_price : "40000.00" . to_string ( ) ,
120+ last_price : "40100.00" . to_string ( ) ,
121+ last_quantity : "0.5" . to_string ( ) ,
122+ best_bid_price : "40095.00" . to_string ( ) ,
123+ best_bid_quantity : "1.0" . to_string ( ) ,
124+ best_ask_price : "40105.00" . to_string ( ) ,
125+ best_ask_quantity : "1.0" . to_string ( ) ,
126+ open_price : "39900.00" . to_string ( ) ,
127+ high_price : "40500.00" . to_string ( ) ,
128+ low_price : "39800.00" . to_string ( ) ,
129+ volume : "1500" . to_string ( ) ,
130+ quote_volume : "60000000" . to_string ( ) ,
131+ open_time : 1698237050000 ,
132+ close_time : 1698323450000 ,
133+ first_trade_id : 1 ,
134+ last_trade_id : 2000 ,
135+ num_trades : 2000 ,
136+ } ;
137+
138+ // Wrap the Ticker in DataFeeds::Binance
139+ let data_feed = DataFeeds :: Binance ( ticker) ;
140+
141+ // Create a stream that sends a single item and then ends, boxed and pinned
142+ Box :: pin ( stream:: once ( async { Ok ( data_feed) } ) )
143+ }
144+
145+ #[ tokio:: test]
146+ async fn e2e_oracles ( ) {
147+ reth_tracing:: init_test_tracing ( ) ;
148+
149+ // spawn first instance
150+ let ( ctx_1, _handle) = test_exex_context ( ) . await . unwrap ( ) ;
151+ let data_feed1 = mock_stream ( ) ;
152+ let ( oracle_1, network_1) = start ( ctx_1, 30303 , 30304 , data_feed1) . await . unwrap ( ) ;
153+ let mut broadcast_stream_1 = BroadcastStream :: new ( oracle_1. signed_ticks ( ) . subscribe ( ) ) ;
154+ let signer_1 = oracle_1. signer ( ) . address ( ) ;
155+ tokio:: spawn ( oracle_1) ;
156+ let net_1_events = network_1. event_listener ( ) ;
157+
158+ // spawn second instance
159+ let ( ctx_2, _handle) = test_exex_context ( ) . await . unwrap ( ) ;
160+ let data_feed2 = mock_stream ( ) ;
161+ let ( oracle_2, network_2) = start ( ctx_2, 30305 , 30306 , data_feed2) . await . unwrap ( ) ;
162+ let mut broadcast_stream_2 = BroadcastStream :: new ( oracle_2. signed_ticks ( ) . subscribe ( ) ) ;
163+ let signer_2 = oracle_2. signer ( ) . address ( ) ;
164+ tokio:: spawn ( oracle_2) ;
165+ let net_2_events = network_2. event_listener ( ) ;
166+
167+ // expect peers connected
168+ let ( peer_2, addr_2) = ( network_2. peer_id ( ) , network_2. local_addr ( ) ) ;
169+ network_1. add_peer ( * peer_2, addr_2) ;
170+ let expected_peer_2 = wait_for_session ( net_1_events) . await ;
171+ assert_eq ! ( expected_peer_2, * peer_2) ;
172+
173+ let ( peer_1, addr_1) = ( network_1. peer_id ( ) , network_1. local_addr ( ) ) ;
174+ network_2. add_peer ( * peer_1, addr_1) ;
175+ let expected_peer_1 = wait_for_session ( net_2_events) . await ;
176+ assert_eq ! ( expected_peer_1, * peer_1) ;
177+
178+ // expect signed data
179+ let signed_ticker_1 = broadcast_stream_1. next ( ) . await . unwrap ( ) . unwrap ( ) ;
180+ assert_eq ! ( signed_ticker_1. ticker. symbol, "BTCUSDT" ) ;
181+ assert_eq ! ( signed_ticker_1. signer, signer_1) ;
182+
183+ let signed_ticker_2 = broadcast_stream_2. next ( ) . await . unwrap ( ) . unwrap ( ) ;
184+ assert_eq ! ( signed_ticker_2. ticker. symbol, "BTCUSDT" ) ;
185+ assert_eq ! ( signed_ticker_2. signer, signer_2) ;
186+ }
187+ }
0 commit comments