github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/negotiate.go (about)

     1  package modules
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"io"
     7  	"time"
     8  
     9  	"github.com/NebulousLabs/Sia/build"
    10  	"github.com/NebulousLabs/Sia/crypto"
    11  	"github.com/NebulousLabs/Sia/encoding"
    12  	"github.com/NebulousLabs/Sia/types"
    13  )
    14  
    15  const (
    16  	// AcceptResponse is the response given to an RPC call to indicate
    17  	// acceptance, i.e. that the sender wishes to continue communication.
    18  	AcceptResponse = "accept"
    19  
    20  	// StopResponse is the response given to an RPC call to indicate graceful
    21  	// termination, i.e. that the sender wishes to cease communication, but
    22  	// not due to an error.
    23  	StopResponse = "stop"
    24  
    25  	// NegotiateDownloadTime defines the amount of time that the renter and
    26  	// host have to negotitate a download request batch. The time is set high
    27  	// enough that two nodes behind Tor have a reasonable chance of completing
    28  	// the negotiation.
    29  	NegotiateDownloadTime = 600 * time.Second
    30  
    31  	// NegotiateFileContractTime defines the amount of time that the renter and
    32  	// host have to negotiate a file contract. The time is set high enough that
    33  	// a node behind Tor has a reasonable chance at making the multiple
    34  	// required round trips to complete the negotiation.
    35  	NegotiateFileContractTime = 360 * time.Second
    36  
    37  	// NegotiateFileContractRevisionTime defines the minimum amount of time
    38  	// that the renter and host have to negotiate a file contract revision. The
    39  	// time is set high enough that a full 4MB can be piped through a
    40  	// connection that is running over Tor.
    41  	NegotiateFileContractRevisionTime = 600 * time.Second
    42  
    43  	// NegotiateRecentRevisionTime establishes the minimum amount of time that
    44  	// the connection deadline is expected to be set to when a recent file
    45  	// contract revision is being requested from the host. The deadline is long
    46  	// enough that the connection should be successful even if both parties are
    47  	// running Tor.
    48  	NegotiateRecentRevisionTime = 120 * time.Second
    49  
    50  	// NegotiateRenewContractTime defines the minimum amount of time that the
    51  	// renter and host have to negotiate a final contract renewal. The time is
    52  	// high enough that the negotiation can occur over a Tor connection, and
    53  	// that both the host and the renter can have time to process large Merkle
    54  	// tree calculations that may be involved with renewing a file contract.
    55  	NegotiateRenewContractTime = 600 * time.Second
    56  
    57  	// NegotiateSettingsTime establishes the minimum amount of time that the
    58  	// connection deadline is expected to be set to when settings are being
    59  	// requested from the host. The deadline is long enough that the connection
    60  	// should be successful even if both parties are on Tor.
    61  	NegotiateSettingsTime = 120 * time.Second
    62  
    63  	// NegotiateMaxDownloadActionRequestSize defines the maximum size that a
    64  	// download request can be. Note, this is not a max size for the data that
    65  	// can be requested, but instead is a max size for the definition of the
    66  	// data being requested.
    67  	NegotiateMaxDownloadActionRequestSize = 50e3
    68  
    69  	// NegotiateMaxErrorSize indicates the maximum number of bytes that can be
    70  	// used to encode an error being sent during negotiation.
    71  	NegotiateMaxErrorSize = 256
    72  
    73  	// NegotiateMaxFileContractRevisionSize specifies the maximum size that a
    74  	// file contract revision is allowed to have when being sent over the wire
    75  	// during negotiation.
    76  	NegotiateMaxFileContractRevisionSize = 3e3
    77  
    78  	// NegotiateMaxFileContractSetLen determines the maximum allowed size of a
    79  	// transaction set that can be sent when trying to negotiate a file
    80  	// contract. The transaction set will contain all of the unconfirmed
    81  	// dependencies of the file contract, meaning that it can be quite large.
    82  	// The transaction pool's size limit for transaction sets has been chosen
    83  	// as a reasonable guideline for determining what is too large.
    84  	NegotiateMaxFileContractSetLen = TransactionSetSizeLimit - 1e3
    85  
    86  	// NegotiateMaxHostExternalSettingsLen is the maximum allowed size of an
    87  	// encoded HostExternalSettings.
    88  	NegotiateMaxHostExternalSettingsLen = 16000
    89  
    90  	// NegotiateMaxSiaPubkeySize defines the maximum size that a SiaPubkey is
    91  	// allowed to be when being sent over the wire during negotiation.
    92  	NegotiateMaxSiaPubkeySize = 1e3
    93  
    94  	// NegotiateMaxTransactionSignatureSize defines the maximum size that a
    95  	// transaction signature is allowed to be when being sent over the wire
    96  	// during negoitation.
    97  	NegotiateMaxTransactionSignatureSize = 2e3
    98  
    99  	// NegotiateMaxTransactionSignaturesSize defines the maximum size that a
   100  	// transaction signature slice is allowed to be when being sent over the
   101  	// wire during negoitation.
   102  	NegotiateMaxTransactionSignaturesSize = 5e3
   103  )
   104  
   105  var (
   106  	// ActionDelete is the specifier for a RevisionAction that deletes a
   107  	// sector.
   108  	ActionDelete = types.Specifier{'D', 'e', 'l', 'e', 't', 'e'}
   109  
   110  	// ActionInsert is the specifier for a RevisionAction that inserts a
   111  	// sector.
   112  	ActionInsert = types.Specifier{'I', 'n', 's', 'e', 'r', 't'}
   113  
   114  	// ActionModify is the specifier for a RevisionAction that modifies sector
   115  	// data.
   116  	ActionModify = types.Specifier{'M', 'o', 'd', 'i', 'f', 'y'}
   117  
   118  	// ErrAnnNotAnnouncement indicates that the provided host announcement does
   119  	// not use a recognized specifier, indicating that it's either not a host
   120  	// announcement or it's not a recognized version of a host announcement.
   121  	ErrAnnNotAnnouncement = errors.New("provided data does not form a recognized host announcement")
   122  
   123  	// ErrAnnUnrecognizedSignature is returned when the signature in a host
   124  	// announcement is not a type of signature that is recognized.
   125  	ErrAnnUnrecognizedSignature = errors.New("the signature provided in the host announcement is not recognized")
   126  
   127  	// ErrRevisionCoveredFields is returned if there is a covered fields object
   128  	// in a transaction signature which has the 'WholeTransaction' field set to
   129  	// true, meaning that miner fees cannot be added to the transaction without
   130  	// invalidating the signature.
   131  	ErrRevisionCoveredFields = errors.New("file contract revision transaction signature does not allow miner fees to be added")
   132  
   133  	// ErrRevisionSigCount is returned when a file contract revision has the
   134  	// wrong number of transaction signatures.
   135  	ErrRevisionSigCount = errors.New("file contract revision has the wrong number of transaction signatures")
   136  
   137  	// ErrStopResponse is the error returned by ReadNegotiationAcceptance when
   138  	// it reads the StopResponse string.
   139  	ErrStopResponse = errors.New("sender wishes to stop communicating")
   140  
   141  	// PrefixHostAnnouncement is used to indicate that a transaction's
   142  	// Arbitrary Data field contains a host announcement. The encoded
   143  	// announcement will follow this prefix.
   144  	PrefixHostAnnouncement = types.Specifier{'H', 'o', 's', 't', 'A', 'n', 'n', 'o', 'u', 'n', 'c', 'e', 'm', 'e', 'n', 't'}
   145  
   146  	// RPCDownload is the specifier for downloading a file from a host.
   147  	RPCDownload = types.Specifier{'D', 'o', 'w', 'n', 'l', 'o', 'a', 'd', 2}
   148  
   149  	// RPCFormContract is the specifier for forming a contract with a host.
   150  	RPCFormContract = types.Specifier{'F', 'o', 'r', 'm', 'C', 'o', 'n', 't', 'r', 'a', 'c', 't', 2}
   151  
   152  	// RPCRenewContract is the specifier to renewing an existing contract.
   153  	RPCRenewContract = types.Specifier{'R', 'e', 'n', 'e', 'w', 'C', 'o', 'n', 't', 'r', 'a', 'c', 't', 2}
   154  
   155  	// RPCReviseContract is the specifier for revising an existing file
   156  	// contract.
   157  	RPCReviseContract = types.Specifier{'R', 'e', 'v', 'i', 's', 'e', 'C', 'o', 'n', 't', 'r', 'a', 'c', 't', 2}
   158  
   159  	// RPCRecentRevision is the specifier for getting the most recent file
   160  	// contract revision for a given file contract.
   161  	RPCRecentRevision = types.Specifier{'R', 'e', 'c', 'e', 'n', 't', 'R', 'e', 'v', 'i', 's', 'i', 'o', 'n', 2}
   162  
   163  	// RPCSettings is the specifier for requesting settings from the host.
   164  	RPCSettings = types.Specifier{'S', 'e', 't', 't', 'i', 'n', 'g', 's', 2}
   165  
   166  	// SectorSize defines how large a sector should be in bytes. The sector
   167  	// size needs to be a power of two to be compatible with package
   168  	// merkletree. 4MB has been chosen for the live network because large
   169  	// sectors significantly reduce the tracking overhead experienced by the
   170  	// renter and the host.
   171  	SectorSize = func() uint64 {
   172  		if build.Release == "dev" {
   173  			return 1 << 20 // 1 MiB
   174  		}
   175  		if build.Release == "standard" {
   176  			return 1 << 22 // 4 MiB
   177  		}
   178  		if build.Release == "testing" {
   179  			return 1 << 12 // 4 KiB
   180  		}
   181  		panic("unrecognized release constant in host - sectorSize")
   182  	}()
   183  )
   184  
   185  type (
   186  	// A DownloadAction is a description of a download that the renter would
   187  	// like to make. The MerkleRoot indicates the root of the sector, the
   188  	// offset indicates what portion of the sector is being downloaded, and the
   189  	// length indicates how many bytes should be grabbed starting from the
   190  	// offset.
   191  	DownloadAction struct {
   192  		MerkleRoot crypto.Hash
   193  		Offset     uint64
   194  		Length     uint64
   195  	}
   196  
   197  	// HostAnnouncement is an announcement by the host that appears in the
   198  	// blockchain. 'Specifier' is always 'PrefixHostAnnouncement'. The
   199  	// announcement is always followed by a signature from the public key of
   200  	// the whole announcement.
   201  	HostAnnouncement struct {
   202  		Specifier  types.Specifier
   203  		NetAddress NetAddress
   204  		PublicKey  types.SiaPublicKey
   205  	}
   206  
   207  	// HostExternalSettings are the parameters advertised by the host. These
   208  	// are the values that the renter will request from the host in order to
   209  	// build its database.
   210  	HostExternalSettings struct {
   211  		// MaxBatchSize indicates the maximum size in bytes that a batch is
   212  		// allowed to be. A batch is an array of revision actions, each
   213  		// revision action can have a different number of bytes, depending on
   214  		// the action, so the number of revision actions allowed depends on the
   215  		// sizes of each.
   216  		AcceptingContracts   bool              `json:"acceptingcontracts"`
   217  		MaxDownloadBatchSize uint64            `json:"maxdownloadbatchsize"`
   218  		MaxReviseBatchSize   uint64            `json:"maxrevisebatchsize"`
   219  		MaxDuration          types.BlockHeight `json:"maxduration"`
   220  		NetAddress           NetAddress        `json:"netaddress"`
   221  		RemainingStorage     uint64            `json:"remainingstorage"`
   222  		SectorSize           uint64            `json:"sectorsize"`
   223  		TotalStorage         uint64            `json:"totalstorage"`
   224  		UnlockHash           types.UnlockHash  `json:"unlockhash"`
   225  		WindowSize           types.BlockHeight `json:"windowsize"`
   226  
   227  		// Collateral is the amount of collateral that the host will put up for
   228  		// storage in 'bytes per block', as an assurance to the renter that the
   229  		// host really is committed to keeping the file. But, because the file
   230  		// contract is created with no data available, this does leave the host
   231  		// exposed to an attack by a wealthy renter whereby the renter causes
   232  		// the host to lockup in-advance a bunch of funds that the renter then
   233  		// never uses, meaning the host will not have collateral for other
   234  		// clients.
   235  		//
   236  		// MaxCollateral indicates the maximum number of coins that a host is
   237  		// willing to put into a file contract.
   238  		Collateral    types.Currency `json:"collateral"`
   239  		MaxCollateral types.Currency `json:"maxcollateral"`
   240  
   241  		// ContractPrice is the number of coins that the renter needs to pay to
   242  		// the host just to open a file contract with them. Generally, the
   243  		// price is only to cover the siacoin fees that the host will suffer
   244  		// when submitting the file contract revision and storage proof to the
   245  		// blockchain.
   246  		//
   247  		// The storage price is the cost per-byte-per-block in hastings of
   248  		// storing data on the host.
   249  		//
   250  		// 'Download' bandwidth price is the cost per byte of downloading data
   251  		// from the host.
   252  		//
   253  		// 'Upload' bandwidth price is the cost per byte of uploading data to
   254  		// the host.
   255  		ContractPrice          types.Currency `json:"contractprice"`
   256  		DownloadBandwidthPrice types.Currency `json:"downloadbandwidthprice"`
   257  		StoragePrice           types.Currency `json:"storageprice"`
   258  		UploadBandwidthPrice   types.Currency `json:"uploadbandwidthprice"`
   259  
   260  		// Because the host has a public key, and settings are signed, and
   261  		// because settings may be MITM'd, settings need a revision number so
   262  		// that a renter can compare multiple sets of settings and determine
   263  		// which is the most recent.
   264  		RevisionNumber uint64 `json:"revisionnumber"`
   265  		Version        string `json:"version"`
   266  	}
   267  
   268  	// A RevisionAction is a description of an edit to be performed on a file
   269  	// contract. Three types are allowed, 'ActionDelecte', 'ActionInsert', and
   270  	// 'ActionModify'. ActionDelete just takes a sector index, indicating which
   271  	// sector is going to be deleted. ActionInsert takes a sector index, and a
   272  	// full sector of data, indicating that a sector at the index should be
   273  	// inserted with the provided data. 'Modify' revises the sector at the
   274  	// given index, rewriting it with the provided data starting from the
   275  	// 'offset' within the sector.
   276  	//
   277  	// Modify could be simulated with an insert and a delete, however an insert
   278  	// requires a full sector to be uploaded, and a modify can be just a few
   279  	// kb, which can be significantly faster.
   280  	RevisionAction struct {
   281  		Type        types.Specifier
   282  		SectorIndex uint64
   283  		Offset      uint64
   284  		Data        []byte
   285  	}
   286  )
   287  
   288  // ReadNegotiationAcceptance reads an accept/reject response from r (usually a
   289  // net.Conn). If the response is not AcceptResponse, ReadNegotiationAcceptance
   290  // returns the response as an error. If the response is StopResponse,
   291  // ErrStopResponse is returned, allowing for direct error comparison.
   292  //
   293  // Note that since errors returned by ReadNegotiationAcceptance are newly
   294  // allocated, they cannot be compared to other errors in the traditional
   295  // fashion.
   296  func ReadNegotiationAcceptance(r io.Reader) error {
   297  	var resp string
   298  	err := encoding.ReadObject(r, &resp, NegotiateMaxErrorSize)
   299  	if err != nil {
   300  		return err
   301  	}
   302  	switch resp {
   303  	case AcceptResponse:
   304  		return nil
   305  	case StopResponse:
   306  		return ErrStopResponse
   307  	default:
   308  		return errors.New(resp)
   309  	}
   310  }
   311  
   312  // WriteNegotiationAcceptance writes the 'accept' response to w (usually a
   313  // net.Conn).
   314  func WriteNegotiationAcceptance(w io.Writer) error {
   315  	return encoding.WriteObject(w, AcceptResponse)
   316  }
   317  
   318  // WriteNegotiationRejection will write a rejection response to w (usually a
   319  // net.Conn) and return the input error. If the write fails, the write error
   320  // is joined with the input error.
   321  func WriteNegotiationRejection(w io.Writer, err error) error {
   322  	writeErr := encoding.WriteObject(w, err.Error())
   323  	if writeErr != nil {
   324  		return build.JoinErrors([]error{err, writeErr}, "; ")
   325  	}
   326  	return err
   327  }
   328  
   329  // WriteNegotiationStop writes the 'stop' response to w (usually a
   330  // net.Conn).
   331  func WriteNegotiationStop(w io.Writer) error {
   332  	return encoding.WriteObject(w, StopResponse)
   333  }
   334  
   335  // CreateAnnouncement will take a host announcement and encode it, returning
   336  // the exact []byte that should be added to the arbitrary data of a
   337  // transaction.
   338  func CreateAnnouncement(addr NetAddress, pk types.SiaPublicKey, sk crypto.SecretKey) (signedAnnouncement []byte, err error) {
   339  	if err := addr.IsValid(); err != nil {
   340  		return nil, err
   341  	}
   342  
   343  	// Create the HostAnnouncement and marshal it.
   344  	annBytes := encoding.Marshal(HostAnnouncement{
   345  		Specifier:  PrefixHostAnnouncement,
   346  		NetAddress: addr,
   347  		PublicKey:  pk,
   348  	})
   349  
   350  	// Create a signature for the announcement.
   351  	annHash := crypto.HashBytes(annBytes)
   352  	sig, err := crypto.SignHash(annHash, sk)
   353  	if err != nil {
   354  		return nil, err
   355  	}
   356  	// Return the signed announcement.
   357  	return append(annBytes, sig[:]...), nil
   358  }
   359  
   360  // DecodeAnnouncement decodes announcement bytes into a host announcement,
   361  // verifying the prefix and the signature.
   362  func DecodeAnnouncement(fullAnnouncement []byte) (na NetAddress, spk types.SiaPublicKey, err error) {
   363  	// Read the first part of the announcement to get the intended host
   364  	// announcement.
   365  	var ha HostAnnouncement
   366  	dec := encoding.NewDecoder(bytes.NewReader(fullAnnouncement))
   367  	err = dec.Decode(&ha)
   368  	if err != nil {
   369  		return "", types.SiaPublicKey{}, err
   370  	}
   371  
   372  	// Check that the announcement was registered as a host announcement.
   373  	if ha.Specifier != PrefixHostAnnouncement {
   374  		return "", types.SiaPublicKey{}, ErrAnnNotAnnouncement
   375  	}
   376  	// Check that the public key is a recognized type of public key.
   377  	if ha.PublicKey.Algorithm != types.SignatureEd25519 {
   378  		return "", types.SiaPublicKey{}, ErrAnnUnrecognizedSignature
   379  	}
   380  
   381  	// Read the signature out of the reader.
   382  	var sig crypto.Signature
   383  	err = dec.Decode(&sig)
   384  	if err != nil {
   385  		return "", types.SiaPublicKey{}, err
   386  	}
   387  	// Verify the signature.
   388  	var pk crypto.PublicKey
   389  	copy(pk[:], ha.PublicKey.Key)
   390  	annHash := crypto.HashObject(ha)
   391  	err = crypto.VerifyHash(annHash, pk, sig)
   392  	if err != nil {
   393  		return "", types.SiaPublicKey{}, err
   394  	}
   395  	return ha.NetAddress, ha.PublicKey, nil
   396  }
   397  
   398  // VerifyFileContractRevisionTransactionSignatures checks that the signatures
   399  // on a file contract revision are valid and cover the right fields.
   400  func VerifyFileContractRevisionTransactionSignatures(fcr types.FileContractRevision, tsigs []types.TransactionSignature, height types.BlockHeight) error {
   401  	if len(tsigs) != 2 {
   402  		return ErrRevisionSigCount
   403  	}
   404  	for _, tsig := range tsigs {
   405  		// The transaction needs to be malleable so that miner fees can be
   406  		// added. If the whole transaction is covered, it is doomed to have no
   407  		// fees.
   408  		if tsig.CoveredFields.WholeTransaction {
   409  			return ErrRevisionCoveredFields
   410  		}
   411  	}
   412  	txn := types.Transaction{
   413  		FileContractRevisions: []types.FileContractRevision{fcr},
   414  		TransactionSignatures: tsigs,
   415  	}
   416  	// Check that the signatures verify. This will also check that the covered
   417  	// fields object is not over-aggressive, because if the object is pointing
   418  	// to elements that haven't been added to the transaction, verification
   419  	// will fail.
   420  	return txn.StandaloneValid(height)
   421  }