Skip to content
This repository has been archived by the owner on Nov 2, 2018. It is now read-only.

Commit

Permalink
add RPC call Metadata requesting first sector ID
Browse files Browse the repository at this point in the history
This is second attempt of #2321

The whole list of IDs can be expensive for a host to provide for free,
but the first sector ID is constant size and cheap. At the same time
it opens doors for stateless clients, i.e. recovering everything from
seed only. (IDs of other sectors can be stored in the first sector.)
  • Loading branch information
Boris Nagaev committed Dec 26, 2017
1 parent 4a1659e commit 75c8e7a
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 0 deletions.
1 change: 1 addition & 0 deletions modules/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ type (
RenewCalls uint64 `json:"renewcalls"`
ReviseCalls uint64 `json:"revisecalls"`
SettingsCalls uint64 `json:"settingscalls"`
MetadataCalls uint64 `json:"metadatacalls"`
UnrecognizedCalls uint64 `json:"unrecognizedcalls"`
}

Expand Down
1 change: 1 addition & 0 deletions modules/host/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ type Host struct {
atomicRenewCalls uint64
atomicReviseCalls uint64
atomicSettingsCalls uint64
atomicMetadataCalls uint64
atomicUnrecognizedCalls uint64

// Error management. There are a few different types of errors returned by
Expand Down
32 changes: 32 additions & 0 deletions modules/host/negotiatemetadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package host

import (
"net"

"github.com/NebulousLabs/Sia/encoding"
)

// managedRPCMetadata accepts a request to get list of sector ids.
func (h *Host) managedRPCMetadata(conn net.Conn) error {
// Perform the file contract revision exchange, giving the renter the most
// recent file contract revision and getting the storage obligation that
// will be used to get sector ids.
_, so, err := h.managedRPCRecentRevision(conn)
if err != nil {
return extendErr("RPCRecentRevision failed: ", err)
}
// The storage obligation is received with a lock on it. Defer a call to
// unlock the storage obligation.
defer func() {
h.managedUnlockStorageObligation(so.id())
}()
if len(so.SectorRoots) == 0 {
return nil
}
// Write roots of all sectors.
err = encoding.WriteObject(conn, so.SectorRoots[0])
if err != nil {
return extendErr("cound not write sectors: ", ErrorConnection(err.Error()))
}
return nil
}
3 changes: 3 additions & 0 deletions modules/host/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,9 @@ func (h *Host) threadedHandleConn(conn net.Conn) {
case modules.RPCDownload:
atomic.AddUint64(&h.atomicDownloadCalls, 1)
err = extendErr("incoming RPCDownload failed: ", h.managedRPCDownload(conn))
case modules.RPCMetadata:
atomic.AddUint64(&h.atomicMetadataCalls, 1)
err = extendErr("incoming RPCMetadata failed: ", h.managedRPCMetadata(conn))
case modules.RPCRenewContract:
atomic.AddUint64(&h.atomicRenewCalls, 1)
err = extendErr("incoming RPCRenewContract failed: ", h.managedRPCRenewContract(conn))
Expand Down
10 changes: 10 additions & 0 deletions modules/negotiate.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ const (
// the negotiation.
NegotiateDownloadTime = 600 * time.Second

// NegotiateMetadataTime establishes the minimum amount of time that
// the connection deadline is expected to be set to when a metadata
// is being requested from the host. The deadline is long
// enough that the connection should be successful even if both parties are
// running Tor.
NegotiateMetadataTime = 120 * time.Second

// NegotiateFileContractRevisionTime defines the minimum amount of time
// that the renter and host have to negotiate a file contract revision. The
// time is set high enough that a full 4MB can be piped through a
Expand Down Expand Up @@ -148,6 +155,9 @@ var (
// RPCDownload is the specifier for downloading a file from a host.
RPCDownload = types.Specifier{'D', 'o', 'w', 'n', 'l', 'o', 'a', 'd', 2}

// RPCMetadata is the specifier for getting the list of sector roots.
RPCMetadata = types.Specifier{'M', 'e', 't', 'a', 'd', 'a', 't', 'a'}

// RPCFormContract is the specifier for forming a contract with a host.
RPCFormContract = types.Specifier{'F', 'o', 'r', 'm', 'C', 'o', 'n', 't', 'r', 'a', 'c', 't', 2}

Expand Down
79 changes: 79 additions & 0 deletions modules/renter/contractor/host_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/NebulousLabs/Sia/modules/host"
"github.com/NebulousLabs/Sia/modules/miner"
"github.com/NebulousLabs/Sia/modules/renter/hostdb"
"github.com/NebulousLabs/Sia/modules/renter/proto"
"github.com/NebulousLabs/Sia/modules/transactionpool"
modWallet "github.com/NebulousLabs/Sia/modules/wallet"
"github.com/NebulousLabs/Sia/types"
Expand Down Expand Up @@ -654,3 +655,81 @@ func TestContractPresenceLeak(t *testing.T) {
t.Fatalf("Expected to get equal errors, got %q and %q.", errors[0], errors[1])
}
}

// TestIntegrationMetadata tests the Metadata RPC.
func TestIntegrationMetadata(t *testing.T) {
if testing.Short() {
t.SkipNow()
}
t.Parallel()
// create testing trio
h, c, _, err := newTestingTrio(t.Name())
if err != nil {
t.Fatal(err)
}
defer h.Close()
defer c.Close()

// get the host's entry from the db
hostEntry, ok := c.hdb.Host(h.PublicKey())
if !ok {
t.Fatal("no entry for host in db")
}

// form a contract with the host
contract, err := c.managedNewContract(hostEntry, types.SiacoinPrecision.Mul64(10), c.blockHeight+100)
if err != nil {
t.Fatal(err)
}
sc, has := c.contracts.Acquire(contract.ID)
if !has {
t.Fatal("c.contracts.Acquire returned false")
}
secketKey := sc.Metadata().SecretKey
windowStart := sc.Metadata().EndHeight
c.contracts.Return(sc)

// get revision and ID of the first sector from the host
lastRevision, _, err := proto.GetMetadata(hostEntry, contract.ID, secketKey, windowStart, nil)
if err != nil {
t.Fatalf("RPCMetadata returned error: %v", err)
}
wantSize := uint64(0)
if lastRevision.NewFileSize != wantSize {
t.Errorf("lastRevision.NewFileSize = %d, want %d", lastRevision.NewFileSize, wantSize)
}

// revise the contract
editor, err := c.Editor(contract.ID, nil)
if err != nil {
t.Fatal(err)
}
var want crypto.Hash
for i := 0; i < 10; i++ {
data := fastrand.Bytes(int(modules.SectorSize))
root, err := editor.Upload(data)
if err != nil {
t.Fatal(err)
}
if i == 0 {
want = root
}
}
err = editor.Close()
if err != nil {
t.Fatal(err)
}

// get revision and ID of the first sector from the host
lastRevision, got, err := proto.GetMetadata(hostEntry, contract.ID, secketKey, windowStart, nil)
if err != nil {
t.Fatalf("RPCMetadata returned error: %v", err)
}
wantSize = 10 * modules.SectorSize
if lastRevision.NewFileSize != wantSize {
t.Errorf("lastRevision.NewFileSize = %d, want %d", lastRevision.NewFileSize, wantSize)
}
if got != want {
t.Errorf("RPCMetadata returned wrong data")
}
}
41 changes: 41 additions & 0 deletions modules/renter/proto/metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package proto

import (
"errors"
"net"
"time"

"github.com/NebulousLabs/Sia/crypto"
"github.com/NebulousLabs/Sia/encoding"
"github.com/NebulousLabs/Sia/modules"
"github.com/NebulousLabs/Sia/types"
)

// GetMetadata downloads ID of the first sector from the host.
func GetMetadata(host modules.HostDBEntry, fcid types.FileContractID, sk crypto.SecretKey, windowStart types.BlockHeight, cancel <-chan struct{}) (lastRevision types.FileContractRevision, id crypto.Hash, err error) {
conn, err := (&net.Dialer{
Cancel: cancel,
Timeout: 15 * time.Second,
}).Dial("tcp", string(host.NetAddress))
if err != nil {
return types.FileContractRevision{}, crypto.Hash{}, err
}
defer conn.Close()
// allot 2 minutes for RPC request + revision exchange
extendDeadline(conn, modules.NegotiateMetadataTime)
if err = encoding.WriteObject(conn, modules.RPCMetadata); err != nil {
err = errors.New("couldn't initiate RPC: " + err.Error())
return
}
lastRevision, err = getRecentRevision(conn, fcid, sk, windowStart, host.Version)
if err != nil {
return
}
if lastRevision.NewFileSize != 0 {
if err = encoding.ReadObject(conn, &id, crypto.HashSize); err != nil {
err = errors.New("unable to read 'ids': " + err.Error())
return
}
}
return
}

0 comments on commit 75c8e7a

Please sign in to comment.