Skip to content
This repository was archived by the owner on Nov 24, 2025. It is now read-only.

Commit 0148317

Browse files
committed
pact service enforce minimal chainweb version at startup, if required
1 parent 1b7c50c commit 0148317

5 files changed

Lines changed: 174 additions & 1 deletion

File tree

chainweb.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,7 @@ test-suite chainweb-tests
665665
Chainweb.Test.TreeDB.RemoteDB
666666
Chainweb.Test.Version
667667
Test.Chainweb.SPV.Argument
668+
Chainweb.Test.PayloadProvider.StartupTest
668669

669670
-- Data
670671
Data.Test.PQueue

src/Chainweb/Pact/Backend/ChainwebPactDb.hs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,7 @@ getConsensusState db = do
810810
initSchema :: SQLiteEnv -> IO ()
811811
initSchema sql =
812812
withSavepoint sql InitSchemaSavePoint $ throwOnDbError $ do
813+
createChainwebMetaTable
813814
createConsensusStateTable
814815
createBlockHistoryTable
815816
createTableCreationTable
@@ -824,6 +825,13 @@ initSchema sql =
824825
create tablename = do
825826
createVersionedTable tablename sql
826827

828+
createChainwebMetaTable :: ExceptT LocatedSQ3Error IO ()
829+
createChainwebMetaTable = do
830+
exec_ sql
831+
"CREATE TABLE IF NOT EXISTS ChainwebMeta \
832+
\(minMajorVersion INTEGER NOT NULL, \
833+
\ minMinorVersion INTEGER NOT NULL);"
834+
827835
createConsensusStateTable :: ExceptT LocatedSQ3Error IO ()
828836
createConsensusStateTable = do
829837
exec_ sql

src/Chainweb/Pact/PactService.hs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ import Chainweb.Miner.Pact
5252
import Chainweb.Pact.Backend.ChainwebPactDb qualified as ChainwebPactDb
5353
import Chainweb.Pact.Backend.ChainwebPactDb qualified as Pact
5454
import Chainweb.Pact.Backend.Types
55-
import Chainweb.Pact.Backend.Utils (withSavepoint, SavepointName (..))
55+
import Chainweb.Pact.Backend.Utils (withSavepoint, SavepointName (..), LocatedSQ3Error)
5656
import Chainweb.Pact.NoCoinbase qualified as Pact
5757
import Chainweb.Pact.PactService.Checkpointer qualified as Checkpointer
5858
import Chainweb.Pact.PactService.ExecBlock
@@ -98,6 +98,8 @@ import Data.Monoid
9898
import Data.Pool (Pool)
9999
import Data.Pool qualified as Pool
100100
import Data.Text qualified as Text
101+
import Data.Version (Version(..))
102+
import Paths_chainweb (version)
101103
import Data.Vector (Vector)
102104
import Data.Vector qualified as V
103105
import Data.Void
@@ -117,6 +119,8 @@ import System.LogLevel
117119
import Chainweb.Version.Guards (pact5)
118120
import Control.Concurrent.MVar (newMVar)
119121
import Chainweb.Pact.Payload.RestAPI.Client (payloadClient)
122+
import Pact.Types.SQLite
123+
120124

121125
withPactService
122126
:: (Logger logger, CanPayloadCas tbl)
@@ -141,6 +145,17 @@ withPactService cid http memPoolAccess chainwebLogger txFailuresCounter pdb read
141145
)
142146

143147
liftIO $ ChainwebPactDb.initSchema readWriteSqlenv
148+
149+
-- Check if the SQLiteEnv requires a minimal version of chainweb to work properly
150+
mMinChainwebVersion <- liftIO $ getMinChainwebVersion readWriteSqlenv
151+
case mMinChainwebVersion of
152+
Nothing -> pure ()
153+
Just minVersion ->
154+
if version >= minVersion
155+
then pure ()
156+
else error $ "PactService required at least version: " <> show minVersion <> ", currently at: " <> show version
157+
158+
144159
candidatePdb <- liftIO MapTable.emptyTable
145160
moduleInitCacheVar <- liftIO $ newMVar mempty
146161

@@ -175,6 +190,13 @@ withPactService cid http memPoolAccess chainwebLogger txFailuresCounter pdb read
175190
_ -> liftIO $ initialPayloadState chainwebLogger pse
176191
return pse
177192

