massa_execution_exports/
mapping_grpc.rs

1// Copyright (c) 2023 MASSA LABS <info@massa.net>
2
3use std::str::FromStr;
4
5use crate::execution_info::ExecutionInfoForSlot;
6
7#[cfg(feature = "execution-info")]
8use crate::execution_info::TransferContext;
9use crate::{
10    ExecutionOutput, ExecutionQueryCycleInfos, ExecutionQueryError, ExecutionQueryExecutionStatus,
11    ExecutionQueryRequestItem, ExecutionQueryResponseItem, ExecutionQueryStakerInfo,
12    SlotExecutionOutput,
13};
14use grpc_api::execution_query_request_item as exec;
15use massa_models::address::Address;
16use massa_models::datastore::cleanup_datastore_key_range_query;
17use massa_models::deferred_calls::DeferredCallId;
18use massa_models::error::ModelsError;
19use massa_models::execution::EventFilter;
20use massa_models::mapping_grpc::to_denunciation_index;
21use massa_models::operation::OperationId;
22use massa_models::prehash::{CapacityAllocator, PreHashSet};
23use massa_proto_rs::massa::api::v1 as grpc_api;
24#[cfg(feature = "execution-info")]
25use massa_proto_rs::massa::model::v1::CoinOrigin;
26use massa_proto_rs::massa::model::v1::{self as grpc_model};
27
28/// Convert a `grpc_api::ScExecutionEventsRequest` to a `ScExecutionEventsRequest`
29pub fn to_querystate_filter(
30    query: grpc_api::ExecutionQueryRequestItem,
31    max_datastore_query_config: Option<u32>,
32    max_datastore_key_length: u8,
33) -> Result<ExecutionQueryRequestItem, ModelsError> {
34    if let Some(item) = query.request_item {
35        match item {
36            exec::RequestItem::AddressExistsCandidate(value) => {
37                Ok(ExecutionQueryRequestItem::AddressExistsCandidate(
38                    Address::from_str(&value.address)?,
39                ))
40            }
41            exec::RequestItem::AddressExistsFinal(value) => Ok(
42                ExecutionQueryRequestItem::AddressExistsFinal(Address::from_str(&value.address)?),
43            ),
44            exec::RequestItem::AddressBalanceCandidate(value) => {
45                Ok(ExecutionQueryRequestItem::AddressBalanceCandidate(
46                    Address::from_str(&value.address)?,
47                ))
48            }
49            exec::RequestItem::AddressBalanceFinal(value) => Ok(
50                ExecutionQueryRequestItem::AddressBalanceFinal(Address::from_str(&value.address)?),
51            ),
52            exec::RequestItem::AddressBytecodeCandidate(value) => {
53                Ok(ExecutionQueryRequestItem::AddressBytecodeCandidate(
54                    Address::from_str(&value.address)?,
55                ))
56            }
57            exec::RequestItem::AddressBytecodeFinal(value) => Ok(
58                ExecutionQueryRequestItem::AddressBytecodeFinal(Address::from_str(&value.address)?),
59            ),
60            exec::RequestItem::AddressDatastoreKeysCandidate(value) => {
61                let address = Address::from_str(&value.address)?;
62
63                let start_key = match (value.start_key, value.inclusive_start_key.unwrap_or(true)) {
64                    (None, _) => std::ops::Bound::Unbounded,
65                    (Some(k), true) => std::ops::Bound::Included(k),
66                    (Some(k), false) => std::ops::Bound::Excluded(k),
67                };
68                let end_key = match (value.end_key, value.inclusive_end_key.unwrap_or(true)) {
69                    (None, _) => std::ops::Bound::Unbounded,
70                    (Some(k), true) => std::ops::Bound::Included(k),
71                    (Some(k), false) => std::ops::Bound::Excluded(k),
72                };
73
74                let (prefix, start_key, end_key, count) = cleanup_datastore_key_range_query(
75                    &value.prefix,
76                    start_key,
77                    end_key,
78                    value.limit,
79                    max_datastore_key_length,
80                    max_datastore_query_config,
81                )?;
82
83                Ok(ExecutionQueryRequestItem::AddressDatastoreKeysCandidate {
84                    address,
85                    prefix,
86                    start_key,
87                    end_key,
88                    count,
89                })
90            }
91            exec::RequestItem::AddressDatastoreKeysFinal(value) => {
92                let address = Address::from_str(&value.address)?;
93
94                let start_key = match (value.start_key, value.inclusive_start_key.unwrap_or(true)) {
95                    (None, _) => std::ops::Bound::Unbounded,
96                    (Some(k), true) => std::ops::Bound::Included(k),
97                    (Some(k), false) => std::ops::Bound::Excluded(k),
98                };
99                let end_key = match (value.end_key, value.inclusive_end_key.unwrap_or(true)) {
100                    (None, _) => std::ops::Bound::Unbounded,
101                    (Some(k), true) => std::ops::Bound::Included(k),
102                    (Some(k), false) => std::ops::Bound::Excluded(k),
103                };
104
105                let (prefix, start_key, end_key, count) = cleanup_datastore_key_range_query(
106                    &value.prefix,
107                    start_key,
108                    end_key,
109                    value.limit,
110                    max_datastore_key_length,
111                    max_datastore_query_config,
112                )?;
113
114                Ok(ExecutionQueryRequestItem::AddressDatastoreKeysFinal {
115                    address,
116                    prefix,
117                    start_key,
118                    end_key,
119                    count,
120                })
121            }
122            exec::RequestItem::AddressDatastoreValueCandidate(value) => {
123                Ok(ExecutionQueryRequestItem::AddressDatastoreValueCandidate {
124                    addr: Address::from_str(&value.address)?,
125                    key: value.key,
126                })
127            }
128            exec::RequestItem::AddressDatastoreValueFinal(value) => {
129                Ok(ExecutionQueryRequestItem::AddressDatastoreValueFinal {
130                    addr: Address::from_str(&value.address)?,
131                    key: value.key,
132                })
133            }
134            exec::RequestItem::OpExecutionStatusCandidate(value) => {
135                Ok(ExecutionQueryRequestItem::OpExecutionStatusCandidate(
136                    OperationId::from_str(&value.operation_id)?,
137                ))
138            }
139            exec::RequestItem::OpExecutionStatusFinal(value) => {
140                Ok(ExecutionQueryRequestItem::OpExecutionStatusFinal(
141                    OperationId::from_str(&value.operation_id)?,
142                ))
143            }
144            //TODO to be improved
145            exec::RequestItem::DenunciationExecutionStatusCandidate(value) => Ok(
146                ExecutionQueryRequestItem::DenunciationExecutionStatusCandidate(
147                    to_denunciation_index(value.denunciation_index.ok_or_else(|| {
148                        ModelsError::ErrorRaised("no denounciation index found".to_string())
149                    })?)?,
150                ),
151            ),
152            //TODO to be improved
153            exec::RequestItem::DenunciationExecutionStatusFinal(value) => {
154                Ok(ExecutionQueryRequestItem::DenunciationExecutionStatusFinal(
155                    to_denunciation_index(value.denunciation_index.ok_or_else(|| {
156                        ModelsError::ErrorRaised("no denounciation index found".to_string())
157                    })?)?,
158                ))
159            }
160            exec::RequestItem::AddressRollsCandidate(value) => {
161                Ok(ExecutionQueryRequestItem::AddressRollsCandidate(
162                    Address::from_str(&value.address)?,
163                ))
164            }
165            exec::RequestItem::AddressRollsFinal(value) => Ok(
166                ExecutionQueryRequestItem::AddressRollsFinal(Address::from_str(&value.address)?),
167            ),
168            exec::RequestItem::AddressDeferredCreditsCandidate(value) => {
169                Ok(ExecutionQueryRequestItem::AddressDeferredCreditsCandidate(
170                    Address::from_str(&value.address)?,
171                ))
172            }
173            exec::RequestItem::AddressDeferredCreditsFinal(value) => {
174                Ok(ExecutionQueryRequestItem::AddressDeferredCreditsFinal(
175                    Address::from_str(&value.address)?,
176                ))
177            }
178            //TODO to be checked
179            exec::RequestItem::CycleInfos(value) => {
180                let addresses = value
181                    .restrict_to_addresses
182                    .into_iter()
183                    .map(|address| Address::from_str(&address))
184                    .collect::<Result<Vec<_>, _>>()?;
185                let mut addresses_set = PreHashSet::with_capacity(addresses.len());
186                addresses_set.extend(addresses);
187                Ok(ExecutionQueryRequestItem::CycleInfos {
188                    cycle: value.cycle,
189                    restrict_to_addresses: Some(addresses_set),
190                })
191            }
192            exec::RequestItem::Events(value) => {
193                let event_filter = to_event_filter(value.filters)?;
194                Ok(ExecutionQueryRequestItem::Events(event_filter))
195            }
196            exec::RequestItem::DeferredCallQuote(value) => {
197                Ok(ExecutionQueryRequestItem::DeferredCallQuote {
198                    target_slot: value
199                        .target_slot
200                        .ok_or(ModelsError::ErrorRaised(
201                            "target slot is required".to_string(),
202                        ))?
203                        .into(),
204                    max_gas_request: value.max_gas,
205                    params_size: value.params_size,
206                })
207            }
208            exec::RequestItem::DeferredCallInfo(info) => {
209                let id = DeferredCallId::from_str(&info.call_id)?;
210                Ok(ExecutionQueryRequestItem::DeferredCallInfo(id))
211            }
212            exec::RequestItem::DeferredCallsBySlot(value) => {
213                Ok(ExecutionQueryRequestItem::DeferredCallsBySlot(
214                    value
215                        .slot
216                        .ok_or(ModelsError::ErrorRaised("slot is required".to_string()))?
217                        .into(),
218                ))
219            }
220        }
221    } else {
222        Err(ModelsError::ErrorRaised("no filter provided".to_string()))
223    }
224}
225
226/// Convert a vector of `grpc_model::ScExecutionEventsFilter` to a `EventFilter`
227pub fn to_event_filter(
228    sce_filters: Vec<grpc_api::ScExecutionEventsFilter>,
229) -> Result<EventFilter, ModelsError> {
230    let mut event_filter = EventFilter::default();
231    for query in sce_filters {
232        if let Some(filter) = query.filter {
233            match filter {
234                grpc_api::sc_execution_events_filter::Filter::SlotRange(slot_range) => {
235                    event_filter.start = slot_range.start_slot.map(|slot| slot.into());
236                    event_filter.end = slot_range.end_slot.map(|slot| slot.into());
237                }
238                grpc_api::sc_execution_events_filter::Filter::CallerAddress(caller_address) => {
239                    event_filter.original_caller_address =
240                        Some(Address::from_str(&caller_address)?);
241                }
242                grpc_api::sc_execution_events_filter::Filter::EmitterAddress(emitter_address) => {
243                    event_filter.emitter_address = Some(Address::from_str(&emitter_address)?);
244                }
245                grpc_api::sc_execution_events_filter::Filter::OriginalOperationId(operation_id) => {
246                    event_filter.original_operation_id =
247                        Some(OperationId::from_str(&operation_id)?);
248                }
249                grpc_api::sc_execution_events_filter::Filter::IsFailure(is_failure) => {
250                    event_filter.is_error = Some(is_failure);
251                }
252                grpc_api::sc_execution_events_filter::Filter::Status(status) => {
253                    // See grpc_model::ScExecutionEventStatus
254                    match grpc_model::ScExecutionEventStatus::try_from(status) {
255                        Ok(grpc_model::ScExecutionEventStatus::Final) => {
256                            event_filter.is_final = Some(true)
257                        }
258                        Ok(grpc_model::ScExecutionEventStatus::Candidate) => {
259                            event_filter.is_final = Some(false)
260                        }
261                        _ => {}
262                    }
263                }
264            }
265        }
266    }
267
268    Ok(event_filter)
269}
270
271/// Converts a `ExecutionQueryResponse` to a `grpc_api::ExecutionQueryResponse`
272pub fn to_execution_query_response(
273    value: Result<ExecutionQueryResponseItem, ExecutionQueryError>,
274) -> grpc_api::ExecutionQueryResponse {
275    match value {
276        Ok(item) => grpc_api::ExecutionQueryResponse {
277            response: Some(grpc_api::execution_query_response::Response::Result(
278                to_execution_query_result(item),
279            )),
280        },
281        Err(err) => grpc_api::ExecutionQueryResponse {
282            response: Some(grpc_api::execution_query_response::Response::Error(
283                err.into(),
284            )),
285        },
286    }
287}
288
289// Convertss a `ExecutionQueryResponseItem` to a `grpc_api::ExecutionQueryResponseItem`
290fn to_execution_query_result(
291    value: ExecutionQueryResponseItem,
292) -> grpc_api::ExecutionQueryResponseItem {
293    let response_item = match value {
294        ExecutionQueryResponseItem::Boolean(result) => {
295            grpc_api::execution_query_response_item::ResponseItem::Boolean(result)
296        }
297        ExecutionQueryResponseItem::RollCount(result) => {
298            grpc_api::execution_query_response_item::ResponseItem::RollCount(result)
299        }
300        ExecutionQueryResponseItem::Amount(result) => {
301            grpc_api::execution_query_response_item::ResponseItem::Amount(result.into())
302        }
303        ExecutionQueryResponseItem::Bytecode(result) => {
304            grpc_api::execution_query_response_item::ResponseItem::Bytes(result.0)
305        }
306        ExecutionQueryResponseItem::DatastoreValue(result) => {
307            grpc_api::execution_query_response_item::ResponseItem::Bytes(result)
308        }
309        ExecutionQueryResponseItem::AddressDatastoreKeys(result, _address, _is_final) => {
310            grpc_api::execution_query_response_item::ResponseItem::VecBytes(
311                grpc_model::ArrayOfBytesWrapper {
312                    items: result.into_iter().collect(),
313                },
314            )
315        }
316        ExecutionQueryResponseItem::DeferredCredits(result) => {
317            grpc_api::execution_query_response_item::ResponseItem::DeferredCredits(
318                grpc_api::DeferredCreditsEntryWrapper {
319                    entries: result
320                        .into_iter()
321                        .map(|(slot, amount)| grpc_api::DeferredCreditsEntry {
322                            slot: Some(slot.into()),
323                            amount: Some(amount.into()),
324                        })
325                        .collect(),
326                },
327            )
328        }
329        ExecutionQueryResponseItem::ExecutionStatus(result) => match result {
330            ExecutionQueryExecutionStatus::AlreadyExecutedWithSuccess => {
331                grpc_api::execution_query_response_item::ResponseItem::ExecutionStatus(
332                    grpc_api::ExecutionQueryExecutionStatus::AlreadyExecutedWithSuccess as i32,
333                )
334            }
335            ExecutionQueryExecutionStatus::AlreadyExecutedWithFailure => {
336                grpc_api::execution_query_response_item::ResponseItem::ExecutionStatus(
337                    grpc_api::ExecutionQueryExecutionStatus::AlreadyExecutedWithFailure as i32,
338                )
339            }
340            ExecutionQueryExecutionStatus::ExecutableOrExpired => {
341                grpc_api::execution_query_response_item::ResponseItem::ExecutionStatus(
342                    grpc_api::ExecutionQueryExecutionStatus::ExecutableOrExpired as i32,
343                )
344            }
345        },
346        ExecutionQueryResponseItem::CycleInfos(result) => {
347            grpc_api::execution_query_response_item::ResponseItem::CycleInfos(to_cycle_info(result))
348        }
349        ExecutionQueryResponseItem::Events(result) => {
350            grpc_api::execution_query_response_item::ResponseItem::Events(
351                grpc_api::ScOutputEventsWrapper {
352                    events: result.into_iter().map(|event| event.into()).collect(),
353                },
354            )
355        }
356        ExecutionQueryResponseItem::DeferredCallQuote(
357            target_slot,
358            max_gas_request,
359            available,
360            price,
361        ) => grpc_api::execution_query_response_item::ResponseItem::DeferredCallQuote(
362            grpc_api::DeferredCallQuoteResponse {
363                target_slot: Some(target_slot.into()),
364                max_gas_request,
365                available,
366                price: Some(price.into()),
367            },
368        ),
369        ExecutionQueryResponseItem::DeferredCallInfo(call_id, call) => {
370            grpc_api::execution_query_response_item::ResponseItem::DeferredCallInfo(
371                grpc_api::DeferredCallInfoResponse {
372                    call_id: call_id.to_string(),
373                    call: Some(call.into()),
374                },
375            )
376        }
377        ExecutionQueryResponseItem::DeferredCallsBySlot(slot, ids) => {
378            let arr = ids.into_iter().map(|id| id.to_string()).collect();
379            grpc_api::execution_query_response_item::ResponseItem::DeferredCallsBySlot(
380                grpc_api::DeferredCallsBySlotResponse {
381                    slot: Some(slot.into()),
382                    call_ids: arr,
383                },
384            )
385        }
386    };
387
388    grpc_api::ExecutionQueryResponseItem {
389        response_item: Some(response_item),
390    }
391}
392
393// Convertss a `ExecutionQueryCycleInfos` to a `grpc_api::CycleInfos`
394fn to_cycle_info(value: ExecutionQueryCycleInfos) -> grpc_api::ExecutionQueryCycleInfos {
395    grpc_api::ExecutionQueryCycleInfos {
396        cycle: value.cycle,
397        is_final: value.is_final,
398        staker_infos: value
399            .staker_infos
400            .into_iter()
401            .map(|(address, info)| to_execution_query_staker_info(address, info))
402            .collect(),
403    }
404}
405
406// Convertss a `ExecutionQueryStakerInfo` to a `grpc_api::ExecutionQueryStakerInfo`
407fn to_execution_query_staker_info(
408    address: Address,
409    info: ExecutionQueryStakerInfo,
410) -> grpc_api::ExecutionQueryStakerInfoEntry {
411    grpc_api::ExecutionQueryStakerInfoEntry {
412        address: address.to_string(),
413        info: Some(grpc_api::ExecutionQueryStakerInfo {
414            active_rolls: info.active_rolls,
415            production_stats: Some(grpc_api::ExecutionQueryStakerInfoProductionStatsEntry {
416                address: address.to_string(),
417                stats: Some(grpc_api::ExecutionQueryStakerInfoProductionStats {
418                    block_success_count: info.production_stats.block_success_count,
419                    block_failure_count: info.production_stats.block_failure_count,
420                }),
421            }),
422        }),
423    }
424}
425
426impl From<SlotExecutionOutput> for grpc_model::SlotExecutionOutput {
427    fn from(value: SlotExecutionOutput) -> Self {
428        match value {
429            SlotExecutionOutput::ExecutedSlot(execution_output) => {
430                grpc_model::SlotExecutionOutput {
431                    status: grpc_model::ExecutionOutputStatus::Candidate as i32,
432                    execution_output: Some(execution_output.into()),
433                }
434            }
435            SlotExecutionOutput::FinalizedSlot(execution_output) => {
436                grpc_model::SlotExecutionOutput {
437                    status: grpc_model::ExecutionOutputStatus::Final as i32,
438                    execution_output: Some(execution_output.into()),
439                }
440            }
441        }
442    }
443}
444
445impl From<ExecutionOutput> for grpc_model::ExecutionOutput {
446    fn from(value: ExecutionOutput) -> Self {
447        grpc_model::ExecutionOutput {
448            slot: Some(value.slot.into()),
449            block_id: value.block_info.map(|i| i.block_id.to_string()),
450            events: value
451                .events
452                .0
453                .into_iter()
454                .map(|event| event.into())
455                .collect(),
456            state_changes: Some(value.state_changes.into()),
457        }
458    }
459}
460
461impl From<ExecutionQueryError> for grpc_model::Error {
462    fn from(value: ExecutionQueryError) -> Self {
463        match value {
464            ExecutionQueryError::NotFound(error) => grpc_model::Error {
465                //TODO to be defined
466                code: 404,
467                message: error,
468            },
469            ExecutionQueryError::TooLargeResponse(error) => grpc_model::Error {
470                code: 413,
471                message: error,
472            },
473        }
474    }
475}
476
477impl From<ExecutionInfoForSlot> for grpc_api::NewTransfersInfoServerResponse {
478    fn from(value: ExecutionInfoForSlot) -> Self {
479        #[cfg(feature = "execution-info")]
480        let transfers_info: Vec<grpc_model::ExecTransferInfo> = value
481            .transfers
482            .into_iter()
483            .map(|transfer| {
484                let id = transfer.id.unwrap_or("Unknown id".to_string());
485
486                let from_address = transfer.from.map(|a| a.to_string());
487                let to_address = transfer.to.map(|a| a.to_string());
488
489                let value = match transfer.value {
490                    crate::execution_info::TransferValue::Rolls(roll_count) => {
491                        grpc_model::TransferValue {
492                            value: Some(grpc_model::transfer_value::Value::Rolls(
493                                roll_count.into(),
494                            )),
495                        }
496                    }
497                    crate::execution_info::TransferValue::DeferredCredits(amount) => {
498                        grpc_model::TransferValue {
499                            value: Some(grpc_model::transfer_value::Value::DeferredCredits(
500                                amount.into(),
501                            )),
502                        }
503                    }
504                    crate::execution_info::TransferValue::Coins(amount) => {
505                        grpc_model::TransferValue {
506                            value: Some(grpc_model::transfer_value::Value::Coins(amount.into())),
507                        }
508                    }
509                };
510
511                let (origin, ctx) = match transfer.context {
512                    TransferContext::TransactionCoins(ctx) => {
513                        (CoinOrigin::OpTransactionCoins as i32, Some(ctx))
514                    }
515                    TransferContext::AyncMsgCancel(ctx) => {
516                        (CoinOrigin::AsyncMsgCancel as i32, Some(ctx))
517                    }
518                    TransferContext::DeferredCredits(ctx) => {
519                        (CoinOrigin::DeferredCredit as i32, Some(ctx))
520                    }
521                    TransferContext::DeferredCallFail(ctx) => {
522                        (CoinOrigin::DeferredCallFail as i32, Some(ctx))
523                    }
524                    TransferContext::DeferredCallCancel(ctx) => {
525                        (CoinOrigin::DeferredCallCancel as i32, Some(ctx))
526                    }
527                    TransferContext::DeferredCallCoins(ctx) => {
528                        (CoinOrigin::DeferredCallCoins as i32, Some(ctx))
529                    }
530                    TransferContext::DeferredCallRegister(ctx) => {
531                        (CoinOrigin::DeferredCallRegister as i32, Some(ctx))
532                    }
533                    TransferContext::DeferredCallStorageRefund(ctx) => {
534                        (CoinOrigin::DeferredCallStorageRefund as i32, Some(ctx))
535                    }
536                    TransferContext::OperationFee(ctx) => {
537                        (CoinOrigin::OpTransactionFees as i32, Some(ctx))
538                    }
539                    TransferContext::RollBuy(ctx) => (CoinOrigin::OpRollBuy as i32, Some(ctx)),
540                    TransferContext::RollSell(ctx) => (CoinOrigin::OpRollSell as i32, Some(ctx)),
541                    TransferContext::RollSlash => (CoinOrigin::Slash as i32, None),
542                    TransferContext::CreateSCStorage => (CoinOrigin::CreateScStorage as i32, None),
543                    TransferContext::DatastoreStorage => {
544                        (CoinOrigin::DatastoreStorage as i32, None)
545                    }
546                    TransferContext::CallSCCoins(ctx) => {
547                        (CoinOrigin::OpCallscCoins as i32, Some(ctx))
548                    }
549                    TransferContext::AsyncMsgCoins(ctx) => {
550                        (CoinOrigin::AsyncMsgCoins as i32, Some(ctx))
551                    }
552                    TransferContext::EndorsementCreatorReward => {
553                        (CoinOrigin::EndorsementReward as i32, None)
554                    }
555                    TransferContext::EndorsementTargetReward => {
556                        (CoinOrigin::EndorsedReward as i32, None)
557                    }
558                    TransferContext::BlockCreatorReward => (CoinOrigin::BlockReward as i32, None),
559                    TransferContext::ReadOnlyBytecodeExecutionFee => {
560                        (CoinOrigin::ReadOnlyBytecodeExecFees as i32, None)
561                    }
562                    TransferContext::ReadOnlyFunctionCallFee => {
563                        (CoinOrigin::ReadOnlyFnCallFees as i32, None)
564                    }
565                    TransferContext::ReadOnlyFunctionCallCoins => {
566                        (CoinOrigin::ReadOnlyFnCallCoins as i32, None)
567                    }
568                    TransferContext::SetBytecodeStorage => {
569                        (CoinOrigin::SetBytecodeStorage as i32, None)
570                    }
571                    TransferContext::AbiCallCoins => (CoinOrigin::AbiCallCoins as i32, None),
572                    TransferContext::AbiTransferCoins => {
573                        (CoinOrigin::AbiTransferCoins as i32, None)
574                    }
575                    TransferContext::AbiTransferForCoins => {
576                        (CoinOrigin::AbiTransferForCoins as i32, None)
577                    }
578                    TransferContext::AbiSendMsgCoins => (CoinOrigin::AbiSendMsgCoins as i32, None),
579                    TransferContext::AbiSendMsgFee => (CoinOrigin::AbiSendMsgFees as i32, None),
580                };
581
582                let (operation_id, async_msg_id, deferred_call_id, denunciation_index) = match ctx {
583                    Some(ctx) => (
584                        ctx.operation_id.map(|id| id.to_string()),
585                        ctx.async_message_id_str,
586                        ctx.deferred_call_id.map(|id| id.to_string()),
587                        ctx.denunciation_index.map(|idx| idx.into()),
588                    ),
589                    None => (None, None, None, None),
590                };
591
592                grpc_model::ExecTransferInfo {
593                    id,
594                    from_address,
595                    to_address,
596                    origin,
597                    value: Some(value),
598                    operation_id,
599                    async_msg_id,
600                    deferred_call_id,
601                    denunciation_index,
602                }
603            })
604            .collect();
605
606        grpc_api::NewTransfersInfoServerResponse {
607            slot: Some(value.slot.into()),
608            timestamp: value.timestamp.as_millis() as i64,
609            block_id: value.opt_block_id.map(|b| b.to_string()),
610            #[cfg(feature = "execution-info")]
611            transfers_info,
612            #[cfg(not(feature = "execution-info"))]
613            transfers_info: vec![],
614        }
615    }
616}