From c519bb945023684196a076c93b32b8e637bd3e33 Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Sun, 1 Sep 2024 20:49:08 +0200 Subject: [PATCH] solana-trie: add support for witness account MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Witness of the trie is an account which stores the trie’s hash. Using Solana’s account delta hash this allows us to construct a proof of the root. --- common/cf-solana/src/header.rs | 11 +- .../programs/solana-ibc/src/storage.rs | 2 +- solana/trie-geyser/Cargo.lock | 185 ++++++++++++++---- solana/trie/src/account.rs | 1 + solana/trie/src/lib.rs | 80 ++++++-- solana/trie/src/witness.rs | 135 +++++++++++++ solana/witnessed-trie/Cargo.toml | 3 +- solana/witnessed-trie/src/api.rs | 65 +----- solana/witnessed-trie/src/contract.rs | 20 +- 9 files changed, 368 insertions(+), 134 deletions(-) create mode 100644 solana/trie/src/witness.rs diff --git a/common/cf-solana/src/header.rs b/common/cf-solana/src/header.rs index d705aa7f..01c1a41d 100644 --- a/common/cf-solana/src/header.rs +++ b/common/cf-solana/src/header.rs @@ -34,12 +34,11 @@ impl Header { /// trie and Solana block timestamp in seconds. /// /// Returns None if the witness account data has unexpected format - /// (e.g. it’s not 40-byte long). See `WitnessedData` in - /// `solana-witnessed-trie`. - // TODO(mina86): Ideally we would use wittrie::api::WitnessedData here but - // wittrie depends on Solana and we don’t want to introduce required Solana - // dependencies here. Moving WitnessData to a crate in common/ is an option - // but for the time being we’re duplicating the logic here. + /// (e.g. it’s not 40-byte long). See `witness::Data` in solana-trie. + // TODO(mina86): Ideally we would use solana_trie::witness::Data here but + // solana_trie depends on Solana and we don’t want to introduce required + // Solana dependencies here. Moving witness::Data to a crate in common/ is + // an option but for the time being we’re duplicating the logic here. pub fn decode_witness(&self) -> Option<(&CryptoHash, NonZeroU64)> { let data = self.witness_proof.account_hash_data.data().try_into().ok()?; diff --git a/solana/solana-ibc/programs/solana-ibc/src/storage.rs b/solana/solana-ibc/programs/solana-ibc/src/storage.rs index a57fac5c..178f810f 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/storage.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/storage.rs @@ -391,7 +391,7 @@ impl PrivateStorage { /// Provable storage, i.e. the trie, held in an account. pub type TrieAccount<'a, 'b> = - solana_trie::TrieAccount>; + solana_trie::TrieAccount<'a, solana_trie::ResizableAccount<'a, 'b>>; /// Checks contents of given unchecked account and returns a trie if it’s valid. /// diff --git a/solana/trie-geyser/Cargo.lock b/solana/trie-geyser/Cargo.lock index 9a487b64..8c51233d 100644 --- a/solana/trie-geyser/Cargo.lock +++ b/solana/trie-geyser/Cargo.lock @@ -237,7 +237,7 @@ dependencies = [ "bincode", "borsh 0.10.3", "bytemuck", - "solana-program", + "solana-program 2.0.0", "thiserror", ] @@ -864,7 +864,7 @@ dependencies = [ "prost-build", "proto-utils", "serde", - "solana-program", + "solana-program 2.0.0", "stdx", "trie-ids", ] @@ -2115,6 +2115,7 @@ dependencies = [ "rayon", "serde", "sha2 0.10.8", + "solana-program 1.18.23", "stdx", ] @@ -3379,8 +3380,8 @@ dependencies = [ "smallvec", "solana-bucket-map", "solana-config-program", - "solana-frozen-abi", - "solana-frozen-abi-macro", + "solana-frozen-abi 2.0.0", + "solana-frozen-abi-macro 2.0.0", "solana-measure", "solana-metrics", "solana-nohash-hasher", @@ -3447,6 +3448,31 @@ dependencies = [ "solana-sdk", ] +[[package]] +name = "solana-frozen-abi" +version = "1.18.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bfcde2fc6946c99c7e3400fadd04d1628d675bfd66cb34d461c0f3224bd27d1" +dependencies = [ + "block-buffer 0.10.4", + "bs58 0.4.0", + "bv", + "either", + "generic-array", + "im", + "lazy_static", + "log", + "memmap2", + "rustc_version", + "serde", + "serde_bytes", + "serde_derive", + "sha2 0.10.8", + "solana-frozen-abi-macro 1.18.23", + "subtle", + "thiserror", +] + [[package]] name = "solana-frozen-abi" version = "2.0.0" @@ -3465,11 +3491,23 @@ dependencies = [ "serde_bytes", "serde_derive", "sha2 0.10.8", - "solana-frozen-abi-macro", + "solana-frozen-abi-macro 2.0.0", "subtle", "thiserror", ] +[[package]] +name = "solana-frozen-abi-macro" +version = "1.18.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5024d241425f4e99f112ee03bfa89e526c86c7ca9bd7e13448a7f2dffb7e060" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.71", +] + [[package]] name = "solana-frozen-abi-macro" version = "2.0.0" @@ -3543,6 +3581,61 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b8a731ed60e89177c8a7ab05fe0f1511cedd3e70e773f288f9de33a9cfdc21e" +[[package]] +name = "solana-program" +version = "1.18.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76056fecde0fe0ece8b457b719729c17173333471c72ad41969982975a10d6e0" +dependencies = [ + "ark-bn254", + "ark-ec", + "ark-ff", + "ark-serialize", + "base64 0.21.7", + "bincode", + "bitflags 2.6.0", + "blake3", + "borsh 0.10.3", + "borsh 0.9.3", + "borsh 1.5.1", + "bs58 0.4.0", + "bv", + "bytemuck", + "cc", + "console_error_panic_hook", + "console_log", + "curve25519-dalek", + "getrandom 0.2.15", + "itertools", + "js-sys", + "lazy_static", + "libc", + "libsecp256k1", + "light-poseidon", + "log", + "memoffset", + "num-bigint 0.4.6", + "num-derive 0.4.2", + "num-traits", + "parking_lot", + "rand 0.8.5", + "rustc_version", + "rustversion", + "serde", + "serde_bytes", + "serde_derive", + "serde_json", + "sha2 0.10.8", + "sha3 0.10.8", + "solana-frozen-abi 1.18.23", + "solana-frozen-abi-macro 1.18.23", + "solana-sdk-macro 1.18.23", + "thiserror", + "tiny-bip39", + "wasm-bindgen", + "zeroize", +] + [[package]] name = "solana-program" version = "2.0.0" @@ -3588,9 +3681,9 @@ dependencies = [ "serde_json", "sha2 0.10.8", "sha3 0.10.8", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-sdk-macro", + "solana-frozen-abi 2.0.0", + "solana-frozen-abi-macro 2.0.0", + "solana-sdk-macro 2.0.0", "thiserror", "tiny-bip39", "wasm-bindgen", @@ -3615,8 +3708,8 @@ dependencies = [ "rand 0.8.5", "rustc_version", "serde", - "solana-frozen-abi", - "solana-frozen-abi-macro", + "solana-frozen-abi 2.0.0", + "solana-frozen-abi-macro 2.0.0", "solana-measure", "solana-metrics", "solana-sdk", @@ -3678,16 +3771,29 @@ dependencies = [ "sha2 0.10.8", "sha3 0.10.8", "siphasher", - "solana-frozen-abi", - "solana-frozen-abi-macro", + "solana-frozen-abi 2.0.0", + "solana-frozen-abi-macro 2.0.0", "solana-logger", - "solana-program", - "solana-sdk-macro", + "solana-program 2.0.0", + "solana-sdk-macro 2.0.0", "thiserror", "uriparse", "wasm-bindgen", ] +[[package]] +name = "solana-sdk-macro" +version = "1.18.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8613ca80150f7e277e773620ba65d2c5fcc3a08eb8026627d601421ab43aef" +dependencies = [ + "bs58 0.4.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.71", +] + [[package]] name = "solana-sdk-macro" version = "2.0.0" @@ -3730,8 +3836,8 @@ dependencies = [ "percentage", "rustc_version", "solana-bpf-loader-program", - "solana-frozen-abi", - "solana-frozen-abi-macro", + "solana-frozen-abi 2.0.0", + "solana-frozen-abi-macro 2.0.0", "solana-loader-v4-program", "solana-measure", "solana-metrics", @@ -3777,6 +3883,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "solana-trie" +version = "0.0.3" +dependencies = [ + "bytemuck", + "lib", + "memory", + "sealable-trie", + "solana-program 1.18.23", + "stdx", +] + [[package]] name = "solana-vote-program" version = "2.0.0" @@ -3789,10 +3907,10 @@ dependencies = [ "rustc_version", "serde", "serde_derive", - "solana-frozen-abi", - "solana-frozen-abi-macro", + "solana-frozen-abi 2.0.0", + "solana-frozen-abi-macro 2.0.0", "solana-metrics", - "solana-program", + "solana-program 2.0.0", "solana-program-runtime", "solana-sdk", "thiserror", @@ -3807,7 +3925,8 @@ dependencies = [ "cf-solana", "derive_more", "lib", - "solana-program", + "solana-program 2.0.0", + "solana-trie", "stdx", "strum 0.25.0", ] @@ -3833,7 +3952,7 @@ dependencies = [ "serde", "serde_json", "sha3 0.9.1", - "solana-program", + "solana-program 2.0.0", "solana-sdk", "subtle", "thiserror", @@ -3885,7 +4004,7 @@ dependencies = [ "borsh 1.5.1", "num-derive 0.4.2", "num-traits", - "solana-program", + "solana-program 2.0.0", "spl-token", "spl-token-2022", "thiserror", @@ -3898,7 +4017,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a38ea8b6dedb7065887f12d62ed62c1743aa70749e8558f963609793f6fb12bc" dependencies = [ "bytemuck", - "solana-program", + "solana-program 2.0.0", "spl-discriminator-derive", ] @@ -3932,7 +4051,7 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0dba2f2bb6419523405d21c301a32c9f9568354d4742552e7972af801f4bdb3" dependencies = [ - "solana-program", + "solana-program 2.0.0", ] [[package]] @@ -3944,7 +4063,7 @@ dependencies = [ "borsh 1.5.1", "bytemuck", "bytemuck_derive", - "solana-program", + "solana-program 2.0.0", "solana-zk-token-sdk", "spl-program-error", ] @@ -3957,7 +4076,7 @@ checksum = "d7b28bed65356558133751cc32b48a7a5ddfc59ac4e941314630bbed1ac10532" dependencies = [ "num-derive 0.4.2", "num-traits", - "solana-program", + "solana-program 2.0.0", "spl-program-error-derive", "thiserror", ] @@ -3981,7 +4100,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37a75a5f0fcc58126693ed78a17042e9dc53f07e357d6be91789f7d62aff61a4" dependencies = [ "bytemuck", - "solana-program", + "solana-program 2.0.0", "spl-discriminator", "spl-pod", "spl-program-error", @@ -3999,7 +4118,7 @@ dependencies = [ "num-derive 0.4.2", "num-traits", "num_enum", - "solana-program", + "solana-program 2.0.0", "thiserror", ] @@ -4014,7 +4133,7 @@ dependencies = [ "num-derive 0.4.2", "num-traits", "num_enum", - "solana-program", + "solana-program 2.0.0", "solana-security-txt", "solana-zk-token-sdk", "spl-memo", @@ -4034,7 +4153,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df8752b85a5ecc1d9f3a43bce3dd9a6a053673aacf5deb513d1cbb88d3534ffd" dependencies = [ "bytemuck", - "solana-program", + "solana-program 2.0.0", "spl-discriminator", "spl-pod", "spl-program-error", @@ -4047,7 +4166,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6c2318ddff97e006ed9b1291ebec0750a78547f870f62a69c56fe3b46a5d8fc" dependencies = [ "borsh 1.5.1", - "solana-program", + "solana-program 2.0.0", "spl-discriminator", "spl-pod", "spl-program-error", @@ -4062,7 +4181,7 @@ checksum = "a110f33d941275d9f868b96daaa993f1e73b6806cc8836e43075b4d3ad8338a7" dependencies = [ "arrayref", "bytemuck", - "solana-program", + "solana-program 2.0.0", "spl-discriminator", "spl-pod", "spl-program-error", @@ -4077,7 +4196,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdcd73ec187bc409464c60759232e309f83b52a18a9c5610bf281c9c6432918c" dependencies = [ "bytemuck", - "solana-program", + "solana-program 2.0.0", "spl-discriminator", "spl-pod", "spl-program-error", diff --git a/solana/trie/src/account.rs b/solana/trie/src/account.rs index 2dc06106..5053ab99 100644 --- a/solana/trie/src/account.rs +++ b/solana/trie/src/account.rs @@ -7,6 +7,7 @@ use solana_program::rent::Rent; use solana_program::system_instruction::MAX_PERMITTED_DATA_LENGTH; use solana_program::sysvar::Sysvar; +/// An account backing a Trie which can be resized. #[derive(Debug)] pub struct ResizableAccount<'a, 'info> { account: &'a AccountInfo<'info>, diff --git a/solana/trie/src/lib.rs b/solana/trie/src/lib.rs index 9e6b137e..6e7c5e11 100644 --- a/solana/trie/src/lib.rs +++ b/solana/trie/src/lib.rs @@ -1,3 +1,4 @@ +use core::cell::RefMut; use core::mem::ManuallyDrop; #[cfg(test)] @@ -5,11 +6,13 @@ use pretty_assertions::assert_eq; use solana_program::account_info::AccountInfo; use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; +use solana_program::sysvar::Sysvar; mod account; mod alloc; mod data_ref; mod header; +pub mod witness; pub use account::ResizableAccount; pub use data_ref::DataRef; @@ -17,12 +20,14 @@ pub use sealable_trie::Trie; /// Trie stored in a Solana account. -#[derive(Debug)] -pub struct TrieAccount( - ManuallyDrop>>, -); +pub struct TrieAccount<'a, D: DataRef + Sized>(ManuallyDrop>); -impl TrieAccount { +struct Inner<'a, D: DataRef + Sized> { + trie: sealable_trie::Trie>, + witness: Option>, +} + +impl<'a, D: DataRef + Sized> TrieAccount<'a, D> { /// Creates a new TrieAccount from data in an account. /// /// If the data in the account isn’t initialised (i.e. has zero @@ -30,11 +35,31 @@ impl TrieAccount { pub fn new(data: D) -> Option { let (alloc, root) = alloc::Allocator::new(data)?; let trie = sealable_trie::Trie::from_parts(alloc, root.0, root.1); - Some(Self(ManuallyDrop::new(trie))) + Some(Self(ManuallyDrop::new(Inner { trie, witness: None }))) + } + + /// Returns witness data if any. + pub fn witness(&self) -> Option<&RefMut<'a, witness::Data>> { + self.0.witness.as_ref() + } + + /// Sets the witness account. + /// + /// `witness` must be initialised, owned by `owner` and exactly 40 bytes + /// (see [`witness::Data::SIZE`]). Witness is updated automatically once + /// this object is dropped. + pub fn with_witness_account<'info>( + mut self, + witness: &'a AccountInfo<'info>, + owner: &Pubkey, + ) -> Result { + check_account(witness, owner)?; + self.0.witness = Some(witness::Data::from_account_info(witness)?); + Ok(self) } } -impl<'a, 'b> TrieAccount> { +impl<'a, 'info> TrieAccount<'a, RefMut<'a, &'info mut [u8]>> { /// Creates a new TrieAccount from data in an account specified by given /// info. /// @@ -43,7 +68,7 @@ impl<'a, 'b> TrieAccount> { /// Created TrieAccount holds exclusive reference on the account’s data thus /// no other code can access it while this object is alive. pub fn from_account_info( - account: &'a AccountInfo<'b>, + account: &'a AccountInfo<'info>, owner: &Pubkey, ) -> Result { check_account(account, owner)?; @@ -52,7 +77,7 @@ impl<'a, 'b> TrieAccount> { } } -impl<'a, 'b> TrieAccount> { +impl<'a, 'info> TrieAccount<'a, ResizableAccount<'a, 'info>> { /// Creates a new TrieAccount from data in an account specified by given /// info. /// @@ -64,9 +89,9 @@ impl<'a, 'b> TrieAccount> { /// If the account needs to increase in size, `payer`’s account is used to /// transfer lamports necessary to keep the account rent-exempt. pub fn from_account_with_payer( - account: &'a AccountInfo<'b>, + account: &'a AccountInfo<'info>, owner: &Pubkey, - payer: &'a AccountInfo<'b>, + payer: &'a AccountInfo<'info>, ) -> Result { check_account(account, owner)?; let data = ResizableAccount::new(account, payer)?; @@ -90,13 +115,22 @@ fn check_account( } } -impl core::ops::Drop for TrieAccount { +impl<'a, D: DataRef + Sized> core::ops::Drop for TrieAccount<'a, D> { /// Updates the header in the Solana account. fn drop(&mut self) { // SAFETY: Once we’re done with self.0 we are dropped and no one else is // going to have access to self.0. - let trie = unsafe { ManuallyDrop::take(&mut self.0) }; + let Inner { trie, witness } = + unsafe { ManuallyDrop::take(&mut self.0) }; let (mut alloc, root_ptr, root_hash) = trie.into_parts(); + + // Update witness + if let Some(mut witness) = witness { + let clock = solana_program::clock::Clock::get().unwrap(); + *witness = witness::Data::new(root_hash, &clock); + } + + // Update header let hdr = header::Header { root_ptr, root_hash, @@ -108,16 +142,28 @@ impl core::ops::Drop for TrieAccount { } } -impl core::ops::Deref for TrieAccount { +impl<'a, D: DataRef> core::ops::Deref for TrieAccount<'a, D> { type Target = sealable_trie::Trie>; - fn deref(&self) -> &Self::Target { &self.0 } + fn deref(&self) -> &Self::Target { &self.0.trie } } -impl core::ops::DerefMut for TrieAccount { - fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } +impl<'a, D: DataRef> core::ops::DerefMut for TrieAccount<'a, D> { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0.trie } } +impl core::fmt::Debug for TrieAccount<'_, D> { + fn fmt(&self, fmtr: &mut core::fmt::Formatter) -> core::fmt::Result { + let mut fmtr = fmtr.debug_struct("TrieAccount"); + fmtr.field("trie", &self.0.trie); + if let Some(witness) = self.0.witness.as_ref() { + let root: &witness::Data = witness; + fmtr.field("witness", root); + } + fmtr.finish() + } +} + #[test] fn test_trie_sanity() { const ONE: lib::hash::CryptoHash = lib::hash::CryptoHash([1; 32]); diff --git a/solana/trie/src/witness.rs b/solana/trie/src/witness.rs new file mode 100644 index 00000000..253045c1 --- /dev/null +++ b/solana/trie/src/witness.rs @@ -0,0 +1,135 @@ +use core::cell::RefMut; + +use lib::hash::CryptoHash; +use solana_program::account_info::AccountInfo; +use solana_program::program_error::ProgramError; + +/// Encoding of the data in witness account. +#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(C)] +pub struct Data { + /// The root of the sealable trie. + pub trie_root: CryptoHash, + + /// Rest of the witness account encoding Solana block timestamp. + /// + /// The timestamp is encoded using only six bytes. The seventh byte is + /// a single byte of a slot number and the last byte is always zero. + /// + /// Single byte of slot is included so that data of the account changes for + /// every slot even if two slots are created at the same second. + /// + /// The last byte is zero for potential future use. + rest: [u8; 8], +} + +impl Data { + /// Size of the witness account data. + pub const SIZE: usize = core::mem::size_of::(); + + /// Formats new witness account data with timestamp and slot number taken + /// from Solana clock. + pub fn new( + trie_root: CryptoHash, + clock: &solana_program::clock::Clock, + ) -> Self { + let mut rest = clock.unix_timestamp.to_le_bytes(); + rest[6] = clock.slot as u8; + rest[7] = 0; + Self { trie_root, rest } + } + + /// Returns root of the saleable trie and Solana block timestamp in seconds. + /// + /// Returns `Err` if the account data is malformed. The error holds + /// reference to the full data of the account. This happens if the last + /// byte of the data is non-zero. + pub fn decode(&self) -> Result<(&CryptoHash, u64), &[u8; Data::SIZE]> { + if self.rest[7] != 0 { + return Err(bytemuck::cast_ref(self)); + } + let timestamp = u64::from_le_bytes(self.rest) & 0xffff_ffff_ffff; + Ok((&self.trie_root, timestamp)) + } + + /// Creates a new borrowed reference to the data held in given account. + /// + /// Checks that the account is mutable and exactly [`Data::SIZE`] bytes. If + /// so, updates the timestamp and slot of the account and returns reference + /// to the trie’s root held inside of the account. + pub(crate) fn from_account_info<'a>( + witness: &'a AccountInfo<'_>, + ) -> Result, ProgramError> { + RefMut::filter_map(witness.try_borrow_mut_data()?, |data| { + let data: &mut [u8] = data; + <&mut Data>::try_from(data).ok() + }) + .map_err(|_| ProgramError::InvalidAccountData) + } +} + + + +impl From<[u8; Data::SIZE]> for Data { + fn from(bytes: [u8; Data::SIZE]) -> Self { bytemuck::cast(bytes) } +} + +impl<'a> From<&'a [u8; Data::SIZE]> for &'a Data { + fn from(bytes: &'a [u8; Data::SIZE]) -> Self { bytemuck::cast_ref(bytes) } +} + +impl<'a> From<&'a [u8; Data::SIZE]> for Data { + fn from(bytes: &'a [u8; Data::SIZE]) -> Self { *bytemuck::cast_ref(bytes) } +} + +impl<'a> TryFrom<&'a [u8]> for &'a Data { + type Error = core::array::TryFromSliceError; + + fn try_from(bytes: &'a [u8]) -> Result { + <&[u8; Data::SIZE]>::try_from(bytes).map(Self::from) + } +} + +impl<'a> TryFrom<&'a [u8]> for Data { + type Error = core::array::TryFromSliceError; + + fn try_from(bytes: &'a [u8]) -> Result { + <&[u8; Data::SIZE]>::try_from(bytes).map(Data::from) + } +} + +impl<'a> From<&'a mut [u8; Data::SIZE]> for &'a mut Data { + fn from(bytes: &'a mut [u8; Data::SIZE]) -> Self { + bytemuck::cast_mut(bytes) + } +} + +impl<'a> TryFrom<&'a mut [u8]> for &'a mut Data { + type Error = core::array::TryFromSliceError; + + fn try_from(bytes: &'a mut [u8]) -> Result { + <&mut [u8; Data::SIZE]>::try_from(bytes).map(Self::from) + } +} + + +impl From for [u8; Data::SIZE] { + fn from(data: Data) -> Self { bytemuck::cast(data) } +} + +impl<'a> From<&'a Data> for &'a [u8; Data::SIZE] { + fn from(bytes: &'a Data) -> Self { bytes.as_ref() } +} + +impl<'a> From<&'a mut Data> for &'a mut [u8; Data::SIZE] { + fn from(bytes: &'a mut Data) -> Self { bytes.as_mut() } +} + + +impl AsRef<[u8; Data::SIZE]> for Data { + fn as_ref(&self) -> &[u8; Data::SIZE] { bytemuck::cast_ref(self) } +} + +impl AsMut<[u8; Data::SIZE]> for Data { + fn as_mut(&mut self) -> &mut [u8; Data::SIZE] { bytemuck::cast_mut(self) } +} diff --git a/solana/witnessed-trie/Cargo.toml b/solana/witnessed-trie/Cargo.toml index cf723cb2..f1c98bde 100644 --- a/solana/witnessed-trie/Cargo.toml +++ b/solana/witnessed-trie/Cargo.toml @@ -21,7 +21,7 @@ cf-solana = { workspace = true, optional = true } lib.workspace = true memory = { workspace = true, optional = true } sealable-trie = { workspace = true, optional = true } -solana-trie = { workspace = true, optional = true } +solana-trie.workspace = true stdx.workspace = true [dev-dependencies] @@ -45,5 +45,4 @@ contract = [ "hex", "memory", "sealable-trie", - "solana-trie", ] diff --git a/solana/witnessed-trie/src/api.rs b/solana/witnessed-trie/src/api.rs index 04a19648..408492e5 100644 --- a/solana/witnessed-trie/src/api.rs +++ b/solana/witnessed-trie/src/api.rs @@ -2,6 +2,7 @@ use lib::hash::CryptoHash; use solana_program::pubkey::{Pubkey, MAX_SEED_LEN}; #[cfg(all(not(feature = "api"), feature = "api2"))] use solana_program_2 as solana_program; +pub use solana_trie::witness::Data as WitnessData; use crate::utils; @@ -220,66 +221,6 @@ impl From for ParseError { } -/// Encoding of the data in witness account. -#[derive(Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)] -#[repr(C)] -pub struct WitnessedData { - /// The root of the sealable trie. - trie_root: [u8; 32], - - /// Rest of the witness account encoding Solana block timestamp. - /// - /// The timestamp is encoded using only six bytes. The seventh byte is - /// a single byte of a slot number and the last byte is always zero. - /// - /// Single byte of slot is included so that data of the account changes for - /// every slot even if two slots are created at the same second. - /// - /// The last byte is zero for potential future use. - rest: [u8; 8], -} - -impl WitnessedData { - /// Formats new witness account data with timestamp and slot number taken - /// from Solana clock. - pub fn new( - trie_root: &CryptoHash, - clock: &solana_program::clock::Clock, - ) -> Self { - let mut rest = clock.unix_timestamp.to_le_bytes(); - rest[6] = clock.slot as u8; - rest[7] = 0; - Self { trie_root: trie_root.into(), rest } - } - - /// Returns root of the saleable trie and Solana block timestamp in seconds. - /// - /// Returns `Err` if the account data is malformed. The error holds - /// reference to the full data of the account. This happens if the last - /// byte of the data is non-zero. - pub fn decode(&self) -> Result<(&CryptoHash, u64), &[u8; 40]> { - if self.rest[7] != 0 { - return Err(bytemuck::cast_ref(self)); - } - let timestamp = u64::from_le_bytes(self.rest) & 0xffff_ffff_ffff; - Ok(((&self.trie_root).into(), timestamp)) - } -} - -impl From<[u8; 40]> for WitnessedData { - fn from(bytes: [u8; 40]) -> Self { bytemuck::cast(bytes) } -} - -impl From for [u8; 40] { - fn from(data: WitnessedData) -> Self { bytemuck::cast(data) } -} - -impl AsRef<[u8; 40]> for WitnessedData { - fn as_ref(&self) -> &[u8; 40] { bytemuck::cast_ref(self) } -} - - - /// Value returned from the contract in return data. /// /// It holds information about the witness account needed to compute its hash @@ -300,7 +241,7 @@ pub struct ReturnData { pub lamports: [u8; 8], pub rent_epoch: [u8; 8], #[deref] - pub data: WitnessedData, + pub data: WitnessData, } impl ReturnData { @@ -346,7 +287,7 @@ fn test_hash_account() { solana_program::pubkey!("ENEWG4MWwJQUfJxDgqarJQ1bf2P4fADsCYsPCjvLRaa2"); const OWNER: Pubkey = solana_program::pubkey!("4FjVmuvPYnE1fqBtvjGh5JF7QDwUmyBZ5wv1uygHvTey"); - const DATA: [u8; 40] = [ + const DATA: [u8; WitnessData::SIZE] = [ 0xa9, 0x1e, 0x26, 0xed, 0x91, 0x28, 0xdd, 0x6f, 0xed, 0xa2, 0xe8, 0x6a, 0xf7, 0x9b, 0xe2, 0xe1, 0x77, 0x89, 0xaf, 0x08, 0x72, 0x08, 0x69, 0x22, 0x13, 0xd3, 0x95, 0x5e, 0x07, 0x4c, 0xee, 0x9c, 1, 2, 3, 4, 5, 6, 7, 8, diff --git a/solana/witnessed-trie/src/contract.rs b/solana/witnessed-trie/src/contract.rs index 42616062..06d22229 100644 --- a/solana/witnessed-trie/src/contract.rs +++ b/solana/witnessed-trie/src/contract.rs @@ -1,7 +1,6 @@ use solana_program::account_info::AccountInfo; use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; -use solana_program::sysvar::Sysvar; use crate::{accounts, api, utils}; @@ -40,6 +39,7 @@ pub(crate) fn process_instruction( ) -> Result { let data = api::Data::from_slice(instruction)?; + // Get the accounts (trie root and witness) let (mut trie, witness) = { let accounts = &mut accounts.iter(); let payer = accounts::get_payer(accounts)?; @@ -52,7 +52,8 @@ pub(crate) fn process_instruction( )?; let witness = accounts::get_witness(payer, accounts, program_id, root)?; let trie = solana_trie::TrieAccount::new(root.try_borrow_mut_data()?) - .ok_or(ProgramError::InvalidAccountData)?; + .ok_or(ProgramError::InvalidAccountData)? + .with_witness_account(witness, program_id)?; (trie, witness) }; @@ -70,22 +71,15 @@ pub(crate) fn process_instruction( })?; } - // Update witness - let clock = solana_program::clock::Clock::get()?; - let data = api::WitnessedData::new(trie.hash(), &clock); - - { - let mut dst = witness.try_borrow_mut_data()?; - let dst: &mut [u8] = &mut dst; - let dst: &mut [u8; 40] = dst.try_into().unwrap(); - *dst = data.into(); - } + // Drop the trie so that witness is updated. + core::mem::drop(trie); // Return enough information so that witness account can be hashed. let ret = api::ReturnData { lamports: witness.lamports().to_le_bytes(), rent_epoch: witness.rent_epoch.to_le_bytes(), - data, + data: api::WitnessData::try_from(&**witness.try_borrow_data()?) + .unwrap(), }; solana_program::program::set_return_data(bytemuck::bytes_of(&ret));