Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jörmungandr binary roundtrips #476

Merged
merged 6 commits into from
Jul 3, 2019
Merged

Jörmungandr binary roundtrips #476

merged 6 commits into from
Jul 3, 2019

Conversation

Anviking
Copy link
Collaborator

@Anviking Anviking commented Jun 25, 2019

Issue Number

#219

Overview

  • I've added roundtrips testing decode (encode signedTx) === signedTx and the same for addresses.

Comments

@Anviking Anviking self-assigned this Jun 25, 2019
@Anviking Anviking force-pushed the anviking/219/testing branch 2 times, most recently from b1457a8 to 71d571f Compare June 26, 2019 17:07
@Anviking Anviking changed the title Jörmungandr binary roundtrips (+ maybe more tests) Jörmungandr binary roundtrips Jun 27, 2019
@Anviking Anviking force-pushed the anviking/219/testing branch 2 times, most recently from 5d0298c to c9f19dd Compare June 27, 2019 10:07
@Anviking Anviking changed the base branch from master to anviking/219/stricter-encoders June 27, 2019 10:08
@Anviking Anviking marked this pull request as ready for review June 27, 2019 10:18
try' = unsafePerformIO . wrap

wrap :: a -> IO (Either String a)
wrap = fmap (either (Left . show) Right) . (try @SomeException) . evaluate
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two functions above are dodgy :s ... What's wrong with using runGet ?

Copy link
Collaborator Author

@Anviking Anviking Jun 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's wrong with using runGet

Not sure what you're referring to here, but runGet throws exceptions. And it seemed easier to not have to use the different api of Test.QuickCheck.Monadic when I was starting this. Hence the unsafePerformIO and Control.Exception.try.

There is actually an Either-returning version of runGet. But none for runPut.

unless (length outs < 256) $
Left "number of outputs must be lower than 256"
forM_ ins $ \(TxIn _tx ix, _coin) ->
unless (ix < 256) $ Left "input index must be < 256"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like we are re-encoding code source logic into tests which is a sign that either of them are wrong. If the source can't be easily tested, that's a problem. Or maybe, the test isn't testing at the right level of abstraction.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! I had a thought about remaking this function to

validateSignedTx :: (Tx, [TxWitness]) -> Either ErrSignedTxValidation (Tx, [TxWitness])

, moving it and calling it in mkStdTx. We could test that decode (encode signedTx) throws iff validateSignedTx returns Left.

Although then it would be tempting to have jormungandr-specific types:

data SignedTxIn = SignedTxIn Address Word8 Coin TxWitness

data SignedTx = SignedTx
    { signedInputs :: [SignedTxIn]
    , outputs :: [TxOut]

and make the encoders work with them… 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure this function should even exist (cf comments on the other PR). Instead:

  • generators for property-based tests should generate valid values
  • there should be some "negative tests" showing that, for invalid transaction types, the serialization fails.

Copy link
Collaborator Author

@Anviking Anviking Jun 28, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To begin with:
upon further reflection, I think there are two important properties:

-- Note: tx :: (Tx, [TxWitness])) below

-- **Jörmungandr-valid transactions roundtrip**
forall tx. valid tx ==>  decode (encode tx) === tx

-- **We must not be able to encode a jörmungandr-invalid transaction
-- to valid binary**. "Valid binary" means that we can decode it.
--
-- So while we don't care about general Tx round-tripping, we care about 
-- the result either 1) not being a 'Tx' or 2) being Jörmungandr-valid, in which
-- case it must be the same tx as we started with:
forall tx. case (decode (encode tx)) of
                   Right tx' -> tx' == tx
                   Left _ -> True -- we don't care

Sketch on the topic:
Skärmavbild 2019-06-28 kl  14 49 29

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And well, then we need the definition of a [jörmungandr-]valid transaction in one way or another. @KtorZ thoughts?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I forgot one point. By labelling the errors - either via error messages in the Binary module or in the valid function we can know that we are generating interesting enough jörmungandr-invalid transactions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm... valid tx ==> decode (encode tx) is an invalid use-case for the pre-condition. Just make your generator valid in the first place! If you want to test invalid transaction, then make a generator which generates invalid ones... though, this is a bit counter-productive with having a type-system in a first place.

The idea of a strongly typed system is to make sure that, having something of a given type gives us some guarantees and invariant about this type. So, by considering a Tx, we are assuming that it is well-formed, otherwise, we are better of having a ([TxIn], [TxOut]) and makes no assumption about the number of inputs and outputs.

We have properties checking that our coin selection preserve such invariant (and only construct valid txs). We can assume something similar for transaction coming out of Jörmungandr. However, there's nothing wrong with expressing those invariants in code and have some explicit assertions which would loudly fail in case they are violated. It's usually better to encode in the type system whatever we can, although here, we are really deserializing bytes from a trusted source, and there's not much we could do apart from crashing if a tx was invalid.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I was paranoid about the "strong typed system" being too weak and the safetynet failing us 🤔

Anyhow --> #476 (comment)

shrinkFixedBS bs = if bs /= zeros then [zeros] else []
where
len = BS.length bs
zeros = BS.pack (replicate len 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if the shrinker buys us anything here. Bytes are just ... bytes, there's no sensible to shrink them (in that context at least).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted the failing test cases to be less noisy by shrinking to all zeros, and I didn't want to generate only zeros.

shrink (Hash bytes) = Hash <$> shrinkFixedBS bytes

instance Arbitrary Coin where
arbitrary = genericArbitrary
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if genericArbitrary does preserve Bounded instances, does it ?

Copy link
Collaborator Author

@Anviking Anviking Jul 2, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, but I wrote a custom one now.

@Anviking Anviking force-pushed the anviking/219/stricter-encoders branch 6 times, most recently from aa8851a to b399323 Compare July 1, 2019 16:16
@Anviking Anviking force-pushed the anviking/219/testing branch 3 times, most recently from 56c2dfd to 7f63a72 Compare July 2, 2019 13:06
@Anviking Anviking changed the base branch from anviking/219/stricter-encoders to master July 2, 2019 13:06
- Only generate valid Signed transactions for roundtrips
- adjust Arbitrary instances to shrink better

- Remove redundant debug-info and check line-lengths

It is good practice to use 'label' and 'cover' etc, but there wasn't
anything interesting to capture here in the end.

- Re-generate nix
@Anviking
Copy link
Collaborator Author

Anviking commented Jul 2, 2019

Generators now only generate valid transactions. Potential remaining points for objections:

  • Use of unsafePerformIO instead of monadicIO
  • Shrinkers - we could remove them

@Anviking Anviking requested a review from KtorZ July 2, 2019 14:07
case f x of
(x':_) -> (x':) <$> xs
(_) -> Nothing
) (Just []) l
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure to get the need for this one? The formers:

        [SignedTx (Tx ins' outs, wits')
        | (ins', wits') <- unzip <$> shrinkList' (zip ins wits)] ++
        [SignedTx (Tx ins outs', wits) | outs' <- shrinkList' outs ]

will already shrink outputs to very reasonable counter examples, won't they?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shrinkList' doesn't shrink the elements of the lists. The way it is now that's not guaranteed either, but it might shrink the elements 😬

So we… could remove most of the other shrinkers and we'd be kind of fine, keep them as is, or we could make them perfect 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you tried the shrinker with only:

        [SignedTx (Tx ins' outs, wits')
        | (ins', wits') <- unzip <$> shrinkList' (zip ins wits)] ++
        [SignedTx (Tx ins outs', wits) | outs' <- shrinkList' outs ]

and observed the output ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes…

coin = Coin {getCoin = 34180938945809574}},TxOut {address = Address {unAddress = "\ETXEx\\'>v56F\vx\NUL\EOT,Zn~\SOre<]\DC1Q8\DC4\\\DLEw\ETB\t\DLE"}, coin = Coin {getCoin = 44999999999999985}},TxOut {address = Address {unAddress = "\ETXx\vl\DC3&Z\vX1]S\ETXeo_A\CAN]Y\DC3\DC2=v\SOebqzO-QK"}, coin = Coin {getCoin = 15}},TxOut {address = Address {unAddress = "\ETX#\SOHi_:\NUL>Of\STXrc.\NAK??\EMqVk2_1\SO|1jM]~\128\RS"}, coin = Coin {getCoin = 44999999999999906}},TxOut {address = Address {unAddress = "\ETXF\SYN\CAN\STXZFp\r$<X\SI\ACKME6\DC34!)\ESCD\STX\SO0-\DEL8[yE\a"}, coin = Coin {getCoin = 57}},TxOut {address = Address {unAddress = "\ETXAyxP[2\DLENY7|q[<T@\NUL0\RSI&\ETB\USj[.N$\EM\ETX\128\FS"}, coin = Coin {getCoin = 3773066673805617}},TxOut {address = Address {unAddress = "\ETXUv\US\FSVRG$e\DEL\EOTXm3N~u!]\EMR3\f\RSY|#<*O'\FS"}, coin = Coin {getCoin = 44}},TxOut {address = Address {unAddress = "\ETX\DC2\\\fxeSH\DC3EqIt!\NAK*l/$R\128cP\128}\NAKDFV\ACK_*\EM"}, coin = Coin {getCoin = 44999999999999929}},TxOut {address = Address {unAddress = "\ETXE\DEL23u\SOS HAKuf9\USS\DEL[>D~\ENQqSP=\STX\GS U6f"}, coin = Coin {getCoin = 44999999999999965}},TxOut {address = Address {unAddress = "\ETX\b'?r!!*&<y>K1\EOT#9G=BrW\SYN=;\DC3D+P\DC2#\EM\""}, coin = Coin {getCoin = 75}},TxOut {address = Address {unAddress = "\ETX6gC~U+\\a06\SOi#\atOsp\ETX#H`\fEwZzc\br\"6"}, coin = Coin {getCoin = 6}},TxOut {address = Address {unAddress = "\ETXO\NAKf\SUB5\128Js!3 \SOH\DLEv|^\DC21~\ETXx+m7vLV`Z\DC3@\n"}, coin = Coin {getCoin = 869739039304620}},TxOut {address = Address {unAddress = "\ETX2#\v.Fo]A8Mn<n;.\SYN`\vy\STXkf$'\ENQ?M0wbs7"}, coin = Coin {getCoin = 90}},TxOut {address = Address {unAddress = "\ETX\DC1.dTDX_}x1 x\SI'i\USLbTieB`&\DEL\"\DC3@\DC4=.C"}, coin = Coin {getCoin = 93}},TxOut {address = Address {unAddress = "\ETXZp\128&\DC2`\n`T\ACKW0O\"R\bdv}%:\GS,9\nMSG\n\rf\ESC"}, coin = Coin {getCoin = 43970056871992574}},TxOut {address = Address {unAddress = "\ETXB-,+f9#p]Z\DC1A*x\ENQYG`\DC26nioWa\RSnv2k\SOHG"}, coin = Coin {getCoin = 20}},TxOut {address = Address {unAddress = "\ETXQ\b\\+\SO{BoCJ\ETB\STX8g\SIsJg\"G4npxB\r\DLEqJ\NUL\NAK)"}, coin = Coin {getCoin = 17218733216125888}},TxOut {address = Address {unAddress = "\ETX\ENQNbp0\EMf\NUL\ETBTj\US\CAN\NULav_\128\&2~N/O\ACKc|_\\=b\a\CAN"}, coin = Coin {getCoin = 3022692135060161}},TxOut {address = Address {unAddress = "\ETXFNF6zh\DC3\SYN\DC1|g{ +7\FS#Z\EOT[#68W\NULrWmD\ETXYw"}, coin = Coin {getCoin = 44999999999999987}},TxOut {address = Address {unAddress = "\ETXgG\tv\v-XkL\DC4MbWW>\f^hR\ap\NAK`\a\FS(I\US1-1\US"}, coin = Coin {getCoin = 44999999999999933}},TxOut {address = Address {unAddress = "\ETX/x\SYN03\DC1C.Wv\ENQ\ACKK.\CAN~u\SIZ\SYNN:\DC41\FSjS`c\SIMD"}, coin = Coin {getCoin = 86}},TxOut {address = Address {unAddress = "\ETXX;> \fupz\SI#>&(6\RSFD]\DLE\USi\CAN\SYN\GSA`\DC3.ds9\DC4"}, coin = Coin {getCoin = 34000846781260719}},TxOut {address = Address {unAddress = "\ETXRvi$y\SOH\DC1^\DEL~sbF8[:7zcD`\EOT\ACK\128&ZQ(}+\SOH)"}, coin = Coin {getCoin = 16227796498456957}},TxOut {address = Address {unAddress = "\ETX\nX\NAK\EM\GS&\US\ACK\CAN0v(9$0DVw-\f\b\b\\\NULF?!Ig\v?K"}, coin = Coin {getCoin = 44999999999999904}},TxOut {address = Address {unAddress = "\ETX`MIo[ZI}kO\STX Y>\SUBCW\ESCR=Jw&t'0Hh*\DC2f&"}, coin = Coin {getCoin = 2975782956370087}},TxOut {address = Address {unAddress = "\ETXP5+Xal\ACKm4\DELI$TCS6 MNEpT4$\DEL(u=C`P*"}, coin = Coin {getCoin = 35297643722518077}},TxOut {address = Address {unAddress = "\ETXi+eK\DLE.6:NpX<\fg$\SI\DC4LA\SUByE\DC3\SUBJ\DEL*~\fJ#h"}, coin = Coin {getCoin = 44999999999999961}},TxOut {address = Address {unAddress = "\ETXwM{k\r\ENQ<<$\v\NULC\b2D^\t\SI\SYN2TG!6T|z\EMK\ETX%\ETB"}, coin = Coin {getCoin = 49}},TxOut {address = Address {unAddress = "\ETXK\"*@\NAKl\STXL4\SYNcLr!\ENQp?y#\DLE3[AB\GS1+s\DC1j\DC4\t"}, coin = Coin {getCoin = 1612748889065292}},TxOut {address = Address {unAddress = "\ETX[^\128\NAK{\FSL)7p\CAN8ZcL@Nra~/G\SYNcyc.\DC3E\b~~"}, coin = Coin {getCoin = 34149941902569273}},TxOut {address = Address {unAddress = "\ETXw(wr\GS0\n<\ENQp\DEL\EMofs\CAN\RS_*F\NUL4\SO'yX\SIfz]&\a"}, coin = Coin {getCoin = 9478177015978517}},TxOut {address = Address {unAddress = "\ETXx%{WU\SI\SO\ACK\\\US%\"yB\EM\nEBMsE` EyE^2O\SUBBT"}, coin = Coin {getCoin = 21}},TxOut {address = Address {unAddress = "\ETXXiQj>rW\ETBdI';}\t\EM<\DELX/:8\"=4\ETBZ\v'H\ETX;\NAK"}, coin = Coin {getCoin = 6205270086029695}},TxOut {address = Address {unAddress = "\ETXm\ETB\\!f\ESC\RS\ETX\DC1Sop\DELQo\a\EMO`@\US\n]FGSFbb\rx\DC4"}, coin = Coin {getCoin = 44999999999999930}},TxOut {address = Address {unAddress = "\ETXI?\ESC\ACKht01u;U\DEL,\EOT\SOV+9\ETX&G\ACK\SI\tS6S\EMFwN\DC1"}, coin = Coin {getCoin = 8858930130137839}},TxOut {address = Address {unAddress = "\ETX6XO\aK(\EOT*N\ETX>p/vYE-9D@WU\CAN\NAKM9\DC2R*W,C"}, coin = Coin {getCoin = 52}},TxOut {address = Address {unAddress = "\ETX(bHo\NUL_S9%|#!vl+c\DC3\DC3G,(=s\NULKceg\r\RS]`"}, coin = Coin {getCoin = 89}},TxOut {address = Address {unAddress = "\ETX0\128(N|!0x*2X\EMd6Z\"xL\"znIH7}D\SOHEN-o\ESC"}, coin = Coin {getCoin = 44999999999999909}},TxOut {address = Address {unAddress = "\ETX,<CY\SO\b4/\FSkUj\SI\DC4_\ETB\USM/MU\EOTj-\DC2O,)g\DC3s\SOH"}, coin = Coin {getCoin = 19498067338884164}},TxOut {address = Address {unAddress = "\ETX\CAN\RS\SI\SYNkRi p.9a~S>jKR\DC3\DELB\DELM3\DELpn[\ETX\DC4o-"}, coin = Coin {getCoin = 1748180663868147}},TxOut {address = Address {unAddress = "\ETX~\rN!\t?EM'4s\ETXgR|T,>EX\"D`6mRBbN4B\DC3"}, coin = Coin {getCoin = 88}},TxOut {address = Address {unAddress = "\ETX`3'=\DLEJ}z`\NAKvbpi[\DLE\f\CANTJ-W|\DC1P\DC38?w6AI"}, coin = Coin {getCoin = 79}},TxOut {address = Address {unAddress = "\ETX\ENQylp*\ETB\\Nzh\vg$;\DLE\EOTDy4\SUB\USG!g\\\NAKM\DC2(+7\FS"}, coin = Coin {getCoin = 44999999999999944}},TxOut {address = Address {unAddress = "\ETXJJ\a,uu}C?x[AW\SOH*mR\EM\ETXF:FHXM <\ETXC\EMa("}, coin = Coin {getCoin = 3}},TxOut {address = Address {unAddress = "\ETXq15Drmo\CAN#hU=F)NZ{E\f7//3r\ACK8\STXi5ts-"}, coin = Coin {getCoin = 20}},TxOut {address = Address {unAddress = "\ETXb\t\"w\ACK5q=\SYNHX\DC1\t>SmWW\SI\t6oG\STX\FSFbh:7\DC4 "}, coin = Coin {getCoin = 48}},TxOut {address = Address {unAddress = "\ETXQE?|?w&i!h\SIN\DC2L'RZ!}m\DC3~\ETBu= \CAN~&wvb"}, coin = Coin {getCoin = 44999999999999905}},TxOut {address = Address {unAddress = "\ETX853-\DLE\SOH\nU\"\SUB8=djlT@KYa*m/h\DC1\US\DC3/g[\SI@"}, coin = Coin {getCoin = 33174635739604593}},TxOut {address = Address {unAddress = "\ETXc{@a'2Qoxv<MQ\DC3'\ESC\DC28#zj\ETB J\128[\128\RS\ETXRB`"}, coin = Coin {getCoin = 44999999999999959}},TxOut {address = Address {unAddress = "\ETX74}d}{\ETX*\\smsA*X\EM^\NAK\ACK\DC1`T*5\ESCD\128\\\FSYn\ENQ"}, coin = Coin {getCoin = 11}},TxOut {address = Address {unAddress = "\ETXAZ\SOX'l[0\DC2P\EOTK\SUB.\STX\DLE.\fX\SO!y(2;\SOHFVz\GS.\ACK"}, coin = Coin {getCoin = 33876087739400020}},TxOut {address = Address {unAddress = "\ETX\v}\DC1ivJ\RS;{\GS;_`:B\CANI`0,\a+\ESC_uM_T7,!4"}, coin = Coin {getCoin = 44999999999999973}},TxOut {address = Address {unAddress = "\ETX%aUl~\\\v-;/#,MQ\128\EM\DC2\SYN\ENQys\SI\fWs}K9\SOH}-\f"}, coin = Coin {getCoin = 44999999999999919}},TxOut {address = Address {unAddress = "\ETX\CANs\v*?>u(wc^|It[\128}O}F/1u\t.oA\b*c\v\NAK"}, coin = Coin {getCoin = 44999999999999990}},TxOut {address = Address {unAddress = "\ETX>G`C\SO5\DEL[J\DC1 7D|h*\NUL^)ii]\DEL2!/N\ACK.Y\RSm"}, coin = Coin {getCoin = 0}},TxOut {address = Address {unAddress = "\ETX\SUB\STXI(\DC4\asz,\DLEG&\DC1|\FSe]V\DEL~1 hI\ACKFU[#\SUB<r"}, coin = Coin {getCoin = 22611204891216084}},TxOut {address = Address {unAddress = "\ETX;m&\SO8-\DC2NQ\t\SOHX\SI\FS Z>wIB1+g>P\t^O5$)$"}, coin = Coin {getCoin = 67}},TxOut {address = Address {unAddress = "\ETXH0|U\GS4]\EOTU2h9\ENQ\DC2\NAK\STX>A@EA\NULW\FSl\rB\ETXA\SIsE"}, coin = Coin {getCoin = 42296183007052279}},TxOut {address = Address {unAddress = "\ETXg+z\SYN\r\RS\DLEG_uRx$\DC3\EM\"V\SYNPIA9g)\EM\ENQ\DC4\SI=w\NULo"}, coin = Coin {getCoin = 52}},TxOut {address = Address {unAddress = "\ETX\ACK\SOHqyn^\128\a\NAKh}y[M\EOT\NAK\CANC\ETB=Y\FS\SOH7N\EOTo-,<\FS\NUL"}, coin = Coin {getCoin = 30784557493257846}},TxOut {address = Address {unAddress = "\ETX^O3\EM\DC3QG\"2\DC4\DLE;KIuja6\ETBR(\GS\f/UL\SOH\DEL\r\r\EOT\n"}, coin = Coin {getCoin = 83}},TxOut {address = Address {unAddress = "\ETXw\128@PFSTi~*S0,5G \GS/*i\FS2\ETX\RSi\DC1UAF#x\EM"}, coin = Coin {getCoin = 44999999999999954}},TxOut {address = Address {unAddress = "\ETX\CANq~\SYNvixw\DC4\ETBv|\SO&\SO6t B8L\GS,\EOT|y\NAKx4\SUB\\9"}, coin = Coin {getCoin = 50}},TxOut {address = Address {unAddress = "\ETX\EOTKx\DC3-\vDO3~YUOyD_m7)vA\a9,UTC71 \FSv"}, coin = Coin {getCoin = 25}},TxOut {address = Address {unAddress = "\ETX\\U4f\EOT\DC1\t6=\tpPOB(bjQP;*s]\SUB\EOT4\CANl\DC3\ACKsX"}, coin = Coin {getCoin = 44999999999999960}},TxOut {address = Address {unAddress = "\ETX\SI-\ENQt\"9\\zh,!\bdU\\LT\DEL4\STXyZ\\awW\a8\n\v>-"}, coin = Coin {getCoin = 44999999999999922}}]},[TxWitness {unWitness = "\ETB*K\bb\US=\128Y\FS\ETB88V:)\n\STXPR\SYN\FSh60\ESC\ESCYH_!\DC3&\NUL\SOr61 E\STX\ah;za\SUB\DC1DxS\ETXD7'sL\RSC\EMc/jz"}])

Definition of shrinkList':

        shrinkList' xs  = filter (not . null)
            [ take n xs | Positive n <- shrink (Positive $ length xs) ]

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vanilla shrinkList works, but it is very slow. With a slight tweak it seems perfect though!:

        shrinkList' xs  =
            (shrinkHeadAndReplicate shrink xs) ++
            (shrinkList shrink xs)

        -- Try shrinking the 'head' of the list and replace the elements in
        -- the 'tail' with the exact same element. If the failure is related
        -- to the size of the list, this makes the shrinking much faster.
        shrinkHeadAndReplicate f (x:xs) =
            f x >>= \x' -> return $ x':(map (const x') xs)
        shrinkHeadAndReplicate _f [] = []

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, the point of a shrinker is to be applied repeatedly... The shrinkList above does a fair job at shrinking...

let shrinkOne = fmap fst . uncons . shrink
let shrinks = take 10 $ iterate (>>= shrinkOne) (pure tx)
TIO.putStrLn $ pretty (blockListF shrinks)
- ~> 2nd 101445f5...4f64892e
  ~> 2nd 70677272...436a0003
  ~> 1st ac184923...db215619
  ~> 2nd 6e567f73...1e3056fc
  ~> 1st 0a58186e...3c556037
  <~ 63086 @ 41444452...44523034
  <~ 22649 @ 41444452...44523034
  <~ 59611 @ 41444452...44523033
  <~ 88300 @ 41444452...44523031
  <~ 95915 @ 41444452...44523033

- ~> 2nd 101445f5...4f64892e
  ~> 2nd 70677272...436a0003
  ~> 1st ac184923...db215619
  <~ 63086 @ 41444452...44523034
  <~ 22649 @ 41444452...44523034
  <~ 59611 @ 41444452...44523033
  <~ 88300 @ 41444452...44523031
  <~ 95915 @ 41444452...44523033

- ~> 2nd 101445f5...4f64892e
  ~> 2nd 70677272...436a0003
  <~ 63086 @ 41444452...44523034
  <~ 22649 @ 41444452...44523034
  <~ 59611 @ 41444452...44523033
  <~ 88300 @ 41444452...44523031
  <~ 95915 @ 41444452...44523033

- ~> 2nd 101445f5...4f64892e
  <~ 63086 @ 41444452...44523034
  <~ 22649 @ 41444452...44523034
  <~ 59611 @ 41444452...44523033
  <~ 88300 @ 41444452...44523031
  <~ 95915 @ 41444452...44523033

- ~> 2nd 101445f5...4f64892e
  <~ 63086 @ 41444452...44523034
  <~ 22649 @ 41444452...44523034
  <~ 59611 @ 41444452...44523033

- ~> 2nd 101445f5...4f64892e
  <~ 63086 @ 41444452...44523034
  <~ 22649 @ 41444452...44523034

- ~> 2nd 101445f5...4f64892e
  <~ 63086 @ 41444452...4452303

It's true that it requires quite a few steps. If you have really big transactions, it could be nice to make it slightly faster.

property $ \(SignedTx signedTx) -> do
let bytes = try' (runPut $ putSignedTx signedTx)
let tx' = try' . unMessage . runGet getMessage =<< bytes
tx' === (Right signedTx)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not simply runPut and runGet? Since you're testing happy path.. let it throw in case of unexpected error. unsafePerformIO is not really something we want to use when we can avoid it and here clearly, there's no need.

Note that (since you mentioned it somewhere) using catch and a monadicIO property isn't much more work:

property $ \(SignedTx signedTx) -> monadicIO $ liftIO $ do
    return (runPut ...) `catch` (\MyException -> ....)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 Don't we lose failing-test-case-output without === ?

I guess this is still a roundtrip so we would be using like assert (tx' == tx) still?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm.. why would we loose them? In case of failure, we can call something like expectationFailure with a clear error message.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldBe seemed to work well, except for hanging for ~40 s when printing large outputs. I suspect it's trying to find the minimal-edit-distance.

So went with:

                if tx' == Right signedTx
                then return ()
                else expectationFailure $
                    "tx /= decode (encode tx) == " ++ show tx'

resulting in the following output:

Skärmavbild 2019-07-02 kl  19 18 36

@KtorZ KtorZ merged commit bf917c0 into master Jul 3, 2019
@KtorZ KtorZ deleted the anviking/219/testing branch July 3, 2019 08:58
@KtorZ KtorZ mentioned this pull request Jul 3, 2019
12 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants