{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} module Main where import Data.Bitcoin.PaymentChannel import Data.Bitcoin.PaymentChannel.Types import Data.Bitcoin.PaymentChannel.Util import qualified Data.Bitcoin.PaymentChannel.Internal.State as S import qualified Network.Haskoin.Transaction as HT import qualified Network.Haskoin.Crypto as HC import qualified Network.Haskoin.Script as HS import Network.Haskoin.Test import qualified Data.Aeson as JSON import qualified Data.Serialize as Bin import Data.Typeable import Test.QuickCheck import Debug.Trace dUST_LIMIT = 700 :: BitcoinAmount mIN_CHANNEL_SIZE = 1400 :: BitcoinAmount testAddrTestnet = "2N414xMNQaiaHCT5D7JamPz7hJEc9RG7469" :: HC.Address testAddrLivenet = "14wjVnwHwMAXDr6h5Fw38shCWUB6RSEa63" :: HC.Address data ArbChannelPair = ArbChannelPair SenderPaymentChannel ReceiverPaymentChannel [BitcoinAmount] [BitcoinAmount] (HC.Hash256 -> HC.Signature) instance Show ArbChannelPair where show (ArbChannelPair spc rpc _ _ _) = "SendState: " ++ show spc ++ "\n" ++ "RecvState: " ++ show rpc doPayment :: ArbChannelPair -> BitcoinAmount -> ArbChannelPair doPayment (ArbChannelPair spc rpc sendList recvList f) amount = let (amountSent, pmn, newSpc) = sendPayment spc amount eitherRpc = recvPayment rpc pmn in case eitherRpc of Left e -> error (show e) Right (recvAmount, newRpc) -> ArbChannelPair newSpc newRpc (amountSent : sendList) (recvAmount : recvList) f newtype ChanScript = ChanScript HS.Script deriving (Eq,Show,Bin.Serialize) instance Arbitrary ArbChannelPair where arbitrary = fmap fst mkChanPair instance Arbitrary FullPayment where arbitrary = fmap snd mkChanPair instance Arbitrary ChannelParameters where arbitrary = fmap fst mkChanParams instance Arbitrary ChanScript where arbitrary = ChanScript . getRedeemScript <$> arbitrary mkChanParams :: Gen (ChannelParameters, (HC.PrvKey, HC.PrvKey)) mkChanParams = do -- sender key pair ArbitraryPubKey sendPriv sendPK <- arbitrary -- receiver key pair ArbitraryPubKey recvPriv recvPK <- arbitrary -- expiration date lockTime <- arbitrary return (CChannelParameters (MkSendPubKey sendPK) (MkRecvPubKey recvPK) lockTime dUST_LIMIT, (sendPriv, recvPriv)) mkChanPair :: Gen (ArbChannelPair, FullPayment) mkChanPair = do (cp, (sendPriv, recvPriv)) <- mkChanParams fti <- arbitrary -- value of first payment initPayAmount <- arbitrary -- fromIntegral <$> choose (0, chanAmount) -- create states let (initPayActualAmount,paymnt,sendChan) = channelWithInitialPaymentOf cp fti (flip HC.signMsg sendPriv) (getFundingAddress cp) initPayAmount let eitherRecvChan = channelFromInitialPayment cp fti (getFundingAddress cp) paymnt case eitherRecvChan of Left e -> error (show e) Right (initRecvAmount,recvChan) -> return (ArbChannelPair sendChan recvChan [initPayActualAmount] [initRecvAmount] (flip HC.signMsg recvPriv), paymnt) instance Arbitrary BitcoinLockTime where arbitrary = fmap parseBitcoinLocktime arbitrary instance Arbitrary FundingTxInfo where arbitrary = do ArbitraryTxHash h <- arbitrary i <- arbitrary amt <- fmap fromIntegral (choose (fromIntegral mIN_CHANNEL_SIZE, round $ 21e6 * 1e8 :: Integer)) :: Gen BitcoinAmount return $ CFundingTxInfo h i amt instance Arbitrary BitcoinAmount where arbitrary = fromIntegral <$> choose (0, round $ 21e6 * 1e8 :: Integer) runChanPair :: ArbChannelPair -> [BitcoinAmount] -> (ArbChannelPair, [BitcoinAmount]) runChanPair chanPair paymentAmountList = (foldl doPayment chanPair paymentAmountList, paymentAmountList) checkChanPair :: (ArbChannelPair, [BitcoinAmount]) -> Bool checkChanPair (ArbChannelPair sendChan recvChan amountSent amountRecvd recvSignFunc, _) = do let settleTx = getSettlementBitcoinTx recvChan recvSignFunc testAddrLivenet 0 let clientChangeAmount = HT.outValue . head . HT.txOut $ settleTx -- Check that the client change amount in the settlement transaction equals the -- channel funding amount minus the sum of all payment amounts. let fundAmountMinusPaySum = S.pcsChannelTotalValue (getChannelState recvChan) - fromIntegral (sum amountSent) let clientValueIsGood = fromIntegral clientChangeAmount == fundAmountMinusPaySum :: Bool -- Debug print let checkGoodClientValue goodCV = if not goodCV then error $ "Bad client change value. Actual: " ++ show clientChangeAmount ++ " should be " ++ show fundAmountMinusPaySum else goodCV -- Check that sender/receiver (client/server) agree on channel state let statesMatch = getChannelState recvChan == getChannelState sendChan -- Send/recv value lists match let recvSendAmountMatch = amountSent == amountRecvd checkGoodClientValue clientValueIsGood && statesMatch && recvSendAmountMatch jsonSerDeser :: (Show a, Eq a, JSON.FromJSON a, JSON.ToJSON a) => a -> Bool jsonSerDeser fp = maybe False checkEquals decodedObj where json = JSON.encode fp decodedObj = JSON.decode json 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 testPaymentSession :: ArbChannelPair -> [BitcoinAmount] -> Bool testPaymentSession arbChanPair paymentAmountList = checkChanPair (runChanPair arbChanPair paymentAmountList) testPaymentJSON = jsonSerDeser :: FullPayment -> Bool testPaymentBin = binSerDeser :: FullPayment -> Bool testScriptBin = binSerDeser :: ChanScript -> Bool main :: IO () main = do quickCheckWith stdArgs testPaymentSession quickCheckWith stdArgs testPaymentJSON quickCheckWith stdArgs testPaymentBin -- quickCheckWith stdArgs testScriptBin