Beispiel #1
0
// applyFileContracts iterates through all of the file contracts in a
// transaction and applies them to the state, updating the diffs in the block
// node.
func (cs *State) applyFileContracts(bn *blockNode, t types.Transaction) {
	for i, fc := range t.FileContracts {
		// Sanity check - the file contract should not exists within the state.
		fcid := t.FileContractID(i)
		if build.DEBUG {
			_, exists := cs.fileContracts[fcid]
			if exists {
				panic(ErrMisuseApplyFileContracts)
			}
		}

		fcd := modules.FileContractDiff{
			Direction:    modules.DiffApply,
			ID:           fcid,
			FileContract: fc,
		}
		bn.fileContractDiffs = append(bn.fileContractDiffs, fcd)
		cs.commitFileContractDiff(fcd, modules.DiffApply)

		// Get the portion of the contract that goes into the siafund pool and
		// add it to the siafund pool.
		sfpd := modules.SiafundPoolDiff{
			Previous: cs.siafundPool,
			Adjusted: cs.siafundPool.Add(fc.Tax()),
		}
		bn.siafundPoolDiffs = append(bn.siafundPoolDiffs, sfpd)
		cs.commitSiafundPoolDiff(sfpd, modules.DiffApply)
	}
	return
}
Beispiel #2
0
Datei: update.go Projekt: mm3/Sia
// removeFileContracts removes all of the file contracts of a transaction from
// the unconfirmed consensus set.
func (tp *TransactionPool) removeFileContracts(t types.Transaction) {
	for i, _ := range t.FileContracts {
		fcid := t.FileContractID(i)
		// Sanity check - file contract should be in the unconfirmed set as
		// there should be no dependent transactions who have terminated the
		// contract.
		if build.DEBUG {
			_, exists := tp.fileContracts[fcid]
			if !exists {
				panic("trying to remove missing file contract")
			}
		}

		delete(tp.fileContracts, fcid)
	}
}
Beispiel #3
0
// TestDuplicateStorageProof applies a storage proof which has already been
// applied.
func TestDuplicateStorageProof(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	cst, err := createConsensusSetTester("TestDuplicateStorageProof")
	if err != nil {
		t.Fatal(err)
	}

	// Create a block node.
	bn := new(blockNode)
	bn.height = cst.cs.height()

	// Create a file contract for the storage proof to prove.
	txn0 := types.Transaction{
		FileContracts: []types.FileContract{
			{
				Payout: types.NewCurrency64(300e3),
				ValidProofOutputs: []types.SiacoinOutput{
					{Value: types.NewCurrency64(290e3)},
				},
			},
		},
	}
	cst.cs.applyFileContracts(bn, txn0)
	fcid := txn0.FileContractID(0)

	// Apply a single storage proof.
	txn1 := types.Transaction{
		StorageProofs: []types.StorageProof{{ParentID: fcid}},
	}
	cst.cs.applyStorageProofs(bn, txn1)

	// Trigger a panic by applying the storage proof again.
	defer func() {
		r := recover()
		if r != ErrDuplicateValidProofOutput {
			t.Error("failed to trigger ErrDuplicateValidProofOutput:", r)
		}
	}()
	cst.cs.applyFileContracts(bn, txn0) // File contract was consumed by the first proof.
	cst.cs.applyStorageProofs(bn, txn1)
}
Beispiel #4
0
// applyFileContracts iterates through all of the file contracts in a
// transaction and applies them to the state, updating the diffs in the proccesed
// block.
func applyFileContracts(tx *bolt.Tx, pb *processedBlock, t types.Transaction) {
	for i, fc := range t.FileContracts {
		fcid := t.FileContractID(uint64(i))
		fcd := modules.FileContractDiff{
			Direction:    modules.DiffApply,
			ID:           fcid,
			FileContract: fc,
		}
		pb.FileContractDiffs = append(pb.FileContractDiffs, fcd)
		commitFileContractDiff(tx, fcd, modules.DiffApply)

		// Get the portion of the contract that goes into the siafund pool and
		// add it to the siafund pool.
		sfp := getSiafundPool(tx)
		sfpd := modules.SiafundPoolDiff{
			Direction: modules.DiffApply,
			Previous:  sfp,
			Adjusted:  sfp.Add(types.Tax(blockHeight(tx), fc.Payout)),
		}
		pb.SiafundPoolDiffs = append(pb.SiafundPoolDiffs, sfpd)
		commitSiafundPoolDiff(tx, sfpd, modules.DiffApply)
	}
}
Beispiel #5
0
// applyFileContracts iterates through all of the file contracts in a
// transaction and applies them to the state, updating the diffs in the proccesed
// block.
func (cs *ConsensusSet) applyFileContracts(tx *bolt.Tx, pb *processedBlock, t types.Transaction) error {
	for i, fc := range t.FileContracts {
		fcid := t.FileContractID(i)
		fcd := modules.FileContractDiff{
			Direction:    modules.DiffApply,
			ID:           fcid,
			FileContract: fc,
		}
		pb.FileContractDiffs = append(pb.FileContractDiffs, fcd)
		cs.commitTxFileContractDiff(tx, fcd, modules.DiffApply)

		// Get the portion of the contract that goes into the siafund pool and
		// add it to the siafund pool.
		sfp := getSiafundPool(tx)
		sfpd := modules.SiafundPoolDiff{
			Direction: modules.DiffApply,
			Previous:  sfp,
			Adjusted:  sfp.Add(fc.Tax()),
		}
		pb.SiafundPoolDiffs = append(pb.SiafundPoolDiffs, sfpd)
		cs.commitTxSiafundPoolDiff(tx, sfpd, modules.DiffApply)
	}
	return nil
}
Beispiel #6
0
// buildExplorerTransaction takes a transaction and the height + id of the
// block it appears in an uses that to build an explorer transaction.
func (srv *Server) buildExplorerTransaction(height types.BlockHeight, parent types.BlockID, txn types.Transaction) (et ExplorerTransaction) {
	// Get the header information for the transaction.
	et.ID = txn.ID()
	et.Height = height
	et.Parent = parent
	et.RawTransaction = txn

	// Add the siacoin outputs that correspond with each siacoin input.
	for _, sci := range txn.SiacoinInputs {
		sco, exists := srv.explorer.SiacoinOutput(sci.ParentID)
		if build.DEBUG && !exists {
			panic("could not find corresponding siacoin output")
		}
		et.SiacoinInputOutputs = append(et.SiacoinInputOutputs, sco)
	}

	for i := range txn.SiacoinOutputs {
		et.SiacoinOutputIDs = append(et.SiacoinOutputIDs, txn.SiacoinOutputID(uint64(i)))
	}

	// Add all of the valid and missed proof ids as extra data to the file
	// contracts.
	for i, fc := range txn.FileContracts {
		fcid := txn.FileContractID(uint64(i))
		var fcvpoids []types.SiacoinOutputID
		var fcmpoids []types.SiacoinOutputID
		for j := range fc.ValidProofOutputs {
			fcvpoids = append(fcvpoids, fcid.StorageProofOutputID(types.ProofValid, uint64(j)))
		}
		for j := range fc.MissedProofOutputs {
			fcmpoids = append(fcmpoids, fcid.StorageProofOutputID(types.ProofMissed, uint64(j)))
		}
		et.FileContractIDs = append(et.FileContractIDs, fcid)
		et.FileContractValidProofOutputIDs = append(et.FileContractValidProofOutputIDs, fcvpoids)
		et.FileContractMissedProofOutputIDs = append(et.FileContractMissedProofOutputIDs, fcmpoids)
	}

	// Add all of the valid and missed proof ids as extra data to the file
	// contract revisions.
	for _, fcr := range txn.FileContractRevisions {
		var fcrvpoids []types.SiacoinOutputID
		var fcrmpoids []types.SiacoinOutputID
		for j := range fcr.NewValidProofOutputs {
			fcrvpoids = append(fcrvpoids, fcr.ParentID.StorageProofOutputID(types.ProofValid, uint64(j)))
		}
		for j := range fcr.NewMissedProofOutputs {
			fcrmpoids = append(fcrmpoids, fcr.ParentID.StorageProofOutputID(types.ProofMissed, uint64(j)))
		}
		et.FileContractValidProofOutputIDs = append(et.FileContractValidProofOutputIDs, fcrvpoids)
		et.FileContractMissedProofOutputIDs = append(et.FileContractMissedProofOutputIDs, fcrmpoids)
	}

	// Add all of the output ids and outputs corresponding with each storage
	// proof.
	for _, sp := range txn.StorageProofs {
		fileContract, fileContractRevisions, fileContractExists, _ := srv.explorer.FileContractHistory(sp.ParentID)
		if !fileContractExists && build.DEBUG {
			panic("could not find a file contract connected with a storage proof")
		}
		var storageProofOutputs []types.SiacoinOutput
		if len(fileContractRevisions) > 0 {
			storageProofOutputs = fileContractRevisions[len(fileContractRevisions)-1].NewValidProofOutputs
		} else {
			storageProofOutputs = fileContract.ValidProofOutputs
		}
		var storageProofOutputIDs []types.SiacoinOutputID
		for i := range storageProofOutputs {
			storageProofOutputIDs = append(storageProofOutputIDs, sp.ParentID.StorageProofOutputID(types.ProofValid, uint64(i)))
		}
		et.StorageProofOutputIDs = append(et.StorageProofOutputIDs, storageProofOutputIDs)
		et.StorageProofOutputs = append(et.StorageProofOutputs, storageProofOutputs)
	}

	// Add the siafund outputs that correspond to each siacoin input.
	for _, sci := range txn.SiafundInputs {
		sco, exists := srv.explorer.SiafundOutput(sci.ParentID)
		if build.DEBUG && !exists {
			panic("could not find corresponding siafund output")
		}
		et.SiafundInputOutputs = append(et.SiafundInputOutputs, sco)
	}

	for i := range txn.SiafundOutputs {
		et.SiafundOutputIDs = append(et.SiafundOutputIDs, txn.SiafundOutputID(uint64(i)))
	}

	for _, sfi := range txn.SiafundInputs {
		et.SiaClaimOutputIDs = append(et.SiaClaimOutputIDs, sfi.ParentID.SiaClaimOutputID())
	}
	return et
}
Beispiel #7
0
// addTransaction is called from addBlockDB, and delegates the adding
// of information to the database to the functions defined above
func (tx *boltTx) addTransaction(txn types.Transaction) {
	// Store this for quick lookup
	txid := txn.ID()

	// Append each input to the list of modifications
	for _, input := range txn.SiacoinInputs {
		tx.addAddress(input.UnlockConditions.UnlockHash(), txid)
		tx.addSiacoinInput(input.ParentID, txid)
	}

	// Handle all the transaction outputs
	for i, output := range txn.SiacoinOutputs {
		tx.addAddress(output.UnlockHash, txid)
		tx.addNewOutput(txn.SiacoinOutputID(uint64(i)), txid)
	}

	// Handle each file contract individually
	for i, contract := range txn.FileContracts {
		fcid := txn.FileContractID(uint64(i))
		tx.addNewHash("FileContracts", hashFilecontract, crypto.Hash(fcid), fcInfo{
			Contract: txid,
		})

		for j, output := range contract.ValidProofOutputs {
			tx.addAddress(output.UnlockHash, txid)
			tx.addNewOutput(fcid.StorageProofOutputID(true, uint64(j)), txid)
		}
		for j, output := range contract.MissedProofOutputs {
			tx.addAddress(output.UnlockHash, txid)
			tx.addNewOutput(fcid.StorageProofOutputID(false, uint64(j)), txid)
		}

		tx.addAddress(contract.UnlockHash, txid)
	}

	// Update the list of revisions
	for _, revision := range txn.FileContractRevisions {
		tx.addFcRevision(revision.ParentID, txid)

		// Note the old outputs will still be there in the
		// database. This is to provide information to the
		// people who may just need it.
		for i, output := range revision.NewValidProofOutputs {
			tx.addAddress(output.UnlockHash, txid)
			tx.addNewOutput(revision.ParentID.StorageProofOutputID(true, uint64(i)), txid)
		}
		for i, output := range revision.NewMissedProofOutputs {
			tx.addAddress(output.UnlockHash, txid)
			tx.addNewOutput(revision.ParentID.StorageProofOutputID(false, uint64(i)), txid)
		}

		tx.addAddress(revision.NewUnlockHash, txid)
	}

	// Update the list of storage proofs
	for _, proof := range txn.StorageProofs {
		tx.addFcProof(proof.ParentID, txid)
	}

	// Append all the siafund inputs to the modification list
	for _, input := range txn.SiafundInputs {
		tx.addSiafundInput(input.ParentID, txid)
	}

	// Handle all the siafund outputs
	for i, output := range txn.SiafundOutputs {
		tx.addAddress(output.UnlockHash, txid)
		tx.addNewSFOutput(txn.SiafundOutputID(uint64(i)), txid)

	}

	tx.putObject("Hashes", txid, hashTransaction)
}
Beispiel #8
0
// TestApplyStorageProofs probes the applyStorageProofs method of the consensus
// set.
func TestApplyStorageProofs(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	cst, err := createConsensusSetTester("TestApplyStorageProofs")
	if err != nil {
		t.Fatal(err)
	}

	// Create a block node to use with application.
	bn := new(blockNode)
	bn.height = cst.cs.height()

	// Apply a transaction with two file contracts - there is a reason to
	// create a storage proof.
	txn := types.Transaction{
		FileContracts: []types.FileContract{
			{
				Payout: types.NewCurrency64(300e3),
				ValidProofOutputs: []types.SiacoinOutput{
					{Value: types.NewCurrency64(290e3)},
				},
			},
			{},
			{
				Payout: types.NewCurrency64(600e3),
				ValidProofOutputs: []types.SiacoinOutput{
					{Value: types.NewCurrency64(280e3)},
					{Value: types.NewCurrency64(300e3)},
				},
			},
		},
	}
	cst.cs.applyFileContracts(bn, txn)
	fcid0 := txn.FileContractID(0)
	fcid1 := txn.FileContractID(1)
	fcid2 := txn.FileContractID(2)

	// Apply a single storage proof.
	txn = types.Transaction{
		StorageProofs: []types.StorageProof{{ParentID: fcid0}},
	}
	cst.cs.applyStorageProofs(bn, txn)
	_, exists := cst.cs.fileContracts[fcid0]
	if exists {
		t.Error("Storage proof did not disable a file contract.")
	}
	if len(cst.cs.fileContracts) != 2 {
		t.Error("file contracts not correctly updated")
	}
	if len(bn.fileContractDiffs) != 4 { // 3 creating the initial contracts, 1 for the storage proof.
		t.Error("block node was not updated for single element transaction")
	}
	if bn.fileContractDiffs[3].Direction != modules.DiffRevert {
		t.Error("wrong diff direction applied when revising a file contract")
	}
	if bn.fileContractDiffs[3].ID != fcid0 {
		t.Error("wrong id used when revising a file contract")
	}
	spoid0 := fcid0.StorageProofOutputID(types.ProofValid, 0)
	sco, exists := cst.cs.delayedSiacoinOutputs[bn.height+types.MaturityDelay][spoid0]
	if !exists {
		t.Error("storage proof output not created after applying a storage proof")
	}
	if sco.Value.Cmp(types.NewCurrency64(290e3)) != 0 {
		t.Error("storage proof output was created with the wrong value")
	}

	// Apply a transaction with 2 storage proofs.
	txn = types.Transaction{
		StorageProofs: []types.StorageProof{
			{ParentID: fcid1},
			{ParentID: fcid2},
		},
	}
	cst.cs.applyStorageProofs(bn, txn)
	_, exists = cst.cs.fileContracts[fcid1]
	if exists {
		t.Error("Storage proof failed to consume file contract.")
	}
	_, exists = cst.cs.fileContracts[fcid2]
	if exists {
		t.Error("storage proof did not consume file contract")
	}
	if len(cst.cs.fileContracts) != 0 {
		t.Error("file contracts not correctly updated")
	}
	if len(bn.fileContractDiffs) != 6 {
		t.Error("block node was not updated correctly")
	}
	spoid1 := fcid1.StorageProofOutputID(types.ProofValid, 0)
	_, exists = cst.cs.siacoinOutputs[spoid1]
	if exists {
		t.Error("output created when file contract had no corresponding output")
	}
	spoid2 := fcid2.StorageProofOutputID(types.ProofValid, 0)
	sco, exists = cst.cs.delayedSiacoinOutputs[bn.height+types.MaturityDelay][spoid2]
	if !exists {
		t.Error("no output created by first output of file contract")
	}
	if sco.Value.Cmp(types.NewCurrency64(280e3)) != 0 {
		t.Error("first siacoin output created has wrong value")
	}
	spoid3 := fcid2.StorageProofOutputID(types.ProofValid, 1)
	sco, exists = cst.cs.delayedSiacoinOutputs[bn.height+types.MaturityDelay][spoid3]
	if !exists {
		t.Error("second output not created for storage proof")
	}
	if sco.Value.Cmp(types.NewCurrency64(300e3)) != 0 {
		t.Error("second siacoin output has wrong value")
	}
	if cst.cs.siafundPool.Cmp(types.NewCurrency64(30e3)) != 0 {
		t.Error("siafund pool not being added up correctly")
	}
}
Beispiel #9
0
// TestApplyFileContractRevisions probes the applyFileContractRevisions method
// of the consensus set.
func TestApplyFileContractRevisions(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	cst, err := createConsensusSetTester("TestApplyFileContractRevisions")
	if err != nil {
		t.Fatal(err)
	}

	// Create a block node to use with application.
	bn := new(blockNode)

	// Apply a transaction with two file contracts - that way there is
	// something to revise.
	txn := types.Transaction{
		FileContracts: []types.FileContract{
			{},
			{Payout: types.NewCurrency64(1)},
		},
	}
	cst.cs.applyFileContracts(bn, txn)
	fcid0 := txn.FileContractID(0)
	fcid1 := txn.FileContractID(1)

	// Apply a single file contract revision.
	txn = types.Transaction{
		FileContractRevisions: []types.FileContractRevision{
			{
				ParentID:    fcid0,
				NewFileSize: 1,
			},
		},
	}
	cst.cs.applyFileContractRevisions(bn, txn)
	fc, exists := cst.cs.fileContracts[fcid0]
	if !exists {
		t.Error("Revision killed a file contract")
	}
	if fc.FileSize != 1 {
		t.Error("file contract filesize not properly updated")
	}
	if len(cst.cs.fileContracts) != 2 {
		t.Error("file contracts not correctly updated")
	}
	if len(bn.fileContractDiffs) != 4 { // 2 creating the initial contracts, 1 to remove the old, 1 to add the revision.
		t.Error("block node was not updated for single element transaction")
	}
	if bn.fileContractDiffs[2].Direction != modules.DiffRevert {
		t.Error("wrong diff direction applied when revising a file contract")
	}
	if bn.fileContractDiffs[3].Direction != modules.DiffApply {
		t.Error("wrong diff direction applied when revising a file contract")
	}
	if bn.fileContractDiffs[2].ID != fcid0 {
		t.Error("wrong id used when revising a file contract")
	}
	if bn.fileContractDiffs[3].ID != fcid0 {
		t.Error("wrong id used when revising a file contract")
	}

	// Apply a transaction with 2 file contract revisions.
	txn = types.Transaction{
		FileContractRevisions: []types.FileContractRevision{
			{
				ParentID:    fcid0,
				NewFileSize: 2,
			},
			{
				ParentID:    fcid1,
				NewFileSize: 3,
			},
		},
	}
	cst.cs.applyFileContractRevisions(bn, txn)
	fc0, exists := cst.cs.fileContracts[fcid0]
	if !exists {
		t.Error("Revision ate file contract")
	}
	fc1, exists := cst.cs.fileContracts[fcid1]
	if !exists {
		t.Error("Revision ate file contract")
	}
	if fc0.FileSize != 2 {
		t.Error("Revision not correctly applied")
	}
	if fc1.FileSize != 3 {
		t.Error("Revision not correctly applied")
	}
	if len(cst.cs.fileContracts) != 2 {
		t.Error("file contracts not correctly updated")
	}
	if len(bn.fileContractDiffs) != 8 {
		t.Error("block node was not updated correctly")
	}
}
Beispiel #10
0
// TestApplyFileContracts probes the applyFileContracts method of the
// consensus set.
func TestApplyFileContracts(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	cst, err := createConsensusSetTester("TestApplyFileContracts")
	if err != nil {
		t.Fatal(err)
	}

	// Create a block node to use with application.
	bn := new(blockNode)

	// Apply a transaction with a single file contract.
	txn := types.Transaction{
		FileContracts: []types.FileContract{{}},
	}
	cst.cs.applyFileContracts(bn, txn)
	fcid := txn.FileContractID(0)
	_, exists := cst.cs.fileContracts[fcid]
	if !exists {
		t.Error("Failed to create file contract")
	}
	if len(cst.cs.fileContracts) != 1 {
		t.Error("file contracts not correctly updated")
	}
	if len(bn.fileContractDiffs) != 1 {
		t.Error("block node was not updated for single element transaction")
	}
	if bn.fileContractDiffs[0].Direction != modules.DiffApply {
		t.Error("wrong diff direction applied when creating a file contract")
	}
	if bn.fileContractDiffs[0].ID != fcid {
		t.Error("wrong id used when creating a file contract")
	}

	// Apply a transaction with 2 file contracts.
	txn = types.Transaction{
		FileContracts: []types.FileContract{
			{Payout: types.NewCurrency64(1)},
			{Payout: types.NewCurrency64(300e3)},
		},
	}
	cst.cs.applyFileContracts(bn, txn)
	fcid0 := txn.FileContractID(0)
	fcid1 := txn.FileContractID(1)
	_, exists = cst.cs.fileContracts[fcid0]
	if !exists {
		t.Error("Failed to create file contract")
	}
	_, exists = cst.cs.fileContracts[fcid1]
	if !exists {
		t.Error("Failed to create file contract")
	}
	if len(cst.cs.fileContracts) != 3 {
		t.Error("file contracts not correctly updated")
	}
	if len(bn.fileContractDiffs) != 3 {
		t.Error("block node was not updated correctly")
	}
	if cst.cs.siafundPool.Cmp(types.NewCurrency64(10e3)) != 0 {
		t.Error("siafund pool did not update correctly upon creation of a file contract")
	}
}
Beispiel #11
0
// TestUploadConstraints checks that file contract negotiation correctly
// rejects contracts that don't meet required criteria.
func TestUploadConstraints(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	ht, err := newHostTester("TestUploadConstraints")
	if err != nil {
		t.Fatal(err)
	}
	h := ht.host
	settings := h.Settings()
	settings.TotalStorage = 10e3
	err = h.SetSettings(settings)
	if err != nil {
		t.Fatal(err)
	}

	// Create a valid file contract transaction.
	filesize := uint64(5e3)
	merkleRoot := crypto.Hash{51, 23}
	windowStart := ht.cs.Height() + 1 + settings.MinDuration
	windowEnd := ht.cs.Height() + 1 + settings.MinDuration + 1 + settings.WindowSize
	currencyDuration := types.NewCurrency64(1 + uint64(settings.MinDuration))
	payment := types.NewCurrency64(filesize).Mul(settings.Price).Mul(currencyDuration)
	payout := payment.Mul(types.NewCurrency64(20))
	refund := types.PostTax(ht.cs.Height(), payout).Sub(payment)
	renterKey := types.SiaPublicKey{}
	txn := types.Transaction{
		FileContracts: []types.FileContract{{
			FileSize:       filesize,
			FileMerkleRoot: merkleRoot,
			WindowStart:    windowStart,
			WindowEnd:      windowEnd,
			Payout:         payout,
			ValidProofOutputs: []types.SiacoinOutput{
				{
					Value:      refund,
					UnlockHash: types.UnlockHash{},
				},
				{
					Value:      payment,
					UnlockHash: settings.UnlockHash,
				},
			},
			MissedProofOutputs: []types.SiacoinOutput{
				{
					Value:      refund,
					UnlockHash: types.UnlockHash{},
				},
				{
					Value:      payment,
					UnlockHash: types.UnlockHash{},
				},
			},
			UnlockHash: types.UnlockConditions{
				PublicKeys:         []types.SiaPublicKey{renterKey, h.publicKey},
				SignaturesRequired: 2,
			}.UnlockHash(),
			RevisionNumber: 3,
		}},
	}
	err = h.considerContract(txn, renterKey, filesize, merkleRoot)
	if err != nil {
		t.Fatal(err)
	}

	// Test that under-paid file contracts get rejected.
	underPayment := types.NewCurrency64(filesize * 5 / 6).Mul(settings.Price).Mul(currencyDuration)
	underRefund := types.PostTax(ht.cs.Height(), payout).Sub(underPayment)
	txn.FileContracts[0].ValidProofOutputs[0].Value = underRefund
	txn.FileContracts[0].ValidProofOutputs[1].Value = underPayment
	txn.FileContracts[0].MissedProofOutputs[0].Value = underRefund
	txn.FileContracts[0].MissedProofOutputs[1].Value = underPayment
	err = h.considerContract(txn, renterKey, filesize, merkleRoot)
	if err != ErrLowPayment {
		t.Fatal(err)
	}

	// Test that too-large files get rejected.
	largeFilesize := uint64(10001)
	largeFilePayment := types.NewCurrency64(largeFilesize).Mul(settings.Price).Mul(currencyDuration)
	largeFileRefund := types.PostTax(ht.cs.Height(), payout).Sub(largeFilePayment)
	txn.FileContracts[0].FileSize = largeFilesize
	txn.FileContracts[0].ValidProofOutputs[0].Value = largeFileRefund
	txn.FileContracts[0].ValidProofOutputs[1].Value = largeFilePayment
	txn.FileContracts[0].MissedProofOutputs[0].Value = largeFileRefund
	txn.FileContracts[0].MissedProofOutputs[1].Value = largeFilePayment
	err = h.considerContract(txn, renterKey, largeFilesize, merkleRoot)
	if err != ErrHostCapacity {
		t.Fatal(err)
	}

	// Reset the file contract to a working contract, and create an obligation
	// from the transaction.
	txn.FileContracts[0].FileSize = filesize
	txn.FileContracts[0].ValidProofOutputs[0].Value = refund
	txn.FileContracts[0].ValidProofOutputs[1].Value = payment
	txn.FileContracts[0].MissedProofOutputs[0].Value = refund
	txn.FileContracts[0].MissedProofOutputs[1].Value = payment
	obligation := &contractObligation{
		ID:                txn.FileContractID(0),
		OriginTransaction: txn,
	}

	// Create a legal revision transaction.
	newFileSize := filesize + uint64(4e3)
	revisedPayment := payment.Add(types.NewCurrency64(newFileSize - filesize).Mul(currencyDuration).Mul(settings.Price))
	revisedRefund := types.PostTax(ht.cs.Height(), payout).Sub(revisedPayment)
	revisionTxn := types.Transaction{
		FileContractRevisions: []types.FileContractRevision{{
			ParentID: txn.FileContractID(0),
			UnlockConditions: types.UnlockConditions{
				PublicKeys:         []types.SiaPublicKey{renterKey, h.publicKey},
				SignaturesRequired: 2,
			},
			NewRevisionNumber: txn.FileContracts[0].RevisionNumber + 1,

			NewFileSize:       newFileSize,
			NewFileMerkleRoot: merkleRoot,
			NewWindowStart:    windowStart,
			NewWindowEnd:      windowEnd,
			NewValidProofOutputs: []types.SiacoinOutput{
				{
					Value:      revisedRefund,
					UnlockHash: types.UnlockHash{},
				},
				{
					Value:      revisedPayment,
					UnlockHash: settings.UnlockHash,
				},
			},
			NewMissedProofOutputs: []types.SiacoinOutput{
				{
					Value:      revisedRefund,
					UnlockHash: types.UnlockHash{},
				},
				{
					Value:      revisedPayment,
					UnlockHash: types.UnlockHash{},
				},
			},
			NewUnlockHash: txn.FileContracts[0].UnlockHash,
		}},
	}
	err = ht.host.considerRevision(revisionTxn, obligation)
	if err != nil {
		t.Fatal(err)
	}

	// Test that too large revisions get rejected.
	settings.TotalStorage = 3e3
	ht.host.SetSettings(settings)
	if ht.host.spaceRemaining != 3e3 {
		t.Fatal("host is not getting the correct space remaining")
	}
	err = ht.host.considerRevision(revisionTxn, obligation)
	if err != ErrHostCapacity {
		t.Fatal(err)
	}

	// Test that file revisions get accepted if the updated file size is too
	// large but just the added data is small enough (regression test).
	settings.TotalStorage = 8e3
	ht.host.SetSettings(settings)
	err = ht.host.considerRevision(revisionTxn, obligation)
	if err != nil {
		t.Fatal(err)
	}

	// Test that underpaid revisions get rejected.
	revisedUnderPayment := payment.Add(types.NewCurrency64(newFileSize - filesize - 1e3).Mul(currencyDuration).Mul(settings.Price))
	revisedUnderRefund := types.PostTax(ht.cs.Height(), payout).Sub(revisedUnderPayment)
	revisionTxn.FileContractRevisions[0].NewValidProofOutputs[0].Value = revisedUnderRefund
	revisionTxn.FileContractRevisions[0].NewValidProofOutputs[1].Value = revisedUnderPayment
	revisionTxn.FileContractRevisions[0].NewMissedProofOutputs[0].Value = revisedUnderRefund
	revisionTxn.FileContractRevisions[0].NewMissedProofOutputs[1].Value = revisedUnderPayment
	revisionTxn.FileContractRevisions[0].NewMissedProofOutputs[1].Value = revisedUnderPayment
	revisionTxn.FileContractRevisions[0].NewMissedProofOutputs[1].Value = revisedUnderPayment
	err = ht.host.considerRevision(revisionTxn, obligation)
	if err != ErrLowPayment {
		t.Fatal(err)
	}
}
Beispiel #12
0
// rpcContract is an RPC that negotiates a file contract. If the
// negotiation is successful, the file is downloaded and the host begins
// submitting proofs of storage.
func (h *Host) rpcContract(conn net.Conn) (err error) {
	// Read the contract terms.
	var terms modules.ContractTerms
	err = encoding.ReadObject(conn, &terms, maxContractLen)
	if err != nil {
		return
	}

	// Consider the contract terms. If they are unacceptable, return an error
	// describing why.
	lockID := h.mu.RLock()
	err = h.considerTerms(terms)
	h.mu.RUnlock(lockID)
	if err != nil {
		err = encoding.WriteObject(conn, err.Error())
		return
	}

	// terms are acceptable; allocate space for file
	lockID = h.mu.Lock()
	file, path, err := h.allocate(terms.FileSize)
	h.mu.Unlock(lockID)
	if err != nil {
		return
	}
	defer file.Close()

	// rollback everything if something goes wrong
	defer func() {
		lockID := h.mu.Lock()
		defer h.mu.Unlock(lockID)
		if err != nil {
			h.deallocate(terms.FileSize, path)
		}
	}()

	// signal that we are ready to download file
	err = encoding.WriteObject(conn, modules.AcceptTermsResponse)
	if err != nil {
		return
	}

	// simultaneously download file and calculate its Merkle root.
	tee := io.TeeReader(
		// use a LimitedReader to ensure we don't read indefinitely
		io.LimitReader(conn, int64(terms.FileSize)),
		// each byte we read from tee will also be written to file
		file,
	)
	merkleRoot, err := crypto.ReaderMerkleRoot(tee)
	if err != nil {
		return
	}

	// Data has been sent, read in the unsigned transaction with the file
	// contract.
	var unsignedTxn types.Transaction
	err = encoding.ReadObject(conn, &unsignedTxn, maxContractLen)
	if err != nil {
		return
	}

	// Verify that the transaction matches the agreed upon terms, and that the
	// Merkle root in the file contract matches our independently calculated
	// Merkle root.
	err = verifyTransaction(unsignedTxn, terms, merkleRoot)
	if err != nil {
		err = errors.New("transaction does not satisfy terms: " + err.Error())
		return
	}

	// Add the collateral to the transaction, but do not sign the transaction.
	collateralTxn, txnBuilder, err := h.addCollateral(unsignedTxn, terms)
	if err != nil {
		return
	}
	err = encoding.WriteObject(conn, collateralTxn)
	if err != nil {
		return
	}

	// Read in the renter-signed transaction and check that it matches the
	// previously accepted transaction.
	var signedTxn types.Transaction
	err = encoding.ReadObject(conn, &signedTxn, maxContractLen)
	if err != nil {
		return
	}
	if collateralTxn.ID() != signedTxn.ID() {
		err = errors.New("signed transaction does not match the transaction with collateral")
		return
	}

	// Add the signatures from the renter signed transaction, and then sign the
	// transaction, then submit the transaction.
	for _, sig := range signedTxn.TransactionSignatures {
		txnBuilder.AddTransactionSignature(sig)
		if err != nil {
			return
		}
	}
	txnSet, err := txnBuilder.Sign(true)
	if err != nil {
		return
	}
	err = h.tpool.AcceptTransactionSet(txnSet)
	if err != nil {
		return
	}

	// Add this contract to the host's list of obligations.
	fcid := signedTxn.FileContractID(0)
	fc := signedTxn.FileContracts[0]
	proofHeight := fc.WindowStart + StorageProofReorgDepth
	co := contractObligation{
		ID:           fcid,
		FileContract: fc,
		Path:         path,
	}
	lockID = h.mu.Lock()
	h.obligationsByHeight[proofHeight] = append(h.obligationsByHeight[proofHeight], co)
	h.obligationsByID[fcid] = co
	h.save()
	h.mu.Unlock(lockID)

	// Send an ack to the renter that all is well.
	err = encoding.WriteObject(conn, true)
	if err != nil {
		return
	}

	// TODO: we don't currently watch the blockchain to make sure that the
	// transaction actually gets into the blockchain.

	return
}