// startDaemon uses the config parameters to initialize Sia modules and start // siad. func startDaemon(config Config) (err error) { // Prompt user for API password. if config.Siad.AuthenticateAPI { config.APIPassword, err = speakeasy.Ask("Enter API password: "******"" { return errors.New("password cannot be blank") } } // Process the config variables after they are parsed by cobra. config, err = processConfig(config) if err != nil { return err } // Print a startup message. fmt.Println("Loading...") loadStart := time.Now() // Create the server and start serving daemon routes immediately. fmt.Printf("(0/%d) Loading siad...\n", len(config.Siad.Modules)) srv, err := NewServer(config.Siad.APIaddr, config.Siad.RequiredUserAgent, config.APIPassword) if err != nil { return err } servErrs := make(chan error) go func() { servErrs <- srv.Serve() }() // Initialize the Sia modules i := 0 var g modules.Gateway if strings.Contains(config.Siad.Modules, "g") { i++ fmt.Printf("(%d/%d) Loading gateway...\n", i, len(config.Siad.Modules)) g, err = gateway.New(config.Siad.RPCaddr, !config.Siad.NoBootstrap, filepath.Join(config.Siad.SiaDir, modules.GatewayDir)) if err != nil { return err } defer g.Close() } var cs modules.ConsensusSet if strings.Contains(config.Siad.Modules, "c") { i++ fmt.Printf("(%d/%d) Loading consensus...\n", i, len(config.Siad.Modules)) cs, err = consensus.New(g, !config.Siad.NoBootstrap, filepath.Join(config.Siad.SiaDir, modules.ConsensusDir)) if err != nil { return err } defer cs.Close() } var e modules.Explorer if strings.Contains(config.Siad.Modules, "e") { i++ fmt.Printf("(%d/%d) Loading explorer...\n", i, len(config.Siad.Modules)) e, err = explorer.New(cs, filepath.Join(config.Siad.SiaDir, modules.ExplorerDir)) if err != nil { return err } defer e.Close() } var tpool modules.TransactionPool if strings.Contains(config.Siad.Modules, "t") { i++ fmt.Printf("(%d/%d) Loading transaction pool...\n", i, len(config.Siad.Modules)) tpool, err = transactionpool.New(cs, g, filepath.Join(config.Siad.SiaDir, modules.TransactionPoolDir)) if err != nil { return err } defer tpool.Close() } var w modules.Wallet if strings.Contains(config.Siad.Modules, "w") { i++ fmt.Printf("(%d/%d) Loading wallet...\n", i, len(config.Siad.Modules)) w, err = wallet.New(cs, tpool, filepath.Join(config.Siad.SiaDir, modules.WalletDir)) if err != nil { return err } defer w.Close() } var m modules.Miner if strings.Contains(config.Siad.Modules, "m") { i++ fmt.Printf("(%d/%d) Loading miner...\n", i, len(config.Siad.Modules)) m, err = miner.New(cs, tpool, w, filepath.Join(config.Siad.SiaDir, modules.MinerDir)) if err != nil { return err } defer m.Close() } var h modules.Host if strings.Contains(config.Siad.Modules, "h") { i++ fmt.Printf("(%d/%d) Loading host...\n", i, len(config.Siad.Modules)) h, err = host.New(cs, tpool, w, config.Siad.HostAddr, filepath.Join(config.Siad.SiaDir, modules.HostDir)) if err != nil { return err } defer h.Close() } var r modules.Renter if strings.Contains(config.Siad.Modules, "r") { i++ fmt.Printf("(%d/%d) Loading renter...\n", i, len(config.Siad.Modules)) r, err = renter.New(cs, w, tpool, filepath.Join(config.Siad.SiaDir, modules.RenterDir)) if err != nil { return err } defer r.Close() } // Create the Sia API a := api.New( config.Siad.RequiredUserAgent, config.APIPassword, cs, e, g, h, m, r, tpool, w, ) // connect the API to the server srv.mux.Handle("/", a) // stop the server if a kill signal is caught sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, os.Kill) go func() { <-sigChan fmt.Println("\rCaught stop signal, quitting...") srv.Close() }() // Print a 'startup complete' message. startupTime := time.Since(loadStart) fmt.Println("Finished loading in", startupTime.Seconds(), "seconds") err = <-servErrs if err != nil { build.Critical(err) } return nil }
// negotiateContract establishes a connection to a host and negotiates an // initial file contract according to the terms of the host. func negotiateContract(conn net.Conn, addr modules.NetAddress, fc types.FileContract, txnBuilder modules.TransactionBuilder, tpool modules.TransactionPool) (hostContract, error) { // allow 30 seconds for negotiation conn.SetDeadline(time.Now().Add(30 * time.Second)) // read host key var hostPublicKey types.SiaPublicKey if err := encoding.ReadObject(conn, &hostPublicKey, 256); err != nil { return hostContract{}, errors.New("couldn't read host's public key: " + err.Error()) } // create our key ourSK, ourPK, err := crypto.GenerateKeyPair() if err != nil { return hostContract{}, errors.New("failed to generate keypair: " + err.Error()) } ourPublicKey := types.SiaPublicKey{ Algorithm: types.SignatureEd25519, Key: ourPK[:], } // send our public key if err := encoding.WriteObject(conn, ourPublicKey); err != nil { return hostContract{}, errors.New("couldn't send our public key: " + err.Error()) } // create unlock conditions uc := types.UnlockConditions{ PublicKeys: []types.SiaPublicKey{ourPublicKey, hostPublicKey}, SignaturesRequired: 2, } // add UnlockHash to file contract fc.UnlockHash = uc.UnlockHash() // build transaction containing fc err = txnBuilder.FundSiacoins(fc.Payout) if err != nil { return hostContract{}, err } txnBuilder.AddFileContract(fc) txn, parents := txnBuilder.View() txnSet := append(parents, txn) // calculate contract ID fcid := txn.FileContractID(0) // TODO: is it actually 0? // send txn if err := encoding.WriteObject(conn, txnSet); err != nil { return hostContract{}, errors.New("couldn't send our proposed contract: " + err.Error()) } // read back acceptance var response string if err := encoding.ReadObject(conn, &response, 128); err != nil { return hostContract{}, errors.New("couldn't read the host's response to our proposed contract: " + err.Error()) } if response != modules.AcceptResponse { return hostContract{}, errors.New("host rejected proposed contract: " + response) } // read back txn with host collateral. var hostTxnSet []types.Transaction if err := encoding.ReadObject(conn, &hostTxnSet, types.BlockSizeLimit); err != nil { return hostContract{}, errors.New("couldn't read the host's updated contract: " + err.Error()) } // check that txn is okay. For now, no collateral will be added, so the // transaction sets should be identical. if len(hostTxnSet) != len(txnSet) { return hostContract{}, errors.New("host sent bad collateral transaction") } for i := range hostTxnSet { if hostTxnSet[i].ID() != txnSet[i].ID() { return hostContract{}, errors.New("host sent bad collateral transaction") } } // sign the txn and resend // NOTE: for now, we are assuming that the transaction has not changed // since we sent it. Otherwise, the txnBuilder would have to be updated // with whatever fields were added by the host. signedTxnSet, err := txnBuilder.Sign(true) if err != nil { return hostContract{}, err } if err := encoding.WriteObject(conn, signedTxnSet); err != nil { return hostContract{}, errors.New("couldn't send the contract signed by us: " + err.Error()) } // read signed txn from host var signedHostTxnSet []types.Transaction if err := encoding.ReadObject(conn, &signedHostTxnSet, types.BlockSizeLimit); err != nil { return hostContract{}, errors.New("couldn't read the contract signed by the host: " + err.Error()) } // submit to blockchain err = tpool.AcceptTransactionSet(signedHostTxnSet) if err == modules.ErrDuplicateTransactionSet { // this can happen if the renter is uploading to itself err = nil } if err != nil { return hostContract{}, err } // create host contract hc := hostContract{ IP: addr, ID: fcid, FileContract: fc, LastRevision: types.FileContractRevision{ ParentID: fcid, UnlockConditions: uc, NewRevisionNumber: fc.RevisionNumber, NewFileSize: fc.FileSize, NewFileMerkleRoot: fc.FileMerkleRoot, NewWindowStart: fc.WindowStart, NewWindowEnd: fc.WindowEnd, NewValidProofOutputs: []types.SiacoinOutput{fc.ValidProofOutputs[0], fc.ValidProofOutputs[1]}, NewMissedProofOutputs: []types.SiacoinOutput{fc.MissedProofOutputs[0], fc.MissedProofOutputs[1]}, NewUnlockHash: fc.UnlockHash, }, LastRevisionTxn: types.Transaction{}, SecretKey: ourSK, } return hc, nil }