github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/api/wallet_test.go (about) 1 package api 2 3 import ( 4 "fmt" 5 "net/url" 6 "path/filepath" 7 "testing" 8 9 "github.com/NebulousLabs/Sia/build" 10 "github.com/NebulousLabs/Sia/modules" 11 "github.com/NebulousLabs/Sia/modules/consensus" 12 "github.com/NebulousLabs/Sia/modules/gateway" 13 "github.com/NebulousLabs/Sia/modules/transactionpool" 14 "github.com/NebulousLabs/Sia/modules/wallet" 15 "github.com/NebulousLabs/Sia/types" 16 ) 17 18 // TestIntegrationWalletGETEncrypted probes the GET call to /wallet when the 19 // wallet has never been encrypted. 20 func TestIntegrationWalletGETEncrypted(t *testing.T) { 21 if testing.Short() { 22 t.SkipNow() 23 } 24 25 // Check a wallet that has never been encrypted. 26 testdir := build.TempDir("api", "TestIntegrationWalletGETEncrypted") 27 g, err := gateway.New("localhost:0", filepath.Join(testdir, modules.GatewayDir)) 28 if err != nil { 29 t.Fatal("Failed to create gateway:", err) 30 } 31 cs, err := consensus.New(g, filepath.Join(testdir, modules.ConsensusDir)) 32 if err != nil { 33 t.Fatal("Failed to create consensus set:", err) 34 } 35 tp, err := transactionpool.New(cs, g, filepath.Join(testdir, modules.TransactionPoolDir)) 36 if err != nil { 37 t.Fatal("Failed to create tpool:", err) 38 } 39 w, err := wallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) 40 if err != nil { 41 t.Fatal("Failed to create wallet:", err) 42 } 43 srv, err := NewServer("localhost:0", "Sia-Agent", cs, nil, g, nil, nil, nil, tp, w) 44 if err != nil { 45 t.Fatal(err) 46 } 47 48 // Assemble the serverTester and start listening for api requests. 49 st := &serverTester{ 50 cs: cs, 51 gateway: g, 52 tpool: tp, 53 wallet: w, 54 55 server: srv, 56 } 57 errChan := make(chan error) 58 go func() { 59 listenErr := srv.Serve() 60 errChan <- listenErr 61 }() 62 defer func() { 63 err := <-errChan 64 if err != nil { 65 t.Fatalf("API server quit: %v", err) 66 } 67 }() 68 defer st.server.Close() 69 70 var wg WalletGET 71 err = st.getAPI("/wallet", &wg) 72 if err != nil { 73 t.Fatal(err) 74 } 75 if wg.Encrypted { 76 t.Error("Wallet has never been encrypted") 77 } 78 if wg.Unlocked { 79 t.Error("Wallet has never been unlocked") 80 } 81 } 82 83 // TestIntegrationWalletBlankEncrypt tries to encrypt and unlock the wallet 84 // through the api using a blank encryption key - meaning that the wallet seed 85 // returned by the encryption call can be used as the encryption key. 86 func TestIntegrationWalletBlankEncrypt(t *testing.T) { 87 if testing.Short() { 88 t.SkipNow() 89 } 90 // Create a server object without encrypting or unlocking the wallet. 91 testdir := build.TempDir("api", "TestIntegrationWalletBlankEncrypt") 92 g, err := gateway.New("localhost:0", filepath.Join(testdir, modules.GatewayDir)) 93 if err != nil { 94 t.Fatal(err) 95 } 96 cs, err := consensus.New(g, filepath.Join(testdir, modules.ConsensusDir)) 97 if err != nil { 98 t.Fatal(err) 99 } 100 tp, err := transactionpool.New(cs, g, filepath.Join(testdir, modules.TransactionPoolDir)) 101 if err != nil { 102 t.Fatal(err) 103 } 104 w, err := wallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) 105 if err != nil { 106 t.Fatal(err) 107 } 108 srv, err := NewServer("localhost:0", "Sia-Agent", cs, nil, g, nil, nil, nil, tp, w) 109 if err != nil { 110 t.Fatal(err) 111 } 112 // Assemble the serverTester. 113 st := &serverTester{ 114 cs: cs, 115 gateway: g, 116 tpool: tp, 117 wallet: w, 118 server: srv, 119 } 120 go func() { 121 listenErr := srv.Serve() 122 if listenErr != nil { 123 panic(listenErr) 124 } 125 }() 126 defer st.server.Close() 127 128 // Make a call to /wallet/encrypt and get the seed. Provide no encryption 129 // key so that the encryption key is the seed that gets returned. 130 var wep WalletEncryptPOST 131 err = st.postAPI("/wallet/encrypt", url.Values{}, &wep) 132 if err != nil { 133 t.Fatal(err) 134 } 135 // Use the seed to call /wallet/unlock. 136 unlockValues := url.Values{} 137 unlockValues.Set("encryptionpassword", wep.PrimarySeed) 138 err = st.stdPostAPI("/wallet/unlock", unlockValues) 139 if err != nil { 140 t.Fatal(err) 141 } 142 // Check that the wallet actually unlocked. 143 if !w.Unlocked() { 144 t.Error("wallet is not unlocked") 145 } 146 } 147 148 // TestIntegrationWalletGETSiacoins probes the GET call to /wallet when the 149 // siacoin balance is being manipulated. 150 func TestIntegrationWalletGETSiacoins(t *testing.T) { 151 if testing.Short() { 152 t.SkipNow() 153 } 154 st, err := createServerTester("TestIntegrationWalletGETSiacoins") 155 if err != nil { 156 t.Fatal(err) 157 } 158 defer st.server.Close() 159 160 // Check the initial wallet is encrypted, unlocked, and has the siacoins 161 // that got mined. 162 var wg WalletGET 163 err = st.getAPI("/wallet", &wg) 164 if err != nil { 165 t.Fatal(err) 166 } 167 if !wg.Encrypted { 168 t.Error("Wallet has been encrypted") 169 } 170 if !wg.Unlocked { 171 t.Error("Wallet has been unlocked") 172 } 173 if wg.ConfirmedSiacoinBalance.Cmp(types.CalculateCoinbase(1)) != 0 { 174 t.Error("reported wallet balance does not reflect the single block that has been mined") 175 } 176 if wg.UnconfirmedOutgoingSiacoins.Cmp(types.NewCurrency64(0)) != 0 { 177 t.Error("there should not be unconfirmed outgoing siacoins") 178 } 179 if wg.UnconfirmedIncomingSiacoins.Cmp(types.NewCurrency64(0)) != 0 { 180 t.Error("there should not be unconfirmed incoming siacoins") 181 } 182 183 // Send coins to a wallet address through the api. 184 var wag WalletAddressGET 185 err = st.getAPI("/wallet/address", &wag) 186 if err != nil { 187 t.Fatal(err) 188 } 189 sendSiacoinsValues := url.Values{} 190 sendSiacoinsValues.Set("amount", "1234") 191 sendSiacoinsValues.Add("destination", wag.Address.String()) 192 err = st.stdPostAPI("/wallet/siacoins", sendSiacoinsValues) 193 if err != nil { 194 t.Fatal(err) 195 } 196 197 // Check that the wallet is reporting unconfirmed siacoins. 198 err = st.getAPI("/wallet", &wg) 199 if err != nil { 200 t.Fatal(err) 201 } 202 if !wg.Encrypted { 203 t.Error("Wallet has been encrypted") 204 } 205 if !wg.Unlocked { 206 t.Error("Wallet has been unlocked") 207 } 208 if wg.ConfirmedSiacoinBalance.Cmp(types.CalculateCoinbase(1)) != 0 { 209 t.Error("reported wallet balance does not reflect the single block that has been mined") 210 } 211 if wg.UnconfirmedOutgoingSiacoins.Cmp(types.NewCurrency64(0)) <= 0 { 212 t.Error("there should be unconfirmed outgoing siacoins") 213 } 214 if wg.UnconfirmedIncomingSiacoins.Cmp(types.NewCurrency64(0)) <= 0 { 215 t.Error("there should be unconfirmed incoming siacoins") 216 } 217 if wg.UnconfirmedOutgoingSiacoins.Cmp(wg.UnconfirmedIncomingSiacoins) <= 0 { 218 t.Error("net movement of siacoins should be outgoing (miner fees)") 219 } 220 221 // Mine a block and see that the unconfirmed balances reduce back to 222 // nothing. 223 _, err = st.miner.AddBlock() 224 if err != nil { 225 t.Fatal(err) 226 } 227 err = st.getAPI("/wallet", &wg) 228 if err != nil { 229 t.Fatal(err) 230 } 231 if wg.ConfirmedSiacoinBalance.Cmp(types.CalculateCoinbase(1).Add(types.CalculateCoinbase(2))) >= 0 { 232 t.Error("reported wallet balance does not reflect mining two blocks and eating a miner fee") 233 } 234 if wg.UnconfirmedOutgoingSiacoins.Cmp(types.NewCurrency64(0)) != 0 { 235 t.Error("there should not be unconfirmed outgoing siacoins") 236 } 237 if wg.UnconfirmedIncomingSiacoins.Cmp(types.NewCurrency64(0)) != 0 { 238 t.Error("there should not be unconfirmed incoming siacoins") 239 } 240 } 241 242 // TestIntegrationWalletTransactionGETid queries the /wallet/transaction/$(id) 243 // api call. 244 func TestIntegrationWalletTransactionGETid(t *testing.T) { 245 if testing.Short() { 246 t.SkipNow() 247 } 248 st, err := createServerTester("TestIntegrationWalletTransactionGETid") 249 if err != nil { 250 t.Fatal(err) 251 } 252 defer st.server.Close() 253 254 // Mining blocks should have created transactions for the wallet containing 255 // miner payouts. Get the list of transactions. 256 var wtg WalletTransactionsGET 257 err = st.getAPI("/wallet/transactions?startheight=0&endheight=10", &wtg) 258 if err != nil { 259 t.Fatal(err) 260 } 261 if len(wtg.ConfirmedTransactions) == 0 { 262 t.Error("expecting a few wallet transactions, corresponding to miner payouts.") 263 } 264 if len(wtg.UnconfirmedTransactions) != 0 { 265 t.Error("expecting 0 unconfirmed transactions") 266 } 267 268 // Query the details of the first transaction using 269 // /wallet/transaction/$(id) 270 var wtgid WalletTransactionGETid 271 wtgidQuery := fmt.Sprintf("/wallet/transaction/%s", wtg.ConfirmedTransactions[0].TransactionID) 272 err = st.getAPI(wtgidQuery, &wtgid) 273 if err != nil { 274 t.Fatal(err) 275 } 276 if len(wtgid.Transaction.Inputs) != 0 { 277 t.Error("miner payout should appear as an output, not an input") 278 } 279 if len(wtgid.Transaction.Outputs) != 1 { 280 t.Fatal("a single miner payout output should have been created") 281 } 282 if wtgid.Transaction.Outputs[0].FundType != types.SpecifierMinerPayout { 283 t.Error("fund type should be a miner payout") 284 } 285 }