github.com/nebulouslabs/sia@v1.3.7/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  	"path/filepath"
    14  
    15  	"github.com/NebulousLabs/Sia/modules"
    16  	"github.com/NebulousLabs/Sia/modules/consensus"
    17  	"github.com/NebulousLabs/Sia/modules/gateway"
    18  	"github.com/NebulousLabs/Sia/modules/host"
    19  	"github.com/NebulousLabs/Sia/modules/miner"
    20  	"github.com/NebulousLabs/Sia/modules/renter"
    21  	"github.com/NebulousLabs/Sia/modules/renter/contractor"
    22  	"github.com/NebulousLabs/Sia/modules/renter/hostdb"
    23  	"github.com/NebulousLabs/Sia/modules/renter/proto"
    24  	"github.com/NebulousLabs/Sia/modules/transactionpool"
    25  	"github.com/NebulousLabs/Sia/modules/wallet"
    26  	"github.com/NebulousLabs/Sia/persist"
    27  
    28  	"github.com/NebulousLabs/errors"
    29  )
    30  
    31  // NodeParams contains a bunch of parameters for creating a new test node. As
    32  // there are many options, templates are provided that you can modify which
    33  // cover the most common use cases.
    34  //
    35  // Each module is created separately. There are several ways to create a module,
    36  // though not all methods are currently available for each module. You should
    37  // only use one method for creating a module, using multiple methods will cause
    38  // an error.
    39  //		+ Indicate with the 'CreateModule' bool that a module should be created
    40  //		  automatically. To create the module with custom dependencies, pass the
    41  //		  custom dependencies in using the 'ModuleDependencies' field.
    42  //		+ Pass an existing module in directly.
    43  //		+ Set 'CreateModule' to false and do not pass in an existing module.
    44  //		  This will result in a 'nil' module, meaning the node will not have
    45  //		  that module.
    46  type NodeParams struct {
    47  	// Flags to indicate which modules should be created automatically by the
    48  	// server. If you are providing a pre-existing module, do not set the flag
    49  	// for that module.
    50  	//
    51  	// NOTE / TODO: The code does not currently enforce this, but you should not
    52  	// provide a custom module unless all of its dependencies are also custom.
    53  	// Example: if the ConsensusSet is custom, the Gateway should also be
    54  	// custom. The TransactionPool however does not need to be custom in this
    55  	// example.
    56  	CreateConsensusSet    bool
    57  	CreateExplorer        bool
    58  	CreateGateway         bool
    59  	CreateHost            bool
    60  	CreateMiner           bool
    61  	CreateRenter          bool
    62  	CreateTransactionPool bool
    63  	CreateWallet          bool
    64  
    65  	// Custom modules - if the modules is provided directly, the provided
    66  	// module will be used instead of creating a new one. If a custom module is
    67  	// provided, the 'omit' flag for that module must be set to false (which is
    68  	// the default setting).
    69  	ConsensusSet    modules.ConsensusSet
    70  	Explorer        modules.Explorer
    71  	Gateway         modules.Gateway
    72  	Host            modules.Host
    73  	Miner           modules.TestMiner
    74  	Renter          modules.Renter
    75  	TransactionPool modules.TransactionPool
    76  	Wallet          modules.Wallet
    77  
    78  	// Dependencies for each module supporting dependency injection.
    79  	ContractorDeps  modules.Dependencies
    80  	ContractSetDeps modules.Dependencies
    81  	HostDBDeps      modules.Dependencies
    82  	RenterDeps      modules.Dependencies
    83  	WalletDeps      modules.Dependencies
    84  
    85  	// Custom settings for modules
    86  	Allowance modules.Allowance
    87  
    88  	// The following fields are used to skip parts of the node set up
    89  	SkipSetAllowance  bool
    90  	SkipHostDiscovery bool
    91  
    92  	// The high level directory where all the persistence gets stored for the
    93  	// modules.
    94  	Dir string
    95  }
    96  
    97  // Node is a collection of Sia modules operating together as a Sia node.
    98  type Node struct {
    99  	// The modules of the node. Modules that are not initialized will be nil.
   100  	ConsensusSet    modules.ConsensusSet
   101  	Explorer        modules.Explorer
   102  	Gateway         modules.Gateway
   103  	Host            modules.Host
   104  	Miner           modules.TestMiner
   105  	Renter          modules.Renter
   106  	TransactionPool modules.TransactionPool
   107  	Wallet          modules.Wallet
   108  
   109  	// The high level directory where all the persistence gets stored for the
   110  	// modules.
   111  	Dir string
   112  }
   113  
   114  // Close will call close on every module within the node, combining and
   115  // returning the errors.
   116  func (n *Node) Close() (err error) {
   117  	if n.Explorer != nil {
   118  		err = errors.Compose(n.Explorer.Close())
   119  	}
   120  	if n.Miner != nil {
   121  		err = errors.Compose(n.Miner.Close())
   122  	}
   123  	if n.Host != nil {
   124  		err = errors.Compose(n.Host.Close())
   125  	}
   126  	if n.Renter != nil {
   127  		err = errors.Compose(n.Renter.Close())
   128  	}
   129  	if n.Wallet != nil {
   130  		err = errors.Compose(n.Wallet.Close())
   131  	}
   132  	if n.TransactionPool != nil {
   133  		err = errors.Compose(n.TransactionPool.Close())
   134  	}
   135  	if n.ConsensusSet != nil {
   136  		err = errors.Compose(n.ConsensusSet.Close())
   137  	}
   138  	if n.Gateway != nil {
   139  		err = errors.Compose(n.Gateway.Close())
   140  	}
   141  	return err
   142  }
   143  
   144  // New will create a new test node. The inputs to the function are the
   145  // respective 'New' calls for each module. We need to use this awkward method
   146  // of initialization because the siatest package cannot import any of the
   147  // modules directly (so that the modules may use the siatest package to test
   148  // themselves).
   149  func New(params NodeParams) (*Node, error) {
   150  	dir := params.Dir
   151  
   152  	// Gateway.
   153  	g, err := func() (modules.Gateway, error) {
   154  		if params.CreateGateway && params.Gateway != nil {
   155  			return nil, errors.New("cannot both create a gateway and use a passed in gateway")
   156  		}
   157  		/* Template for dealing with optional dependencies:
   158  		if !params.CreateGateway && parames.GatewayDependencies != nil {
   159  			return nil, errors.New("cannot pass in gateway dependencies if you are not creating a gateway")
   160  		}
   161  		*/
   162  		if params.Gateway != nil {
   163  			return params.Gateway, nil
   164  		}
   165  		if !params.CreateGateway {
   166  			return nil, nil
   167  		}
   168  		/* Template for dealing with optional dependencies:
   169  		if params.GatewayDependencies == nil {
   170  			gateway.New(...
   171  		} else {
   172  			gateway.NewDeps(...
   173  		}
   174  		*/
   175  		return gateway.New("localhost:0", false, filepath.Join(dir, modules.GatewayDir))
   176  	}()
   177  	if err != nil {
   178  		return nil, errors.Extend(err, errors.New("unable to create gateway"))
   179  	}
   180  
   181  	// Consensus.
   182  	cs, err := func() (modules.ConsensusSet, error) {
   183  		if params.CreateConsensusSet && params.ConsensusSet != nil {
   184  			return nil, errors.New("cannot both create consensus and use passed in consensus")
   185  		}
   186  		if params.ConsensusSet != nil {
   187  			return params.ConsensusSet, nil
   188  		}
   189  		if !params.CreateConsensusSet {
   190  			return nil, nil
   191  		}
   192  		return consensus.New(g, false, filepath.Join(dir, modules.ConsensusDir))
   193  	}()
   194  	if err != nil {
   195  		return nil, errors.Extend(err, errors.New("unable to create consensus set"))
   196  	}
   197  
   198  	// Transaction Pool.
   199  	tp, err := func() (modules.TransactionPool, error) {
   200  		if params.CreateTransactionPool && params.TransactionPool != nil {
   201  			return nil, errors.New("cannot create transaction pool and also use custom transaction pool")
   202  		}
   203  		if params.TransactionPool != nil {
   204  			return params.TransactionPool, nil
   205  		}
   206  		if !params.CreateTransactionPool {
   207  			return nil, nil
   208  		}
   209  		return transactionpool.New(cs, g, filepath.Join(dir, modules.TransactionPoolDir))
   210  	}()
   211  	if err != nil {
   212  		return nil, errors.Extend(err, errors.New("unable to create transaction pool"))
   213  	}
   214  
   215  	// Wallet.
   216  	w, err := func() (modules.Wallet, error) {
   217  		if params.CreateWallet && params.Wallet != nil {
   218  			return nil, errors.New("cannot create wallet and use custom wallet")
   219  		}
   220  		if params.Wallet != nil {
   221  			return params.Wallet, nil
   222  		}
   223  		if !params.CreateWallet {
   224  			return nil, nil
   225  		}
   226  		walletDeps := params.WalletDeps
   227  		if walletDeps == nil {
   228  			walletDeps = modules.ProdDependencies
   229  		}
   230  		return wallet.NewCustomWallet(cs, tp, filepath.Join(dir, modules.WalletDir), walletDeps)
   231  	}()
   232  	if err != nil {
   233  		return nil, errors.Extend(err, errors.New("unable to create wallet"))
   234  	}
   235  
   236  	// Host.
   237  	h, err := func() (modules.Host, error) {
   238  		if params.CreateHost && params.Host != nil {
   239  			return nil, errors.New("cannot create host and use custom host")
   240  		}
   241  		if params.Host != nil {
   242  			return params.Host, nil
   243  		}
   244  		if !params.CreateHost {
   245  			return nil, nil
   246  		}
   247  		return host.New(cs, tp, w, "localhost:0", filepath.Join(dir, modules.HostDir))
   248  	}()
   249  	if err != nil {
   250  		return nil, errors.Extend(err, errors.New("unable to create host"))
   251  	}
   252  
   253  	// Renter.
   254  	r, err := func() (modules.Renter, error) {
   255  		if params.CreateRenter && params.Renter != nil {
   256  			return nil, errors.New("cannot create renter and also use custom renter")
   257  		}
   258  		if params.Renter != nil {
   259  			return params.Renter, nil
   260  		}
   261  		if !params.CreateRenter {
   262  			return nil, nil
   263  		}
   264  		contractorDeps := params.ContractorDeps
   265  		if contractorDeps == nil {
   266  			contractorDeps = modules.ProdDependencies
   267  		}
   268  		contractSetDeps := params.ContractSetDeps
   269  		if contractSetDeps == nil {
   270  			contractSetDeps = modules.ProdDependencies
   271  		}
   272  		hostDBDeps := params.HostDBDeps
   273  		if hostDBDeps == nil {
   274  			hostDBDeps = modules.ProdDependencies
   275  		}
   276  		renterDeps := params.RenterDeps
   277  		if renterDeps == nil {
   278  			renterDeps = modules.ProdDependencies
   279  		}
   280  		persistDir := filepath.Join(dir, modules.RenterDir)
   281  
   282  		// HostDB
   283  		hdb, err := hostdb.NewCustomHostDB(g, cs, persistDir, hostDBDeps)
   284  		if err != nil {
   285  			return nil, err
   286  		}
   287  		// ContractSet
   288  		contractSet, err := proto.NewContractSet(filepath.Join(persistDir, "contracts"), contractSetDeps)
   289  		if err != nil {
   290  			return nil, err
   291  		}
   292  		// Contractor
   293  		logger, err := persist.NewFileLogger(filepath.Join(persistDir, "contractor.log"))
   294  		if err != nil {
   295  			return nil, err
   296  		}
   297  		hc, err := contractor.NewCustomContractor(cs, &contractor.WalletBridge{W: w}, tp, hdb, contractSet, contractor.NewPersist(persistDir), logger, contractorDeps)
   298  		if err != nil {
   299  			return nil, err
   300  		}
   301  		return renter.NewCustomRenter(g, cs, tp, hdb, hc, persistDir, renterDeps)
   302  	}()
   303  	if err != nil {
   304  		return nil, errors.Extend(err, errors.New("unable to create renter"))
   305  	}
   306  
   307  	// Miner.
   308  	m, err := func() (modules.TestMiner, error) {
   309  		if params.CreateMiner && params.Miner != nil {
   310  			return nil, errors.New("cannot create miner and also use custom miner")
   311  		}
   312  		if params.Miner != nil {
   313  			return params.Miner, nil
   314  		}
   315  		if !params.CreateMiner {
   316  			return nil, nil
   317  		}
   318  		m, err := miner.New(cs, tp, w, filepath.Join(dir, modules.MinerDir))
   319  		if err != nil {
   320  			return nil, err
   321  		}
   322  		return m, nil
   323  	}()
   324  	if err != nil {
   325  		return nil, errors.Extend(err, errors.New("unable to create miner"))
   326  	}
   327  
   328  	// Explorer.
   329  	e, err := func() (modules.Explorer, error) {
   330  		if !params.CreateExplorer && params.Explorer != nil {
   331  			return nil, errors.New("cannot create explorer and also use custom explorer")
   332  		}
   333  		if params.Explorer != nil {
   334  			return params.Explorer, nil
   335  		}
   336  		if !params.CreateExplorer {
   337  			return nil, nil
   338  		}
   339  		// TODO: Implement explorer.
   340  		return nil, errors.New("explorer not implemented")
   341  	}()
   342  	if err != nil {
   343  		return nil, errors.Extend(err, errors.New("unable to create explorer"))
   344  	}
   345  
   346  	return &Node{
   347  		ConsensusSet:    cs,
   348  		Explorer:        e,
   349  		Gateway:         g,
   350  		Host:            h,
   351  		Miner:           m,
   352  		Renter:          r,
   353  		TransactionPool: tp,
   354  		Wallet:          w,
   355  
   356  		Dir: dir,
   357  	}, nil
   358  }