// newTesterStorageObligation uses the wallet to create and fund a file
// contract that will form the foundation of a storage obligation.
func (ht *hostTester) newTesterStorageObligation() (*storageObligation, error) {
	// Create the file contract that will be used in the obligation.
	builder := ht.wallet.StartTransaction()
	// Fund the file contract with a payout. The payout needs to be big enough
	// that the expected revenue is larger than the fee that the host may end
	// up paying.
	payout := types.SiacoinPrecision.Mul64(1e3)
	err := builder.FundSiacoins(payout)
	if err != nil {
		return nil, err
	}
	// Add the file contract that consumes the funds.
	_ = builder.AddFileContract(types.FileContract{
		// Because this file contract needs to be able to accept file contract
		// revisions, the expiration is put more than
		// 'revisionSubmissionBuffer' blocks into the future.
		WindowStart: ht.host.blockHeight + revisionSubmissionBuffer + 2,
		WindowEnd:   ht.host.blockHeight + revisionSubmissionBuffer + defaultWindowSize + 2,

		Payout: payout,
		ValidProofOutputs: []types.SiacoinOutput{
			{
				Value: types.PostTax(ht.host.blockHeight, payout),
			},
			{
				Value: types.ZeroCurrency,
			},
		},
		MissedProofOutputs: []types.SiacoinOutput{
			{
				Value: types.PostTax(ht.host.blockHeight, payout),
			},
			{
				Value: types.ZeroCurrency,
			},
		},
		UnlockHash:     (types.UnlockConditions{}).UnlockHash(),
		RevisionNumber: 0,
	})
	// Sign the transaction.
	tSet, err := builder.Sign(true)
	if err != nil {
		return nil, err
	}

	// Assemble and return the storage obligation.
	so := &storageObligation{
		OriginTransactionSet: tSet,

		// TODO: There are no tracking values, because no fees were added.
	}
	return so, nil
}
Пример #2
0
// TestAcceptFCAndConflictingRevision checks that the transaction pool
// correctly accepts a file contract in a transaction set followed by a correct
// revision to that file contract in the a following transaction set, with no
// block separating them.
func TestAcceptFCAndConflictingRevision(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	tpt, err := createTpoolTester("TestAcceptFCAndConflictingRevision")
	if err != nil {
		t.Fatal(err)
	}
	defer tpt.Close()

	// Create and fund a valid file contract.
	builder := tpt.wallet.StartTransaction()
	payout := types.NewCurrency64(1e9)
	err = builder.FundSiacoins(payout)
	if err != nil {
		t.Fatal(err)
	}
	builder.AddFileContract(types.FileContract{
		WindowStart:        tpt.cs.Height() + 2,
		WindowEnd:          tpt.cs.Height() + 5,
		Payout:             payout,
		ValidProofOutputs:  []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}},
		MissedProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}},
		UnlockHash:         types.UnlockConditions{}.UnlockHash(),
	})
	tSet, err := builder.Sign(true)
	if err != nil {
		t.Fatal(err)
	}
	err = tpt.tpool.AcceptTransactionSet(tSet)
	if err != nil {
		t.Fatal(err)
	}
	fcid := tSet[len(tSet)-1].FileContractID(0)

	// Create a file contract revision and submit it.
	rSet := []types.Transaction{{
		FileContractRevisions: []types.FileContractRevision{{
			ParentID:          fcid,
			NewRevisionNumber: 2,

			NewWindowStart:        tpt.cs.Height() + 2,
			NewWindowEnd:          tpt.cs.Height() + 5,
			NewValidProofOutputs:  []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}},
			NewMissedProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}},
		}},
	}}
	err = tpt.tpool.AcceptTransactionSet(rSet)
	if err != nil {
		t.Fatal(err)
	}
}
Пример #3
0
func TestNegotiateContract(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()
	ct, err := newContractorTester("TestNegotiateContract")
	if err != nil {
		t.Fatal(err)
	}

	payout := types.NewCurrency64(1e16)

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

	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)
	}

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

}
Пример #4
0
// considerRevision checks that the provided file contract revision is still
// acceptable to the host.
func (h *Host) considerRevision(txn types.Transaction, obligation *contractObligation) error {
	// Check that there is only one revision.
	if len(txn.FileContractRevisions) != 1 {
		return errors.New("transaction should have only one revision")
	}

	// calculate minimum expected output value
	rev := txn.FileContractRevisions[0]
	duration := types.NewCurrency64(uint64(obligation.windowStart() - h.blockHeight))
	minHostPrice := types.NewCurrency64(rev.NewFileSize).Mul(duration).Mul(h.settings.Price)
	expectedPayout := types.PostTax(h.blockHeight, obligation.payout())

	switch {
	// these fields should never change
	case rev.ParentID != obligation.ID:
		return errors.New("bad revision parent ID")
	case rev.NewWindowStart != obligation.windowStart():
		return errors.New("bad revision window start")
	case rev.NewWindowEnd != obligation.windowEnd():
		return errors.New("bad revision window end")
	case rev.NewUnlockHash != obligation.unlockHash():
		return errors.New("bad revision unlock hash")
	case rev.UnlockConditions.UnlockHash() != obligation.unlockHash():
		return errors.New("bad revision unlock conditions")
	case len(rev.NewValidProofOutputs) != 2:
		return errors.New("bad revision valid proof outputs")
	case len(rev.NewMissedProofOutputs) != 2:
		return errors.New("bad revision missed proof outputs")
	case rev.NewValidProofOutputs[1].UnlockHash != obligation.validProofUnlockHash(),
		rev.NewMissedProofOutputs[1].UnlockHash != obligation.missedProofUnlockHash():
		return errors.New("bad revision proof outputs")

	case rev.NewRevisionNumber <= obligation.revisionNumber():
		return errors.New("revision must have higher revision number")

	case rev.NewFileSize > uint64(h.spaceRemaining):
		return errors.New("revision file size is too large")
	case rev.NewFileSize <= obligation.fileSize():
		return errors.New("revision must add data")
	case rev.NewFileSize-obligation.fileSize() > maxRevisionSize:
		return errors.New("revision adds too much data")

	case rev.NewValidProofOutputs[0].Value.Add(rev.NewValidProofOutputs[1].Value).Cmp(expectedPayout) != 0,
		// valid and missing outputs should still sum to payout
		rev.NewMissedProofOutputs[0].Value.Add(rev.NewMissedProofOutputs[1].Value).Cmp(expectedPayout) != 0:
		return errors.New("revision outputs do not sum to original payout")

	case rev.NewValidProofOutputs[1].Value.Cmp(minHostPrice) < 0:
		// outputs should have been adjusted proportional to the new filesize
		return errors.New("revision price is too small")

	case rev.NewMissedProofOutputs[0].Value.Cmp(rev.NewValidProofOutputs[0].Value) != 0:
		return errors.New("revision missed renter payout does not match valid payout")
	}

	return nil
}
Пример #5
0
// considerRevision checks that the provided file contract revision is still
// acceptable to the host.
// TODO: should take a txn and check that is only contains the single revision
func (h *Host) considerRevision(txn types.Transaction, obligation contractObligation) error {
	// Check that there is only one revision.
	// TODO: check that the txn is empty except for the revision?
	if len(txn.FileContractRevisions) != 1 {
		return errors.New("transaction should have only one revision")
	}

	// calculate minimum expected output value
	rev := txn.FileContractRevisions[0]
	fc := obligation.FileContract
	duration := types.NewCurrency64(uint64(fc.WindowStart - h.blockHeight))
	minHostPrice := types.NewCurrency64(rev.NewFileSize).Mul(duration).Mul(h.Price)
	expectedPayout := types.PostTax(h.blockHeight, fc.Payout)

	switch {
	// these fields should never change
	case rev.ParentID != obligation.ID:
		return errors.New("bad revision parent ID")
	case rev.NewWindowStart != fc.WindowStart:
		return errors.New("bad revision window start")
	case rev.NewWindowEnd != fc.WindowEnd:
		return errors.New("bad revision window end")
	case rev.NewUnlockHash != fc.UnlockHash:
		return errors.New("bad revision unlock hash")
	case rev.UnlockConditions.UnlockHash() != fc.UnlockHash:
		return errors.New("bad revision unlock conditions")
	case len(rev.NewValidProofOutputs) != 2:
		return errors.New("bad revision valid proof outputs")
	case len(rev.NewMissedProofOutputs) != 2:
		return errors.New("bad revision missed proof outputs")
	case rev.NewValidProofOutputs[1].UnlockHash != fc.ValidProofOutputs[1].UnlockHash,
		rev.NewMissedProofOutputs[1].UnlockHash != fc.MissedProofOutputs[1].UnlockHash:
		return errors.New("bad revision proof outputs")

	case rev.NewRevisionNumber <= fc.RevisionNumber:
		return errors.New("revision must have higher revision number")

	case rev.NewFileSize > uint64(h.spaceRemaining) || rev.NewFileSize > h.MaxFilesize:
		return errors.New("revision file size is too large")

	// valid and missing outputs should still sum to payout
	case rev.NewValidProofOutputs[0].Value.Add(rev.NewValidProofOutputs[1].Value).Cmp(expectedPayout) != 0,
		rev.NewMissedProofOutputs[0].Value.Add(rev.NewMissedProofOutputs[1].Value).Cmp(expectedPayout) != 0:
		return errors.New("revision outputs do not sum to original payout")

	// outputs should have been adjusted proportional to the new filesize
	case rev.NewValidProofOutputs[1].Value.Cmp(minHostPrice) <= 0:
		return errors.New("revision price is too small")
	case rev.NewMissedProofOutputs[0].Value.Cmp(rev.NewValidProofOutputs[0].Value) != 0:
		return errors.New("revision missed renter payout does not match valid payout")
	}

	return nil
}
Пример #6
0
Файл: host.go Проект: mantyr/Sia
func (h *Host) Info() modules.HostInfo {
	h.mu.RLock()
	defer h.mu.RUnlock()

	h.HostSettings.IPAddress = h.myAddr // needs to be updated manually
	info := modules.HostInfo{
		HostSettings: h.HostSettings,

		StorageRemaining: h.spaceRemaining,
		NumContracts:     len(h.obligationsByID),
		Profit:           h.profit,
	}
	// sum up the current obligations to calculate PotentialProfit
	for _, obligation := range h.obligationsByID {
		fc := obligation.FileContract
		info.PotentialProfit = info.PotentialProfit.Add(types.PostTax(h.blockHeight, fc.Payout))
	}

	return info
}
Пример #7
0
func (h *Host) Info() modules.HostInfo {
	lockID := h.mu.RLock()
	defer h.mu.RUnlock(lockID)

	info := modules.HostInfo{
		HostSettings: h.HostSettings,

		StorageRemaining: h.spaceRemaining,
		NumContracts:     len(h.obligationsByID),
		Profit:           h.profit,
	}
	// sum up the current obligations to calculate PotentialProfit
	for _, obligation := range h.obligationsByID {
		fc := obligation.FileContract
		info.PotentialProfit = info.PotentialProfit.Add(types.PostTax(h.blockHeight, fc.Payout))
	}

	// Calculate estimated competition (reported in per GB per month). Price
	// calculated by taking the average of hosts 8-15.
	var averagePrice types.Currency
	hosts := h.hostdb.RandomHosts(15)
	for i, host := range hosts {
		if i < 8 {
			continue
		}
		averagePrice = averagePrice.Add(host.Price)
	}
	if len(hosts) == 0 {
		return info
	}
	averagePrice = averagePrice.Div(types.NewCurrency64(uint64(len(hosts))))
	// HACK: 4320 is one month, and 1024^3 is a GB. Price is reported as per GB
	// per month.
	estimatedCost := averagePrice.Mul(types.NewCurrency64(4320)).Mul(types.NewCurrency64(1024 * 1024 * 1024))
	info.Competition = estimatedCost

	return info
}
Пример #8
0
// testValidStorageProofBlocks adds a block with a file contract, and then
// submits a storage proof for that file contract.
func (cst *consensusSetTester) testValidStorageProofBlocks() {
	// 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 file contract that will be successful.
	validProofDest := randAddress()
	payout := types.NewCurrency64(400e6)
	fc := types.FileContract{
		FileSize:       filesize,
		FileMerkleRoot: merkleRoot,
		WindowStart:    cst.cs.dbBlockHeight() + 1,
		WindowEnd:      cst.cs.dbBlockHeight() + 2,
		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),
		}},
	}

	// Submit a transaction with the file contract.
	oldSiafundPool := cst.cs.dbGetSiafundPool()
	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)
	}

	// Check that the siafund pool was increased by the tax on the payout.
	siafundPool := cst.cs.dbGetSiafundPool()
	if siafundPool.Cmp(oldSiafundPool.Add(types.Tax(cst.cs.dbBlockHeight()-1, payout))) != 0 {
		panic("siafund pool was not increased correctly")
	}

	// Check that the file contract made it into the database.
	ti := len(txnSet) - 1
	fcid := txnSet[ti].FileContractID(fcIndex)
	_, err = cst.cs.dbGetFileContract(fcid)
	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")
	}

	// Check that the siafund pool has not changed.
	postProofPool := cst.cs.dbGetSiafundPool()
	if postProofPool.Cmp(siafundPool) != 0 {
		panic("siafund pool should not change after submitting a storage proof")
	}

	// Check that a delayed output was created for the valid proof.
	spoid := fcid.StorageProofOutputID(types.ProofValid, 0)
	dsco, err := cst.cs.dbGetDSCO(cst.cs.dbBlockHeight()+types.MaturityDelay, spoid)
	if err != nil {
		panic(err)
	}
	if dsco.UnlockHash != fc.ValidProofOutputs[0].UnlockHash {
		panic("wrong unlock hash in dsco")
	}
	if dsco.Value.Cmp(fc.ValidProofOutputs[0].Value) != 0 {
		panic("wrong sco value in dsco")
	}
}
Пример #9
0
// COMPATv0.4.0
//
// This test checks that the hardfork scheduled for block 21,000 rolls through
// smoothly.
func TestTaxHardfork(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	cst, err := createConsensusSetTester("TestTaxHardfork")
	if err != nil {
		t.Fatal(err)
	}
	defer cst.closeCst()

	// Create a file contract with a payout that is put into the blockchain
	// before the hardfork block but expires after the hardfork block.
	payout := types.NewCurrency64(400e6)
	outputSize := types.PostTax(cst.cs.dbBlockHeight(), payout)
	fc := types.FileContract{
		WindowStart:        cst.cs.dbBlockHeight() + 12,
		WindowEnd:          cst.cs.dbBlockHeight() + 14,
		Payout:             payout,
		ValidProofOutputs:  []types.SiacoinOutput{{Value: outputSize}},
		MissedProofOutputs: []types.SiacoinOutput{{Value: outputSize}},
		UnlockHash:         types.UnlockConditions{}.UnlockHash(), // The empty UC is anyone-can-spend
	}

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

	// Check that the siafund pool was increased by the faulty float amount.
	siafundPool := cst.cs.dbGetSiafundPool()
	if siafundPool.Cmp(types.NewCurrency64(15590e3)) != 0 {
		t.Fatal("siafund pool was not increased correctly")
	}

	// Mine blocks until the hardfork is reached.
	for i := 0; i < 10; i++ {
		_, err = cst.miner.AddBlock()
		if err != nil {
			t.Fatal(err)
		}
	}

	// Submit a file contract revision and check that the payouts are able to
	// be the same.
	fcid := txnSet[len(txnSet)-1].FileContractID(fcIndex)
	fcr := types.FileContractRevision{
		ParentID:          fcid,
		UnlockConditions:  types.UnlockConditions{},
		NewRevisionNumber: 1,

		NewFileSize:           1,
		NewWindowStart:        cst.cs.dbBlockHeight() + 2,
		NewWindowEnd:          cst.cs.dbBlockHeight() + 4,
		NewValidProofOutputs:  fc.ValidProofOutputs,
		NewMissedProofOutputs: fc.MissedProofOutputs,
	}
	txnBuilder = cst.wallet.StartTransaction()
	txnBuilder.AddFileContractRevision(fcr)
	txnSet, err = txnBuilder.Sign(true)
	if err != nil {
		t.Fatal(err)
	}
	err = cst.tpool.AcceptTransactionSet(txnSet)
	if err != nil {
		t.Fatal(err)
	}
	_, err = cst.miner.AddBlock()
	if err != nil {
		t.Fatal(err)
	}

	// Mine blocks until the revision goes through, such that the sanity checks
	// can be run.
	for i := 0; i < 6; i++ {
		_, err = cst.miner.AddBlock()
		if err != nil {
			t.Fatal(err)
		}
	}

	// Check that the siafund pool did not change after the submitted revision.
	siafundPool = cst.cs.dbGetSiafundPool()
	if siafundPool.Cmp(types.NewCurrency64(15590e3)) != 0 {
		t.Fatal("siafund pool was not increased correctly")
	}
}
Пример #10
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)
	}
}
Пример #11
0
// Renew negotiates a new contract for data already stored with a host, and
// submits the new contract transaction to tpool.
func Renew(contract modules.RenterContract, 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
	ourSK := contract.SecretKey

	// calculate cost to renter and cost to host
	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
	}

	// Calculate additional basePrice and baseCollateral. If the contract
	// height did not increase, basePrice and baseCollateral are zero.
	var basePrice, baseCollateral types.Currency
	if endHeight+host.WindowSize > contract.LastRevision.NewWindowEnd {
		timeExtension := uint64((endHeight + host.WindowSize) - contract.LastRevision.NewWindowEnd)
		basePrice = host.StoragePrice.Mul64(contract.LastRevision.NewFileSize).Mul64(timeExtension)    // cost of data already covered by contract, i.e. lastrevision.Filesize
		baseCollateral = host.Collateral.Mul64(contract.LastRevision.NewFileSize).Mul64(timeExtension) // same but collateral
	}

	hostPayout := hostCollateral.Add(host.ContractPrice).Add(basePrice)
	payout := storageAllocation.Add(hostCollateral.Add(host.ContractPrice)).Mul64(10406).Div64(10000) // renter covers 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")
	} else if hostCollateral.Cmp(baseCollateral) < 0 {
		return modules.RenterContract{}, errors.New("new collateral smaller than old collateral")
	}

	// create file contract
	fc := types.FileContract{
		FileSize:       contract.LastRevision.NewFileSize,
		FileMerkleRoot: contract.LastRevision.NewFileMerkleRoot,
		WindowStart:    endHeight,
		WindowEnd:      endHeight + host.WindowSize,
		Payout:         payout,
		UnlockHash:     contract.LastRevision.NewUnlockHash,
		RevisionNumber: 0,
		ValidProofOutputs: []types.SiacoinOutput{
			// renter
			{Value: types.PostTax(startHeight, payout).Sub(hostPayout), UnlockHash: refundAddress},
			// host
			{Value: hostPayout, UnlockHash: host.UnlockHash},
		},
		MissedProofOutputs: []types.SiacoinOutput{
			// renter
			{Value: types.PostTax(startHeight, payout).Sub(hostPayout), UnlockHash: refundAddress},
			// host gets its unused collateral back, plus the contract price
			{Value: hostCollateral.Sub(baseCollateral).Add(host.ContractPrice), UnlockHash: host.UnlockHash},
			// void gets the spent storage fees, plus the collateral being risked
			{Value: basePrice.Add(baseCollateral), 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, verifyRecentRevision, and verifySettings
	extendDeadline(conn, modules.NegotiateRecentRevisionTime+modules.NegotiateSettingsTime)
	if err = encoding.WriteObject(conn, modules.RPCRenewContract); err != nil {
		return modules.RenterContract{}, errors.New("couldn't initiate RPC: " + err.Error())
	}
	// verify that both parties are renewing the same contract
	if err = verifyRecentRevision(conn, contract); err != nil {
		return modules.RenterContract{}, errors.New("revision exchange failed: " + err.Error())
	}
	// verify the host's settings and confirm its identity
	host, err = verifySettings(conn, host)
	if err != nil {
		return modules.RenterContract{}, errors.New("settings exchange failed: " + err.Error())
	}
	if !host.AcceptingContracts {
		return modules.RenterContract{}, errors.New("host is not accepting contracts")
	}

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

	// 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:  contract.LastRevision.UnlockConditions,
		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,
		MerkleRoots:     contract.MerkleRoots,
		NetAddress:      host.NetAddress,
		SecretKey:       ourSK,
	}, nil
}
Пример #12
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")
	}
}
Пример #13
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
}
Пример #14
0
// considerRevision checks that the provided file contract revision is still
// acceptable to the host.
func (h *Host) considerRevision(txn types.Transaction, obligation *contractObligation) error {
	// Check that there is only one revision.
	if len(txn.FileContractRevisions) != 1 {
		return errors.New("transaction should have only one revision")
	}

	// calculate minimum expected output value
	rev := txn.FileContractRevisions[0]
	duration := types.NewCurrency64(uint64(obligation.windowStart() - h.blockHeight))
	sizeDiff := rev.NewFileSize - obligation.fileSize()
	priceAdd := types.NewCurrency64(sizeDiff).Mul(duration).Mul(h.settings.Price)
	minPayment := obligation.value().Add(priceAdd)
	expectedPayout := types.PostTax(h.blockHeight, obligation.payout())

	switch {
	// Check that the revision matches the previous file contract.
	case rev.ParentID != obligation.ID:
		return errors.New("bad revision parent ID")
	case rev.NewRevisionNumber <= obligation.revisionNumber():
		return errors.New("revision must have higher revision number")
	case rev.NewUnlockHash != obligation.unlockHash():
		return errors.New("bad revision unlock hash")
	case rev.UnlockConditions.UnlockHash() != obligation.unlockHash():
		return errors.New("bad revision unlock conditions")

	// Check that the window is unchanged.
	case rev.NewWindowStart != obligation.windowStart():
		return errors.New("bad revision window start")
	case rev.NewWindowEnd != obligation.windowEnd():
		return errors.New("bad revision window end")

	// Check that the change in filesize is legal.
	//
	// TODO: Revisions should leave enough headroom so that renewals always
	// have some space.
	case rev.NewFileSize <= obligation.fileSize():
		return errors.New("revision must add data")
	case rev.NewFileSize-obligation.fileSize() > uint64(h.spaceRemaining):
		return ErrHostCapacity
	case rev.NewFileSize-obligation.fileSize() > maxRevisionSize:
		return errors.New("revision adds too much data")

	// Check that the payout information is correct.
	case len(rev.NewValidProofOutputs) != 2:
		return errors.New("bad revision valid proof outputs")
	case len(rev.NewMissedProofOutputs) != 2:
		return errors.New("bad revision missed proof outputs")
	case rev.NewValidProofOutputs[1].UnlockHash != obligation.validProofUnlockHash(),
		rev.NewMissedProofOutputs[1].UnlockHash != obligation.missedProofUnlockHash():
		return errors.New("bad revision proof outputs")
	case rev.NewValidProofOutputs[0].Value.Add(rev.NewValidProofOutputs[1].Value).Cmp(expectedPayout) != 0,
		rev.NewMissedProofOutputs[0].Value.Add(rev.NewMissedProofOutputs[1].Value).Cmp(expectedPayout) != 0:
		return errors.New("revision outputs do not sum to original payout")
	case rev.NewValidProofOutputs[1].Value.Cmp(minPayment) < 0:
		return ErrLowPayment
	case rev.NewMissedProofOutputs[0].Value.Cmp(rev.NewValidProofOutputs[0].Value) != 0:
		return errors.New("revision missed renter payout does not match valid payout")
	}

	return nil
}
Пример #15
0
// considerContract checks that the provided transaction matches the host's
// terms, and doesn't contain any flagrant errors.
func (h *Host) considerContract(txn types.Transaction, renterKey types.SiaPublicKey, filesize uint64, merkleRoot crypto.Hash) error {
	// Check that there is only one file contract.
	if len(txn.FileContracts) != 1 {
		return errors.New("transaction should have only one file contract")
	}

	// convenience variables
	fc := txn.FileContracts[0]
	duration := fc.WindowStart - h.blockHeight
	minPayment := types.NewCurrency64(filesize).Mul(types.NewCurrency64(uint64(duration))).Mul(h.settings.Price)
	expectedOutputSum := types.PostTax(h.blockHeight, fc.Payout)

	// check contract fields for sanity and acceptability
	switch {
	// Check for legal filesize and content.
	case fc.FileSize != filesize:
		return errors.New("bad initial file size")
	case fc.FileSize >= uint64(h.spaceRemaining):
		return ErrHostCapacity
	case fc.FileMerkleRoot != merkleRoot:
		return errors.New("bad file contract Merkle root")

	// Check for legal duration and proof window.
	case fc.WindowStart <= h.blockHeight:
		return errors.New("window start cannot be in the past")
	case duration < h.settings.MinDuration || duration > h.settings.MaxDuration:
		return errors.New("duration is out of bounds")
	case fc.WindowEnd <= fc.WindowStart:
		return errors.New("window cannot end before it starts")
	case fc.WindowEnd-fc.WindowStart < h.settings.WindowSize:
		return errors.New("challenge window is not large enough")

	// Check for legal payout.
	case fc.Payout.IsZero():
		return errors.New("bad file contract payout")
	case len(fc.ValidProofOutputs) != 2:
		return errors.New("bad file contract valid proof outputs")
	case len(fc.MissedProofOutputs) != 2:
		return errors.New("bad file contract missed proof outputs")
	case fc.ValidProofOutputs[0].Value.Add(fc.ValidProofOutputs[1].Value).Cmp(expectedOutputSum) != 0,
		fc.MissedProofOutputs[0].Value.Add(fc.MissedProofOutputs[1].Value).Cmp(expectedOutputSum) != 0:
		return errors.New("file contract outputs do not sum to original payout")
	case fc.ValidProofOutputs[1].UnlockHash != h.settings.UnlockHash:
		return errors.New("file contract valid proof output not sent to host")
	case fc.ValidProofOutputs[1].Value.Cmp(minPayment) < 0:
		return ErrLowPayment
	case fc.MissedProofOutputs[0].Value.Cmp(fc.ValidProofOutputs[0].Value) != 0:
		return errors.New("file contract missed renter payout does not match valid payout")
	case fc.MissedProofOutputs[1].UnlockHash != (types.UnlockHash{}):
		return errors.New("file contract missed proof output not sent to void")
	}

	// check unlock hash
	uc := types.UnlockConditions{
		PublicKeys:         []types.SiaPublicKey{renterKey, h.publicKey},
		SignaturesRequired: 2,
	}
	if fc.UnlockHash != uc.UnlockHash() {
		return errors.New("bad file contract unlock hash")
	}

	return nil
}
Пример #16
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)
	}
}
Пример #17
0
// Renew negotiates a new contract for data already stored with a host. It
// returns the ID of the new contract. This is a blocking call that performs
// network I/O.
func (hdb *HostDB) Renew(fcid types.FileContractID, newEndHeight types.BlockHeight) (types.FileContractID, error) {
	hdb.mu.RLock()
	height := hdb.blockHeight
	hc, ok := hdb.contracts[fcid]
	host, eok := hdb.allHosts[hc.IP]
	hdb.mu.RUnlock()
	if !ok {
		return types.FileContractID{}, errors.New("no record of that contract")
	} else if !eok {
		return types.FileContractID{}, errors.New("no record of that host")
	} else if newEndHeight < height {
		return types.FileContractID{}, errors.New("cannot renew below current height")
	} else if host.Price.Cmp(maxPrice) > 0 {
		return types.FileContractID{}, errTooExpensive
	}

	// get an address to use for negotiation
	hdb.mu.Lock()
	if hdb.cachedAddress == (types.UnlockHash{}) {
		uc, err := hdb.wallet.NextAddress()
		if err != nil {
			hdb.mu.Unlock()
			return types.FileContractID{}, err
		}
		hdb.cachedAddress = uc.UnlockHash()
	}
	ourAddress := hdb.cachedAddress
	hdb.mu.Unlock()

	renterCost := host.Price.Mul(types.NewCurrency64(hc.LastRevision.NewFileSize)).Mul(types.NewCurrency64(uint64(newEndHeight - height)))
	renterCost = renterCost.MulFloat(1.05) // extra buffer to guarantee we won't run out of money during revision
	payout := renterCost                   // no collateral

	// create file contract
	fc := types.FileContract{
		FileSize:       hc.LastRevision.NewFileSize,
		FileMerkleRoot: hc.LastRevision.NewFileMerkleRoot,
		WindowStart:    newEndHeight,
		WindowEnd:      newEndHeight + host.WindowSize,
		Payout:         payout,
		UnlockHash:     types.UnlockHash{}, // to be filled in by negotiateContract
		RevisionNumber: 0,
		ValidProofOutputs: []types.SiacoinOutput{
			// nothing returned to us; everything goes to the host
			{Value: types.ZeroCurrency, UnlockHash: ourAddress},
			{Value: types.PostTax(height, renterCost), UnlockHash: host.UnlockHash},
		},
		MissedProofOutputs: []types.SiacoinOutput{
			// nothing returned to us; everything goes to the void
			{Value: types.ZeroCurrency, UnlockHash: ourAddress},
			{Value: types.PostTax(height, renterCost), UnlockHash: types.UnlockHash{}},
		},
	}

	// create transaction builder
	txnBuilder := hdb.wallet.StartTransaction()

	// initiate connection
	conn, err := hdb.dialer.DialTimeout(hc.IP, 15*time.Second)
	if err != nil {
		return types.FileContractID{}, err
	}
	defer conn.Close()
	if err := encoding.WriteObject(conn, modules.RPCRenew); err != nil {
		return types.FileContractID{}, errors.New("couldn't initiate RPC: " + err.Error())
	}
	if err := encoding.WriteObject(conn, fcid); err != nil {
		return types.FileContractID{}, errors.New("couldn't send contract ID: " + err.Error())
	}

	// execute negotiation protocol
	newContract, err := negotiateContract(conn, hc.IP, fc, txnBuilder, hdb.tpool)
	if err != nil {
		txnBuilder.Drop() // return unused outputs to wallet
		return types.FileContractID{}, err
	}

	// update host contract
	hdb.mu.Lock()
	hdb.contracts[newContract.ID] = newContract
	hdb.cachedAddress = types.UnlockHash{} // clear cachedAddress
	err = hdb.save()
	hdb.mu.Unlock()
	if err != nil {
		hdb.log.Println("WARN: failed to save the hostdb:", err)
	}

	return newContract.ID, nil
}
Пример #18
0
// newContract negotiates an initial file contract with the specified host
// and returns a hostContract. The contract is also saved by the HostDB.
func (hdb *HostDB) newContract(host modules.HostSettings, filesize uint64, duration types.BlockHeight) (hostContract, error) {
	// reject hosts that are too expensive
	if host.Price.Cmp(maxPrice) > 0 {
		return hostContract{}, errTooExpensive
	}

	// get an address to use for negotiation
	hdb.mu.Lock()
	if hdb.cachedAddress == (types.UnlockHash{}) {
		uc, err := hdb.wallet.NextAddress()
		if err != nil {
			hdb.mu.Unlock()
			return hostContract{}, err
		}
		hdb.cachedAddress = uc.UnlockHash()
	}
	ourAddress := hdb.cachedAddress
	hdb.mu.Unlock()

	// create file contract
	renterCost := host.Price.Mul(types.NewCurrency64(filesize)).Mul(types.NewCurrency64(uint64(duration)))
	renterCost = renterCost.MulFloat(1.05) // extra buffer to guarantee we won't run out of money during revision
	payout := renterCost                   // no collateral

	hdb.mu.RLock()
	height := hdb.blockHeight
	hdb.mu.RUnlock()

	fc := types.FileContract{
		FileSize:       0,
		FileMerkleRoot: crypto.Hash{}, // no proof possible without data
		WindowStart:    height + duration,
		WindowEnd:      height + duration + host.WindowSize,
		Payout:         payout,
		UnlockHash:     types.UnlockHash{}, // to be filled in by negotiateContract
		RevisionNumber: 0,
		ValidProofOutputs: []types.SiacoinOutput{
			// outputs need to account for tax
			{Value: types.PostTax(height, renterCost), UnlockHash: ourAddress},
			// no collateral
			{Value: types.ZeroCurrency, UnlockHash: host.UnlockHash},
		},
		MissedProofOutputs: []types.SiacoinOutput{
			// same as above
			{Value: types.PostTax(height, renterCost), UnlockHash: ourAddress},
			// goes to the void, not the renter
			{Value: types.ZeroCurrency, UnlockHash: types.UnlockHash{}},
		},
	}

	// create transaction builder
	txnBuilder := hdb.wallet.StartTransaction()

	// initiate connection
	conn, err := hdb.dialer.DialTimeout(host.NetAddress, 15*time.Second)
	if err != nil {
		return hostContract{}, err
	}
	defer conn.Close()
	if err := encoding.WriteObject(conn, modules.RPCUpload); err != nil {
		return hostContract{}, err
	}

	// execute negotiation protocol
	contract, err := negotiateContract(conn, host.NetAddress, fc, txnBuilder, hdb.tpool)
	if err != nil {
		txnBuilder.Drop() // return unused outputs to wallet
		return hostContract{}, err
	}

	hdb.mu.Lock()
	hdb.contracts[contract.ID] = contract
	// clear the cached address
	hdb.cachedAddress = types.UnlockHash{}
	hdb.save()
	hdb.mu.Unlock()

	return contract, nil
}
Пример #19
0
// TestPartialConfirmation checks that the transaction pool correctly accepts a
// transaction set which has parents that have been accepted by the consensus
// set but not the whole set has been accepted by the consensus set.
func TestPartialConfirmation(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	tpt, err := createTpoolTester("TestPartialConfirmation")
	if err != nil {
		t.Fatal(err)
	}
	defer tpt.Close()

	// Create and fund a valid file contract.
	builder := tpt.wallet.StartTransaction()
	payout := types.NewCurrency64(1e9)
	err = builder.FundSiacoins(payout)
	if err != nil {
		t.Fatal(err)
	}
	builder.AddFileContract(types.FileContract{
		WindowStart:        tpt.cs.Height() + 2,
		WindowEnd:          tpt.cs.Height() + 5,
		Payout:             payout,
		ValidProofOutputs:  []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}},
		MissedProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}},
		UnlockHash:         types.UnlockConditions{}.UnlockHash(),
	})
	tSet, err := builder.Sign(true)
	if err != nil {
		t.Fatal(err)
	}
	fcid := tSet[len(tSet)-1].FileContractID(0)

	// Create a file contract revision.
	rSet := []types.Transaction{{
		FileContractRevisions: []types.FileContractRevision{{
			ParentID:          fcid,
			NewRevisionNumber: 2,

			NewWindowStart:        tpt.cs.Height() + 2,
			NewWindowEnd:          tpt.cs.Height() + 5,
			NewValidProofOutputs:  []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}},
			NewMissedProofOutputs: []types.SiacoinOutput{{Value: types.PostTax(tpt.cs.Height(), payout)}},
		}},
	}}

	// Combine the contract and revision in to a single set.
	fullSet := append(tSet, rSet...)

	// Get the tSet onto the blockchain.
	unsolvedBlock, target, err := tpt.miner.BlockForWork()
	if err != nil {
		t.Fatal(err)
	}
	unsolvedBlock.Transactions = append(unsolvedBlock.Transactions, tSet...)
	solvedBlock, solved := tpt.miner.SolveBlock(unsolvedBlock, target)
	if !solved {
		t.Fatal("Failed to solve block")
	}
	err = tpt.cs.AcceptBlock(solvedBlock)
	if err != nil {
		t.Fatal(err)
	}

	// Try to get the full set into the transaction pool. The transaction pool
	// should recognize that the set is partially accepted, and be able to
	// accept on the the transactions that are new and are not yet on the
	// blockchain.
	err = tpt.tpool.AcceptTransactionSet(fullSet)
	if err != nil {
		t.Fatal(err)
	}
}
Пример #20
0
// testMissedStorageProofBlocks adds a block with a file contract, and then
// fails to submit a storage proof before expiration.
func (cst *consensusSetTester) testMissedStorageProofBlocks() {
	// Create a file contract that will be successful.
	filesize := uint64(4e3)
	payout := types.NewCurrency64(400e6)
	missedProofDest := randAddress()
	fc := types.FileContract{
		FileSize:       filesize,
		FileMerkleRoot: crypto.Hash{},
		WindowStart:    cst.cs.dbBlockHeight() + 1,
		WindowEnd:      cst.cs.dbBlockHeight() + 2,
		Payout:         payout,
		ValidProofOutputs: []types.SiacoinOutput{{
			UnlockHash: types.UnlockHash{},
			Value:      types.PostTax(cst.cs.dbBlockHeight(), payout),
		}},
		MissedProofOutputs: []types.SiacoinOutput{{
			UnlockHash: missedProofDest,
			Value:      types.PostTax(cst.cs.dbBlockHeight(), payout),
		}},
	}

	// Submit a transaction with the file contract.
	oldSiafundPool := cst.cs.dbGetSiafundPool()
	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)
	}

	// Check that the siafund pool was increased by the tax on the payout.
	siafundPool := cst.cs.dbGetSiafundPool()
	if siafundPool.Cmp(oldSiafundPool.Add(types.Tax(cst.cs.dbBlockHeight()-1, payout))) != 0 {
		panic("siafund pool was not increased correctly")
	}

	// Check that the file contract made it into the database.
	ti := len(txnSet) - 1
	fcid := txnSet[ti].FileContractID(fcIndex)
	_, err = cst.cs.dbGetFileContract(fcid)
	if err != nil {
		panic(err)
	}

	// Mine a block to close the storage proof window.
	_, 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")
	}

	// Check that the siafund pool has not changed.
	postProofPool := cst.cs.dbGetSiafundPool()
	if postProofPool.Cmp(siafundPool) != 0 {
		panic("siafund pool should not change after submitting a storage proof")
	}

	// Check that a delayed output was created for the missed proof.
	spoid := fcid.StorageProofOutputID(types.ProofMissed, 0)
	dsco, err := cst.cs.dbGetDSCO(cst.cs.dbBlockHeight()+types.MaturityDelay, spoid)
	if err != nil {
		panic(err)
	}
	if dsco.UnlockHash != fc.MissedProofOutputs[0].UnlockHash {
		panic("wrong unlock hash in dsco")
	}
	if dsco.Value.Cmp(fc.MissedProofOutputs[0].Value) != 0 {
		panic("wrong sco value in dsco")
	}
}