|
1 | | -{-# LANGUAGE RankNTypes, ScopedTypeVariables #-} |
2 | | --- This modules tests Simplicity's serialization and deserialization functions. |
| 1 | +-- This module tests some serialization functionality. |
3 | 2 | module Simplicity.Bitcoin.Serialization.Tests (tests) where |
4 | 3 |
|
5 | | -import Control.Arrow ((|||)) |
6 | | -import Data.Either (lefts) |
| 4 | +import Control.Monad (mzero) |
7 | 5 | import Data.Foldable (toList) |
8 | | -import Data.Maybe (fromMaybe) |
9 | | -import Data.Serialize (Get, Putter, runGetState, runPut) |
10 | | -import qualified Data.Vector as V |
11 | | -import qualified Data.Vector.Unboxed as UV |
12 | | -import Lens.Family2 (Traversal, (&), (.~)) |
13 | | - |
14 | | -import Simplicity.Bitcoin.Dag |
15 | | -import Simplicity.Bitcoin.Inference |
| 6 | +import qualified Data.List as List |
| 7 | +import qualified Data.Vector.Unboxed as V |
| 8 | + |
| 9 | +import Simplicity.Arbitrary |
| 10 | +import Simplicity.CoreJets |
| 11 | +import Simplicity.Bitcoin.Jets as Bitcoin |
| 12 | +import Simplicity.Bitcoin.FFI.Primitive as Bitcoin |
| 13 | +import Simplicity.FFI.Dag |
16 | 14 | import Simplicity.MerkleRoot |
17 | | -import Simplicity.Bitcoin.Primitive |
18 | | -import Simplicity.Bitcoin.JetType |
19 | | -import Simplicity.Bitcoin.Serialization.BitString as BitString |
20 | | -import Simplicity.Bitcoin.Serialization.ByteString as ByteString |
21 | | -import Simplicity.Bitcoin.Term |
22 | | -import Simplicity.Digest |
23 | | -import qualified Simplicity.Programs.Arith as Arith |
24 | | -import Simplicity.Programs.Sha256.Lib |
25 | 15 | import Simplicity.Serialization |
26 | | -import Simplicity.Ty.Tests hiding (tests) |
| 16 | +import Simplicity.Ty |
27 | 17 | import Simplicity.Ty.Word |
28 | 18 |
|
29 | 19 | import Test.Tasty (TestTree, testGroup) |
30 | | -import Test.Tasty.QuickCheck ( Testable, testProperty, Positive(Positive) |
31 | | - , Gen, Property, arbitrary, choose, elements, forAll, listOf1, oneof) |
32 | | -import Test.QuickCheck.Property (Result, failed, succeeded, reason) |
| 20 | +import Test.Tasty.HUnit ((@=?), testCase) |
| 21 | +import Test.Tasty.QuickCheck (Property, arbitrary, forAll, chooseInt, testProperty, vectorOf) |
33 | 22 |
|
34 | | --- This collects the tests for the various serialization/deserialization pairs. |
| 23 | +-- Run tests comparing Bit Machine execution with Simplicity's denotational semantics using both naive and TCO translation. |
35 | 24 | tests :: TestTree |
36 | | -tests = testGroup "Serialization" |
37 | | - [ testProperty "get-put bit-string" prop_getPutBitString |
38 | | - , testProperty "get-put positive" prop_getPutPositive |
39 | | - , testProperty "get-put BitString DAG" prop_getPutBitStringDag |
40 | | - , testProperty "get-put ByteString DAG" prop_getPutByteStringDag |
41 | | - -- This collection tests type inference on a few sample programs. |
42 | | - , testGroup "Inference" |
43 | | - [ testInference "full_add word8" (Arith.full_add word8) |
44 | | - , testInference "add word8" (Arith.add word8) |
45 | | - , testInference "full_multiply word8" (Arith.full_multiply word8) |
46 | | - , testInference "multiply word8" (Arith.multiply word8) |
47 | | - , testInference "hashBlock" hashBlock |
48 | | - ] ] |
49 | | - |
50 | | --- Check that deserialization of serialization of bit-strings returns the original input. |
51 | | -prop_getPutBitString :: [Bool] -> Bool |
52 | | -prop_getPutBitString l = evalExactVector getBitString (UV.fromList (putBitString l [])) == Just l |
53 | | - |
54 | | --- Check that deserialization of serialization of positive numbers returns the original input. |
55 | | -prop_getPutPositive :: Positive Integer -> Bool |
56 | | -prop_getPutPositive (Positive n) = evalExactVector getPositive (UV.fromList (putPositive n [])) == Just n |
57 | | - |
58 | | --- Test a 'SimplicityDag' predicate over suitable Arbitrary inputs. |
59 | | -forallSimplicityDag :: Testable prop => (SimplicityDag [] Ty (SomeArrow NoJets) UntypedValue -> prop) -> Property |
60 | | -forallSimplicityDag = forAll gen_UntypedTermF_list |
| 25 | +tests = testGroup "Bitcoin Serialization" |
| 26 | + [ testGroup "Haskell" |
| 27 | + [ testDecodeBitcoinJet jt | SomeArrow jt@(Bitcoin.BitcoinJet _) <- toList Bitcoin.jetMap ] |
| 28 | + , testGroup "C" |
| 29 | + $ [ testDecodeBitcoinJetFFI jt | SomeArrow jt <- toList Bitcoin.jetMap ] |
| 30 | + ++ [ testProperty "prop_wordCMR" prop_wordCMR ] |
| 31 | + ] |
| 32 | + |
| 33 | +testDecodeBitcoinJet :: (TyC a, TyC b) => Bitcoin.JetType a b -> TestTree |
| 34 | +testDecodeBitcoinJet jt = testCase (show jt) (Just (SomeArrow jt) @=? decode) |
61 | 35 | where |
62 | | - gen_UntypedTermF_list = do |
63 | | - l <- traverse f =<< (zip [1..] <$> listOf1 gen_UntypedTermF) |
64 | | - case l of |
65 | | - [] -> return [] |
66 | | - nl -> (:nl) <$> elements [Iden one, Unit one] |
67 | | - where |
68 | | - f (i, term) = traverse (const (choose (1,i))) term |
69 | | - -- We are subverting putDag's type annotation requirement. It is for purpose of testing the 'putDag' function, so maybe it is okay to do. |
70 | | - -- :TODO: replace this with a proper generator for well-typed Simplicity terms. |
71 | | - gen_UntypedTermF :: Gen (TermF Ty j UntypedValue ()) |
72 | | - gen_UntypedTermF = oneof |
73 | | - [ pure $ Iden one |
74 | | - , pure $ Unit one |
75 | | - , pure $ Injl one one one () |
76 | | - , pure $ Injr one one one () |
77 | | - , pure $ Take one one one () |
78 | | - , pure $ Drop one one one () |
79 | | - , pure $ Comp one one one () () |
80 | | - , pure $ Case one one one one () () |
81 | | - , pure $ Pair one one one () () |
82 | | - , pure $ Disconnect one one one one () () |
83 | | - , Hidden <$> get256Bits arbitrary |
84 | | - , wit |
85 | | - ] |
86 | | - where |
87 | | - -- Here we are careful to place a correct type annotation for witness values. |
88 | | - wit = do |
89 | | - b <- arbTy |
90 | | - v <- case reflect b of SomeTy rb -> untypedValue <$> arbValueR rb |
91 | | - return (Witness one b v) |
| 36 | + vector = V.fromList $ Bitcoin.putJetBit jt [] |
| 37 | + decode = evalExactVector (Bitcoin.getJetBit mzero) vector |
92 | 38 |
|
93 | | --- Compare 'SimplicityDag' disregarding most type annotations. |
94 | | --- Witness nodes are compared using the 'compareWitness' function which may or may not consider type annotations. |
95 | | -compareDag :: (Eq a, Eq j) => (ty0 -> w0 -> ty1 -> w1 -> Bool) -> [TermF ty0 j w0 a] -> [TermF ty1 j w1 a] -> Bool |
96 | | -compareDag compareWitness v1 v2 = (and $ zipWith compareNode v1 v2) && (length v1 == length v2) |
| 39 | +testDecodeBitcoinJetFFI :: (TyC a, TyC b) => Bitcoin.JetType a b -> TestTree |
| 40 | +testDecodeBitcoinJetFFI jt = testCase (show jt) (Right cmr @=? Bitcoin.decodeJetCMR bitstream) |
97 | 41 | where |
98 | | - compareNode (Iden _) (Iden _) = True |
99 | | - compareNode (Unit _) (Unit _) = True |
100 | | - compareNode (Injl _ _ _ x0) (Injl _ _ _ x1) = x0 == x1 |
101 | | - compareNode (Injr _ _ _ x0) (Injr _ _ _ x1) = x0 == x1 |
102 | | - compareNode (Take _ _ _ x0) (Take _ _ _ x1) = x0 == x1 |
103 | | - compareNode (Drop _ _ _ x0) (Drop _ _ _ x1) = x0 == x1 |
104 | | - compareNode (Comp _ _ _ x0 y0) (Comp _ _ _ x1 y1) = [x0,y0] == [x1,y1] |
105 | | - compareNode (Case _ _ _ _ x0 y0) (Case _ _ _ _ x1 y1) = [x0,y0] == [x1,y1] |
106 | | - compareNode (Pair _ _ _ x0 y0) (Pair _ _ _ x1 y1) = [x0,y0] == [x1,y1] |
107 | | - compareNode (Disconnect _ _ _ _ x0 y0) (Disconnect _ _ _ _ x1 y1) = [x0,y0] == [x1,y1] |
108 | | - compareNode (Hidden h0) (Hidden h1) = h0 == h1 |
109 | | - compareNode (Witness _ b0 w0) (Witness _ b1 w1) = compareWitness b0 w0 b1 w1 |
110 | | - compareNode (Jet j0) (Jet j1) = j0 == j1 |
111 | | - compareNode _ _ = False |
| 42 | + -- All jet encodings should begin with a 1 bit, which we consume. |
| 43 | + True:bitstream = Bitcoin.putJetBit jt [] |
| 44 | + cmr = commitmentRoot (asJet jt) |
112 | 45 |
|
113 | | --- Check that 'BitString.putDag's serialization of 'SimplicityDag's works can be deserialized by a combination of 'BitString.getDagNoWitness' and 'BitString.getWitnessData'. |
114 | | --- Note: Because we do not yet have a generator for arbitrary well-typed Simplicity expressions we cannot easily test 'BitString.putTerm' with 'BitString.getTerm'. |
115 | | --- Instead we perform an awkward combinator of 'BitString.getDagNoWitness' and 'BitString.getWitnessData' on mostly untyped Simplicity DAGs for now. |
116 | | -prop_getPutBitStringDag :: Property |
117 | | -prop_getPutBitStringDag = forallSimplicityDag prop |
| 46 | +prop_wordCMR :: SomeConstWordContent -> Property |
| 47 | +prop_wordCMR (SomeConstWordContent cwc) = forAll prefix prop |
118 | 48 | where |
119 | | - compareWitness _ w0 _ w1 = w0 == w1 |
120 | | - prop :: SimplicityDag [] Ty (SomeArrow NoJets) UntypedValue -> Result |
121 | | - prop v = case eval of |
122 | | - Left msg -> failed { reason = show msg } |
123 | | - Right (pdag, wdag) | not (compareDag (\_ _ _ _ -> True) v (toList pdag)) -> failed { reason = "Bitstring.getDagNoWiness returned bad value" } |
124 | | - | not (compareDag compareWitness v (toList wdag)) -> failed { reason = "Bitstring.getWitnessData returned bad value" } |
125 | | - | otherwise -> succeeded |
| 49 | + prefix = do |
| 50 | + n <- chooseInt (0, 7) |
| 51 | + vectorOf n arbitrary |
| 52 | + prop l = wordCMR == computeWordCMR (length l) (l ++ stream) |
126 | 53 | where |
127 | | - pbs = BitString.putDagNoWitnessLengthCode v |
128 | | - Just wbs = BitString.putWitnessData v -- generation is designed to create terms that always succeed at serializaiton. |
129 | | - evalPDag = flip evalStreamWithError pbs $ \abort next -> do |
130 | | - BitString.getDagNoWitnessLengthCode abort next |
131 | | - evalWDag = flip evalStreamWithError wbs $ \abort next -> do |
132 | | - BitString.getWitnessData vStripped next |
133 | | - eval = (,) <$> evalPDag <*> evalWDag |
134 | | - vStripped = v & traverse . witness_ .~ () |
135 | | - where |
136 | | - witness_ :: Traversal (TermF ty j w0 a) (TermF ty j w1 a) w0 w1 |
137 | | - witness_ = witnessData . const |
138 | | - |
139 | | --- Check that deserialization of serialization of 'SimplicityDag's works for the byte-string serialization. |
140 | | -prop_getPutByteStringDag :: Property |
141 | | -prop_getPutByteStringDag = forallSimplicityDag prop |
142 | | - where |
143 | | - compareWitness b w0 _ w1 = case reflect b of |
144 | | - SomeTy rb -> fromMaybe False $ (==) <$> castUntypedValueR rb w0 <*> evalExactVector (getValueR rb) w1 |
145 | | - prop v = case runGetState (toList <$> ByteString.getDag) bs 0 of |
146 | | - Left _ -> False |
147 | | - Right (v', rest) -> rest == mempty && compareDag compareWitness v (v' :: SimplicityDag [] () (SomeArrow NoJets) (UV.Vector Bool)) |
148 | | - where |
149 | | - Just bs = runPut <$> ByteString.putDag v -- generation is designed to create terms that always succeed at serializaiton. |
150 | | - |
151 | | --- Check that type inference on Simplicity expressions produce correct terms by testing their Merkle roots. |
152 | | -testInference :: forall a b. (TyC a, TyC b) => String -> (forall term. (Core term) => term a b) -> TestTree |
153 | | -testInference name program = testGroup name [testProperty "CommitmentRoot" assertion1, testProperty "AnnotatedRoot" assertion2] |
154 | | - where |
155 | | - dag :: NoJetDag a b |
156 | | - dag = program |
157 | | - -- type inference on first pass is not necessarily equal to the original program because the Haskell type of internal nodes in the original program might not have the term's principle type. |
158 | | - pass1 :: forall term. Simplicity term => Either String (term a b) |
159 | | - pass1 = typeCheck =<< typeInference dag (jetDag dag) |
160 | | - -- Type inference on the second pass ought to always be equal to the first pass. |
161 | | - pass2 :: forall term. Simplicity term => Either String (term a b) |
162 | | - pass2 = typeCheck =<< (typeInference dag . jetDag) =<< (pass1 :: Either String (NoJetDag a b)) |
163 | | - assertion1 = pass1 == Right (program :: CommitmentRoot a b) |
164 | | - assertion2 = pass2 == (pass1 :: Either String (AnnotatedRoot a b)) |
| 54 | + wordCMR = commitmentRoot $ asJet (ConstWordJet cwc) |
| 55 | + stream = putConstWordValueBit cwc |
0 commit comments