Esempio n. 1
0
func TestOutputSplittingOversizeTx(t *testing.T) {
	tearDown, pool, _ := TstCreatePoolAndTxStore(t)
	defer tearDown()

	requestAmount := dcrutil.Amount(5)
	bigInput := int64(3)
	smallInput := int64(2)
	request := TstNewOutputRequest(
		t, 1, "34eVkREKgvvGASZW7hkgE2uNc1yycntMK6", requestAmount, pool.Manager().ChainParams())
	seriesID, eligible := TstCreateCreditsOnNewSeries(t, pool, []int64{smallInput, bigInput})
	changeStart := TstNewChangeAddress(t, pool, seriesID, 0)
	w := newWithdrawal(0, []OutputRequest{request}, eligible, *changeStart)
	w.txOptions = func(tx *withdrawalTx) {
		tx.calculateFee = TstConstantFee(0)
		tx.calculateSize = func() int {
			// Trigger an output split right after the second input is added.
			if len(tx.inputs) == 2 {
				return txMaxSize + 1
			}
			return txMaxSize - 1
		}
	}

	if err := w.fulfillRequests(); err != nil {
		t.Fatal(err)
	}

	if len(w.transactions) != 2 {
		t.Fatalf("Wrong number of finalized transactions; got %d, want 2", len(w.transactions))
	}

	tx1 := w.transactions[0]
	if len(tx1.outputs) != 1 {
		t.Fatalf("Wrong number of outputs on tx1; got %d, want 1", len(tx1.outputs))
	}
	if tx1.outputs[0].amount != dcrutil.Amount(bigInput) {
		t.Fatalf("Wrong amount for output in tx1; got %d, want %d", tx1.outputs[0].amount,
			bigInput)
	}

	tx2 := w.transactions[1]
	if len(tx2.outputs) != 1 {
		t.Fatalf("Wrong number of outputs on tx2; got %d, want 1", len(tx2.outputs))
	}
	if tx2.outputs[0].amount != dcrutil.Amount(smallInput) {
		t.Fatalf("Wrong amount for output in tx2; got %d, want %d", tx2.outputs[0].amount,
			smallInput)
	}

	if len(w.status.outputs) != 1 {
		t.Fatalf("Wrong number of output statuses; got %d, want 1", len(w.status.outputs))
	}
	status := w.status.outputs[request.outBailmentID()].status
	if status != statusSplit {
		t.Fatalf("Wrong output status; got '%s', want '%s'", status, statusSplit)
	}
}
Esempio n. 2
0
func TestWithdrawalTxInputTotal(t *testing.T) {
	tearDown, pool, _ := TstCreatePoolAndTxStore(t)
	defer tearDown()

	tx := createWithdrawalTx(t, pool, []int64{5}, []int64{})

	if tx.inputTotal() != dcrutil.Amount(5) {
		t.Fatalf("Wrong total output; got %v, want %v", tx.outputTotal(), dcrutil.Amount(5))
	}
}
Esempio n. 3
0
func TestStoreTransactionsWithChangeOutput(t *testing.T) {
	tearDown, pool, store := TstCreatePoolAndTxStore(t)
	defer tearDown()

	wtx := createWithdrawalTxWithStoreCredits(t, store, pool, []int64{5e6}, []int64{1e6, 1e6})
	wtx.changeOutput = wire.NewTxOut(int64(3e6), []byte{})
	msgtx := wtx.toMsgTx()
	tx := &changeAwareTx{MsgTx: msgtx, changeIdx: int32(len(msgtx.TxOut) - 1)}

	if err := storeTransactions(store, []*changeAwareTx{tx}); err != nil {
		t.Fatal(err)
	}

	sha := msgtx.TxSha()
	txDetails, err := store.TxDetails(&sha)
	if err != nil {
		t.Fatal(err)
	}
	if txDetails == nil {
		t.Fatal("The new tx doesn't seem to have been stored")
	}

	storedTx := txDetails.TxRecord.MsgTx
	outputTotal := int64(0)
	for i, txOut := range storedTx.TxOut {
		if int32(i) != tx.changeIdx {
			outputTotal += txOut.Value
		}
	}
	if outputTotal != int64(2e6) {
		t.Fatalf("Unexpected output amount; got %v, want %v", outputTotal, int64(2e6))
	}

	inputTotal := dcrutil.Amount(0)
	for _, debit := range txDetails.Debits {
		inputTotal += debit.Amount
	}
	if inputTotal != dcrutil.Amount(5e6) {
		t.Fatalf("Unexpected input amount; got %v, want %v", inputTotal, dcrutil.Amount(5e6))
	}

	credits, err := store.UnspentOutputs()
	if err != nil {
		t.Fatal(err)
	}
	if len(credits) != 1 {
		t.Fatalf("Unexpected number of credits in txstore; got %d, want 1", len(credits))
	}
	changeOutpoint := wire.OutPoint{Hash: sha, Index: uint32(tx.changeIdx)}
	if credits[0].OutPoint != changeOutpoint {
		t.Fatalf("Credit's outpoint (%v) doesn't match the one from change output (%v)",
			credits[0].OutPoint, changeOutpoint)
	}
}
Esempio n. 4
0
func TestWithdrawalTxOutputTotal(t *testing.T) {
	tearDown, pool, _ := TstCreatePoolAndTxStore(t)
	defer tearDown()

	tx := createWithdrawalTx(t, pool, []int64{}, []int64{4})
	tx.changeOutput = wire.NewTxOut(int64(1), []byte{})

	if tx.outputTotal() != dcrutil.Amount(4) {
		t.Fatalf("Wrong total output; got %v, want %v", tx.outputTotal(), dcrutil.Amount(4))
	}
}
Esempio n. 5
0
func (s *walletServer) FundTransaction(ctx context.Context, req *pb.FundTransactionRequest) (
	*pb.FundTransactionResponse, error) {

	policy := wallet.OutputSelectionPolicy{
		Account:               req.Account,
		RequiredConfirmations: req.RequiredConfirmations,
	}
	unspentOutputs, err := s.wallet.UnspentOutputs(policy)
	if err != nil {
		return nil, translateError(err)
	}

	selectedOutputs := make([]*pb.FundTransactionResponse_PreviousOutput, 0, len(unspentOutputs))
	var totalAmount dcrutil.Amount
	for _, output := range unspentOutputs {
		selectedOutputs = append(selectedOutputs, &pb.FundTransactionResponse_PreviousOutput{
			TransactionHash: output.OutPoint.Hash[:],
			OutputIndex:     output.OutPoint.Index,
			Amount:          output.Output.Value,
			PkScript:        output.Output.PkScript,
			ReceiveTime:     output.ReceiveTime.Unix(),
			FromCoinbase:    output.OutputKind == wallet.OutputKindCoinbase,
			Tree:            int32(output.OutPoint.Tree),
		})
		totalAmount += dcrutil.Amount(output.Output.Value)

		if req.TargetAmount != 0 && totalAmount > dcrutil.Amount(req.TargetAmount) {
			break
		}
	}

	var changeScript []byte
	if req.IncludeChangeScript && totalAmount > dcrutil.Amount(req.TargetAmount) {
		changeAddr, err := s.wallet.NewAddress(req.Account,
			waddrmgr.InternalBranch)
		if err != nil {
			return nil, translateError(err)
		}
		changeScript, err = txscript.PayToAddrScript(changeAddr)
		if err != nil {
			return nil, translateError(err)
		}
	}

	return &pb.FundTransactionResponse{
		SelectedOutputs: selectedOutputs,
		TotalAmount:     int64(totalAmount),
		ChangePkScript:  changeScript,
	}, nil
}
Esempio n. 6
0
// TestRollbackLastOutputWhenNewOutputAdded checks that we roll back the last
// output if a tx becomes too big right after we add a new output to it.
func TestRollbackLastOutputWhenNewOutputAdded(t *testing.T) {
	tearDown, pool, _ := TstCreatePoolAndTxStore(t)
	defer tearDown()

	net := pool.Manager().ChainParams()
	series, eligible := TstCreateCreditsOnNewSeries(t, pool, []int64{5, 5})
	requests := []OutputRequest{
		// This is ordered by bailment ID
		TstNewOutputRequest(t, 1, "34eVkREKgvvGASZW7hkgE2uNc1yycntMK6", 1, net),
		TstNewOutputRequest(t, 2, "3PbExiaztsSYgh6zeMswC49hLUwhTQ86XG", 2, net),
	}
	changeStart := TstNewChangeAddress(t, pool, series, 0)

	w := newWithdrawal(0, requests, eligible, *changeStart)
	w.txOptions = func(tx *withdrawalTx) {
		tx.calculateFee = TstConstantFee(0)
		tx.calculateSize = func() int {
			// Trigger an output split right after the second output is added.
			if len(tx.outputs) > 1 {
				return txMaxSize + 1
			}
			return txMaxSize - 1
		}
	}

	if err := w.fulfillRequests(); err != nil {
		t.Fatal("Unexpected error:", err)
	}

	// At this point we should have two finalized transactions.
	if len(w.transactions) != 2 {
		t.Fatalf("Wrong number of finalized transactions; got %d, want 2", len(w.transactions))
	}

	// First tx should have one output with 1 and one change output with 4
	// atoms.
	firstTx := w.transactions[0]
	req1 := requests[0]
	checkTxOutputs(t, firstTx,
		[]*withdrawalTxOut{&withdrawalTxOut{request: req1, amount: req1.Amount}})
	checkTxChangeAmount(t, firstTx, dcrutil.Amount(4))

	// Second tx should have one output with 2 and one changeoutput with 3 atomss.
	secondTx := w.transactions[1]
	req2 := requests[1]
	checkTxOutputs(t, secondTx,
		[]*withdrawalTxOut{&withdrawalTxOut{request: req2, amount: req2.Amount}})
	checkTxChangeAmount(t, secondTx, dcrutil.Amount(3))
}
Esempio n. 7
0
func ExampleAmount() {

	a := dcrutil.Amount(0)
	fmt.Println("Zero Atom:", a)

	a = dcrutil.Amount(1e8)
	fmt.Println("100,000,000 Atoms:", a)

	a = dcrutil.Amount(1e5)
	fmt.Println("100,000 Atoms:", a)
	// Output:
	// Zero Atom: 0 Coin
	// 100,000,000 Atoms: 1 Coin
	// 100,000 Atoms: 0.001 Coin
}
Esempio n. 8
0
// Check that some requested outputs are not fulfilled when we don't have credits for all
// of them.
func TestFulfillRequestsNotEnoughCreditsForAllRequests(t *testing.T) {
	tearDown, pool, _ := TstCreatePoolAndTxStore(t)
	defer tearDown()
	net := pool.Manager().ChainParams()

	// Create eligible inputs and the list of outputs we need to fulfil.
	seriesID, eligible := TstCreateCreditsOnNewSeries(t, pool, []int64{2e6, 4e6})
	out1 := TstNewOutputRequest(
		t, 1, "34eVkREKgvvGASZW7hkgE2uNc1yycntMK6", dcrutil.Amount(3e6), net)
	out2 := TstNewOutputRequest(
		t, 2, "3PbExiaztsSYgh6zeMswC49hLUwhTQ86XG", dcrutil.Amount(2e6), net)
	out3 := TstNewOutputRequest(
		t, 3, "3Qt1EaKRD9g9FeL2DGkLLswhK1AKmmXFSe", dcrutil.Amount(5e6), net)
	outputs := []OutputRequest{out1, out2, out3}
	changeStart := TstNewChangeAddress(t, pool, seriesID, 0)

	w := newWithdrawal(0, outputs, eligible, *changeStart)
	if err := w.fulfillRequests(); err != nil {
		t.Fatal(err)
	}

	tx := w.transactions[0]
	// The created tx should spend both eligible credits, so we expect it to have
	// an input amount of 2e6+4e6 atoms.
	inputAmount := eligible[0].Amount + eligible[1].Amount
	// We expect it to include outputs for requests 1 and 2, plus a change output, but
	// output request #3 should not be there because we don't have enough credits.
	change := inputAmount - (out1.Amount + out2.Amount + tx.calculateFee())
	expectedOutputs := []OutputRequest{out1, out2}
	sort.Sort(byOutBailmentID(expectedOutputs))
	expectedOutputs = append(
		expectedOutputs, TstNewOutputRequest(t, 4, changeStart.addr.String(), change, net))
	msgtx := tx.toMsgTx()
	checkMsgTxOutputs(t, msgtx, expectedOutputs)

	// withdrawal.status should state that outputs 1 and 2 were successfully fulfilled,
	// and that output 3 was not.
	expectedStatuses := map[OutBailmentID]outputStatus{
		out1.outBailmentID(): statusSuccess,
		out2.outBailmentID(): statusSuccess,
		out3.outBailmentID(): statusPartial}
	for _, wOutput := range w.status.outputs {
		if wOutput.status != expectedStatuses[wOutput.request.outBailmentID()] {
			t.Fatalf("Unexpected status for %v; got '%s', want '%s'", wOutput.request,
				wOutput.status, expectedStatuses[wOutput.request.outBailmentID()])
		}
	}
}
Esempio n. 9
0
// TstCreateCreditsOnStore inserts a new credit in the given store for
// every item in the amounts slice.
func TstCreateCreditsOnStore(t *testing.T, s *wtxmgr.Store, pkScript []byte,
	amounts []int64) []wtxmgr.Credit {
	msgTx := createMsgTx(pkScript, amounts)
	meta := &wtxmgr.BlockMeta{
		Block: wtxmgr.Block{Height: TstInputsBlock},
	}

	rec, err := wtxmgr.NewTxRecordFromMsgTx(msgTx, time.Now())
	if err != nil {
		t.Fatal(err)
	}

	if err := s.InsertTx(rec, meta); err != nil {
		t.Fatal("Failed to create inputs: ", err)
	}

	credits := make([]wtxmgr.Credit, len(msgTx.TxOut))
	for i := range msgTx.TxOut {
		if err := s.AddCredit(rec, meta, uint32(i), false); err != nil {
			t.Fatal("Failed to create inputs: ", err)
		}
		credits[i] = wtxmgr.Credit{
			OutPoint: wire.OutPoint{
				Hash:  rec.Hash,
				Index: uint32(i),
			},
			BlockMeta: *meta,
			Amount:    dcrutil.Amount(msgTx.TxOut[i].Value),
			PkScript:  msgTx.TxOut[i].PkScript,
		}
	}
	return credits
}
Esempio n. 10
0
func makeInputSource(unspents []*wire.TxOut) InputSource {
	// Return outputs in order.
	currentTotal := dcrutil.Amount(0)
	currentInputs := make([]*wire.TxIn, 0, len(unspents))
	f := func(target dcrutil.Amount) (dcrutil.Amount, []*wire.TxIn, [][]byte, error) {
		for currentTotal < target && len(unspents) != 0 {
			u := unspents[0]
			unspents = unspents[1:]
			nextInput := wire.NewTxIn(&wire.OutPoint{}, nil)
			currentTotal += dcrutil.Amount(u.Value)
			currentInputs = append(currentInputs, nextInput)
		}
		return currentTotal, currentInputs, make([][]byte, len(currentInputs)), nil
	}
	return InputSource(f)
}
Esempio n. 11
0
// parseAccountBalanceNtfnParams parses out the account name, total balance,
// and whether or not the balance is confirmed or unconfirmed from the
// parameters of an accountbalance notification.
func parseAccountBalanceNtfnParams(params []json.RawMessage) (account string,
	balance dcrutil.Amount, confirmed bool, err error) {

	if len(params) != 3 {
		return "", 0, false, wrongNumParams(len(params))
	}

	// Unmarshal first parameter as a string.
	err = json.Unmarshal(params[0], &account)
	if err != nil {
		return "", 0, false, err
	}

	// Unmarshal second parameter as a floating point number.
	var fbal float64
	err = json.Unmarshal(params[1], &fbal)
	if err != nil {
		return "", 0, false, err
	}

	// Unmarshal third parameter as a boolean.
	err = json.Unmarshal(params[2], &confirmed)
	if err != nil {
		return "", 0, false, err
	}

	// Bounds check amount.
	bal, err := dcrutil.NewAmount(fbal)
	if err != nil {
		return "", 0, false, err
	}

	return account, dcrutil.Amount(bal), confirmed, nil
}
Esempio n. 12
0
// parseTicketPurchasedNtfnParams parses out the ticket hash and amount
// from a recent ticket purchase in the wallet.
func parseTicketPurchasedNtfnParams(params []json.RawMessage) (txHash *chainhash.Hash,
	amount dcrutil.Amount, err error) {

	if len(params) != 2 {
		return nil, 0, wrongNumParams(len(params))
	}

	// Unmarshal first parameter as a string and convert to hash.
	var th string
	err = json.Unmarshal(params[0], &th)
	if err != nil {
		return nil, 0, err
	}
	thHash, err := chainhash.NewHashFromStr(th)
	if err != nil {
		return nil, 0, err
	}

	// Unmarshal second parameter as an int64.
	var amt int64
	err = json.Unmarshal(params[1], &amt)
	if err != nil {
		return nil, 0, err
	}

	return thHash, dcrutil.Amount(amt), nil
}
Esempio n. 13
0
// mockCredits decodes the given txHex and returns the outputs with
// the given indices as eligible inputs.
func mockCredits(t *testing.T, txHex string, indices []uint32) []wtxmgr.Credit {
	serialized, err := hex.DecodeString(txHex)
	if err != nil {
		t.Fatal(err)
	}
	utx, err := dcrutil.NewTxFromBytes(serialized)
	if err != nil {
		t.Fatal(err)
	}
	tx := utx.MsgTx()

	isCB := blockchain.IsCoinBaseTx(tx)
	now := time.Now()

	eligible := make([]wtxmgr.Credit, len(indices))
	c := wtxmgr.Credit{
		OutPoint: wire.OutPoint{Hash: *utx.Sha()},
		BlockMeta: wtxmgr.BlockMeta{
			Block: wtxmgr.Block{Height: -1},
		},
	}
	for i, idx := range indices {
		c.OutPoint.Index = idx
		c.Amount = dcrutil.Amount(tx.TxOut[idx].Value)
		c.PkScript = tx.TxOut[idx].PkScript
		c.Received = now
		c.FromCoinBase = isCB
		eligible[i] = c
	}
	return eligible
}
Esempio n. 14
0
func TestFindingSpentCredits(t *testing.T) {
	t.Parallel()

	s, teardown, err := testStore()
	defer teardown()
	if err != nil {
		t.Fatal(err)
	}

	// Insert transaction and credit which will be spent.
	recvRec, err := NewTxRecord(TstRecvSerializedTx, time.Now())
	if err != nil {
		t.Fatal(err)
	}

	err = s.InsertTx(recvRec, TstRecvTxBlockDetails)
	if err != nil {
		t.Fatal(err)
	}
	defaultAccount := uint32(0)
	err = s.AddCredit(recvRec, TstRecvTxBlockDetails, 1, false, defaultAccount)
	if err != nil {
		t.Fatal(err)
	}

	// Insert confirmed transaction which spends the above credit.
	spendingRec, err := NewTxRecord(TstSpendingSerializedTx, time.Now())
	if err != nil {
		t.Fatal(err)
	}

	err = s.InsertTx(spendingRec, TstSignedTxBlockDetails)
	if err != nil {
		t.Fatal(err)
	}
	err = s.AddCredit(spendingRec, TstSignedTxBlockDetails, 0, false, defaultAccount)
	if err != nil {
		t.Fatal(err)
	}

	bal, err := s.Balance(1, TstSignedTxBlockDetails.Height, wtxmgr.BFBalanceLockedStake, true, defaultAccount)
	if err != nil {
		t.Fatal(err)
	}
	expectedBal := dcrutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value)
	if bal != expectedBal {
		t.Fatalf("bad balance: %v != %v", bal, expectedBal)
	}
	unspents, err := s.UnspentOutputs()
	if err != nil {
		t.Fatal(err)
	}
	op := wire.NewOutPoint(TstSpendingTx.Sha(), 0, dcrutil.TxTreeStake)
	if unspents[0].OutPoint != *op {
		t.Fatal("unspent outpoint doesn't match expected")
	}
	if len(unspents) > 1 {
		t.Fatal("has more than one unspent credit")
	}
}
Esempio n. 15
0
func createAndFulfillWithdrawalRequests(t *testing.T, pool *Pool, roundID uint32) withdrawalInfo {

	params := pool.Manager().ChainParams()
	seriesID, eligible := TstCreateCreditsOnNewSeries(t, pool, []int64{2e6, 4e6})
	requests := []OutputRequest{
		TstNewOutputRequest(t, 1, "TsaEqDBJf63vJnqULb892wwwwPBTURUuuvn", 3e6, params),
		TstNewOutputRequest(t, 2, "Tsk7JZPtyeQHuNsSZ2K5Q8apJBusEzNtWPk", 2e6, params),
	}
	changeStart := TstNewChangeAddress(t, pool, seriesID, 0)
	dustThreshold := dcrutil.Amount(1e4)
	startAddr := TstNewWithdrawalAddress(t, pool, seriesID, 1, 0)
	lastSeriesID := seriesID
	w := newWithdrawal(roundID, requests, eligible, *changeStart)
	if err := w.fulfillRequests(); err != nil {
		t.Fatal(err)
	}
	return withdrawalInfo{
		requests:      requests,
		startAddress:  *startAddr,
		changeStart:   *changeStart,
		lastSeriesID:  lastSeriesID,
		dustThreshold: dustThreshold,
		status:        *w.status,
	}
}
Esempio n. 16
0
// Check that withdrawal.status correctly states that no outputs were fulfilled when we
// don't have enough eligible credits for any of them.
func TestFulfillRequestsNoSatisfiableOutputs(t *testing.T) {
	tearDown, pool, _ := TstCreatePoolAndTxStore(t)
	defer tearDown()

	seriesID, eligible := TstCreateCreditsOnNewSeries(t, pool, []int64{1e6})
	request := TstNewOutputRequest(
		t, 1, "3Qt1EaKRD9g9FeL2DGkLLswhK1AKmmXFSe", dcrutil.Amount(3e6), pool.Manager().ChainParams())
	changeStart := TstNewChangeAddress(t, pool, seriesID, 0)

	w := newWithdrawal(0, []OutputRequest{request}, eligible, *changeStart)
	if err := w.fulfillRequests(); err != nil {
		t.Fatal(err)
	}

	if len(w.transactions) != 0 {
		t.Fatalf("Unexpected number of transactions; got %d, want 0", len(w.transactions))
	}

	if len(w.status.outputs) != 1 {
		t.Fatalf("Unexpected number of outputs in WithdrawalStatus; got %d, want 1",
			len(w.status.outputs))
	}

	status := w.status.outputs[request.outBailmentID()].status
	if status != statusPartial {
		t.Fatalf("Unexpected status for requested outputs; got '%s', want '%s'",
			status, statusPartial)
	}
}
Esempio n. 17
0
// parseTxAcceptedNtfnParams parses out the transaction hash and total amount
// from the parameters of a txaccepted notification.
func parseTxAcceptedNtfnParams(params []json.RawMessage) (*chainhash.Hash,
	dcrutil.Amount, error) {

	if len(params) != 2 {
		return nil, 0, wrongNumParams(len(params))
	}

	// Unmarshal first parameter as a string.
	var txHashStr string
	err := json.Unmarshal(params[0], &txHashStr)
	if err != nil {
		return nil, 0, err
	}

	// Unmarshal second parameter as a floating point number.
	var famt float64
	err = json.Unmarshal(params[1], &famt)
	if err != nil {
		return nil, 0, err
	}

	// Bounds check amount.
	amt, err := dcrutil.NewAmount(famt)
	if err != nil {
		return nil, 0, err
	}

	// Decode string encoding of transaction sha.
	txHash, err := chainhash.NewHashFromStr(txHashStr)
	if err != nil {
		return nil, 0, err
	}

	return txHash, dcrutil.Amount(amt), nil
}
Esempio n. 18
0
func compareMsgTxAndWithdrawalTxOutputs(t *testing.T, msgtx *wire.MsgTx, tx *withdrawalTx) {
	nOutputs := len(tx.outputs)

	if tx.changeOutput != nil {
		nOutputs++
	}

	if len(msgtx.TxOut) != nOutputs {
		t.Fatalf("Unexpected number of TxOuts; got %d, want %d", len(msgtx.TxOut), nOutputs)
	}

	for i, output := range tx.outputs {
		outputRequest := output.request
		txOut := msgtx.TxOut[i]
		if !bytes.Equal(txOut.PkScript, outputRequest.PkScript) {
			t.Fatalf(
				"Unexpected pkScript for outputRequest %d; got %x, want %x",
				i, txOut.PkScript, outputRequest.PkScript)
		}
		gotAmount := dcrutil.Amount(txOut.Value)
		if gotAmount != outputRequest.Amount {
			t.Fatalf(
				"Unexpected amount for outputRequest %d; got %v, want %v",
				i, gotAmount, outputRequest.Amount)
		}
	}

	// Finally check the change output if it exists
	if tx.changeOutput != nil {
		msgTxChange := msgtx.TxOut[len(msgtx.TxOut)-1]
		if msgTxChange != tx.changeOutput {
			t.Fatalf("wrong TxOut in msgtx; got %v, want %v", msgTxChange, tx.changeOutput)
		}
	}
}
Esempio n. 19
0
// TstCreateSeriesCredits creates a new credit for every item in the amounts
// slice, locked to the given series' address with branch==1 and index==0.
func TstCreateSeriesCredits(t *testing.T, pool *Pool, seriesID uint32, amounts []int64) []credit {
	addr := TstNewWithdrawalAddress(t, pool, seriesID, Branch(1), Index(0))
	pkScript, err := txscript.PayToAddrScript(addr.addr)
	if err != nil {
		t.Fatal(err)
	}
	msgTx := createMsgTx(pkScript, amounts)
	txSha := msgTx.TxSha()
	credits := make([]credit, len(amounts))
	for i := range msgTx.TxOut {
		c := wtxmgr.Credit{
			OutPoint: wire.OutPoint{
				Hash:  txSha,
				Index: uint32(i),
			},
			BlockMeta: wtxmgr.BlockMeta{
				Block: wtxmgr.Block{Height: TstInputsBlock},
			},
			Amount:   dcrutil.Amount(msgTx.TxOut[i].Value),
			PkScript: msgTx.TxOut[i].PkScript,
		}
		credits[i] = newCredit(c, *addr)
	}
	return credits
}
Esempio n. 20
0
// TestOutputSplittingNotEnoughInputs checks that an output will get split if we
// don't have enough inputs to fulfil it.
func TestOutputSplittingNotEnoughInputs(t *testing.T) {
	tearDown, pool, _ := TstCreatePoolAndTxStore(t)
	defer tearDown()

	net := pool.Manager().ChainParams()
	output1Amount := dcrutil.Amount(2)
	output2Amount := dcrutil.Amount(3)
	requests := []OutputRequest{
		// These output requests will have the same server ID, so we know
		// they'll be fulfilled in the order they're defined here, which is
		// important for this test.
		TstNewOutputRequest(t, 1, "34eVkREKgvvGASZW7hkgE2uNc1yycntMK6", output1Amount, net),
		TstNewOutputRequest(t, 2, "34eVkREKgvvGASZW7hkgE2uNc1yycntMK6", output2Amount, net),
	}
	seriesID, eligible := TstCreateCreditsOnNewSeries(t, pool, []int64{7})
	w := newWithdrawal(0, requests, eligible, *TstNewChangeAddress(t, pool, seriesID, 0))
	w.txOptions = func(tx *withdrawalTx) {
		// Trigger an output split because of lack of inputs by forcing a high fee.
		// If we just started with not enough inputs for the requested outputs,
		// fulfillRequests() would drop outputs until we had enough.
		tx.calculateFee = TstConstantFee(3)
	}

	if err := w.fulfillRequests(); err != nil {
		t.Fatal(err)
	}

	if len(w.transactions) != 1 {
		t.Fatalf("Wrong number of finalized transactions; got %d, want 1", len(w.transactions))
	}

	tx := w.transactions[0]
	if len(tx.outputs) != 2 {
		t.Fatalf("Wrong number of outputs; got %d, want 2", len(tx.outputs))
	}

	// The first output should've been left untouched.
	if tx.outputs[0].amount != output1Amount {
		t.Fatalf("Wrong amount for first tx output; got %v, want %v",
			tx.outputs[0].amount, output1Amount)
	}

	// The last output should have had its amount updated to whatever we had
	// left after satisfying all previous outputs.
	newAmount := tx.inputTotal() - output1Amount - tx.calculateFee()
	checkLastOutputWasSplit(t, w, tx, output2Amount, newAmount)
}
Esempio n. 21
0
// newWithdrawalTx creates a new withdrawalTx and calls setOptions()
// passing the newly created tx.
func newWithdrawalTx(setOptions func(tx *withdrawalTx)) *withdrawalTx {
	tx := &withdrawalTx{}
	tx.calculateSize = func() int { return calculateTxSize(tx) }
	tx.calculateFee = func() dcrutil.Amount {
		return dcrutil.Amount(1+tx.calculateSize()/1000) * feeIncrement
	}
	setOptions(tx)
	return tx
}
Esempio n. 22
0
// maybeDropRequests will check the total amount we have in eligible inputs and drop
// requested outputs (in descending amount order) if we don't have enough to
// fulfill them all. For every dropped output request we update its entry in
// w.status.outputs with the status string set to statusPartial.
func (w *withdrawal) maybeDropRequests() {
	inputAmount := dcrutil.Amount(0)
	for _, input := range w.eligibleInputs {
		inputAmount += input.Amount
	}
	outputAmount := dcrutil.Amount(0)
	for _, request := range w.pendingRequests {
		outputAmount += request.Amount
	}
	sort.Sort(sort.Reverse(byAmount(w.pendingRequests)))
	for inputAmount < outputAmount {
		request := w.popRequest()
		log.Infof("Not fulfilling request to send %v to %v; not enough credits.",
			request.Amount, request.Address)
		outputAmount -= request.Amount
		w.status.outputs[request.outBailmentID()].status = statusPartial
	}
}
Esempio n. 23
0
// submitBlock submits the passed block to network after ensuring it passes all
// of the consensus validation rules.
func (m *CPUMiner) submitBlock(block *dcrutil.Block) bool {
	m.submitBlockLock.Lock()
	defer m.submitBlockLock.Unlock()

	_, latestHeight := m.server.blockManager.chainState.Best()

	// Be sure to set this so ProcessBlock doesn't fail! - Decred
	block.SetHeight(latestHeight + 1)

	// Process this block using the same rules as blocks coming from other
	// nodes. This will in turn relay it to the network like normal.
	isOrphan, err := m.server.blockManager.ProcessBlock(block, blockchain.BFNone)
	if err != nil {
		// Anything other than a rule violation is an unexpected error,
		// so log that error as an internal error.
		if rErr, ok := err.(blockchain.RuleError); !ok {
			minrLog.Errorf("Unexpected error while processing "+
				"block submitted via CPU miner: %v", err)
			return false
		} else {
			// Occasionally errors are given out for timing errors with
			// ResetMinDifficulty and high block works that is above
			// the target. Feed these to debug.
			if m.server.chainParams.ResetMinDifficulty &&
				rErr.ErrorCode == blockchain.ErrHighHash {
				minrLog.Debugf("Block submitted via CPU miner rejected "+
					"because of ResetMinDifficulty time sync failure: %v",
					err)
				return false
			} else {
				// Other rule errors should be reported.
				minrLog.Errorf("Block submitted via CPU miner rejected: %v", err)
				return false
			}
		}

	}
	if isOrphan {
		minrLog.Errorf("Block submitted via CPU miner is an orphan building "+
			"on parent %v", block.MsgBlock().Header.PrevBlock)
		return false
	}

	// The block was accepted.
	coinbaseTxOuts := block.MsgBlock().Transactions[0].TxOut
	coinbaseTxGenerated := int64(0)
	for _, out := range coinbaseTxOuts {
		coinbaseTxGenerated += out.Value
	}
	minrLog.Infof("Block submitted via CPU miner accepted (hash %s, "+
		"height %v, amount %v)",
		block.Sha(),
		block.Height(),
		dcrutil.Amount(coinbaseTxGenerated))
	return true
}
Esempio n. 24
0
func makeTxSummary(dbtx walletdb.ReadTx, w *Wallet, details *wtxmgr.TxDetails) TransactionSummary {
	serializedTx := details.SerializedTx
	if serializedTx == nil {
		var buf bytes.Buffer
		err := details.MsgTx.Serialize(&buf)
		if err != nil {
			log.Errorf("Transaction serialization: %v", err)
		}
		serializedTx = buf.Bytes()
	}
	var fee dcrutil.Amount
	if len(details.Debits) == len(details.MsgTx.TxIn) {
		for _, deb := range details.Debits {
			fee += deb.Amount
		}
		for _, txOut := range details.MsgTx.TxOut {
			fee -= dcrutil.Amount(txOut.Value)
		}
	}
	var inputs []TransactionSummaryInput
	if len(details.Debits) != 0 {
		inputs = make([]TransactionSummaryInput, len(details.Debits))
		for i, d := range details.Debits {
			inputs[i] = TransactionSummaryInput{
				Index:           d.Index,
				PreviousAccount: lookupInputAccount(dbtx, w, details, d),
				PreviousAmount:  d.Amount,
			}
		}
	}
	outputs := make([]TransactionSummaryOutput, 0, len(details.MsgTx.TxOut))
	for i := range details.MsgTx.TxOut {
		credIndex := len(outputs)
		mine := len(details.Credits) > credIndex && details.Credits[credIndex].Index == uint32(i)
		if !mine {
			continue
		}
		acct, internal := lookupOutputChain(dbtx, w, details, details.Credits[credIndex])
		output := TransactionSummaryOutput{
			Index:    uint32(i),
			Account:  acct,
			Internal: internal,
		}
		outputs = append(outputs, output)
	}
	return TransactionSummary{
		Hash:        &details.Hash,
		Transaction: serializedTx,
		MyInputs:    inputs,
		MyOutputs:   outputs,
		Fee:         fee,
		Timestamp:   details.Received.Unix(),
	}
}
Esempio n. 25
0
// TestRollBackLastOutputInsufficientOutputs checks that
// rollBackLastOutput returns an error if there are less than two
// outputs in the transaction.
func TestRollBackLastOutputInsufficientOutputs(t *testing.T) {
	tx := newWithdrawalTx(defaultTxOptions)
	_, _, err := tx.rollBackLastOutput()
	TstCheckError(t, "", err, ErrPreconditionNotMet)

	output := &WithdrawalOutput{request: TstNewOutputRequest(
		t, 1, "34eVkREKgvvGASZW7hkgE2uNc1yycntMK6", dcrutil.Amount(3), &chaincfg.MainNetParams)}
	tx.addOutput(output.request)
	_, _, err = tx.rollBackLastOutput()
	TstCheckError(t, "", err, ErrPreconditionNotMet)
}
Esempio n. 26
0
// StakePoolTicketFee determines the stake pool ticket fee for a given ticket
// from the passed percentage. Pool fee as a percentage is truncated from 0.01%
// to 100.00%. This all must be done with integers, so bear with the big.Int
// usage below.
//
// See the included doc.go of this package for more information about the
// calculation of this fee.
func StakePoolTicketFee(stakeDiff dcrutil.Amount, relayFee dcrutil.Amount,
	height int32, poolFee float64, params *chaincfg.Params) dcrutil.Amount {
	// Shift the decimal two places, e.g. 1.00%
	// to 100. This assumes that the proportion
	// is already multiplied by 100 to give a
	// percentage, thus making the entirety
	// be a multiplication by 10000.
	poolFeeAbs := math.Floor(poolFee * 100.0)
	poolFeeInt := int64(poolFeeAbs)

	// Subsidy is fetched from the blockchain package, then
	// pushed forward a number of adjustment periods for
	// compensation in gradual subsidy decay. Recall that
	// the average time to claiming 50% of the tickets as
	// votes is the approximately the same as the ticket
	// pool size (params.TicketPoolSize), so take the
	// ceiling of the ticket pool size divided by the
	// reduction interval.
	adjs := int(math.Ceil(float64(params.TicketPoolSize) /
		float64(params.ReductionInterval)))
	initSubsidyCacheOnce.Do(func() {
		subsidyCache = blockchain.NewSubsidyCache(int64(height), params)
	})
	subsidy := blockchain.CalcStakeVoteSubsidy(subsidyCache, int64(height),
		params)
	for i := 0; i < adjs; i++ {
		subsidy *= 100
		subsidy /= 101
	}

	// The numerator is (p*10000*s*(v+z)) << 64.
	shift := uint(64)
	s := new(big.Int).SetInt64(subsidy)
	v := new(big.Int).SetInt64(int64(stakeDiff))
	z := new(big.Int).SetInt64(int64(relayFee))
	num := new(big.Int).SetInt64(poolFeeInt)
	num.Mul(num, s)
	vPlusZ := new(big.Int).Add(v, z)
	num.Mul(num, vPlusZ)
	num.Lsh(num, shift)

	// The denominator is 10000*(s+v).
	// The extra 10000 above cancels out.
	den := new(big.Int).Set(s)
	den.Add(den, v)
	den.Mul(den, new(big.Int).SetInt64(10000))

	// Divide and shift back.
	num.Div(num, den)
	num.Rsh(num, shift)

	return dcrutil.Amount(num.Int64())
}
Esempio n. 27
0
// FeeForSerializeSize calculates the required fee for a transaction of some
// arbitrary size given a mempool's relay fee policy.
func FeeForSerializeSize(relayFeePerKb dcrutil.Amount, txSerializeSize int) dcrutil.Amount {
	fee := relayFeePerKb * dcrutil.Amount(txSerializeSize) / 1000

	if fee == 0 && relayFeePerKb > 0 {
		fee = relayFeePerKb
	}

	if fee < 0 || fee > dcrutil.MaxAmount {
		fee = dcrutil.MaxAmount
	}

	return fee
}
Esempio n. 28
0
func TestTxFeeEstimationForSmallTx(t *testing.T) {
	tx := newWithdrawalTx(defaultTxOptions)

	// A tx that is smaller than 1000 bytes in size should have a fee of 10000
	// atomss.
	tx.calculateSize = func() int { return 999 }
	fee := tx.calculateFee()

	wantFee := dcrutil.Amount(1e3)
	if fee != wantFee {
		t.Fatalf("Unexpected tx fee; got %v, want %v", fee, wantFee)
	}
}
Esempio n. 29
0
func TestTxFeeEstimationForLargeTx(t *testing.T) {
	tx := newWithdrawalTx(defaultTxOptions)

	// A tx that is larger than 1000 bytes in size should have a fee of 1e3
	// atoms plus 1e3 for every 1000 bytes.
	tx.calculateSize = func() int { return 3000 }
	fee := tx.calculateFee()

	wantFee := dcrutil.Amount(4e3)
	if fee != wantFee {
		t.Fatalf("Unexpected tx fee; got %v, want %v", fee, wantFee)
	}
}
Esempio n. 30
0
// IsDustOutput determines whether a transaction output is considered dust.
// Transactions with dust outputs are not standard and are rejected by mempools
// with default policies.
func IsDustOutput(output *wire.TxOut, relayFeePerKb dcrutil.Amount) bool {
	// Unspendable outputs which solely carry data are not checked for dust.
	if txscript.GetScriptClass(output.Version, output.PkScript) == txscript.NullDataTy {
		return false
	}

	// All other unspendable outputs are considered dust.
	if txscript.IsUnspendable(output.Value, output.PkScript) {
		return true
	}

	return IsDustAmount(dcrutil.Amount(output.Value), len(output.PkScript),
		relayFeePerKb)
}