Skip to content

Commit 64df068

Browse files
committed
db: Use proper Word64 support
This commit uses a version of persistent that has been modified to add proper Word64 support. Previously when the schema used `Word64` as the column type, Persistent would use `SqlInt64` as the SQL representation which means that `Word64` values above `maxBound :: Int64` would be stored as negative values in the database. That is fine for a database only accessed from Haskell but is a pain in the neck when the database is used as an interop layer for other languages. The solution is to define a 'DbWord64' newtype wrapper around 'Word64' and then map 'DbWord64' to the 'numeric(20,0)' SQL type with a bounds check. Closes: #163
1 parent 9f6ab7a commit 64df068

File tree

6 files changed

+43
-24
lines changed

6 files changed

+43
-24
lines changed

cardano-db-sync/src/Cardano/DbSync/Plugin/Default/Shelley/Insert.hs

+3-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import Cardano.Prelude
1515

1616
import Cardano.BM.Trace (Trace, logDebug, logError, logInfo)
1717

18+
import Cardano.Db (DbWord64 (..))
19+
1820
import Control.Monad.Logger (LoggingT)
1921
import Control.Monad.Trans.Except.Extra (firstExceptT, newExceptT, runExceptT)
2022

@@ -208,7 +210,7 @@ insertPoolRegister tracer txId params = do
208210
DB.PoolUpdate
209211
{ DB.poolUpdateHashId = poolHashId
210212
, DB.poolUpdateVrfKey = Crypto.hashToBytes (Shelley._poolVrf params)
211-
, DB.poolUpdatePledge = fromIntegral $ Shelley.unCoin (Shelley._poolPledge params)
213+
, DB.poolUpdatePledge = DbWord64 $ fromIntegral (Shelley.unCoin $ Shelley._poolPledge params)
212214
, DB.poolUpdateRewardAddrId = rewardId
213215
, DB.poolUpdateMeta = mdId
214216
, DB.poolUpdateMargin = realToFrac $ Shelley.intervalValue (Shelley._poolMargin params)

cardano-db/src/Cardano/Db/Schema.hs

+20-19
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
module Cardano.Db.Schema where
1818

1919
import Cardano.Db.Schema.Orphans ()
20+
import Cardano.Db.Types (DbWord64)
2021

2122
import Data.ByteString.Char8 (ByteString)
2223
import Data.Int (Int64)
@@ -82,7 +83,7 @@ share
8283
slotLeader SlotLeaderId
8384
size Word64 sqltype=uinteger
8485
time UTCTime sqltype=timestamp
85-
txCount Word64 sqltype=uinteger
86+
txCount Word64
8687
-- Shelley specific
8788
vrfKey ByteString Maybe sqltype=hash32type
8889
opCert ByteString Maybe sqltype=hash32type
@@ -91,11 +92,11 @@ share
9192

9293
Tx
9394
hash ByteString sqltype=hash32type
94-
block BlockId -- This type is the primary key for the 'block' table.
95-
blockIndex Word64 sqltype=uinteger -- The index of this transaction within the block.
95+
block BlockId -- This type is the primary key for the 'block' table.
96+
blockIndex Word64 sqltype=uinteger -- The index of this transaction within the block.
9697
outSum Word64 sqltype=lovelace
9798
fee Word64 sqltype=lovelace
98-
deposit Int64
99+
deposit Int64 -- Needs to allow negaitve values.
99100
size Word64 sqltype=uinteger
100101
UniqueTx hash
101102

@@ -131,7 +132,7 @@ share
131132
-- hold 204 times the total Lovelace distribution. The chance of that much being transacted
132133
-- in a single epoch is relatively low.
133134
Epoch
134-
outSum Word128 sqltype=word128
135+
outSum Word128 sqltype=word128type
135136
txCount Word64 sqltype=uinteger
136137
blkCount Word64 sqltype=uinteger
137138
no Word64 sqltype=uinteger
@@ -160,12 +161,12 @@ share
160161
PoolUpdate
161162
hashId PoolHashId
162163
vrfKey ByteString sqltype=hash32type
163-
pledge Word64 -- This really should be sqltype=lovelace See https://github.com/input-output-hk/cardano-ledger-specs/issues/1551
164+
pledge DbWord64 sqltype=word64type
164165
rewardAddrId StakeAddressId
165166
meta PoolMetaDataId Maybe
166-
margin Double -- sqltype=percentage????
167-
fixedCost Word64
168-
registeredTxId TxId -- Slot number in which the pool was registered.
167+
margin Double -- sqltype=percentage????
168+
fixedCost Word64 sqltype=lovelace
169+
registeredTxId TxId -- Slot number in which the pool was registered.
169170
UniquePoolUpdate hashId registeredTxId
170171

171172
PoolOwner
@@ -175,8 +176,8 @@ share
175176

176177
PoolRetire
177178
updateId PoolUpdateId
178-
announcedTxId TxId -- Slot number in which the pool announced it was retiring.
179-
retiringEpoch Word64 -- Epoch number in which the pool will retire.
179+
announcedTxId TxId -- Slot number in which the pool announced it was retiring.
180+
retiringEpoch Word64 sqltype=uinteger -- Epoch number in which the pool will retire.
180181
UniquePoolRetiring updateId
181182

182183
PoolRelay
@@ -246,16 +247,16 @@ share
246247
-- Table to hold parameter updates.
247248

248249
ParamUpdate
249-
epochNo Word64
250-
minFee Word64
251-
maxFee Word64
252-
maxBlockSize Word64
253-
maxTxSize Word64
254-
maxBhSize Word64
250+
epochNo Word64 sqltype=uinteger
251+
minFee Word64 sqltype=uinteger
252+
maxFee Word64 sqltype=uinteger
253+
maxBlockSize Word64 sqltype=uinteger
254+
maxTxSize Word64 sqltype=uinteger
255+
maxBhSize Word64 sqltype=uinteger
255256
keyDeposit Word64 sqltype=lovelace
256257
poolDeposit Word64 sqltype=lovelace
257-
maxEpoch Word64
258-
nOptimal Word64
258+
maxEpoch Word64 sqltype=uinteger
259+
nOptimal Word64 sqltype=uinteger
259260
influence Double -- sqltype=rational
260261
monetaryExpandRate Word64 sqltype=interval
261262
treasuryGrowthRate Word64 sqltype=interval

cardano-db/src/Cardano/Db/Schema/Orphans.hs

+7
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,20 @@
44

55
module Cardano.Db.Schema.Orphans where
66

7+
import Cardano.Db.Types (DbWord64 (..))
8+
79
import Data.WideWord.Word128 (Word128)
810

911
import qualified Data.Text as Text
1012

1113
import Database.Persist.Class (PersistField (..))
1214
import Database.Persist.Types (PersistValue (..))
1315

16+
instance PersistField DbWord64 where
17+
toPersistValue = PersistText . Text.pack . show . unDbWord64
18+
fromPersistValue (PersistText bs) = Right $ DbWord64 (read $ Text.unpack bs)
19+
fromPersistValue x =
20+
Left $ mconcat [ "Failed to parse Haskell type Word64: ", Text.pack (show x) ]
1421

1522
instance PersistField Word128 where
1623
toPersistValue = PersistText . Text.pack . show

cardano-db/src/Cardano/Db/Types.hs

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
module Cardano.Db.Types
88
( Ada (..)
9+
, DbWord64 (..)
910
, lovelaceToAda
1011
, renderAda
1112
, scientificToAda
@@ -43,10 +44,12 @@ instance ToJSON Ada where
4344

4445
toJSON = error "Ada.toJSON not supported due to numeric issues. Use toEncoding instead."
4546

46-
4747
instance Show Ada where
4848
show (Ada ada) = showFixed True ada
4949

50+
-- Newtype wrapper around Word64 so we can hand define a PersistentField instance.
51+
newtype DbWord64 = DbWord64 { unDbWord64 :: Word64 }
52+
5053
lovelaceToAda :: Micro -> Ada
5154
lovelaceToAda ll =
5255
Ada (ll / 1000000)

schema/migration-1-0001-20190730.sql

+4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ BEGIN
2525
-- is positive.
2626
EXECUTE 'CREATE DOMAIN word128type AS numeric (38, 0) CHECK (VALUE >= 0);';
2727

28+
-- 'maxBound :: Word64' as a decimal has 20 digits but not all 20 digit values are less than
29+
-- 'maxBound :: Word64'.
30+
EXECUTE 'CREATE DOMAIN word64type AS numeric (20, 0) CHECK (VALUE >= 0 AND VALUE <= 18446744073709551615);';
31+
2832
UPDATE "schema_version" SET stage_one = 1;
2933
RAISE NOTICE 'DB has been migrated to stage_one version %', next_version;
3034
END IF;

schema/migration-2-0003-20200723.sql schema/migration-2-0003-20200724.sql

+5-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ BEGIN
1010
EXECUTE 'ALTER TABLE "pool_hash" ADD CONSTRAINT "unique_pool_hash" UNIQUE("hash")' ;
1111
EXECUTE 'ALTER TABLE "slot_leader" ALTER COLUMN "hash" TYPE hash32type' ;
1212
EXECUTE 'ALTER TABLE "slot_leader" ADD COLUMN "pool_hash_id" INT8 NULL' ;
13+
EXECUTE 'ALTER TABLE "block" ALTER COLUMN "tx_count" TYPE INT8' ;
1314
EXECUTE 'ALTER TABLE "block" ADD COLUMN "vrf_key" hash32type NULL' ;
1415
EXECUTE 'ALTER TABLE "block" ADD COLUMN "op_cert" hash32type NULL' ;
1516
EXECUTE 'ALTER TABLE "block" ADD COLUMN "proto_version" VARCHAR NULL' ;
@@ -18,12 +19,13 @@ BEGIN
1819
EXECUTE 'ALTER TABLE "meta" DROP COLUMN "protocol_const"' ;
1920
EXECUTE 'ALTER TABLE "meta" DROP COLUMN "slot_duration"' ;
2021
EXECUTE 'ALTER TABLE "meta" DROP COLUMN "slots_per_epoch"' ;
22+
EXECUTE 'ALTER TABLE "epoch" ALTER COLUMN "out_sum" TYPE word128type' ;
2123
EXECUTE 'CREATe TABLE "stake_address"("id" SERIAL8 PRIMARY KEY UNIQUE,"hash" addr29type NOT NULL)' ;
2224
EXECUTE 'ALTER TABLE "stake_address" ADD CONSTRAINT "unique_stake_address" UNIQUE("hash")' ;
2325
EXECUTE 'CREATe TABLE "pool_meta_data"("id" SERIAL8 PRIMARY KEY UNIQUE,"url" VARCHAR NOT NULL,"hash" hash32type NOT NULL,"registered_tx_id" INT8 NOT NULL)' ;
2426
EXECUTE 'ALTER TABLE "pool_meta_data" ADD CONSTRAINT "unique_pool_meta_data" UNIQUE("url","hash")' ;
2527
EXECUTE 'ALTER TABLE "pool_meta_data" ADD CONSTRAINT "pool_meta_data_registered_tx_id_fkey" FOREIGN KEY("registered_tx_id") REFERENCES "tx"("id")' ;
26-
EXECUTE 'CREATe TABLE "pool_update"("id" SERIAL8 PRIMARY KEY UNIQUE,"hash_id" INT8 NOT NULL,"vrf_key" hash32type NOT NULL,"pledge" INT8 NOT NULL,"reward_addr_id" INT8 NOT NULL,"meta" INT8 NULL,"margin" DOUBLE PRECISION NOT NULL,"fixed_cost" INT8 NOT NULL,"registered_tx_id" INT8 NOT NULL)' ;
28+
EXECUTE 'CREATe TABLE "pool_update"("id" SERIAL8 PRIMARY KEY UNIQUE,"hash_id" INT8 NOT NULL,"vrf_key" hash32type NOT NULL,"pledge" word64type NOT NULL,"reward_addr_id" INT8 NOT NULL,"meta" INT8 NULL,"margin" DOUBLE PRECISION NOT NULL,"fixed_cost" lovelace NOT NULL,"registered_tx_id" INT8 NOT NULL)' ;
2729
EXECUTE 'ALTER TABLE "pool_update" ADD CONSTRAINT "unique_pool_update" UNIQUE("hash_id","registered_tx_id")' ;
2830
EXECUTE 'ALTER TABLE "pool_update" ADD CONSTRAINT "pool_update_hash_id_fkey" FOREIGN KEY("hash_id") REFERENCES "pool_hash"("id")' ;
2931
EXECUTE 'ALTER TABLE "pool_update" ADD CONSTRAINT "pool_update_reward_addr_id_fkey" FOREIGN KEY("reward_addr_id") REFERENCES "stake_address"("id")' ;
@@ -32,7 +34,7 @@ BEGIN
3234
EXECUTE 'CREATe TABLE "pool_owner"("id" SERIAL8 PRIMARY KEY UNIQUE,"hash" hash28type NOT NULL,"pool_id" INT8 NOT NULL)' ;
3335
EXECUTE 'ALTER TABLE "pool_owner" ADD CONSTRAINT "unique_pool_owner" UNIQUE("hash")' ;
3436
EXECUTE 'ALTER TABLE "pool_owner" ADD CONSTRAINT "pool_owner_pool_id_fkey" FOREIGN KEY("pool_id") REFERENCES "pool_hash"("id")' ;
35-
EXECUTE 'CREATe TABLE "pool_retire"("id" SERIAL8 PRIMARY KEY UNIQUE,"update_id" INT8 NOT NULL,"announced_tx_id" INT8 NOT NULL,"retiring_epoch" INT8 NOT NULL)' ;
37+
EXECUTE 'CREATe TABLE "pool_retire"("id" SERIAL8 PRIMARY KEY UNIQUE,"update_id" INT8 NOT NULL,"announced_tx_id" INT8 NOT NULL,"retiring_epoch" uinteger NOT NULL)' ;
3638
EXECUTE 'ALTER TABLE "pool_retire" ADD CONSTRAINT "unique_pool_retiring" UNIQUE("update_id")' ;
3739
EXECUTE 'ALTER TABLE "pool_retire" ADD CONSTRAINT "pool_retire_update_id_fkey" FOREIGN KEY("update_id") REFERENCES "pool_update"("id")' ;
3840
EXECUTE 'ALTER TABLE "pool_retire" ADD CONSTRAINT "pool_retire_announced_tx_id_fkey" FOREIGN KEY("announced_tx_id") REFERENCES "tx"("id")' ;
@@ -66,7 +68,7 @@ BEGIN
6668
EXECUTE 'ALTER TABLE "stake" ADD CONSTRAINT "unique_stake" UNIQUE("addr_id","stake")' ;
6769
EXECUTE 'ALTER TABLE "stake" ADD CONSTRAINT "stake_addr_id_fkey" FOREIGN KEY("addr_id") REFERENCES "stake_address"("id")' ;
6870
EXECUTE 'ALTER TABLE "stake" ADD CONSTRAINT "stake_tx_id_fkey" FOREIGN KEY("tx_id") REFERENCES "tx"("id")' ;
69-
EXECUTE 'CREATe TABLE "param_update"("id" SERIAL8 PRIMARY KEY UNIQUE,"epoch_no" INT8 NOT NULL,"min_fee" INT8 NOT NULL,"max_fee" INT8 NOT NULL,"max_block_size" INT8 NOT NULL,"max_tx_size" INT8 NOT NULL,"max_bh_size" INT8 NOT NULL,"key_deposit" lovelace NOT NULL,"pool_deposit" lovelace NOT NULL,"max_epoch" INT8 NOT NULL,"n_optimal" INT8 NOT NULL,"influence" DOUBLE PRECISION NOT NULL,"monetary_expand_rate" interval NOT NULL,"treasury_growth_rate" interval NOT NULL,"active_slot_coeff" interval NOT NULL,"decentralisation" interval NOT NULL,"entropy" hash32type NOT NULL,"protocol_version" BYTEA NOT NULL,"min_coin" lovelace NOT NULL)' ;
71+
EXECUTE 'CREATe TABLE "param_update"("id" SERIAL8 PRIMARY KEY UNIQUE,"epoch_no" uinteger NOT NULL,"min_fee" uinteger NOT NULL,"max_fee" uinteger NOT NULL,"max_block_size" uinteger NOT NULL,"max_tx_size" uinteger NOT NULL,"max_bh_size" uinteger NOT NULL,"key_deposit" lovelace NOT NULL,"pool_deposit" lovelace NOT NULL,"max_epoch" uinteger NOT NULL,"n_optimal" uinteger NOT NULL,"influence" DOUBLE PRECISION NOT NULL,"monetary_expand_rate" interval NOT NULL,"treasury_growth_rate" interval NOT NULL,"active_slot_coeff" interval NOT NULL,"decentralisation" interval NOT NULL,"entropy" hash32type NOT NULL,"protocol_version" BYTEA NOT NULL,"min_coin" lovelace NOT NULL)' ;
7072
EXECUTE 'ALTER TABLE "param_update" ADD CONSTRAINT "unique_param_update" UNIQUE("epoch_no")' ;
7173
-- Hand written SQL statements can be added here.
7274
UPDATE schema_version SET stage_two = 3 ;

0 commit comments

Comments
 (0)