@@ -28,7 +28,7 @@ use dapi_grpc::platform::v0::subscribe_to_state_transitions_response::Version;
2828use dapi_grpc:: platform:: v0:: { SubscribeToStateTransitionsRequest , SubscribeToStateTransitionsResponse } ;
2929use dapi_grpc:: tonic:: { Code , Status , Streaming } ;
3030use dash_platform_queries:: subscriptions:: {
31- subscribe_request, ResolvedFilters , StateTransitionFilter , MAX_FILTERS ,
31+ canonical_operands , subscribe_request, ResolvedFilters , StateTransitionFilter , MAX_FILTERS ,
3232} ;
3333use dpp:: dashcore:: hashes:: { sha256, Hash } ;
3434use dpp:: data_contract:: DataContract ;
@@ -37,7 +37,7 @@ use dpp::serialization::PlatformDeserializableUntrusted;
3737use dpp:: state_transition:: StateTransition ;
3838use dpp:: version:: PlatformVersion ;
3939use rs_dapi_client:: transport:: { sleep, TransportError } ;
40- use rs_dapi_client:: { DapiClientError , DapiRequestExecutor , IntoInner , RequestSettings } ;
40+ use rs_dapi_client:: { Address , DapiClientError , DapiRequestExecutor , RequestSettings } ;
4141use std:: collections:: { BTreeMap , BTreeSet } ;
4242use std:: sync:: Arc ;
4343use std:: time:: Duration ;
@@ -93,6 +93,8 @@ pub struct StateTransitionSubscription {
9393 stream : Option < Streaming < SubscribeToStateTransitionsResponse > > ,
9494 /// Messages received on the current stream.
9595 stream_messages : u32 ,
96+ /// The node serving the current stream.
97+ stream_address : Option < Address > ,
9698 /// Where the next stream starts; `None` until the first checkpoint of a live-only start.
9799 resume_from : Option < u64 > ,
98100 consecutive_failures : u32 ,
@@ -142,6 +144,14 @@ impl Sdk {
142144 data_contract_ids : data_contract_ids. into_iter ( ) . collect ( ) ,
143145 } ) ;
144146 }
147+ // Send operands in the form the schema stores them, so the node reads what was meant.
148+ let filters = filters
149+ . iter ( )
150+ . map ( |filter| {
151+ canonical_operands ( filter, |id| contracts. get ( id) . cloned ( ) , self . version ( ) )
152+ } )
153+ . collect :: < Result < Vec < _ > , _ > > ( )
154+ . map_err ( Error :: Config ) ?;
145155 let resolved = ResolvedFilters :: resolve (
146156 filters. clone ( ) ,
147157 |id| contracts. get ( id) . cloned ( ) ,
@@ -156,6 +166,7 @@ impl Sdk {
156166 resolved,
157167 stream : None ,
158168 stream_messages : 0 ,
169+ stream_address : None ,
159170 resume_from : from_block_height,
160171 consecutive_failures : 0 ,
161172 contracts_to_refresh : BTreeSet :: new ( ) ,
@@ -201,6 +212,19 @@ impl StateTransitionSubscription {
201212 } ;
202213 if let Some ( status) = & failure {
203214 tracing:: debug!( code = ?status. code( ) , message = status. message( ) , "state transition stream broke; resuming" ) ;
215+ // Resume elsewhere when the node itself is at fault, rather than reconnecting
216+ // to it; a stream ended by its lifetime or the client says nothing of the node.
217+ if node_at_fault ( status) {
218+ if let Some ( address) = & self . stream_address {
219+ self . sdk . address_list ( ) . ban_with_reason (
220+ address,
221+ Some ( format ! (
222+ "state transition stream failed: {}" ,
223+ status. message( )
224+ ) ) ,
225+ ) ;
226+ }
227+ }
204228 }
205229 self . stream = None ;
206230 self . consecutive_failures += 1 ;
@@ -231,14 +255,16 @@ impl StateTransitionSubscription {
231255 } )
232256 }
233257
234- async fn open ( & self ) -> Result < Streaming < SubscribeToStateTransitionsResponse > , Error > {
258+ async fn open ( & mut self ) -> Result < Streaming < SubscribeToStateTransitionsResponse > , Error > {
235259 let request: SubscribeToStateTransitionsRequest =
236260 subscribe_request ( & self . filters , self . resume_from ) ?;
237- self . sdk
238- . execute ( request, RequestSettings :: default ( ) )
239- . await
240- . into_inner ( )
241- . map_err ( Error :: from)
261+ match self . sdk . execute ( request, RequestSettings :: default ( ) ) . await {
262+ Ok ( response) => {
263+ self . stream_address = Some ( response. address ) ;
264+ Ok ( response. inner )
265+ }
266+ Err ( error) => Err ( Error :: from ( error. inner ) ) ,
267+ }
242268 }
243269
244270 /// Open a new stream after a break, backing off after consecutive failures.
@@ -368,6 +394,20 @@ impl StateTransitionSubscription {
368394 }
369395}
370396
397+ /// Whether a stream failure says the serving node cannot serve this subscription (it lacks
398+ /// history, cannot read or decode a block, or Tenderdash is unreachable), so another node
399+ /// should be tried; not an ended stream lifetime, a cancellation or a slow client.
400+ fn node_at_fault ( status : & Status ) -> bool {
401+ matches ! (
402+ status. code( ) ,
403+ Code :: OutOfRange
404+ | Code :: FailedPrecondition
405+ | Code :: Unavailable
406+ | Code :: Internal
407+ | Code :: Unknown
408+ )
409+ }
410+
371411/// Whether a stream that failed with `status` can continue on a new stream.
372412fn resumable ( status : & Status ) -> bool {
373413 matches ! (
@@ -429,6 +469,7 @@ mod tests {
429469 resolved,
430470 stream : None ,
431471 stream_messages : 0 ,
472+ stream_address : None ,
432473 resume_from : None ,
433474 consecutive_failures : 0 ,
434475 contracts_to_refresh : BTreeSet :: new ( ) ,
@@ -751,4 +792,25 @@ mod tests {
751792 . expect( "skipped, not failed" )
752793 . is_none( ) ) ;
753794 }
795+
796+ #[ test]
797+ fn should_move_off_a_node_only_when_it_is_at_fault ( ) {
798+ for code in [
799+ Code :: OutOfRange ,
800+ Code :: FailedPrecondition ,
801+ Code :: Unavailable ,
802+ Code :: Internal ,
803+ ] {
804+ assert ! ( node_at_fault( & Status :: new( code, "" ) ) , "{code:?}" ) ;
805+ }
806+ // An ended lifetime, a cancellation or a slow client is not the node's fault.
807+ for code in [
808+ Code :: DeadlineExceeded ,
809+ Code :: Cancelled ,
810+ Code :: ResourceExhausted ,
811+ Code :: Aborted ,
812+ ] {
813+ assert ! ( !node_at_fault( & Status :: new( code, "" ) ) , "{code:?}" ) ;
814+ }
815+ }
754816}
0 commit comments