This repository has been archived by the owner. It is now read-only.
Permalink
Browse files

add (untested) logic to switch away from bad hosts

  • Loading branch information...
DavidVorick committed Jul 5, 2017
1 parent 19541c8 commit 59105d8a4a5a845f22977072ee2b4f3945d37bc2
View
@@ -224,6 +224,11 @@ type RenterContract struct {
ContractFee types.Currency `json:"contractfee"`
TxnFee types.Currency `json:"txnfee"`
SiafundFee types.Currency `json:"siafundfee"`
// GoodForUpload indicates whether the contract should be used to upload new
// data or not.
GoodForUpload bool
GoodForRenew bool
}
// EndHeight returns the height at which the host is no longer obligated to
@@ -55,4 +55,11 @@ var (
maxDownloadPrice = maxStoragePrice.Mul64(3 * 4320)
maxStoragePrice = types.SiacoinPrecision.Mul64(30e3).Div(modules.BlockBytesPerMonthTerabyte) // 30k SC / TB / Month
maxUploadPrice = maxStoragePrice.Mul64(4320)
// scoreLeeway defines the factor by which a host can miss the goal score
// for a set of hosts. To determine the goal score, a new set of hosts is
// queried from the hostdb and the lowest scoring among them is selected.
// That score is then divided by scoreLeeway to get the minimum score that a
// host is allowed to have before being marked as !GoodForUpload.
scoreLeeway = types.NewCurrency64(25)
)
@@ -34,9 +34,12 @@ func (newStub) FeeEstimation() (a types.Currency, b types.Currency) { return }
func (newStub) AllHosts() []modules.HostDBEntry { return nil }
func (newStub) ActiveHosts() []modules.HostDBEntry { return nil }
func (newStub) Host(types.SiaPublicKey) (settings modules.HostDBEntry, ok bool) { return }
func (newStub) RandomHosts(int, []types.SiaPublicKey) []modules.HostDBEntry { return nil }
func (newStub) IncrementSuccessfulInteractions(key types.SiaPublicKey) { return }
func (newStub) IncrementFailedInteractions(key types.SiaPublicKey) { return }
func (newStub) RandomHosts(int, []types.SiaPublicKey) []modules.HostDBEntry { return nil }
func (newStub) ScoreBreakdown(modules.HostDBEntry) modules.HostScoreBreakdown {
return modules.HostScoreBreakdown{}
}
// TestNew tests the New function.
func TestNew(t *testing.T) {
@@ -187,10 +190,13 @@ type stubHostDB struct{}
func (stubHostDB) AllHosts() (hs []modules.HostDBEntry) { return }
func (stubHostDB) ActiveHosts() (hs []modules.HostDBEntry) { return }
func (stubHostDB) Host(types.SiaPublicKey) (h modules.HostDBEntry, ok bool) { return }
func (stubHostDB) PublicKey() (spk types.SiaPublicKey) { return }
func (stubHostDB) RandomHosts(int, []types.SiaPublicKey) (hs []modules.HostDBEntry) { return }
func (stubHostDB) IncrementSuccessfulInteractions(key types.SiaPublicKey) { return }
func (stubHostDB) IncrementFailedInteractions(key types.SiaPublicKey) { return }
func (stubHostDB) PublicKey() (spk types.SiaPublicKey) { return }
func (stubHostDB) RandomHosts(int, []types.SiaPublicKey) (hs []modules.HostDBEntry) { return }
func (stubHostDB) ScoreBreakdown(modules.HostDBEntry) modules.HostScoreBreakdown {
return modules.HostScoreBreakdown{}
}
// TestIntegrationSetAllowance tests the SetAllowance method.
func TestIntegrationSetAllowance(t *testing.T) {
@@ -83,6 +83,104 @@ func (c *Contractor) contractEndHeight() types.BlockHeight {
return endHeight
}
// managedMarkContractsUtility checks every active contract in the contractor and
// figures out whether the contract is useful for uploading, and whehter the
// contract should be renewed.
func (c *Contractor) managedMarkContractsUtility() {
// Pull a new set of hosts from the hostdb that could be used as a new set
// to match the allowance. The lowest scoring host of these new hosts will
// be used as a baseline for determining whether our existing contracts are
// worthwhile.
c.mu.RLock()
hostCount := int(c.allowance.Hosts)
c.mu.RUnlock()
hosts := c.hdb.RandomHosts(hostCount+5, nil)
// Find the lowest score of this batch of hosts.
if len(hosts) <= 0 {
return
}
lowestScore := c.hdb.ScoreBreakdown(hosts[0]).Score
for i := 1; i < len(hosts); i++ {
score := c.hdb.ScoreBreakdown(hosts[i]).Score
if score.Cmp(lowestScore) < 0 {
lowestScore = score
}
}
// Set the minimum acceptable score to a factor of the lowest score.
minScore := lowestScore.Div(scoreLeeway)
// Pull together the set of contracts.
c.mu.RLock()
contracts := make([]modules.RenterContract, 0, len(c.contracts))
for _, contract := range c.contracts {
contracts = append(contracts, contract)
}
c.mu.RUnlock()
// Go through and figure out if the utility fields need to be changed.
for i := 0; i < len(contracts); i++ {
// Start the contract in good standing.
contracts[i].GoodForUpload = true
contracts[i].GoodForRenew = true
host, exists := c.hdb.Host(contracts[i].HostPublicKey)
// Contract has no utility if the host is not in the database.
if !exists {
contracts[i].GoodForUpload = false
contracts[i].GoodForRenew = false
continue
}
// Contract has no utility if the score is poor.
if c.hdb.ScoreBreakdown(host).Score.Cmp(minScore) < 0 {
contracts[i].GoodForUpload = false
contracts[i].GoodForRenew = false
continue
}
// Contract has no utility if the host is offline.
c.mu.Lock()
offline := c.isOffline(contracts[i].ID)
c.mu.Unlock()
if offline {
contracts[i].GoodForUpload = false
contracts[i].GoodForRenew = false
continue
}
// Contract should not be used for upload if the number of Merkle roots
// exceeds 25e3 - this is in place because the current hosts do not
// really perform well beyond this number of sectors in a single
// contract. Future updates will fix this, at which point this limit
// will change and also have to switch based on host version.
if len(contracts[i].MerkleRoots) > 25e3 {
// Contract is still fine to be renewed, we just shouldn't keep
// adding data to this contract.
contracts[i].GoodForUpload = false
}
// Contract should not be used for uploading if the time has come to
// renew the contract.
c.mu.RLock()
if c.blockHeight+c.allowance.RenewWindow >= contracts[i].EndHeight() {
contracts[i].GoodForUpload = false
}
c.mu.RUnlock()
}
// Update the contractor to reflect the new state for each of the contracts.
c.mu.Lock()
for i := 0; i < len(contracts); i++ {
contract, exists := c.contracts[contracts[i].ID]
if !exists {
continue
}
contract.GoodForUpload = contracts[i].GoodForUpload
contract.GoodForRenew = contracts[i].GoodForRenew
c.contracts[contracts[i].ID] = contract
}
c.mu.Unlock()
}
// managedNewContract negotiates an initial file contract with the specified
// host, saves it, and returns it.
func (c *Contractor) managedNewContract(host modules.HostDBEntry, numSectors uint64, endHeight types.BlockHeight) (modules.RenterContract, error) {
@@ -207,23 +305,27 @@ func (c *Contractor) threadedContractMaintenance() {
}
defer c.maintenanceLock.Unlock()
// Update the utility fields for this contract based on the most recent
// hostdb.
c.managedMarkContractsUtility()
// Renew any contracts that need to be renewed.
//
// TODO: Include checking if the contract needs more money.
//
// TODO: Add the IsGoodForRenew logic.
c.mu.Lock()
c.mu.RLock()
var renewSet []types.FileContractID
for _, contract := range c.onlineContracts() {
if c.blockHeight+c.allowance.RenewWindow >= contract.EndHeight() {
for _, contract := range c.contracts {
if contract.GoodForRenew && c.blockHeight+c.allowance.RenewWindow >= contract.EndHeight() {
renewSet = append(renewSet, contract.ID)
}
}
c.mu.Unlock()
c.mu.RUnlock()
if len(renewSet) != 0 {
c.log.Printf("renewing %v contracts", len(renewSet))
}
// TODO: Need some loop somewhere that renews contracts which haven't gone
// through the full period yet, but are out of money (yet the allowance
// still has room to refill some contracts)
// Figure out the end height and target sector count for the contracts being
// renewed.
//
@@ -331,13 +433,18 @@ func (c *Contractor) threadedContractMaintenance() {
default:
}
// Count the number of contracts, and add more where they are needed.
//
// TODO: Use the IsGoodForUpload logic.
c.mu.Lock()
neededContracts := int(c.allowance.Hosts) - len(c.onlineContracts())
c.mu.Unlock()
// Nothing to do if there are no needed contracts.
// Count the number of contracts which are good for uploading, and then make
// more as needed to fill the gap.
// Renew any contracts that need to be renewed.
c.mu.RLock()
uploadContracts := 0
for _, contract := range c.contracts {
if contract.GoodForUpload || (contract.GoodForRenew && c.blockHeight+c.allowance.RenewWindow >= contract.EndHeight()) {
uploadContracts++
}
}
neededContracts := int(c.allowance.Hosts) - uploadContracts
c.mu.RUnlock()
if neededContracts <= 0 {
return
}
@@ -356,7 +463,7 @@ func (c *Contractor) threadedContractMaintenance() {
// Form contracts with the hosts one at a time, until we have enough
// contracts.
for _, host := range hosts {
// Attempt forming a contract with htis host.
// Attempt forming a contract with this host.
newContract, err := c.managedNewContract(host, numSectors, endHeight)
if err != nil {
c.log.Printf("Attempted to form a contract with %v, but if failed: %v\n", host.NetAddress, err)
@@ -49,9 +49,10 @@ type (
AllHosts() []modules.HostDBEntry
ActiveHosts() []modules.HostDBEntry
Host(types.SiaPublicKey) (modules.HostDBEntry, bool)
RandomHosts(n int, exclude []types.SiaPublicKey) []modules.HostDBEntry
IncrementSuccessfulInteractions(key types.SiaPublicKey)
IncrementFailedInteractions(key types.SiaPublicKey)
RandomHosts(n int, exclude []types.SiaPublicKey) []modules.HostDBEntry
ScoreBreakdown(modules.HostDBEntry) modules.HostScoreBreakdown
}
persister interface {
@@ -412,6 +412,9 @@ func (hdb *HostDB) EstimateHostScore(entry modules.HostDBEntry) modules.HostScor
// ScoreBreakdown provdes a detailed set of scalars and bools indicating
// elements of the host's overall score.
func (hdb *HostDB) ScoreBreakdown(entry modules.HostDBEntry) modules.HostScoreBreakdown {
hdb.mu.Lock()
defer hdb.mu.Unlock()
score := hdb.calculateHostWeight(entry)
return modules.HostScoreBreakdown{
Score: score,

0 comments on commit 59105d8

Please sign in to comment.