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  }