@@ -4,6 +4,7 @@ use std::cmp::Ordering;
44use enumorph:: Enumorph ;
55use ibc_classic_spec:: IbcClassic ;
66use ibc_union_spec:: { IbcUnion , query:: PacketsByBatchHash } ;
7+ use itertools:: Itertools ;
78use macros:: model;
89use serde_json:: json;
910use tracing:: { debug, info, instrument, warn} ;
@@ -18,7 +19,7 @@ use voyager_sdk::{
1819 primitives:: { ChainId , QueryHeight } ,
1920 rpc:: { RpcError , RpcResult } ,
2021 types:: RawClientId ,
21- vm:: { Op , data, now, promise} ,
22+ vm:: { Op , conc , data, now, promise} ,
2223} ;
2324
2425use crate :: {
5556 module : & Module ,
5657 voyager_client : & VoyagerClient ,
5758 ) -> RpcResult < Op < VoyagerMessage > > {
59+ #[ derive( Debug ) ]
60+ enum TargetHeights {
61+ None ,
62+ Min ( Height ) ,
63+ Exact ( Vec < Height > ) ,
64+ }
65+
5866 let client_state_meta = voyager_client
5967 . client_state_meta :: < V > (
6068 module. chain_id . clone ( ) ,
@@ -71,55 +79,57 @@ where
7179 . query_latest_height ( client_state_meta. counterparty_chain_id . clone ( ) , true )
7280 . await ?;
7381
74- let target_height = self
82+ let target_heights = self
7583 . batches
7684 . iter ( )
7785 . flatten ( )
7886 . map ( |e| e. provable_height )
79- . reduce ( |acc, elem| match ( elem, acc) {
80- ( EventProvableHeight :: Min ( elem) , EventProvableHeight :: Min ( acc) ) => {
81- // the min target height of a batch of `Min` events is the highest min height
82- // given the batch [10, 11, 12]
83- // the min height that all events are provable at is 12
84- EventProvableHeight :: Min ( elem. max ( acc) )
87+ . try_fold ( TargetHeights :: None , |acc, elem| match ( elem, acc) {
88+ ( EventProvableHeight :: Min ( elem) , TargetHeights :: None ) => {
89+ Ok ( TargetHeights :: Min ( elem) )
8590 }
86- ( EventProvableHeight :: Exactly ( elem) , EventProvableHeight :: Exactly ( acc) ) => {
87- assert_eq ! ( elem, acc, "multiple exact heights in the batch" ) ;
88- EventProvableHeight :: Exactly ( elem)
91+ ( EventProvableHeight :: Exactly ( elem) , TargetHeights :: None ) => {
92+ Ok ( TargetHeights :: Exact ( vec ! [ elem] ) )
8993 }
90- tuple => {
91- panic ! ( "cannot mix exact and min provable heights currently (found {tuple:?})" ) ;
94+
95+ ( EventProvableHeight :: Min ( elem) , TargetHeights :: Min ( acc) ) => {
96+ Ok ( TargetHeights :: Min ( elem. max ( acc) ) )
9297 }
93- } )
94- . expect ( "batch has at least one event; qed;" ) ;
95-
96- // at this point we assume that a valid update exists - we only ever enqueue this message behind the relevant WaitForHeight on the counterparty chain. to prevent explosions, we do a sanity check here.
97- {
98- let ( EventProvableHeight :: Min ( target_height)
99- | EventProvableHeight :: Exactly ( target_height) ) = target_height;
100-
101- if latest_height < target_height {
102- // we treat this as a missing state error, since this message assumes the state exists.
103- return Err ( RpcError :: missing_state ( format ! (
104- "the latest height of the counterparty chain ({counterparty_chain_id}) \
105- is {latest_height} and the latest trusted height on the client tracking \
106- it ({client_id}) on this chain ({self_chain_id}) is {trusted_height}. \
107- in order to create an update for this client, we need to wait for the \
108- counterparty chain to progress to the next consensus checkpoint greater \
109- than the required target height {target_height}",
110- counterparty_chain_id = client_state_meta. counterparty_chain_id,
111- trusted_height = client_state_meta. counterparty_height,
112- client_id = self . client_id,
113- self_chain_id = module. chain_id,
114- ) )
115- . with_data ( json ! ( {
116- "current_timestamp" : now( ) ,
117- } ) ) ) ;
118- }
119- }
12098
121- match target_height {
122- EventProvableHeight :: Min ( target_height) => {
99+ ( EventProvableHeight :: Exactly ( elem) , TargetHeights :: Exact ( acc) ) => Ok (
100+ TargetHeights :: Exact ( acc. into_iter ( ) . chain ( [ elem] ) . collect ( ) ) ,
101+ ) ,
102+
103+ ( elem, acc) => Err ( RpcError :: fatal_from_message ( format ! (
104+ "cannot mix exact and min update heights in a \
105+ single instance of this plugin: {elem:?}, {acc:?}"
106+ ) ) ) ,
107+ } ) ?;
108+
109+ let mut ops = vec ! [ ] ;
110+
111+ // TODO: Check the same for exact heights?
112+ match target_heights {
113+ TargetHeights :: Min ( target_height) => {
114+ // at this point we assume that a valid update exists - we only ever enqueue this message behind the relevant WaitForHeight on the counterparty chain. to prevent explosions, we do a sanity check here.
115+ if latest_height < target_height {
116+ // we treat this as a missing state error, since this message assumes the state exists.
117+ return Err ( RpcError :: missing_state ( format ! (
118+ "the latest height of the counterparty chain ({counterparty_chain_id}) \
119+ is {latest_height} and the latest trusted height on the client tracking \
120+ it ({client_id}) on this chain ({self_chain_id}) is {trusted_height}. \
121+ in order to create an update for this client, we need to wait for the \
122+ counterparty chain to progress to the next consensus checkpoint greater \
123+ than the required target height {target_height}",
124+ counterparty_chain_id = client_state_meta. counterparty_chain_id,
125+ trusted_height = client_state_meta. counterparty_height,
126+ client_id = self . client_id,
127+ self_chain_id = module. chain_id,
128+ ) )
129+ . with_data ( json ! ( {
130+ "current_timestamp" : now( ) ,
131+ } ) ) ) ;
132+ }
123133 if client_state_meta. counterparty_height >= target_height {
124134 info ! (
125135 "client {client_id} has already been updated to a height \
@@ -128,28 +138,28 @@ where
128138 client_id = self . client_id,
129139 ) ;
130140
131- make_msgs (
141+ ops . push ( make_msgs (
132142 module,
133143 self . client_id ,
134144 self . batches ,
135145 None ,
136146 client_state_meta. clone ( ) ,
137147 client_state_meta. counterparty_height ,
138- )
148+ ) ? ) ;
139149 } else {
140- Ok ( promise (
150+ ops . push ( promise (
141151 [ call ( FetchUpdateHeaders {
142- client_type : client_info. client_type ,
152+ client_type : client_info. client_type . clone ( ) ,
143153 counterparty_chain_id : module. chain_id . clone ( ) ,
144- chain_id : client_state_meta. counterparty_chain_id ,
154+ chain_id : client_state_meta. counterparty_chain_id . clone ( ) ,
145155 client_id : RawClientId :: new ( self . client_id . clone ( ) ) ,
146156 update_from : client_state_meta. counterparty_height ,
147157 update_to : if latest_height. height ( ) < target_height. height ( ) {
148158 warn ! (
149159 "latest height {latest_height} is less than the target \
150- height {target_height}, there may be something wrong \
151- with the rpc for {} - client {} will be updated to the \
152- target height instead of the latest height",
160+ height {target_height}, there may be something wrong \
161+ with the rpc for {} - client {} will be updated to the \
162+ target height instead of the latest height",
153163 module. chain_id, self . client_id
154164 ) ;
155165 target_height
@@ -162,61 +172,53 @@ where
162172 module. plugin_name ( ) ,
163173 ModuleCallback :: from ( MakeIbcMessagesFromUpdate :: < V > {
164174 client_id : self . client_id . clone ( ) ,
165- batches : self . batches ,
175+ batches : self . batches . clone ( ) ,
166176 } ) ,
167177 ) ,
168- ) )
178+ ) ) ;
169179 }
170180 }
171- EventProvableHeight :: Exactly ( target_height) => {
172- match client_state_meta. counterparty_height . cmp ( & target_height) {
173- Ordering :: Equal => {
174- info ! (
175- "client {client_id} has already been updated to \
176- the desired target height ({} == {target_height})",
177- client_state_meta. counterparty_height,
178- client_id = self . client_id,
179- ) ;
180- make_msgs (
181- module,
182- self . client_id ,
183- self . batches ,
184- None ,
185- client_state_meta. clone ( ) ,
186- client_state_meta. counterparty_height ,
187- )
188- }
189- Ordering :: Less => Ok ( promise (
190- [ call ( FetchUpdateHeaders {
191- client_type : client_info. client_type ,
192- counterparty_chain_id : module. chain_id . clone ( ) ,
193- chain_id : client_state_meta. counterparty_chain_id ,
194- client_id : RawClientId :: new ( self . client_id . clone ( ) ) ,
195- update_from : client_state_meta. counterparty_height ,
196- update_to : target_height,
197- } ) ] ,
198- [ ] ,
199- PluginMessage :: new (
200- module. plugin_name ( ) ,
201- ModuleCallback :: from ( MakeIbcMessagesFromUpdate :: < V > {
202- client_id : self . client_id . clone ( ) ,
203- batches : self . batches ,
204- } ) ,
205- ) ,
206- ) ) ,
207- // update backwards
208- // currently this is only supported in sui, and as such has some baked-in assumptions about the semantics of when this branch is hit
209- Ordering :: Greater => {
210- info ! (
211- "updating client to an earlier height ({} -> {target_height})" ,
212- client_state_meta. counterparty_height
213- ) ;
214-
215- Ok ( promise (
181+ TargetHeights :: None => todo ! ( ) ,
182+ #[ allow( unstable_name_collisions) ]
183+ TargetHeights :: Exact ( target_heights) => {
184+ info ! (
185+ "found exact heights: [{}]" ,
186+ target_heights
187+ . iter( )
188+ . map( |h| h. to_string( ) )
189+ . intersperse( "," . to_string( ) )
190+ . collect:: <String >( ) ,
191+ ) ;
192+
193+ for events in self . batches {
194+ let EventProvableHeight :: Exactly ( target_height) = events[ 0 ] . provable_height
195+ else {
196+ panic ! ( "???" )
197+ } ;
198+
199+ match client_state_meta. counterparty_height . cmp ( & target_height) {
200+ Ordering :: Equal => {
201+ info ! (
202+ "client {client_id} has already been updated to \
203+ the desired target height ({} == {target_height})",
204+ client_state_meta. counterparty_height,
205+ client_id = self . client_id,
206+ ) ;
207+
208+ ops. push ( make_msgs (
209+ module,
210+ self . client_id . clone ( ) ,
211+ vec ! [ events] ,
212+ None ,
213+ client_state_meta. clone ( ) ,
214+ client_state_meta. counterparty_height ,
215+ ) ?) ;
216+ }
217+ Ordering :: Less => ops. push ( promise (
216218 [ call ( FetchUpdateHeaders {
217- client_type : client_info. client_type ,
219+ client_type : client_info. client_type . clone ( ) ,
218220 counterparty_chain_id : module. chain_id . clone ( ) ,
219- chain_id : client_state_meta. counterparty_chain_id ,
221+ chain_id : client_state_meta. counterparty_chain_id . clone ( ) ,
220222 client_id : RawClientId :: new ( self . client_id . clone ( ) ) ,
221223 update_from : client_state_meta. counterparty_height ,
222224 update_to : target_height,
@@ -226,14 +228,43 @@ where
226228 module. plugin_name ( ) ,
227229 ModuleCallback :: from ( MakeIbcMessagesFromUpdate :: < V > {
228230 client_id : self . client_id . clone ( ) ,
229- batches : self . batches ,
231+ batches : vec ! [ events ] ,
230232 } ) ,
231233 ) ,
232- ) )
234+ ) ) ,
235+ // update backwards
236+ // currently this is only supported in sui, and as such has some baked-in assumptions about the semantics of when this branch is hit
237+ Ordering :: Greater => {
238+ info ! (
239+ "updating client to an earlier height ({} -> {target_height})" ,
240+ client_state_meta. counterparty_height
241+ ) ;
242+
243+ ops. push ( promise (
244+ [ call ( FetchUpdateHeaders {
245+ client_type : client_info. client_type . clone ( ) ,
246+ counterparty_chain_id : module. chain_id . clone ( ) ,
247+ chain_id : client_state_meta. counterparty_chain_id . clone ( ) ,
248+ client_id : RawClientId :: new ( self . client_id . clone ( ) ) ,
249+ update_from : client_state_meta. counterparty_height ,
250+ update_to : target_height,
251+ } ) ] ,
252+ [ ] ,
253+ PluginMessage :: new (
254+ module. plugin_name ( ) ,
255+ ModuleCallback :: from ( MakeIbcMessagesFromUpdate :: < V > {
256+ client_id : self . client_id . clone ( ) ,
257+ batches : vec ! [ events] ,
258+ } ) ,
259+ ) ,
260+ ) ) ;
261+ }
233262 }
234263 }
235264 }
236265 }
266+
267+ Ok ( conc ( ops) )
237268 }
238269}
239270
0 commit comments