// TestRepeatInsert inserts 2 hosts with the same address. func TestRepeatInsert(t *testing.T) { if testing.Short() { t.SkipNow() } hdb := &HostDB{ activeHosts: make(map[modules.NetAddress]*hostNode), allHosts: make(map[modules.NetAddress]*hostEntry), scanPool: make(chan *hostEntry, scanPoolSize), } var dbe modules.HostDBEntry dbe.NetAddress = fakeAddr(0) entry1 := hostEntry{ HostDBEntry: dbe, Weight: types.NewCurrency64(1), } entry2 := entry1 hdb.insertNode(&entry1) entry2.Weight = types.NewCurrency64(100) hdb.insertNode(&entry2) if len(hdb.activeHosts) != 1 { t.Error("insterting the same entry twice should result in only 1 entry in the hostdb") } }
// TestVariedWeights runs broad statistical tests on selecting hosts with // multiple different weights. func TestVariedWeights(t *testing.T) { if testing.Short() { t.SkipNow() } hdb := &HostDB{ activeHosts: make(map[modules.NetAddress]*hostNode), allHosts: make(map[modules.NetAddress]*hostEntry), scanPool: make(chan *hostEntry, scanPoolSize), } // insert i hosts with the weights 0, 1, ..., i-1. 100e3 selections will be made // per weight added to the tree, the total number of selections necessary // will be tallied up as hosts are created. var dbe modules.HostDBEntry dbe.AcceptingContracts = true hostCount := 5 expectedPerWeight := int(10e3) selections := 0 for i := 0; i < hostCount; i++ { dbe.NetAddress = fakeAddr(uint8(i)) entry := hostEntry{ HostDBEntry: dbe, Weight: types.NewCurrency64(uint64(i)), } hdb.insertNode(&entry) selections += i * expectedPerWeight } // Perform many random selections, noting which host was selected each // time. selectionMap := make(map[string]int) for i := 0; i < selections; i++ { randEntry := hdb.RandomHosts(1, nil) if len(randEntry) == 0 { t.Fatal("no hosts!") } node, exists := hdb.activeHosts[randEntry[0].NetAddress] if !exists { t.Fatal("can't find randomly selected node in tree") } selectionMap[node.hostEntry.Weight.String()]++ } // Check that each host was selected an expected number of times. An error // will be reported if the host of 0 weight is ever selected. acceptableError := 0.2 for weight, timesSelected := range selectionMap { intWeight, err := strconv.Atoi(weight) if err != nil { t.Fatal(err) } expectedSelected := float64(intWeight * expectedPerWeight) if float64(expectedSelected)*acceptableError > float64(timesSelected) || float64(expectedSelected)/acceptableError < float64(timesSelected) { t.Error("weighted list not selecting in a uniform distribution based on weight") t.Error(expectedSelected) t.Error(timesSelected) } } }
// TestInsertHost tests the insertHost method, which also depends on the // scanHostEntry method. func TestInsertHost(t *testing.T) { hdb := bareHostDB() // invalid host should not be scanned var dbe modules.HostDBEntry dbe.NetAddress = "foo" hdb.insertHost(dbe) select { case <-hdb.scanPool: t.Error("invalid host was added to scan pool") case <-time.After(100 * time.Millisecond): } // valid host should be scanned dbe.NetAddress = "foo.com:1234" hdb.insertHost(dbe) select { case <-hdb.scanPool: case <-time.After(time.Second): t.Error("host was not scanned") } // duplicate host should not be scanned hdb.insertHost(dbe) select { case <-hdb.scanPool: t.Error("duplicate host was added to scan pool") case <-time.After(100 * time.Millisecond): } }
// findHostAnnouncements returns a list of the host announcements found within // a given block. No check is made to see that the ip address found in the // announcement is actually a valid ip address. func findHostAnnouncements(b types.Block) (announcements []modules.HostDBEntry) { for _, t := range b.Transactions { // the HostAnnouncement must be prefaced by the standard host // announcement string for _, arb := range t.ArbitraryData { addr, pubKey, err := modules.DecodeAnnouncement(arb) if err != nil { continue } // Add the announcement to the slice being returned. var host modules.HostDBEntry host.NetAddress = addr host.PublicKey = pubKey announcements = append(announcements, host) } } return }
// 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) { // reject hosts that are too expensive if host.StoragePrice.Cmp(maxStoragePrice) > 0 { return modules.RenterContract{}, errTooExpensive } // cap host.MaxCollateral if host.MaxCollateral.Cmp(maxCollateral) > 0 { host.MaxCollateral = maxCollateral } // get an address to use for negotiation uc, err := c.wallet.NextAddress() if err != nil { return modules.RenterContract{}, err } // create contract params c.mu.RLock() params := proto.ContractParams{ Host: host, Filesize: numSectors * modules.SectorSize, StartHeight: c.blockHeight, EndHeight: endHeight, RefundAddress: uc.UnlockHash(), } c.mu.RUnlock() // create transaction builder txnBuilder := c.wallet.StartTransaction() contract, err := proto.FormContract(params, txnBuilder, c.tpool) if err != nil { txnBuilder.Drop() return modules.RenterContract{}, err } contractValue := contract.RenterFunds() c.log.Printf("Formed contract with %v for %v SC", host.NetAddress, contractValue.Div(types.SiacoinPrecision)) return contract, nil }
// verifySettings reads a signed HostSettings object from conn, validates the // signature, and checks for discrepancies between the known settings and the // received settings. If there is a discrepancy, the hostDB is notified. The // received settings are returned. func verifySettings(conn net.Conn, host modules.HostDBEntry) (modules.HostDBEntry, error) { // convert host key (types.SiaPublicKey) to a crypto.PublicKey if host.PublicKey.Algorithm != types.SignatureEd25519 || len(host.PublicKey.Key) != crypto.PublicKeySize { build.Critical("hostdb did not filter out host with wrong signature algorithm:", host.PublicKey.Algorithm) return modules.HostDBEntry{}, errors.New("host used unsupported signature algorithm") } var pk crypto.PublicKey copy(pk[:], host.PublicKey.Key) // read signed host settings var recvSettings modules.HostExternalSettings if err := crypto.ReadSignedObject(conn, &recvSettings, modules.NegotiateMaxHostExternalSettingsLen, pk); err != nil { return modules.HostDBEntry{}, errors.New("couldn't read host's settings: " + err.Error()) } // TODO: check recvSettings against host.HostExternalSettings. If there is // a discrepancy, write the error to conn. if recvSettings.NetAddress != host.NetAddress { // for now, just overwrite the NetAddress, since we know that // host.NetAddress works (it was the one we dialed to get conn) recvSettings.NetAddress = host.NetAddress } host.HostExternalSettings = recvSettings return host, nil }
// TestRandomHosts probes the RandomHosts function. func TestRandomHosts(t *testing.T) { // Create the hostdb. hdb := bareHostDB() // Empty. if hosts := hdb.RandomHosts(1, nil); len(hosts) != 0 { t.Errorf("empty hostdb returns %v hosts: %v", len(hosts), hosts) } // Insert 3 hosts to be selected. var dbe modules.HostDBEntry dbe.NetAddress = fakeAddr(1) dbe.AcceptingContracts = true entry1 := hostEntry{ HostDBEntry: dbe, Weight: types.NewCurrency64(1), } dbe.NetAddress = fakeAddr(2) entry2 := hostEntry{ HostDBEntry: dbe, Weight: types.NewCurrency64(2), } dbe.NetAddress = fakeAddr(3) entry3 := hostEntry{ HostDBEntry: dbe, Weight: types.NewCurrency64(3), } hdb.insertNode(&entry1) hdb.insertNode(&entry2) hdb.insertNode(&entry3) if len(hdb.activeHosts) != 3 { t.Error("wrong number of hosts") } if hdb.hostTree.weight.Cmp(types.NewCurrency64(6)) != 0 { t.Error("unexpected weight at initialization") t.Error(hdb.hostTree.weight) } // Grab 1 random host. randHosts := hdb.RandomHosts(1, nil) if len(randHosts) != 1 { t.Error("didn't get 1 hosts") } // Grab 2 random hosts. randHosts = hdb.RandomHosts(2, nil) if len(randHosts) != 2 { t.Error("didn't get 2 hosts") } if randHosts[0].NetAddress == randHosts[1].NetAddress { t.Error("doubled up") } // Grab 3 random hosts. randHosts = hdb.RandomHosts(3, nil) if len(randHosts) != 3 { t.Error("didn't get 3 hosts") } if randHosts[0].NetAddress == randHosts[1].NetAddress || randHosts[0].NetAddress == randHosts[2].NetAddress || randHosts[1].NetAddress == randHosts[2].NetAddress { t.Error("doubled up") } // Grab 4 random hosts. 3 should be returned. randHosts = hdb.RandomHosts(4, nil) if len(randHosts) != 3 { t.Error("didn't get 3 hosts") } if randHosts[0].NetAddress == randHosts[1].NetAddress || randHosts[0].NetAddress == randHosts[2].NetAddress || randHosts[1].NetAddress == randHosts[2].NetAddress { t.Error("doubled up") } // Ask for 3 hosts that are not in randHosts. No hosts should be // returned. uniqueHosts := hdb.RandomHosts(3, []modules.NetAddress{ randHosts[0].NetAddress, randHosts[1].NetAddress, randHosts[2].NetAddress, }) if len(uniqueHosts) != 0 { t.Error("didn't get 0 hosts") } // Ask for 3 hosts, blacklisting non-existent hosts. 3 should be returned. randHosts = hdb.RandomHosts(3, []modules.NetAddress{"foo", "bar", "baz"}) if len(randHosts) != 3 { t.Error("didn't get 3 hosts") } if randHosts[0].NetAddress == randHosts[1].NetAddress || randHosts[0].NetAddress == randHosts[2].NetAddress || randHosts[1].NetAddress == randHosts[2].NetAddress { t.Error("doubled up") } // entry4 should not every be returned by RandomHosts because it is not // accepting contracts. dbe.NetAddress = fakeAddr(4) dbe.AcceptingContracts = false entry4 := hostEntry{ HostDBEntry: dbe, Weight: types.NewCurrency64(4), } hdb.insertNode(&entry4) // Grab 4 random hosts. 3 should be returned. randHosts = hdb.RandomHosts(4, nil) if len(randHosts) != 3 { t.Error("didn't get 3 hosts") } if randHosts[0].NetAddress == randHosts[1].NetAddress || randHosts[0].NetAddress == randHosts[2].NetAddress || randHosts[1].NetAddress == randHosts[2].NetAddress { t.Error("doubled up") } }
// TestWeightedList inserts and removes nodes in a semi-random manner and // verifies that the tree stays consistent through the adjustments. func TestWeightedList(t *testing.T) { if testing.Short() { t.SkipNow() } // Create a hostdb and 3 equal entries to insert. hdb := bareHostDB() // Create a bunch of host entries of equal weight. var dbe modules.HostDBEntry dbe.AcceptingContracts = true firstInsertions := 64 for i := 0; i < firstInsertions; i++ { dbe.NetAddress = fakeAddr(uint8(i)) entry := hostEntry{ HostDBEntry: dbe, Weight: types.NewCurrency64(10), } hdb.insertNode(&entry) } err := uniformTreeVerification(hdb, firstInsertions) if err != nil { t.Error(err) } // Remove a few hosts and check that the tree is still in order. removals := 12 // Keep a map of what we've removed so far. removedMap := make(map[uint8]struct{}) for i := 0; i < removals; i++ { // Try numbers until we roll a number that's not been removed yet. var randInt uint8 for { randBig, err := rand.Int(rand.Reader, big.NewInt(int64(firstInsertions))) if err != nil { t.Fatal(err) } randInt = uint8(randBig.Int64()) _, exists := removedMap[randInt] if !exists { break } } // Remove the entry and add it to the list of removed entries err := hdb.removeHost(fakeAddr(randInt)) if err != nil { t.Fatal(err) } removedMap[randInt] = struct{}{} } err = uniformTreeVerification(hdb, firstInsertions-removals) if err != nil { t.Error(err) } // Do some more insertions. secondInsertions := 64 for i := firstInsertions; i < firstInsertions+secondInsertions; i++ { dbe.NetAddress = fakeAddr(uint8(i)) entry := hostEntry{ HostDBEntry: dbe, Weight: types.NewCurrency64(10), } hdb.insertNode(&entry) } err = uniformTreeVerification(hdb, firstInsertions-removals+secondInsertions) if err != nil { t.Error(err) } }
// TestEditor tests the failure conditions of the Editor method. The method is // more fully tested in the host integration test. func TestEditor(t *testing.T) { // use a mock hostdb to supply hosts hdb := &editorHostDB{ hosts: make(map[modules.NetAddress]modules.HostDBEntry), } c := &Contractor{ hdb: hdb, } // empty contract _, err := c.Editor(modules.RenterContract{}) if err == nil { t.Error("expected error, got nil") } // expired contract c.blockHeight = 3 _, err = c.Editor(modules.RenterContract{}) if err == nil { t.Error("expected error, got nil") } c.blockHeight = 0 // expensive host _, hostPublicKey := crypto.GenerateKeyPairDeterministic([32]byte{}) dbe := modules.HostDBEntry{ PublicKey: types.SiaPublicKey{ Algorithm: types.SignatureEd25519, Key: hostPublicKey[:], }, } dbe.AcceptingContracts = true dbe.StoragePrice = types.NewCurrency64(^uint64(0)) hdb.hosts["foo"] = dbe _, err = c.Editor(modules.RenterContract{NetAddress: "foo"}) if err == nil { t.Error("expected error, got nil") } // invalid contract dbe.StoragePrice = types.NewCurrency64(500) hdb.hosts["bar"] = dbe _, err = c.Editor(modules.RenterContract{NetAddress: "bar"}) if err == nil { t.Error("expected error, got nil") } // spent contract contract := modules.RenterContract{ NetAddress: "bar", LastRevision: types.FileContractRevision{ NewValidProofOutputs: []types.SiacoinOutput{ {Value: types.NewCurrency64(0)}, {Value: types.NewCurrency64(^uint64(0))}, }, }, } _, err = c.Editor(contract) if err == nil { t.Error("expected error, got nil") } }