module Network.Haskoin.Wallet.Units (tests) where import Test.HUnit (Assertion, assertEqual, assertFailure) import Test.Framework (Test, testGroup) import Test.Framework.Providers.HUnit (testCase) import Control.Monad (liftM, guard) import Control.Monad.Trans (liftIO) import Control.Exception (Exception, handleJust) import Control.Monad.Trans.Resource (ResourceT) import Control.Monad.Logger (NoLoggingT) import Data.Word (Word32, Word64) import Data.Maybe (fromJust) import qualified Data.ByteString as BS ( ByteString , empty , pack ) import Database.Persist (Entity(..), entityVal, getBy) import Database.Persist.Sqlite ( runSqlite , runMigrationSilent , SqlPersistT ) import Network.Haskoin.Wallet.Internals import Network.Haskoin.Node.HeaderTree import Network.Haskoin.Block import Network.Haskoin.Transaction import Network.Haskoin.Script import Network.Haskoin.Crypto import Network.Haskoin.Util type App = SqlPersistT (NoLoggingT (ResourceT IO)) tests :: [Test] tests = [ testGroup "KeyRing creation" [ testCase "Calling newKeyRing with an empty seed should fail" $ assertException (WalletException "The seed is empty") (newKeyRing "main" BS.empty) , testCase "Creating two KeyRings with the same name should fail" $ assertException (WalletException "KeyRing main already exists") $ do _ <- newKeyRing "main" $ BS.pack [0] newKeyRing "main" $ BS.pack [1] ] , testGroup "Account tests" [ testCase "Creating two accounts with the same name should fail" $ assertException (WalletException "Account acc already exists") $ do keyE <- newKeyRing "main" $ BS.pack [1] _ <- newAccount keyE "acc" (AccountRegular False) [] newAccount keyE "acc" (AccountRegular False) [] , testCase "Invalid multisig parameters (0 of 1)" $ assertException (WalletException "Invalid account type") $ do keyE <- newKeyRing "main" $ BS.pack [0] newAccount keyE "ms" (AccountMultisig False 0 1) [] , testCase "Invalid multisig parameters (2 of 1)" $ assertException (WalletException "Invalid account type") $ do keyE <- newKeyRing "main" $ BS.pack [0] newAccount keyE "ms" (AccountMultisig False 2 1) [] , testCase "Invalid multisig parameters (15 of 16)" $ assertException (WalletException "Invalid account type") $ do keyE <- newKeyRing "main" $ BS.pack [0] newAccount keyE "ms" (AccountMultisig False 15 16) [] , testCase "To many multisig keys (2 keys for 1 of 2)" $ assertException (WalletException "Invalid account keys") $ do keyE <- newKeyRing "main" $ BS.pack [0] newAccount keyE "ms" (AccountMultisig False 1 2) [ deriveXPubKey $ makeXPrvKey (BS.pack [1]) , deriveXPubKey $ makeXPrvKey (BS.pack [2]) ] , testCase "Calling addAccountKeys with an empty key list should fail" $ assertException (WalletException "Invalid account keys") $ do keyE <- newKeyRing "main" $ BS.pack [0] accE <- newAccount keyE "default" (AccountRegular True) [] addAccountKeys accE [] , testCase "Calling addAccountKeys on a non-multisig account should fail" $ assertException (WalletException "The account is already complete") $ do keyE <- newKeyRing "main" $ BS.pack [0] _ <- newAccount keyE "default" (AccountRegular False) [] (_, accE) <- getAccount "main" "default" addAccountKeys accE [ deriveXPubKey $ makeXPrvKey (BS.pack [1]) ] , testCase "Adding keys to a complete multisig account should fail" $ assertException (WalletException "The account is already complete") $ do keyE <- newKeyRing "main" $ BS.pack [0] _ <- newAccount keyE "ms" (AccountMultisig False 2 3) [ deriveXPubKey $ makeXPrvKey (BS.pack [1]) , deriveXPubKey $ makeXPrvKey (BS.pack [2]) ] (_, accE) <- getAccount "main" "ms" addAccountKeys accE [deriveXPubKey $ makeXPrvKey (BS.pack [3])] , testCase "Getting a non-existing account should fail" $ assertException (WalletException "Account default does not exist") $ do _ <- newKeyRing "main" $ BS.pack [0] getAccount "main" "default" ] , testGroup "Address tests" [ testCase "Displaying page 0 should fail" $ assertException (WalletException "Invalid page request (Page: 0, Page size: 1)" ) $ do keyE <- newKeyRing "main" $ BS.pack [0] accE <- newAccount keyE "default" (AccountRegular False) [] addressPage accE AddressExternal $ PageRequest 0 1 False , testCase "Displaying 0 results per page should fail" $ assertException (WalletException "Invalid page request (Page: 1, Page size: 0)" ) $ do keyE <- newKeyRing "main" $ BS.pack [0] accE <- newAccount keyE "default" (AccountRegular False) [] addressPage accE AddressExternal $ PageRequest 1 0 False , testCase "Displaying a page number that is too high should fail" $ assertException (WalletException "Invalid page number 5") $ do keyE <- newKeyRing "main" $ BS.pack [0] accE <- newAccount keyE "default" (AccountRegular False) [] addressPage accE AddressExternal $ PageRequest 5 3 False , testCase "Decreasing the address gap should fail" $ assertException (WalletException "The gap of an account can only be increased") $ do keyE <- newKeyRing "main" $ BS.pack [0] _ <- newAccount keyE "default" (AccountRegular False) [] (_, acc1E) <- getAccount "main" "default" _ <- setAccountGap acc1E 15 (_, acc2E) <- getAccount "main" "default" setAccountGap acc2E 14 , testCase "Setting a label on a hidden address key should fail" $ assertException (WalletException "Invalid address index 10") $ do keyE <- newKeyRing "main" $ BS.pack [0] accE <- newAccount keyE "default" (AccountRegular False) [] setAddrLabel accE 10 AddressExternal "Gym membership" , testCase "Setting a label on an invalid address key should fail" $ assertException (WalletException "Invalid address index 20") $ do keyE <- newKeyRing "main" $ BS.pack [0] accE <- newAccount keyE "default" (AccountRegular False) [] setAddrLabel accE 20 AddressExternal "Gym membership" , testCase "Requesting an address prvkey on a read-only account should fail" $ assertException (WalletException "Invalid address") $ do keyE@(Entity _ kr) <- newKeyRing "main" $ BS.pack [0] accE <- newAccount keyE "default" (AccountRegular True) [deriveXPubKey $ makeXPrvKey $ BS.pack [1]] addressPrvKey kr accE 2 AddressExternal ] , testGroup "Wallet tests" [ testCase "Verify address derivations" $ runUnit testDerivations , testCase "Verify balances" $ runUnit testBalances , testCase "Verify balances in conflict" $ runUnit testConflictBalances , testCase "Offline transactions" $ runUnit testOffline , testCase "Kill an offline tx by spending his coins" $ runUnit testKillOffline , testCase "Offline transaction exceptions" testOfflineExceptions , testCase "Multisig test 1" $ runUnit testImportMultisig , testCase "Kill Tx" $ runUnit testKillTx ] ] assertException :: (Exception e, Eq e) => e -> App a -> Assertion assertException ex action = handleJust matchEx (const $ return ()) $ do runUnit action assertFailure $ "Expecting exception: " ++ show ex where matchEx = guard . (== ex) runUnit :: App a -> Assertion runUnit action = do _ <- runSqlite ":memory:" $ do _ <- runMigrationSilent migrateWallet initWallet 0.0001 action return () bs1 :: BS.ByteString bs1 = fromRight $ mnemonicToSeed pass "mass coast dance birth online various renew alert crunch middle absurd health" pass :: BS.ByteString pass = "passw0rd" tid1 :: TxHash tid1 = "0000000000000000000000000000000000000000000000000000000000000001" bid0 :: BlockHash bid0 = "0000000000000000000000000000000000000000000000000000000000000000" bid1 :: BlockHash bid1 = "0000000000000000000000000000000000000000000000000000000000000001" bid2 :: BlockHash bid2 = "0000000000000000000000000000000000000000000000000000000000000002" bid3 :: BlockHash bid3 = "0000000000000000000000000000000000000000000000000000000000000003" bid4 :: BlockHash bid4 = "0000000000000000000000000000000000000000000000000000000000000004" bid5 :: BlockHash bid5 = "0000000000000000000000000000000000000000000000000000000000000005" bid6 :: BlockHash bid6 = "0000000000000000000000000000000000000000000000000000000000000006" bid7 :: BlockHash bid7 = "0000000000000000000000000000000000000000000000000000000000000007" -- Creates fake testing blocks fakeNode :: Word32 -> BlockHash -> BlockHeaderNode fakeNode i h = BlockHeaderNode { nodeBlockHash = h , nodeHeader = BlockHeader 1 z1 z2 0 0 0 , nodeHeaderHeight = i , nodeChainWork = 0 , nodeChild = Nothing , nodeMedianTimes = [] , nodeMinWork = 0 } where z1 = "0000000000000000000000000000000000000000000000000000000000000000" z2 = "0000000000000000000000000000000000000000000000000000000000000000" fakeTx :: [(TxHash, Word32)] -> [(BS.ByteString, Word64)] -> Tx fakeTx xs ys = Tx 1 txi txo 0 where txi = map (\(h,p) -> TxIn (OutPoint h p) (BS.pack [1]) maxBound) xs f = encodeOutputBS . PayPKHash . fromJust . base58ToAddr txo = map (\(a,v) -> TxOut v $ f a ) ys testDerivations :: App () testDerivations = do keyE <- newKeyRing "test" bs1 accE <- newAccount keyE "acc1" (AccountRegular False) [] unusedAddresses accE AddressExternal >>= liftIO . assertEqual "Generated external addresses do not match" [ "13XaDQvvE4rqiVKMi4MApsaZwTcDNiwfuR" , "1BECmeSVxBYCwL493wt9Vqx8mvaWozTF4r" , "1J7n7Lz1VKYdemEDWfyFoGQpSByK9doqeZ" , "184p3tofVNgFXfA7Ry3VU1uTPyr5dGCiUF" , "1FkBfN2P6RdvSE6M4k1BGZqFYRLXMXyJen" , "1MQRM1Luzq4rkrKV8ii7BiukjCa63wt91D" , "14zzWHCS5969DL4ZqphMrsG7p2gCSJnCV7" , "1FFCS3SzGduAv2MBM9Ak9tALT5snVySST" , "18VNX8vQre2hGneuCrXtXwB5D1NVTBUB46" , "17mE4ZUaWETvjyLXbTcgoyqTc3A1f7eWVs" ] . map (addrToBase58 . keyRingAddrAddress) unusedAddresses accE AddressInternal >>= liftIO . assertEqual "Generated internal addresses do not match" [ "1BwbQ8Wp7YUfaYeiQPgXu6br5e4ogKjuKd" , "16wQCfrqW5QegVe5pXpczHaxDmqTAn4ieM" , "1PZjbfPbGzvB7jvoRSkCQZfne154mjU3sY" , "152Nc7WrB24foAydrHJ7Sie954NgXCx5Tn" , "1HojKLGEQb9bZMMckXgujnv9HGCNxtowCP" , "13X9ds52rRYGvLwfbAvQDVU7K13j9cU7BR" , "1LSBEYAcmsZuxyPVpF1GqxXTRxpg4CaJPF" , "1MUcLFqrYhkSHjYcQdfZJRwnkEi9xWaGZU" , "12vgEgi8ExgCo7EBPG1kxwJGR5FCXmZpoB" , "1K14RjZ3he6erLHFNrPWwvmxm4nbr1MEYC" ] . map (addrToBase58 . keyRingAddrAddress) testBalances :: App () testBalances = do keyE <- newKeyRing "test" bs1 accE@(Entity ai _) <- newAccount keyE "acc1" (AccountRegular False) [] let fundingTx = fakeTx [ (tid1, 0) ] [ ("13XaDQvvE4rqiVKMi4MApsaZwTcDNiwfuR", 10000000) , ("1BECmeSVxBYCwL493wt9Vqx8mvaWozTF4r", 20000000) ] let tx1 = fakeTx [ (txHash fundingTx, 0) , (txHash fundingTx, 1) ] [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 30000000) ] -- external tx2 = fakeTx [ (txHash fundingTx, 0) ] [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 5000000) -- external , ("1BwbQ8Wp7YUfaYeiQPgXu6br5e4ogKjuKd", 5000000) -- change ] accountBalance ai 0 False >>= liftIO . (assertEqual "Balance is not 0") 0 -- Import funding transaction twice. This operation should be idempotent importNetTx fundingTx >>= liftIO . (assertEqual "Confidence is not pending" ([(ai, TxPending)], 2)) . testTx importNetTx fundingTx >>= liftIO . (assertEqual "Confidence is not pending" ([(ai, TxPending)], 0)) . testTx spendableCoins ai 0 (const . const []) >>= liftIO . (assertEqual "0-conf spendable coins is not 2" 2) . length spendableCoins ai 1 (const . const []) >>= liftIO . (assertEqual "1-conf spendable coins is not 0" 0) . length accountBalance ai 0 False >>= liftIO . (assertEqual "Balance is not 30000000") 30000000 accountBalance ai 1 False >>= liftIO . (assertEqual "Balance is not 0") 0 addressBalances accE 0 0 AddressExternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 0, 1, 0)") [(0, BalanceInfo 10000000 0 1 0)] addressBalances accE 1 1 AddressExternal 0 False >>= liftIO . (assertEqual "Address 1 balance is not (20000000, 0, 1, 0)") [(1, BalanceInfo 20000000 0 1 0)] addressBalances accE 0 0 AddressExternal 1 False >>= liftIO . (assertEqual "Address 0 1-conf balance is not (0, 0, 0, 0)") [(0, BalanceInfo 0 0 0 0)] addressBalances accE 1 1 AddressExternal 1 False >>= liftIO . (assertEqual "Address 1 1-conf balance is not (0, 0, 0, 0)") [(1, BalanceInfo 0 0 0 0)] importNetTx tx1 >>= liftIO . (assertEqual "Confidence is not pending" ([(ai, TxPending)], 0)) . testTx accountBalance ai 0 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai 1 False >>= liftIO . (assertEqual "Balance is not 0") 0 addressBalances accE 0 0 AddressExternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 1 1 AddressExternal 0 False >>= liftIO . (assertEqual "Address 1 balance is not (20000000, 20000000, 1, 1)") [(1, BalanceInfo 20000000 20000000 1 1)] addressBalances accE 0 0 AddressExternal 1 False >>= liftIO . (assertEqual "Address 0 1-conf balance is not (0, 0, 0, 0)") [(0, BalanceInfo 0 0 0 0)] addressBalances accE 1 1 AddressExternal 1 False >>= liftIO . (assertEqual "Address 1 1-conf balance is not (0, 0, 0, 0)") [(1, BalanceInfo 0 0 0 0)] -- We re-import tx1. This operation has to be idempotent with respect to -- balances. importNetTx tx1 >>= liftIO . (assertEqual "Confidence is not pending" ([(ai, TxPending)], 0)) . testTx accountBalance ai 0 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai 1 False >>= liftIO . (assertEqual "Balance is not 0") 0 addressBalances accE 0 0 AddressExternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 1 1 AddressExternal 0 False >>= liftIO . (assertEqual "Address 1 balance is not (20000000, 20000000, 1, 1)") [(1, BalanceInfo 20000000 20000000 1 1)] addressBalances accE 0 0 AddressExternal 1 False >>= liftIO . (assertEqual "Address 0 1-conf balance is not (0, 0, 0, 0)") [(0, BalanceInfo 0 0 0 0)] addressBalances accE 1 1 AddressExternal 1 False >>= liftIO . (assertEqual "Address 1 1-conf balance is not (0, 0, 0, 0)") [(1, BalanceInfo 0 0 0 0)] -- Importing tx2 twice. This operation has to be idempotent. importNetTx tx2 >>= liftIO . (assertEqual "Confidence is not dead" ([(ai, TxDead)], 1)) . testTx importNetTx tx2 >>= liftIO . (assertEqual "Confidence is not dead" ([(ai, TxDead)], 0)) . testTx accountBalance ai 0 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai 1 False >>= liftIO . (assertEqual "Balance is not 0") 0 addressBalances accE 0 0 AddressExternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 1 1 AddressExternal 0 False >>= liftIO . (assertEqual "Address 1 balance is not (20000000, 20000000, 1, 1)") [(1, BalanceInfo 20000000 20000000 1 1)] -- Confirm the funding transaction at height 1 importMerkles ((BestChain [fakeNode 1 bid1])) [[txHash fundingTx]] accountBalance ai 0 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai 1 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai 2 False >>= liftIO . (assertEqual "Balance is not 0") 0 addressBalances accE 0 0 AddressExternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 1 1 AddressExternal 0 False >>= liftIO . (assertEqual "Address 1 balance is not (20000000, 20000000, 1, 1)") [(1, BalanceInfo 20000000 20000000 1 1)] addressBalances accE 0 0 AddressExternal 1 False >>= liftIO . (assertEqual "Address 0 1-conf balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 1 1 AddressExternal 1 False >>= liftIO . (assertEqual "Address 1 1-conf balance is not (20000000, 20000000, 1, 1)") [(1, BalanceInfo 20000000 20000000 1 1)] -- Confirm tx1 at height 2 importMerkles ((BestChain [fakeNode 2 bid2])) [[txHash tx1]] accountBalance ai 0 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai 1 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai 2 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai 3 False >>= liftIO . (assertEqual "Balance is not 0") 0 addressBalances accE 0 0 AddressExternal 2 False >>= liftIO . (assertEqual "Address 0 2-conf balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 1 1 AddressExternal 2 False >>= liftIO . (assertEqual "Address 1 2-conf balance is not (20000000, 20000000, 1, 1)") [(1, BalanceInfo 20000000 20000000 1 1)] addressBalances accE 0 0 AddressExternal 3 False >>= liftIO . (assertEqual "Address 0 3-conf balance is not (0, 0, 0, 0)") [(0, BalanceInfo 0 0 0 0)] addressBalances accE 1 1 AddressExternal 3 False >>= liftIO . (assertEqual "Address 1 3-conf balance is not (0, 0, 0, 0)") [(1, BalanceInfo 0 0 0 0)] -- Reorg on tx2 let s = fakeNode 1 bid1 o = [fakeNode 2 bid2] n = [fakeNode 2 bid3, fakeNode 3 bid4] importMerkles (ChainReorg s o n) [[], [txHash tx2]] getBy (UniqueAccTx ai (txHash tx1)) >>= liftIO . (assertEqual "Confidence is not dead" TxDead) . keyRingTxConfidence . entityVal . fromJust getBy (UniqueAccTx ai (txHash tx2)) >>= liftIO . (assertEqual "Confidence is not building" TxBuilding) . keyRingTxConfidence . entityVal . fromJust accountBalance ai 0 False >>= liftIO . (assertEqual "Balance is not 25000000") 25000000 accountBalance ai 1 False >>= liftIO . (assertEqual "Balance is not 25000000") 25000000 accountBalance ai 2 False >>= liftIO . (assertEqual "Balance is not 20000000") 20000000 accountBalance ai 3 False >>= liftIO . (assertEqual "Balance is not 20000000") 20000000 accountBalance ai 4 False >>= liftIO . (assertEqual "Balance is not 0") 0 addressBalances accE 0 0 AddressExternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 1 1 AddressExternal 0 False >>= liftIO . (assertEqual "Address 1 balance is not (20000000, 0, 1, 0)") [(1, BalanceInfo 20000000 0 1 0)] addressBalances accE 0 0 AddressExternal 3 False >>= liftIO . (assertEqual "Address 0 3-conf balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 1 1 AddressExternal 3 False >>= liftIO . (assertEqual "Address 1 3-conf balance is not (20000000, 0, 1, 0)") [(1, BalanceInfo 20000000 0 1 0)] addressBalances accE 0 0 AddressInternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (5000000, 0, 1, 0)") [(0, BalanceInfo 5000000 0 1 0)] addressBalances accE 0 0 AddressInternal 1 False >>= liftIO . (assertEqual "Address 0 1-conf balance is not (5000000, 0, 1, 0)") [(0, BalanceInfo 5000000 0 1 0)] addressBalances accE 0 0 AddressInternal 2 False >>= liftIO . (assertEqual "Address 0 2-conf balance is not (0, 0, 0, 0)") [(0, BalanceInfo 0 0 0 0)] -- Reimporting tx2 should be idempotent and return TxBuilding importNetTx tx2 >>= liftIO . (assertEqual "Confidence is not building" ([(ai, TxBuilding)], 0)) . testTx accountBalance ai 0 False >>= liftIO . (assertEqual "Balance is not 25000000") 25000000 accountBalance ai 1 False >>= liftIO . (assertEqual "Balance is not 25000000") 25000000 accountBalance ai 2 False >>= liftIO . (assertEqual "Balance is not 20000000") 20000000 accountBalance ai 3 False >>= liftIO . (assertEqual "Balance is not 20000000") 20000000 accountBalance ai 4 False >>= liftIO . (assertEqual "Balance is not 0") 0 addressBalances accE 0 0 AddressExternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 1 1 AddressExternal 0 False >>= liftIO . (assertEqual "Address 1 balance is not (20000000, 0, 1, 0)") [(1, BalanceInfo 20000000 0 1 0)] addressBalances accE 0 0 AddressExternal 3 False >>= liftIO . (assertEqual "Address 0 3-conf balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 1 1 AddressExternal 3 False >>= liftIO . (assertEqual "Address 1 3-conf balance is not (20000000, 0, 1, 0)") [(1, BalanceInfo 20000000 0 1 0)] addressBalances accE 0 0 AddressInternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (5000000, 0, 1, 0)") [(0, BalanceInfo 5000000 0 1 0)] addressBalances accE 0 0 AddressInternal 1 False >>= liftIO . (assertEqual "Address 0 1-conf balance is not (5000000, 0, 1, 0)") [(0, BalanceInfo 5000000 0 1 0)] addressBalances accE 0 0 AddressInternal 2 False >>= liftIO . (assertEqual "Address 0 2-conf balance is not (0, 0, 0, 0)") [(0, BalanceInfo 0 0 0 0)] -- Reorg back onto tx1 let s2 = fakeNode 1 bid1 o2 = [fakeNode 2 bid3, fakeNode 3 bid4] n2 = [fakeNode 2 bid2, fakeNode 3 bid5, fakeNode 4 bid6] importMerkles (ChainReorg s2 o2 n2) [[txHash tx1], [], []] accountBalance ai 0 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai 1 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai 2 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai 3 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai 4 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai 5 False >>= liftIO . (assertEqual "Balance is not 0") 0 addressBalances accE 0 0 AddressExternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 1 1 AddressExternal 0 False >>= liftIO . (assertEqual "Address 1 balance is not (20000000, 20000000, 1, 1)") [(1, BalanceInfo 20000000 20000000 1 1)] addressBalances accE 0 0 AddressExternal 4 False >>= liftIO . (assertEqual "Address 0 2-conf balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 1 1 AddressExternal 4 False >>= liftIO . (assertEqual "Address 1 2-conf balance is not (20000000, 20000000, 1, 1)") [(1, BalanceInfo 20000000 20000000 1 1)] addressBalances accE 0 0 AddressExternal 5 False >>= liftIO . (assertEqual "Address 0 4-conf balance is not (0, 0, 0, 0)") [(0, BalanceInfo 0 0 0 0)] addressBalances accE 1 1 AddressExternal 5 False >>= liftIO . (assertEqual "Address 1 4-conf balance is not (0, 0, 0, 0)") [(1, BalanceInfo 0 0 0 0)] addressBalances accE 0 0 AddressInternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (0, 0, 0, 0)") [(0, BalanceInfo 0 0 0 0)] -- tx1, tx2 and tx3 form a chain, and tx4 is in conflict with tx1 testConflictBalances :: App () testConflictBalances = do keyE <- newKeyRing "test" bs1 accE@(Entity ai _) <- newAccount keyE "acc1" (AccountRegular False) [] let tx1 = fakeTx [ (tid1, 4) ] [ ("13XaDQvvE4rqiVKMi4MApsaZwTcDNiwfuR", 10000000) ] tx2 = fakeTx [ (txHash tx1, 0) ] [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 6000000) -- external , ("1BwbQ8Wp7YUfaYeiQPgXu6br5e4ogKjuKd", 4000000) -- change ] tx3 = fakeTx [ (txHash tx2, 1) ] [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 4000000) ] -- external tx4 = fakeTx [ (tid1, 4) ] [ ("13XaDQvvE4rqiVKMi4MApsaZwTcDNiwfuR", 20000000) ] -- Import first transaction importNetTx tx1 >>= liftIO . (assertEqual "Confidence is not pending" ([(ai, TxPending)], 1)) . testTx accountBalance ai 0 False >>= liftIO . (assertEqual "Balance is not 10000000") 10000000 accountBalance ai 0 True >>= liftIO . (assertEqual "Balance is not 10000000") 10000000 accountBalance ai 1 False >>= liftIO . (assertEqual "Balance is not 0") 0 addressBalances accE 0 0 AddressExternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 0, 1, 0)") [(0, BalanceInfo 10000000 0 1 0)] -- Import second transaction importNetTx tx2 >>= liftIO . (assertEqual "Confidence is not pending" ([(ai, TxPending)], 1)) . testTx accountBalance ai 0 False >>= liftIO . (assertEqual "Balance is not 4000000") 4000000 accountBalance ai 1 False >>= liftIO . (assertEqual "Balance is not 0") 0 addressBalances accE 0 0 AddressExternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 0 0 AddressInternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (4000000, 0, 1, 0)") [(0, BalanceInfo 4000000 0 1 0)] -- Let's confirm these two transactions importMerkles (BestChain [fakeNode 1 bid1, fakeNode 2 bid2 ]) [[txHash tx1], [txHash tx2]] accountBalance ai 0 False >>= liftIO . (assertEqual "Balance is not 4000000") 4000000 accountBalance ai 1 False >>= liftIO . (assertEqual "Balance is not 4000000") 4000000 accountBalance ai 2 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai 3 False >>= liftIO . (assertEqual "Balance is not 0") 0 addressBalances accE 0 0 AddressExternal 1 False >>= liftIO . (assertEqual "Address 0 1-conf balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 0 0 AddressInternal 1 False >>= liftIO . (assertEqual "Address 0 1-conf balance is not (4000000, 0, 1, 0)") [(0, BalanceInfo 4000000 0 1 0)] addressBalances accE 0 0 AddressExternal 2 False >>= liftIO . (assertEqual "Address 0 2-conf balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 0 0 AddressInternal 2 False >>= liftIO . (assertEqual "Address 0 2-conf balance is not (0, 0, 0, 0)") [(0, BalanceInfo 0 0 0 0)] -- Import third transaction importNetTx tx3 >>= liftIO . (assertEqual "Confidence is not pending" ([(ai, TxPending)], 0)) . testTx accountBalance ai 0 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai 1 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai 2 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai 3 False >>= liftIO . (assertEqual "Balance is not 0") 0 addressBalances accE 0 0 AddressExternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 0 0 AddressInternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (4000000, 4000000, 1, 1)") [(0, BalanceInfo 4000000 4000000 1 1)] addressBalances accE 0 0 AddressExternal 1 False >>= liftIO . (assertEqual "Address 0 1-conf balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 0 0 AddressInternal 1 False >>= liftIO . (assertEqual "Address 0 1-conf balance is not (4000000, 4000000, 1, 1)") [(0, BalanceInfo 4000000 4000000 1 1)] addressBalances accE 0 0 AddressExternal 2 False >>= liftIO . (assertEqual "Address 0 2-conf balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 0 0 AddressInternal 2 False >>= liftIO . (assertEqual "Address 0 2-conf balance is not (0, 0, 0, 0)") [(0, BalanceInfo 0 0 0 0)] -- Now let's add tx4 which is in conflict with tx1 importNetTx tx4 >>= liftIO . (assertEqual "Confidence is not pending" ([(ai, TxDead)], 0)) . testTx accountBalance ai 0 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai 1 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai 2 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai 3 False >>= liftIO . (assertEqual "Balance is not 0") 0 addressBalances accE 0 0 AddressExternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 0 0 AddressInternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (4000000, 4000000, 1, 1)") [(0, BalanceInfo 4000000 4000000 1 1)] addressBalances accE 0 0 AddressExternal 1 False >>= liftIO . (assertEqual "Address 0 1-conf balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 0 0 AddressInternal 1 False >>= liftIO . (assertEqual "Address 0 1-conf balance is not (4000000, 4000000, 1, 1)") [(0, BalanceInfo 4000000 4000000 1 1)] addressBalances accE 0 0 AddressExternal 2 False >>= liftIO . (assertEqual "Address 0 2-conf balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 0 0 AddressInternal 2 False >>= liftIO . (assertEqual "Address 0 2-conf balance is not (0, 0, 0, 0)") [(0, BalanceInfo 0 0 0 0)] -- Now we trigger a reorg that validates tx4. tx1, tx2 and tx3 should be dead let s = fakeNode 0 bid0 o = [fakeNode 1 bid1, fakeNode 2 bid2] n = [fakeNode 1 bid3, fakeNode 2 bid4, fakeNode 3 bid5] importMerkles (ChainReorg s o n) [[], [txHash tx4], []] getBy (UniqueAccTx ai $ txHash tx1) >>= liftIO . (assertEqual "tx1 confidence is not dead") (Just TxDead) . fmap (keyRingTxConfidence . entityVal) getBy (UniqueAccTx ai $ txHash tx2) >>= liftIO . (assertEqual "tx2 confidence is not dead") (Just TxDead) . fmap (keyRingTxConfidence . entityVal) getBy (UniqueAccTx ai $ txHash tx3) >>= liftIO . (assertEqual "tx3 confidence is not dead") (Just TxDead) . fmap (keyRingTxConfidence . entityVal) accountBalance ai 0 False >>= liftIO . (assertEqual "Balance is not 20000000") 20000000 accountBalance ai 1 False >>= liftIO . (assertEqual "Balance is not 20000000") 20000000 accountBalance ai 2 False >>= liftIO . (assertEqual "Balance is not 20000000") 20000000 accountBalance ai 3 False >>= liftIO . (assertEqual "Balance is not 0") 0 addressBalances accE 0 0 AddressExternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (20000000, 0, 1, 0)") [(0, BalanceInfo 20000000 0 1 0)] addressBalances accE 0 0 AddressInternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (0, 0, 0, 0)") [(0, BalanceInfo 0 0 0 0)] addressBalances accE 0 0 AddressExternal 2 False >>= liftIO . (assertEqual "Address 0 2-conf balance is not (20000000, 0, 1, 0)") [(0, BalanceInfo 20000000 0 1 0)] addressBalances accE 0 0 AddressExternal 3 False >>= liftIO . (assertEqual "Address 0 2-conf balance is not (0, 0, 0, 0)") [(0, BalanceInfo 0 0 0 0)] -- Reorg back to tx1, tx2 and tx3 let s2 = fakeNode 0 bid0 o2 = [ fakeNode 1 bid3, fakeNode 2 bid4, fakeNode 3 bid5 ] n2 = [ fakeNode 1 bid1, fakeNode 2 bid2 , fakeNode 3 bid6, fakeNode 4 bid7 ] importMerkles (ChainReorg s2 o2 n2) [[txHash tx1], [txHash tx2], [], []] getBy (UniqueAccTx ai $ txHash tx1) >>= liftIO . (assertEqual "tx1 confidence is not building") (Just TxBuilding) . fmap (keyRingTxConfidence . entityVal) getBy (UniqueAccTx ai $ txHash tx2) >>= liftIO . (assertEqual "tx2 confidence is not building") (Just TxBuilding) . fmap (keyRingTxConfidence . entityVal) -- Tx3 remains dead until it is included into a block. Dead transaction are -- only revived upon confirmations. They are not revived if they are not -- confirmed even if they have no conflicts anymore. getBy (UniqueAccTx ai $ txHash tx3) >>= liftIO . (assertEqual "tx3 confidence is not dead") (Just TxDead) . fmap (keyRingTxConfidence . entityVal) getBy (UniqueAccTx ai $ txHash tx4) >>= liftIO . (assertEqual "tx4 confidence is not dead") (Just TxDead) . fmap (keyRingTxConfidence . entityVal) accountBalance ai 0 False >>= liftIO . (assertEqual "Balance is not 4000000") 4000000 accountBalance ai 1 False >>= liftIO . (assertEqual "Balance is not 4000000") 4000000 accountBalance ai 2 False >>= liftIO . (assertEqual "Balance is not 4000000") 4000000 accountBalance ai 3 False >>= liftIO . (assertEqual "Balance is not 4000000") 4000000 accountBalance ai 4 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai 5 False >>= liftIO . (assertEqual "Balance is not 0") 0 addressBalances accE 0 0 AddressExternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 0 0 AddressInternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (4000000, 0, 1, 0)") [(0, BalanceInfo 4000000 0 1 0)] addressBalances accE 0 0 AddressExternal 3 False >>= liftIO . (assertEqual "Address 0 3-conf balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 0 0 AddressInternal 3 False >>= liftIO . (assertEqual "Address 0 3-conf balance is not (4000000, 0, 1, 0)") [(0, BalanceInfo 4000000 0 1 0)] addressBalances accE 0 0 AddressExternal 4 False >>= liftIO . (assertEqual "Address 0 4-conf balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 0 0 AddressInternal 4 False >>= liftIO . (assertEqual "Address 0 4-conf balance is not (0, 0, 0, 0)") [(0, BalanceInfo 0 0 0 0)] addressBalances accE 0 0 AddressExternal 5 False >>= liftIO . (assertEqual "Address 0 5-conf balance is not (0, 0, 0, 0)") [(0, BalanceInfo 0 0 0 0)] testOffline :: App () testOffline = do keyE <- newKeyRing "test" bs1 accE@(Entity ai _) <- newAccount keyE "acc1" (AccountRegular False) [] let tx1 = fakeTx [ (tid1, 4) ] [ ("13XaDQvvE4rqiVKMi4MApsaZwTcDNiwfuR", 10000000) ] tx2 = fakeTx [ (txHash tx1, 0) ] [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 6000000) -- external , ("1BwbQ8Wp7YUfaYeiQPgXu6br5e4ogKjuKd", 4000000) -- change ] tx3 = fakeTx [ (txHash tx2, 1) ] [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 4000000) ] -- external tx4 = fakeTx [ (tid1, 4) ] [ ("13XaDQvvE4rqiVKMi4MApsaZwTcDNiwfuR", 20000000) ] -- Import first transaction importTx tx1 ai >>= liftIO . (assertEqual "Confidence is not offline" ([(ai, TxOffline)], 1)) . testTx accountBalance ai 0 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai 0 True >>= liftIO . (assertEqual "Balance is not 10000000") 10000000 accountBalance ai 1 False >>= liftIO . (assertEqual "Balance is not 0") 0 addressBalances accE 0 0 AddressExternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (0, 0, 0, 0)") [(0, BalanceInfo 0 0 0 0)] addressBalances accE 0 0 AddressExternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 0, 1, 0)") [(0, BalanceInfo 10000000 0 1 0)] -- Reimporting a transaction should me idempotent importTx tx1 ai >>= liftIO . (assertEqual "Confidence is not offline" ([(ai, TxOffline)], 0)) . testTx accountBalance ai 0 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai 0 True >>= liftIO . (assertEqual "Balance is not 10000000") 10000000 accountBalance ai 1 False >>= liftIO . (assertEqual "Balance is not 0") 0 addressBalances accE 0 0 AddressExternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (0, 0, 0, 0)") [(0, BalanceInfo 0 0 0 0)] addressBalances accE 0 0 AddressExternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 0, 1, 0)") [(0, BalanceInfo 10000000 0 1 0)] -- Import tx2 importTx tx2 ai >>= liftIO . (assertEqual "Confidence is not offline" ([(ai, TxOffline)], 1)) . testTx accountBalance ai 0 True >>= liftIO . (assertEqual "Balance is not 4000000") 4000000 addressBalances accE 0 0 AddressExternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 0 0 AddressInternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (4000000, 0, 1, 0)") [(0, BalanceInfo 4000000 0 1 0)] -- Import tx3 importTx tx3 ai >>= liftIO . (assertEqual "Confidence is not offline" ([(ai, TxOffline)], 0)) . testTx accountBalance ai 0 True >>= liftIO . (assertEqual "Balance is not 0") 0 addressBalances accE 0 0 AddressExternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 0 0 AddressInternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (4000000, 4000000, 1, 1)") [(0, BalanceInfo 4000000 4000000 1 1)] -- Import tx4 importTx tx4 ai >>= liftIO . (assertEqual "Confidence is not offline" ([(ai, TxOffline)], 0)) . testTx getBy (UniqueAccTx ai $ txHash tx1) >>= liftIO . (assertEqual "tx1 confidence is not dead") (Just TxDead) . fmap (keyRingTxConfidence . entityVal) getBy (UniqueAccTx ai $ txHash tx2) >>= liftIO . (assertEqual "tx2 confidence is not dead") (Just TxDead) . fmap (keyRingTxConfidence . entityVal) getBy (UniqueAccTx ai $ txHash tx3) >>= liftIO . (assertEqual "tx3 confidence is not dead") (Just TxDead) . fmap (keyRingTxConfidence . entityVal) accountBalance ai 0 True >>= liftIO . (assertEqual "Balance is not 20000000") 20000000 addressBalances accE 0 0 AddressExternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (20000000, 0, 1, 0)") [(0, BalanceInfo 20000000 0 1 0)] addressBalances accE 0 0 AddressInternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (0, 0, 0, 0)") [(0, BalanceInfo 0 0 0 0)] -- importTx should be idempotent importTx tx4 ai >>= liftIO . (assertEqual "Confidence is not offline" ([(ai, TxOffline)], 0)) . testTx accountBalance ai 0 True >>= liftIO . (assertEqual "Balance is not 20000000") 20000000 addressBalances accE 0 0 AddressExternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (20000000, 0, 1, 0)") [(0, BalanceInfo 20000000 0 1 0)] addressBalances accE 0 0 AddressInternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (0, 0, 0, 0)") [(0, BalanceInfo 0 0 0 0)] testKillOffline :: App () testKillOffline = do keyE <- newKeyRing "test" bs1 accE@(Entity ai _) <- newAccount keyE "acc1" (AccountRegular False) [] let tx1 = fakeTx [ (tid1, 4) ] [ ("13XaDQvvE4rqiVKMi4MApsaZwTcDNiwfuR", 10000000) ] tx2 = fakeTx [ (txHash tx1, 0) ] [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 6000000) -- external , ("1BwbQ8Wp7YUfaYeiQPgXu6br5e4ogKjuKd", 4000000) -- change ] tx3 = fakeTx [ (txHash tx2, 1) ] [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 4000000) ] -- external tx4 = fakeTx [ (txHash tx1, 0) ] [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 2000000) -- external , ("1BwbQ8Wp7YUfaYeiQPgXu6br5e4ogKjuKd", 3000000) -- change , ("13XaDQvvE4rqiVKMi4MApsaZwTcDNiwfuR", 5000000) -- more change ] -- Import tx1 as a network transaction importNetTx tx1 >>= liftIO . (assertEqual "Confidence is not pending" ([(ai, TxPending)], 1)) . testTx accountBalance ai 0 False >>= liftIO . (assertEqual "Balance is not 10000000") 10000000 accountBalance ai 0 True >>= liftIO . (assertEqual "Balance is not 10000000") 10000000 accountBalance ai 1 False >>= liftIO . (assertEqual "Balance is not 0") 0 addressBalances accE 0 0 AddressExternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 0, 1, 0)") [(0, BalanceInfo 10000000 0 1 0)] -- Import tx2 as offline importTx tx2 ai >>= liftIO . (assertEqual "Confidence is not offline" ([(ai, TxOffline)], 1)) . testTx accountBalance ai 0 False >>= liftIO . (assertEqual "Balance is not 10000000") 10000000 accountBalance ai 0 True >>= liftIO . (assertEqual "Offline balance is not 4000000") 4000000 accountBalance ai 1 False >>= liftIO . (assertEqual "1-conf Balance is not 0") 0 accountBalance ai 1 True >>= liftIO . (assertEqual "1-conf Offline balance is not 0") 0 addressBalances accE 0 0 AddressExternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 0, 1, 0)") [(0, BalanceInfo 10000000 0 1 0)] addressBalances accE 0 0 AddressInternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (0, 0, 0, 0)") [(0, BalanceInfo 0 0 0 0)] addressBalances accE 0 0 AddressExternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 10000000, 1 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 0 0 AddressInternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (4000000, 0, 1, 0)") [(0, BalanceInfo 4000000 0 1 0)] -- Import tx3 as offline importTx tx3 ai >>= liftIO . (assertEqual "Confidence is not offline" ([(ai, TxOffline)], 0)) . testTx accountBalance ai 0 False >>= liftIO . (assertEqual "Balance is not 10000000") 10000000 accountBalance ai 0 True >>= liftIO . (assertEqual "Offline balance is not 0") 0 accountBalance ai 1 False >>= liftIO . (assertEqual "1-conf Balance is not 0") 0 accountBalance ai 1 True >>= liftIO . (assertEqual "1-conf Offline balance is not 0") 0 addressBalances accE 0 0 AddressExternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 0, 1, 0)") [(0, BalanceInfo 10000000 0 1 0)] addressBalances accE 0 0 AddressInternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (0, 0, 0, 0)") [(0, BalanceInfo 0 0 0 0)] addressBalances accE 0 0 AddressExternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 10000000, 1 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 0 0 AddressInternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (4000000, 4000000, 1, 1)") [(0, BalanceInfo 4000000 4000000 1 1)] -- Import tx4 as a network transaction. It should override tx2 and tx3. importNetTx tx4 >>= liftIO . (assertEqual "Confidence is not pending" ([(ai, TxPending)], 0)) . testTx getBy (UniqueAccTx ai (txHash tx2)) >>= liftIO . (assertEqual "Confidence is not dead" TxDead) . keyRingTxConfidence . entityVal . fromJust getBy (UniqueAccTx ai (txHash tx3)) >>= liftIO . (assertEqual "Confidence is not dead" TxDead) . keyRingTxConfidence . entityVal . fromJust accountBalance ai 0 False >>= liftIO . (assertEqual "Balance is not 8000000") 8000000 accountBalance ai 0 True >>= liftIO . (assertEqual "Offline balance is not 8000000") 8000000 accountBalance ai 1 False >>= liftIO . (assertEqual "1-conf Balance is not 0") 0 accountBalance ai 1 True >>= liftIO . (assertEqual "1-conf Offline balance is not 0") 0 addressBalances accE 0 0 AddressExternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (15000000, 10000000, 2, 1)") [(0, BalanceInfo 15000000 10000000 2 1)] addressBalances accE 0 0 AddressInternal 0 False >>= liftIO . (assertEqual "Address 0 balance is not (3000000, 0, 1, 0)") [(0, BalanceInfo 3000000 0 1 0)] addressBalances accE 0 0 AddressExternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (15000000, 10000000, 2 1)") [(0, BalanceInfo 15000000 10000000 2 1)] addressBalances accE 0 0 AddressInternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (3000000, 0, 1, 0)") [(0, BalanceInfo 3000000 0 1 0)] testOfflineExceptions :: Assertion testOfflineExceptions = do let tx1 = fakeTx [ (tid1, 4) ] [ ("13XaDQvvE4rqiVKMi4MApsaZwTcDNiwfuR", 10000000) ] tx2 = fakeTx [ (txHash tx1, 0) ] [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 6000000) -- external , ("1BwbQ8Wp7YUfaYeiQPgXu6br5e4ogKjuKd", 4000000) -- change ] tx3 = fakeTx [ (txHash tx2, 1) ] [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 4000000) ] -- external tx4 = fakeTx [ (tid1, 4) ] [ ("13XaDQvvE4rqiVKMi4MApsaZwTcDNiwfuR", 20000000) ] assertException (WalletException "Could not import offline transaction") $ do keyE <- newKeyRing "test" bs1 _ <- newAccount keyE "acc1" (AccountRegular False) [] (_, Entity ai _) <- getAccount "test" "acc1" importNetTx tx1 >>= liftIO . (assertEqual "Confidence is not pending" ([(ai, TxPending)], 1)) . testTx importTx tx4 ai assertException (WalletException "Could not import offline transaction") $ do keyE <- newKeyRing "test" bs1 _ <- newAccount keyE "acc1" (AccountRegular False) [] (_, Entity ai _) <- getAccount "test" "acc1" importNetTx tx4 >>= liftIO . (assertEqual "Confidence is not pending" ([(ai, TxPending)], 1)) . testTx importNetTx tx1 >>= liftIO . (assertEqual "Confidence is not dead" ([(ai, TxDead)], 0)) . testTx importNetTx tx2 >>= liftIO . (assertEqual "Confidence is not dead" ([(ai, TxDead)], 1)) . testTx importTx tx3 ai assertException (WalletException "Could not import offline transaction") $ do keyE <- newKeyRing "test" bs1 _ <- newAccount keyE "acc1" (AccountRegular False) [] (_, Entity ai _) <- getAccount "test" "acc1" importNetTx tx1 >>= liftIO . (assertEqual "Confidence is not pending" ([(ai, TxPending)], 1)) . testTx importTx tx1 ai -- This test create a multisig account with the key of testImportMultisig2 testImportMultisig :: App () testImportMultisig = do keyE <- newKeyRing "test" bs1 _ <- newAccount keyE "ms1" (AccountMultisig False 2 2) [fromJust $ xPubImport "xpub69iinth3CTrfkmijzhQXi3kwhGQjba31fncrBgA9vM9T9tv69qSwp525yDVYmX2BTAdeuYSZqkcWhkrqD5Xbsz5YHJZL6CzYGL2WACorpdS"] _ <- newAccount keyE "ms2" (AccountMultisig False 2 2) [fromJust $ xPubImport "xpub69iinth3CTrfh5efv7baTWwk9hHi4zqcQEsNFgVwEJvdaZVEPytZzmNxjYTnF5F5x2CamLXvmD1T4RhpsuaXSFPo2MnLN5VqWqrWb82U7ED"] Entity _ keyRing <- getKeyRing "test" (_, accE1@(Entity ai1 _)) <- getAccount "test" "ms1" (_, accE2@(Entity ai2 _)) <- getAccount "test" "ms2" let fundingTx = Tx 1 [ TxIn (OutPoint tid1 0) (BS.pack [1]) maxBound ] -- dummy input [ TxOut 10000000 $ encodeOutputBS $ PayScriptHash $ fromJust $ base58ToAddr "3Dgz9gqsAMPr7i9qocLMNHU8wuoKqtUNoM" ] 0 importNetTx fundingTx >>= liftIO . (assertEqual "Confidence is not pending" ([(ai1, TxPending), (ai2, TxPending)], 2)) . testTx -- Create a transaction which has 0 signatures in ms1 (tx1, _) <- createTx keyRing accE1 [ ( fromJust $ base58ToAddr "3C9fz8kDwX2rV25YeWC7YcDNHtTreAV52m" , 5000000 ) ] 10000 0 False True liftIO $ assertEqual "Confidence is not offline" TxOffline $ keyRingTxConfidence tx1 spendableCoins ai1 0 (const . const []) >>= liftIO . (assertEqual "Wrong txhash in coins" []) . map (keyRingCoinHash . entityVal . inCoinDataCoin) txPage ai1 (PageRequest 1 10 False) >>= liftIO . (assertEqual "Wrong txhash in tx list" [txHash fundingTx, keyRingTxHash tx1]) . (map keyRingTxHash) . fst accountBalance ai1 0 False >>= liftIO . (assertEqual "Balance is not 10000000") 10000000 accountBalance ai1 1 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai1 0 True >>= liftIO . (assertEqual "Offline balance is not 9990000") 9990000 -- Import the empty transaction in ms2 (tx2:_, _) <- importTx (keyRingTxTx tx1) ai2 -- This second import should be idempotent _ <- importTx (keyRingTxTx tx1) ai2 liftIO $ assertEqual "Txid do not match" (keyRingTxHash tx1) (keyRingTxHash tx2) liftIO $ assertEqual "Confidence is not offline" TxOffline $ keyRingTxConfidence tx2 spendableCoins ai2 0 (const . const []) >>= liftIO . (assertEqual "Wrong txhash in coins" []) . map (keyRingCoinHash . entityVal . inCoinDataCoin) txPage ai2 (PageRequest 1 10 False) >>= liftIO . (assertEqual "Wrong txhash in tx list" [txHash fundingTx, keyRingTxHash tx2]) . (map keyRingTxHash) . fst accountBalance ai2 0 False >>= liftIO . (assertEqual "Balance is not 10000000") 10000000 accountBalance ai2 1 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai2 0 True >>= liftIO . (assertEqual "Balance is not 9990000") 9990000 -- Sign the transaction in ms2 (tx3:_, _) <- signKeyRingTx keyRing accE2 $ keyRingTxHash tx2 liftIO $ assertEqual "Confidence is not pending" TxPending $ keyRingTxConfidence tx3 spendableCoins ai2 0 (const . const []) >>= liftIO . (assertEqual "Wrong txhash in coins" [keyRingTxHash tx3, keyRingTxHash tx3]) . map (keyRingCoinHash . entityVal . inCoinDataCoin) txPage ai2 (PageRequest 1 10 False) >>= liftIO . (assertEqual "Wrong txhash in tx list" [txHash fundingTx, keyRingTxHash tx3]) . (map keyRingTxHash) . fst accountBalance ai2 0 False >>= liftIO . (assertEqual "Balance is not 9990000") 9990000 accountBalance ai2 1 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai2 0 True >>= liftIO . (assertEqual "Balance is not 9990000") 9990000 tx4 <- liftM (entityVal . fromJust) $ getBy $ UniqueAccTx ai1 $ keyRingTxHash tx3 liftIO $ assertEqual "Confidence is not pending" TxPending $ keyRingTxConfidence tx4 spendableCoins ai1 0 (const . const []) >>= liftIO . (assertEqual "Wrong txhash in coins" [keyRingTxHash tx3, keyRingTxHash tx3]) . map (keyRingCoinHash . entityVal . inCoinDataCoin) txPage ai1 (PageRequest 1 10 False) >>= liftIO . (assertEqual "Wrong txhash in tx list" [txHash fundingTx, keyRingTxHash tx3]) . (map keyRingTxHash ) . fst accountBalance ai1 0 False >>= liftIO . (assertEqual "Balance is not 9990000") 9990000 accountBalance ai1 1 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai1 0 True >>= liftIO . (assertEqual "Balance is not 9990000") 9990000 -- Importing the transaction should have no effect as it was globally -- imported already in the previous step. (tx5:_, _) <- importTx (keyRingTxTx tx3) ai1 liftIO $ assertEqual "Confidence is not pending" TxPending $ keyRingTxConfidence tx5 spendableCoins ai1 0 (const . const []) >>= liftIO . (assertEqual "Wrong txhash in coins" [keyRingTxHash tx5, keyRingTxHash tx5]) . map (keyRingCoinHash . entityVal . inCoinDataCoin) txPage ai1 (PageRequest 1 10 False) >>= liftIO . (assertEqual "Wrong txhash in tx list" [txHash fundingTx, keyRingTxHash tx5]) . (map keyRingTxHash) . fst accountBalance ai1 0 False >>= liftIO . (assertEqual "Balance is not 9990000") 9990000 accountBalance ai1 1 False >>= liftIO . (assertEqual "Balance is not 0") 0 accountBalance ai1 0 True >>= liftIO . (assertEqual "Balance is not 9990000") 9990000 testKillTx :: App () testKillTx = do keyE <- newKeyRing "test" bs1 _ <- newAccount keyE "acc1" (AccountRegular False) [] (_, accE@(Entity ai _)) <- getAccount "test" "acc1" let tx1 = fakeTx [ (tid1, 4) ] [ ("13XaDQvvE4rqiVKMi4MApsaZwTcDNiwfuR", 10000000) ] tx2 = fakeTx [ (txHash tx1, 0) ] [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 6000000) -- external , ("1BwbQ8Wp7YUfaYeiQPgXu6br5e4ogKjuKd", 4000000) -- change ] tx3 = fakeTx [ (txHash tx2, 1) ] [ ("1MchgrtQEUgV1f7Nqe1vEzvdmBzJHz8zrY", 4000000) ] -- external importNetTx tx1 >>= liftIO . (assertEqual "Confidence is not pending" ([(ai, TxPending)], 1)) . testTx importNetTx tx2 >>= liftIO . (assertEqual "Confidence is not pending" ([(ai, TxPending)], 1)) . testTx importNetTx tx3 >>= liftIO . (assertEqual "Confidence is not pending" ([(ai, TxPending)], 0)) . testTx accountBalance ai 0 True >>= liftIO . (assertEqual "Balance is not 0") 0 addressBalances accE 0 0 AddressExternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 0 0 AddressInternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (4000000, 4000000, 1, 1)") [(0, BalanceInfo 4000000 4000000 1 1)] killTxs [txHash tx2] accountBalance ai 0 True >>= liftIO . (assertEqual "Balance is not 10000000") 10000000 addressBalances accE 0 0 AddressExternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 0, 1, 0)") [(0, BalanceInfo 10000000 0 1 0)] addressBalances accE 0 0 AddressInternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (0, 0, 0, 0)") [(0, BalanceInfo 0 0 0 0)] -- Killing a transaction should be idempotent killTxs [txHash tx2] accountBalance ai 0 True >>= liftIO . (assertEqual "Balance is not 10000000") 10000000 addressBalances accE 0 0 AddressExternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 0, 1, 0)") [(0, BalanceInfo 10000000 0 1 0)] addressBalances accE 0 0 AddressInternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (0, 0, 0, 0)") [(0, BalanceInfo 0 0 0 0)] killTxs [txHash tx3] accountBalance ai 0 True >>= liftIO . (assertEqual "Balance is not 10000000") 10000000 addressBalances accE 0 0 AddressExternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 0, 1, 0)") [(0, BalanceInfo 10000000 0 1 0)] addressBalances accE 0 0 AddressInternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (0, 0, 0, 0)") [(0, BalanceInfo 0 0 0 0)] reviveTx tx2 accountBalance ai 0 True >>= liftIO . (assertEqual "Balance is not 4000000") 4000000 addressBalances accE 0 0 AddressExternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 0 0 AddressInternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (4000000, 0, 1, 0)") [(0, BalanceInfo 4000000 0 1 0)] -- Reviving a transaction should be idempotent reviveTx tx2 accountBalance ai 0 True >>= liftIO . (assertEqual "Balance is not 4000000") 4000000 addressBalances accE 0 0 AddressExternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (10000000, 10000000, 1, 1)") [(0, BalanceInfo 10000000 10000000 1 1)] addressBalances accE 0 0 AddressInternal 0 True >>= liftIO . (assertEqual "Address 0 balance is not (4000000, 0, 1, 0)") [(0, BalanceInfo 4000000 0 1 0)] testTx :: ([KeyRingTx], [KeyRingAddr]) -> ([(KeyRingAccountId, TxConfidence)], Int) testTx (txs, addrs) = (map f txs, length addrs) where f tx = (keyRingTxAccount tx, keyRingTxConfidence tx)