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 }