Example #1
0
// establishDefaults configures the default settings for the host, overwriting
// any existing settings.
func (h *Host) establishDefaults() error {
	// Configure the settings object.
	h.settings = modules.HostSettings{
		TotalStorage: defaultTotalStorage,
		MaxDuration:  defaultMaxDuration,
		WindowSize:   defaultWindowSize,
		Price:        defaultPrice,
		Collateral:   defaultCollateral,
	}
	h.spaceRemaining = h.settings.TotalStorage

	// Generate signing key, for revising contracts.
	sk, pk, err := crypto.GenerateKeyPair()
	if err != nil {
		return err
	}
	h.secretKey = sk
	h.publicKey = types.SiaPublicKey{
		Algorithm: types.SignatureEd25519,
		Key:       pk[:],
	}

	// Subscribe to the consensus set.
	err = h.initConsensusSubscription()
	if err != nil {
		return err
	}

	return nil
}
Example #2
0
// makeSignedAnnouncement creates a []byte that contains an encoded and signed
// host announcement for the given net address.
func makeSignedAnnouncement(na modules.NetAddress) ([]byte, error) {
	sk, pk, err := crypto.GenerateKeyPair()
	if err != nil {
		return nil, err
	}
	spk := types.SiaPublicKey{
		Algorithm: types.SignatureEd25519,
		Key:       pk[:],
	}
	return modules.CreateAnnouncement(na, spk, sk)
}
// BenchmarkStandaloneValid times how long it takes to verify a single
// large transaction, with a certain number of signatures
func BenchmarkStandaloneValid(b *testing.B) {
	numSigs := 7
	// make a transaction numSigs with valid inputs with valid signatures
	b.ReportAllocs()
	txn := Transaction{}
	sk := make([]crypto.SecretKey, numSigs)
	pk := make([]crypto.PublicKey, numSigs)
	for i := 0; i < numSigs; i++ {
		s, p, err := crypto.GenerateKeyPair()
		if err != nil {
			b.Fatal(err)
		}
		sk[i] = s
		pk[i] = p

		uc := UnlockConditions{
			PublicKeys: []SiaPublicKey{
				{Algorithm: SignatureEd25519, Key: pk[i][:]},
			},
			SignaturesRequired: 1,
		}
		txn.SiacoinInputs = append(txn.SiacoinInputs, SiacoinInput{
			UnlockConditions: uc,
		})
		copy(txn.SiacoinInputs[i].ParentID[:], encoding.Marshal(i))
		txn.TransactionSignatures = append(txn.TransactionSignatures, TransactionSignature{
			CoveredFields: CoveredFields{WholeTransaction: true},
		})
		copy(txn.TransactionSignatures[i].ParentID[:], encoding.Marshal(i))
	}
	// Transaction must be constructed before signing
	for i := 0; i < numSigs; i++ {
		sigHash := txn.SigHash(i)
		sig0, err := crypto.SignHash(sigHash, sk[i])
		if err != nil {
			b.Fatal(err)
		}
		txn.TransactionSignatures[i].Signature = sig0[:]
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		err := txn.StandaloneValid(10)
		if err != nil {
			b.Fatal(err)
		}
	}
}
Example #4
0
// establishDefaults configures the default settings for the host, overwriting
// any existing settings.
func (h *Host) establishDefaults() error {
	// Configure the settings object.
	h.settings = modules.HostInternalSettings{
		MaxDownloadBatchSize: uint64(defaultMaxDownloadBatchSize),
		MaxDuration:          defaultMaxDuration,
		MaxReviseBatchSize:   uint64(defaultMaxReviseBatchSize),
		WindowSize:           defaultWindowSize,

		Collateral:       defaultCollateral,
		CollateralBudget: defaultCollateralBudget,
		MaxCollateral:    defaultMaxCollateral,

		MinStoragePrice:           defaultStoragePrice,
		MinContractPrice:          defaultContractPrice,
		MinDownloadBandwidthPrice: defaultDownloadBandwidthPrice,
		MinUploadBandwidthPrice:   defaultUploadBandwidthPrice,
	}

	// Generate signing key, for revising contracts.
	sk, pk, err := crypto.GenerateKeyPair()
	if err != nil {
		return err
	}
	h.secretKey = sk
	h.publicKey = types.SiaPublicKey{
		Algorithm: types.SignatureEd25519,
		Key:       pk[:],
	}

	// Subscribe to the consensus set.
	err = h.initConsensusSubscription()
	if err != nil {
		return err
	}
	return nil
}
Example #5
0
// TestTransactionValidSignatures probes the validSignatures method of the
// Transaction type.
func TestTransactionValidSignatures(t *testing.T) {
	// Create keys for use in signing and verifying.
	sk, pk, err := crypto.GenerateKeyPair()
	if err != nil {
		t.Fatal(err)
	}

	// Create UnlockConditions with 3 keys, 2 of which are required. The first
	// possible key is a standard signature. The second key is an unknown
	// signature type, which should always be accepted. The final type is an
	// entropy type, which should never be accepted.
	uc := UnlockConditions{
		PublicKeys: []SiaPublicKey{
			{Algorithm: SignatureEd25519, Key: pk[:]},
			{},
			{Algorithm: SignatureEntropy},
		},
		SignaturesRequired: 2,
	}

	// Create a transaction with each type of unlock condition.
	txn := Transaction{
		SiacoinInputs: []SiacoinInput{
			{UnlockConditions: uc},
		},
		FileContractRevisions: []FileContractRevision{
			{UnlockConditions: uc},
		},
		SiafundInputs: []SiafundInput{
			{UnlockConditions: uc},
		},
	}
	txn.FileContractRevisions[0].ParentID[0] = 1 // can't overlap with other objects
	txn.SiafundInputs[0].ParentID[0] = 2         // can't overlap with other objects

	// Create the signatures that spend the output.
	txn.TransactionSignatures = []TransactionSignature{
		// First signatures use cryptography.
		{
			Timelock:      5,
			CoveredFields: CoveredFields{WholeTransaction: true},
		},
		{
			CoveredFields: CoveredFields{WholeTransaction: true},
		},
		{
			CoveredFields: CoveredFields{WholeTransaction: true},
		},

		// The second signatures should always work for being unrecognized
		// types.
		{PublicKeyIndex: 1},
		{PublicKeyIndex: 1},
		{PublicKeyIndex: 1},
	}
	txn.TransactionSignatures[1].ParentID[0] = 1
	txn.TransactionSignatures[2].ParentID[0] = 2
	txn.TransactionSignatures[4].ParentID[0] = 1
	txn.TransactionSignatures[5].ParentID[0] = 2
	sigHash0 := txn.SigHash(0)
	sigHash1 := txn.SigHash(1)
	sigHash2 := txn.SigHash(2)
	sig0, err := crypto.SignHash(sigHash0, sk)
	if err != nil {
		t.Fatal(err)
	}
	sig1, err := crypto.SignHash(sigHash1, sk)
	if err != nil {
		t.Fatal(err)
	}
	sig2, err := crypto.SignHash(sigHash2, sk)
	if err != nil {
		t.Fatal(err)
	}
	txn.TransactionSignatures[0].Signature = sig0[:]
	txn.TransactionSignatures[1].Signature = sig1[:]
	txn.TransactionSignatures[2].Signature = sig2[:]

	// Check that the signing was successful.
	err = txn.validSignatures(10)
	if err != nil {
		t.Error(err)
	}

	// Corrupt one of the sigantures.
	sig0[0]++
	txn.TransactionSignatures[0].Signature = sig0[:]
	err = txn.validSignatures(10)
	if err == nil {
		t.Error("Corrupted a signature but the txn was still accepted as valid!")
	}
	sig0[0]--
	txn.TransactionSignatures[0].Signature = sig0[:]

	// Fail the validCoveredFields check.
	txn.TransactionSignatures[0].CoveredFields.SiacoinInputs = []uint64{33}
	err = txn.validSignatures(10)
	if err == nil {
		t.Error("failed to flunk the validCoveredFields check")
	}
	txn.TransactionSignatures[0].CoveredFields.SiacoinInputs = nil

	// Double spend a SiacoinInput, FileContractTermination, and SiafundInput.
	txn.SiacoinInputs = append(txn.SiacoinInputs, SiacoinInput{UnlockConditions: UnlockConditions{}})
	err = txn.validSignatures(10)
	if err == nil {
		t.Error("failed to double spend a siacoin input")
	}
	txn.SiacoinInputs = txn.SiacoinInputs[:len(txn.SiacoinInputs)-1]
	txn.FileContractRevisions = append(txn.FileContractRevisions, FileContractRevision{UnlockConditions: UnlockConditions{}})
	err = txn.validSignatures(10)
	if err == nil {
		t.Error("failed to double spend a file contract termination")
	}
	txn.FileContractRevisions = txn.FileContractRevisions[:len(txn.FileContractRevisions)-1]
	txn.SiafundInputs = append(txn.SiafundInputs, SiafundInput{UnlockConditions: UnlockConditions{}})
	err = txn.validSignatures(10)
	if err == nil {
		t.Error("failed to double spend a siafund input")
	}
	txn.SiafundInputs = txn.SiafundInputs[:len(txn.SiafundInputs)-1]

	// Add a frivilous signature
	txn.TransactionSignatures = append(txn.TransactionSignatures, TransactionSignature{})
	err = txn.validSignatures(10)
	if err != ErrFrivilousSignature {
		t.Error(err)
	}
	txn.TransactionSignatures = txn.TransactionSignatures[:len(txn.TransactionSignatures)-1]

	// Replace one of the cryptography signatures with an always-accepted
	// signature. This should get rejected because the always-accepted
	// signature has already been used.
	tmpTxn0 := txn.TransactionSignatures[0]
	txn.TransactionSignatures[0] = TransactionSignature{PublicKeyIndex: 1}
	err = txn.validSignatures(10)
	if err != ErrPublicKeyOveruse {
		t.Error(err)
	}
	txn.TransactionSignatures[0] = tmpTxn0

	// Fail the timelock check for signatures.
	err = txn.validSignatures(4)
	if err != ErrPrematureSignature {
		t.Error(err)
	}

	// Try to spend an entropy signature.
	txn.TransactionSignatures[0] = TransactionSignature{PublicKeyIndex: 2}
	err = txn.validSignatures(10)
	if err != ErrEntropyKey {
		t.Error(err)
	}
	txn.TransactionSignatures[0] = tmpTxn0

	// Try to point to a nonexistent public key.
	txn.TransactionSignatures[0] = TransactionSignature{PublicKeyIndex: 5}
	err = txn.validSignatures(10)
	if err != ErrInvalidPubKeyIndex {
		t.Error(err)
	}
	txn.TransactionSignatures[0] = tmpTxn0

	// Insert a malformed public key into the transaction.
	txn.SiacoinInputs[0].UnlockConditions.PublicKeys[0].Key = []byte{'b', 'a', 'd'}
	err = txn.validSignatures(10)
	if err == nil {
		t.Error(err)
	}
	txn.SiacoinInputs[0].UnlockConditions.PublicKeys[0].Key = pk[:]

	// Insert a malformed signature into the transaction.
	txn.TransactionSignatures[0].Signature = []byte{'m', 'a', 'l'}
	err = txn.validSignatures(10)
	if err == nil {
		t.Error(err)
	}
	txn.TransactionSignatures[0] = tmpTxn0

	// Try to spend a transaction when not every required signature is
	// available.
	txn.TransactionSignatures = txn.TransactionSignatures[1:]
	err = txn.validSignatures(10)
	if err != ErrMissingSignatures {
		t.Error(err)
	}
}
Example #6
0
func TestReviseContract(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()
	ct, err := newContractorTester("TestReviseContract")
	if err != nil {
		t.Fatal(err)
	}

	// get an address
	ourAddr, err := ct.wallet.NextAddress()
	if err != nil {
		t.Fatal(err)
	}

	// generate keys
	sk, pk, err := crypto.GenerateKeyPair()
	if err != nil {
		t.Fatal(err)
	}
	renterPubKey := types.SiaPublicKey{
		Algorithm: types.SignatureEd25519,
		Key:       pk[:],
	}

	uc := types.UnlockConditions{
		PublicKeys:         []types.SiaPublicKey{renterPubKey, renterPubKey},
		SignaturesRequired: 1,
	}

	// create file contract
	payout := types.NewCurrency64(1e16)

	fc := types.FileContract{
		FileSize:       0,
		FileMerkleRoot: crypto.Hash{}, // no proof possible without data
		WindowStart:    100,
		WindowEnd:      1000,
		Payout:         payout,
		UnlockHash:     uc.UnlockHash(),
		RevisionNumber: 0,
	}
	// outputs need account for tax
	fc.ValidProofOutputs = []types.SiacoinOutput{
		{Value: types.PostTax(ct.contractor.blockHeight, payout), UnlockHash: ourAddr.UnlockHash()},
		{Value: types.ZeroCurrency, UnlockHash: types.UnlockHash{}}, // no collateral
	}
	fc.MissedProofOutputs = []types.SiacoinOutput{
		// same as above
		fc.ValidProofOutputs[0],
		// goes to the void, not the hostdb
		{Value: types.ZeroCurrency, UnlockHash: types.UnlockHash{}},
	}

	txnBuilder := ct.wallet.StartTransaction()
	err = txnBuilder.FundSiacoins(fc.Payout)
	if err != nil {
		t.Fatal(err)
	}
	txnBuilder.AddFileContract(fc)
	signedTxnSet, err := txnBuilder.Sign(true)
	if err != nil {
		t.Fatal(err)
	}

	// submit contract
	err = ct.tpool.AcceptTransactionSet(signedTxnSet)
	if err != nil {
		t.Fatal(err)
	}

	// create revision
	fcid := signedTxnSet[len(signedTxnSet)-1].FileContractID(0)
	rev := types.FileContractRevision{
		ParentID:              fcid,
		UnlockConditions:      uc,
		NewFileSize:           10,
		NewWindowStart:        100,
		NewWindowEnd:          1000,
		NewRevisionNumber:     1,
		NewValidProofOutputs:  fc.ValidProofOutputs,
		NewMissedProofOutputs: fc.MissedProofOutputs,
	}

	// create transaction containing the revision
	signedTxn := types.Transaction{
		FileContractRevisions: []types.FileContractRevision{rev},
		TransactionSignatures: []types.TransactionSignature{{
			ParentID:       crypto.Hash(fcid),
			CoveredFields:  types.CoveredFields{FileContractRevisions: []uint64{0}},
			PublicKeyIndex: 0, // hostdb key is always first -- see negotiateContract
		}},
	}

	// sign the transaction
	encodedSig, err := crypto.SignHash(signedTxn.SigHash(0), sk)
	if err != nil {
		t.Fatal(err)
	}
	signedTxn.TransactionSignatures[0].Signature = encodedSig[:]

	err = signedTxn.StandaloneValid(ct.contractor.blockHeight)
	if err != nil {
		t.Fatal(err)
	}

	// submit revision
	err = ct.tpool.AcceptTransactionSet([]types.Transaction{signedTxn})
	if err != nil {
		t.Fatal(err)
	}
}
Example #7
0
// testFileContractRevision creates and revises a file contract on the
// blockchain.
func (cst *consensusSetTester) testFileContractRevision() {
	// COMPATv0.4.0 - Step the block height up past the hardfork amount. This
	// code stops nondeterministic failures when producing storage proofs that
	// is related to buggy old code.
	for cst.cs.dbBlockHeight() <= 10 {
		_, err := cst.miner.AddBlock()
		if err != nil {
			panic(err)
		}
	}

	// Create a file (as a bytes.Buffer) that will be used for the file
	// contract.
	filesize := uint64(4e3)
	file := randFile(filesize)
	merkleRoot, err := crypto.ReaderMerkleRoot(file)
	if err != nil {
		panic(err)
	}
	file.Seek(0, 0)

	// Create a spendable unlock hash for the file contract.
	sk, pk, err := crypto.GenerateKeyPair()
	if err != nil {
		panic(err)
	}
	uc := types.UnlockConditions{
		PublicKeys: []types.SiaPublicKey{{
			Algorithm: types.SignatureEd25519,
			Key:       pk[:],
		}},
		SignaturesRequired: 1,
	}

	// Create a file contract that will be revised.
	validProofDest := randAddress()
	payout := types.NewCurrency64(400e6)
	fc := types.FileContract{
		FileSize:       filesize,
		FileMerkleRoot: crypto.Hash{},
		WindowStart:    cst.cs.dbBlockHeight() + 2,
		WindowEnd:      cst.cs.dbBlockHeight() + 3,
		Payout:         payout,
		ValidProofOutputs: []types.SiacoinOutput{{
			UnlockHash: validProofDest,
			Value:      types.PostTax(cst.cs.dbBlockHeight(), payout),
		}},
		MissedProofOutputs: []types.SiacoinOutput{{
			UnlockHash: types.UnlockHash{},
			Value:      types.PostTax(cst.cs.dbBlockHeight(), payout),
		}},
		UnlockHash: uc.UnlockHash(),
	}

	// Submit a transaction with the file contract.
	txnBuilder := cst.wallet.StartTransaction()
	err = txnBuilder.FundSiacoins(payout)
	if err != nil {
		panic(err)
	}
	fcIndex := txnBuilder.AddFileContract(fc)
	txnSet, err := txnBuilder.Sign(true)
	if err != nil {
		panic(err)
	}
	err = cst.tpool.AcceptTransactionSet(txnSet)
	if err != nil {
		panic(err)
	}
	_, err = cst.miner.AddBlock()
	if err != nil {
		panic(err)
	}

	// Submit a revision for the file contract.
	ti := len(txnSet) - 1
	fcid := txnSet[ti].FileContractID(fcIndex)
	fcr := types.FileContractRevision{
		ParentID:          fcid,
		UnlockConditions:  uc,
		NewRevisionNumber: 69292,

		NewFileSize:           filesize,
		NewFileMerkleRoot:     merkleRoot,
		NewWindowStart:        cst.cs.dbBlockHeight() + 1,
		NewWindowEnd:          cst.cs.dbBlockHeight() + 2,
		NewValidProofOutputs:  fc.ValidProofOutputs,
		NewMissedProofOutputs: fc.MissedProofOutputs,
		NewUnlockHash:         uc.UnlockHash(),
	}
	ts := types.TransactionSignature{
		ParentID:       crypto.Hash(fcid),
		CoveredFields:  types.CoveredFields{WholeTransaction: true},
		PublicKeyIndex: 0,
	}
	txn := types.Transaction{
		FileContractRevisions: []types.FileContractRevision{fcr},
		TransactionSignatures: []types.TransactionSignature{ts},
	}
	encodedSig, err := crypto.SignHash(txn.SigHash(0), sk)
	if err != nil {
		panic(err)
	}
	txn.TransactionSignatures[0].Signature = encodedSig[:]
	err = cst.tpool.AcceptTransactionSet([]types.Transaction{txn})
	if err != nil {
		panic(err)
	}
	_, err = cst.miner.AddBlock()
	if err != nil {
		panic(err)
	}

	// Create and submit a storage proof for the file contract.
	segmentIndex, err := cst.cs.StorageProofSegment(fcid)
	if err != nil {
		panic(err)
	}
	segment, hashSet, err := crypto.BuildReaderProof(file, segmentIndex)
	if err != nil {
		panic(err)
	}
	sp := types.StorageProof{
		ParentID: fcid,
		HashSet:  hashSet,
	}
	copy(sp.Segment[:], segment)
	txnBuilder = cst.wallet.StartTransaction()
	txnBuilder.AddStorageProof(sp)
	txnSet, err = txnBuilder.Sign(true)
	if err != nil {
		panic(err)
	}
	err = cst.tpool.AcceptTransactionSet(txnSet)
	if err != nil {
		panic(err)
	}
	_, err = cst.miner.AddBlock()
	if err != nil {
		panic(err)
	}

	// Check that the file contract has been removed.
	_, err = cst.cs.dbGetFileContract(fcid)
	if err != errNilItem {
		panic("file contract should not exist in the database")
	}
}
Example #8
0
// TestThreadedProbeHosts tests the threadedProbeHosts method.
func TestThreadedProbeHosts(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	hdb := bareHostDB()
	hdb.persist = &memPersist{}

	// create a host to send to threadedProbeHosts
	sk, pk, err := crypto.GenerateKeyPair()
	if err != nil {
		t.Fatal(err)
	}
	h := new(hostEntry)
	h.NetAddress = "foo"
	h.AcceptingContracts = true
	h.PublicKey = types.SiaPublicKey{
		Algorithm: types.SignatureEd25519,
		Key:       pk[:],
	}
	h.Reliability = baseWeight // enough to withstand a few failures

	// define a helper function for running threadedProbeHosts. We send the
	// hostEntry, close the channel, and then call threadedProbeHosts.
	// threadedProbeHosts will receive the host, loop once, and return after
	// seeing the channel has closed.
	//
	// NOTE: since threadedProbeHosts decrements hdb.threadGroup, we Add(100)
	// to prevent it from going negative. This is acceptable because we don't
	// call hdb.Close in this test.
	hdb.threadGroup.Add(100)
	runProbe := func(h *hostEntry) {
		hdb.scanPool <- h
		close(hdb.scanPool)
		hdb.threadedProbeHosts()
		// reset hdb.scanPool
		hdb.scanPool = make(chan *hostEntry, 1)
	}

	// make the dial fail
	hdb.dialer = probeDialer(func(modules.NetAddress, time.Duration) (net.Conn, error) {
		return nil, net.UnknownNetworkError("fail")
	})
	runProbe(h)
	if len(hdb.ActiveHosts()) != 0 {
		t.Error("unresponsive host was added")
	}

	// make the RPC fail
	hdb.dialer = probeDialer(func(modules.NetAddress, time.Duration) (net.Conn, error) {
		ourPipe, theirPipe := net.Pipe()
		ourPipe.Close()
		return theirPipe, nil
	})
	runProbe(h)
	if len(hdb.ActiveHosts()) != 0 {
		t.Error("unresponsive host was added")
	}

	// normal host
	hdb.dialer = probeDialer(func(modules.NetAddress, time.Duration) (net.Conn, error) {
		// create an in-memory conn and spawn a goroutine to handle our half
		ourConn, theirConn := net.Pipe()
		go func() {
			// read the RPC
			encoding.ReadObject(ourConn, new(types.Specifier), types.SpecifierLen)
			// write host settings
			crypto.WriteSignedObject(ourConn, modules.HostExternalSettings{
				AcceptingContracts: true,
				NetAddress:         "probed",
			}, sk)
			ourConn.Close()
		}()
		return theirConn, nil
	})
	runProbe(h)
	if len(hdb.ActiveHosts()) != 1 {
		t.Error("host was not added")
	}
}
Example #9
0
// TestThreadedProbeHostsCorruption tests the threadedProbeHosts method,
// specifically checking for corruption of the hostdb if the weight of a host
// changes after a scan.
func TestThreadedProbeHostsCorruption(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}

	hdb := bareHostDB()
	hdb.persist = &memPersist{}

	// create a host to send to threadedProbeHosts
	sk, pk, err := crypto.GenerateKeyPair()
	if err != nil {
		t.Fatal(err)
	}
	h := new(hostEntry)
	h.NetAddress = "foo"
	h.AcceptingContracts = true
	h.PublicKey = types.SiaPublicKey{
		Algorithm: types.SignatureEd25519,
		Key:       pk[:],
	}
	h.Reliability = baseWeight // enough to withstand a few failures

	// define a helper function for running threadedProbeHosts. We send the
	// hostEntry, close the channel, and then call threadedProbeHosts.
	// threadedProbeHosts will receive the host, loop once, and return after
	// seeing the channel has closed.
	//
	// NOTE: since threadedProbeHosts decrements hdb.threadGroup, we Add(100)
	// to prevent it from going negative. This is acceptable because we don't
	// call hdb.Close in this test.
	hdb.threadGroup.Add(100)
	runProbe := func(h *hostEntry) {
		hdb.scanPool <- h
		close(hdb.scanPool)
		hdb.threadedProbeHosts()
		// reset hdb.scanPool
		hdb.scanPool = make(chan *hostEntry, 1)
	}

	// Add a normal host.
	hdb.dialer = probeDialer(func(modules.NetAddress, time.Duration) (net.Conn, error) {
		// create an in-memory conn and spawn a goroutine to handle our half
		ourConn, theirConn := net.Pipe()
		go func() {
			// read the RPC
			encoding.ReadObject(ourConn, new(types.Specifier), types.SpecifierLen)
			// write host settings
			crypto.WriteSignedObject(ourConn, modules.HostExternalSettings{
				AcceptingContracts: true,
				StoragePrice:       types.NewCurrency64(15e6),
				NetAddress:         "probed",
			}, sk)
			ourConn.Close()
		}()
		return theirConn, nil
	})
	runProbe(h)
	if len(hdb.ActiveHosts()) != 1 {
		t.Error("host was not added")
	}

	// Add the host again, this time changing the storage price, which will
	// change the weight of the host, which at one point would cause a
	// corruption of the host tree.
	hdb.dialer = probeDialer(func(modules.NetAddress, time.Duration) (net.Conn, error) {
		// create an in-memory conn and spawn a goroutine to handle our half
		ourConn, theirConn := net.Pipe()
		go func() {
			// read the RPC
			encoding.ReadObject(ourConn, new(types.Specifier), types.SpecifierLen)
			// write host settings
			crypto.WriteSignedObject(ourConn, modules.HostExternalSettings{
				AcceptingContracts: true,
				StoragePrice:       types.NewCurrency64(15e3), // Lower than the previous, to cause a higher weight.
				NetAddress:         "probed",
			}, sk)
			ourConn.Close()
		}()
		return theirConn, nil
	})
	runProbe(h)
	if len(hdb.ActiveHosts()) != 1 {
		t.Error("host was not added")
	}

	// Check that the host tree has not been corrupted.
	err = repeatCheck(hdb.hostTree)
	if err != nil {
		t.Error(err)
	}
	err = uniformTreeVerification(hdb, 1)
	if err != nil {
		t.Error(err)
	}
}
Example #10
0
// negotiateContract establishes a connection to a host and negotiates an
// initial file contract according to the terms of the host.
func negotiateContract(conn net.Conn, addr modules.NetAddress, fc types.FileContract, txnBuilder transactionBuilder, tpool transactionPool) (hostContract, error) {
	// allow 30 seconds for negotiation
	conn.SetDeadline(time.Now().Add(30 * time.Second))

	// read host key
	var hostPublicKey types.SiaPublicKey
	if err := encoding.ReadObject(conn, &hostPublicKey, 256); err != nil {
		return hostContract{}, errors.New("couldn't read host's public key: " + err.Error())
	}

	// create our key
	ourSK, ourPK, err := crypto.GenerateKeyPair()
	if err != nil {
		return hostContract{}, errors.New("failed to generate keypair: " + err.Error())
	}
	ourPublicKey := types.SiaPublicKey{
		Algorithm: types.SignatureEd25519,
		Key:       ourPK[:],
	}

	// send our public key
	if err := encoding.WriteObject(conn, ourPublicKey); err != nil {
		return hostContract{}, errors.New("couldn't send our public key: " + err.Error())
	}

	// create unlock conditions
	uc := types.UnlockConditions{
		PublicKeys:         []types.SiaPublicKey{ourPublicKey, hostPublicKey},
		SignaturesRequired: 2,
	}

	// add UnlockHash to file contract
	fc.UnlockHash = uc.UnlockHash()

	// build transaction containing fc
	err = txnBuilder.FundSiacoins(fc.Payout)
	if err != nil {
		return hostContract{}, err
	}
	txnBuilder.AddFileContract(fc)
	txn, parents := txnBuilder.View()
	txnSet := append(parents, txn)

	// calculate contract ID
	fcid := txn.FileContractID(0) // TODO: is it actually 0?

	// send txn
	if err := encoding.WriteObject(conn, txnSet); err != nil {
		return hostContract{}, errors.New("couldn't send our proposed contract: " + err.Error())
	}

	// read back acceptance
	var response string
	if err := encoding.ReadObject(conn, &response, 128); err != nil {
		return hostContract{}, errors.New("couldn't read the host's response to our proposed contract: " + err.Error())
	}
	if response != modules.AcceptResponse {
		return hostContract{}, errors.New("host rejected proposed contract: " + response)
	}

	// read back txn with host collateral.
	var hostTxnSet []types.Transaction
	if err := encoding.ReadObject(conn, &hostTxnSet, types.BlockSizeLimit); err != nil {
		return hostContract{}, errors.New("couldn't read the host's updated contract: " + err.Error())
	}

	// check that txn is okay. For now, no collateral will be added, so the
	// transaction sets should be identical.
	if len(hostTxnSet) != len(txnSet) {
		return hostContract{}, errors.New("host sent bad collateral transaction")
	}
	for i := range hostTxnSet {
		if hostTxnSet[i].ID() != txnSet[i].ID() {
			return hostContract{}, errors.New("host sent bad collateral transaction")
		}
	}

	// sign the txn and resend
	// NOTE: for now, we are assuming that the transaction has not changed
	// since we sent it. Otherwise, the txnBuilder would have to be updated
	// with whatever fields were added by the host.
	signedTxnSet, err := txnBuilder.Sign(true)
	if err != nil {
		return hostContract{}, err
	}
	if err := encoding.WriteObject(conn, signedTxnSet); err != nil {
		return hostContract{}, errors.New("couldn't send the contract signed by us: " + err.Error())
	}

	// read signed txn from host
	var signedHostTxnSet []types.Transaction
	if err := encoding.ReadObject(conn, &signedHostTxnSet, types.BlockSizeLimit); err != nil {
		return hostContract{}, errors.New("couldn't read the contract signed by the host: " + err.Error())
	}

	// submit to blockchain
	err = tpool.AcceptTransactionSet(signedHostTxnSet)
	if err == modules.ErrDuplicateTransactionSet {
		// this can happen if the renter is uploading to itself
		err = nil
	}
	if err != nil {
		return hostContract{}, err
	}

	// create host contract
	hc := hostContract{
		IP:           addr,
		ID:           fcid,
		FileContract: fc,
		LastRevision: types.FileContractRevision{
			ParentID:              fcid,
			UnlockConditions:      uc,
			NewRevisionNumber:     fc.RevisionNumber,
			NewFileSize:           fc.FileSize,
			NewFileMerkleRoot:     fc.FileMerkleRoot,
			NewWindowStart:        fc.WindowStart,
			NewWindowEnd:          fc.WindowEnd,
			NewValidProofOutputs:  []types.SiacoinOutput{fc.ValidProofOutputs[0], fc.ValidProofOutputs[1]},
			NewMissedProofOutputs: []types.SiacoinOutput{fc.MissedProofOutputs[0], fc.MissedProofOutputs[1]},
			NewUnlockHash:         fc.UnlockHash,
		},
		LastRevisionTxn: types.Transaction{},
		SecretKey:       ourSK,
	}

	return hc, nil
}
Example #11
0
// TestAnnouncementHandling checks that CreateAnnouncement and
// DecodeAnnouncement work together correctly.
func TestAnnouncementHandling(t *testing.T) {
	t.Parallel()

	// Create the keys that will be used to generate the announcement.
	sk, pk, err := crypto.GenerateKeyPair()
	if err != nil {
		t.Fatal(err)
	}
	spk := types.SiaPublicKey{
		Algorithm: types.SignatureEd25519,
		Key:       pk[:],
	}
	addr := NetAddress("f.o:1234")

	// Generate the announcement.
	annBytes, err := CreateAnnouncement(addr, spk, sk)
	if err != nil {
		t.Fatal(err)
	}

	// Decode the announcement
	decAddr, decPubKey, err := DecodeAnnouncement(annBytes)
	if err != nil {
		t.Fatal(err)
	}
	if decPubKey.Algorithm != spk.Algorithm {
		t.Error("decoded announcement has the wrong algorithm on the public key")
	}
	if decAddr != addr {
		t.Error("decoded announcement has the wrong net address")
	}
	if !bytes.Equal(decPubKey.Key, spk.Key) {
		t.Error("decoded announcement has the wrong public key")
	}

	// Corrupt the data, and see that decoding fails. Decoding should fail
	// because the signature should not be valid anymore.
	//
	// First 16 bytes are the host announcement prefix, followed by 8 bytes
	// describing the length of the net address, followed by the net address.
	// Corrupt the net address.
	annBytes[25]++
	_, _, err = DecodeAnnouncement(annBytes)
	if err != crypto.ErrInvalidSignature {
		t.Error(err)
	}
	annBytes[25]--

	// The final byte is going to be a part of the signature. Corrupt the final
	// byte and verify that there's an error.
	lastIndex := len(annBytes) - 1
	annBytes[lastIndex]++
	_, _, err = DecodeAnnouncement(annBytes)
	if err != crypto.ErrInvalidSignature {
		t.Error(err)
	}
	annBytes[lastIndex]--

	// Pass in a bad specifier - change the host announcement type.
	annBytes[0]++
	_, _, err = DecodeAnnouncement(annBytes)
	if err != ErrAnnNotAnnouncement {
		t.Error(err)
	}
	annBytes[0]--

	// Pass in a bad signature algorithm. 16 bytes to pass the specifier, 8+8 bytes to pass the net address.
	annBytes[33]++
	_, _, err = DecodeAnnouncement(annBytes)
	if err != ErrAnnUnrecognizedSignature {
		t.Error(err)
	}
	annBytes[33]--

	// Cause the decoding to fail altogether.
	_, _, err = DecodeAnnouncement(annBytes[:12])
	if err == nil {
		t.Error(err)
	}
}
Example #12
0
// FormContract forms a contract with a host and submits the contract
// transaction to tpool.
func FormContract(params ContractParams, txnBuilder transactionBuilder, tpool transactionPool) (modules.RenterContract, error) {
	// extract vars from params, for convenience
	host, filesize, startHeight, endHeight, refundAddress := params.Host, params.Filesize, params.StartHeight, params.EndHeight, params.RefundAddress

	// create our key
	ourSK, ourPK, err := crypto.GenerateKeyPair()
	if err != nil {
		return modules.RenterContract{}, err
	}
	ourPublicKey := types.SiaPublicKey{
		Algorithm: types.SignatureEd25519,
		Key:       ourPK[:],
	}
	// create unlock conditions
	uc := types.UnlockConditions{
		PublicKeys:         []types.SiaPublicKey{ourPublicKey, host.PublicKey},
		SignaturesRequired: 2,
	}

	// calculate cost to renter and cost to host
	// TODO: clarify/abstract this math
	storageAllocation := host.StoragePrice.Mul64(filesize).Mul64(uint64(endHeight - startHeight))
	hostCollateral := host.Collateral.Mul64(filesize).Mul64(uint64(endHeight - startHeight))
	if hostCollateral.Cmp(host.MaxCollateral) > 0 {
		// TODO: if we have to cap the collateral, it probably means we shouldn't be using this host
		// (ok within a factor of 2)
		hostCollateral = host.MaxCollateral
	}
	hostPayout := hostCollateral.Add(host.ContractPrice)
	payout := storageAllocation.Add(hostPayout).Mul64(10406).Div64(10000) // renter pays for siafund fee
	renterCost := payout.Sub(hostCollateral)

	// check for negative currency
	if types.PostTax(startHeight, payout).Cmp(hostPayout) < 0 {
		return modules.RenterContract{}, errors.New("payout smaller than host payout")
	}

	// create file contract
	fc := types.FileContract{
		FileSize:       0,
		FileMerkleRoot: crypto.Hash{}, // no proof possible without data
		WindowStart:    endHeight,
		WindowEnd:      endHeight + host.WindowSize,
		Payout:         payout,
		UnlockHash:     uc.UnlockHash(),
		RevisionNumber: 0,
		ValidProofOutputs: []types.SiacoinOutput{
			// outputs need to account for tax
			{Value: types.PostTax(startHeight, payout).Sub(hostPayout), UnlockHash: refundAddress},
			// collateral is returned to host
			{Value: hostPayout, UnlockHash: host.UnlockHash},
		},
		MissedProofOutputs: []types.SiacoinOutput{
			// same as above
			{Value: types.PostTax(startHeight, payout).Sub(hostPayout), UnlockHash: refundAddress},
			// same as above
			{Value: hostPayout, UnlockHash: host.UnlockHash},
			// once we start doing revisions, we'll move some coins to the host and some to the void
			{Value: types.ZeroCurrency, UnlockHash: types.UnlockHash{}},
		},
	}

	// calculate transaction fee
	_, maxFee := tpool.FeeEstimation()
	fee := maxFee.Mul64(estTxnSize)

	// build transaction containing fc
	err = txnBuilder.FundSiacoins(renterCost.Add(fee))
	if err != nil {
		return modules.RenterContract{}, err
	}
	txnBuilder.AddFileContract(fc)

	// add miner fee
	txnBuilder.AddMinerFee(fee)

	// create initial transaction set
	txn, parentTxns := txnBuilder.View()
	txnSet := append(parentTxns, txn)

	// initiate connection
	conn, err := net.DialTimeout("tcp", string(host.NetAddress), 15*time.Second)
	if err != nil {
		return modules.RenterContract{}, err
	}
	defer func() { _ = conn.Close() }()

	// allot time for sending RPC ID + verifySettings
	extendDeadline(conn, modules.NegotiateSettingsTime)
	if err = encoding.WriteObject(conn, modules.RPCFormContract); err != nil {
		return modules.RenterContract{}, err
	}

	// verify the host's settings and confirm its identity
	host, err = verifySettings(conn, host)
	if err != nil {
		return modules.RenterContract{}, err
	}
	if !host.AcceptingContracts {
		return modules.RenterContract{}, errors.New("host is not accepting contracts")
	}

	// allot time for negotiation
	extendDeadline(conn, modules.NegotiateFileContractTime)

	// send acceptance, txn signed by us, and pubkey
	if err = modules.WriteNegotiationAcceptance(conn); err != nil {
		return modules.RenterContract{}, errors.New("couldn't send initial acceptance: " + err.Error())
	}
	if err = encoding.WriteObject(conn, txnSet); err != nil {
		return modules.RenterContract{}, errors.New("couldn't send the contract signed by us: " + err.Error())
	}
	if err = encoding.WriteObject(conn, ourSK.PublicKey()); err != nil {
		return modules.RenterContract{}, errors.New("couldn't send our public key: " + err.Error())
	}

	// read acceptance and txn signed by host
	if err = modules.ReadNegotiationAcceptance(conn); err != nil {
		return modules.RenterContract{}, errors.New("host did not accept our proposed contract: " + err.Error())
	}
	// host now sends any new parent transactions, inputs and outputs that
	// were added to the transaction
	var newParents []types.Transaction
	var newInputs []types.SiacoinInput
	var newOutputs []types.SiacoinOutput
	if err = encoding.ReadObject(conn, &newParents, types.BlockSizeLimit); err != nil {
		return modules.RenterContract{}, errors.New("couldn't read the host's added parents: " + err.Error())
	}
	if err = encoding.ReadObject(conn, &newInputs, types.BlockSizeLimit); err != nil {
		return modules.RenterContract{}, errors.New("couldn't read the host's added inputs: " + err.Error())
	}
	if err = encoding.ReadObject(conn, &newOutputs, types.BlockSizeLimit); err != nil {
		return modules.RenterContract{}, errors.New("couldn't read the host's added outputs: " + err.Error())
	}

	// merge txnAdditions with txnSet
	txnBuilder.AddParents(newParents)
	for _, input := range newInputs {
		txnBuilder.AddSiacoinInput(input)
	}
	for _, output := range newOutputs {
		txnBuilder.AddSiacoinOutput(output)
	}

	// sign the txn
	signedTxnSet, err := txnBuilder.Sign(true)
	if err != nil {
		return modules.RenterContract{}, modules.WriteNegotiationRejection(conn, errors.New("failed to sign transaction: "+err.Error()))
	}

	// calculate signatures added by the transaction builder
	var addedSignatures []types.TransactionSignature
	_, _, _, addedSignatureIndices := txnBuilder.ViewAdded()
	for _, i := range addedSignatureIndices {
		addedSignatures = append(addedSignatures, signedTxnSet[len(signedTxnSet)-1].TransactionSignatures[i])
	}

	// create initial (no-op) revision, transaction, and signature
	initRevision := types.FileContractRevision{
		ParentID:          signedTxnSet[len(signedTxnSet)-1].FileContractID(0),
		UnlockConditions:  uc,
		NewRevisionNumber: 1,

		NewFileSize:           fc.FileSize,
		NewFileMerkleRoot:     fc.FileMerkleRoot,
		NewWindowStart:        fc.WindowStart,
		NewWindowEnd:          fc.WindowEnd,
		NewValidProofOutputs:  fc.ValidProofOutputs,
		NewMissedProofOutputs: fc.MissedProofOutputs,
		NewUnlockHash:         fc.UnlockHash,
	}
	renterRevisionSig := types.TransactionSignature{
		ParentID:       crypto.Hash(initRevision.ParentID),
		PublicKeyIndex: 0,
		CoveredFields: types.CoveredFields{
			FileContractRevisions: []uint64{0},
		},
	}
	revisionTxn := types.Transaction{
		FileContractRevisions: []types.FileContractRevision{initRevision},
		TransactionSignatures: []types.TransactionSignature{renterRevisionSig},
	}
	encodedSig, err := crypto.SignHash(revisionTxn.SigHash(0), ourSK)
	if err != nil {
		return modules.RenterContract{}, modules.WriteNegotiationRejection(conn, errors.New("failed to sign revision transaction: "+err.Error()))
	}
	revisionTxn.TransactionSignatures[0].Signature = encodedSig[:]

	// Send acceptance and signatures
	if err = modules.WriteNegotiationAcceptance(conn); err != nil {
		return modules.RenterContract{}, errors.New("couldn't send transaction acceptance: " + err.Error())
	}
	if err = encoding.WriteObject(conn, addedSignatures); err != nil {
		return modules.RenterContract{}, errors.New("couldn't send added signatures: " + err.Error())
	}
	if err = encoding.WriteObject(conn, revisionTxn.TransactionSignatures[0]); err != nil {
		return modules.RenterContract{}, errors.New("couldn't send revision signature: " + err.Error())
	}

	// Read the host acceptance and signatures.
	err = modules.ReadNegotiationAcceptance(conn)
	if err != nil {
		return modules.RenterContract{}, errors.New("host did not accept our signatures: " + err.Error())
	}
	var hostSigs []types.TransactionSignature
	if err = encoding.ReadObject(conn, &hostSigs, 2e3); err != nil {
		return modules.RenterContract{}, errors.New("couldn't read the host's signatures: " + err.Error())
	}
	for _, sig := range hostSigs {
		txnBuilder.AddTransactionSignature(sig)
	}
	var hostRevisionSig types.TransactionSignature
	if err = encoding.ReadObject(conn, &hostRevisionSig, 2e3); err != nil {
		return modules.RenterContract{}, errors.New("couldn't read the host's revision signature: " + err.Error())
	}
	revisionTxn.TransactionSignatures = append(revisionTxn.TransactionSignatures, hostRevisionSig)

	// Construct the final transaction.
	txn, parentTxns = txnBuilder.View()
	txnSet = append(parentTxns, txn)

	// Submit to blockchain.
	err = tpool.AcceptTransactionSet(txnSet)
	if err == modules.ErrDuplicateTransactionSet {
		// as long as it made it into the transaction pool, we're good
		err = nil
	}
	if err != nil {
		return modules.RenterContract{}, err
	}

	// calculate contract ID
	fcid := txn.FileContractID(0)

	return modules.RenterContract{
		FileContract:    fc,
		ID:              fcid,
		LastRevision:    initRevision,
		LastRevisionTxn: revisionTxn,
		NetAddress:      host.NetAddress,
		SecretKey:       ourSK,
	}, nil
}