// managedFinalizeContract will take a file contract, add the host's // collateral, and then try submitting the file contract to the transaction // pool. If there is no error, the completed transaction set will be returned // to the caller. func (h *Host) managedFinalizeContract(builder modules.TransactionBuilder, renterPK crypto.PublicKey, renterSignatures []types.TransactionSignature, renterRevisionSignature types.TransactionSignature, initialSectorRoots []crypto.Hash, hostCollateral, hostInitialRevenue, hostInitialRisk types.Currency) ([]types.TransactionSignature, types.TransactionSignature, types.FileContractID, error) { for _, sig := range renterSignatures { builder.AddTransactionSignature(sig) } fullTxnSet, err := builder.Sign(true) if err != nil { builder.Drop() return nil, types.TransactionSignature{}, types.FileContractID{}, err } // Verify that the signature for the revision from the renter is correct. h.mu.RLock() blockHeight := h.blockHeight hostSPK := h.publicKey hostSK := h.secretKey h.mu.RUnlock() contractTxn := fullTxnSet[len(fullTxnSet)-1] fc := contractTxn.FileContracts[0] noOpRevision := types.FileContractRevision{ ParentID: contractTxn.FileContractID(0), UnlockConditions: types.UnlockConditions{ PublicKeys: []types.SiaPublicKey{ { Algorithm: types.SignatureEd25519, Key: renterPK[:], }, hostSPK, }, SignaturesRequired: 2, }, NewRevisionNumber: fc.RevisionNumber + 1, NewFileSize: fc.FileSize, NewFileMerkleRoot: fc.FileMerkleRoot, NewWindowStart: fc.WindowStart, NewWindowEnd: fc.WindowEnd, NewValidProofOutputs: fc.ValidProofOutputs, NewMissedProofOutputs: fc.MissedProofOutputs, NewUnlockHash: fc.UnlockHash, } // createRevisionSignature will also perform validation on the result, // returning an error if the renter provided an incorrect signature. revisionTransaction, err := createRevisionSignature(noOpRevision, renterRevisionSignature, hostSK, blockHeight) if err != nil { return nil, types.TransactionSignature{}, types.FileContractID{}, err } // Create and add the storage obligation for this file contract. fullTxn, _ := builder.View() so := storageObligation{ SectorRoots: initialSectorRoots, ContractCost: h.settings.MinContractPrice, LockedCollateral: hostCollateral, PotentialStorageRevenue: hostInitialRevenue, RiskedCollateral: hostInitialRisk, OriginTransactionSet: fullTxnSet, RevisionTransactionSet: []types.Transaction{revisionTransaction}, } // Get a lock on the storage obligation. lockErr := h.managedTryLockStorageObligation(so.id()) if lockErr != nil { build.Critical("failed to get a lock on a brand new storage obligation") return nil, types.TransactionSignature{}, types.FileContractID{}, lockErr } defer func() { if err != nil { h.managedUnlockStorageObligation(so.id()) } }() // addStorageObligation will submit the transaction to the transaction // pool, and will only do so if there was not some error in creating the // storage obligation. If the transaction pool returns a consensus // conflict, wait 30 seconds and try again. err = func() error { // Try adding the storage obligation. If there's an error, wait a few // seconds and try again. Eventually time out. It should be noted that // the storage obligation locking is both crappy and incomplete, and // that I'm not sure how this timeout plays with the overall host // timeouts. // // The storage obligation locks should occur at the highest level, not // just when the actual modification is happening. i := 0 for { h.mu.Lock() err = h.addStorageObligation(so) h.mu.Unlock() if err == nil { return nil } if err != nil && i > 4 { h.log.Println(err) builder.Drop() return err } i++ if build.Release == "standard" { time.Sleep(time.Second * 15) } } }() if err != nil { return nil, types.TransactionSignature{}, types.FileContractID{}, err } // Get the host's transaction signatures from the builder. var hostTxnSignatures []types.TransactionSignature _, _, _, txnSigIndices := builder.ViewAdded() for _, sigIndex := range txnSigIndices { hostTxnSignatures = append(hostTxnSignatures, fullTxn.TransactionSignatures[sigIndex]) } return hostTxnSignatures, revisionTransaction.TransactionSignatures[1], so.id(), 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 }