193+
where
194+
getMinChainwebVersion :: SQLiteEnv -> IO (Maybe Version)
195+
getMinChainwebVersion sql = qry_ sql "SELECT minMajorVersion, minMinorVersion from ChainwebMeta limit 1" [RInt, RInt] >>= \case
196+
[[SInt major, SInt minor]] -> pure $ Just $ Version [fromIntegral major, fromIntegral minor] []
197+
[] -> pure Nothing
198+
_ -> error "getMinChainwebVersion: incorrect column types"
199+
178200
initialPayloadState
179201
:: Logger logger
180202
=> HasVersion
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
{-# LANGUAGE BangPatterns #-}
2+
{-# LANGUAGE DataKinds #-}
3+
{-# LANGUAGE FlexibleContexts #-}
4+
{-# LANGUAGE ImportQualifiedPost #-}
5+
{-# LANGUAGE LambdaCase #-}
6+
{-# LANGUAGE NumericUnderscores #-}
7+
{-# LANGUAGE OverloadedStrings #-}
8+
{-# LANGUAGE PackageImports #-}
9+
{-# LANGUAGE RankNTypes #-}
10+
{-# LANGUAGE ScopedTypeVariables #-}
11+
{-# LANGUAGE TypeApplications #-}
12+
13+
module Chainweb.Test.PayloadProvider.StartupTest
14+
( tests
15+
) where
16+
17+
import Chainweb.ChainId
18+
import Chainweb.Graph (singletonChainGraph)
19+
import Chainweb.Logger
20+
import Chainweb.Pact.Backend.ChainwebPactDb qualified as ChainwebPactDb
21+
import Chainweb.Pact.Backend.Types
22+
import Chainweb.Pact.PactService
23+
import Chainweb.Pact.Payload.PayloadStore.InMemory
24+
import Chainweb.Pact.Types
25+
import Chainweb.Test.Pact.Utils
26+
import Chainweb.Test.TestVersions
27+
import Chainweb.Test.Utils
28+
import Chainweb.Version
29+
import Control.Exception (try, displayException)
30+
import Control.Exception.Safe (SomeException)
31+
import Control.Monad
32+
import Control.Monad.IO.Class
33+
import Control.Monad.Trans.Resource
34+
import Data.List
35+
import Data.Maybe
36+
import Data.Version (Version(..))
37+
import Pact.Types.SQLite
38+
import Test.Tasty
39+
import Test.Tasty.HUnit
40+
41+
tests :: TestTree
42+
tests = testGroup "PayloadProvider.Startup"
43+
[ testMinChainwebVersionValidation
44+
]
45+
46+
-- | Helper function for setting up tests with loggerand sqlite
47+
withStartupTestSetup
48+
:: TestName
49+
-> (GenericLogger -> SQLiteEnv -> IO ())
50+
-> (HasVersion => GenericLogger -> SQLiteEnv -> IO ())
51+
-> TestTree
52+
withStartupTestSetup name setup action = withResourceT (withTempChainSqlite cid) $ \sqlIO -> do
53+
testCase name $ do
54+
logger <- getTestLogger
55+
(sql, _sqlReadPool) <- sqlIO
56+
57+
setup logger sql
58+
59+
withVersion v $ runResourceT $ do
60+
liftIO $ action logger sql
61+
where
62+
cid = unsafeChainId 0
63+
v = instantCpmTestVersion singletonChainGraph
64+
65+
-- | Initialize schema for tests
66+
initStartupTestSchema :: GenericLogger -> SQLiteEnv -> IO ()
67+
initStartupTestSchema _logger sql = ChainwebPactDb.initSchema sql
68+
69+
-- | Test that the minimum chainweb version validation works correctly
70+
testMinChainwebVersionValidation :: TestTree
71+
testMinChainwebVersionValidation = withStartupTestSetup "minimum chainweb version validation"
72+
initStartupTestSchema
73+
$ \logger sql -> do
74+
-- Test with no existing version - should succeed
75+
pdb <- newPayloadDb
76+
77+
result1 <- try @SomeException $ runResourceT $ withPactService cid Nothing mempty logger Nothing pdb pool sql cfg genesis
78+
case result1 of
79+
Left e
80+
| "PactService required at least version:" `isInfixOf` displayException e ->
81+
liftIO $ assertFailure $ "PactService should start successfully when no minimum version is set: " <> displayException e
82+
_ -> return ()
83+
84+
let version2 = Version [2, 2] []
85+
setMinChainwebVersion sql version2
86+
87+
version2' <- getMinChainwebVersion sql
88+
liftIO $ assertEqual "Should return the set version" (Just version2) version2'
89+
90+
91+
result2 <- try @SomeException $ runResourceT $ withPactService cid Nothing mempty logger Nothing pdb pool sql cfg genesis
92+
case result2 of
93+
Left e
94+
| "PactService required at least version:" `isInfixOf` displayException e ->
95+
liftIO $ assertFailure $ "PactService should start successfully when no minimum version is set: " <> displayException e
96+
_ -> return ()
97+
98+
let version3 = Version [200, 2] []
99+
setMinChainwebVersion sql version3
100+
101+
version3' <- getMinChainwebVersion sql
102+
liftIO $ assertEqual "Should return the set version" (Just version3) version3'
103+
104+
result3 <- try @SomeException $ runResourceT $ withPactService cid Nothing mempty logger Nothing pdb pool sql cfg genesis
105+
case result3 of
106+
Left e
107+
| "PactService required at least version:" `isInfixOf` displayException e ->
108+
pure ()
109+
_ -> liftIO $ assertFailure $ "PactService should not start successfully when minimum version is not reached"
110+
111+
where
112+
cid = unsafeChainId 0
113+
cfg = defaultPactServiceConfig
114+
genesis = GeneratingGenesis
115+
pool = error "Pool not needed for this test"
116+
117+
118+
-- Helper functions (copied from PactService.hs local where clause)
119+
getMinChainwebVersion :: SQLiteEnv -> IO (Maybe Version)
120+
getMinChainwebVersion sql = qry_ sql "SELECT minMajorVersion, minMinorVersion from ChainwebMeta limit 1" [RInt, RInt] >>= \case
121+
[[SInt major, SInt minor]] -> pure $ Just $ Version [fromIntegral major, fromIntegral minor] []
122+
[] -> pure Nothing
123+
_ -> error "incorrect column types"
124+
125+
setMinChainwebVersion :: SQLiteEnv -> Version -> IO ()
126+
setMinChainwebVersion sql (Version (major:minor:_) _) = do
127+
mMinVersion <- getMinChainwebVersion sql
128+
if isJust mMinVersion
129+
then
130+
void $ qry sql
131+
"UPDATE ChainwebMeta \
132+
\SET minMajorVersion = ?, minMinorVersion = ?"
133+
[SInt (fromIntegral major), SInt (fromIntegral minor)] [RInt]
134+
else
135+
void $ qry sql
136+
"INSERT INTO ChainwebMeta (minMajorVersion, minMinorVersion) VALUES (?, ?)"
137+
[SInt (fromIntegral major), SInt (fromIntegral minor)] [RInt]
138+
setMinChainwebVersion _ (Version _ _) =
139+
error "version formatting does not match"

test/unit/ChainwebTests.hs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import Chainweb.Test.Pact4.RewardsTest qualified
4646
import Chainweb.Test.Pact4.SQLite qualified
4747
import Chainweb.Test.Pact4.TransactionTests qualified
4848
import Chainweb.Test.Pact4.VerifierPluginTest qualified
49+
import Chainweb.Test.PayloadProvider.StartupTest qualified (tests)
4950
import Chainweb.Test.RestAPI qualified (tests)
5051
import Chainweb.Test.Roundtrips qualified (tests)
5152
import Chainweb.Test.Sync.WebBlockHeaderStore qualified (properties)
@@ -65,6 +66,7 @@ import Test.Tasty
6566
import Test.Tasty.JsonReporter
6667
import Test.Tasty.QuickCheck
6768

69+
6870
setTestLogLevel :: LogLevel -> IO ()
6971
setTestLogLevel l = setEnv "CHAINWEB_TEST_LOG_LEVEL" (show l)
7072

@@ -131,6 +133,7 @@ suite rdb =
131133
, Chainweb.Test.BlockHeader.Genesis.tests
132134
, Chainweb.Test.BlockHeader.Validation.tests
133135
, Chainweb.Test.Version.tests
136+
, Chainweb.Test.PayloadProvider.StartupTest.tests
134137
, testProperties "Chainweb.Test.Chainweb.Utils.Paging" Chainweb.Test.Chainweb.Utils.Paging.properties
135138
, testProperties "Chainweb.Test.HostAddress" Chainweb.Test.HostAddress.properties
136139
, testProperties "Chainweb.Test.Sync.WebBlockHeaderStore" Chainweb.Test.Sync.WebBlockHeaderStore.properties

0 commit comments

Comments
 (0)