Пример #1
0
// createContractTransaction takes contract terms and a merkle root and uses
// them to build a transaction containing a file contract that satisfies the
// terms, including providing an input balance. The transaction does not get
// signed.
func (r *Renter) createContractTransaction(terms modules.ContractTerms, merkleRoot crypto.Hash) (txn types.Transaction, txnBuilder modules.TransactionBuilder, err error) {
	// Get the payout as set by the missed proofs, and the client fund as determined by the terms.
	sizeCurrency := types.NewCurrency64(terms.FileSize)
	durationCurrency := types.NewCurrency64(uint64(terms.Duration))
	clientCost := terms.Price.Mul(sizeCurrency).Mul(durationCurrency)
	hostCollateral := terms.Collateral.Mul(sizeCurrency).Mul(durationCurrency)
	payout := clientCost.Add(hostCollateral)

	// Fill out the contract.
	contract := types.FileContract{
		FileMerkleRoot:     merkleRoot,
		FileSize:           terms.FileSize,
		WindowStart:        terms.DurationStart + terms.Duration,
		WindowEnd:          terms.DurationStart + terms.Duration + terms.WindowSize,
		Payout:             payout,
		ValidProofOutputs:  terms.ValidProofOutputs,
		MissedProofOutputs: terms.MissedProofOutputs,
	}

	// Create the transaction.
	txnBuilder = r.wallet.RegisterTransaction(txn, nil)
	err = txnBuilder.FundSiacoins(clientCost)
	if err != nil {
		return
	}
	txnBuilder.AddFileContract(contract)
	txn, _ = txnBuilder.View()
	return
}
Пример #2
0
// StoragePriceToConsensus converts a human storage price, having the unit
// 'Siacoins Per Month Per Terabyte', to a consensus storage price, having the
// unit 'Hastings Per Block Per Byte'.
func StoragePriceToConsensus(siacoinsMonthTB uint64) (hastingsBlockByte types.Currency) {
	// Perform multiplication first to preserve precision.
	hastingsMonthTB := types.NewCurrency64(siacoinsMonthTB).Mul(types.SiacoinPrecision)
	hastingsBlockTB := hastingsMonthTB.Div(types.NewCurrency64(4320))
	hastingsBlockByte = hastingsBlockTB.Div(types.NewCurrency64(1e12))
	return hastingsBlockByte
}
Пример #3
0
// TestApplySiacoinOutputs probes the applySiacoinOutput method of the
// consensus set.
func TestApplySiacoinOutputs(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	cst, err := createConsensusSetTester("TestApplySiacoinOutputs")
	if err != nil {
		t.Fatal(err)
	}

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

	// Apply a transaction with a single siacoin output.
	txn := types.Transaction{
		SiacoinOutputs: []types.SiacoinOutput{{}},
	}
	cst.cs.applySiacoinOutputs(bn, txn)
	scoid := txn.SiacoinOutputID(0)
	_, exists := cst.cs.siacoinOutputs[scoid]
	if !exists {
		t.Error("Failed to create siacoin output")
	}
	if len(cst.cs.siacoinOutputs) != 3 { // 3 because createConsensusSetTester has 2 initially.
		t.Error("siacoin outputs not correctly updated")
	}
	if len(bn.siacoinOutputDiffs) != 1 {
		t.Error("block node was not updated for single element transaction")
	}
	if bn.siacoinOutputDiffs[0].Direction != modules.DiffApply {
		t.Error("wrong diff direction applied when creating a siacoin output")
	}
	if bn.siacoinOutputDiffs[0].ID != scoid {
		t.Error("wrong id used when creating a siacoin output")
	}

	// Apply a transaction with 2 siacoin outputs.
	txn = types.Transaction{
		SiacoinOutputs: []types.SiacoinOutput{
			{Value: types.NewCurrency64(1)},
			{Value: types.NewCurrency64(2)},
		},
	}
	cst.cs.applySiacoinOutputs(bn, txn)
	scoid0 := txn.SiacoinOutputID(0)
	scoid1 := txn.SiacoinOutputID(1)
	_, exists = cst.cs.siacoinOutputs[scoid0]
	if !exists {
		t.Error("Failed to create siacoin output")
	}
	_, exists = cst.cs.siacoinOutputs[scoid1]
	if !exists {
		t.Error("Failed to create siacoin output")
	}
	if len(cst.cs.siacoinOutputs) != 5 { // 5 because createConsensusSetTester has 2 initially.
		t.Error("siacoin outputs not correctly updated")
	}
	if len(bn.siacoinOutputDiffs) != 3 {
		t.Error("block node was not updated correctly")
	}
}
Пример #4
0
// TestWeightedList inserts and removes nodes in a semi-random manner and
// verifies that the tree stays consistent through the adjustments.
func TestWeightedList(t *testing.T) {
	// Create a hostdb and 3 equal entries to insert.
	hdbt := newHDBTester("TestWeightedList", t)

	// Create a bunch of host entries of equal weight.
	firstInsertions := 64
	for i := 0; i < firstInsertions; i++ {
		entry := hostEntry{
			HostSettings: modules.HostSettings{IPAddress: fakeAddr(uint8(i))},
			weight:       types.NewCurrency64(10),
		}
		hdbt.hostdb.insertNode(&entry)
	}
	err := hdbt.uniformTreeVerification(firstInsertions)
	if err != nil {
		t.Error(err)
	}

	// Remove a few hosts and check that the tree is still in order.
	removals := 12
	// Keep a map of what we've removed so far.
	removedMap := make(map[uint8]struct{})
	for i := 0; i < removals; i++ {
		// Try numbers until we roll a number that's not been removed yet.
		var randInt uint8
		for {
			randBig, err := rand.Int(rand.Reader, big.NewInt(int64(firstInsertions)))
			if err != nil {
				t.Fatal(err)
			}
			randInt = uint8(randBig.Int64())
			_, exists := removedMap[randInt]
			if !exists {
				break
			}
		}

		// Remove the entry and add it to the list of removed entries
		err := hdbt.hostdb.RemoveHost(fakeAddr(randInt))
		if err != nil {
			t.Fatal(err)
		}
		removedMap[randInt] = struct{}{}
	}
	err = hdbt.uniformTreeVerification(firstInsertions - removals)
	if err != nil {
		t.Error(err)
	}

	// Do some more insertions.
	secondInsertions := 64
	for i := firstInsertions; i < firstInsertions+secondInsertions; i++ {
		entry := hostEntry{
			HostSettings: modules.HostSettings{IPAddress: fakeAddr(uint8(i))},
			weight:       types.NewCurrency64(10),
		}
		hdbt.hostdb.insertNode(&entry)
	}
	hdbt.uniformTreeVerification(firstInsertions - removals + secondInsertions)
}
Пример #5
0
// TestActiveHosts tests the ActiveHosts method.
func TestActiveHosts(t *testing.T) {
	hdb := bareHostDB()

	// empty
	if hosts := hdb.ActiveHosts(); len(hosts) != 0 {
		t.Errorf("wrong number of hosts: expected %v, got %v", 0, len(hosts))
	}

	// with one host
	h1 := new(hostEntry)
	h1.NetAddress = "foo"
	h1.Weight = types.NewCurrency64(1)
	h1.AcceptingContracts = true
	hdb.insertNode(h1)
	if hosts := hdb.ActiveHosts(); len(hosts) != 1 {
		t.Errorf("wrong number of hosts: expected %v, got %v", 1, len(hosts))
	} else if hosts[0].NetAddress != h1.NetAddress {
		t.Errorf("ActiveHosts returned wrong host: expected %v, got %v", h1.NetAddress, hosts[0].NetAddress)
	}

	// with multiple hosts
	h2 := new(hostEntry)
	h2.NetAddress = "bar"
	h2.Weight = types.NewCurrency64(1)
	h2.AcceptingContracts = true
	hdb.insertNode(h2)
	if hosts := hdb.ActiveHosts(); len(hosts) != 2 {
		t.Errorf("wrong number of hosts: expected %v, got %v", 2, len(hosts))
	} else if hosts[0].NetAddress != h1.NetAddress && hosts[1].NetAddress != h1.NetAddress {
		t.Errorf("ActiveHosts did not contain an inserted host: %v (missing %v)", hosts, h1.NetAddress)
	} else if hosts[0].NetAddress != h2.NetAddress && hosts[1].NetAddress != h2.NetAddress {
		t.Errorf("ActiveHosts did not contain an inserted host: %v (missing %v)", hosts, h2.NetAddress)
	}
}
Пример #6
0
// checkSiacoins counts the number of siacoins in the database and verifies
// that it matches the sum of all the coinbases.
func (cs *ConsensusSet) checkSiacoins() error {
	// Calculate the number of expected coins in constant time.
	deflationBlocks := types.InitialCoinbase - types.MinimumCoinbase
	expectedSiacoins := types.CalculateCoinbase(0).Add(types.CalculateCoinbase(cs.height())).Div(types.NewCurrency64(2))
	if cs.height() < types.BlockHeight(deflationBlocks) {
		expectedSiacoins = expectedSiacoins.Mul(types.NewCurrency64(uint64(cs.height()) + 1))
	} else {
		expectedSiacoins = expectedSiacoins.Mul(types.NewCurrency64(deflationBlocks + 1))
		trailingSiacoins := types.NewCurrency64(uint64(cs.height()) - deflationBlocks).Mul(types.CalculateCoinbase(cs.height()))
		expectedSiacoins = expectedSiacoins.Add(trailingSiacoins)
	}

	totalSiacoins := types.ZeroCurrency
	cs.db.forEachSiacoinOutputs(func(scoid types.SiacoinOutputID, sco types.SiacoinOutput) {
		totalSiacoins = totalSiacoins.Add(sco.Value)
	})
	cs.db.forEachFileContracts(func(fcid types.FileContractID, fc types.FileContract) {
		var payout types.Currency
		for _, output := range fc.ValidProofOutputs {
			payout = payout.Add(output.Value)
		}
		totalSiacoins = totalSiacoins.Add(payout)
	})
	cs.db.forEachDelayedSiacoinOutputs(func(v types.SiacoinOutputID, dso types.SiacoinOutput) {
		totalSiacoins = totalSiacoins.Add(dso.Value)
	})
	cs.db.forEachSiafundOutputs(func(sfoid types.SiafundOutputID, sfo types.SiafundOutput) {
		sfoSiacoins := cs.siafundPool.Sub(sfo.ClaimStart).Div(types.SiafundCount).Mul(sfo.Value)
		totalSiacoins = totalSiacoins.Add(sfoSiacoins)
	})
	if expectedSiacoins.Cmp(totalSiacoins) != 0 {
		return errSiacoinMiscount
	}
	return nil
}
Пример #7
0
// TestRepeatInsert inserts 2 hosts with the same address.
func TestRepeatInsert(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	hdb := &HostDB{
		activeHosts: make(map[modules.NetAddress]*hostNode),
		allHosts:    make(map[modules.NetAddress]*hostEntry),
		scanPool:    make(chan *hostEntry, scanPoolSize),
	}

	var dbe modules.HostDBEntry
	dbe.NetAddress = fakeAddr(0)
	entry1 := hostEntry{
		HostDBEntry: dbe,
		Weight:      types.NewCurrency64(1),
	}
	entry2 := entry1
	hdb.insertNode(&entry1)

	entry2.Weight = types.NewCurrency64(100)
	hdb.insertNode(&entry2)
	if len(hdb.activeHosts) != 1 {
		t.Error("insterting the same entry twice should result in only 1 entry in the hostdb")
	}
}
Пример #8
0
// checkWalletBalance looks at an upload and determines if there is enough
// money in the wallet to support such an upload. An error is returned if it is
// determined that there is not enough money.
func (r *Renter) checkWalletBalance(up modules.FileUploadParams) error {
	// Get the size of the file.
	fileInfo, err := os.Stat(up.Filename)
	if err != nil {
		return err
	}
	curSize := types.NewCurrency64(uint64(fileInfo.Size()))

	var averagePrice types.Currency
	sampleSize := up.ErasureCode.NumPieces() * 3 / 2
	hosts := r.hostDB.RandomHosts(sampleSize)
	for _, host := range hosts {
		averagePrice = averagePrice.Add(host.Price)
	}
	if len(hosts) == 0 {
		return errors.New("no hosts!")
	}
	averagePrice = averagePrice.Div(types.NewCurrency64(uint64(len(hosts))))
	estimatedCost := averagePrice.Mul(types.NewCurrency64(uint64(up.Duration))).Mul(curSize)
	bufferedCost := estimatedCost.Mul(types.NewCurrency64(2))

	siacoinBalance, _, _ := r.wallet.ConfirmedBalance()
	if bufferedCost.Cmp(siacoinBalance) > 0 {
		return errors.New("insufficient balance for upload")
	}
	return nil
}
Пример #9
0
// TestAveragePrice tests the AveragePrice method, which also depends on the
// randomHosts method.
func TestAveragePrice(t *testing.T) {
	hdb := bareHostDB()

	// empty
	if avg := hdb.AveragePrice(); !avg.IsZero() {
		t.Error("average of empty hostdb should be zero:", avg)
	}

	// with one host
	h1 := new(hostEntry)
	h1.NetAddress = "foo"
	h1.Price = types.NewCurrency64(100)
	h1.weight = baseWeight
	hdb.insertNode(h1)
	if avg := hdb.AveragePrice(); avg.Cmp(h1.Price) != 0 {
		t.Error("average of one host should be that host's price:", avg)
	}

	// with two hosts
	h2 := new(hostEntry)
	h2.NetAddress = "bar"
	h2.Price = types.NewCurrency64(300)
	h2.weight = baseWeight
	hdb.insertNode(h2)
	if len(hdb.activeHosts) != 2 {
		t.Error("host was not added:", hdb.activeHosts)
	}
	if avg := hdb.AveragePrice(); avg.Cmp(types.NewCurrency64(200)) != 0 {
		t.Error("average of two hosts should be their sum/2:", avg)
	}
}
Пример #10
0
// TestRepeatInsert inserts 2 hosts with the same address.
func TestRepeatInsert(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	hdb := &HostDB{
		activeHosts: make(map[modules.NetAddress]*hostNode),
		allHosts:    make(map[modules.NetAddress]*hostEntry),
		scanPool:    make(chan *hostEntry, scanPoolSize),

		mu: sync.New(modules.SafeMutexDelay, 1),
	}

	entry1 := hostEntry{
		HostSettings: modules.HostSettings{IPAddress: fakeAddr(0)},
		weight:       types.NewCurrency64(1),
	}
	entry2 := entry1
	hdb.insertNode(&entry1)

	entry2.weight = types.NewCurrency64(100)
	hdb.insertNode(&entry2)
	if len(hdb.activeHosts) != 1 {
		t.Error("insterting the same entry twice should result in only 1 entry in the hostdb")
	}
}
Пример #11
0
// Info returns generic information about the renter and the files that are
// being rented.
func (r *Renter) Info() (ri modules.RentInfo) {
	lockID := r.mu.RLock()
	defer r.mu.RUnlock(lockID)

	// Include the list of files the renter knows about.
	for filename := range r.files {
		ri.Files = append(ri.Files, filename)
	}

	// Calculate the average cost of a file.
	var totalPrice types.Currency
	redundancy := 6 // reasonable estimate until we come up with an alternative
	sampleSize := redundancy * 3
	hosts := r.hostDB.RandomHosts(sampleSize)
	for _, host := range hosts {
		totalPrice = totalPrice.Add(host.Price)
	}
	if len(hosts) == 0 {
		return
	}
	averagePrice := totalPrice.Div(types.NewCurrency64(uint64(len(hosts)))).Mul(types.NewCurrency64(uint64(redundancy)))
	// HACK: 6000 is the duration (set by the API), and 1024^3 is a GB. Price
	// is reported as per GB, no timeframe is given.
	estimatedCost := averagePrice.Mul(types.NewCurrency64(6000)).Mul(types.NewCurrency64(1024 * 1024 * 1024))
	bufferedCost := estimatedCost.Mul(types.NewCurrency64(3)) // For some reason, this estimate can still be off by a large factor.
	ri.Price = bufferedCost

	// Report the number of known hosts.
	ri.KnownHosts = len(r.hostDB.ActiveHosts())

	return
}
Пример #12
0
// Info returns generic information about the renter and the files that are
// being rented.
func (r *Renter) Info() (ri modules.RentInfo) {
	lockID := r.mu.RLock()
	// Include the list of files the renter knows about.
	for filename := range r.files {
		ri.Files = append(ri.Files, filename)
	}
	r.mu.RUnlock(lockID)

	// Calculate the average cost of a file.
	var totalPrice types.Currency
	sampleSize := defaultParityPieces + defaultDataPieces
	hosts := r.hostDB.RandomHosts(sampleSize)
	for _, host := range hosts {
		totalPrice = totalPrice.Add(host.Price)
	}
	if len(hosts) == 0 {
		return
	}
	averagePrice := totalPrice.Div(types.NewCurrency64(uint64(len(hosts))))
	estimatedCost := averagePrice.Mul(types.NewCurrency64(defaultDuration)).Mul(types.NewCurrency64(1e9)).Mul(types.NewCurrency64(defaultParityPieces + defaultDataPieces))
	// this also accounts for the buffering in the contract negotiation
	bufferedCost := estimatedCost.Mul(types.NewCurrency64(5)).Div(types.NewCurrency64(2))
	ri.Price = bufferedCost

	// Report the number of known hosts.
	ri.KnownHosts = len(r.hostDB.ActiveHosts())

	return
}
Пример #13
0
// TestDecrementReliability tests the decrementReliability method.
func TestDecrementReliability(t *testing.T) {
	hdb := bareHostDB()

	// Decrementing a non-existent host should be a no-op.
	// NOTE: can't check any post-conditions here; only indication of correct
	// behavior is that the test doesn't panic.
	hdb.decrementReliability("foo", types.NewCurrency64(0))

	// Add a host to allHosts and activeHosts. Decrementing it should remove it
	// from activeHosts.
	h := new(hostEntry)
	h.NetAddress = "foo"
	h.reliability = types.NewCurrency64(1)
	hdb.allHosts[h.NetAddress] = h
	hdb.activeHosts[h.NetAddress] = &hostNode{hostEntry: h}
	hdb.decrementReliability(h.NetAddress, types.NewCurrency64(0))
	if len(hdb.ActiveHosts()) != 0 {
		t.Error("decrementing did not remove host from activeHosts")
	}

	// Decrement reliability to 0. This should remove the host from allHosts.
	hdb.decrementReliability(h.NetAddress, h.reliability)
	if len(hdb.AllHosts()) != 0 {
		t.Error("decrementing did not remove host from allHosts")
	}
}
Пример #14
0
// Upload revises an existing file contract with a host, and then uploads a
// piece to it.
func (hu *hostUploader) Upload(data []byte) (uint64, error) {
	// offset is old filesize
	offset := hu.contract.LastRevision.NewFileSize

	// calculate price
	hu.hdb.mu.RLock()
	height := hu.hdb.blockHeight
	hu.hdb.mu.RUnlock()
	if height > hu.contract.FileContract.WindowStart {
		return 0, errors.New("contract has already ended")
	}
	piecePrice := types.NewCurrency64(uint64(len(data))).Mul(types.NewCurrency64(uint64(hu.contract.FileContract.WindowStart - height))).Mul(hu.price)
	piecePrice = piecePrice.MulFloat(1.02) // COMPATv0.4.8 -- hosts reject exact prices

	// calculate new merkle root (no error possible with bytes.Reader)
	_ = hu.tree.ReadSegments(bytes.NewReader(data))
	merkleRoot := hu.tree.Root()

	// revise the file contract
	rev := newRevision(hu.contract.LastRevision, uint64(len(data)), merkleRoot, piecePrice)
	signedTxn, err := negotiateRevision(hu.conn, rev, data, hu.contract.SecretKey)
	if err != nil {
		return 0, err
	}

	// update host contract
	hu.contract.LastRevision = rev
	hu.contract.LastRevisionTxn = signedTxn
	hu.hdb.mu.Lock()
	hu.hdb.contracts[hu.contract.ID] = hu.contract
	hu.hdb.save()
	hu.hdb.mu.Unlock()

	return offset, nil
}
Пример #15
0
// TestHostWeight probes the hostWeight function.
func TestHostWeight(t *testing.T) {
	hdbt := newHDBTester("TestHostWeight", t)

	// Create two identical entries, except that one has a price that is 2x the
	// other. The weight returned by hostWeight should be 1/8 for the more
	// expensive host.
	entry1 := hostEntry{
		HostSettings: modules.HostSettings{
			Price: types.NewCurrency64(3),
		},
	}
	entry2 := hostEntry{
		HostSettings: modules.HostSettings{
			Price: types.NewCurrency64(6),
		},
	}

	weight1 := hdbt.hostdb.hostWeight(entry1)
	weight2 := hdbt.hostdb.hostWeight(entry2)
	expectedWeight := weight1.Div(types.NewCurrency64(8))
	if weight2.Cmp(expectedWeight) != 0 {
		t.Error("Weight of expensive host is not the correct value.")
	}

	// Try a 0 price.
	entry3 := hostEntry{
		HostSettings: modules.HostSettings{
			Price: types.NewCurrency64(0),
		},
	}
	weight3 := hdbt.hostdb.hostWeight(entry3)
	if weight3.Cmp(weight1) <= 0 {
		t.Error("Free host not weighing fairly")
	}
}
Пример #16
0
// TestSendSiacoins probes the SendSiacoins method of the wallet.
func TestSendSiacoins(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	wt, err := createWalletTester("TestSendSiacoins")
	if err != nil {
		t.Fatal(err)
	}
	defer wt.closeWt()

	// Get the initial balance - should be 1 block. The unconfirmed balances
	// should be 0.
	confirmedBal, _, _ := wt.wallet.ConfirmedBalance()
	unconfirmedOut, unconfirmedIn := wt.wallet.UnconfirmedBalance()
	if confirmedBal.Cmp(types.CalculateCoinbase(1)) != 0 {
		t.Error("unexpected confirmed balance")
	}
	if unconfirmedOut.Cmp(types.ZeroCurrency) != 0 {
		t.Error("unconfirmed balance should be 0")
	}
	if unconfirmedIn.Cmp(types.ZeroCurrency) != 0 {
		t.Error("unconfirmed balance should be 0")
	}

	// Send 5000 hastings. The wallet will automatically add a fee. Outgoing
	// unconfirmed siacoins - incoming unconfirmed siacoins should equal 5000 +
	// fee.
	tpoolFee := types.NewCurrency64(10).Mul(types.SiacoinPrecision)
	_, err = wt.wallet.SendSiacoins(types.NewCurrency64(5000), types.UnlockHash{})
	if err != nil {
		t.Fatal(err)
	}
	confirmedBal2, _, _ := wt.wallet.ConfirmedBalance()
	unconfirmedOut2, unconfirmedIn2 := wt.wallet.UnconfirmedBalance()
	if confirmedBal2.Cmp(confirmedBal) != 0 {
		t.Error("confirmed balance changed without introduction of blocks")
	}
	if unconfirmedOut2.Cmp(unconfirmedIn2.Add(types.NewCurrency64(5000)).Add(tpoolFee)) != 0 {
		t.Error("sending siacoins appears to be ineffective")
	}

	// Move the balance into the confirmed set.
	b, _ := wt.miner.FindBlock()
	err = wt.cs.AcceptBlock(b)
	if err != nil {
		t.Fatal(err)
	}
	confirmedBal3, _, _ := wt.wallet.ConfirmedBalance()
	unconfirmedOut3, unconfirmedIn3 := wt.wallet.UnconfirmedBalance()
	if confirmedBal3.Cmp(confirmedBal2.Add(types.CalculateCoinbase(2)).Sub(types.NewCurrency64(5000)).Sub(tpoolFee)) != 0 {
		t.Error("confirmed balance did not adjust to the expected value")
	}
	if unconfirmedOut3.Cmp(types.ZeroCurrency) != 0 {
		t.Error("unconfirmed balance should be 0")
	}
	if unconfirmedIn3.Cmp(types.ZeroCurrency) != 0 {
		t.Error("unconfirmed balance should be 0")
	}
}
Пример #17
0
// TestCheckMinerPayouts probes the checkMinerPayouts function.
func TestCheckMinerPayouts(t *testing.T) {
	// All tests are done at height = 0.
	coinbase := types.CalculateCoinbase(0)

	// Create a block with a single valid payout.
	b := types.Block{
		MinerPayouts: []types.SiacoinOutput{
			{Value: coinbase},
		},
	}
	if !checkMinerPayouts(b, 0) {
		t.Error("payouts evaluated incorrectly when there is only one payout.")
	}

	// Try a block with an incorrect payout.
	b = types.Block{
		MinerPayouts: []types.SiacoinOutput{
			{Value: coinbase.Sub(types.NewCurrency64(1))},
		},
	}
	if checkMinerPayouts(b, 0) {
		t.Error("payouts evaluated incorrectly when there is a too-small payout")
	}

	// Try a block with 2 payouts.
	b = types.Block{
		MinerPayouts: []types.SiacoinOutput{
			{Value: coinbase.Sub(types.NewCurrency64(1))},
			{Value: types.NewCurrency64(1)},
		},
	}
	if !checkMinerPayouts(b, 0) {
		t.Error("payouts evaluated incorrectly when there are 2 payouts")
	}

	// Try a block with 2 payouts that are too large.
	b = types.Block{
		MinerPayouts: []types.SiacoinOutput{
			{Value: coinbase},
			{Value: coinbase},
		},
	}
	if checkMinerPayouts(b, 0) {
		t.Error("payouts evaluated incorrectly when there are two large payouts")
	}

	// Create a block with an empty payout.
	b = types.Block{
		MinerPayouts: []types.SiacoinOutput{
			{Value: coinbase},
			{},
		},
	}
	if checkMinerPayouts(b, 0) {
		t.Error("payouts evaluated incorrectly when there is only one payout.")
	}
}
Пример #18
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
}
Пример #19
0
// hostWeight returns the weight of a host according to the settings of the
// host database. Currently, only the price is considered.
func (hdb *HostDB) hostWeight(entry hostEntry) (weight types.Currency) {
	// Prevent a divide by zero error by making sure the price is at least one.
	price := entry.Price
	if price.Cmp(types.NewCurrency64(0)) <= 0 {
		price = types.NewCurrency64(1)
	}

	// Divide the base weight by the cube of the price.
	return baseWeight.Div(price).Div(price).Div(price)
}
Пример #20
0
// TestIntegrationSortedOutputsSorting checks that the outputs are being correctly sorted
// by the currency value.
func TestIntegrationSortedOutputsSorting(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	so := sortedOutputs{
		ids: []types.SiacoinOutputID{{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}},
		outputs: []types.SiacoinOutput{
			{Value: types.NewCurrency64(2)},
			{Value: types.NewCurrency64(3)},
			{Value: types.NewCurrency64(4)},
			{Value: types.NewCurrency64(7)},
			{Value: types.NewCurrency64(6)},
			{Value: types.NewCurrency64(0)},
			{Value: types.NewCurrency64(1)},
			{Value: types.NewCurrency64(5)},
		},
	}
	sort.Sort(so)

	expectedIDSorting := []types.SiacoinOutputID{{5}, {6}, {0}, {1}, {2}, {7}, {4}, {3}}
	for i := uint64(0); i < 8; i++ {
		if so.ids[i] != expectedIDSorting[i] {
			t.Error("an id is out of place: ", i)
		}
		if so.outputs[i].Value.Cmp(types.NewCurrency64(i)) != 0 {
			t.Error("a value is out of place: ", i)
		}
	}
}
Пример #21
0
// testFundTransaction funds and completes a transaction using the
// build-your-own transaction functions, checking that a no-refund transaction
// is created that is valid.
func (wt *walletTester) testFundTransaction() error {
	// Build a transaction that intentionally needs a refund.
	id, err := wt.wallet.RegisterTransaction(types.Transaction{})
	fund := wt.wallet.Balance(false).Sub(types.NewCurrency64(1))
	if err != nil {
		return err
	}
	_, err = wt.wallet.FundTransaction(id, fund)
	if err != nil {
		return err
	}
	wt.tpUpdateWait()
	_, _, err = wt.wallet.AddMinerFee(id, fund)
	if err != nil {
		return err
	}
	t, err := wt.wallet.SignTransaction(id, true)
	if err != nil {
		return err
	}
	err = wt.tpool.AcceptTransaction(t)
	if err != nil {
		return err
	}
	wt.tpUpdateWait()

	// Check that the length of the created transaction is 1 siacoin, and that
	// the unconfirmed balance of the wallet is 1.
	if len(t.SiacoinOutputs) != 0 {
		return errors.New("expecting 0 siacoin outputs, got non-zero result")
	}
	if wt.wallet.Balance(true).Cmp(types.NewCurrency64(1)) != 0 {
		return errors.New("incorrect balance being reported")
	}

	// Dump the transaction pool into a block and see that the balance still
	// registers correctly.
	b, _ := wt.miner.FindBlock()
	err = wt.cs.AcceptBlock(b)
	if err != nil {
		return err
	}
	wt.csUpdateWait()

	// Check that the length of the created transaction is 1 siacoin, and that
	// the unconfirmed balance of the wallet is 1 + BlockReward.
	if len(t.SiacoinOutputs) != 0 {
		return errors.New("wrong number of siacoin outputs - expecting 0")
	}
	expectedBalance := types.CalculateCoinbase(2).Add(types.NewCurrency64(1))
	if bal := wt.wallet.Balance(true); bal.Cmp(expectedBalance) != 0 {
		return errors.New("did not arrive at the expected balance")
	}
	return nil
}
Пример #22
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 := fc.Payout.Sub(fc.Tax())

	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
}
Пример #23
0
// TestApplyFileContractMaintenance probes the applyFileContractMaintenance
// method of the consensus set.
func TestApplyFileContractMaintenance(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	cst, err := createConsensusSetTester("TestApplyMissedStorageProof")
	if err != nil {
		t.Fatal(err)
	}
	defer cst.closeCst()

	// Create a block node.
	pb := new(processedBlock)
	pb.Height = cst.cs.height()

	// Create a file contract that's expiring and has 1 missed proof output.
	expiringFC := types.FileContract{
		Payout:             types.NewCurrency64(300e3),
		WindowEnd:          pb.Height,
		MissedProofOutputs: []types.SiacoinOutput{{Value: types.NewCurrency64(290e3)}},
	}
	// Assign the contract a 0-id.
	cst.cs.db.addFileContracts(types.FileContractID{}, expiringFC)
	cst.cs.db.addFCExpirations(pb.Height)
	cst.cs.db.addFCExpirationsHeight(pb.Height, types.FileContractID{})
	cst.cs.db.Update(func(tx *bolt.Tx) error {
		return cst.cs.applyFileContractMaintenance(tx, pb)
	})
	if err != nil {
		t.Fatal(err)
	}
	exists := cst.cs.db.inFileContracts(types.FileContractID{})
	if exists {
		t.Error("file contract was not consumed in missed storage proof")
	}
	spoid := types.FileContractID{}.StorageProofOutputID(types.ProofMissed, 0)
	exists = cst.cs.db.inDelayedSiacoinOutputsHeight(pb.Height+types.MaturityDelay, spoid)
	if !exists {
		t.Error("missed proof output was never created")
	}
	exists = cst.cs.db.inSiacoinOutputs(spoid)
	if exists {
		t.Error("storage proof output made it into the siacoin output set")
	}
	exists = cst.cs.db.inFileContracts(types.FileContractID{})
	if exists {
		t.Error("file contract remains after expiration")
	}
}
Пример #24
0
// TestVariedWeights runs broad statistical tests on selecting hosts with
// multiple different weights.
func TestVariedWeights(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	hdb := &HostDB{
		activeHosts: make(map[modules.NetAddress]*hostNode),
		allHosts:    make(map[modules.NetAddress]*hostEntry),
		scanPool:    make(chan *hostEntry, scanPoolSize),
	}

	// insert i hosts with the weights 0, 1, ..., i-1. 100e3 selections will be made
	// per weight added to the tree, the total number of selections necessary
	// will be tallied up as hosts are created.
	var dbe modules.HostDBEntry
	dbe.AcceptingContracts = true
	hostCount := 5
	expectedPerWeight := int(10e3)
	selections := 0
	for i := 0; i < hostCount; i++ {
		dbe.NetAddress = fakeAddr(uint8(i))
		entry := hostEntry{
			HostDBEntry: dbe,
			Weight:      types.NewCurrency64(uint64(i)),
		}
		hdb.insertNode(&entry)
		selections += i * expectedPerWeight
	}

	// Perform many random selections, noting which host was selected each
	// time.
	selectionMap := make(map[string]int)
	for i := 0; i < selections; i++ {
		randEntry := hdb.RandomHosts(1, nil)
		if len(randEntry) == 0 {
			t.Fatal("no hosts!")
		}
		node, exists := hdb.activeHosts[randEntry[0].NetAddress]
		if !exists {
			t.Fatal("can't find randomly selected node in tree")
		}
		selectionMap[node.hostEntry.Weight.String()]++
	}

	// Check that each host was selected an expected number of times. An error
	// will be reported if the host of 0 weight is ever selected.
	acceptableError := 0.2
	for weight, timesSelected := range selectionMap {
		intWeight, err := strconv.Atoi(weight)
		if err != nil {
			t.Fatal(err)
		}

		expectedSelected := float64(intWeight * expectedPerWeight)
		if float64(expectedSelected)*acceptableError > float64(timesSelected) || float64(expectedSelected)/acceptableError < float64(timesSelected) {
			t.Error("weighted list not selecting in a uniform distribution based on weight")
			t.Error(expectedSelected)
			t.Error(timesSelected)
		}
	}
}
Пример #25
0
// TestTryInvalidTransactionSet submits an invalid transaction set to the
// TryTransaction method.
func TestTryInvalidTransactionSet(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	cst, err := createConsensusSetTester("TestValidTransaction")
	if err != nil {
		t.Fatal(err)
	}
	defer cst.Close()
	initialHash := cst.cs.dbConsensusChecksum()

	// Try a valid transaction followed by an invalid transaction.
	_, err = cst.wallet.SendSiacoins(types.NewCurrency64(1), types.UnlockHash{})
	if err != nil {
		t.Fatal(err)
	}
	txns := cst.tpool.TransactionList()
	txn := types.Transaction{
		SiacoinInputs: []types.SiacoinInput{{}},
	}
	txns = append(txns, txn)
	cc, err := cst.cs.TryTransactionSet(txns)
	if err == nil {
		t.Error("bad transaction survived filter")
	}
	if cst.cs.dbConsensusChecksum() != initialHash {
		t.Error("TryTransactionSet did not restore order")
	}
	if len(cc.SiacoinOutputDiffs) != 0 {
		t.Error("consensus change was not empty despite an error being returned")
	}
}
Пример #26
0
// TestTryValidTransactionSet submits a valid transaction set to the
// TryTransactionSet method.
func TestTryValidTransactionSet(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	cst, err := createConsensusSetTester("TestValidTransaction")
	if err != nil {
		t.Fatal(err)
	}
	defer cst.Close()
	initialHash := cst.cs.dbConsensusChecksum()

	// Try a valid transaction.
	_, err = cst.wallet.SendSiacoins(types.NewCurrency64(1), types.UnlockHash{})
	if err != nil {
		t.Fatal(err)
	}
	txns := cst.tpool.TransactionList()
	cc, err := cst.cs.TryTransactionSet(txns)
	if err != nil {
		t.Error(err)
	}
	if cst.cs.dbConsensusChecksum() != initialHash {
		t.Error("TryTransactionSet did not resotre order")
	}
	if len(cc.SiacoinOutputDiffs) == 0 {
		t.Error("consensus change is missing diffs after verifying a transction clump")
	}
}
Пример #27
0
// TestInconsistencyCheck puts the consensus set in to an inconsistent state
// and makes sure that the santiy checks are triggering panics.
func TestInconsistentCheck(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	cst, err := createConsensusSetTester("TestInconsistentCheck")
	if err != nil {
		t.Fatal(err)
	}
	defer cst.closeCst()

	// Corrupt the consensus set by adding a new siafund output.
	sfo := types.SiafundOutput{
		Value: types.NewCurrency64(1),
	}
	cst.cs.dbAddSiafundOutput(types.SiafundOutputID{}, sfo)

	// Catch a panic that should be caused by the inconsistency check after a
	// block is mined.
	defer func() {
		r := recover()
		if r == nil {
			t.Fatalf("inconsistency panic not triggered by corrupted database")
		}
	}()
	cst.miner.AddBlock()
}
Пример #28
0
// TestCommitDelayedSiacoinOutputDiffBadMaturity commits a delayed sicoin
// output that has a bad maturity height and triggers a panic.
func TestCommitDelayedSiacoinOutputDiffBadMaturity(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	cst, err := createConsensusSetTester("TestCommitDelayedSiacoinOutputDiffBadMaturity")
	if err != nil {
		t.Fatal(err)
	}

	// Trigger an inconsistency check.
	defer func() {
		r := recover()
		if r == nil {
			t.Error("expecting error after corrupting database")
		}
	}()

	// Commit a delayed siacoin output with maturity height = cs.height()+1
	maturityHeight := cst.cs.height() - 1
	id := types.SiacoinOutputID{'1'}
	dsco := types.SiacoinOutput{Value: types.NewCurrency64(1)}
	dscod := modules.DelayedSiacoinOutputDiff{
		Direction:      modules.DiffApply,
		ID:             id,
		SiacoinOutput:  dsco,
		MaturityHeight: maturityHeight,
	}
	cst.cs.commitDelayedSiacoinOutputDiff(dscod, modules.DiffApply)
}
Пример #29
0
// checkMinerFees checks that the total amount of transaction fees in the
// transaction set is sufficient to earn a spot in the transaction pool.
func (tp *TransactionPool) checkMinerFees(ts []types.Transaction) error {
	// Transactions cannot be added after the TransactionPoolSizeLimit has been
	// hit.
	if tp.transactionListSize > TransactionPoolSizeLimit {
		return errFullTransactionPool
	}

	// The first TransactionPoolSizeForFee transactions do not need fees.
	if tp.transactionListSize > TransactionPoolSizeForFee {
		// Currently required fees are set on a per-transaction basis. 2 coins
		// are required per transaction if the free-fee limit has been reached,
		// adding a larger fee is not useful.
		var feeSum types.Currency
		for i := range ts {
			for _, fee := range ts[i].MinerFees {
				feeSum = feeSum.Add(fee)
			}
		}
		feeRequired := TransactionMinFee.Mul(types.NewCurrency64(uint64(len(ts))))
		if feeSum.Cmp(feeRequired) < 0 {
			return errLowMinerFees
		}
	}
	return nil
}
Пример #30
0
// renewBasePrice returns the base cost of the storage in the file contract,
// using the host external settings and the starting file contract.
func renewBasePrice(so storageObligation, settings modules.HostExternalSettings, fc types.FileContract) types.Currency {
	if fc.WindowEnd <= so.proofDeadline() {
		return types.NewCurrency64(0)
	}
	timeExtension := fc.WindowEnd - so.proofDeadline()
	return settings.StoragePrice.Mul64(fc.FileSize).Mul64(uint64(timeExtension))
}