Skip to content

Commit ab431d9

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

File tree

9 files changed

+722
-124
lines changed

9 files changed

+722
-124
lines changed
Lines changed: 89 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,120 @@
1-
{-# LANGUAGE OverloadedStrings #-}
2-
31
-- |
42
-- Bindings into the @plt-scheduler@ Rust library exposing safe wrappers.
53
--
64
-- Each foreign imported function must match the signature of functions found in @plt-scheduler/src/ffi.rs@.
75
module Concordium.PLTScheduler (
8-
PLTBlockState,
9-
initialPLTBlockState,
106
executeTransaction,
7+
ExecutionOutcome (..),
8+
ExecutionAccepts (..),
119
) where
1210

1311
import qualified Data.ByteString as BS
1412
import qualified Data.ByteString.Unsafe as BS
1513
import qualified Data.Word as Word
1614
import qualified Foreign as FFI
1715
import qualified Foreign.C.Types as FFI
16+
import Control.Monad.IO.Class (liftIO)
17+
18+
import qualified Concordium.PLTScheduler.PLTBlockState as PLTBlockState
19+
import qualified Concordium.Types as Types
20+
import qualified Data.FixedByteString as FixedByteString
21+
import qualified Concordium.GlobalState.Persistent.BlobStore as BlobStore
22+
import qualified Concordium.GlobalState.ContractStateFFIHelpers as FFI
1823

1924
-- | 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.
2125
--
2226
-- See @execute_transaction@ in @plt-scheduler@ rust crate for details.
23-
executeTransaction ::
27+
executeTransaction :: (BlobStore.MonadBlobStore m) =>
2428
-- | Block state to mutate.
25-
PLTBlockState ->
29+
PLTBlockState.PLTBlockState ->
2630
-- | Transaction payload byte string.
2731
BS.ByteString ->
28-
-- | 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'"
32+
-- | The account index of the account which signed as the sender of the transaction.
33+
Types.AccountIndex ->
34+
-- | The account address of the account which signed as the sender of the transaction.
35+
Types.AccountAddress ->
36+
-- | Remaining energy.
37+
Types.Energy ->
38+
-- | Outcome of the execution
39+
m ExecutionOutcome
40+
executeTransaction
41+
blockState
42+
transactionPayload
43+
senderAccountIndex
44+
(Types.AccountAddress senderAccountAddress)
45+
remainingEnergy = do
46+
loadCallback <- fst <$> BlobStore.getCallbacks
47+
liftIO $ FFI.alloca $ \remainingEnergyOut -> FFI.alloca $ \updatedBlockStatePtrOut -> do
48+
-- Invoke the ffi call
49+
statusCode <- PLTBlockState.withPLTBlockState blockState $ \blockStatePtr ->
50+
FixedByteString.withPtrReadOnly senderAccountAddress $ \senderAccountAddressPtr ->
51+
BS.unsafeUseAsCStringLen transactionPayload $
52+
\(transactionPayloadPtr, transactionPayloadLen) ->
53+
ffiExecuteTransaction
54+
loadCallback
55+
blockStatePtr
56+
(FFI.castPtr transactionPayloadPtr)
57+
(fromIntegral transactionPayloadLen)
58+
(fromIntegral senderAccountIndex)
59+
senderAccountAddressPtr
60+
(fromIntegral remainingEnergy)
61+
updatedBlockStatePtrOut
62+
remainingEnergyOut
63+
-- Process the and construct the outcome
64+
newRemainingEnergy <- fromIntegral <$> FFI.peek remainingEnergyOut
65+
status <- case statusCode of
66+
0 -> do
67+
updatedBlockState <- FFI.peek updatedBlockStatePtrOut >>= PLTBlockState.wrapFFIPtr
68+
return $
69+
Right
70+
ExecutionAccepts
71+
{ eaUpdatedPLTBlockState = updatedBlockState,
72+
eaEvents = ()
73+
}
74+
1 -> return $ Left ()
75+
_ -> error "Unexpected status code from calling 'ffiExecuteTransaction'"
76+
return
77+
ExecutionOutcome
78+
{ erRemainingEnergy = newRemainingEnergy,
79+
erStatus = status
80+
}
3881

3982
foreign import ccall "ffi_execute_transaction"
4083
ffiExecuteTransaction ::
41-
FFI.Ptr PLTBlockState ->
84+
-- | Called to read data from blob store.
85+
FFI.LoadCallback ->
86+
-- | Pointer to the starting block state.
87+
FFI.Ptr PLTBlockState.PLTBlockState ->
4288
-- | Pointer to transaction payload bytes.
4389
FFI.Ptr Word.Word8 ->
4490
-- | Byte length of transaction payload.
4591
FFI.CSize ->
92+
-- | The account index of the account which signed as the sender of the transaction.
93+
Word.Word64 ->
94+
-- | Pointer to 32 bytes representing the account address of the account which signed as the
95+
-- sender of the transaction.
96+
FFI.Ptr Word.Word8 ->
97+
-- | Remaining energy
98+
Word.Word64 ->
99+
-- | Output location for the updated block state.
100+
FFI.Ptr (FFI.Ptr PLTBlockState.PLTBlockState) ->
101+
-- | Output location for the remaining energy after execution.
102+
FFI.Ptr Word.Word64 ->
46103
-- | Status code
47104
IO Word.Word8
48105

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
106+
-- | The outcome of executing a transaction using the PLT scheduler.
107+
data ExecutionOutcome = ExecutionOutcome
108+
{ -- | The amount of energy remaining after the execution.
109+
erRemainingEnergy :: Types.Energy,
110+
-- | The resulting execution status.
111+
erStatus :: Either () ExecutionAccepts
112+
}
61113

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

0 commit comments

Comments
 (0)