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