Skip to content

Commit

Permalink
Set tx pointer during block production (#1054)
Browse files Browse the repository at this point in the history
closes: [#544](#544)

- adds logic to the executor for setting / verifying tx_pointers
- adds tx pointer to chain config for coins and contracts
- adds utxo id to chain config for contracts

---------

Co-authored-by: Green Baneling <XgreenX9999@gmail.com>
  • Loading branch information
Voxelot and xgreenx authored Mar 8, 2023
1 parent 1813b9d commit b69ac7e
Show file tree
Hide file tree
Showing 33 changed files with 1,221 additions and 224 deletions.
77 changes: 67 additions & 10 deletions crates/chain-config/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ mod tests {
use fuel_core_types::{
blockchain::primitives::DaBlockHeight,
fuel_asm::op,
fuel_tx::{
TxPointer,
UtxoId,
},
fuel_types::{
AssetId,
Bytes32,
},
fuel_vm::Contract,
};

use rand::{
prelude::StdRng,
Rng,
Expand Down Expand Up @@ -106,14 +109,14 @@ mod tests {

#[test]
fn snapshot_simple_contract() {
let config = test_config_contract(false, false);
let config = test_config_contract(false, false, false, false);
let json = serde_json::to_string_pretty(&config).unwrap();
insta::assert_snapshot!(json);
}

#[test]
fn can_roundtrip_simple_contract() {
let config = test_config_contract(false, false);
let config = test_config_contract(false, false, false, false);
let json = serde_json::to_string(&config).unwrap();
let deserialized_config: ChainConfig =
serde_json::from_str(json.as_str()).unwrap();
Expand All @@ -122,14 +125,14 @@ mod tests {

#[test]
fn snapshot_contract_with_state() {
let config = test_config_contract(true, false);
let config = test_config_contract(true, false, false, false);
let json = serde_json::to_string_pretty(&config).unwrap();
insta::assert_snapshot!(json);
}

#[test]
fn can_roundtrip_contract_with_state() {
let config = test_config_contract(true, false);
let config = test_config_contract(true, false, false, false);
let json = serde_json::to_string(&config).unwrap();
let deserialized_config: ChainConfig =
serde_json::from_str(json.as_str()).unwrap();
Expand All @@ -138,14 +141,46 @@ mod tests {

#[test]
fn snapshot_contract_with_balances() {
let config = test_config_contract(false, true);
let config = test_config_contract(false, true, false, false);
let json = serde_json::to_string_pretty(&config).unwrap();
insta::assert_snapshot!(json);
}

#[test]
fn can_roundtrip_contract_with_balances() {
let config = test_config_contract(false, true);
let config = test_config_contract(false, true, false, false);
let json = serde_json::to_string(&config).unwrap();
let deserialized_config: ChainConfig =
serde_json::from_str(json.as_str()).unwrap();
assert_eq!(config, deserialized_config);
}

#[test]
fn snapshot_contract_with_utxo_id() {
let config = test_config_contract(false, false, true, false);
let json = serde_json::to_string_pretty(&config).unwrap();
insta::assert_snapshot!(json);
}

#[test]
fn can_roundtrip_contract_with_utxoid() {
let config = test_config_contract(false, false, true, false);
let json = serde_json::to_string(&config).unwrap();
let deserialized_config: ChainConfig =
serde_json::from_str(json.as_str()).unwrap();
assert_eq!(config, deserialized_config);
}

#[test]
fn snapshot_contract_with_tx_pointer() {
let config = test_config_contract(false, false, false, true);
let json = serde_json::to_string_pretty(&config).unwrap();
insta::assert_snapshot!(json);
}

#[test]
fn can_roundtrip_contract_with_tx_pointer() {
let config = test_config_contract(false, false, false, true);
let json = serde_json::to_string(&config).unwrap();
let deserialized_config: ChainConfig =
serde_json::from_str(json.as_str()).unwrap();
Expand Down Expand Up @@ -184,7 +219,12 @@ mod tests {
assert_eq!(config, deserialized_config);
}

fn test_config_contract(state: bool, balances: bool) -> ChainConfig {
fn test_config_contract(
state: bool,
balances: bool,
utxo_id: bool,
tx_pointer: bool,
) -> ChainConfig {
let mut rng = StdRng::seed_from_u64(1);
let state = if state {
let test_key: Bytes32 = rng.gen();
Expand All @@ -200,6 +240,17 @@ mod tests {
} else {
None
};
let utxo_id = if utxo_id {
Some(UtxoId::new(rng.gen(), rng.gen()))
} else {
None
};
let tx_pointer = if tx_pointer {
Some(TxPointer::new(rng.gen(), rng.gen()))
} else {
None
};

let contract = Contract::from(op::ret(0x10).to_bytes().to_vec());

ChainConfig {
Expand All @@ -209,6 +260,10 @@ mod tests {
salt: Default::default(),
state,
balances,
tx_id: utxo_id.map(|utxo_id| *utxo_id.tx_id()),
output_index: utxo_id.map(|utxo_id| utxo_id.output_index()),
tx_pointer_block_height: tx_pointer.map(|p| p.block_height().into()),
tx_pointer_tx_idx: tx_pointer.map(|p| p.tx_index()),
}]),
..Default::default()
}),
Expand All @@ -219,8 +274,9 @@ mod tests {
fn test_config_coin_state() -> ChainConfig {
let mut rng = StdRng::seed_from_u64(1);
let tx_id: Option<Bytes32> = Some(rng.gen());
let output_index: Option<u64> = Some(rng.gen());
let output_index: Option<u8> = Some(rng.gen());
let block_created = Some(rng.next_u32().into());
let block_created_tx_idx = Some(rng.gen());
let maturity = Some(rng.next_u32().into());
let owner = rng.gen();
let amount = rng.gen();
Expand All @@ -231,7 +287,8 @@ mod tests {
coins: Some(vec![CoinConfig {
tx_id,
output_index,
block_created,
tx_pointer_block_height: block_created,
tx_pointer_tx_idx: block_created_tx_idx,
maturity,
owner,
amount,
Expand Down
5 changes: 3 additions & 2 deletions crates/chain-config/src/config/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,9 @@ impl ChainConfig {

CoinConfig {
tx_id: utxo_id.as_ref().map(|u| *u.tx_id()),
output_index: utxo_id.as_ref().map(|u| u.output_index() as u64),
block_created: None,
output_index: utxo_id.as_ref().map(|u| u.output_index()),
tx_pointer_block_height: None,
tx_pointer_tx_idx: None,
maturity: None,
owner: address,
amount,
Expand Down
15 changes: 10 additions & 5 deletions crates/chain-config/src/config/coin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ use fuel_core_types::{
Bytes32,
},
};

use serde::{
Deserialize,
Serialize,
Expand All @@ -36,11 +35,16 @@ pub struct CoinConfig {
pub tx_id: Option<Bytes32>,
#[serde_as(as = "Option<HexNumber>")]
#[serde(default)]
pub output_index: Option<u64>,
/// used if coin is forked from another chain to preserve id
pub output_index: Option<u8>,
/// used if coin is forked from another chain to preserve id & tx_pointer
#[serde_as(as = "Option<HexNumber>")]
#[serde(default)]
pub tx_pointer_block_height: Option<BlockHeight>,
/// used if coin is forked from another chain to preserve id & tx_pointer
/// The index of the originating tx within `tx_pointer_block_height`
#[serde_as(as = "Option<HexNumber>")]
#[serde(default)]
pub block_created: Option<BlockHeight>,
pub tx_pointer_tx_idx: Option<u16>,
#[serde_as(as = "Option<HexNumber>")]
#[serde(default)]
pub maturity: Option<BlockHeight>,
Expand All @@ -60,7 +64,8 @@ impl GenesisCommitment for CompressedCoin {
.chain(self.asset_id)
.chain((*self.maturity).to_be_bytes())
.chain([self.status as u8])
.chain((*self.block_created).to_be_bytes())
.chain(self.tx_pointer.block_height().to_be_bytes())
.chain(self.tx_pointer.tx_index().to_be_bytes())
.finalize();

Ok(coin_hash)
Expand Down
31 changes: 27 additions & 4 deletions crates/chain-config/src/config/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ use crate::serialization::{
HexNumber,
HexType,
};
use fuel_core_types::fuel_types::{
AssetId,
Bytes32,
Salt,
use fuel_core_types::{
blockchain::primitives::BlockHeight,
fuel_types::{
AssetId,
Bytes32,
Salt,
},
};
use serde::{
Deserialize,
Expand All @@ -30,4 +33,24 @@ pub struct ContractConfig {
#[serde_as(as = "Option<Vec<(HexType, HexNumber)>>")]
#[serde(default)]
pub balances: Option<Vec<(AssetId, u64)>>,
/// UtxoId: auto-generated if None
#[serde_as(as = "Option<HexType>")]
#[serde(default)]
pub tx_id: Option<Bytes32>,
/// UtxoId: auto-generated if None
#[serde_as(as = "Option<HexNumber>")]
#[serde(default)]
pub output_index: Option<u8>,
/// TxPointer: auto-generated if None
/// used if contract is forked from another chain to preserve id & tx_pointer
/// The block height that the contract was last used in
#[serde_as(as = "Option<HexNumber>")]
#[serde(default)]
pub tx_pointer_block_height: Option<BlockHeight>,
/// TxPointer: auto-generated if None
/// used if contract is forked from another chain to preserve id & tx_pointer
/// The index of the originating tx within `tx_pointer_block_height`
#[serde_as(as = "Option<HexNumber>")]
#[serde(default)]
pub tx_pointer_tx_idx: Option<u16>,
}
89 changes: 49 additions & 40 deletions crates/chain-config/src/serialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use fuel_core_types::{
BlockHeight,
DaBlockHeight,
},
fuel_asm::Word,
fuel_types::bytes::WORD_SIZE,
};
use serde::{
Expand All @@ -21,50 +20,12 @@ use std::convert::TryFrom;
/// Used for primitive number types which don't implement AsRef or TryFrom<&[u8]>
pub(crate) struct HexNumber;

impl SerializeAs<u64> for HexNumber {
fn serialize_as<S>(value: &u64, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let bytes = value.to_be_bytes();
serde_hex::serialize(bytes, serializer)
}
}

impl<'de> DeserializeAs<'de, Word> for HexNumber {
fn deserialize_as<D>(deserializer: D) -> Result<Word, D::Error>
where
D: Deserializer<'de>,
{
let mut bytes: Vec<u8> = serde_hex::deserialize(deserializer)?;
match bytes.len() {
len if len > WORD_SIZE => {
return Err(D::Error::custom(format!(
"value cant exceed {WORD_SIZE} bytes"
)))
}
len if len < WORD_SIZE => {
// pad if length < word size
bytes = (0..WORD_SIZE - len)
.map(|_| 0u8)
.chain(bytes.into_iter())
.collect();
}
_ => {}
}
// We've already verified the bytes.len == WORD_SIZE, force the conversion here.
Ok(Word::from_be_bytes(
bytes.try_into().expect("byte lengths checked"),
))
}
}

impl SerializeAs<BlockHeight> for HexNumber {
fn serialize_as<S>(value: &BlockHeight, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let number: u64 = (*value).into();
let number: u32 = (*value).into();
HexNumber::serialize_as(&number, serializer)
}
}
Expand Down Expand Up @@ -159,3 +120,51 @@ pub mod serde_hex {
Ok(result)
}
}

macro_rules! impl_hex_number {
($i:ident) => {
impl SerializeAs<$i> for HexNumber {
fn serialize_as<S>(value: &$i, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let bytes = value.to_be_bytes();
serde_hex::serialize(bytes, serializer)
}
}

impl<'de> DeserializeAs<'de, $i> for HexNumber {
fn deserialize_as<D>(deserializer: D) -> Result<$i, D::Error>
where
D: Deserializer<'de>,
{
const SIZE: usize = core::mem::size_of::<$i>();
let mut bytes: Vec<u8> = serde_hex::deserialize(deserializer)?;
match bytes.len() {
len if len > SIZE => {
return Err(D::Error::custom(format!(
"value cant exceed {WORD_SIZE} bytes"
)))
}
len if len < SIZE => {
// pad if length < word size
bytes = (0..SIZE - len)
.map(|_| 0u8)
.chain(bytes.into_iter())
.collect();
}
_ => {}
}
// We've already verified the bytes.len == WORD_SIZE, force the conversion here.
Ok($i::from_be_bytes(
bytes.try_into().expect("byte lengths checked"),
))
}
}
};
}

impl_hex_number!(u8);
impl_hex_number!(u16);
impl_hex_number!(u32);
impl_hex_number!(u64);
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ expression: json
"chain_name": "local_testnet",
"block_gas_limit": 1000000000,
"initial_state": {
"height": "0x0000000014c8be1f"
"height": "0x14c8be1f"
},
"transaction_parameters": {
"contract_max_size": 16777216,
Expand Down
Loading

0 comments on commit b69ac7e

Please sign in to comment.