use crate::{
VersionedBlockPayload,
VersionedCompressedBlock,
config::Config,
ports::{
HistoryLookup,
TemporalRegistry,
},
registry::TemporalRegistryAll,
};
use fuel_core_types::{
blockchain::block::PartialFuelBlock,
fuel_compression::{
Compressible,
ContextError,
Decompress,
DecompressibleBy,
RegistryKey,
},
fuel_tx::{
AssetId,
CompressedUtxoId,
Mint,
ScriptCode,
Transaction,
TxPointer as FuelTxPointer,
UtxoId,
field::TxPointer,
input::{
AsField,
PredicateCode,
coin::{
Coin,
CoinSpecification,
},
message::{
Message,
MessageSpecification,
},
},
},
fuel_types::{
Address,
ContractId,
},
tai64::Tai64,
};
#[cfg(not(feature = "fault-proving"))]
pub mod not_fault_proving {
use super::*;
pub trait DecompressDb: TemporalRegistryAll + HistoryLookup {}
impl<T> DecompressDb for T where T: TemporalRegistryAll + HistoryLookup {}
}
#[cfg(feature = "fault-proving")]
pub mod fault_proving {
use super::*;
use crate::ports::GetRegistryRoot;
pub trait DecompressDb:
TemporalRegistryAll + HistoryLookup + GetRegistryRoot
{
}
impl<T> DecompressDb for T where T: TemporalRegistryAll + HistoryLookup + GetRegistryRoot {}
}
#[cfg(feature = "fault-proving")]
use fault_proving::DecompressDb;
use fuel_core_types::fuel_types::bytes::Bytes;
#[cfg(not(feature = "fault-proving"))]
use not_fault_proving::DecompressDb;
pub async fn decompress<D>(
config: Config,
mut db: D,
block: VersionedCompressedBlock,
) -> anyhow::Result<PartialFuelBlock>
where
D: DecompressDb,
{
block
.registrations()
.write_to_registry(&mut db, block.consensus_header().time)?;
let ctx = DecompressCtx {
config,
timestamp: block.consensus_header().time,
db,
};
let mut transactions = <Vec<Transaction> as DecompressibleBy<_>>::decompress_with(
block.transactions(),
&ctx,
)
.await?;
let transaction_count = transactions.len();
let mint_tx = transactions
.last_mut()
.ok_or_else(|| anyhow::anyhow!("No transactions"))?;
if let Transaction::Mint(mint) = mint_tx {
let tx_pointer = mint.tx_pointer_mut();
*tx_pointer = FuelTxPointer::new(
block.consensus_header().height,
#[allow(clippy::arithmetic_side_effects)]
u16::try_from(transaction_count - 1)?,
);
} else {
anyhow::bail!("Last transaction is not a mint");
}
#[cfg(feature = "fault-proving")]
{
match block {
VersionedCompressedBlock::V0(_) => {}
VersionedCompressedBlock::V1(ref block) => {
let registry_root_after_decompression = ctx
.db
.registry_root()
.map_err(|e| anyhow::anyhow!("Failed to get registry root: {}", e))?;
let registry_root_after_compression = block.header.registry_root;
if registry_root_after_decompression != registry_root_after_compression {
anyhow::bail!(
"Registry root mismatch. registry root after decompression: {:?}, registry root after compression: {:?}",
registry_root_after_decompression,
registry_root_after_compression
);
}
}
}
}
Ok(PartialFuelBlock {
header: block.partial_block_header(),
transactions,
})
}
pub struct DecompressCtx<D> {
pub config: Config,
pub timestamp: Tai64,
pub db: D,
}
impl<D> ContextError for DecompressCtx<D> {
type Error = anyhow::Error;
}
impl<D> DecompressibleBy<DecompressCtx<D>> for UtxoId
where
D: HistoryLookup,
{
async fn decompress_with(
c: CompressedUtxoId,
ctx: &DecompressCtx<D>,
) -> anyhow::Result<Self> {
ctx.db.utxo_id(c)
}
}
macro_rules! decompress_impl {
($($type:ty),*) => { paste::paste! {
$(
impl<D> DecompressibleBy<DecompressCtx<D>> for $type
where
D: TemporalRegistry<$type>
{
async fn decompress_with(
key: RegistryKey,
ctx: &DecompressCtx<D>,
) -> anyhow::Result<Self> {
if key == RegistryKey::DEFAULT_VALUE {
return Ok(<$type>::default());
}
let key_timestamp = ctx.db.read_timestamp(&key)?;
if !ctx.config.is_timestamp_accessible(ctx.timestamp, key_timestamp)? {
anyhow::bail!("Timestamp not accessible");
}
ctx.db.read_registry(&key)
}
}
)*
}};
}
decompress_impl!(AssetId, ContractId, Address, PredicateCode, ScriptCode);
impl<D, Specification> DecompressibleBy<DecompressCtx<D>> for Coin<Specification>
where
D: DecompressDb,
Specification: CoinSpecification,
Specification::Predicate: DecompressibleBy<DecompressCtx<D>>,
Specification::PredicateData: DecompressibleBy<DecompressCtx<D>>,
Specification::PredicateGasUsed: DecompressibleBy<DecompressCtx<D>>,
Specification::Witness: DecompressibleBy<DecompressCtx<D>>,
{
async fn decompress_with(
c: <Coin<Specification> as Compressible>::Compressed,
ctx: &DecompressCtx<D>,
) -> anyhow::Result<Coin<Specification>> {
let utxo_id = UtxoId::decompress_with(c.utxo_id, ctx).await?;
let coin_info = ctx.db.coin(utxo_id)?;
let witness_index = c.witness_index.decompress(ctx).await?;
let predicate_gas_used = c.predicate_gas_used.decompress(ctx).await?;
let predicate = c.predicate.decompress(ctx).await?;
let predicate_data = c.predicate_data.decompress(ctx).await?;
Ok(Self {
utxo_id,
owner: coin_info.owner,
amount: coin_info.amount,
asset_id: coin_info.asset_id,
tx_pointer: Default::default(),
witness_index,
predicate_gas_used,
predicate,
predicate_data,
})
}
}
impl<D, Specification> DecompressibleBy<DecompressCtx<D>> for Message<Specification>
where
D: DecompressDb,
Specification: MessageSpecification,
Specification::Data: DecompressibleBy<DecompressCtx<D>> + Default,
Specification::Predicate: DecompressibleBy<DecompressCtx<D>>,
Specification::PredicateData: DecompressibleBy<DecompressCtx<D>>,
Specification::PredicateGasUsed: DecompressibleBy<DecompressCtx<D>>,
Specification::Witness: DecompressibleBy<DecompressCtx<D>>,
{
async fn decompress_with(
c: <Message<Specification> as Compressible>::Compressed,
ctx: &DecompressCtx<D>,
) -> anyhow::Result<Message<Specification>> {
let msg = ctx.db.message(c.nonce)?;
let witness_index = c.witness_index.decompress(ctx).await?;
let predicate_gas_used = c.predicate_gas_used.decompress(ctx).await?;
let predicate = c.predicate.decompress(ctx).await?;
let predicate_data = c.predicate_data.decompress(ctx).await?;
let mut message: Message<Specification> = Message {
sender: msg.sender,
recipient: msg.recipient,
amount: msg.amount,
nonce: c.nonce,
witness_index,
predicate_gas_used,
data: Default::default(),
predicate,
predicate_data,
};
if let Some(data) = message.data.as_mut_field() {
*data = Bytes::new(msg.data.clone());
}
Ok(message)
}
}
impl<D> DecompressibleBy<DecompressCtx<D>> for Mint
where
D: DecompressDb,
{
async fn decompress_with(
c: Self::Compressed,
ctx: &DecompressCtx<D>,
) -> anyhow::Result<Self> {
Ok(Transaction::mint(
Default::default(), c.input_contract.decompress(ctx).await?,
c.output_contract.decompress(ctx).await?,
c.mint_amount.decompress(ctx).await?,
c.mint_asset_id.decompress(ctx).await?,
c.gas_price.decompress(ctx).await?,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde::{
Deserialize,
Serialize,
};
#[tokio::test]
async fn decompress_block_with_unknown_version() {
#[derive(Clone, Serialize, Deserialize)]
#[allow(clippy::large_enum_variant)]
enum CompressedBlockWithNewVersions {
V0(crate::CompressedBlockPayloadV0),
NewVersion(u32),
#[serde(untagged)]
Unknown,
}
let bad_block =
postcard::to_stdvec(&CompressedBlockWithNewVersions::NewVersion(1234))
.unwrap();
let result: Result<VersionedCompressedBlock, _> =
postcard::from_bytes(&bad_block);
let _ =
result.expect_err("should fail to deserialize because of unknown version");
}
}