github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/node/api/server_helpers_test.go (about) 1 package api 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "net" 8 "net/http" 9 "net/url" 10 "path/filepath" 11 "runtime/debug" 12 "strings" 13 "testing" 14 "time" 15 16 "SiaPrime/build" 17 "SiaPrime/config" 18 "SiaPrime/crypto" 19 "SiaPrime/modules" 20 "SiaPrime/modules/consensus" 21 "SiaPrime/modules/explorer" 22 "SiaPrime/modules/gateway" 23 "SiaPrime/modules/host" 24 "SiaPrime/modules/index" 25 "SiaPrime/modules/miner" 26 "SiaPrime/modules/miningpool" 27 "SiaPrime/modules/renter" 28 "SiaPrime/modules/transactionpool" 29 "SiaPrime/modules/wallet" 30 "SiaPrime/persist" 31 "SiaPrime/types" 32 33 "gitlab.com/NebulousLabs/errors" 34 "gitlab.com/NebulousLabs/threadgroup" 35 ) 36 37 // A Server is a collection of siad modules that can be communicated with over 38 // an http api. 39 type Server struct { 40 api *API 41 apiServer *http.Server 42 listener net.Listener 43 requiredUserAgent string 44 tg threadgroup.ThreadGroup 45 } 46 47 // panicClose will close a Server, panicking if there is an error upon close. 48 func (srv *Server) panicClose() { 49 err := srv.Close() 50 if err != nil { 51 // Print the stack. 52 debug.PrintStack() 53 panic(err) 54 } 55 } 56 57 // Close closes the Server's listener, causing the HTTP server to shut down. 58 func (srv *Server) Close() error { 59 err := srv.listener.Close() 60 err = errors.Extend(err, srv.tg.Stop()) 61 62 // Safely close each module. 63 mods := []struct { 64 name string 65 c io.Closer 66 }{ 67 {"explorer", srv.api.explorer}, 68 {"host", srv.api.host}, 69 {"renter", srv.api.renter}, 70 {"miner", srv.api.miner}, 71 {"stratumminer", srv.api.stratumminer}, 72 {"wallet", srv.api.wallet}, 73 {"tpool", srv.api.tpool}, 74 {"consensus", srv.api.cs}, 75 {"gateway", srv.api.gateway}, 76 } 77 for _, mod := range mods { 78 if mod.c != nil { 79 if closeErr := mod.c.Close(); closeErr != nil { 80 err = errors.Extend(err, fmt.Errorf("%v.Close failed: %v", mod.name, closeErr)) 81 } 82 } 83 } 84 return errors.AddContext(err, "error while closing server") 85 } 86 87 // Serve listens for and handles API calls. It is a blocking function. 88 func (srv *Server) Serve() error { 89 err := srv.tg.Add() 90 if err != nil { 91 return errors.AddContext(err, "unable to initialize server") 92 } 93 defer srv.tg.Done() 94 95 // The server will run until an error is encountered or the listener is 96 // closed, via either the Close method or by signal handling. Closing the 97 // listener will result in the benign error handled below. 98 err = srv.apiServer.Serve(srv.listener) 99 if err != nil && !strings.HasSuffix(err.Error(), "use of closed network connection") { 100 return err 101 } 102 return nil 103 } 104 105 // NewServer creates a new API server from the provided modules. The API will 106 // require authentication using HTTP basic auth if the supplied password is not 107 // the empty string. Usernames are ignored for authentication. This type of 108 // authentication sends passwords in plaintext and should therefore only be 109 // used if the APIaddr is localhost. 110 func NewServer(APIaddr string, requiredUserAgent string, requiredPassword string, cs modules.ConsensusSet, e modules.Explorer, g modules.Gateway, h modules.Host, m modules.Miner, r modules.Renter, tp modules.TransactionPool, w modules.Wallet, mp modules.Pool, sm modules.StratumMiner, i modules.Index) (*Server, error) { 111 listener, err := net.Listen("tcp", APIaddr) 112 if err != nil { 113 return nil, err 114 } 115 116 api := New(requiredUserAgent, requiredPassword, cs, e, g, h, m, r, tp, w, mp, sm, i) 117 srv := &Server{ 118 api: api, 119 apiServer: &http.Server{ 120 Handler: api, 121 }, 122 listener: listener, 123 requiredUserAgent: requiredUserAgent, 124 } 125 return srv, nil 126 } 127 128 // serverTester contains a server and a set of channels for keeping all of the 129 // modules synchronized during testing. 130 type serverTester struct { 131 cs modules.ConsensusSet 132 explorer modules.Explorer 133 gateway modules.Gateway 134 host modules.Host 135 miner modules.TestMiner 136 renter modules.Renter 137 tpool modules.TransactionPool 138 wallet modules.Wallet 139 walletKey crypto.TwofishKey 140 141 server *Server 142 143 dir string 144 } 145 146 // assembleServerTester creates a bunch of modules and assembles them into a 147 // server tester, without creating any directories or mining any blocks. 148 func assembleServerTester(key crypto.TwofishKey, testdir string) (*serverTester, error) { 149 // assembleServerTester should not get called during short tests, as it 150 // takes a long time to run. 151 if testing.Short() { 152 panic("assembleServerTester called during short tests") 153 } 154 155 // Create the modules. 156 g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir)) 157 if err != nil { 158 return nil, err 159 } 160 cs, err := consensus.New(g, false, filepath.Join(testdir, modules.ConsensusDir)) 161 if err != nil { 162 return nil, err 163 } 164 tp, err := transactionpool.New(cs, g, filepath.Join(testdir, modules.TransactionPoolDir)) 165 if err != nil { 166 return nil, err 167 } 168 w, err := wallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) 169 if err != nil { 170 return nil, err 171 } 172 encrypted, err := w.Encrypted() 173 if err != nil { 174 return nil, err 175 } 176 if !encrypted { 177 _, err = w.Encrypt(key) 178 if err != nil { 179 return nil, err 180 } 181 } 182 err = w.Unlock(key) 183 if err != nil { 184 return nil, err 185 } 186 m, err := miner.New(cs, tp, w, filepath.Join(testdir, modules.MinerDir)) 187 if err != nil { 188 return nil, err 189 } 190 h, err := host.New(cs, g, tp, w, "localhost:0", filepath.Join(testdir, modules.HostDir)) 191 if err != nil { 192 return nil, err 193 } 194 r, err := renter.New(g, cs, w, tp, filepath.Join(testdir, modules.RenterDir)) 195 if err != nil { 196 return nil, err 197 } 198 srv, err := NewServer("localhost:0", "SiaPrime-Agent", "", cs, nil, g, h, m, r, tp, w, nil, nil, nil) 199 if err != nil { 200 return nil, err 201 } 202 203 // Assemble the serverTester. 204 st := &serverTester{ 205 cs: cs, 206 gateway: g, 207 host: h, 208 miner: m, 209 renter: r, 210 tpool: tp, 211 wallet: w, 212 walletKey: key, 213 214 server: srv, 215 216 dir: testdir, 217 } 218 219 // TODO: A more reasonable way of listening for server errors. 220 go func() { 221 listenErr := srv.Serve() 222 if listenErr != nil { 223 panic(listenErr) 224 } 225 }() 226 return st, nil 227 } 228 229 // assembleAuthenticatedServerTester creates a bunch of modules and assembles 230 // them into a server tester that requires authentication with the given 231 // requiredPassword. No directories are created and no blocks are mined. 232 func assembleAuthenticatedServerTester(requiredPassword string, key crypto.TwofishKey, testdir string) (*serverTester, error) { 233 // assembleAuthenticatedServerTester should not get called during short 234 // tests, as it takes a long time to run. 235 if testing.Short() { 236 panic("assembleServerTester called during short tests") 237 } 238 239 // Create the modules. 240 g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir)) 241 if err != nil { 242 return nil, err 243 } 244 cs, err := consensus.New(g, false, filepath.Join(testdir, modules.ConsensusDir)) 245 if err != nil { 246 return nil, err 247 } 248 tp, err := transactionpool.New(cs, g, filepath.Join(testdir, modules.TransactionPoolDir)) 249 if err != nil { 250 return nil, err 251 } 252 w, err := wallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) 253 if err != nil { 254 return nil, err 255 } 256 encrypted, err := w.Encrypted() 257 if err != nil { 258 return nil, err 259 } 260 if !encrypted { 261 _, err = w.Encrypt(key) 262 if err != nil { 263 return nil, err 264 } 265 } 266 err = w.Unlock(key) 267 if err != nil { 268 return nil, err 269 } 270 m, err := miner.New(cs, tp, w, filepath.Join(testdir, modules.MinerDir)) 271 if err != nil { 272 return nil, err 273 } 274 h, err := host.New(cs, g, tp, w, "localhost:0", filepath.Join(testdir, modules.HostDir)) 275 if err != nil { 276 return nil, err 277 } 278 r, err := renter.New(g, cs, w, tp, filepath.Join(testdir, modules.RenterDir)) 279 if err != nil { 280 return nil, err 281 } 282 mp, err := pool.New(cs, tp, g, w, filepath.Join(testdir, modules.PoolDir), config.MiningPoolConfig{}) 283 if err != nil { 284 return nil, err 285 } 286 idx, err := index.New(cs, tp, g, w, filepath.Join(testdir, modules.IndexDir), config.IndexConfig{}) 287 if err != nil { 288 return nil, err 289 } 290 srv, err := NewServer("localhost:0", "SiaPrime-Agent", requiredPassword, cs, nil, g, h, m, r, tp, w, mp, nil, idx) 291 if err != nil { 292 return nil, err 293 } 294 295 // Assemble the serverTester. 296 st := &serverTester{ 297 cs: cs, 298 gateway: g, 299 host: h, 300 miner: m, 301 renter: r, 302 tpool: tp, 303 wallet: w, 304 walletKey: key, 305 306 server: srv, 307 308 dir: testdir, 309 } 310 311 // TODO: A more reasonable way of listening for server errors. 312 go func() { 313 listenErr := srv.Serve() 314 if listenErr != nil { 315 panic(listenErr) 316 } 317 }() 318 return st, nil 319 } 320 321 // assembleExplorerServerTester creates all the explorer dependencies and 322 // explorer module without creating any directories. The user agent requirement 323 // is disabled. 324 func assembleExplorerServerTester(testdir string) (*serverTester, error) { 325 // assembleExplorerServerTester should not get called during short tests, 326 // as it takes a long time to run. 327 if testing.Short() { 328 panic("assembleServerTester called during short tests") 329 } 330 331 // Create the modules. 332 g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir)) 333 if err != nil { 334 return nil, err 335 } 336 cs, err := consensus.New(g, false, filepath.Join(testdir, modules.ConsensusDir)) 337 if err != nil { 338 return nil, err 339 } 340 e, err := explorer.New(cs, filepath.Join(testdir, modules.ExplorerDir)) 341 if err != nil { 342 return nil, err 343 } 344 srv, err := NewServer("localhost:0", "", "", cs, e, g, nil, nil, nil, nil, nil, nil, nil, nil) 345 if err != nil { 346 return nil, err 347 } 348 349 // Assemble the serverTester. 350 st := &serverTester{ 351 cs: cs, 352 explorer: e, 353 gateway: g, 354 355 server: srv, 356 357 dir: testdir, 358 } 359 360 // TODO: A more reasonable way of listening for server errors. 361 go func() { 362 listenErr := srv.Serve() 363 if listenErr != nil { 364 panic(listenErr) 365 } 366 }() 367 return st, nil 368 } 369 370 // blankServerTester creates a server tester object that is ready for testing, 371 // without mining any blocks. 372 func blankServerTester(name string) (*serverTester, error) { 373 // createServerTester is expensive, and therefore should not be called 374 // during short tests. 375 if testing.Short() { 376 panic("blankServerTester called during short tests") 377 } 378 379 // Create the server tester with key. 380 testdir := build.TempDir("api", name) 381 key := crypto.GenerateTwofishKey() 382 st, err := assembleServerTester(key, testdir) 383 if err != nil { 384 return nil, err 385 } 386 return st, nil 387 } 388 389 // createServerTester creates a server tester object that is ready for testing, 390 // including money in the wallet and all modules initialized. 391 func createServerTester(name string) (*serverTester, error) { 392 // createServerTester is expensive, and therefore should not be called 393 // during short tests. 394 if testing.Short() { 395 panic("createServerTester called during short tests") 396 } 397 398 // Create the testing directory. 399 testdir := build.TempDir("api", name) 400 401 key := crypto.GenerateTwofishKey() 402 st, err := assembleServerTester(key, testdir) 403 if err != nil { 404 return nil, err 405 } 406 407 // Mine blocks until the wallet has confirmed money. 408 for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { 409 _, err := st.miner.AddBlock() 410 if err != nil { 411 return nil, err 412 } 413 } 414 415 return st, nil 416 } 417 418 // createAuthenticatedServerTester creates an authenticated server tester 419 // object that is ready for testing, including money in the wallet and all 420 // modules initialized. 421 func createAuthenticatedServerTester(name string, password string) (*serverTester, error) { 422 // createAuthenticatedServerTester should not get called during short 423 // tests, as it takes a long time to run. 424 if testing.Short() { 425 panic("assembleServerTester called during short tests") 426 } 427 428 // Create the testing directory. 429 testdir := build.TempDir("authenticated-api", name) 430 431 key := crypto.GenerateTwofishKey() 432 st, err := assembleAuthenticatedServerTester(password, key, testdir) 433 if err != nil { 434 return nil, err 435 } 436 437 // Mine blocks until the wallet has confirmed money. 438 for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { 439 _, err := st.miner.AddBlock() 440 if err != nil { 441 return nil, err 442 } 443 } 444 445 return st, nil 446 } 447 448 // createExplorerServerTester creates a server tester object containing only 449 // the explorer and some presets that match standard explorer setups. 450 func createExplorerServerTester(name string) (*serverTester, error) { 451 testdir := build.TempDir("api", name) 452 st, err := assembleExplorerServerTester(testdir) 453 if err != nil { 454 return nil, err 455 } 456 return st, nil 457 } 458 459 // decodeError returns the api.Error from a API response. This method should 460 // only be called if the response's status code is non-2xx. The error returned 461 // may not be of type api.Error in the event of an error unmarshalling the 462 // JSON. 463 func decodeError(resp *http.Response) error { 464 var apiErr Error 465 err := json.NewDecoder(resp.Body).Decode(&apiErr) 466 if err != nil { 467 return err 468 } 469 return apiErr 470 } 471 472 // non2xx returns true for non-success HTTP status codes. 473 func non2xx(code int) bool { 474 return code < 200 || code > 299 475 } 476 477 // panicClose attempts to close a serverTester. If it fails, panic is called 478 // with the error. 479 func (st *serverTester) panicClose() { 480 st.server.panicClose() 481 } 482 483 // reloadedServerTester creates a server tester where all of the persistent 484 // data has been copied to a new folder and all of the modules re-initialized 485 // on the new folder. This gives an opportunity to see how modules will behave 486 // when they are relying on their persistent structures. 487 func (st *serverTester) reloadedServerTester() (*serverTester, error) { 488 // Copy the testing directory. 489 copiedDir := st.dir + " - " + persist.RandomSuffix() 490 err := build.CopyDir(st.dir, copiedDir) 491 if err != nil { 492 return nil, err 493 } 494 copyST, err := assembleServerTester(st.walletKey, copiedDir) 495 if err != nil { 496 return nil, err 497 } 498 return copyST, nil 499 } 500 501 // netAddress returns the NetAddress of the caller. 502 func (st *serverTester) netAddress() modules.NetAddress { 503 return st.server.api.gateway.Address() 504 } 505 506 // coinAddress returns a coin address that the caller is able to spend from. 507 func (st *serverTester) coinAddress() string { 508 var addr struct { 509 Address string 510 } 511 st.getAPI("/wallet/address", &addr) 512 return addr.Address 513 } 514 515 // acceptContracts instructs the host to begin accepting contracts. 516 func (st *serverTester) acceptContracts() error { 517 settingsValues := url.Values{} 518 settingsValues.Set("acceptingcontracts", "true") 519 return st.stdPostAPI("/host", settingsValues) 520 } 521 522 // setHostStorage adds a storage folder to the host. 523 func (st *serverTester) setHostStorage() error { 524 values := url.Values{} 525 values.Set("path", st.dir) 526 values.Set("size", "1048576") 527 return st.stdPostAPI("/host/storage/folders/add", values) 528 } 529 530 // announceHost announces the host, mines a block, and waits for the 531 // announcement to register. 532 func (st *serverTester) announceHost() error { 533 // Check how many hosts there are to begin with. 534 var hosts HostdbActiveGET 535 err := st.getAPI("/hostdb/active", &hosts) 536 if err != nil { 537 return err 538 } 539 initialHosts := len(hosts.Hosts) 540 541 // Set the host to be accepting contracts. 542 acceptingContractsValues := url.Values{} 543 acceptingContractsValues.Set("acceptingcontracts", "true") 544 err = st.stdPostAPI("/host", acceptingContractsValues) 545 if err != nil { 546 return build.ExtendErr("couldn't make an api call to the host:", err) 547 } 548 549 announceValues := url.Values{} 550 announceValues.Set("address", string(st.host.ExternalSettings().NetAddress)) 551 err = st.stdPostAPI("/host/announce", announceValues) 552 if err != nil { 553 return err 554 } 555 // mine block 556 _, err = st.miner.AddBlock() 557 if err != nil { 558 return err 559 } 560 // wait for announcement 561 err = st.getAPI("/hostdb/active", &hosts) 562 if err != nil { 563 return err 564 } 565 for i := 0; i < 50 && len(hosts.Hosts) <= initialHosts; i++ { 566 time.Sleep(100 * time.Millisecond) 567 err = st.getAPI("/hostdb/active", &hosts) 568 if err != nil { 569 return err 570 } 571 } 572 if len(hosts.Hosts) <= initialHosts { 573 return errors.New("host announcement not seen") 574 } 575 return nil 576 } 577 578 // getAPI makes an API call and decodes the response. 579 func (st *serverTester) getAPI(call string, obj interface{}) error { 580 resp, err := HttpGET("http://" + st.server.listener.Addr().String() + call) 581 if err != nil { 582 return err 583 } 584 defer resp.Body.Close() 585 586 if non2xx(resp.StatusCode) { 587 return decodeError(resp) 588 } 589 590 // Return early because there is no content to decode. 591 if resp.StatusCode == http.StatusNoContent { 592 return nil 593 } 594 595 // Decode the response into 'obj'. 596 err = json.NewDecoder(resp.Body).Decode(obj) 597 if err != nil { 598 return err 599 } 600 return nil 601 } 602 603 // postAPI makes an API call and decodes the response. 604 func (st *serverTester) postAPI(call string, values url.Values, obj interface{}) error { 605 resp, err := HttpPOST("http://"+st.server.listener.Addr().String()+call, values.Encode()) 606 if err != nil { 607 return err 608 } 609 defer resp.Body.Close() 610 611 if non2xx(resp.StatusCode) { 612 return decodeError(resp) 613 } 614 615 // Return early because there is no content to decode. 616 if resp.StatusCode == http.StatusNoContent { 617 return nil 618 } 619 620 // Decode the response into 'obj'. 621 err = json.NewDecoder(resp.Body).Decode(obj) 622 if err != nil { 623 return err 624 } 625 return nil 626 } 627 628 // stdGetAPI makes an API call and discards the response. 629 func (st *serverTester) stdGetAPI(call string) error { 630 resp, err := HttpGET("http://" + st.server.listener.Addr().String() + call) 631 if err != nil { 632 return err 633 } 634 defer resp.Body.Close() 635 636 if non2xx(resp.StatusCode) { 637 return decodeError(resp) 638 } 639 return nil 640 } 641 642 // stdGetAPIUA makes an API call with a custom user agent. 643 func (st *serverTester) stdGetAPIUA(call string, userAgent string) error { 644 req, err := http.NewRequest("GET", "http://"+st.server.listener.Addr().String()+call, nil) 645 if err != nil { 646 return err 647 } 648 req.Header.Set("User-Agent", userAgent) 649 resp, err := http.DefaultClient.Do(req) 650 if err != nil { 651 return err 652 } 653 defer resp.Body.Close() 654 655 if non2xx(resp.StatusCode) { 656 return decodeError(resp) 657 } 658 return nil 659 } 660 661 // stdPostAPI makes an API call and discards the response. 662 func (st *serverTester) stdPostAPI(call string, values url.Values) error { 663 resp, err := HttpPOST("http://"+st.server.listener.Addr().String()+call, values.Encode()) 664 if err != nil { 665 return err 666 } 667 defer resp.Body.Close() 668 669 if non2xx(resp.StatusCode) { 670 return decodeError(resp) 671 } 672 return nil 673 }