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) } }
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)) } }
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) } }
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)) } }
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 }
// 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)) }
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 }
// 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()]) } } }
// 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 }
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) }
// 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 }
// 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 }
// 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 }
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") } }
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, } }
// 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) } }
// 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 }
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) } } }
// 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 }
// 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) }
// 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 }
// 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 } }
// 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 }
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(), } }
// 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) }
// 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()) }
// 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 }
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) } }
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) } }
// 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) }