Commit 26a7acbf authored by Luke Champine's avatar Luke Champine

overhaul contract negotiation

parent 5af32e27
......@@ -21,6 +21,8 @@ type (
Target hash.Hash
)
var ZeroAddress = CoinAddress{0}
// A Block contains all of the changes to the state that have occurred since
// the previous block. There are constraints that make it difficult and
// rewarding to find a block.
......@@ -83,7 +85,7 @@ type Output struct {
// stores a file.
type FileContract struct {
FileMerkleRoot hash.Hash
FileSize uint64 // in bytes
FileSize uint64
Start, End BlockHeight
Payout Currency
ValidProofAddress CoinAddress
......
......@@ -11,6 +11,26 @@ const (
AcceptContractResponse = "accept"
)
// ContractTerms are the parameters agreed upon by a client and a host when
// forming a FileContract.
type ContractTerms struct {
FileSize uint64
StartHeight consensus.BlockHeight
WindowSize consensus.BlockHeight // how many blocks a host has to submit each proof
NumWindows uint64
ClientPayout consensus.Currency // client contribution towards payout each window
HostPayout consensus.Currency // host contribution towards payout each window
ValidProofAddress consensus.CoinAddress
MissedProofAddress consensus.CoinAddress
}
type HostInfo struct {
HostSettings
StorageRemaining int64
NumContracts int
}
type Host interface {
// Announce announces the host on the blockchain. A host announcement
// requires two things: the host's address, and a volume of "frozen"
......
This diff is collapsed.
......@@ -5,7 +5,6 @@ import (
"io"
"net"
"os"
"path/filepath"
"sync"
"github.com/NebulousLabs/Sia/consensus"
......@@ -23,7 +22,7 @@ const (
)
type contractObligation struct {
filename string // Where on disk the file is stored.
path string // Where on disk the file is stored.
}
type Host struct {
......@@ -104,8 +103,7 @@ func (h *Host) RetrieveFile(conn net.Conn) (err error) {
}
// Open the file.
fullname := filepath.Join(h.hostDir, contractObligation.filename)
file, err := os.Open(fullname)
file, err := os.Open(contractObligation.path)
if err != nil {
return
}
......@@ -134,22 +132,15 @@ func (h *Host) Settings() (modules.HostSettings, error) {
return h.HostSettings, nil
}
type HostInfo struct {
modules.HostSettings
StorageRemaining int64
ContractCount int
}
func (h *Host) Info() HostInfo {
func (h *Host) Info() modules.HostInfo {
h.mu.RLock()
defer h.mu.RUnlock()
info := HostInfo{
info := modules.HostInfo{
HostSettings: h.HostSettings,
StorageRemaining: h.spaceRemaining,
ContractCount: len(h.contracts),
NumContracts: len(h.contracts),
}
return info
}
package host
import (
"errors"
"os"
"path/filepath"
//"errors"
//"os"
//"path/filepath"
"github.com/NebulousLabs/Sia/consensus"
"github.com/NebulousLabs/Sia/hash"
//"github.com/NebulousLabs/Sia/hash"
)
// ContractEntry houses a single contract with its id - you cannot derive the
// id of a contract without having the transaction. Rather than keep the whole
// transaction, we store only the id.
// TODO: is this needed?
type ContractEntry struct {
ID consensus.ContractID
Contract consensus.FileContract
}
// TODO: Hold off on both storage proofs and deleting files for a few blocks
// after the first possible opportunity to reduce risk of loss due to
// blockchain reorganization.
......@@ -123,31 +132,33 @@ func (h *Host) threadedConsensusListen(updateChan chan struct{}) {
// Create a proof of storage for a contract, using the state height to
// determine the random seed. Create proof must be under a host and state lock.
func (h *Host) createStorageProof(entry ContractEntry, heightForProof consensus.BlockHeight) (sp consensus.StorageProof, err error) {
// Get the file associated with the contract.
contractObligation, exists := h.contracts[entry.ID]
if !exists {
err = errors.New("no record of that file")
return
}
fullname := filepath.Join(h.hostDir, contractObligation.filename)
/*
// Get the file associated with the contract.
contractObligation, exists := h.contracts[entry.ID]
if !exists {
err = errors.New("no record of that file")
return
}
fullname := filepath.Join(h.hostDir, contractObligation.filename)
// Open the file.
file, err := os.Open(fullname)
if err != nil {
return
}
defer file.Close()
// Open the file.
file, err := os.Open(fullname)
if err != nil {
return
}
defer file.Close()
// Build the proof using the hash library.
numSegments := hash.CalculateSegments(entry.Contract.FileSize)
segmentIndex, err := h.state.StorageProofSegment(entry.ID)
if err != nil {
return
}
base, hashSet, err := hash.BuildReaderProof(file, numSegments, segmentIndex)
if err != nil {
return
}
sp = consensus.StorageProof{entry.ID, base, hashSet}
// Build the proof using the hash library.
numSegments := hash.CalculateSegments(entry.Contract.FileSize)
segmentIndex, err := h.state.StorageProofSegment(entry.ID)
if err != nil {
return
}
base, hashSet, err := hash.BuildReaderProof(file, numSegments, segmentIndex)
if err != nil {
return
}
sp = consensus.StorageProof{entry.ID, base, hashSet}
*/
return
}
......@@ -7,6 +7,7 @@ import (
"github.com/NebulousLabs/Sia/hash"
)
// TODO: embed ContractTerms?
type UploadParams struct {
Data io.ReadSeeker
Duration consensus.BlockHeight
......
......@@ -7,6 +7,7 @@ import (
"github.com/NebulousLabs/Sia/consensus"
"github.com/NebulousLabs/Sia/encoding"
"github.com/NebulousLabs/Sia/hash"
"github.com/NebulousLabs/Sia/modules"
)
......@@ -15,28 +16,25 @@ const (
minerFee = 10
)
func (r *Renter) createContractTransaction(host modules.HostEntry, up modules.UploadParams) (t consensus.Transaction, contract consensus.FileContract, err error) {
// get state height
r.state.RLock()
height := r.state.Height()
r.state.RUnlock()
func (r *Renter) createContractTransaction(host modules.HostEntry, terms modules.ContractTerms, merkleRoot hash.Hash) (txn consensus.Transaction, err error) {
// Fill out the contract according to the whims of the host.
contract = consensus.FileContract{
FileMerkleRoot: up.MerkleRoot,
FileSize: up.FileSize,
Start: height + up.Delay,
End: height + up.Delay + up.Duration,
duration := terms.WindowSize * consensus.BlockHeight(terms.NumWindows)
contract := consensus.FileContract{
FileMerkleRoot: merkleRoot,
FileSize: terms.FileSize,
Start: terms.StartHeight,
End: terms.StartHeight + duration,
ValidProofAddress: host.CoinAddress,
MissedProofAddress: consensus.CoinAddress{}, // The empty address is the burn address.
MissedProofAddress: consensus.ZeroAddress, // The empty address is the burn address.
}
fund := host.Price*consensus.Currency(duration)*consensus.Currency(terms.FileSize) + minerFee
// Create the transaction.
id, err := r.wallet.RegisterTransaction(t)
id, err := r.wallet.RegisterTransaction(txn)
if err != nil {
return
}
fund := host.Price*consensus.Currency(up.Duration+up.Delay)*consensus.Currency(up.FileSize) + minerFee
err = r.wallet.FundTransaction(id, fund)
if err != nil {
return
......@@ -49,7 +47,7 @@ func (r *Renter) createContractTransaction(host modules.HostEntry, up modules.Up
if err != nil {
return
}
t, err = r.wallet.SignTransaction(id, false)
txn, err = r.wallet.SignTransaction(id, false)
if err != nil {
return
}
......@@ -57,10 +55,28 @@ func (r *Renter) createContractTransaction(host modules.HostEntry, up modules.Up
return
}
func negotiateContract(host modules.HostEntry, t consensus.Transaction, up modules.UploadParams) error {
return host.IPAddress.Call("NegotiateContract", func(conn net.Conn) (err error) {
// send contract
if _, err = encoding.WriteObject(conn, t); err != nil {
func (r *Renter) negotiateContract(host modules.HostEntry, up modules.UploadParams) (contract consensus.FileContract, err error) {
r.state.RLock()
height := r.state.Height()
r.state.RUnlock()
// create ContractTerms
terms := modules.ContractTerms{
FileSize: up.FileSize,
StartHeight: height + up.Delay,
WindowSize: 0, // ??
NumWindows: 0, // ?? duration/windowsize + 1?
ClientPayout: 0, // ??
HostPayout: 0, // ??
ValidProofAddress: host.CoinAddress,
MissedProofAddress: consensus.ZeroAddress,
}
// TODO: call r.hostDB.FlagHost(host.IPAddress) if negotiation unnecessful
// (and it isn't our fault)
err = host.IPAddress.Call("NegotiateContract", func(conn net.Conn) (err error) {
// send ContractTerms
if _, err = encoding.WriteObject(conn, terms); err != nil {
return
}
// read response
......@@ -72,10 +88,21 @@ func negotiateContract(host modules.HostEntry, t consensus.Transaction, up modul
return errors.New(response)
}
// host accepted, so transmit file data
// (no prefix needed, since the host already knows the filesize
_, err = io.CopyN(conn, up.Data, int64(up.FileSize))
// reset seek position
up.Data.Seek(0, 0)
if err != nil {
return
}
// create and transmit transaction containing file contract
txn, err := r.createContractTransaction(host, terms, up.MerkleRoot)
if err != nil {
return
}
contract = txn.FileContracts[0]
_, err = encoding.WriteObject(conn, txn)
return
})
return
}
......@@ -28,27 +28,17 @@ func (r *Renter) uploadPiece(up modules.UploadParams) (piece FilePiece, err erro
return
}
// Create file contract using this host's parameters. An error here is
// unrecoverable.
t, fileContract, txnErr := r.createContractTransaction(host, up)
if txnErr != nil {
err = errors.New("unable to create contract transaction: " + txnErr.Error())
return
}
// Negotiate the contract with the host. If the negotiation is
// successful, the file will be uploaded.
err = negotiateContract(host, t, up)
if err != nil {
// If there was a problem, we need to try again with a new host.
r.hostDB.FlagHost(host.IPAddress)
// unsuccessful, we need to try again with a new host. Otherwise, the
// file will be uploaded and we'll be done.
contract, contractErr := r.negotiateContract(host, up)
if contractErr != nil {
continue
}
// Otherwise, we're done.
piece = FilePiece{
Host: host,
Contract: fileContract,
Contract: contract,
}
return
}
......
......@@ -5,7 +5,7 @@ import (
"github.com/spf13/cobra"
"github.com/NebulousLabs/Sia/modules/host"
"github.com/NebulousLabs/Sia/modules"
)
var (
......@@ -66,7 +66,7 @@ func hostannouncecmd(freezeVolume, freezeDuration string) {
}
func hoststatuscmd() {
config := new(host.HostInfo)
config := new(modules.HostInfo)
err := getAPI("/host/config", config)
if err != nil {
fmt.Println("Could not fetch host settings:", err)
......
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