gitlab.com/SiaPrime/SiaPrime@v1.4.1/node/node.go (about)

     1  // Package node provides tooling for creating a Sia node. Sia nodes consist of a
     2  // collection of modules. The node package gives you tools to easily assemble
     3  // various combinations of modules with varying dependencies and settings,
     4  // including templates for assembling sane no-hassle Sia nodes.
     5  package node
     6  
     7  // TODO: Add support for the explorer.
     8  
     9  // TODO: Add support for custom dependencies and parameters for all of the
    10  // modules.
    11  
    12  import (
    13  	"fmt"
    14  	"path/filepath"
    15  	"time"
    16  
    17  	"gitlab.com/NebulousLabs/errors"
    18  
    19  	"gitlab.com/SiaPrime/SiaPrime/build"
    20  	"gitlab.com/SiaPrime/SiaPrime/config"
    21  	"gitlab.com/SiaPrime/SiaPrime/modules"
    22  	"gitlab.com/SiaPrime/SiaPrime/modules/consensus"
    23  	"gitlab.com/SiaPrime/SiaPrime/modules/explorer"
    24  	"gitlab.com/SiaPrime/SiaPrime/modules/gateway"
    25  	"gitlab.com/SiaPrime/SiaPrime/modules/host"
    26  	"gitlab.com/SiaPrime/SiaPrime/modules/miner"
    27  	"gitlab.com/SiaPrime/SiaPrime/modules/miningpool"
    28  	"gitlab.com/SiaPrime/SiaPrime/modules/renter"
    29  	"gitlab.com/SiaPrime/SiaPrime/modules/renter/contractor"
    30  	"gitlab.com/SiaPrime/SiaPrime/modules/renter/hostdb"
    31  	"gitlab.com/SiaPrime/SiaPrime/modules/renter/proto"
    32  	"gitlab.com/SiaPrime/SiaPrime/modules/stratumminer"
    33  	"gitlab.com/SiaPrime/SiaPrime/modules/transactionpool"
    34  	"gitlab.com/SiaPrime/SiaPrime/modules/wallet"
    35  	"gitlab.com/SiaPrime/SiaPrime/persist"
    36  )
    37  
    38  // NodeParams contains a bunch of parameters for creating a new test node. As
    39  // there are many options, templates are provided that you can modify which
    40  // cover the most common use cases.
    41  //
    42  // Each module is created separately. There are several ways to create a module,
    43  // though not all methods are currently available for each module. You should
    44  // only use one method for creating a module, using multiple methods will cause
    45  // an error.
    46  //		+ Indicate with the 'CreateModule' bool that a module should be created
    47  //		  automatically. To create the module with custom dependencies, pass the
    48  //		  custom dependencies in using the 'ModuleDependencies' field.
    49  //		+ Pass an existing module in directly.
    50  //		+ Set 'CreateModule' to false and do not pass in an existing module.
    51  //		  This will result in a 'nil' module, meaning the node will not have
    52  //		  that module.
    53  type NodeParams struct {
    54  	// Flags to indicate which modules should be created automatically by the
    55  	// server. If you are providing a pre-existing module, do not set the flag
    56  	// for that module.
    57  	//
    58  	// NOTE / TODO: The code does not currently enforce this, but you should not
    59  	// provide a custom module unless all of its dependencies are also custom.
    60  	// Example: if the ConsensusSet is custom, the Gateway should also be
    61  	// custom. The TransactionPool however does not need to be custom in this
    62  	// example.
    63  	CreateConsensusSet    bool
    64  	CreateExplorer        bool
    65  	CreateGateway         bool
    66  	CreateHost            bool
    67  	CreateMiner           bool
    68  	CreateMiningPool      bool
    69  	CreateStratumMiner    bool
    70  	CreateRenter          bool
    71  	CreateTransactionPool bool
    72  	CreateWallet          bool
    73  
    74  	// Custom modules - if the modules is provided directly, the provided
    75  	// module will be used instead of creating a new one. If a custom module is
    76  	// provided, the 'omit' flag for that module must be set to false (which is
    77  	// the default setting).
    78  	ConsensusSet    modules.ConsensusSet
    79  	Explorer        modules.Explorer
    80  	Gateway         modules.Gateway
    81  	Host            modules.Host
    82  	Miner           modules.TestMiner
    83  	MiningPool      modules.Pool
    84  	StratumMiner    modules.StratumMiner
    85  	Renter          modules.Renter
    86  	TransactionPool modules.TransactionPool
    87  	Wallet          modules.Wallet
    88  
    89  	// Dependencies for each module supporting dependency injection.
    90  	ContractorDeps  modules.Dependencies
    91  	ContractSetDeps modules.Dependencies
    92  	HostDBDeps      modules.Dependencies
    93  	RenterDeps      modules.Dependencies
    94  	WalletDeps      modules.Dependencies
    95  
    96  	// Custom settings for modules
    97  	Allowance   modules.Allowance
    98  	Bootstrap   bool
    99  	HostAddress string
   100  	HostStorage uint64
   101  	RPCAddress  string
   102  
   103  	// Initialize node from existing seed.
   104  	PrimarySeed string
   105  
   106  	// The following fields are used to skip parts of the node set up
   107  	SkipSetAllowance     bool
   108  	SkipHostDiscovery    bool
   109  	SkipHostAnnouncement bool
   110  
   111  	// The high level directory where all the persistence gets stored for the
   112  	// modules.
   113  	Dir string
   114  }
   115  
   116  // Node is a collection of Sia modules operating together as a Sia node.
   117  type Node struct {
   118  	// The modules of the node. Modules that are not initialized will be nil.
   119  	ConsensusSet    modules.ConsensusSet
   120  	Explorer        modules.Explorer
   121  	Gateway         modules.Gateway
   122  	Host            modules.Host
   123  	Miner           modules.TestMiner
   124  	MiningPool      modules.Pool
   125  	StratumMiner    modules.StratumMiner
   126  	Renter          modules.Renter
   127  	TransactionPool modules.TransactionPool
   128  	Wallet          modules.Wallet
   129  
   130  	// The high level directory where all the persistence gets stored for the
   131  	// modules.
   132  	Dir string
   133  }
   134  
   135  // NumModules returns how many of the major modules the given NodeParams would
   136  // create.
   137  func (np NodeParams) NumModules() (n int) {
   138  	if np.CreateGateway || np.Gateway != nil {
   139  		n++
   140  	}
   141  	if np.CreateConsensusSet || np.ConsensusSet != nil {
   142  		n++
   143  	}
   144  	if np.CreateTransactionPool || np.TransactionPool != nil {
   145  		n++
   146  	}
   147  	if np.CreateWallet || np.Wallet != nil {
   148  		n++
   149  	}
   150  	if np.CreateHost || np.Host != nil {
   151  		n++
   152  	}
   153  	if np.CreateRenter || np.Renter != nil {
   154  		n++
   155  	}
   156  	if np.CreateMiner || np.Miner != nil {
   157  		n++
   158  	}
   159  	if np.CreateExplorer || np.Explorer != nil {
   160  		n++
   161  	}
   162  	if np.CreateMiningPool || np.MiningPool != nil {
   163  		n++
   164  	}
   165  	if np.CreateStratumMiner || np.StratumMiner != nil {
   166  		n++
   167  	}
   168  	return
   169  }
   170  
   171  // printlnRelease is a wrapper that only prints to stdout in release builds.
   172  func printlnRelease(a ...interface{}) (int, error) {
   173  	if build.Release == "standard" {
   174  		return fmt.Println(a...)
   175  	}
   176  	return 0, nil
   177  }
   178  
   179  // printfRelease is a wrapper that only prints to stdout in release builds.
   180  func printfRelease(format string, a ...interface{}) (int, error) {
   181  	if build.Release == "standard" {
   182  		return fmt.Printf(format, a...)
   183  	}
   184  	return 0, nil
   185  }
   186  
   187  // Close will call close on every module within the node, combining and
   188  // returning the errors.
   189  func (n *Node) Close() (err error) {
   190  	if n.MiningPool != nil {
   191  		printlnRelease("Closing mining pool...")
   192  		err = errors.Compose(n.MiningPool.Close())
   193  	}
   194  	if n.StratumMiner != nil {
   195  		printlnRelease("Closing stratum miner...")
   196  		err = errors.Compose(n.StratumMiner.Close())
   197  	}
   198  	if n.Renter != nil {
   199  		printlnRelease("Closing renter...")
   200  		err = errors.Compose(n.Renter.Close())
   201  	}
   202  	if n.Host != nil {
   203  		printlnRelease("Closing host...")
   204  		err = errors.Compose(n.Host.Close())
   205  	}
   206  	if n.Miner != nil {
   207  		printlnRelease("Closing miner...")
   208  		err = errors.Compose(n.Miner.Close())
   209  	}
   210  	if n.Wallet != nil {
   211  		printlnRelease("Closing wallet...")
   212  		err = errors.Compose(n.Wallet.Close())
   213  	}
   214  	if n.TransactionPool != nil {
   215  		printlnRelease("Closing transactionpool...")
   216  		err = errors.Compose(n.TransactionPool.Close())
   217  	}
   218  	if n.Explorer != nil {
   219  		printlnRelease("Closing explorer...")
   220  		err = errors.Compose(n.Explorer.Close())
   221  	}
   222  	if n.ConsensusSet != nil {
   223  		printlnRelease("Closing consensusset...")
   224  		err = errors.Compose(n.ConsensusSet.Close())
   225  	}
   226  	if n.Gateway != nil {
   227  		printlnRelease("Closing gateway...")
   228  		err = errors.Compose(n.Gateway.Close())
   229  	}
   230  	return err
   231  }
   232  
   233  // New will create a new test node. The inputs to the function are the
   234  // respective 'New' calls for each module. We need to use this awkward method
   235  // of initialization because the siatest package cannot import any of the
   236  // modules directly (so that the modules may use the siatest package to test
   237  // themselves).
   238  func New(params NodeParams) (*Node, error) {
   239  	dir := params.Dir
   240  	numModules := params.NumModules()
   241  	i := 0
   242  	printfRelease("Loading modules:\n")
   243  	loadStart := time.Now()
   244  
   245  	// Gateway.
   246  	g, err := func() (modules.Gateway, error) {
   247  		if params.CreateGateway && params.Gateway != nil {
   248  			return nil, errors.New("cannot both create a gateway and use a passed in gateway")
   249  		}
   250  		if params.Gateway != nil {
   251  			return params.Gateway, nil
   252  		}
   253  		if !params.CreateGateway {
   254  			return nil, nil
   255  		}
   256  		if params.RPCAddress == "" {
   257  			params.RPCAddress = "localhost:0"
   258  		}
   259  		i++
   260  		printfRelease("(%d/%d) Loading gateway...", i, numModules)
   261  		return gateway.New(params.RPCAddress, params.Bootstrap, filepath.Join(dir, modules.GatewayDir))
   262  	}()
   263  	if err != nil {
   264  		return nil, errors.Extend(err, errors.New("unable to create gateway"))
   265  	}
   266  	printlnRelease(" done in ", time.Since(loadStart).Seconds(), "seconds.")
   267  	loadStart = time.Now()
   268  	// Consensus.
   269  	cs, err := func() (modules.ConsensusSet, error) {
   270  		if params.CreateConsensusSet && params.ConsensusSet != nil {
   271  			return nil, errors.New("cannot both create consensus and use passed in consensus")
   272  		}
   273  		if params.ConsensusSet != nil {
   274  			return params.ConsensusSet, nil
   275  		}
   276  		if !params.CreateConsensusSet {
   277  			return nil, nil
   278  		}
   279  		i++
   280  		printfRelease("(%d/%d) Loading consensus...", i, numModules)
   281  		return consensus.New(g, params.Bootstrap, filepath.Join(dir, modules.ConsensusDir))
   282  	}()
   283  	if err != nil {
   284  		return nil, errors.Extend(err, errors.New("unable to create consensus set"))
   285  	}
   286  	printlnRelease(" done in ", time.Since(loadStart).Seconds(), "seconds.")
   287  	loadStart = time.Now()
   288  
   289  	// Explorer.
   290  	e, err := func() (modules.Explorer, error) {
   291  		if !params.CreateExplorer && params.Explorer != nil {
   292  			return nil, errors.New("cannot create explorer and also use custom explorer")
   293  		}
   294  		if params.Explorer != nil {
   295  			return params.Explorer, nil
   296  		}
   297  		if !params.CreateExplorer {
   298  			return nil, nil
   299  		}
   300  		i++
   301  		printfRelease("(%d/%d) Loading explorer...", i, numModules)
   302  		e, err := explorer.New(cs, filepath.Join(dir, modules.ExplorerDir))
   303  		if err != nil {
   304  			return nil, err
   305  		}
   306  		return e, nil
   307  	}()
   308  	if err != nil {
   309  		return nil, errors.Extend(err, errors.New("unable to create explorer"))
   310  	}
   311  	if e != nil {
   312  		printlnRelease(" done in ", time.Since(loadStart).Seconds(), "seconds.")
   313  	}
   314  	loadStart = time.Now()
   315  	// Transaction Pool.
   316  	tp, err := func() (modules.TransactionPool, error) {
   317  		if params.CreateTransactionPool && params.TransactionPool != nil {
   318  			return nil, errors.New("cannot create transaction pool and also use custom transaction pool")
   319  		}
   320  		if params.TransactionPool != nil {
   321  			return params.TransactionPool, nil
   322  		}
   323  		if !params.CreateTransactionPool {
   324  			return nil, nil
   325  		}
   326  		i++
   327  		printfRelease("(%d/%d) Loading transaction pool...", i, numModules)
   328  		return transactionpool.New(cs, g, filepath.Join(dir, modules.TransactionPoolDir))
   329  	}()
   330  	if err != nil {
   331  		return nil, errors.Extend(err, errors.New("unable to create transaction pool"))
   332  	}
   333  	if tp != nil {
   334  		printlnRelease(" done in ", time.Since(loadStart).Seconds(), "seconds.")
   335  	}
   336  	loadStart = time.Now()
   337  
   338  	// Wallet.
   339  	w, err := func() (modules.Wallet, error) {
   340  		if params.CreateWallet && params.Wallet != nil {
   341  			return nil, errors.New("cannot create wallet and use custom wallet")
   342  		}
   343  		if params.Wallet != nil {
   344  			return params.Wallet, nil
   345  		}
   346  		if !params.CreateWallet {
   347  			return nil, nil
   348  		}
   349  		walletDeps := params.WalletDeps
   350  		if walletDeps == nil {
   351  			walletDeps = modules.ProdDependencies
   352  		}
   353  		i++
   354  		printfRelease("(%d/%d) Loading wallet...", i, numModules)
   355  		return wallet.NewCustomWallet(cs, tp, filepath.Join(dir, modules.WalletDir), walletDeps)
   356  	}()
   357  	if err != nil {
   358  		return nil, errors.Extend(err, errors.New("unable to create wallet"))
   359  	}
   360  	if w != nil {
   361  		printlnRelease(" done in ", time.Since(loadStart).Seconds(), "seconds.")
   362  	}
   363  	loadStart = time.Now()
   364  	// Miner.
   365  	m, err := func() (modules.TestMiner, error) {
   366  		if params.CreateMiner && params.Miner != nil {
   367  			return nil, errors.New("cannot create miner and also use custom miner")
   368  		}
   369  		if params.Miner != nil {
   370  			return params.Miner, nil
   371  		}
   372  		if !params.CreateMiner {
   373  			return nil, nil
   374  		}
   375  		i++
   376  		printfRelease("(%d/%d) Loading miner...", i, numModules)
   377  		m, err := miner.New(cs, tp, w, filepath.Join(dir, modules.MinerDir))
   378  		if err != nil {
   379  			return nil, err
   380  		}
   381  		return m, nil
   382  	}()
   383  	if err != nil {
   384  		return nil, errors.Extend(err, errors.New("unable to create miner"))
   385  	}
   386  	if m != nil {
   387  		printlnRelease(" done in ", time.Since(loadStart).Seconds(), "seconds.")
   388  	}
   389  	loadStart = time.Now()
   390  
   391  	// Host.
   392  	h, err := func() (modules.Host, error) {
   393  		if params.CreateHost && params.Host != nil {
   394  			return nil, errors.New("cannot create host and use custom host")
   395  		}
   396  		if params.Host != nil {
   397  			return params.Host, nil
   398  		}
   399  		if !params.CreateHost {
   400  			return nil, nil
   401  		}
   402  		if params.HostAddress == "" {
   403  			params.HostAddress = "localhost:0"
   404  		}
   405  		i++
   406  		printfRelease("(%d/%d) Loading host...", i, numModules)
   407  		return host.New(cs, g, tp, w, params.HostAddress, filepath.Join(dir, modules.HostDir))
   408  	}()
   409  	if err != nil {
   410  		return nil, errors.Extend(err, errors.New("unable to create host"))
   411  	}
   412  	if h != nil {
   413  		printlnRelease(" done in ", time.Since(loadStart).Seconds(), "seconds.")
   414  	}
   415  	loadStart = time.Now()
   416  
   417  	// Renter.
   418  	r, err := func() (modules.Renter, error) {
   419  		if params.CreateRenter && params.Renter != nil {
   420  			return nil, errors.New("cannot create renter and also use custom renter")
   421  		}
   422  		if params.Renter != nil {
   423  			return params.Renter, nil
   424  		}
   425  		if !params.CreateRenter {
   426  			return nil, nil
   427  		}
   428  		contractorDeps := params.ContractorDeps
   429  		if contractorDeps == nil {
   430  			contractorDeps = modules.ProdDependencies
   431  		}
   432  		contractSetDeps := params.ContractSetDeps
   433  		if contractSetDeps == nil {
   434  			contractSetDeps = modules.ProdDependencies
   435  		}
   436  		hostDBDeps := params.HostDBDeps
   437  		if hostDBDeps == nil {
   438  			hostDBDeps = modules.ProdDependencies
   439  		}
   440  		renterDeps := params.RenterDeps
   441  		if renterDeps == nil {
   442  			renterDeps = modules.ProdDependencies
   443  		}
   444  		persistDir := filepath.Join(dir, modules.RenterDir)
   445  
   446  		i++
   447  		printfRelease("(%d/%d) Loading renter...", i, numModules)
   448  
   449  		// HostDB
   450  		hdb, err := hostdb.NewCustomHostDB(g, cs, tp, persistDir, hostDBDeps)
   451  		if err != nil {
   452  			return nil, err
   453  		}
   454  
   455  		// ContractSet
   456  		contractSet, err := proto.NewContractSet(filepath.Join(persistDir, "contracts"), contractSetDeps)
   457  		if err != nil {
   458  			return nil, err
   459  		}
   460  
   461  		// Contractor
   462  		logger, err := persist.NewFileLogger(filepath.Join(persistDir, "contractor.log"))
   463  		if err != nil {
   464  			return nil, err
   465  		}
   466  		contractorWallet := &contractor.WalletBridge{W: w}
   467  		contractorPersist := contractor.NewPersist(persistDir)
   468  		hc, err := contractor.NewCustomContractor(cs, contractorWallet, tp, hdb, contractSet, contractorPersist, logger, contractorDeps)
   469  		if err != nil {
   470  			logger.Debugln("Renter start aborted, error starting contractor.")
   471  			return nil, err
   472  		}
   473  		return renter.NewCustomRenter(g, cs, tp, hdb, w, hc, persistDir, renterDeps)
   474  	}()
   475  	if err != nil {
   476  		return nil, errors.Extend(err, errors.New("unable to create renter"))
   477  	}
   478  	if r != nil {
   479  		printlnRelease(" done in ", time.Since(loadStart).Seconds(), "seconds.")
   480  	}
   481  	loadStart = time.Now()
   482  
   483  	// Mining Pool.
   484  	p, err := func() (modules.Pool, error) {
   485  		if params.CreateMiningPool && params.MiningPool != nil {
   486  			return nil, errors.New("cannot create mining pool and also use custom mining pool")
   487  		}
   488  		if params.MiningPool != nil {
   489  			return params.MiningPool, nil
   490  		}
   491  		if !params.CreateMiningPool {
   492  			return nil, nil
   493  		}
   494  
   495  		i++
   496  		printfRelease("(%d/%d) Loading mining pool...", i, numModules)
   497  		p, err := pool.New(cs, tp, g, w, filepath.Join(dir, modules.PoolDir), config.MiningPoolConfig{})
   498  		if err != nil {
   499  			return nil, err
   500  		}
   501  		return p, nil
   502  	}()
   503  	if err != nil {
   504  		return nil, errors.Extend(err, errors.New("unable to create mining pool"))
   505  	}
   506  	if p != nil {
   507  		printlnRelease(" done in ", time.Since(loadStart).Seconds(), "seconds.")
   508  	}
   509  	loadStart = time.Now()
   510  
   511  	// Stratum Miner.
   512  	sm, err := func() (modules.StratumMiner, error) {
   513  		if params.CreateStratumMiner && params.StratumMiner != nil {
   514  			return nil, errors.New("cannot create stratum miner and also use custom stratum miner")
   515  		}
   516  		if params.StratumMiner != nil {
   517  			return params.StratumMiner, nil
   518  		}
   519  		if !params.CreateStratumMiner {
   520  			return nil, nil
   521  		}
   522  		i++
   523  		printfRelease("(%d/%d) Loading stratum miner...", i, numModules)
   524  		sm, err := stratumminer.New(filepath.Join(dir, modules.StratumMinerDir))
   525  		if err != nil {
   526  			return nil, err
   527  		}
   528  		return sm, nil
   529  	}()
   530  	if err != nil {
   531  		return nil, errors.Extend(err, errors.New("unable to create stratumminer"))
   532  	}
   533  	if sm != nil {
   534  		printlnRelease(" done in ", time.Since(loadStart).Seconds(), "seconds.")
   535  	}
   536  
   537  	return &Node{
   538  		ConsensusSet:    cs,
   539  		Explorer:        e,
   540  		Gateway:         g,
   541  		Host:            h,
   542  		Miner:           m,
   543  		MiningPool:      p,
   544  		StratumMiner:    sm,
   545  		Renter:          r,
   546  		TransactionPool: tp,
   547  		Wallet:          w,
   548  
   549  		Dir: dir,
   550  	}, nil
   551  }