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  }