Commit 81c9a17d authored by David Vorick's avatar David Vorick

add host interaction weight to the hostdb

parent c516b0d1
......@@ -630,6 +630,7 @@ overall.
"ageadjustment": 0.1234,
"burnadjustment": 0.1234,
"collateraladjustment": 23.456,
"interactionadjustment": 0.1234,
"priceadjustment": 0.1234,
"storageremainingadjustment": 0.1234,
"uptimeadjustment": 0.1234,
......
......@@ -277,6 +277,14 @@ overall.
// a point it can be detrimental.
"collateraladjustment": 23.456,
// The multipler that gets applied to a host based on previous interactions
// with the host. A high ratio of successful interactions will improve this
// hosts score, and a high ratio of failed interactions will hurt this
// hosts score. This adjustment helps account for hosts that are on
// unstable connections, don't keep their wallets unlocked, ran out of
// funds, etc.
"interactionadjustment": 0.1234,
// The multiplier that gets applied to a host based on the host's price.
// Lower prices are almost always better. Below a certain, very low price,
// there is no advantage.
......
......@@ -98,10 +98,10 @@ type HostDBEntry struct {
HistoricUptime time.Duration `json:"historicuptime"`
ScanHistory HostDBScans `json:"scanhistory"`
HistoricSuccessfulInteractions uint64 `json:"historicSuccessfulInteractions"`
HistoricFailedInteractions uint64 `json:"historicFailedInteractions"`
RecentSuccessfulInteractions uint64 `json:"recentSuccessfulInteractions"`
HistoricSuccessfulInteractions uint64 `json:"historicSuccessfulInteractions"`
RecentFailedInteractions uint64 `json:"recentFailedInteractions"`
RecentSuccessfulInteractions uint64 `json:"recentSuccessfulInteractions"`
LastHistoricUpdate types.BlockHeight
......@@ -130,6 +130,7 @@ type HostScoreBreakdown struct {
AgeAdjustment float64 `json:"ageadjustment"`
BurnAdjustment float64 `json:"burnadjustment"`
CollateralAdjustment float64 `json:"collateraladjustment"`
InteractionAdjustment float64 `json:"interactionadjustment"`
PriceAdjustment float64 `json:"pricesmultiplier"`
StorageRemainingAdjustment float64 `json:"storageremainingadjustment"`
UptimeAdjustment float64 `json:"uptimeadjustment"`
......
......@@ -7,32 +7,45 @@ import (
)
const (
// historicInteractionDecay defines the decay of the HistoricSuccessfulInteractions
// and HistoricFailedInteractions after every block for a host entry.
historicInteractionDecay = 0.9995
// historicInteractionDecalLimit defines the number of historic
// interactions required before decay is applied.
historicInteractionDecayLimit = 500
// hostRequestTimeout indicates how long a host has to respond to a dial.
hostRequestTimeout = 2 * time.Minute
// hostScanDeadline indicates how long a host has to complete an entire
// scan.
hostScanDeadline = 4 * time.Minute
// maxHostDowntime specifies the maximum amount of time that a host is
// allowed to be offline while still being in the hostdb.
maxHostDowntime = 10 * 24 * time.Hour
// minScans specifies the number of scans that a host should have before the
// scans start getting compressed.
minScans = 12
// maxSettingsLen indicates how long in bytes the host settings field is
// allowed to be before being ignored as a DoS attempt.
maxSettingsLen = 10e3
// hostRequestTimeout indicates how long a host has to respond to a dial.
hostRequestTimeout = 2 * time.Minute
// minScans specifies the number of scans that a host should have before the
// scans start getting compressed.
minScans = 12
// hostScanDeadline indicates how long a host has to complete an entire
// scan.
hostScanDeadline = 4 * time.Minute
// recentInteractionWeightLimit caps the number of recent interactions as a
// percentage of the historic interactions, to be certain that a large
// amount of activity in a short period of time does not overwhelm the
// score for a host.
//
// Non-stop heavy interactions for half a day can result in gaining more
// than half the total weight at this limit.
recentInteractionWeightLimit = 0.01
// saveFrequency defines how frequently the hostdb will save to disk. Hostdb
// will also save immediately prior to shutdown.
saveFrequency = 2 * time.Minute
// historicInteractionDecay defines the decay of the HistoricSuccessfulInteractions
// and HistoricFailedInteractions after every block
historicInteractionDecay = 0.999
)
var (
......
......@@ -7,7 +7,6 @@ package hostdb
import (
"errors"
"fmt"
"math"
"os"
"path/filepath"
"sync"
......@@ -184,50 +183,6 @@ func newHostDB(g modules.Gateway, cs modules.ConsensusSet, persistDir string, de
return hdb, nil
}
// updateHostDBEntry updates a HostDBEntries's historic interactions if more
// than one block passed since the last update. This should be called every time
// before the recent interactions are updated. if passedTime is e.g. 10, this
// means that the recent interactions were updated 10 blocks ago but never
// since. So we need to apply the decay of 1 block before we append the recent
// interactions from 10 blocks ago and then apply the decay of 9 more blocks in
// which the recent interactions have been 0
func updateHostHistoricInteractions(host *modules.HostDBEntry, bh types.BlockHeight) {
passedTime := bh - host.LastHistoricUpdate
if passedTime == 0 {
// no time passed. nothing to do.
return
}
// tmp float64 values for more accurate decay
hsi := float64(host.HistoricSuccessfulInteractions)
hfi := float64(host.HistoricFailedInteractions)
// Apply the decay of a single block
decay := historicInteractionDecay
hsi *= decay
hfi *= decay
// Apply the recent interactions of that single block
hsi += float64(host.RecentSuccessfulInteractions)
hfi += float64(host.RecentFailedInteractions)
// Apply the decay of the rest of the blocks
if passedTime > 1 {
decay = math.Pow(historicInteractionDecay, float64(passedTime-1))
hsi *= decay
hfi *= decay
}
// Set new values
host.HistoricSuccessfulInteractions = uint64(hsi)
host.HistoricFailedInteractions = uint64(hfi)
host.RecentSuccessfulInteractions = 0
host.RecentFailedInteractions = 0
// Update the time of the last update
host.LastHistoricUpdate = bh
}
// ActiveHosts returns a list of hosts that are currently online, sorted by
// weight.
func (hdb *HostDB) ActiveHosts() (activeHosts []modules.HostDBEntry) {
......@@ -283,44 +238,3 @@ func (hdb *HostDB) Host(spk types.SiaPublicKey) (modules.HostDBEntry, bool) {
func (hdb *HostDB) RandomHosts(n int, excludeKeys []types.SiaPublicKey) []modules.HostDBEntry {
return hdb.hostTree.SelectRandom(n, excludeKeys)
}
// IncrementSuccessfulInteractions increments the number of successful
// interactions with a host for a given key
func (hdb *HostDB) IncrementSuccessfulInteractions(key types.SiaPublicKey) {
hdb.mu.Lock()
defer hdb.mu.Unlock()
// Fetch the host.
host, haveHost := hdb.hostTree.Select(key)
if !haveHost {
return
}
// Update historic values if necessary
updateHostHistoricInteractions(&host, hdb.blockHeight)
// Increment the successful interactions
host.RecentSuccessfulInteractions++
hdb.hostTree.Modify(host)
}
// IncrementFailedInteractions increments the number of failed interactions with
// a host for a given key
func (hdb *HostDB) IncrementFailedInteractions(key types.SiaPublicKey) {
hdb.mu.Lock()
defer hdb.mu.Unlock()
// Fetch the host.
host, haveHost := hdb.hostTree.Select(key)
if !haveHost || !hdb.online {
// If we are offline it probably wasn't the host's fault
return
}
// Update historic values if necessary
updateHostHistoricInteractions(&host, hdb.blockHeight)
// Increment the failed interactions
host.RecentFailedInteractions++
hdb.hostTree.Modify(host)
}
package hostdb
import (
"math"
"github.com/NebulousLabs/Sia/modules"
"github.com/NebulousLabs/Sia/types"
)
// updateHostDBEntry updates a HostDBEntries's historic interactions if more
// than one block passed since the last update. This should be called every time
// before the recent interactions are updated. if passedTime is e.g. 10, this
// means that the recent interactions were updated 10 blocks ago but never
// since. So we need to apply the decay of 1 block before we append the recent
// interactions from 10 blocks ago and then apply the decay of 9 more blocks in
// which the recent interactions have been 0
func updateHostHistoricInteractions(host *modules.HostDBEntry, bh types.BlockHeight) {
passedTime := bh - host.LastHistoricUpdate
if passedTime == 0 {
// no time passed. nothing to do.
return
}
// tmp float64 values for more accurate decay
hsi := float64(host.HistoricSuccessfulInteractions)
hfi := float64(host.HistoricFailedInteractions)
// Apply the decay of a single block, but only if there are more than
// historicInteractionDecalyLimit datapoints total.
if hsi+hfi > historicInteractionDecayLimit {
decay := historicInteractionDecay
hsi *= decay
hfi *= decay
}
// Apply the recent interactions of that single block. Recent interactions
// cannot represent more than recentInteractionWeightLimit of historic
// interactions, unless there are less than historicInteractionDecayLimit
// total interactions, and then the recent interactions cannot count for
// more than recentInteractionWeightLimit of the decay limit.
rsi := float64(host.RecentSuccessfulInteractions)
rfi := float64(host.RecentFailedInteractions)
if hsi+hfi > historicInteractionDecayLimit {
if rsi+rfi > recentInteractionWeightLimit*(hsi+hfi) {
adjustment := recentInteractionWeightLimit * (hsi + hfi) / (rsi + rfi)
rsi *= adjustment
rfi *= adjustment
}
} else {
if rsi+rfi > recentInteractionWeightLimit*historicInteractionDecayLimit {
adjustment := recentInteractionWeightLimit * historicInteractionDecayLimit / (rsi + rfi)
rsi *= adjustment
rfi *= adjustment
}
}
hsi += rsi
hfi += rfi
// Apply the decay of the rest of the blocks
if passedTime > 1 && hsi+hfi > historicInteractionDecayLimit {
decay := math.Pow(historicInteractionDecay, float64(passedTime-1))
hsi *= decay
hfi *= decay
}
// Set new values
host.HistoricSuccessfulInteractions = uint64(hsi)
host.HistoricFailedInteractions = uint64(hfi)
host.RecentSuccessfulInteractions = 0
host.RecentFailedInteractions = 0
// Update the time of the last update
host.LastHistoricUpdate = bh
}
// IncrementSuccessfulInteractions increments the number of successful
// interactions with a host for a given key
func (hdb *HostDB) IncrementSuccessfulInteractions(key types.SiaPublicKey) {
hdb.mu.Lock()
defer hdb.mu.Unlock()
// Fetch the host.
host, haveHost := hdb.hostTree.Select(key)
if !haveHost {
return
}
// Update historic values if necessary
updateHostHistoricInteractions(&host, hdb.blockHeight)
// Increment the successful interactions
host.RecentSuccessfulInteractions++
hdb.hostTree.Modify(host)
}
// IncrementFailedInteractions increments the number of failed interactions with
// a host for a given key
func (hdb *HostDB) IncrementFailedInteractions(key types.SiaPublicKey) {
hdb.mu.Lock()
defer hdb.mu.Unlock()
// Fetch the host.
host, haveHost := hdb.hostTree.Select(key)
if !haveHost || !hdb.online {
// If we are offline it probably wasn't the host's fault
return
}
// Update historic values if necessary
updateHostHistoricInteractions(&host, hdb.blockHeight)
// Increment the failed interactions
host.RecentFailedInteractions++
hdb.hostTree.Modify(host)
}
......@@ -92,6 +92,33 @@ func (hdb *HostDB) collateralAdjustments(entry modules.HostDBEntry) float64 {
return weight
}
// interactionAdjustments determine the penalty to be applied to a host for the
// historic and currnet interactions with that host. This function focuses on
// historic interactions and ignores recent interactions.
func (hdb *HostDB) interactionAdjustments(entry modules.HostDBEntry) float64 {
// Give the host a baseline of 30 successful interactions and two failed
// interactions. This gives the host a baseline if we've had few
// interactions with them. The two failed interactions will become
// irrelevant after sufficient interactions with the host.
hsi := entry.HistoricSuccessfulInteractions + 30
hfi := entry.HistoricFailedInteractions + 2
// Determine the intraction ratio based off of the historic interactions.
ratio := float64(hsi) / float64(hsi+hfi)
// Raise the ratio to the 6th power and return that.
//
// 98% = 0.885
// 95% = 0.735
// 93% = 0.646 - this is roughly the penalty applied to fresh hosts
// 90% = 0.531
// 85% = 0.377
// 75% = 0.177
// 60% = 0.046
// 50% = 0.015
return math.Pow(ratio, 6)
}
// priceAdjustments will adjust the weight of the entry according to the prices
// that it has set.
func (hdb *HostDB) priceAdjustments(entry modules.HostDBEntry) float64 {
......@@ -345,14 +372,16 @@ func (hdb *HostDB) uptimeAdjustments(entry modules.HostDBEntry) float64 {
// the host database entry.
func (hdb *HostDB) calculateHostWeight(entry modules.HostDBEntry) types.Currency {
collateralReward := hdb.collateralAdjustments(entry)
interactionPenalty := hdb.interactionAdjustments(entry)
lifetimePenalty := hdb.lifetimeAdjustments(entry)
pricePenalty := hdb.priceAdjustments(entry)
storageRemainingPenalty := storageRemainingAdjustments(entry)
versionPenalty := versionAdjustments(entry)
lifetimePenalty := hdb.lifetimeAdjustments(entry)
uptimePenalty := hdb.uptimeAdjustments(entry)
versionPenalty := versionAdjustments(entry)
// Combine the adjustments.
fullPenalty := collateralReward * pricePenalty * storageRemainingPenalty * versionPenalty * lifetimePenalty * uptimePenalty
fullPenalty := collateralReward * interactionPenalty * lifetimePenalty *
pricePenalty * storageRemainingPenalty * uptimePenalty * versionPenalty
// Return a types.Currency.
weight := baseWeight.MulFloat(fullPenalty)
......@@ -384,17 +413,22 @@ func (hdb *HostDB) calculateConversionRate(score types.Currency) float64 {
// EstimateHostScore takes a HostExternalSettings and returns the estimated
// score of that host in the hostdb, assuming no penalties for age or uptime.
func (hdb *HostDB) EstimateHostScore(entry modules.HostDBEntry) modules.HostScoreBreakdown {
// Grab the adjustments. Age, and uptime penalties are set to '1', to
// assume best behavior from the host.
collateralReward := hdb.collateralAdjustments(entry)
pricePenalty := hdb.priceAdjustments(entry)
storageRemainingPenalty := storageRemainingAdjustments(entry)
versionPenalty := versionAdjustments(entry)
fullPenalty := collateralReward * pricePenalty * storageRemainingPenalty * versionPenalty
// Combine into a full penalty, then determine the resulting estimated
// score.
fullPenalty := collateralReward * pricePenalty * storageRemainingPenalty * versionPenalty
estimatedScore := baseWeight.MulFloat(fullPenalty)
if estimatedScore.IsZero() {
estimatedScore = types.NewCurrency64(1)
}
// Compile the estimates into a host score breakdown.
return modules.HostScoreBreakdown{
Score: estimatedScore,
ConversionRate: hdb.calculateConversionRate(estimatedScore),
......@@ -423,6 +457,7 @@ func (hdb *HostDB) ScoreBreakdown(entry modules.HostDBEntry) modules.HostScoreBr
AgeAdjustment: hdb.lifetimeAdjustments(entry),
BurnAdjustment: 1,
CollateralAdjustment: hdb.collateralAdjustments(entry),
InteractionAdjustment: hdb.interactionAdjustments(entry),
PriceAdjustment: hdb.priceAdjustments(entry),
StorageRemainingAdjustment: storageRemainingAdjustments(entry),
UptimeAdjustment: hdb.uptimeAdjustments(entry),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment