Skip to content

Commit 87f57f8

Browse files
committed
Add FFI for block state managed by rust
1 parent 9b1274a commit 87f57f8

File tree

9 files changed

+581
-66
lines changed

9 files changed

+581
-66
lines changed

concordium-consensus/src/Concordium/PLTScheduler.hs

Lines changed: 53 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
{-# LANGUAGE OverloadedStrings #-}
2+
{-# LANGUAGE ScopedTypeVariables #-}
23

34
-- |
45
-- Bindings into the @plt-scheduler@ Rust library exposing safe wrappers.
56
--
67
-- Each foreign imported function must match the signature of functions found in @plt-scheduler/src/ffi.rs@.
78
module Concordium.PLTScheduler (
8-
PLTBlockState,
9-
initialPLTBlockState,
109
executeTransaction,
1110
) where
1211

@@ -16,55 +15,72 @@ import qualified Data.Word as Word
1615
import qualified Foreign as FFI
1716
import qualified Foreign.C.Types as FFI
1817

18+
import qualified Concordium.PLTScheduler.PLTBlockState as PLTBlockState
19+
import qualified Concordium.Types as Types
20+
1921
-- | Execute a transaction payload modifying the `block_state` accordingly.
20-
-- The caller must ensure to rollback state changes in case of the transaction being rejected.
2122
--
2223
-- See @execute_transaction@ in @plt-scheduler@ rust crate for details.
2324
executeTransaction ::
2425
-- | Block state to mutate.
25-
PLTBlockState ->
26+
PLTBlockState.PLTBlockState ->
2627
-- | Transaction payload byte string.
2728
BS.ByteString ->
2829
-- | The events produced or the reject reason.
29-
IO (Either () ())
30-
executeTransaction blockState transactionPayload = do
31-
statusCode <- withPLTBlockState blockState $ \blockStatePtr ->
32-
BS.unsafeUseAsCStringLen transactionPayload $ \(transactionPayloadPtr, transactionPayloadLen) -> do
33-
ffiExecuteTransaction blockStatePtr (FFI.castPtr transactionPayloadPtr) (fromIntegral transactionPayloadLen)
34-
case statusCode of
35-
0 -> return $ Right ()
36-
1 -> return $ Left ()
37-
_ -> error "Unexpected status code from calling 'ffiExecuteTransaction'"
30+
IO ExecutionOutcome
31+
executeTransaction blockState transactionPayload =
32+
FFI.alloca $ \usedEnergyOut ->
33+
FFI.alloca $ \updatedBlockStatePtrOut -> do
34+
statusCode <- PLTBlockState.withPLTBlockState blockState $ \blockStatePtr ->
35+
BS.unsafeUseAsCStringLen transactionPayload $
36+
\(transactionPayloadPtr, transactionPayloadLen) ->
37+
ffiExecuteTransaction
38+
blockStatePtr
39+
(FFI.castPtr transactionPayloadPtr)
40+
(fromIntegral transactionPayloadLen)
41+
updatedBlockStatePtrOut
42+
usedEnergyOut
43+
usedEnergy :: Types.Energy <- fromIntegral <$> FFI.peek usedEnergyOut
44+
status <- case statusCode of
45+
0 -> do
46+
updatedBlockState <- FFI.peek updatedBlockStatePtrOut >>= PLTBlockState.wrapFFIPtr
47+
return $
48+
Right
49+
ExecutionAccepts
50+
{ eaUpdatedPLTBlockState = updatedBlockState,
51+
eaEvents = ()
52+
}
53+
1 -> return $ Left ()
54+
_ -> error "Unexpected status code from calling 'ffiExecuteTransaction'"
55+
return
56+
ExecutionOutcome
57+
{ erUsedEnergy = usedEnergy,
58+
erStatus = status
59+
}
60+
61+
data ExecutionOutcome = ExecutionOutcome
62+
{ -- | The amount of energy used during the execution.
63+
erUsedEnergy :: Types.Energy,
64+
-- | The resulting execution status.
65+
erStatus :: Either () ExecutionAccepts
66+
}
67+
68+
data ExecutionAccepts = ExecutionAccepts
69+
{ -- | The updated PLT block state after the execution
70+
eaUpdatedPLTBlockState :: PLTBlockState.PLTBlockState,
71+
eaEvents :: ()
72+
}
3873

3974
foreign import ccall "ffi_execute_transaction"
4075
ffiExecuteTransaction ::
41-
FFI.Ptr PLTBlockState ->
76+
FFI.Ptr PLTBlockState.PLTBlockState ->
4277
-- | Pointer to transaction payload bytes.
4378
FFI.Ptr Word.Word8 ->
4479
-- | Byte length of transaction payload.
4580
FFI.CSize ->
81+
-- | Output location for the updated block state.
82+
FFI.Ptr (FFI.Ptr PLTBlockState.PLTBlockState) ->
83+
-- | Output location for the used energy.
84+
FFI.Ptr Word.Word64 ->
4685
-- | Status code
4786
IO Word.Word8
48-
49-
-- Block state FFI
50-
51-
-- | Opaque pointer to the PLT block state managed by the rust library.
52-
--
53-
-- Memory is deallocated using a finalizer.
54-
newtype PLTBlockState = PLTBlockState (FFI.ForeignPtr PLTBlockState)
55-
56-
-- | Allocate new initial block state
57-
initialPLTBlockState :: IO PLTBlockState
58-
initialPLTBlockState = do
59-
state <- ffiInitialPLTBlockState
60-
PLTBlockState <$> FFI.newForeignPtr ffiFreePLTBlockState state
61-
62-
foreign import ccall "ffi_initial_plt_block_state" ffiInitialPLTBlockState :: IO (FFI.Ptr PLTBlockState)
63-
foreign import ccall unsafe "&ffi_free_plt_block_state" ffiFreePLTBlockState :: FFI.FinalizerPtr PLTBlockState
64-
65-
-- | Get temporary access to the block state pointer. The pointer should not be
66-
-- leaked from the computation.
67-
--
68-
-- This ensures the finalizer is not called until the computation is over.
69-
withPLTBlockState :: PLTBlockState -> (FFI.Ptr PLTBlockState -> IO a) -> IO a
70-
withPLTBlockState (PLTBlockState foreignPtr) = FFI.withForeignPtr foreignPtr
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
{-# LANGUAGE DerivingVia #-}
2+
{-# LANGUAGE ScopedTypeVariables #-}
3+
4+
-- | Bindings to the PLT block state implementation found in @plt-scheduler/block_state.rs@.
5+
module Concordium.PLTScheduler.PLTBlockState (
6+
PLTBlockState,
7+
empty,
8+
wrapFFIPtr,
9+
withPLTBlockState,
10+
migrate,
11+
Hash,
12+
-- | Get the inner @SHA256.Hash@.
13+
innerSha256Hash,
14+
) where
15+
16+
import qualified Data.Serialize as Serialize
17+
import qualified Foreign as FFI
18+
19+
import qualified Concordium.Crypto.SHA256 as SHA256
20+
import qualified Concordium.GlobalState.ContractStateFFIHelpers as FFI
21+
import qualified Concordium.GlobalState.Persistent.BlobStore as BlobStore
22+
import qualified Concordium.Types.HashableTo as Hashable
23+
import Control.Monad.Trans (lift, liftIO)
24+
import qualified Data.FixedByteString as FixedByteString
25+
26+
-- | Opaque pointer to a immutable PLT block state save-point managed by the rust library.
27+
--
28+
-- Memory is deallocated using a finalizer.
29+
newtype PLTBlockState = PLTBlockState (FFI.ForeignPtr PLTBlockState)
30+
31+
-- | Helper function to convert a raw pointer passed by the Rust library into a `PLTBlockState` object.
32+
wrapFFIPtr :: FFI.Ptr PLTBlockState -> IO PLTBlockState
33+
wrapFFIPtr blockStatePtr = PLTBlockState <$> FFI.newForeignPtr ffiFreePLTBlockState blockStatePtr
34+
35+
-- | Deallocate a pointer to `PLTBlockState`.
36+
foreign import ccall unsafe "&ffi_free_plt_block_state"
37+
ffiFreePLTBlockState :: FFI.FinalizerPtr PLTBlockState
38+
39+
-- | Allocate new empty block state
40+
empty :: (BlobStore.MonadBlobStore m) => m PLTBlockState
41+
empty = liftIO $ do
42+
state <- ffiEmptyPLTBlockState
43+
wrapFFIPtr state
44+
45+
foreign import ccall "ffi_empty_plt_block_state"
46+
ffiEmptyPLTBlockState :: IO (FFI.Ptr PLTBlockState)
47+
48+
instance (BlobStore.MonadBlobStore m) => BlobStore.BlobStorable m PLTBlockState where
49+
load = do
50+
br :: BlobStore.BlobRef PLTBlockState <- Serialize.get
51+
pure $! do
52+
loadCallback <- fst <$> BlobStore.getCallbacks
53+
liftIO $! do
54+
blockState <- ffiLoadPLTBlockState loadCallback br
55+
wrapFFIPtr blockState
56+
storeUpdate pltBlockState = do
57+
storeCallback <- snd <$> BlobStore.getCallbacks
58+
blobRef <- liftIO $ withPLTBlockState pltBlockState $ ffiStorePLTBlockState storeCallback
59+
return (Serialize.put blobRef, pltBlockState)
60+
61+
-- | Load PLT block state from the given disk reference.
62+
foreign import ccall "ffi_load_plt_block_state"
63+
ffiLoadPLTBlockState ::
64+
-- | Called to read data from blob store.
65+
FFI.LoadCallback ->
66+
-- | Reference in the blob store.
67+
BlobStore.BlobRef PLTBlockState ->
68+
-- | Pointer to the loaded block state.
69+
IO (FFI.Ptr PLTBlockState)
70+
71+
-- | Write out the block state using the provided callback, and return a `BlobRef`.
72+
foreign import ccall "ffi_store_plt_block_state"
73+
ffiStorePLTBlockState ::
74+
-- | The provided closure is called to write data to blob store.
75+
FFI.StoreCallback ->
76+
-- | Pointer to the block state to write.
77+
FFI.Ptr PLTBlockState ->
78+
-- | New reference in the blob store.
79+
IO (BlobStore.BlobRef PLTBlockState)
80+
81+
instance (BlobStore.MonadBlobStore m) => BlobStore.Cacheable m PLTBlockState where
82+
cache blockState = do
83+
loadCallback <- fst <$> BlobStore.getCallbacks
84+
liftIO $! withPLTBlockState blockState (ffiCachePLTBlockState loadCallback)
85+
return blockState
86+
87+
-- | Cache block state into memory.
88+
foreign import ccall "ffi_cache_plt_block_state"
89+
ffiCachePLTBlockState ::
90+
-- | Called to read data from blob store.
91+
FFI.LoadCallback ->
92+
-- | Pointer to the block state to cache into memory.
93+
FFI.Ptr PLTBlockState ->
94+
IO ()
95+
96+
-- | The hash of some `PLTBlockState`.
97+
newtype Hash = Hash {innerSha256Hash :: SHA256.Hash}
98+
deriving newtype (Eq, Ord, Show, Serialize.Serialize)
99+
100+
instance (BlobStore.MonadBlobStore m) => Hashable.MHashableTo m Hash PLTBlockState where
101+
getHashM blockState = do
102+
loadCallback <- fst <$> BlobStore.getCallbacks
103+
((), hash) <-
104+
liftIO $
105+
withPLTBlockState blockState $
106+
FixedByteString.createWith . ffiHashPLTBlockState loadCallback
107+
return $ Hash (SHA256.Hash hash)
108+
109+
-- | Compute the hash of the block state.
110+
foreign import ccall "ffi_hash_plt_block_state"
111+
ffiHashPLTBlockState ::
112+
-- | Called to read data from blob store.
113+
FFI.LoadCallback ->
114+
-- | Pointer to the block state to write.
115+
FFI.Ptr PLTBlockState ->
116+
-- | Pointer to write destination of the hash
117+
FFI.Ptr FFI.Word8 ->
118+
IO ()
119+
120+
-- | Get temporary access to the block state pointer. The pointer should not be
121+
-- leaked from the computation.
122+
--
123+
-- This ensures the finalizer is not called until the computation is over.
124+
withPLTBlockState :: PLTBlockState -> (FFI.Ptr PLTBlockState -> IO a) -> IO a
125+
withPLTBlockState (PLTBlockState foreignPtr) = FFI.withForeignPtr foreignPtr
126+
127+
-- | Run migration during a protocol update.
128+
migrate ::
129+
forall t m.
130+
(BlobStore.SupportMigration m t) =>
131+
-- | Current block state
132+
PLTBlockState ->
133+
-- | New migrated block state
134+
t m PLTBlockState
135+
migrate currentState = do
136+
loadCallback <- fst <$> lift BlobStore.getCallbacks
137+
storeCallback <- snd <$> BlobStore.getCallbacks
138+
newState <- liftIO $ withPLTBlockState currentState $ ffiMigratePLTBlockState loadCallback storeCallback
139+
liftIO $ PLTBlockState <$> FFI.newForeignPtr ffiFreePLTBlockState newState
140+
141+
-- | Migrate PLT block state from one blob store to another.
142+
foreign import ccall "ffi_migrate_plt_block_state"
143+
ffiMigratePLTBlockState ::
144+
-- | Called to read data from the old blob store.
145+
FFI.LoadCallback ->
146+
-- | Called to write data to the new blob store.
147+
FFI.StoreCallback ->
148+
-- | Pointer to the old block state.
149+
FFI.Ptr PLTBlockState ->
150+
-- | Pointer to the new block state.
151+
IO (FFI.Ptr PLTBlockState)

plt-scheduler/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plt-scheduler/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ concordium_base = {path = "../concordium-base/rust-src/concordium_base"}
1414
plt-deployment-unit = {path = "../plt-deployment-unit"}
1515
derive_more = { version = "2.1.0", features = ["into", "from"] }
1616
libc = { version = "0.2", optional = true }
17+
thiserror = "2.0.17"

plt-scheduler/src/block_state.rs

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,95 @@
1+
//! This module contains the [`BlockState`] which provides an implementation of [`BlockStateOperations`].
2+
//!
3+
4+
pub mod blob_store;
5+
#[cfg(feature = "ffi")]
6+
pub mod ffi;
7+
18
use crate::BlockStateOperations;
29

3-
pub struct BlockState {}
10+
pub enum PltBlockStateHashMarker {}
11+
pub type PltBlockStateHash = concordium_base::hashes::HashBytes<PltBlockStateHashMarker>;
412

5-
impl BlockState {
13+
/// Immutable block state save-point.
14+
///
15+
/// This is a safe wrapper around a [`BlockState`] ensuring further mutations can only be done by
16+
/// unwrapping using [`BlockStateSavepoint::new_generation`] which creates a new generation.
17+
#[derive(Debug)]
18+
pub struct BlockStateSavepoint {
19+
/// The inner block state, which will not be mutated further for this generation.
20+
block_state: BlockState,
21+
}
22+
23+
impl BlockStateSavepoint {
624
/// Initialize a new block state.
7-
pub fn new() -> Self {
8-
Self {}
25+
pub fn empty() -> Self {
26+
Self {
27+
block_state: BlockState::empty(),
28+
}
29+
}
30+
31+
/// Compute the hash.
32+
pub fn hash(&self, _loader: impl blob_store::BackingStoreLoad) -> PltBlockStateHash {
33+
todo!()
34+
}
35+
36+
/// Store a PLT block state in a blob store.
37+
pub fn store_update(
38+
&mut self,
39+
_storer: &mut impl blob_store::BackingStoreStore,
40+
) -> blob_store::StoreResult<blob_store::Reference> {
41+
todo!()
42+
}
43+
44+
/// Migrate the PLT block state from one blob store to another.
45+
pub fn migrate(
46+
&mut self,
47+
_loader: &impl blob_store::BackingStoreLoad,
48+
_storer: &mut impl blob_store::BackingStoreStore,
49+
) -> blob_store::LoadStoreResult<Self> {
50+
todo!()
51+
}
52+
53+
/// Cache the block state in memory.
54+
pub fn cache(&mut self, _loader: &impl blob_store::BackingStoreLoad) {
55+
todo!()
56+
}
57+
58+
/// Construct a new generation block state which can be mutated without affecting this
59+
/// save-point.
60+
pub fn new_generation(&self) -> BlockState {
61+
let mut block_state = self.block_state.clone();
62+
block_state.generation += 1;
63+
block_state
64+
}
65+
}
66+
67+
impl blob_store::Loadable for BlockStateSavepoint {
68+
fn load<S: std::io::Read, F: blob_store::BackingStoreLoad>(
69+
_loader: &mut F,
70+
_source: &mut S,
71+
) -> blob_store::LoadResult<Self> {
72+
todo!()
73+
}
74+
}
75+
76+
/// Block state providing the various block state operations.
77+
#[derive(Debug, Clone)]
78+
pub struct BlockState {
79+
/// The generation counter for the block state.
80+
/// This is used to
81+
generation: usize,
82+
}
83+
84+
impl BlockState {
85+
/// Construct an empty block state.
86+
fn empty() -> Self {
87+
BlockState { generation: 0 }
88+
}
89+
90+
/// Consume the mutable block state and create a immutable save-point.
91+
pub fn savepoint(self) -> BlockStateSavepoint {
92+
BlockStateSavepoint { block_state: self }
993
}
1094
}
1195

0 commit comments

Comments
 (0)