Commit c14698e5 authored by Luke Champine's avatar Luke Champine

implement new host announcements

parent 4b20cfaf
......@@ -32,8 +32,8 @@ type Host struct {
wallet modules.Wallet
latestBlock consensus.BlockID
// our HostEntry settings, embedded for convenience
modules.HostEntry
// our HostSettings, embedded for convenience
modules.HostSettings
hostDir string
spaceRemaining int64
......@@ -63,7 +63,7 @@ func New(state *consensus.State, wallet modules.Wallet) (h *Host, err error) {
state: state,
wallet: wallet,
HostEntry: modules.HostEntry{
HostSettings: modules.HostSettings{
MaxFilesize: 4 * 1000 * 1000,
MaxDuration: 1008, // One week.
MinWindow: 20,
......@@ -119,16 +119,16 @@ func (h *Host) RetrieveFile(conn net.Conn) (err error) {
return
}
// SetConfig updates the host's internal HostEntry object. To modify
// a specific field, use a combination of Info and SetAnnouncement.
func (h *Host) SetConfig(entry modules.HostEntry) {
// SetConfig updates the host's internal HostSettings object. To modify
// a specific field, use a combination of Info and SetConfig
func (h *Host) SetConfig(settings modules.HostSettings) {
h.mu.Lock()
defer h.mu.Unlock()
h.HostEntry = entry
h.HostSettings = settings
}
type HostInfo struct {
modules.HostEntry
modules.HostSettings
StorageRemaining int64
ContractCount int
......@@ -139,7 +139,7 @@ func (h *Host) Info() HostInfo {
defer h.mu.RUnlock()
info := HostInfo{
HostEntry: h.HostEntry,
HostSettings: h.HostSettings,
StorageRemaining: h.spaceRemaining,
ContractCount: len(h.contracts),
......
......@@ -6,13 +6,26 @@ import (
)
const (
HostAnnouncementPrefix = 1
// Denotes a host announcement in the Arbitrary Data section.
HostAnnouncementPrefix = "HostAnnouncement"
)
// A HostEntry contains information about a host on the network. The HostDB
// uses this information to select an optimal host.
type HostEntry struct {
IPAddress network.Address
// HostAnnouncements are stored in the Arbitrary Data section of transactions
// on the blockchain. They announce the willingness of a node to host files.
// Renters can contact the host privately to obtain more detailed hosting
// parameters (see HostSettings). To mitigate Sybil attacks, HostAnnouncements
// are paired with a volume of 'frozen' coins. The FreezeIndex indicates which
// output in the transaction contains the frozen coins.
type HostAnnouncement struct {
IPAddress network.Address
FreezeIndex uint64 // the index of the output that froze coins
SpendConditions consensus.SpendConditions
}
// HostSettings are the parameters advertised by the host. These are the
// values that the HostDB will request from the host in order to build its
// database.
type HostSettings struct {
TotalStorage int64 // Can go negative.
MinFilesize uint64
MaxFilesize uint64
......@@ -21,10 +34,16 @@ type HostEntry struct {
MinWindow consensus.BlockHeight
Price consensus.Currency
Burn consensus.Currency
Freeze consensus.Currency
CoinAddress consensus.CoinAddress // Host may want to give different addresses to each client.
CoinAddress consensus.CoinAddress
}
SpendConditions consensus.SpendConditions
// A HostEntry is an entry in the HostDB. It contains the HostSettings, as
// well as the IP address where the host can be found, and the value of the
// coins frozen in the host's announcement transaction.
type HostEntry struct {
HostSettings
IPAddress network.Address
Freeze consensus.Currency
}
type HostDB interface {
......
package hostdb
import (
"strings"
"github.com/NebulousLabs/Sia/consensus"
"github.com/NebulousLabs/Sia/encoding"
"github.com/NebulousLabs/Sia/modules"
)
// findHostAnnouncements scans a block and pulls out every host announcement
// that appears in the block, returning a list of entries that correspond with
// the announcements.
func findHostAnnouncements(height consensus.BlockHeight, b consensus.Block) (entries []modules.HostEntry, err error) {
// findHostAnnouncements returns a list of the host announcements found within
// a given block.
func findHostAnnouncements(height consensus.BlockHeight, b consensus.Block) (announcements []modules.HostEntry) {
for _, t := range b.Transactions {
// Check the arbitrary data of the transaction to fill out the host database.
if len(t.ArbitraryData) == 0 {
continue
}
if len(t.ArbitraryData[0]) < 8 {
continue
}
for _, data := range t.ArbitraryData {
// the HostAnnouncement must be prefaced by the standard host announcement string
if !strings.HasPrefix(data, modules.HostAnnouncementPrefix) {
continue
}
// TODO: switch dataIndicator
// TODO: new announcement struct
dataIndicator := encoding.DecUint64([]byte(t.ArbitraryData[0][0:8]))
if dataIndicator == modules.HostAnnouncementPrefix {
var entry modules.HostEntry
err = encoding.Unmarshal([]byte(t.ArbitraryData[0][8:]), &entry)
// decode the HostAnnouncement
var ha modules.HostAnnouncement
encAnnouncement := []byte(strings.TrimPrefix(data, modules.HostAnnouncementPrefix))
err := encoding.Unmarshal(encAnnouncement, &ha)
if err != nil {
return
continue
}
// check that spend conditions are valid
if ha.SpendConditions.CoinAddress() != t.Outputs[ha.FreezeIndex].SpendHash {
continue
}
// Verify that the host has declared values that are relevant to our
// interests.
// TODO: need a way to get the freeze index
/*
if entry.SpendConditions.CoinAddress() != t.Outputs[entry.FreezeIndex].SpendHash {
continue
}
entry.Freeze = consensus.Currency(entry.SpendConditions.TimeLock-height) * t.Outputs[entry.FreezeIndex].Value
if entry.Freeze <= 0 {
continue
}
*/
entries = append(entries, entry)
// calculate freeze
freeze := consensus.Currency(ha.SpendConditions.TimeLock-height) * t.Outputs[ha.FreezeIndex].Value
// check for sane freeze value
if freeze <= 0 {
continue
}
// At this point, the HostSettings are unknown. Before inserting
// the host, the HostDB will call threadedInsertFromAnnouncement
// to fill out the HostSettings.
announcements = append(announcements, modules.HostEntry{
IPAddress: ha.IPAddress,
Freeze: freeze,
})
}
}
return
}
// threadedInsertFromAnnouncement requests a host's hosting parameters, and inserts
// the resulting HostEntry into the database.
func (hdb *HostDB) threadedInsertFromAnnouncement(entry modules.HostEntry) {
err := entry.IPAddress.RPC("HostSettings", nil, &entry.HostSettings)
if err != nil {
return
}
hdb.Insert(entry)
}
......@@ -83,10 +83,7 @@ func (hdb *HostDB) FlagHost(addr network.Address) error {
}
// Remove deletes an entry from the hostdb.
func (hdb *HostDB) Remove(addr network.Address) error {
hdb.mu.Lock()
defer hdb.mu.Unlock()
func (hdb *HostDB) remove(addr network.Address) error {
// See if the node is in the set of active hosts.
node, exists := hdb.activeHosts[addr]
if !exists {
......@@ -108,42 +105,51 @@ func (hdb *HostDB) Remove(addr network.Address) error {
return nil
}
// Update throws a bunch of blocks at the hostdb to be integrated.
func (hdb *HostDB) Update(initialStateHeight consensus.BlockHeight, rewoundBlocks []consensus.Block, appliedBlocks []consensus.Block) (err error) {
func (hdb *HostDB) Remove(addr network.Address) error {
hdb.mu.Lock()
defer hdb.mu.Unlock()
return hdb.remove(addr)
}
// Remove hosts found in blocks that were rewound. Because the hostdb is
// like a stack, you can just pop the hosts and be certain that they are
// the same hosts.
for _, b := range rewoundBlocks {
var entries []modules.HostEntry
entries, err = findHostAnnouncements(initialStateHeight, b)
if err != nil {
return
// Update throws a bunch of blocks at the hostdb to be integrated.
func (hdb *HostDB) update() (err error) {
hdb.state.RLock()
initialStateHeight := hdb.state.Height()
rewoundBlocks, appliedBlocks, err := hdb.state.BlocksSince(hdb.recentBlock)
if err != nil {
// TODO: this may be a serious problem; if recentBlock is not updated,
// will BlocksSince always return an error?
hdb.state.RUnlock()
return
}
hdb.recentBlock = hdb.state.CurrentBlock().ID()
hdb.state.RUnlock()
// Remove hosts announced in blocks that were rewound.
for _, blockID := range rewoundBlocks {
block, exists := hdb.state.Block(blockID)
if !exists {
continue
}
for _, entry := range entries {
err = hdb.Remove(entry.IPAddress)
for _, entry := range findHostAnnouncements(initialStateHeight, block) {
err = hdb.remove(entry.IPAddress)
if err != nil {
return
}
}
}
// Add hosts found in blocks that were applied.
for _, b := range appliedBlocks {
var entries []modules.HostEntry
entries, err = findHostAnnouncements(initialStateHeight, b)
if err != nil {
return
// Add hosts announced in blocks that were applied. For security reasons,
// the announcements themselves do not contain hosting parameters; we must
// request these separately, using the address given in the announcement.
// Each such request is made in a separate thread.
for _, blockID := range appliedBlocks {
block, exists := hdb.state.Block(blockID)
if !exists {
continue
}
for _, entry := range entries {
err = hdb.insert(entry)
if err != nil {
return
}
for _, entry := range findHostAnnouncements(initialStateHeight, block) {
go hdb.threadedInsertFromAnnouncement(entry)
}
}
......
......@@ -7,7 +7,7 @@ import (
func (d *daemon) hostConfigHandler(w http.ResponseWriter, req *http.Request) {
// load current settings
config := d.host.Info().HostEntry
config := d.host.Info().HostSettings
// map each query string to a field in the host announcement object
qsVars := map[string]interface{}{
......
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