{-# LANGUAGE OverloadedStrings, RecordWildCards, GeneralizedNewtypeDeriving, DeriveFunctor #-} module Main where import PaymentChannel.Test import PaymentChannel.Util import Bitcoin.SpendCond.Util import qualified Network.Haskoin.Transaction as HT import qualified Network.Haskoin.Crypto as HC import qualified Data.Aeson as JSON import qualified Data.Serialize as Bin import Data.Typeable import Data.Maybe import Data.Either import Test.Framework (Test, testGroup, defaultMain) import Test.Framework.Providers.QuickCheck2 (testProperty) import Test.QuickCheck -- (arbitrary) import Test.QuickCheck.Monadic (monadic, run , assert) import Test.QuickCheck.Property (Property) import Control.Monad.Identity (Identity(Identity)) import Debug.Trace {-# ANN module ("HLint: ignore Redundant if"::String) #-} newtype TestM a = TestM { runTestId :: Identity a } deriving (Functor, Applicative, Monad) instance MonadTime TestM where currentTime = return nowishTimestamp runTestM :: forall c. TestM c -> c runTestM = getIt . runTestId where getIt (Identity a) = a -- testAddrTestnet :: HC.Address -- testAddrTestnet = "2N414xMNQaiaHCT5D7JamPz7hJEc9RG7469" testAddrLivenet :: HC.Address testAddrLivenet = "14wjVnwHwMAXDr6h5Fw38shCWUB6RSEa63" recvSettleAddr :: HC.Address recvSettleAddr = testAddrLivenet main :: IO () main = putStrLn "" >> defaultMain tests tests :: [Test] tests = [ testGroup "Payment" [ testGroup "State" [ testProperty "Sender/receiver match" $ testPaymentSessionM checkSendRecvStateMatch , testProperty "Sent amount == received amount" $ testPaymentSessionM checkRecvSendAmount ] , testGroup "Settlement tx" [ testProperty "Sender value" $ testPaymentSessionM checkSenderValue , testProperty "Receiver value" $ testPaymentSessionM checkReceiverValue , testProperty "At least one output" $ testPaymentSessionM alwaysOneOutput ] ] , testGroup "Conversion" [ testProperty "RedeemScript" redeemScriptConversion ] , testGroup "Serialization" [ testGroup "JSON" [ testProperty "Payment" (jsonSerDeser :: Payment BtcSig -> Bool) , testProperty "ServerPayChan" (jsonSerDeser :: ServerPayChan -> Bool) ] , testGroup "Binary" [ testProperty "Payment" (binSerDeser :: Payment BtcSig -> Bool) , testProperty "PayChanState" (binSerDeser :: PayChanState BtcSig -> Bool) , testProperty "ChanParams" (binSerDeser :: ChanParams -> Bool) ] ] ] redeemScriptConversion :: ChanParams -> Bool redeemScriptConversion cp = case fromRedeemScript (getRedeemScript cp) of Left e -> error (show cp ++ "\n\n" ++ show e) Right r -> r == cp || error (show cp ++ "\n\n" ++ show r) -- if r /= cp then error (show cp ++ "\n\n" ++ show r) else True indexOf :: HT.Tx -> HC.Address -> Maybe Int indexOf tx addr = listToMaybe $ catMaybes $ zipWith f [0..] (HT.txOut tx) where f idx out = if HT.scriptOutput out == addressToScriptPubKeyBS addr then Just idx else Nothing mkSettleTx :: Monad m => ChannelPairResult -> m Tx mkSettleTx ChannelPairResult{..} = do settleTxE <- getSettlementBitcoinTx resRecvChan recvSettleAddr (recvPrvKey resInitPair) (SatoshisPerByte 0) KeepDust return $ either (error . show) id settleTxE checkSenderValue :: ChannelPairResult -> TestM Bool checkSenderValue cpr@ChannelPairResult{..} = do settleTx <- mkSettleTx cpr let clientChangeOutIndex = indexOf settleTx (fundingAddress resSendChan) clientChangeAmount = maybe 0 (HT.outValue . (HT.txOut settleTx !!)) clientChangeOutIndex -- Check that the client change amount in the settlement transaction equals the -- channel funding amount minus the sum of all payment amounts. let fundingVal = fundingValue $ pcsPayment (getChanState resRecvChan) fundValMinusPaym = fundingVal - fromIntegral (sum resSentAmounts) return $ fromIntegral clientChangeAmount == fundValMinusPaym checkReceiverValue :: ChannelPairResult -> TestM Bool checkReceiverValue cpr@ChannelPairResult{..} = do settleTx <- mkSettleTx cpr let recvOutIndex = indexOf settleTx recvSettleAddr recvAmount = maybe 0 (HT.outValue . (HT.txOut settleTx !!)) recvOutIndex -- Check receiver amount in settlement transaction with zero fee equals sum -- of all payments. return $ (fromIntegral recvAmount :: BtcAmount) == fromIntegral (sum resRecvdAmounts) alwaysOneOutput :: ChannelPairResult -> TestM Bool alwaysOneOutput cpr = not . null . HT.txOut <$> mkSettleTx cpr checkSendRecvStateMatch :: ChannelPairResult -> TestM Bool checkSendRecvStateMatch ChannelPairResult{..} = return $ getChanState resSendChan == getChanState resRecvChan checkRecvSendAmount :: ChannelPairResult -> TestM Bool checkRecvSendAmount ChannelPairResult{..} = return $ if sum resSentAmounts == sum resRecvdAmounts then True else error $ show (resSentAmounts, resRecvdAmounts) checkSpendCondTx :: ChannelPairResult -> TestM Bool checkSpendCondTx cpr@ChannelPairResult{..} = do tx <- mkSettleTx cpr let rdmScr = pairRedeemScript $ pcsPayment $ spcState resSendChan let ins = getPrevIn tx rdmScr :: [InputG (Pay2 (ScriptHash (Witness (Cond ChanParams)))) ()] return $ show ins `trace` True testPaymentSessionM :: (ChannelPairResult -> TestM Bool) -> ArbChannelPair -> [BtcAmount] -> Property testPaymentSessionM testFunc arbChanPair payLst = monadic runTestM $ run (runChanPair arbChanPair payLst) >>= run . testFunc >>= assert -- testPaymentSessionM' :: -- (ChannelPairResult -> TestM Property) -- -> ArbChannelPair -- -> Property -- testPaymentSessionM' testFunc arbChanPair = -- monadic runTestM $ -- run (runChanPair arbChanPair) >>= run . testFunc jsonSerDeser :: (Show a, Eq a, JSON.FromJSON a, JSON.ToJSON a) => a -> Bool jsonSerDeser fp = maybe False checkEquals decodedObj where decodedObj = JSON.decode $ JSON.encode fp checkEquals serDeserVal = if serDeserVal /= fp then error $ "Ser/deser mismatch.\nOriginal: " ++ show fp ++ "\nCopy: " ++ show decodedObj else True binSerDeser :: (Typeable a, Show a, Eq a, Bin.Serialize a) => a -> Bool binSerDeser fp = checkEquals decodeRes where bs = Bin.encode fp decodeRes = deserEither bs checkEquals serDeserRes = case serDeserRes of Left e -> error $ "Serialize/deserialize error: " ++ show e Right res -> if res /= fp then error $ "Ser/deser mismatch.\nOriginal: " ++ show fp ++ "\nCopy: " ++ show res else True