@@ -22,7 +22,7 @@ import qualified Data.List.NonEmpty as NE
2222import Data.Time.Clock
2323import qualified Data.Text as T
2424import GHC.Generics
25−import Data.Binary
25+import Data.Binary as B
2626import Data.Either (lefts, rights, isRight)
2727#if __GLASGOW_HASKELL__ < 804
2828import Data.Monoid
@@ -30,6 +30,10 @@ import Data.Monoid
3030import Control.Arrow
3131import Data.Maybe
3232import Data.UUID.V4
33+import qualified Data.ByteString as BS
34+import qualified Data.ByteString.Lazy as BL
35+import ProjectM36.DatabaseContext as DBC
36+import Crypto.Hash.SHA256
3337
3438-- | Record a lookup for a specific transaction in the graph.
3539data TransactionIdLookup = TransactionIdLookup TransactionId |
@@ -67,7 +71,8 @@ bootstrapTransactionGraph stamp' freshId context = TransactionGraph bootstrapHea
6771 bootstrapHeads = M.singleton "master" freshTransaction
6872 newSchemas = Schemas context M.empty
6973 freshTransaction = fresh freshId stamp' newSchemas
70− bootstrapTransactions = S.singleton freshTransaction
74+ hashedTransaction = Transaction freshId ((transactionInfo freshTransaction) { merkleHash = calculateMerkleHash freshTransaction emptyTransactionGraph }) newSchemas
75+ bootstrapTransactions = S.singleton hashedTransaction
7176
7277-- | Create a transaction graph from a context.
7378freshTransactionGraph :: DatabaseContext -> IO (TransactionGraph, TransactionId)
@@ -125,10 +130,13 @@ addBranch stamp' newId newBranchName branchPointId graph = do
125130
126131--adds a disconnected transaction to a transaction graph at some head
127132addDisconnectedTransaction :: UTCTime -> TransactionId -> HeadName -> DisconnectedTransaction -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
128−addDisconnectedTransaction stamp' newId headName (DisconnectedTransaction parentId schemas' _) = addTransactionToGraph headName newTrans
133+addDisconnectedTransaction stamp' newId headName (DisconnectedTransaction parentId schemas' _) graph = addTransactionToGraph headName newTrans' graph
129134 where
130− newTrans = Transaction newId (TI.singleParent parentId stamp') schemas'
131−
135+ newTrans = Transaction newId newTInfo schemas'
136+ newTInfo = TI.singleParent parentId stamp'
137+ newTrans' = Transaction newId (newTInfo { merkleHash = newHash }) schemas'
138+ newHash = calculateMerkleHash newTrans graph--lookup parents in graph to calculate
139+--LOOP
132140addTransactionToGraph :: HeadName -> Transaction -> TransactionGraph -> Either RelationalError (Transaction, TransactionGraph)
133141addTransactionToGraph headName newTrans graph = do
134142 let parentIds' = parentIds newTrans
@@ -295,6 +303,7 @@ graphAsRelation (DisconnectedTransaction parentId _ _) graph@(TransactionGraph _
295303 mkRelationFromList attrs tupleMatrix
296304 where
297305 attrs = A.attributesFromList [Attribute "id" TextAtomType,
306+ Attribute "hash" ByteStringAtomType,
298307 Attribute "stamp" DateTimeAtomType,
299308 Attribute "parents" (RelationAtomType parentAttributes),
300309 Attribute "current" BoolAtomType,
@@ -304,6 +313,7 @@ graphAsRelation (DisconnectedTransaction parentId _ _) graph@(TransactionGraph _
304313 tupleGenerator transaction = case transactionParentsRelation transaction graph of
305314 Left err -> Left err
306315 Right parentTransRel -> Right [TextAtom $ T.pack $ show (transactionId transaction),
316+ ByteStringAtom $ merkleHash (transactionInfo transaction),
307317 DateTimeAtom (timestamp transaction),
308318 RelationAtom parentTransRel,
309319 BoolAtom $ parentId == transactionId transaction,
@@ -338,7 +348,8 @@ createMergeTransaction stamp' newId (SelectedBranchMergeStrategy selectedBranch)
338348 pure $ Transaction newId (TransactionInfo {
339349 parents = NE.fromList [transactionId trans1,
340350 transactionId trans2],
341− stamp = stamp' }) (schemas selectedTrans)
351+ stamp = stamp',
352+ merkleHash = unimplemented }) (schemas selectedTrans)
342353
343354-- merge functions, relvars, individually
344355createMergeTransaction stamp' newId strat@UnionMergeStrategy t2 =
@@ -496,7 +507,8 @@ createUnionMergeTransaction stamp' newId strategy (t1,t2) = do
496507 pure (Transaction newId (TransactionInfo {
497508 parents = NE.fromList [transactionId t1,
498509 transactionId t2],
499− stamp = stamp'}) newSchemas)
510+ stamp = stamp',
511+ merkleHash = unimplemented }) newSchemas)
500512
501513lookupTransaction :: TransactionGraph -> TransactionIdLookup -> Either RelationalError Transaction
502514lookupTransaction graph (TransactionIdLookup tid) = transactionForId tid graph
@@ -572,3 +584,21 @@ autoMergeToHead stamp' (tempBranchTransId, tempCommitTransId, mergeTransId) disc
572584 pure (discon''''', graph''''')
573585
574586
587+-- the new hash includes the parents' ids, the current id, and the hash of the context, and the merkle hashes of the parent transactions
588+calculateMerkleHash :: Transaction -> TransactionGraph -> BS.ByteString
589+calculateMerkleHash trans graph = hashlazy (mconcat ([transIds,
590+ dbcBytes,
591+ schemasBytes,
592+ parentMerkleHashes]))
593+ where
594+ parentMerkleHashes = BL.fromChunks $ map getMerkleHash parentTranses
595+ parentTranses =
596+ case transactionsForIds (parentIds trans) graph of
597+ Left RootTransactionTraversalError -> []
598+ Left e -> error ("failed to find transaction in Merkle hash construction: " ++ show e)
599+ Right t -> S.toList t
600+ getMerkleHash t = merkleHash (transactionInfo t)
601+ transIds = B.encode (transactionId trans : S.toList (parentIds trans))
602+ dbcBytes = DBC.hashBytes (concreteDatabaseContext trans)
603+ schemasBytes = B.encode (subschemas trans)
604+