use std::cmp;
use std::num::NonZeroUsize;
use hedera_proto::services;
use tonic::transport::Channel;
use super::{
TransactionData,
TransactionExecute,
};
use crate::entity_id::ValidateChecksums;
use crate::execute::Execute;
use crate::{
AccountId,
BoxGrpcFuture,
Error,
Transaction,
TransactionHash,
TransactionId,
TransactionResponse,
};
#[cfg(feature = "ffi")]
const fn max_chunks_is_default(value: &usize) -> bool {
*value == ChunkData::DEFAULT_MAX_CHUNKS
}
#[cfg(feature = "ffi")]
const fn chunk_size_is_default(value: &NonZeroUsize) -> bool {
value.get() == ChunkData::DEFAULT_CHUNK_SIZE.get()
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "ffi", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "ffi", serde(default, rename_all = "camelCase"))]
pub struct ChunkData {
#[cfg_attr(feature = "ffi", serde(skip_serializing_if = "max_chunks_is_default"))]
pub(crate) max_chunks: usize,
#[cfg_attr(feature = "ffi", serde(skip_serializing_if = "chunk_size_is_default"))]
pub(crate) chunk_size: NonZeroUsize,
#[cfg_attr(
feature = "ffi",
serde(
with = "serde_with::As::<serde_with::base64::Base64>",
skip_serializing_if = "Vec::is_empty"
)
)]
pub(crate) data: Vec<u8>,
}
impl Default for ChunkData {
fn default() -> Self {
Self {
max_chunks: Self::DEFAULT_MAX_CHUNKS,
chunk_size: Self::DEFAULT_CHUNK_SIZE,
data: Vec::new(),
}
}
}
impl ChunkData {
const DEFAULT_MAX_CHUNKS: usize = 20;
const DEFAULT_CHUNK_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(1024) };
pub(crate) fn used_chunks(&self) -> usize {
if self.data.len() == 0 {
return 1;
}
(self.data.len() + self.chunk_size.get()) / self.chunk_size
}
pub(crate) fn message_chunk(&self, chunk_info: &ChunkInfo) -> &[u8] {
debug_assert!(chunk_info.current < self.used_chunks());
let start = self.chunk_size.get() * chunk_info.current;
let end = cmp::min(self.chunk_size.get() * (chunk_info.current + 1), self.data.len());
&self.data[start..end]
}
pub(crate) fn max_message_len(&self) -> usize {
self.max_chunks * self.chunk_size.get()
}
}
pub struct ChunkInfo {
pub(crate) current: usize,
pub(crate) total: usize,
pub(crate) initial_transaction_id: TransactionId,
pub(crate) current_transaction_id: TransactionId,
pub(crate) node_account_id: AccountId,
}
impl ChunkInfo {
#[must_use]
pub(crate) fn assert_single_transaction(&self) -> (TransactionId, AccountId) {
assert!(self.current == 0 && self.total == 1);
(self.current_transaction_id, self.node_account_id)
}
#[must_use]
pub(crate) const fn single(transaction_id: TransactionId, node_account_id: AccountId) -> Self {
Self::initial(1, transaction_id, node_account_id)
}
#[must_use]
pub(crate) const fn initial(
total: usize,
transaction_id: TransactionId,
node_account_id: AccountId,
) -> Self {
Self {
current: 0,
total,
initial_transaction_id: transaction_id,
current_transaction_id: transaction_id,
node_account_id,
}
}
}
pub(super) struct FirstChunkView<'a, D> {
pub(super) transaction: &'a Transaction<D>,
pub(super) total_chunks: usize,
}
impl<'a, D> Execute for FirstChunkView<'a, D>
where
D: TransactionExecute,
{
type GrpcRequest = services::Transaction;
type GrpcResponse = services::TransactionResponse;
type Context = TransactionHash;
type Response = TransactionResponse;
fn node_account_ids(&self) -> Option<&[AccountId]> {
self.transaction.body.node_account_ids.as_deref()
}
fn transaction_id(&self) -> Option<TransactionId> {
self.transaction.get_transaction_id()
}
fn requires_transaction_id(&self) -> bool {
true
}
fn make_request(
&self,
transaction_id: &Option<TransactionId>,
node_account_id: AccountId,
) -> crate::Result<(Self::GrpcRequest, Self::Context)> {
assert!(self.transaction.is_frozen());
self.transaction.make_request_inner(&ChunkInfo::initial(
self.total_chunks,
transaction_id.ok_or(Error::NoPayerAccountOrTransactionId)?,
node_account_id,
))
}
fn execute(
&self,
channel: Channel,
request: Self::GrpcRequest,
) -> BoxGrpcFuture<'_, Self::GrpcResponse> {
self.transaction.body.data.execute(channel, request)
}
fn make_response(
&self,
_response: Self::GrpcResponse,
context: Self::Context,
node_account_id: AccountId,
transaction_id: Option<TransactionId>,
) -> crate::Result<Self::Response> {
Ok(TransactionResponse {
node_account_id,
transaction_id: transaction_id.unwrap(),
transaction_hash: context,
validate_status: true,
})
}
fn make_error_pre_check(
&self,
status: services::ResponseCodeEnum,
transaction_id: Option<TransactionId>,
) -> crate::Error {
if let Some(transaction_id) = transaction_id {
crate::Error::TransactionPreCheckStatus { status, transaction_id }
} else {
crate::Error::TransactionNoIdPreCheckStatus { status }
}
}
fn response_pre_check_status(response: &Self::GrpcResponse) -> crate::Result<i32> {
Ok(response.node_transaction_precheck_code)
}
}
impl<'a, D: ValidateChecksums> ValidateChecksums for FirstChunkView<'a, D> {
fn validate_checksums(&self, ledger_id: &crate::LedgerId) -> Result<(), Error> {
self.transaction.validate_checksums(ledger_id)
}
}
pub(super) struct ChunkView<'a, D> {
pub(super) transaction: &'a Transaction<D>,
pub(super) initial_transaction_id: TransactionId,
pub(super) current_chunk: usize,
pub(super) total_chunks: usize,
}
impl<'a, D> Execute for ChunkView<'a, D>
where
D: TransactionExecute,
{
type GrpcRequest = services::Transaction;
type GrpcResponse = services::TransactionResponse;
type Context = TransactionHash;
type Response = TransactionResponse;
fn node_account_ids(&self) -> Option<&[AccountId]> {
self.transaction.body.node_account_ids.as_deref()
}
fn transaction_id(&self) -> Option<TransactionId> {
None
}
fn requires_transaction_id(&self) -> bool {
true
}
fn make_request(
&self,
transaction_id: &Option<TransactionId>,
node_account_id: AccountId,
) -> crate::Result<(Self::GrpcRequest, Self::Context)> {
assert!(self.transaction.is_frozen());
self.transaction.make_request_inner(&ChunkInfo {
total: self.total_chunks,
current: self.current_chunk,
initial_transaction_id: self.initial_transaction_id,
node_account_id,
current_transaction_id: transaction_id.ok_or(Error::NoPayerAccountOrTransactionId)?,
})
}
fn execute(
&self,
channel: Channel,
request: Self::GrpcRequest,
) -> BoxGrpcFuture<'_, Self::GrpcResponse> {
self.transaction.body.data.execute(channel, request)
}
fn make_response(
&self,
_response: Self::GrpcResponse,
context: Self::Context,
node_account_id: AccountId,
transaction_id: Option<TransactionId>,
) -> crate::Result<Self::Response> {
Ok(TransactionResponse {
node_account_id,
transaction_id: transaction_id.unwrap(),
transaction_hash: context,
validate_status: true,
})
}
fn make_error_pre_check(
&self,
status: services::ResponseCodeEnum,
transaction_id: Option<TransactionId>,
) -> crate::Error {
if let Some(transaction_id) = transaction_id {
crate::Error::TransactionPreCheckStatus { status, transaction_id }
} else {
crate::Error::TransactionNoIdPreCheckStatus { status }
}
}
fn response_pre_check_status(response: &Self::GrpcResponse) -> crate::Result<i32> {
Ok(response.node_transaction_precheck_code)
}
}
impl<'a, D: ValidateChecksums> ValidateChecksums for ChunkView<'a, D> {
fn validate_checksums(&self, ledger_id: &crate::LedgerId) -> Result<(), Error> {
self.transaction.validate_checksums(ledger_id)?;
self.initial_transaction_id.validate_checksums(ledger_id)?;
Ok(())
}
}
pub trait ChunkedTransactionData: TransactionData {
fn chunk_data(&self) -> &ChunkData;
fn chunk_data_mut(&mut self) -> &mut ChunkData;
}