-
Notifications
You must be signed in to change notification settings - Fork 87
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
269 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
256 changes: 256 additions & 0 deletions
256
ouroboros-consensus/test-consensus/Test/Consensus/LocalStateQueryServer.hs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,256 @@ | ||
{-# LANGUAGE GADTs #-} | ||
{-# LANGUAGE LambdaCase #-} | ||
{-# LANGUAGE ScopedTypeVariables #-} | ||
|
||
{-# OPTIONS_GHC -Wno-orphans #-} | ||
module Test.Consensus.LocalStateQueryServer where | ||
|
||
import Control.Monad.Except (runExcept) | ||
import Control.Tracer (nullTracer) | ||
import Data.Map (Map) | ||
import qualified Data.Map as Map | ||
|
||
import Control.Monad.IOSim (runSimOrThrow) | ||
|
||
import Cardano.Crypto.DSIGN.Mock | ||
|
||
import Network.TypedProtocol.Proofs (connect) | ||
import Ouroboros.Network.Block (Point (..), SlotNo, blockPoint, | ||
pointSlot) | ||
import Ouroboros.Network.MockChain.Chain (Chain (..)) | ||
import qualified Ouroboros.Network.MockChain.Chain as Chain | ||
import Ouroboros.Network.Point (WithOrigin (..)) | ||
import Ouroboros.Network.Protocol.LocalStateQuery.Client | ||
import Ouroboros.Network.Protocol.LocalStateQuery.Examples | ||
(localStateQueryClient) | ||
import Ouroboros.Network.Protocol.LocalStateQuery.Server | ||
import Ouroboros.Network.Protocol.LocalStateQuery.Type | ||
(AcquireFailure (..)) | ||
|
||
import Ouroboros.Consensus.Block (BlockProtocol, getHeader) | ||
import Ouroboros.Consensus.BlockchainTime.SlotLength | ||
(slotLengthFromSec) | ||
import Ouroboros.Consensus.BlockchainTime.SlotLengths | ||
(singletonSlotLengths) | ||
import Ouroboros.Consensus.Ledger.Abstract | ||
import Ouroboros.Consensus.Ledger.Extended | ||
import Ouroboros.Consensus.LocalStateQueryServer | ||
import Ouroboros.Consensus.Node.ProtocolInfo (NumCoreNodes (..)) | ||
import Ouroboros.Consensus.NodeId | ||
import Ouroboros.Consensus.Protocol.Abstract (NodeConfig, | ||
SecurityParam (..)) | ||
import Ouroboros.Consensus.Protocol.BFT | ||
import Ouroboros.Consensus.Util ((.:)) | ||
import Ouroboros.Consensus.Util.IOLike | ||
|
||
import qualified Ouroboros.Storage.ChainDB.Impl.LedgerCursor as LedgerCursor | ||
import Ouroboros.Storage.ChainDB.Impl.LgrDB (LedgerDbParams (..), | ||
LgrDB, LgrDbArgs (..), mkLgrDB) | ||
import qualified Ouroboros.Storage.ChainDB.Impl.LgrDB as LgrDB | ||
import Ouroboros.Storage.LedgerDB.Conf (LedgerDbConf (..)) | ||
import qualified Ouroboros.Storage.LedgerDB.InMemory as LgrDB | ||
(ledgerDbFromGenesis) | ||
|
||
import Test.QuickCheck hiding (Result) | ||
import Test.Tasty | ||
import Test.Tasty.QuickCheck | ||
|
||
import Test.Util.Orphans.IOLike () | ||
import Test.Util.TestBlock | ||
|
||
|
||
{------------------------------------------------------------------------------- | ||
Top-level tests | ||
-------------------------------------------------------------------------------} | ||
|
||
tests :: TestTree | ||
tests = testGroup "LocalStateQueryServer" | ||
[ testProperty "localStateQueryServer" prop_localStateQueryServer | ||
] | ||
|
||
{------------------------------------------------------------------------------- | ||
Main property | ||
-------------------------------------------------------------------------------} | ||
|
||
-- | Plan: | ||
-- * Preseed the LgrDB of the server with the preferred chain of the | ||
-- 'BlockTree'. | ||
-- * Acquire for each block in the 'BlockTree', including the ones not on the | ||
-- chain, a state and send the 'QueryLedgerTip'. Collect these results. | ||
-- * Check that when acquiring failed, it rightfully failed. Otherwise, check | ||
-- whether the returned tip matches the block. | ||
prop_localStateQueryServer | ||
:: SecurityParam | ||
-> BlockTree | ||
-> Permutation | ||
-> Property | ||
prop_localStateQueryServer k bt p = checkOutcome k chain actualOutcome | ||
where | ||
chain :: Chain TestBlock | ||
chain = treePreferredChain (testCfg k) bt | ||
|
||
points :: [Point TestBlock] | ||
points = blockPoint <$> permute p (treeToBlocks bt) | ||
|
||
actualOutcome = runSimOrThrow $ do | ||
let client = mkClient points | ||
server <- mkServer k chain | ||
(\(a, _, _) -> a) <$> | ||
connect | ||
(localStateQueryClientPeer client) | ||
(localStateQueryServerPeer server) | ||
|
||
{------------------------------------------------------------------------------- | ||
Test setup | ||
-------------------------------------------------------------------------------} | ||
|
||
-- | Checks whether the given outcome is correct: in case of an | ||
-- 'AcquireFailure', we check whether it was warranted. Otherwise we check | ||
-- whether the results are correct. | ||
-- | ||
-- NOTE: when we don't get an 'AcquireFailure', even though we expected it, we | ||
-- accept it. This is because the LgrDB may contain snapshots for blocks on | ||
-- the current chain older than @k@, but we do not want to imitate such | ||
-- implementation details. | ||
-- | ||
-- Additionally, this function labels the test results. | ||
checkOutcome | ||
:: SecurityParam | ||
-> Chain TestBlock | ||
-> [(Point TestBlock, Either AcquireFailure [Result TestBlock])] | ||
-> Property | ||
checkOutcome k chain = conjoin . map (uncurry checkResult) | ||
where | ||
immutableSlot :: WithOrigin SlotNo | ||
immutableSlot = Chain.headSlot $ | ||
Chain.drop (fromIntegral (maxRollbacks k)) chain | ||
|
||
checkResult | ||
:: Point TestBlock | ||
-> Either AcquireFailure [Result TestBlock] | ||
-> Property | ||
checkResult pt = \case | ||
Right results | ||
-> tabulate "Acquired" ["Success"] $ results === [ResultLedgerTip pt] | ||
Left AcquireFailurePointNotOnChain | ||
| Chain.pointOnChain pt chain | ||
-> counterexample | ||
("Point " <> show pt <> | ||
" on chain, but got AcquireFailurePointNotOnChain") | ||
(property False) | ||
| otherwise | ||
-> tabulate "Acquired" ["AcquireFailurePointNotOnChain"] $ property True | ||
Left AcquireFailurePointTooOld | ||
| pointSlot pt >= immutableSlot | ||
-> counterexample | ||
("Point " <> show pt <> | ||
" newer than the immutable tip, but got AcquireFailurePointTooOld") | ||
(property False) | ||
| otherwise | ||
-> tabulate "Acquired" ["AcquireFailurePointTooOld"] $ property True | ||
|
||
mkClient | ||
:: Monad m | ||
=> [Point TestBlock] | ||
-> LocalStateQueryClient | ||
TestBlock | ||
(Query TestBlock) | ||
(Result TestBlock) | ||
m | ||
[(Point TestBlock, Either AcquireFailure [Result TestBlock])] | ||
mkClient points = localStateQueryClient [(pt, [QueryLedgerTip]) | pt <- points] | ||
|
||
mkServer | ||
:: IOLike m | ||
=> SecurityParam | ||
-> Chain TestBlock | ||
-> m (LocalStateQueryServer TestBlock (Query TestBlock) (Result TestBlock) m ()) | ||
mkServer k chain = do | ||
lgrDB <- initLgrDB k chain | ||
return $ localStateQueryServer $ LedgerCursor.newLedgerCursor lgrDB getImmutablePoint | ||
where | ||
getImmutablePoint = return $ Chain.headPoint $ | ||
Chain.drop (fromIntegral (maxRollbacks k)) chain | ||
|
||
-- | Initialise a 'LgrDB' with the given chain. | ||
initLgrDB | ||
:: forall m. IOLike m | ||
=> SecurityParam | ||
-> Chain TestBlock | ||
-> m (LgrDB m TestBlock) | ||
initLgrDB k chain = do | ||
varDB <- newTVarM genesisLedgerDB | ||
varPrevApplied <- newTVarM mempty | ||
let lgrDB = mkLgrDB conf varDB varPrevApplied args | ||
LgrDB.validate lgrDB genesisLedgerDB 0 | ||
(map getHeader (Chain.toOldestFirst chain)) >>= \case | ||
LgrDB.MaximumRollbackExceeded {} -> | ||
error "rollback was 0" | ||
LgrDB.RollbackSuccessful (LgrDB.InvalidBlock {}) -> | ||
error "there were no invalid blocks" | ||
LgrDB.RollbackSuccessful (LgrDB.ValidBlocks ledgerDB') -> do | ||
atomically $ LgrDB.setCurrent lgrDB ledgerDB' | ||
return lgrDB | ||
where | ||
blockMapping :: Map (Point TestBlock) TestBlock | ||
blockMapping = Map.fromList | ||
[(blockPoint b, b) | b <- Chain.toOldestFirst chain] | ||
|
||
params :: LedgerDbParams | ||
params = LedgerDbParams | ||
{ ledgerDbSnapEvery = maxRollbacks k | ||
, ledgerDbSecurityParam = k | ||
} | ||
|
||
cfg = testCfg k | ||
|
||
conf = LedgerDbConf | ||
{ ldbConfGenesis = return testInitExtLedger | ||
, ldbConfApply = runExcept .: | ||
applyExtLedgerState BlockNotPreviouslyApplied cfg | ||
, ldbConfReapply = (mustBeRight . runExcept) .: | ||
applyExtLedgerState BlockPreviouslyApplied cfg | ||
, ldbConfResolve = return . (blockMapping Map.!) | ||
} | ||
|
||
mustBeRight (Left e) = error $ "impossible: " <> show e | ||
mustBeRight (Right a) = a | ||
|
||
genesisLedgerDB = LgrDB.ledgerDbFromGenesis params testInitExtLedger | ||
|
||
args = LgrDbArgs | ||
{ lgrNodeConfig = cfg | ||
, lgrHasFS = error "lgrHasFS" | ||
, lgrDecodeLedger = error "lgrDecodeLedger" | ||
, lgrDecodeChainState = error "lgrDecodeChainState" | ||
, lgrDecodeHash = error "lgrDecodeHash" | ||
, lgrEncodeLedger = error "lgrEncodeLedger" | ||
, lgrEncodeChainState = error "lgrEncodeChainState" | ||
, lgrEncodeHash = error "lgrEncodeHash" | ||
, lgrParams = params | ||
, lgrDiskPolicy = error "lgrDiskPolicy" | ||
, lgrGenesis = return testInitExtLedger | ||
, lgrTracer = nullTracer | ||
, lgrTraceLedger = nullTracer | ||
} | ||
|
||
testCfg :: SecurityParam -> NodeConfig (BlockProtocol TestBlock) | ||
testCfg securityParam = BftNodeConfig | ||
{ bftParams = BftParams { bftSecurityParam = securityParam | ||
, bftNumNodes = NumCoreNodes 1 | ||
, bftSlotLengths = singletonSlotLengths $ | ||
slotLengthFromSec 20 | ||
} | ||
, bftNodeId = CoreId (CoreNodeId 0) | ||
, bftSignKey = SignKeyMockDSIGN 0 | ||
, bftVerKeys = Map.singleton (CoreId (CoreNodeId 0)) (VerKeyMockDSIGN 0) | ||
} | ||
|
||
|
||
{------------------------------------------------------------------------------- | ||
Orphans | ||
-------------------------------------------------------------------------------} | ||
|
||
instance Arbitrary SecurityParam where | ||
arbitrary = SecurityParam <$> choose (1, 100) | ||
shrink (SecurityParam k) = [SecurityParam k' | k' <- shrink k, k' > 0] |