// managedVerifyRenewedContract checks that the contract renewal matches the // previous contract and makes all of the appropriate payments. func (h *Host) managedVerifyRenewedContract(so storageObligation, txnSet []types.Transaction, renterPK crypto.PublicKey) error { // Check that the transaction set is not empty. if len(txnSet) < 1 { return extendErr("zero-length transaction set: ", errEmptyObject) } // Check that the transaction set has a file contract. if len(txnSet[len(txnSet)-1].FileContracts) < 1 { return extendErr("transaction without file contract: ", errEmptyObject) } h.mu.RLock() blockHeight := h.blockHeight externalSettings := h.externalSettings() internalSettings := h.settings lockedStorageCollateral := h.financialMetrics.LockedStorageCollateral publicKey := h.publicKey unlockHash := h.unlockHash h.mu.RUnlock() fc := txnSet[len(txnSet)-1].FileContracts[0] // The file size and merkle root must match the file size and merkle root // from the previous file contract. if fc.FileSize != so.fileSize() { return errBadFileSize } if fc.FileMerkleRoot != so.merkleRoot() { return errBadFileMerkleRoot } // The WindowStart must be at least revisionSubmissionBuffer blocks into // the future. if fc.WindowStart <= blockHeight+revisionSubmissionBuffer { return errEarlyWindow } // WindowEnd must be at least settings.WindowSize blocks after WindowStart. if fc.WindowEnd < fc.WindowStart+externalSettings.WindowSize { return errSmallWindow } // ValidProofOutputs shoud have 2 outputs (renter + host) and missed // outputs should have 3 (renter + host + void) if len(fc.ValidProofOutputs) != 2 || len(fc.MissedProofOutputs) != 3 { return errBadContractOutputCounts } // The unlock hashes of the valid and missed proof outputs for the host // must match the host's unlock hash. The third missed output should point // to the void. if fc.ValidProofOutputs[1].UnlockHash != unlockHash || fc.MissedProofOutputs[1].UnlockHash != unlockHash || fc.MissedProofOutputs[2].UnlockHash != (types.UnlockHash{}) { return errBadPayoutUnlockHashes } // Check that the collateral does not exceed the maximum amount of // collateral allowed. expectedCollateral := renewContractCollateral(so, externalSettings, fc) if expectedCollateral.Cmp(externalSettings.MaxCollateral) > 0 { return errMaxCollateralReached } // Check that the host has enough room in the collateral budget to add this // collateral. if lockedStorageCollateral.Add(expectedCollateral).Cmp(internalSettings.CollateralBudget) > 0 { return errCollateralBudgetExceeded } // Check that the missed proof outputs contain enough money, and that the // void output contains enough money. basePrice := renewBasePrice(so, externalSettings, fc) baseCollateral := renewBaseCollateral(so, externalSettings, fc) if fc.ValidProofOutputs[1].Value.Cmp(basePrice.Add(baseCollateral)) < 0 { return errLowHostValidOutput } expectedHostMissedOutput := fc.ValidProofOutputs[1].Value.Sub(basePrice).Sub(baseCollateral) if fc.MissedProofOutputs[1].Value.Cmp(expectedHostMissedOutput) < 0 { return errLowHostMissedOutput } // Check that the void output has the correct value. expectedVoidOutput := basePrice.Add(baseCollateral) if fc.MissedProofOutputs[2].Value.Cmp(expectedVoidOutput) > 0 { return errLowVoidOutput } // The unlock hash for the file contract must match the unlock hash that // the host knows how to spend. expectedUH := types.UnlockConditions{ PublicKeys: []types.SiaPublicKey{ { Algorithm: types.SignatureEd25519, Key: renterPK[:], }, publicKey, }, SignaturesRequired: 2, }.UnlockHash() if fc.UnlockHash != expectedUH { return errBadUnlockHash } // Check that the transaction set has enough fees on it to get into the // blockchain. setFee := modules.CalculateFee(txnSet) minFee, _ := h.tpool.FeeEstimation() if setFee.Cmp(minFee) < 0 { return errLowTransactionFees } return nil }
// managedVerifyNewContract checks that an incoming file contract matches the host's // expectations for a valid contract. func (h *Host) managedVerifyNewContract(txnSet []types.Transaction, renterPK crypto.PublicKey) error { // Check that the transaction set is not empty. if len(txnSet) < 1 { return extendErr("zero-length transaction set: ", errEmptyObject) } // Check that there is a file contract in the txnSet. if len(txnSet[len(txnSet)-1].FileContracts) < 1 { return extendErr("transaction without file contract: ", errEmptyObject) } h.mu.RLock() blockHeight := h.blockHeight lockedStorageCollateral := h.financialMetrics.LockedStorageCollateral publicKey := h.publicKey settings := h.settings unlockHash := h.unlockHash h.mu.RUnlock() fc := txnSet[len(txnSet)-1].FileContracts[0] // A new file contract should have a file size of zero. if fc.FileSize != 0 { return errBadFileSize } if fc.FileMerkleRoot != (crypto.Hash{}) { return errBadFileMerkleRoot } // WindowStart must be at least revisionSubmissionBuffer blocks into the // future. if fc.WindowStart <= blockHeight+revisionSubmissionBuffer { h.log.Debugf("A renter tried to form a contract that had a window start which was too soon. The contract started at %v, the current height is %v, the revisionSubmissionBuffer is %v, and the comparison was %v <= %v\n", fc.WindowStart, blockHeight, revisionSubmissionBuffer, fc.WindowStart, blockHeight+revisionSubmissionBuffer) return errEarlyWindow } // WindowEnd must be at least settings.WindowSize blocks after // WindowStart. if fc.WindowEnd < fc.WindowStart+settings.WindowSize { return errSmallWindow } // WindowEnd must not be more than settings.MaxDuration blocks into the // future. if fc.WindowStart > blockHeight+settings.MaxDuration { return errLongDuration } // ValidProofOutputs shoud have 2 outputs (renter + host) and missed // outputs should have 3 (renter + host + void) if len(fc.ValidProofOutputs) != 2 || len(fc.MissedProofOutputs) != 3 { return errBadContractOutputCounts } // The unlock hashes of the valid and missed proof outputs for the host // must match the host's unlock hash. The third missed output should point // to the void. if fc.ValidProofOutputs[1].UnlockHash != unlockHash || fc.MissedProofOutputs[1].UnlockHash != unlockHash || fc.MissedProofOutputs[2].UnlockHash != (types.UnlockHash{}) { return errBadPayoutUnlockHashes } // Check that the payouts for the valid proof outputs and the missed proof // outputs are the same - this is important because no data has been added // to the file contract yet. if fc.ValidProofOutputs[1].Value.Cmp(fc.MissedProofOutputs[1].Value) != 0 { return errMismatchedHostPayouts } // Check that there's enough payout for the host to cover at least the // contract price. This will prevent negative currency panics when working // with the collateral. if fc.ValidProofOutputs[1].Value.Cmp(settings.MinContractPrice) < 0 { return errLowHostValidOutput } // Check that the collateral does not exceed the maximum amount of // collateral allowed. expectedCollateral := contractCollateral(settings, fc) if expectedCollateral.Cmp(settings.MaxCollateral) > 0 { return errMaxCollateralReached } // Check that the host has enough room in the collateral budget to add this // collateral. if lockedStorageCollateral.Add(expectedCollateral).Cmp(settings.CollateralBudget) > 0 { return errCollateralBudgetExceeded } // The unlock hash for the file contract must match the unlock hash that // the host knows how to spend. expectedUH := types.UnlockConditions{ PublicKeys: []types.SiaPublicKey{ { Algorithm: types.SignatureEd25519, Key: renterPK[:], }, publicKey, }, SignaturesRequired: 2, }.UnlockHash() if fc.UnlockHash != expectedUH { return errBadUnlockHash } // Check that the transaction set has enough fees on it to get into the // blockchain. setFee := modules.CalculateFee(txnSet) minFee, _ := h.tpool.FeeEstimation() if setFee.Cmp(minFee) < 0 { return errLowTransactionFees } return nil }