Example #1
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
}
Example #2
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")
	}
}
Example #3
0
// testDependentUpdates adds a parent transaction and a dependent transaction
// to the unconfirmed set. Then the parent transaction is added to the
// confirmed set but the dependent is not. A check is made to see that the
// dependent is still in the unconfirmed set.
func (tpt *tpoolTester) testDependentUpdates() {
	// Put two transactions, a parent and a dependent, into the transaction
	// pool. Then create a transaction that is in conflict with the parent.
	parent := tpt.emptyUnlockTransaction()
	dependent := types.Transaction{
		SiacoinInputs: []types.SiacoinInput{
			types.SiacoinInput{
				ParentID: parent.SiacoinOutputID(0),
			},
		},
		MinerFees: []types.Currency{
			parent.SiacoinOutputs[0].Value,
		},
	}
	err := tpt.tpool.AcceptTransaction(parent)
	if err != nil {
		tpt.t.Fatal(err)
	}
	tpt.tpUpdateWait()
	err = tpt.tpool.AcceptTransaction(dependent)
	if err != nil {
		tpt.t.Fatal(err)
	}
	tpt.tpUpdateWait()

	// Mine a block to put the parent into the confirmed set.
	tset := tpt.tpool.TransactionSet()
	tset = tset[:len(tset)-1] // strip 'dependent'
	target, exists := tpt.cs.ChildTarget(tpt.cs.CurrentBlock().ID())
	if !exists {
		tpt.t.Fatal("unable to recover child target")
	}
	block := types.Block{
		ParentID:  tpt.cs.CurrentBlock().ID(),
		Timestamp: types.Timestamp(time.Now().Unix()),
		MinerPayouts: []types.SiacoinOutput{
			types.SiacoinOutput{Value: types.CalculateCoinbase(tpt.cs.Height() + 1)},
		},
		Transactions: tset,
	}
	for {
		var found bool
		block, found = tpt.miner.SolveBlock(block, target)
		if found {
			err = tpt.cs.AcceptBlock(block)
			if err != nil {
				tpt.t.Fatal(err)
			}
			break
		}
	}
	tpt.csUpdateWait()

	// Check that 'parent' and 'dependent' have been removed from the
	// transaction set, since conflict has made the confirmed set.
	if len(tpt.tpool.TransactionSet()) != 1 {
		tpt.t.Error("dependent transaction does not remain unconfirmed after parent has been confirmed:", len(tset))
	}
}
Example #4
0
// TestBuriedBadFork creates a block with an invalid transaction that's not on
// the longest fork. The consensus set will not validate that block. Then valid
// blocks are added on top of it to make it the longest fork. When it becomes
// the longest fork, all the blocks should be fully validated and thrown out
// because a parent is invalid.
func TestBuriedBadFork(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()
	cst, err := createConsensusSetTester("TestBuriedBadFork")
	if err != nil {
		t.Fatal(err)
	}
	defer cst.Close()
	pb := cst.cs.dbCurrentProcessedBlock()

	// Create a bad block that builds on a parent, so that it is part of not
	// the longest fork.
	badBlock := types.Block{
		ParentID:     pb.Block.ParentID,
		Timestamp:    types.CurrentTimestamp(),
		MinerPayouts: []types.SiacoinOutput{{Value: types.CalculateCoinbase(pb.Height)}},
		Transactions: []types.Transaction{{
			SiacoinInputs: []types.SiacoinInput{{}}, // Will trigger an error on full verification but not partial verification.
		}},
	}
	parent, err := cst.cs.dbGetBlockMap(pb.Block.ParentID)
	if err != nil {
		t.Fatal(err)
	}
	badBlock, _ = cst.miner.SolveBlock(badBlock, parent.ChildTarget)
	err = cst.cs.AcceptBlock(badBlock)
	if err != modules.ErrNonExtendingBlock {
		t.Fatal(err)
	}

	// Build another bock on top of the bad block that is fully valid, this
	// will cause a fork and full validation of the bad block, both the bad
	// block and this block should be thrown away.
	block := types.Block{
		ParentID:     badBlock.ID(),
		Timestamp:    types.CurrentTimestamp(),
		MinerPayouts: []types.SiacoinOutput{{Value: types.CalculateCoinbase(pb.Height + 1)}},
	}
	block, _ = cst.miner.SolveBlock(block, parent.ChildTarget) // okay because the target will not change
	err = cst.cs.AcceptBlock(block)
	if err == nil {
		t.Fatal("a bad block failed to cause an error")
	}
}
Example #5
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.")
	}
}
Example #6
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
}
Example #7
0
// createConsensusObjects initialzes the consensus portions of the database.
func (cs *ConsensusSet) createConsensusDB(tx *bolt.Tx) error {
	// Enumerate and create the database buckets.
	buckets := [][]byte{
		BlockHeight,
		BlockMap,
		BlockPath,
		Consistency,
		SiacoinOutputs,
		FileContracts,
		SiafundOutputs,
		SiafundPool,
	}
	for _, bucket := range buckets {
		_, err := tx.CreateBucket(bucket)
		if err != nil {
			return err
		}
	}

	// Set the block height to -1, so the genesis block is at height 0.
	blockHeight := tx.Bucket(BlockHeight)
	underflow := types.BlockHeight(0)
	err := blockHeight.Put(BlockHeight, encoding.Marshal(underflow-1))
	if err != nil {
		return err
	}

	// Set the siafund pool to 0.
	setSiafundPool(tx, types.NewCurrency64(0))

	// Update the siafund output diffs map for the genesis block on disk. This
	// needs to happen between the database being opened/initilized and the
	// consensus set hash being calculated
	for _, sfod := range cs.blockRoot.SiafundOutputDiffs {
		commitSiafundOutputDiff(tx, sfod, modules.DiffApply)
	}

	// Add the miner payout from the genesis block to the delayed siacoin
	// outputs - unspendable, as the unlock hash is blank.
	createDSCOBucket(tx, types.MaturityDelay)
	addDSCO(tx, types.MaturityDelay, cs.blockRoot.Block.MinerPayoutID(0), types.SiacoinOutput{
		Value:      types.CalculateCoinbase(0),
		UnlockHash: types.UnlockHash{},
	})

	// Add the genesis block to the block strucutres - checksum must be taken
	// after pushing the genesis block into the path.
	pushPath(tx, cs.blockRoot.Block.ID())
	if build.DEBUG {
		cs.blockRoot.ConsensusChecksum = consensusChecksum(tx)
	}
	addBlockMap(tx, &cs.blockRoot)
	return nil
}
Example #8
0
// TestBuriedBadTransaction tries submitting a block with a bad transaction
// that is buried under good transactions.
func TestBuriedBadTransaction(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()
	cst, err := createConsensusSetTester("TestBuriedBadTransaction")
	if err != nil {
		t.Fatal(err)
	}
	defer cst.Close()
	pb := cst.cs.dbCurrentProcessedBlock()

	// Create a good transaction using the wallet.
	txnValue := types.NewCurrency64(1200)
	txnBuilder := cst.wallet.StartTransaction()
	err = txnBuilder.FundSiacoins(txnValue)
	if err != nil {
		t.Fatal(err)
	}
	txnBuilder.AddSiacoinOutput(types.SiacoinOutput{Value: txnValue})
	txnSet, err := txnBuilder.Sign(true)
	if err != nil {
		t.Fatal(err)
	}
	err = cst.tpool.AcceptTransactionSet(txnSet)
	if err != nil {
		t.Fatal(err)
	}

	// Create a bad transaction
	badTxn := types.Transaction{
		SiacoinInputs: []types.SiacoinInput{{}},
	}
	txns := append(cst.tpool.TransactionList(), badTxn)

	// Create a block with a buried bad transaction.
	block := types.Block{
		ParentID:     pb.Block.ID(),
		Timestamp:    types.CurrentTimestamp(),
		MinerPayouts: []types.SiacoinOutput{{Value: types.CalculateCoinbase(pb.Height + 1)}},
		Transactions: txns,
	}
	block, _ = cst.miner.SolveBlock(block, pb.ChildTarget)
	err = cst.cs.AcceptBlock(block)
	if err == nil {
		t.Error("buried transaction didn't cause an error")
	}
}
Example #9
0
// initDatabase is run when the database. This has become the true
// init function for consensus set
func (cs *ConsensusSet) initSetDB() error {
	err := cs.db.startConsistencyGuard()
	if err != nil {
		return err
	}

	// add genesis block
	err = cs.db.addBlockMap(&cs.blockRoot)
	if err != nil {
		return err
	}
	err = cs.db.pushPath(cs.blockRoot.Block.ID())
	if err != nil {
		return err
	}

	// Set the siafund pool to 0.
	err = cs.db.Update(func(tx *bolt.Tx) error {
		sfpBucket := tx.Bucket(SiafundPool)
		return sfpBucket.Put(SiafundPool, encoding.Marshal(types.NewCurrency64(0)))
	})
	if err != nil {
		return err
	}

	// Update the siafundoutput diffs map for the genesis block on
	// disk. This needs to happen between the database being
	// opened/initilized and the consensus set hash being calculated
	for _, sfod := range cs.blockRoot.SiafundOutputDiffs {
		cs.commitSiafundOutputDiff(sfod, modules.DiffApply)
	}

	// Prevent the miner payout for the genesis block from being spent
	cs.db.addSiacoinOutputs(cs.blockRoot.Block.MinerPayoutID(0), types.SiacoinOutput{
		Value:      types.CalculateCoinbase(0),
		UnlockHash: types.UnlockHash{},
	})

	if build.DEBUG {
		cs.blockRoot.ConsensusSetHash = cs.consensusSetHash()
		cs.db.updateBlockMap(&cs.blockRoot)
	}
	cs.db.stopConsistencyGuard()
	return nil
}
Example #10
0
// Creates a block ready for nonce grinding, also returning the MerkleRoot of
// the block. Getting the MerkleRoot of a block requires encoding and hashing
// in a specific way, which are implementation details we didn't want to
// require external miners to need to worry about. All blocks returned are
// unique, which means all miners can safely start at the '0' nonce.
func (m *Miner) blockForWork() (types.Block, types.Target) {
	// Determine the timestamp.
	blockTimestamp := types.CurrentTimestamp()
	if blockTimestamp < m.earliestTimestamp {
		blockTimestamp = m.earliestTimestamp
	}

	// Create the miner payouts.
	subsidy := types.CalculateCoinbase(m.height)
	for _, txn := range m.transactions {
		for _, fee := range txn.MinerFees {
			subsidy = subsidy.Add(fee)
		}
	}
	blockPayouts := []types.SiacoinOutput{types.SiacoinOutput{Value: subsidy, UnlockHash: m.address}}

	// Create the list of transacitons, including the randomized transaction.
	// The transactions are assembled by calling append(singleElem,
	// existingSlic) because doing it the reverse way has some side effects,
	// creating a race condition and ultimately changing the block hash for
	// other parts of the program. This is related to the fact that slices are
	// pointers, and not immutable objects. Use of the builtin `copy` function
	// when passing objects like blocks around may fix this problem.
	randBytes := make([]byte, types.SpecifierLen)
	rand.Read(randBytes)
	randTxn := types.Transaction{
		ArbitraryData: [][]byte{append(modules.PrefixNonSia[:], randBytes...)},
	}
	blockTransactions := append([]types.Transaction{randTxn}, m.transactions...)

	// Assemble the block
	b := types.Block{
		ParentID:     m.parent,
		Timestamp:    blockTimestamp,
		MinerPayouts: blockPayouts,
		Transactions: blockTransactions,
	}
	return b, m.target
}
Example #11
0
// checkCurrency verifies that the amount of currency in the system matches the
// amount of currency that is supposed to be in the system.
func (cst *consensusSetTester) checkCurrency() error {
	lockID := cst.cs.mu.RLock()
	defer cst.cs.mu.RUnlock(lockID)

	// Check that there are 10k siafunds.
	totalSiafunds := types.NewCurrency64(0)
	for _, sfo := range cst.cs.siafundOutputs {
		totalSiafunds = totalSiafunds.Add(sfo.Value)
	}
	if totalSiafunds.Cmp(types.NewCurrency64(types.SiafundCount)) != 0 {
		return errors.New("incorrect number of siafunds in the consensus set")
	}

	// Check that there are the expected number of siacoins.
	expectedSiacoins := types.NewCurrency64(0)
	for i := types.BlockHeight(0); i <= cst.cs.height(); i++ {
		expectedSiacoins = expectedSiacoins.Add(types.CalculateCoinbase(i))
	}
	totalSiacoins := cst.cs.siafundPool
	for _, sco := range cst.cs.siacoinOutputs {
		totalSiacoins = totalSiacoins.Add(sco.Value)
	}
	for _, fc := range cst.cs.fileContracts {
		totalSiacoins = totalSiacoins.Add(fc.Payout)
	}
	for height, dsoMap := range cst.cs.delayedSiacoinOutputs {
		if height+types.MaturityDelay > cst.cs.Height() {
			for _, dso := range dsoMap {
				totalSiacoins = totalSiacoins.Add(dso.Value)
			}
		}
	}
	if expectedSiacoins.Cmp(totalSiacoins) != 0 {
		return errors.New("incorrect number of siacoins in the consensus set")
	}
	return nil
}
Example #12
0
// Special handling for the genesis block. No other functions are called on it.
func dbAddGenesisBlock(tx *bolt.Tx) {
	id := types.GenesisID
	dbAddBlockID(tx, id, 0)
	txid := types.GenesisBlock.Transactions[0].ID()
	dbAddTransactionID(tx, txid, 0)
	for i, sfo := range types.GenesisSiafundAllocation {
		sfoid := types.GenesisBlock.Transactions[0].SiafundOutputID(uint64(i))
		dbAddSiafundOutputID(tx, sfoid, txid)
		dbAddUnlockHash(tx, sfo.UnlockHash, txid)
		dbAddSiafundOutput(tx, sfoid, sfo)
	}
	dbAddBlockFacts(tx, blockFacts{
		BlockFacts: modules.BlockFacts{
			BlockID:            id,
			Height:             0,
			Difficulty:         types.RootTarget.Difficulty(),
			Target:             types.RootTarget,
			TotalCoins:         types.CalculateCoinbase(0),
			TransactionCount:   1,
			SiafundOutputCount: uint64(len(types.GenesisSiafundAllocation)),
		},
		Timestamp: types.GenesisBlock.Timestamp,
	})
}
Example #13
0
// checkSiacoinCount checks that the number of siacoins countable within the
// consensus set equal the expected number of siacoins for the block height.
func checkSiacoinCount(tx *bolt.Tx) {
	// Iterate through all the buckets looking for the delayed siacoin output
	// buckets, and check that they are for the correct heights.
	var dscoSiacoins types.Currency
	err := tx.ForEach(func(name []byte, b *bolt.Bucket) error {
		// Check if the bucket is a delayed siacoin output bucket.
		if !bytes.HasPrefix(name, prefixDSCO) {
			return nil
		}

		// Sum up the delayed outputs in this bucket.
		err := b.ForEach(func(_, delayedOutput []byte) error {
			var sco types.SiacoinOutput
			err := encoding.Unmarshal(delayedOutput, &sco)
			if err != nil {
				manageErr(tx, err)
			}
			dscoSiacoins = dscoSiacoins.Add(sco.Value)
			return nil
		})
		if err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		manageErr(tx, err)
	}

	// Add all of the siacoin outputs.
	var scoSiacoins types.Currency
	err = tx.Bucket(SiacoinOutputs).ForEach(func(_, scoBytes []byte) error {
		var sco types.SiacoinOutput
		err := encoding.Unmarshal(scoBytes, &sco)
		if err != nil {
			manageErr(tx, err)
		}
		scoSiacoins = scoSiacoins.Add(sco.Value)
		return nil
	})
	if err != nil {
		manageErr(tx, err)
	}

	// Add all of the payouts from file contracts.
	var fcSiacoins types.Currency
	err = tx.Bucket(FileContracts).ForEach(func(_, fcBytes []byte) error {
		var fc types.FileContract
		err := encoding.Unmarshal(fcBytes, &fc)
		if err != nil {
			manageErr(tx, err)
		}
		var fcCoins types.Currency
		for _, output := range fc.ValidProofOutputs {
			fcCoins = fcCoins.Add(output.Value)
		}
		fcSiacoins = fcSiacoins.Add(fcCoins)
		return nil
	})
	if err != nil {
		manageErr(tx, err)
	}

	// Add all of the siafund claims.
	var claimSiacoins types.Currency
	err = tx.Bucket(SiafundOutputs).ForEach(func(_, sfoBytes []byte) error {
		var sfo types.SiafundOutput
		err := encoding.Unmarshal(sfoBytes, &sfo)
		if err != nil {
			manageErr(tx, err)
		}

		coinsPerFund := getSiafundPool(tx).Sub(sfo.ClaimStart)
		claimCoins := coinsPerFund.Mul(sfo.Value).Div(types.SiafundCount)
		claimSiacoins = claimSiacoins.Add(claimCoins)
		return nil
	})
	if err != nil {
		manageErr(tx, err)
	}

	// Count how many coins should exist
	deflationBlocks := types.BlockHeight(types.InitialCoinbase - types.MinimumCoinbase)
	expectedSiacoins := types.CalculateCoinbase(0).Add(types.CalculateCoinbase(blockHeight(tx))).Div(types.NewCurrency64(2))
	if blockHeight(tx) < deflationBlocks {
		expectedSiacoins = expectedSiacoins.Mul(types.NewCurrency64(uint64(blockHeight(tx) + 1)))
	} else {
		expectedSiacoins = expectedSiacoins.Mul(types.NewCurrency64(uint64(deflationBlocks + 1)))
		trailingSiacoins := types.NewCurrency64(uint64(blockHeight(tx) - deflationBlocks)).Mul(types.CalculateCoinbase(blockHeight(tx)))
		expectedSiacoins = expectedSiacoins.Add(trailingSiacoins)
	}

	totalSiacoins := dscoSiacoins.Add(scoSiacoins).Add(fcSiacoins).Add(claimSiacoins)
	if totalSiacoins.Cmp(expectedSiacoins) != 0 {
		diagnostics := fmt.Sprintf("Wrong number of siacoins\nDsco: %v\nSco: %v\nFc: %v\nClaim: %v\n", dscoSiacoins, scoSiacoins, fcSiacoins, claimSiacoins)
		if totalSiacoins.Cmp(expectedSiacoins) < 0 {
			diagnostics += fmt.Sprintf("total: %v\nexpected: %v\n expected is bigger: %v", totalSiacoins, expectedSiacoins, expectedSiacoins.Sub(totalSiacoins))
		} else {
			diagnostics += fmt.Sprintf("total: %v\nexpected: %v\n expected is bigger: %v", totalSiacoins, expectedSiacoins, totalSiacoins.Sub(expectedSiacoins))
		}
		manageErr(tx, errors.New(diagnostics))
	}
}
Example #14
0
// testBlockConflicts adds a transaction to the unconfirmed set, and then adds
// a conflicting transaction to the confirmed set, checking that the conflict
// is properly handled by the pool.
func (tpt *tpoolTester) testBlockConflicts() {
	// Put two transactions, a parent and a dependent, into the transaction
	// pool. Then create a transaction that is in conflict with the parent.
	parent := tpt.emptyUnlockTransaction()
	dependent := types.Transaction{
		SiacoinInputs: []types.SiacoinInput{
			types.SiacoinInput{
				ParentID: parent.SiacoinOutputID(0),
			},
		},
		MinerFees: []types.Currency{
			parent.SiacoinOutputs[0].Value,
		},
	}
	err := tpt.tpool.AcceptTransaction(parent)
	if err != nil {
		tpt.t.Fatal(err)
	}
	tpt.tpUpdateWait()
	err = tpt.tpool.AcceptTransaction(dependent)
	if err != nil {
		tpt.t.Fatal(err)
	}
	tpt.tpUpdateWait()

	// Create a transaction that is in conflict with the parent.
	parentValue := parent.SiacoinOutputSum()
	conflict := types.Transaction{
		SiacoinInputs: parent.SiacoinInputs,
		MinerFees: []types.Currency{
			parentValue,
		},
	}

	// Mine a block to put the conflict into the confirmed set. 'parent' has
	// dependencies of it's own, and 'conflict' has the same dependencies as
	// 'parent'. So the block we mine needs to include all of the dependencies
	// without including 'parent' or 'dependent'.
	tset := tpt.tpool.TransactionSet()
	tset = tset[:len(tset)-2]     // strip 'parent' and 'dependent'
	tset = append(tset, conflict) // add 'conflict'
	target, exists := tpt.cs.ChildTarget(tpt.cs.CurrentBlock().ID())
	if !exists {
		tpt.t.Fatal("unable to recover child target")
	}
	block := types.Block{
		ParentID:  tpt.cs.CurrentBlock().ID(),
		Timestamp: types.Timestamp(time.Now().Unix()),
		MinerPayouts: []types.SiacoinOutput{
			types.SiacoinOutput{Value: parentValue.Add(types.CalculateCoinbase(tpt.cs.Height() + 1))},
		},
		Transactions: tset,
	}
	for {
		block, found := tpt.miner.SolveBlock(block, target)
		if found {
			err = tpt.cs.AcceptBlock(block)
			if err != nil {
				tpt.t.Fatal(err)
			}
			break
		}
	}
	tpt.csUpdateWait()

	// Check that 'parent' and 'dependent' have been removed from the
	// transaction set, since conflict has made the confirmed set.
	if len(tpt.tpool.TransactionSet()) != 0 {
		tpt.t.Error("parent and dependent transaction are still in the pool after a conflict has been introduced, have", len(tset))
	}
}
Example #15
0
// testRewinding adds transactions in a block, then removes the block and
// verifies that the transaction pool adds the block transactions.
func (tpt *tpoolTester) testRewinding() {
	// Put some transactions into the unconfirmed set.
	tpt.addSiacoinTransactionToPool()
	if len(tpt.tpool.TransactionSet()) == 0 {
		tpt.t.Fatal("transaction pool has no transactions")
	}

	// Prepare an empty block to cause a rewind (by forking).
	target, exists := tpt.cs.ChildTarget(tpt.cs.CurrentBlock().ID())
	if !exists {
		tpt.t.Fatal("unable to recover child target")
	}
	forkStart := types.Block{
		ParentID:  tpt.cs.CurrentBlock().ID(),
		Timestamp: types.Timestamp(time.Now().Unix()),
		MinerPayouts: []types.SiacoinOutput{
			types.SiacoinOutput{Value: types.CalculateCoinbase(tpt.cs.Height() + 1)},
		},
	}
	for {
		var found bool
		forkStart, found = tpt.miner.SolveBlock(forkStart, target)
		if found {
			break
		}
	}

	// Mine a block with the transaction.
	b, _ := tpt.miner.FindBlock()
	err := tpt.cs.AcceptBlock(b)
	if err != nil {
		tpt.t.Fatal(err)
	}
	tpt.csUpdateWait()
	if len(tpt.tpool.TransactionSet()) != 0 {
		tpt.t.Fatal("tset should be empty after FindBlock()")
	}

	// Fork around the block with the transaction.
	err = tpt.cs.AcceptBlock(forkStart)
	if err != nil && err != modules.ErrNonExtendingBlock {
		tpt.t.Fatal(err)
	}
	target, exists = tpt.cs.ChildTarget(tpt.cs.CurrentBlock().ID())
	if !exists {
		tpt.t.Fatal("unable to recover child target")
	}
	forkCommit := types.Block{
		ParentID:  forkStart.ID(),
		Timestamp: types.Timestamp(time.Now().Unix()),
		MinerPayouts: []types.SiacoinOutput{
			types.SiacoinOutput{Value: types.CalculateCoinbase(tpt.cs.Height() + 1)},
		},
	}
	for {
		var found bool
		forkCommit, found = tpt.miner.SolveBlock(forkCommit, target)
		if found {
			tpt.cs.AcceptBlock(forkCommit)
			break
		}
	}
	tpt.csUpdateWait()

	// Check that the transaction which was once confirmed but no longer is
	// confirmed is now unconfirmed.
	if len(tpt.tpool.TransactionSet()) == 0 {
		tpt.t.Error("tset should contain transactions that used to be confirmed but no longer are")
	}
}
Example #16
0
// New returns a new State, containing at least the genesis block. If there is
// an existing block database present in saveDir, it will be loaded. Otherwise,
// a new database will be created.
func New(gateway modules.Gateway, saveDir string) (*State, error) {
	if gateway == nil {
		return nil, ErrNilGateway
	}

	// Create the State object.
	cs := &State{
		blockMap:  make(map[types.BlockID]*blockNode),
		dosBlocks: make(map[types.BlockID]struct{}),

		currentPath: make([]types.BlockID, 1),

		siacoinOutputs:        make(map[types.SiacoinOutputID]types.SiacoinOutput),
		fileContracts:         make(map[types.FileContractID]types.FileContract),
		siafundOutputs:        make(map[types.SiafundOutputID]types.SiafundOutput),
		delayedSiacoinOutputs: make(map[types.BlockHeight]map[types.SiacoinOutputID]types.SiacoinOutput),

		gateway: gateway,

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

	// Create the genesis block and add it as the BlockRoot.
	genesisBlock := types.Block{
		Timestamp: types.GenesisTimestamp,
		Transactions: []types.Transaction{
			{SiafundOutputs: types.GenesisSiafundAllocation},
		},
	}
	cs.blockRoot = &blockNode{
		block:       genesisBlock,
		childTarget: types.RootTarget,
		depth:       types.RootDepth,

		diffsGenerated: true,
	}
	cs.blockMap[genesisBlock.ID()] = cs.blockRoot

	// Fill out the consensus information for the genesis block.
	cs.currentPath[0] = genesisBlock.ID()
	cs.siacoinOutputs[genesisBlock.MinerPayoutID(0)] = types.SiacoinOutput{
		Value:      types.CalculateCoinbase(0),
		UnlockHash: types.ZeroUnlockHash,
	}

	// Allocate the Siafund addresses by putting them all in a big transaction
	// and applying the diffs.
	for i, siafundOutput := range genesisBlock.Transactions[0].SiafundOutputs {
		sfid := genesisBlock.Transactions[0].SiafundOutputID(i)
		sfod := modules.SiafundOutputDiff{
			Direction:     modules.DiffApply,
			ID:            sfid,
			SiafundOutput: siafundOutput,
		}
		cs.commitSiafundOutputDiff(sfod, modules.DiffApply)
		cs.blockRoot.siafundOutputDiffs = append(cs.blockRoot.siafundOutputDiffs, sfod)
	}

	// Send out genesis block update.
	cs.updateSubscribers(nil, []*blockNode{cs.blockRoot})

	// Create the consensus directory.
	err := os.MkdirAll(saveDir, 0700)
	if err != nil {
		return nil, err
	}

	// During short tests, use an in-memory database.
	if build.Release == "testing" && testing.Short() {
		cs.db = persist.NilDB
	} else {
		// Otherwise, try to load an existing database from disk.
		err = cs.load(saveDir)
		if err != nil {
			return nil, err
		}
	}

	// Register RPCs
	gateway.RegisterRPC("SendBlocks", cs.sendBlocks)
	gateway.RegisterRPC("RelayBlock", cs.RelayBlock)
	gateway.RegisterConnectCall("SendBlocks", cs.receiveBlocks)

	// Spawn resynchronize loop.
	go cs.threadedResynchronize()

	return cs, nil
}
func BenchmarkAcceptBigTxBlocks(b *testing.B) {
	b.ReportAllocs()

	numSigs := 7

	cst, err := createConsensusSetTester("BenchmarkEmptyBlocksA")
	if err != nil {
		b.Fatal(err)
	}
	defer cst.closeCst()

	// Mine until the wallet has 100 utxos
	for cst.cs.height() < (types.BlockHeight(numSigs) + types.MaturityDelay) {
		_, err := cst.miner.AddBlock()
		if err != nil {
			b.Fatal(err)
		}
	}

	// Create an alternate testing consensus set, which does not
	// have any subscribers
	testdir := build.TempDir(modules.ConsensusDir, "BenchmarkEmptyBlocksB")
	g, err := gateway.New(":0", filepath.Join(testdir, modules.GatewayDir))
	if err != nil {
		b.Fatal(err)
	}
	cs, err := New(g, filepath.Join(testdir, modules.ConsensusDir))
	if err != nil {
		b.Fatal("Error creating consensus: " + err.Error())
	}
	defer cs.Close()
	h := cst.cs.db.pathHeight()
	for i := types.BlockHeight(1); i < h; i++ {
		err = cs.AcceptBlock(cst.cs.db.getBlockMap(cst.cs.db.getPath(i)).Block)
		if err != nil {
			b.Fatal(err)
		}
	}

	// construct a transaction using numSigs utxo's, and signed numSigs times
	outputValues := make([]types.Currency, numSigs)
	txValue := types.ZeroCurrency
	for i := 1; i <= numSigs; i++ {
		outputValues[i-1] = types.CalculateCoinbase(types.BlockHeight(i))
		txValue = txValue.Add(outputValues[i-1])
	}

	b.ResetTimer()
	b.StopTimer()
	for j := 0; j < b.N; j++ {
		txnBuilder := cst.wallet.StartTransaction()
		err = txnBuilder.FundSiacoins(txValue)
		if err != nil {
			b.Fatal(err)
		}

		for i := 0; i < numSigs; i++ {
			unlockConditions, err := cst.wallet.NextAddress()
			if err != nil {
				b.Fatal(err)
			}
			txnBuilder.AddSiacoinOutput(types.SiacoinOutput{Value: outputValues[i], UnlockHash: unlockConditions.UnlockHash()})
		}

		txnSet, err := txnBuilder.Sign(true)
		if err != nil {
			b.Fatal(err)
		}

		outputVolume := types.ZeroCurrency
		for _, out := range txnSet[0].SiacoinOutputs {
			outputVolume = outputVolume.Add(out.Value)
		}

		blk := types.Block{
			ParentID:  cst.cs.CurrentBlock().ID(),
			Timestamp: types.CurrentTimestamp(),
			MinerPayouts: []types.SiacoinOutput{
				{Value: types.CalculateCoinbase(cst.cs.height())},
			},
			Transactions: txnSet,
		}

		target, _ := cst.cs.ChildTarget(cst.cs.CurrentBlock().ID())
		block, _ := cst.miner.SolveBlock(blk, target)
		// Submit it to the first consensus set for validity
		err = cst.cs.AcceptBlock(block)
		if err != nil {
			b.Fatal(err)
		}
		b.StartTimer()
		// Time the consensus set without subscribers
		err = cs.AcceptBlock(block)
		if err != nil {
			b.Fatal(err)
		}
		b.StopTimer()
	}
}
Example #18
0
// TestIntegrationWalletGETSiacoins probes the GET call to /wallet when the
// siacoin balance is being manipulated.
func TestIntegrationWalletGETSiacoins(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	st, err := createServerTester("TestIntegrationWalletGETSiacoins")
	if err != nil {
		t.Fatal(err)
	}

	// Check the intial wallet is encrypted, unlocked, and has the siacoins
	// that got mined.
	var wg WalletGET
	err = st.getAPI("/wallet", &wg)
	if err != nil {
		t.Fatal(err)
	}
	if !wg.Encrypted {
		t.Error("Wallet has been encrypted")
	}
	if !wg.Unlocked {
		t.Error("Wallet has been unlocked")
	}
	if wg.ConfirmedSiacoinBalance.Cmp(types.CalculateCoinbase(1)) != 0 {
		t.Error("reported wallet balance does not reflect the single block that has been mined")
	}
	if wg.UnconfirmedOutgoingSiacoins.Cmp(types.NewCurrency64(0)) != 0 {
		t.Error("there should not be unconfirmed outgoing siacoins")
	}
	if wg.UnconfirmedIncomingSiacoins.Cmp(types.NewCurrency64(0)) != 0 {
		t.Error("there should not be unconfirmed incoming siacoins")
	}

	// Send coins to a wallet address through the api.
	var wag WalletAddressGET
	err = st.getAPI("/wallet/address", &wag)
	if err != nil {
		t.Fatal(err)
	}
	sendSiacoinsValues := url.Values{}
	sendSiacoinsValues.Set("amount", "1234")
	sendSiacoinsValues.Add("destination", wag.Address.String())
	err = st.stdPostAPI("/wallet/siacoins", sendSiacoinsValues)
	if err != nil {
		t.Fatal(err)
	}

	// Check that the wallet is reporting unconfirmed siacoins.
	err = st.getAPI("/wallet", &wg)
	if err != nil {
		t.Fatal(err)
	}
	if !wg.Encrypted {
		t.Error("Wallet has been encrypted")
	}
	if !wg.Unlocked {
		t.Error("Wallet has been unlocked")
	}
	if wg.ConfirmedSiacoinBalance.Cmp(types.CalculateCoinbase(1)) != 0 {
		t.Error("reported wallet balance does not reflect the single block that has been mined")
	}
	if wg.UnconfirmedOutgoingSiacoins.Cmp(types.NewCurrency64(0)) <= 0 {
		t.Error("there should be unconfirmed outgoing siacoins")
	}
	if wg.UnconfirmedIncomingSiacoins.Cmp(types.NewCurrency64(0)) <= 0 {
		t.Error("there should be unconfirmed incoming siacoins")
	}
	if wg.UnconfirmedOutgoingSiacoins.Cmp(wg.UnconfirmedIncomingSiacoins) <= 0 {
		t.Error("net movement of siacoins should be outgoing (miner fees)")
	}

	// Mine a block and see that the unconfirmed balances reduce back to
	// nothing.
	_, err = st.miner.AddBlock()
	if err != nil {
		t.Fatal(err)
	}
	err = st.getAPI("/wallet", &wg)
	if err != nil {
		t.Fatal(err)
	}
	if wg.ConfirmedSiacoinBalance.Cmp(types.CalculateCoinbase(1).Add(types.CalculateCoinbase(2))) >= 0 {
		t.Error("reported wallet balance does not reflect mining two blocks and eating a miner fee")
	}
	if wg.UnconfirmedOutgoingSiacoins.Cmp(types.NewCurrency64(0)) != 0 {
		t.Error("there should not be unconfirmed outgoing siacoins")
	}
	if wg.UnconfirmedIncomingSiacoins.Cmp(types.NewCurrency64(0)) != 0 {
		t.Error("there should not be unconfirmed incoming siacoins")
	}
}
Example #19
0
// postEncryptionTesting runs a series of checks on the wallet after it has
// been encrypted, to make sure that locking, unlocking, and spending after
// unlocking are all happening in the correct order and returning the correct
// errors.
func postEncryptionTesting(m modules.Miner, w *Wallet, masterKey crypto.TwofishKey) {
	if !w.Encrypted() {
		panic("wallet is not encrypted when starting postEncryptionTesting")
	}
	if w.Unlocked() {
		panic("wallet is unlocked when starting postEncryptionTesting")
	}
	if len(w.seeds) != 0 {
		panic("wallet has seeds in it when startin postEncryptionTesting")
	}

	// Try unlocking and using the wallet.
	err := w.Unlock(masterKey)
	if err != nil {
		panic(err)
	}
	err = w.Unlock(masterKey)
	if err != errAlreadyUnlocked {
		panic(err)
	}
	// Mine enough coins so that a balance appears (and some buffer for the
	// send later).
	for i := types.BlockHeight(0); i <= types.MaturityDelay+1; i++ {
		_, err := m.AddBlock()
		if err != nil {
			panic(err)
		}
	}
	siacoinBal, _, _ := w.ConfirmedBalance()
	if siacoinBal.Cmp(types.NewCurrency64(0)) <= 0 {
		panic("wallet balance reported as 0 after maturing some mined blocks")
	}
	err = w.Unlock(masterKey)
	if err != errAlreadyUnlocked {
		panic(err)
	}

	// Lock, unlock, and trying using the wallet some more.
	err = w.Lock()
	if err != nil {
		panic(err)
	}
	err = w.Lock()
	if err != modules.ErrLockedWallet {
		panic(err)
	}
	err = w.Unlock(crypto.TwofishKey{})
	if err != modules.ErrBadEncryptionKey {
		panic(err)
	}
	err = w.Unlock(masterKey)
	if err != nil {
		panic(err)
	}
	// Verify that the secret keys have been restored by sending coins to the
	// void. Send more coins than are received by mining a block.
	_, err = w.SendSiacoins(types.CalculateCoinbase(0), types.UnlockHash{})
	if err != nil {
		panic(err)
	}
	_, err = m.AddBlock()
	if err != nil {
		panic(err)
	}
	siacoinBal2, _, _ := w.ConfirmedBalance()
	if siacoinBal2.Cmp(siacoinBal) >= 0 {
		panic("balance did not increase")
	}
}
Example #20
0
// checkDSCOs scans the sets of delayed siacoin outputs and checks for
// consistency.
func checkDSCOs(tx *bolt.Tx) {
	// Create a map to track which delayed siacoin output maps exist, and
	// another map to track which ids have appeared in the dsco set.
	dscoTracker := make(map[types.BlockHeight]struct{})
	idMap := make(map[types.SiacoinOutputID]struct{})

	// Iterate through all the buckets looking for the delayed siacoin output
	// buckets, and check that they are for the correct heights.
	err := tx.ForEach(func(name []byte, b *bolt.Bucket) error {
		// If the bucket is not a delayed siacoin output bucket or a file
		// contract expiration bucket, skip.
		if !bytes.HasPrefix(name, prefixDSCO) {
			return nil
		}

		// Add the bucket to the dscoTracker.
		var height types.BlockHeight
		err := encoding.Unmarshal(name[len(prefixDSCO):], &height)
		if err != nil {
			manageErr(tx, err)
		}
		_, exists := dscoTracker[height]
		if exists {
			return errors.New("repeat dsco map")
		}
		dscoTracker[height] = struct{}{}

		var total types.Currency
		err = b.ForEach(func(idBytes, delayedOutput []byte) error {
			// Check that the output id has not appeared in another dsco.
			var id types.SiacoinOutputID
			copy(id[:], idBytes)
			_, exists := idMap[id]
			if exists {
				return errors.New("repeat delayed siacoin output")
			}
			idMap[id] = struct{}{}

			// Sum the funds in the bucket.
			var sco types.SiacoinOutput
			err := encoding.Unmarshal(delayedOutput, &sco)
			if err != nil {
				manageErr(tx, err)
			}
			total = total.Add(sco.Value)
			return nil
		})
		if err != nil {
			return err
		}

		// Check that the minimum value has been achieved - the coinbase from
		// an earlier block is guaranteed to be in the bucket.
		minimumValue := types.CalculateCoinbase(height - types.MaturityDelay)
		if total.Cmp(minimumValue) < 0 {
			return errors.New("total number of coins in the delayed output bucket is incorrect")
		}
		return nil
	})
	if err != nil {
		manageErr(tx, err)
	}

	// Check that all of the correct heights are represented.
	currentHeight := blockHeight(tx)
	expectedBuckets := 0
	for i := currentHeight + 1; i <= currentHeight+types.MaturityDelay; i++ {
		if i < types.MaturityDelay {
			continue
		}
		_, exists := dscoTracker[i]
		if !exists {
			manageErr(tx, errors.New("missing a dsco bucket"))
		}
		expectedBuckets++
	}
	if len(dscoTracker) != expectedBuckets {
		manageErr(tx, errors.New("too many dsco buckets"))
	}
}