Beispiel #1
0
// minimumFee calculates the minimum fee required for a transaction.
// If allowFree is true, a fee may be zero so long as the entire
// transaction has a serialized length less than 1 kilobyte
// and none of the outputs contain a value less than 1 bitcent.
// Otherwise, the fee will be calculated using TxFeeIncrement,
// incrementing the fee for each kilobyte of transaction.
func minimumFee(tx *btcwire.MsgTx, allowFree bool) btcutil.Amount {
	txLen := tx.SerializeSize()
	TxFeeIncrement.Lock()
	incr := TxFeeIncrement.i
	TxFeeIncrement.Unlock()
	fee := btcutil.Amount(int64(1+txLen/1000) * int64(incr))

	if allowFree && txLen < 1000 {
		fee = 0
	}

	if fee < incr {
		for _, txOut := range tx.TxOut {
			if txOut.Value < btcutil.SatoshiPerBitcent {
				return incr
			}
		}
	}

	max := btcutil.Amount(btcutil.MaxSatoshi)
	if fee < 0 || fee > max {
		fee = max
	}

	return fee
}
Beispiel #2
0
// OutputAmount returns the total amount of all outputs for a transaction.
func (t *TxRecord) OutputAmount(ignoreChange bool) btcutil.Amount {
	a := btcutil.Amount(0)
	for i, txOut := range t.Tx().MsgTx().TxOut {
		if ignoreChange {
			switch cs := t.credits; {
			case i < len(cs) && cs[i] != nil && cs[i].change:
				continue
			}
		}
		a += btcutil.Amount(txOut.Value)
	}
	return a
}
Beispiel #3
0
func CreateRawTx2(outputs []output, amount, value int64, toAddr, changeAddr string) (rawtx string, err error) {
	var inputs []btcjson.TransactionInput
	var rawInputs []btcjson.RawTxInput
	var amounts = make(map[btcutil.Address]btcutil.Amount)
	var privKeys []string

	for _, op := range outputs {
		inputs = append(inputs, btcjson.TransactionInput{Txid: op.TxHash, Vout: op.TxN})
		rawInputs = append(rawInputs, btcjson.RawTxInput{
			Txid:         op.TxHash,
			Vout:         op.TxN,
			ScriptPubKey: op.Script,
		})
		privKeys = append(privKeys, op.PrivKey)
	}

	addr, err := btcutil.DecodeAddress(toAddr, &btcnet.MainNetParams)
	if err != nil {
		return
	}
	amounts[addr] = btcutil.Amount(value)
	if amount > value {
		addr, err = btcutil.DecodeAddress(changeAddr, &btcnet.MainNetParams)
		if err != nil {
			return
		}
		amounts[addr] = btcutil.Amount(amount - value)
	}
	client, err := btcRpcClient()
	if err != nil {
		return
	}
	txMsg, err := client.CreateRawTransaction(inputs, amounts)
	if err != nil {
		return
	}

	txMsg, complete, err := client.SignRawTransaction3(txMsg, rawInputs, privKeys)
	if err != nil {
		return
	}
	if !complete {
		return "", errors.New("not complete")
	}

	buffer := &bytes.Buffer{}
	if err = txMsg.BtcEncode(buffer, 1); err != nil {
		return
	}
	return hex.EncodeToString(buffer.Bytes()), nil
}
Beispiel #4
0
// markOutputsSpent marks each previous credit spent by t as spent.  The total
// input of all spent previous outputs is returned.
func (s *Store) markOutputsSpent(spent []*Credit, t *TxRecord) (btcutil.Amount, error) {
	var a btcutil.Amount
	for _, prev := range spent {
		switch prev.BlockHeight {
		case -1: // unconfirmed
			op := prev.OutPoint()
			s.unconfirmed.spentUnconfirmed[*op] = t.txRecord

		default:
			b, err := s.lookupBlock(prev.BlockHeight)
			if err != nil {
				return 0, err
			}
			r, _, err := b.lookupTxRecord(prev.BlockIndex)
			if err != nil {
				return 0, err
			}

			// Update spent info.  If this transaction (and possibly
			// block) no longer contains any unspent transactions,
			// remove from bookkeeping maps.
			credit := prev.txRecord.credits[prev.OutputIndex]
			if credit.spentBy != nil {
				if *credit.spentBy == t.BlockTxKey {
					continue
				}
				return 0, ErrInconsistentStore
			}
			credit.spentBy = &t.BlockTxKey
			if !r.hasUnspents() {
				delete(b.unspent, prev.BlockIndex)
				if len(b.unspent) == 0 {
					delete(s.unspent, b.Height)
				}
			}
			if t.BlockHeight == -1 { // unconfirmed
				op := prev.OutPoint()
				key := prev.outputKey()
				s.unconfirmed.spentBlockOutPointKeys[*op] = *key
				s.unconfirmed.spentBlockOutPoints[*key] = t.txRecord
			}

			// Increment total debited amount.
			v := r.Tx().MsgTx().TxOut[prev.OutputIndex].Value
			a += btcutil.Amount(v)
		}
	}

	// If t refers to a mined transaction, update its block's amount deltas
	// by the total debited amount.
	if t.BlockHeight != -1 {
		b, err := s.lookupBlock(t.BlockHeight)
		if err != nil {
			return 0, err
		}
		b.amountDeltas.Spendable -= a
	}

	return a, nil
}
Beispiel #5
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 btcutil.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 := btcjson.JSONToAmount(fbal)
	if err != nil {
		return "", 0, false, err
	}

	return account, btcutil.Amount(bal), confirmed, nil
}
Beispiel #6
0
// parseTxAcceptedNtfnParams parses out the transaction hash and total amount
// from the parameters of a txaccepted notification.
func parseTxAcceptedNtfnParams(params []json.RawMessage) (*btcwire.ShaHash,
	btcutil.Amount, error) {

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

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

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

	// Decode string encoding of transaction sha.
	txSha, err := btcwire.NewShaHashFromStr(txShaStr)
	if err != nil {
		return nil, 0, err
	}

	return txSha, btcutil.Amount(amt), nil
}
Beispiel #7
0
// AddCredit marks the transaction record as containing a transaction output
// spendable by wallet.  The output is added unspent, and is marked spent
// when a new transaction spending the output is inserted into the store.
func (t *TxRecord) AddCredit(index uint32, change bool) (*Credit, error) {
	if len(t.tx.MsgTx().TxOut) <= int(index) {
		return nil, errors.New("transaction output does not exist")
	}

	c := &credit{change: change}
	if err := t.txRecord.setCredit(c, index, t.tx); err != nil {
		if err == ErrDuplicateInsert {
			return &Credit{t, index}, nil
		}
		return nil, err
	}

	switch t.BlockHeight {
	case -1: // unconfirmed
	default:
		b, err := t.s.lookupBlock(t.BlockHeight)
		if err != nil {
			return nil, err
		}
		_, txsIndex, err := b.lookupTxRecord(t.Tx().Index())
		if err != nil {
			return nil, err
		}

		// New outputs are added unspent.
		t.s.unspent[t.BlockTxKey.BlockHeight] = struct{}{}
		b.unspent[t.Tx().Index()] = txsIndex
		switch a := t.tx.MsgTx().TxOut[index].Value; t.tx.Index() {
		case 0: // Coinbase
			b.amountDeltas.Reward += btcutil.Amount(a)
		default:
			b.amountDeltas.Spendable += btcutil.Amount(a)
		}
	}

	return &Credit{t, index}, nil
}
Beispiel #8
0
// ToJSON returns a slice of objects that may be marshaled as a JSON array
// of JSON objects for a listtransactions RPC reply.
func (c *Credit) ToJSON(account string, chainHeight int32,
	net btcwire.BitcoinNet) (btcjson.ListTransactionsResult, error) {

	msgTx := c.Tx().MsgTx()
	txout := msgTx.TxOut[c.OutputIndex]

	var address string
	_, addrs, _, _ := btcscript.ExtractPkScriptAddrs(txout.PkScript, net)
	if len(addrs) == 1 {
		address = addrs[0].EncodeAddress()
	}

	var category string
	switch {
	case c.IsCoinbase():
		if c.Confirmed(btcchain.CoinbaseMaturity, chainHeight) {
			category = "generate"
		} else {
			category = "immature"
		}
	default:
		category = "receive"
	}

	result := btcjson.ListTransactionsResult{
		Account:         account,
		Category:        category,
		Address:         address,
		Amount:          btcutil.Amount(txout.Value).ToUnit(btcutil.AmountBTC),
		TxID:            c.Tx().Sha().String(),
		Time:            c.received.Unix(),
		TimeReceived:    c.received.Unix(),
		WalletConflicts: []string{},
	}
	if c.BlockHeight != -1 {
		b, err := c.s.lookupBlock(c.BlockHeight)
		if err != nil {
			return btcjson.ListTransactionsResult{}, err
		}

		result.BlockHash = b.Hash.String()
		result.BlockIndex = int64(c.Tx().Index())
		result.BlockTime = b.Time.Unix()
		result.Confirmations = int64(c.Confirmations(chainHeight))
	}

	return result, nil
}
Beispiel #9
0
func TestFindingSpentCredits(t *testing.T) {
	s := New()

	// Insert transaction and credit which will be spent.
	r, err := s.InsertTx(TstRecvTx, TstRecvTxBlockDetails)
	if err != nil {
		t.Fatal(err)
	}
	_, err = r.AddCredit(0, false)
	if err != nil {
		t.Fatal(err)
	}

	// Insert confirmed transaction which spends the above credit.
	TstSpendingTx.SetIndex(TstSignedTxIndex)
	r2, err := s.InsertTx(TstSpendingTx, TstSignedTxBlockDetails)
	if err != nil {
		t.Fatal(err)
	}
	_, err = r2.AddCredit(0, false)
	if err != nil {
		t.Fatal(err)
	}
	_, err = r2.AddDebits(nil)
	if err != nil {
		t.Fatal(err)
	}

	bal, err := s.Balance(1, TstSignedTxBlockDetails.Height)
	if err != nil {
		t.Fatal(err)
	}
	if bal != btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value) {
		t.Fatal("bad balance")
	}
	unspents, err := s.UnspentOutputs()
	if err != nil {
		t.Fatal(err)
	}
	op := btcwire.NewOutPoint(TstSpendingTx.Sha(), 0)
	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")
	}
}
Beispiel #10
0
// submitBlock submits the passed block to network after ensuring it passes all
// of the consensus validation rules.
func (m *CPUMiner) submitBlock(block *btcutil.Block) bool {
	m.submitBlockLock.Lock()
	defer m.submitBlockLock.Unlock()

	// Ensure the block is not stale since a new block could have shown up
	// while the solution was being found.  Typically that condition is
	// detected and all work on the stale block is halted to start work on
	// a new block, but the check only happens periodically, so it is
	// possible a block was found and submitted in between.
	latestHash, _ := m.server.blockManager.chainState.Best()
	msgBlock := block.MsgBlock()
	if !msgBlock.Header.PrevBlock.IsEqual(latestHash) {
		minrLog.Debugf("Block submitted via CPU miner with previous "+
			"block %s is stale", msgBlock.Header.PrevBlock)
		return false
	}

	// 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)
	if err != nil {
		// Anything other than a rule violation is an unexpected error,
		// so log that error as an internal error.
		if _, ok := err.(btcchain.RuleError); !ok {
			minrLog.Errorf("Unexpected error while processing "+
				"block submitted via CPU miner: %v", err)
			return false
		}

		minrLog.Debugf("Block submitted via CPU miner rejected: %v", err)
		return false
	}
	if isOrphan {
		minrLog.Debugf("Block submitted via CPU miner is an orphan")
		return false
	}

	// The block was accepted.
	blockSha, _ := block.Sha()
	coinbaseTx := block.MsgBlock().Transactions[0].TxOut[0]
	minrLog.Infof("Block submitted via CPU miner accepted (hash %s, "+
		"amount %v)", blockSha, btcutil.Amount(coinbaseTx.Value))
	return true
}
Beispiel #11
0
// ToJSON returns a slice of objects that may be marshaled as a JSON array
// of JSON objects for a listtransactions RPC reply.
func (d *Debits) ToJSON(account string, chainHeight int32,
	net btcwire.BitcoinNet) ([]btcjson.ListTransactionsResult, error) {

	msgTx := d.Tx().MsgTx()
	reply := make([]btcjson.ListTransactionsResult, 0, len(msgTx.TxOut))

	for _, txOut := range msgTx.TxOut {
		address := ""
		_, addrs, _, _ := btcscript.ExtractPkScriptAddrs(txOut.PkScript, net)
		if len(addrs) == 1 {
			address = addrs[0].EncodeAddress()
		}

		result := btcjson.ListTransactionsResult{
			Account:         account,
			Address:         address,
			Category:        "send",
			Amount:          btcutil.Amount(-txOut.Value).ToUnit(btcutil.AmountBTC),
			Fee:             d.Fee().ToUnit(btcutil.AmountBTC),
			TxID:            d.Tx().Sha().String(),
			Time:            d.txRecord.received.Unix(),
			TimeReceived:    d.txRecord.received.Unix(),
			WalletConflicts: []string{},
		}
		if d.BlockHeight != -1 {
			b, err := d.s.lookupBlock(d.BlockHeight)
			if err != nil {
				return nil, err
			}

			result.BlockHash = b.Hash.String()
			result.BlockIndex = int64(d.Tx().Index())
			result.BlockTime = b.Time.Unix()
			result.Confirmations = int64(d.Confirmations(chainHeight))
		}
		reply = append(reply, result)
	}

	return reply, nil
}
Beispiel #12
0
// AddCredit marks the transaction record as containing a transaction output
// spendable by wallet.  The output is added unspent, and is marked spent
// when a new transaction spending the output is inserted into the store.
func (t *TxRecord) AddCredit(index uint32, change bool) (Credit, error) {
	if len(t.tx.MsgTx().TxOut) <= int(index) {
		return Credit{}, errors.New("transaction output does not exist")
	}

	if err := t.txRecord.setCredit(index, change, t.tx); err != nil {
		if err == ErrDuplicateInsert {
			return Credit{t, index}, nil
		}
		return Credit{}, err
	}

	txOutAmt := btcutil.Amount(t.tx.MsgTx().TxOut[index].Value)
	log.Debugf("Marking transaction %v output %d (%v) spendable",
		t.tx.Sha(), index, txOutAmt)

	switch t.BlockHeight {
	case -1: // unconfirmed
	default:
		b, err := t.s.lookupBlock(t.BlockHeight)
		if err != nil {
			return Credit{}, err
		}

		// New outputs are added unspent.
		op := btcwire.OutPoint{Hash: *t.tx.Sha(), Index: index}
		t.s.unspent[op] = t.BlockTxKey
		switch t.tx.Index() {
		case 0: // Coinbase
			b.amountDeltas.Reward += txOutAmt
		default:
			b.amountDeltas.Spendable += txOutAmt
		}
	}

	return Credit{t, index}, nil
}
Beispiel #13
0
// ToJSON returns a slice of objects that may be marshaled as a JSON array
// of JSON objects for a listtransactions RPC reply.
func (c *Credit) ToJSON(account string, chainHeight int32,
	net *btcnet.Params) (btcjson.ListTransactionsResult, error) {

	msgTx := c.Tx().MsgTx()
	txout := msgTx.TxOut[c.OutputIndex]

	var address string
	_, addrs, _, _ := btcscript.ExtractPkScriptAddrs(txout.PkScript, net)
	if len(addrs) == 1 {
		address = addrs[0].EncodeAddress()
	}

	result := btcjson.ListTransactionsResult{
		Account:         account,
		Category:        c.Category(chainHeight).String(),
		Address:         address,
		Amount:          btcutil.Amount(txout.Value).ToUnit(btcutil.AmountBTC),
		TxID:            c.Tx().Sha().String(),
		Time:            c.received.Unix(),
		TimeReceived:    c.received.Unix(),
		WalletConflicts: []string{},
	}
	if c.BlockHeight != -1 {
		b, err := c.s.lookupBlock(c.BlockHeight)
		if err != nil {
			return btcjson.ListTransactionsResult{}, err
		}

		result.BlockHash = b.Hash.String()
		result.BlockIndex = int64(c.Tx().Index())
		result.BlockTime = b.Time.Unix()
		result.Confirmations = int64(c.Confirmations(chainHeight))
	}

	return result, nil
}
Beispiel #14
0
// Value returns the value of the Coin
func (c *SimpleCoin) Value() btcutil.Amount {
	return btcutil.Amount(c.txOut().Value)
}
Beispiel #15
0
// txToPairs creates a raw transaction sending the amounts for each
// address/amount pair and fee to each address and the miner.  minconf
// specifies the minimum number of confirmations required before an
// unspent output is eligible for spending. Leftover input funds not sent
// to addr or as a fee for the miner are sent to a newly generated
// address. If change is needed to return funds back to an owned
// address, changeUtxo will point to a unconfirmed (height = -1, zeroed
// block hash) Utxo.  ErrInsufficientFunds is returned if there are not
// enough eligible unspent outputs to create the transaction.
func (a *Account) txToPairs(pairs map[string]btcutil.Amount,
	minconf int) (*CreatedTx, error) {

	// Wallet must be unlocked to compose transaction.
	if a.IsLocked() {
		return nil, wallet.ErrWalletLocked
	}

	// Create a new transaction which will include all input scripts.
	msgtx := btcwire.NewMsgTx()

	// Calculate minimum amount needed for inputs.
	var amt btcutil.Amount
	for _, v := range pairs {
		// Error out if any amount is negative.
		if v <= 0 {
			return nil, ErrNonPositiveAmount
		}
		amt += v
	}

	// Add outputs to new tx.
	for addrStr, amt := range pairs {
		addr, err := btcutil.DecodeAddress(addrStr, cfg.Net())
		if err != nil {
			return nil, fmt.Errorf("cannot decode address: %s", err)
		}

		// Add output to spend amt to addr.
		pkScript, err := btcscript.PayToAddrScript(addr)
		if err != nil {
			return nil, fmt.Errorf("cannot create txout script: %s", err)
		}
		txout := btcwire.NewTxOut(int64(amt), pkScript)
		msgtx.AddTxOut(txout)
	}

	// Get current block's height and hash.
	bs, err := GetCurBlock()
	if err != nil {
		return nil, err
	}

	// Make a copy of msgtx before any inputs are added.  This will be
	// used as a starting point when trying a fee and starting over with
	// a higher fee if not enough was originally chosen.
	txNoInputs := msgtx.Copy()

	unspent, err := a.TxStore.UnspentOutputs()
	if err != nil {
		return nil, err
	}

	var selectedInputs []*txstore.Credit
	// These are nil/zeroed until a change address is needed, and reused
	// again in case a change utxo has already been chosen.
	var changeAddr btcutil.Address

	// Get the number of satoshis to increment fee by when searching for
	// the minimum tx fee needed.
	fee := btcutil.Amount(0)
	for {
		msgtx = txNoInputs.Copy()

		// Select unspent outputs to be used in transaction based on the amount
		// neededing to sent, and the current fee estimation.
		inputs, btcin, err := selectInputs(unspent, amt+fee, minconf)
		if err != nil {
			return nil, err
		}

		// Check if there are leftover unspent outputs, and return coins back to
		// a new address we own.
		//
		// TODO: change needs to be inserted into a random txout index, or else
		// this is a privacy risk.
		change := btcin - amt - fee
		if change > 0 {
			// Get a new change address if one has not already been found.
			if changeAddr == nil {
				changeAddr, err = a.ChangeAddress(&bs, cfg.KeypoolSize)
				if err != nil {
					return nil, fmt.Errorf("failed to get next address: %s", err)
				}

				// Mark change address as belonging to this account.
				AcctMgr.MarkAddressForAccount(changeAddr, a)
			}

			// Spend change.
			pkScript, err := btcscript.PayToAddrScript(changeAddr)
			if err != nil {
				return nil, fmt.Errorf("cannot create txout script: %s", err)
			}
			msgtx.AddTxOut(btcwire.NewTxOut(int64(change), pkScript))
		}

		// Selected unspent outputs become new transaction's inputs.
		for _, ip := range inputs {
			msgtx.AddTxIn(btcwire.NewTxIn(ip.OutPoint(), nil))
		}
		for i, input := range inputs {
			_, addrs, _, _ := input.Addresses(cfg.Net())
			if len(addrs) != 1 {
				continue
			}
			apkh, ok := addrs[0].(*btcutil.AddressPubKeyHash)
			if !ok {
				continue // don't handle inputs to this yes
			}

			ai, err := a.Address(apkh)
			if err != nil {
				return nil, fmt.Errorf("cannot get address info: %v", err)
			}

			pka := ai.(wallet.PubKeyAddress)

			privkey, err := pka.PrivKey()
			if err == wallet.ErrWalletLocked {
				return nil, wallet.ErrWalletLocked
			} else if err != nil {
				return nil, fmt.Errorf("cannot get address key: %v", err)
			}

			sigscript, err := btcscript.SignatureScript(msgtx, i,
				input.TxOut().PkScript, btcscript.SigHashAll, privkey,
				ai.Compressed())
			if err != nil {
				return nil, fmt.Errorf("cannot create sigscript: %s", err)
			}
			msgtx.TxIn[i].SignatureScript = sigscript
		}

		noFeeAllowed := false
		if !cfg.DisallowFree {
			noFeeAllowed = allowFree(bs.Height, inputs, msgtx.SerializeSize())
		}
		if minFee := minimumFee(msgtx, noFeeAllowed); fee < minFee {
			fee = minFee
		} else {
			selectedInputs = inputs
			break
		}
	}

	// Validate msgtx before returning the raw transaction.
	flags := btcscript.ScriptCanonicalSignatures
	bip16 := time.Now().After(btcscript.Bip16Activation)
	if bip16 {
		flags |= btcscript.ScriptBip16
	}
	for i, txin := range msgtx.TxIn {
		engine, err := btcscript.NewScript(txin.SignatureScript,
			selectedInputs[i].TxOut().PkScript, i, msgtx, flags)
		if err != nil {
			return nil, fmt.Errorf("cannot create script engine: %s", err)
		}
		if err = engine.Execute(); err != nil {
			return nil, fmt.Errorf("cannot validate transaction: %s", err)
		}
	}

	buf := bytes.NewBuffer(nil)
	buf.Grow(msgtx.SerializeSize())
	msgtx.BtcEncode(buf, btcwire.ProtocolVersion)
	info := &CreatedTx{
		tx:         btcutil.NewTx(msgtx),
		inputs:     selectedInputs,
		changeAddr: changeAddr,
	}
	return info, nil
}
Beispiel #16
0
// handleNotification examines the passed notification type, performs
// conversions to get the raw notification types into higher level types and
// delivers the notification to the appropriate On<X> handler registered with
// the client.
func (c *Client) handleNotification(cmd btcjson.Cmd) {
	// Ignore the notification if the client is not interested in any
	// notifications.
	if c.ntfnHandlers == nil {
		return
	}

	switch ntfn := cmd.(type) {
	// OnBlockConnected
	case *btcws.BlockConnectedNtfn:
		// Ignore the notification is the client is not interested in
		// it.
		if c.ntfnHandlers.OnBlockConnected == nil {
			return
		}

		hash, err := btcwire.NewShaHashFromStr(ntfn.Hash)
		if err != nil {
			log.Warnf("Received block connected notification with "+
				"invalid hash string: %q", ntfn.Hash)
			return
		}

		c.ntfnHandlers.OnBlockConnected(hash, ntfn.Height)

	// OnBlockDisconnected
	case *btcws.BlockDisconnectedNtfn:
		// Ignore the notification is the client is not interested in
		// it.
		if c.ntfnHandlers.OnBlockDisconnected == nil {
			return
		}

		hash, err := btcwire.NewShaHashFromStr(ntfn.Hash)
		if err != nil {
			log.Warnf("Received block disconnected notification "+
				"with invalid hash string: %q", ntfn.Hash)
			return
		}

		c.ntfnHandlers.OnBlockDisconnected(hash, ntfn.Height)

	// OnRecvTx
	case *btcws.RecvTxNtfn:
		// Ignore the notification is the client is not interested in
		// it.
		if c.ntfnHandlers.OnRecvTx == nil {
			return
		}

		// Decode the serialized transaction hex to raw bytes.
		serializedTx, err := hex.DecodeString(ntfn.HexTx)
		if err != nil {
			log.Warnf("Received recvtx notification with invalid "+
				"transaction hex '%q': %v", ntfn.HexTx, err)
		}

		// Deserialize the transaction.
		var msgTx btcwire.MsgTx
		err = msgTx.Deserialize(bytes.NewReader(serializedTx))
		if err != nil {
			log.Warnf("Received recvtx notification with "+
				"transaction that failed to deserialize: %v",
				err)
		}

		c.ntfnHandlers.OnRecvTx(btcutil.NewTx(&msgTx), ntfn.Block)

	// OnRedeemingTx
	case *btcws.RedeemingTxNtfn:
		// Ignore the notification is the client is not interested in
		// it.
		if c.ntfnHandlers.OnRedeemingTx == nil {
			return
		}

		// Decode the serialized transaction hex to raw bytes.
		serializedTx, err := hex.DecodeString(ntfn.HexTx)
		if err != nil {
			log.Warnf("Received redeemingtx notification with "+
				"invalid transaction hex '%q': %v", ntfn.HexTx,
				err)
		}

		// Deserialize the transaction.
		var msgTx btcwire.MsgTx
		err = msgTx.Deserialize(bytes.NewReader(serializedTx))
		if err != nil {
			log.Warnf("Received redeemingtx notification with "+
				"transaction that failed to deserialize: %v",
				err)
		}

		c.ntfnHandlers.OnRedeemingTx(btcutil.NewTx(&msgTx), ntfn.Block)

	// OnRescanProgress
	case *btcws.RescanProgressNtfn:
		// Ignore the notification is the client is not interested in
		// it.
		if c.ntfnHandlers.OnRescanProgress == nil {
			return
		}

		c.ntfnHandlers.OnRescanProgress(ntfn.LastProcessed)

	// OnTxAccepted
	case *btcws.TxAcceptedNtfn:
		// Ignore the notification is the client is not interested in
		// it.
		if c.ntfnHandlers.OnTxAccepted == nil {
			return
		}

		hash, err := btcwire.NewShaHashFromStr(ntfn.TxID)
		if err != nil {
			log.Warnf("Received tx accepted notification with "+
				"invalid hash string: %q", ntfn.TxID)
			return
		}

		c.ntfnHandlers.OnTxAccepted(hash, btcutil.Amount(ntfn.Amount))

	// OnTxAcceptedVerbose
	case *btcws.TxAcceptedVerboseNtfn:
		// Ignore the notification is the client is not interested in
		// it.
		if c.ntfnHandlers.OnTxAcceptedVerbose == nil {
			return
		}

		c.ntfnHandlers.OnTxAcceptedVerbose(ntfn.RawTx)

	// OnBtcdConnected
	case *btcws.BtcdConnectedNtfn:
		// Ignore the notification is the client is not interested in
		// it.
		if c.ntfnHandlers.OnBtcdConnected == nil {
			return
		}

		c.ntfnHandlers.OnBtcdConnected(ntfn.Connected)

	// OnAccountBalance
	case *btcws.AccountBalanceNtfn:
		// Ignore the notification is the client is not interested in
		// it.
		if c.ntfnHandlers.OnAccountBalance == nil {
			return
		}

		balance, err := btcjson.JSONToAmount(ntfn.Balance)
		if err != nil {
			log.Warnf("Received account balance notification with "+
				"an amount that does not parse: %v",
				ntfn.Balance)
			return
		}

		c.ntfnHandlers.OnAccountBalance(ntfn.Account,
			btcutil.Amount(balance), ntfn.Confirmed)

	// OnWalletLockState
	case *btcws.WalletLockStateNtfn:
		// Ignore the notification is the client is not interested in
		// it.
		if c.ntfnHandlers.OnWalletLockState == nil {
			return
		}

		c.ntfnHandlers.OnWalletLockState(ntfn.Locked)

	// OnUnknownNotification
	default:
		if c.ntfnHandlers.OnUnknownNotification == nil {
			return
		}

		c.ntfnHandlers.OnUnknownNotification(ntfn)
	}
}
Beispiel #17
0
// txToPairs creates a raw transaction sending the amounts for each
// address/amount pair and fee to each address and the miner.  minconf
// specifies the minimum number of confirmations required before an
// unspent output is eligible for spending. Leftover input funds not sent
// to addr or as a fee for the miner are sent to a newly generated
// address. If change is needed to return funds back to an owned
// address, changeUtxo will point to a unconfirmed (height = -1, zeroed
// block hash) Utxo.  ErrInsufficientFunds is returned if there are not
// enough eligible unspent outputs to create the transaction.
func (a *Account) txToPairs(pairs map[string]btcutil.Amount,
	minconf int) (*CreatedTx, error) {

	// Wallet must be unlocked to compose transaction.
	if a.IsLocked() {
		return nil, wallet.ErrWalletLocked
	}

	// Create a new transaction which will include all input scripts.
	msgtx := btcwire.NewMsgTx()

	// Calculate minimum amount needed for inputs.
	var amt btcutil.Amount
	for _, v := range pairs {
		// Error out if any amount is negative.
		if v <= 0 {
			return nil, ErrNonPositiveAmount
		}
		amt += v
	}

	// Add outputs to new tx.
	for addrStr, amt := range pairs {
		addr, err := btcutil.DecodeAddress(addrStr, activeNet.Params)
		if err != nil {
			return nil, fmt.Errorf("cannot decode address: %s", err)
		}

		// Add output to spend amt to addr.
		pkScript, err := btcscript.PayToAddrScript(addr)
		if err != nil {
			return nil, fmt.Errorf("cannot create txout script: %s", err)
		}
		txout := btcwire.NewTxOut(int64(amt), pkScript)
		msgtx.AddTxOut(txout)
	}

	// Get current block's height and hash.
	bs, err := GetCurBlock()
	if err != nil {
		return nil, err
	}

	// Make a copy of msgtx before any inputs are added.  This will be
	// used as a starting point when trying a fee and starting over with
	// a higher fee if not enough was originally chosen.
	txNoInputs := msgtx.Copy()

	unspent, err := a.TxStore.UnspentOutputs()
	if err != nil {
		return nil, err
	}

	// Filter out unspendable outputs, that is, remove those that (at this
	// time) are not P2PKH outputs.  Other inputs must be manually included
	// in transactions and sent (for example, using createrawtransaction,
	// signrawtransaction, and sendrawtransaction).
	eligible := make([]txstore.Credit, 0, len(unspent))
	for i := range unspent {
		switch btcscript.GetScriptClass(unspent[i].TxOut().PkScript) {
		case btcscript.PubKeyHashTy:
			if !unspent[i].Confirmed(minconf, bs.Height) {
				continue
			}
			// Coinbase transactions must have have reached maturity
			// before their outputs may be spent.
			if unspent[i].IsCoinbase() {
				target := btcchain.CoinbaseMaturity
				if !unspent[i].Confirmed(target, bs.Height) {
					continue
				}
			}

			// Locked unspent outputs are skipped.
			if a.LockedOutpoint(*unspent[i].OutPoint()) {
				continue
			}

			eligible = append(eligible, unspent[i])
		}
	}

	// Sort eligible inputs, as selectInputs expects these to be sorted
	// by amount in reverse order.
	sort.Sort(sort.Reverse(ByAmount(eligible)))

	var selectedInputs []txstore.Credit
	// changeAddr is nil/zeroed until a change address is needed, and reused
	// again in case a change utxo has already been chosen.
	var changeAddr btcutil.Address

	// Get the number of satoshis to increment fee by when searching for
	// the minimum tx fee needed.
	fee := btcutil.Amount(0)
	for {
		msgtx = txNoInputs.Copy()

		// Select eligible outputs to be used in transaction based on the amount
		// neededing to sent, and the current fee estimation.
		inputs, btcin, err := selectInputs(eligible, amt, fee, minconf)
		if err != nil {
			return nil, err
		}

		// Check if there are leftover unspent outputs, and return coins back to
		// a new address we own.
		change := btcin - amt - fee
		if change > 0 {
			// Get a new change address if one has not already been found.
			if changeAddr == nil {
				changeAddr, err = a.ChangeAddress(&bs, cfg.KeypoolSize)
				if err != nil {
					return nil, fmt.Errorf("failed to get next address: %s", err)
				}

				// Mark change address as belonging to this account.
				AcctMgr.MarkAddressForAccount(changeAddr, a)
			}

			// Spend change.
			pkScript, err := btcscript.PayToAddrScript(changeAddr)
			if err != nil {
				return nil, fmt.Errorf("cannot create txout script: %s", err)
			}
			msgtx.AddTxOut(btcwire.NewTxOut(int64(change), pkScript))

			// Randomize index of the change output.
			rng := badrand.New(badrand.NewSource(time.Now().UnixNano()))
			r := rng.Int31n(int32(len(msgtx.TxOut))) // random index
			c := len(msgtx.TxOut) - 1                // change index
			msgtx.TxOut[r], msgtx.TxOut[c] = msgtx.TxOut[c], msgtx.TxOut[r]
		}

		// Selected unspent outputs become new transaction's inputs.
		for _, ip := range inputs {
			msgtx.AddTxIn(btcwire.NewTxIn(ip.OutPoint(), nil))
		}
		for i, input := range inputs {
			// Errors don't matter here, as we only consider the
			// case where len(addrs) == 1.
			_, addrs, _, _ := input.Addresses(activeNet.Params)
			if len(addrs) != 1 {
				continue
			}
			apkh, ok := addrs[0].(*btcutil.AddressPubKeyHash)
			if !ok {
				continue // don't handle inputs to this yes
			}

			ai, err := a.Address(apkh)
			if err != nil {
				return nil, fmt.Errorf("cannot get address info: %v", err)
			}

			pka := ai.(wallet.PubKeyAddress)

			privkey, err := pka.PrivKey()
			if err == wallet.ErrWalletLocked {
				return nil, wallet.ErrWalletLocked
			} else if err != nil {
				return nil, fmt.Errorf("cannot get address key: %v", err)
			}

			sigscript, err := btcscript.SignatureScript(msgtx, i,
				input.TxOut().PkScript, btcscript.SigHashAll, privkey,
				ai.Compressed())
			if err != nil {
				return nil, fmt.Errorf("cannot create sigscript: %s", err)
			}
			msgtx.TxIn[i].SignatureScript = sigscript
		}

		noFeeAllowed := false
		if !cfg.DisallowFree {
			noFeeAllowed = allowFree(bs.Height, inputs, msgtx.SerializeSize())
		}
		if minFee := minimumFee(msgtx, noFeeAllowed); fee < minFee {
			fee = minFee
		} else {
			selectedInputs = inputs
			break
		}
	}

	// Validate msgtx before returning the raw transaction.
	flags := btcscript.ScriptCanonicalSignatures
	bip16 := time.Now().After(btcscript.Bip16Activation)
	if bip16 {
		flags |= btcscript.ScriptBip16
	}
	for i, txin := range msgtx.TxIn {
		engine, err := btcscript.NewScript(txin.SignatureScript,
			selectedInputs[i].TxOut().PkScript, i, msgtx, flags)
		if err != nil {
			return nil, fmt.Errorf("cannot create script engine: %s", err)
		}
		if err = engine.Execute(); err != nil {
			return nil, fmt.Errorf("cannot validate transaction: %s", err)
		}
	}

	buf := bytes.Buffer{}
	buf.Grow(msgtx.SerializeSize())
	if err := msgtx.BtcEncode(&buf, btcwire.ProtocolVersion); err != nil {
		// Hitting OOM by growing or writing to a bytes.Buffer already
		// panics, and all returned errors are unexpected.
		panic(err)
	}
	info := &CreatedTx{
		tx:         btcutil.NewTx(msgtx),
		inputs:     selectedInputs,
		changeAddr: changeAddr,
	}
	return info, nil
}
Beispiel #18
0
	{minPrioritySelectors[3], []coinset.Coin{coins[1], coins[2]}, 10000000, []coinset.Coin{coins[1]}, nil},
	{minPrioritySelectors[4], connectedCoins, 1, nil, coinset.ErrCoinsNoSelectionAvailable},
	{minPrioritySelectors[5], connectedCoins, 20000000, []coinset.Coin{coins[1], coins[3]}, nil},
	{minPrioritySelectors[6], connectedCoins, 25000000, []coinset.Coin{coins[3], coins[0]}, nil},
}

func TestMinPrioritySelector(t *testing.T) {
	testCoinSelector(minPriorityTests, t)
}

var (
	// should be two outpoints, with 1st one having 0.035BTC value.
	testSimpleCoinNumConfs            = int64(1)
	testSimpleCoinTxHash              = "9b5965c86de51d5dc824e179a05cf232db78c80ae86ca9d7cb2a655b5e19c1e2"
	testSimpleCoinTxHex               = "0100000001a214a110f79e4abe073865ea5b3745c6e82c913bad44be70652804a5e4003b0a010000008c493046022100edd18a69664efa57264be207100c203e6cade1888cbb88a0ad748548256bb2f0022100f1027dc2e6c7f248d78af1dd90027b5b7d8ec563bb62aa85d4e74d6376f3868c0141048f3757b65ed301abd1b0e8942d1ab5b50594d3314cff0299f300c696376a0a9bf72e74710a8af7a5372d4af4bb519e2701a094ef48c8e48e3b65b28502452dceffffffff02e0673500000000001976a914686dd149a79b4a559d561fbc396d3e3c6628b98d88ace86ef102000000001976a914ac3f995655e81b875b38b64351d6f896ddbfc68588ac00000000"
	testSimpleCoinTxValue0            = btcutil.Amount(3500000)
	testSimpleCoinTxValueAge0         = int64(testSimpleCoinTxValue0) * testSimpleCoinNumConfs
	testSimpleCoinTxPkScript0Hex      = "76a914686dd149a79b4a559d561fbc396d3e3c6628b98d88ac"
	testSimpleCoinTxPkScript0Bytes, _ = hex.DecodeString(testSimpleCoinTxPkScript0Hex)
	testSimpleCoinTxBytes, _          = hex.DecodeString(testSimpleCoinTxHex)
	testSimpleCoinTx, _               = btcutil.NewTxFromBytes(testSimpleCoinTxBytes)
	testSimpleCoin                    = &coinset.SimpleCoin{
		Tx:         testSimpleCoinTx,
		TxIndex:    0,
		TxNumConfs: testSimpleCoinNumConfs,
	}
)

func TestSimpleCoin(t *testing.T) {
	if testSimpleCoin.Hash().String() != testSimpleCoinTxHash {
		t.Error("Different value for tx hash than expected")
Beispiel #19
0
func (t *txRecord) ReadFrom(r io.Reader) (int64, error) {
	var buf [8]byte
	uint64Bytes := buf[:8]
	uint32Bytes := buf[:4]
	singleByte := buf[:1]

	// Read transaction index (as a uint32).
	n, err := io.ReadFull(r, uint32Bytes)
	n64 := int64(n)
	if err != nil {
		return n64, err
	}
	txIndex := int(byteOrder.Uint32(uint32Bytes))

	// Deserialize transaction.
	msgTx := new(msgTx)
	tmpn64, err := msgTx.ReadFrom(r)
	n64 += tmpn64
	if err != nil {
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
		return n64, err
	}

	// Create and save the btcutil.Tx of the read MsgTx and set its index.
	tx := btcutil.NewTx((*btcwire.MsgTx)(msgTx))
	tx.SetIndex(txIndex)
	t.tx = tx

	// Read identifier for existance of debits.
	n, err = io.ReadFull(r, singleByte)
	n64 += int64(n)
	if err != nil {
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
		return n64, err
	}
	hasDebits, err := byteMarksValidPointer(singleByte[0])
	if err != nil {
		return n64, err
	}

	// If debits have been set, read them.  Otherwise, set to nil.
	if hasDebits {
		// Read debited amount (int64).
		n, err := io.ReadFull(r, uint64Bytes)
		n64 += int64(n)
		if err != nil {
			if err == io.EOF {
				err = io.ErrUnexpectedEOF
			}
			return n64, err
		}
		amount := btcutil.Amount(byteOrder.Uint64(uint64Bytes))

		// Read number of written outputs (as a uint32) this record
		// debits from.
		n, err = io.ReadFull(r, uint32Bytes)
		n64 += int64(n)
		if err != nil {
			if err == io.EOF {
				err = io.ErrUnexpectedEOF
			}
			return n64, err
		}
		spendsCount := byteOrder.Uint32(uint32Bytes)

		// For each expected output key, allocate and read the key,
		// appending the result to the spends slice.  This slice is
		// originally set to nil (*not* preallocated to spendsCount
		// size) to prevent accidentally allocating so much memory that
		// the process dies.
		var spends []*BlockOutputKey
		for i := uint32(0); i < spendsCount; i++ {
			k := &BlockOutputKey{}
			tmpn64, err := k.ReadFrom(r)
			n64 += tmpn64
			if err != nil {
				if err == io.EOF {
					err = io.ErrUnexpectedEOF
				}
				return n64, err
			}
			spends = append(spends, k)
		}

		t.debits = &debits{amount, spends}
	} else {
		t.debits = nil
	}

	// Read number of pointers (as a uint32) written to be read into the
	// credits slice (although some may be nil).
	n, err = io.ReadFull(r, uint32Bytes)
	n64 += int64(n)
	if err != nil {
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
		return n64, err
	}
	creditsCount := byteOrder.Uint32(uint32Bytes)

	// For each expected credits slice element, check whether the credit
	// exists or the pointer is nil.  If nil, append nil to credits and
	// continue with the next.  If non-nil, allocated and read the full
	// credit structure.  This slice is originally set to nil (*not*
	// preallocated to creditsCount size) to prevent accidentally allocating
	// so much memory that the process dies.
	var credits []*credit
	for i := uint32(0); i < creditsCount; i++ {
		// Read identifer for a valid pointer.
		n, err := io.ReadFull(r, singleByte)
		n64 += int64(n)
		if err != nil {
			if err == io.EOF {
				err = io.ErrUnexpectedEOF
			}
			return n64, err
		}
		validCredit, err := byteMarksValidPointer(singleByte[0])
		if err != nil {
			return n64, err
		}

		if !validCredit {
			credits = append(credits, nil)
		} else {
			// Read single byte that specifies whether this credit
			// was added as change.
			n, err = io.ReadFull(r, singleByte)
			n64 += int64(n)
			if err != nil {
				if err == io.EOF {
					err = io.ErrUnexpectedEOF
				}
				return n64, err
			}
			change, err := byteAsBool(singleByte[0])
			if err != nil {
				return n64, err
			}

			// Read single byte that specifies whether this credit
			// is locked.
			n, err = io.ReadFull(r, singleByte)
			n64 += int64(n)
			if err != nil {
				if err == io.EOF {
					err = io.ErrUnexpectedEOF
				}
				return n64, err
			}
			locked, err := byteAsBool(singleByte[0])
			if err != nil {
				return n64, err
			}

			// Read identifier for a valid pointer.
			n, err = io.ReadFull(r, singleByte)
			n64 += int64(n)
			if err != nil {
				if err == io.EOF {
					err = io.ErrUnexpectedEOF
				}
				return n64, err
			}
			validSpentBy, err := byteMarksValidPointer(singleByte[0])
			if err != nil {
				return n64, err
			}

			// If spentBy pointer is valid, allocate and read a
			// transaction lookup key.
			var spentBy *BlockTxKey
			if validSpentBy {
				spentBy = &BlockTxKey{}
				tmpn64, err := spentBy.ReadFrom(r)
				n64 += tmpn64
				if err != nil {
					if err == io.EOF {
						err = io.ErrUnexpectedEOF
					}
					return n64, err
				}
			}

			c := &credit{change, locked, spentBy}
			credits = append(credits, c)
		}

	}
	t.credits = credits

	// Read received unix time (int64).
	n, err = io.ReadFull(r, uint64Bytes)
	n64 += int64(n)
	if err != nil {
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
		return n64, err
	}
	received := int64(byteOrder.Uint64(uint64Bytes))
	t.received = time.Unix(received, 0)

	return n64, nil
}
Beispiel #20
0
func (b *blockTxCollection) ReadFrom(r io.Reader) (int64, error) {
	var buf [8]byte
	uint64Bytes := buf[:8]
	uint32Bytes := buf[:4]

	// Read block hash, unix time (int64), and height (int32).
	n, err := io.ReadFull(r, b.Hash[:])
	n64 := int64(n)
	if err != nil {
		return n64, err
	}
	n, err = io.ReadFull(r, uint64Bytes)
	n64 += int64(n)
	if err != nil {
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
		return n64, err
	}
	b.Time = time.Unix(int64(byteOrder.Uint64(uint64Bytes)), 0)
	n, err = io.ReadFull(r, uint32Bytes)
	n64 += int64(n)
	if err != nil {
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
		return n64, err
	}
	b.Height = int32(byteOrder.Uint32(uint32Bytes))

	// Read amount deltas as a result of transactions in this block.  This
	// is the net total spendable balance as a result of transaction debits
	// and credits, and the block reward (not immediately spendable) for
	// coinbase outputs.  Both are int64s.
	n, err = io.ReadFull(r, uint64Bytes)
	n64 += int64(n)
	if err != nil {
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
		return n64, err
	}
	b.amountDeltas.Spendable = btcutil.Amount(byteOrder.Uint64(uint64Bytes))
	n, err = io.ReadFull(r, uint64Bytes)
	n64 += int64(n)
	if err != nil {
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
		return n64, err
	}
	b.amountDeltas.Reward = btcutil.Amount(byteOrder.Uint64(uint64Bytes))

	// Read number of transaction records (as a uint32) followed by a read
	// for each expected record.
	n, err = io.ReadFull(r, uint32Bytes)
	n64 += int64(n)
	if err != nil {
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
		return n64, err
	}
	txCount := byteOrder.Uint32(uint32Bytes)
	// The txs slice is *not* preallocated to txcount size to prevent
	// accidentally allocating so much memory that the process dies.
	for i := uint32(0); i < txCount; i++ {
		t := &txRecord{}
		tmpn64, err := t.ReadFrom(r)
		n64 += tmpn64
		if err != nil {
			if err == io.EOF {
				err = io.ErrUnexpectedEOF
			}
			return n64, err
		}
		b.txs = append(b.txs, t)

		// Recreate txIndexes map.  For each transaction record, map the
		// block index of the underlying transaction to the slice index
		// of the record.
		b.txIndexes[t.tx.Index()] = i

		// Recreate unspent map.  For each credit of this transaction,
		// if any credit is unspent, mark it in unspent map.
		for _, c := range t.credits {
			if c == nil {
				continue
			}
			if c.spentBy == nil {
				b.unspent[t.tx.Index()] = i
				break
			}
		}
	}

	return n64, nil
}
Beispiel #21
0
func TestInsertsCreditsDebitsRollbacks(t *testing.T) {
	// Create a double spend of the received blockchain transaction.
	dupRecvTx, _ := btcutil.NewTxFromBytes(TstRecvSerializedTx)
	// Switch txout amount to 1 BTC.  Transaction store doesn't
	// validate txs, so this is fine for testing a double spend
	// removal.
	TstDupRecvAmount := int64(1e8)
	newDupMsgTx := dupRecvTx.MsgTx()
	newDupMsgTx.TxOut[0].Value = TstDupRecvAmount
	TstDoubleSpendTx := btcutil.NewTx(newDupMsgTx)

	// Create a "signed" (with invalid sigs) tx that spends output 0 of
	// the double spend.
	spendingTx := btcwire.NewMsgTx()
	spendingTxIn := btcwire.NewTxIn(btcwire.NewOutPoint(TstDoubleSpendTx.Sha(), 0), []byte{0, 1, 2, 3, 4})
	spendingTx.AddTxIn(spendingTxIn)
	spendingTxOut1 := btcwire.NewTxOut(1e7, []byte{5, 6, 7, 8, 9})
	spendingTxOut2 := btcwire.NewTxOut(9e7, []byte{10, 11, 12, 13, 14})
	spendingTx.AddTxOut(spendingTxOut1)
	spendingTx.AddTxOut(spendingTxOut2)
	TstSpendingTx := btcutil.NewTx(spendingTx)
	var _ = TstSpendingTx

	tests := []struct {
		name     string
		f        func(*Store) (*Store, error)
		bal, unc btcutil.Amount
		unspents map[btcwire.OutPoint]struct{}
		unmined  map[btcwire.ShaHash]struct{}
	}{
		{
			name: "new store",
			f: func(_ *Store) (*Store, error) {
				return New(), nil
			},
			bal:      0,
			unc:      0,
			unspents: map[btcwire.OutPoint]struct{}{},
			unmined:  map[btcwire.ShaHash]struct{}{},
		},
		{
			name: "txout insert",
			f: func(s *Store) (*Store, error) {
				r, err := s.InsertTx(TstRecvTx, nil)
				if err != nil {
					return nil, err
				}

				_, err = r.AddCredit(0, false)
				if err != nil {
					return nil, err
				}
				// Verify that we can create the JSON output without any
				// errors.
				_, err = r.ToJSON("", 100, &btcnet.MainNetParams)
				if err != nil {
					return nil, err
				}
				return s, nil
			},
			bal: 0,
			unc: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value),
			unspents: map[btcwire.OutPoint]struct{}{
				*btcwire.NewOutPoint(TstRecvTx.Sha(), 0): {},
			},
			unmined: map[btcwire.ShaHash]struct{}{},
		},
		{
			name: "insert duplicate unconfirmed",
			f: func(s *Store) (*Store, error) {
				r, err := s.InsertTx(TstRecvTx, nil)
				if err != nil {
					return nil, err
				}

				_, err = r.AddCredit(0, false)
				if err != nil {
					return nil, err
				}

				_, err = r.ToJSON("", 100, &btcnet.MainNetParams)
				if err != nil {
					return nil, err
				}
				return s, nil
			},
			bal: 0,
			unc: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value),
			unspents: map[btcwire.OutPoint]struct{}{
				*btcwire.NewOutPoint(TstRecvTx.Sha(), 0): {},
			},
			unmined: map[btcwire.ShaHash]struct{}{},
		},
		{
			name: "confirmed txout insert",
			f: func(s *Store) (*Store, error) {
				TstRecvTx.SetIndex(TstRecvIndex)
				r, err := s.InsertTx(TstRecvTx, TstRecvTxBlockDetails)
				if err != nil {
					return nil, err
				}

				_, err = r.AddCredit(0, false)
				if err != nil {
					return nil, err
				}

				_, err = r.ToJSON("", 100, &btcnet.MainNetParams)
				if err != nil {
					return nil, err
				}
				return s, nil
			},
			bal: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value),
			unc: 0,
			unspents: map[btcwire.OutPoint]struct{}{
				*btcwire.NewOutPoint(TstRecvTx.Sha(), 0): {},
			},
			unmined: map[btcwire.ShaHash]struct{}{},
		},
		{
			name: "insert duplicate confirmed",
			f: func(s *Store) (*Store, error) {
				TstRecvTx.SetIndex(TstRecvIndex)
				r, err := s.InsertTx(TstRecvTx, TstRecvTxBlockDetails)
				if err != nil {
					return nil, err
				}

				_, err = r.AddCredit(0, false)
				if err != nil {
					return nil, err
				}

				_, err = r.ToJSON("", 100, &btcnet.MainNetParams)
				if err != nil {
					return nil, err
				}
				return s, nil
			},
			bal: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value),
			unc: 0,
			unspents: map[btcwire.OutPoint]struct{}{
				*btcwire.NewOutPoint(TstRecvTx.Sha(), 0): {},
			},
			unmined: map[btcwire.ShaHash]struct{}{},
		},
		{
			name: "rollback confirmed credit",
			f: func(s *Store) (*Store, error) {
				err := s.Rollback(TstRecvTxBlockDetails.Height)
				if err != nil {
					return nil, err
				}
				return s, nil
			},
			bal: 0,
			unc: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value),
			unspents: map[btcwire.OutPoint]struct{}{
				*btcwire.NewOutPoint(TstRecvTx.Sha(), 0): {},
			},
			unmined: map[btcwire.ShaHash]struct{}{},
		},
		{
			name: "insert confirmed double spend",
			f: func(s *Store) (*Store, error) {
				TstDoubleSpendTx.SetIndex(TstRecvIndex)
				r, err := s.InsertTx(TstDoubleSpendTx, TstRecvTxBlockDetails)
				if err != nil {
					return nil, err
				}

				_, err = r.AddCredit(0, false)
				if err != nil {
					return nil, err
				}

				_, err = r.ToJSON("", 100, &btcnet.MainNetParams)

				if err != nil {
					return nil, err
				}
				return s, nil
			},
			bal: btcutil.Amount(TstDoubleSpendTx.MsgTx().TxOut[0].Value),
			unc: 0,
			unspents: map[btcwire.OutPoint]struct{}{
				*btcwire.NewOutPoint(TstDoubleSpendTx.Sha(), 0): {},
			},
			unmined: map[btcwire.ShaHash]struct{}{},
		},
		{
			name: "insert unconfirmed debit",
			f: func(s *Store) (*Store, error) {
				prev, err := s.InsertTx(TstDoubleSpendTx, TstRecvTxBlockDetails)
				if err != nil {
					return nil, err
				}

				r, err := s.InsertTx(TstSpendingTx, nil)
				if err != nil {
					return nil, err
				}

				_, err = r.AddDebits(prev.Credits())
				if err != nil {
					return nil, err
				}

				_, err = r.ToJSON("", 100, &btcnet.MainNetParams)
				if err != nil {
					return nil, err
				}
				return s, nil
			},
			bal:      0,
			unc:      0,
			unspents: map[btcwire.OutPoint]struct{}{},
			unmined: map[btcwire.ShaHash]struct{}{
				*TstSpendingTx.Sha(): {},
			},
		},
		{
			name: "insert unconfirmed debit again",
			f: func(s *Store) (*Store, error) {
				prev, err := s.InsertTx(TstDoubleSpendTx, TstRecvTxBlockDetails)
				if err != nil {
					return nil, err
				}

				r, err := s.InsertTx(TstSpendingTx, nil)
				if err != nil {
					return nil, err
				}

				_, err = r.AddDebits(prev.Credits())
				if err != nil {
					return nil, err
				}

				_, err = r.ToJSON("", 100, &btcnet.MainNetParams)
				if err != nil {
					return nil, err
				}
				return s, nil
			},
			bal:      0,
			unc:      0,
			unspents: map[btcwire.OutPoint]struct{}{},
			unmined: map[btcwire.ShaHash]struct{}{
				*TstSpendingTx.Sha(): {},
			},
		},
		{
			name: "insert change (index 0)",
			f: func(s *Store) (*Store, error) {
				r, err := s.InsertTx(TstSpendingTx, nil)
				if err != nil {
					return nil, err
				}

				_, err = r.AddCredit(0, true)
				if err != nil {
					return nil, err
				}

				_, err = r.ToJSON("", 100, &btcnet.MainNetParams)
				if err != nil {
					return nil, err
				}
				return s, nil
			},
			bal: 0,
			unc: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value),
			unspents: map[btcwire.OutPoint]struct{}{
				*btcwire.NewOutPoint(TstSpendingTx.Sha(), 0): {},
			},
			unmined: map[btcwire.ShaHash]struct{}{
				*TstSpendingTx.Sha(): {},
			},
		},
		{
			name: "insert output back to this own wallet (index 1)",
			f: func(s *Store) (*Store, error) {
				r, err := s.InsertTx(TstSpendingTx, nil)
				if err != nil {
					return nil, err
				}

				_, err = r.AddCredit(1, true)
				if err != nil {
					return nil, err
				}

				_, err = r.ToJSON("", 100, &btcnet.MainNetParams)
				if err != nil {
					return nil, err
				}
				return s, nil
			},
			bal: 0,
			unc: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value),
			unspents: map[btcwire.OutPoint]struct{}{
				*btcwire.NewOutPoint(TstSpendingTx.Sha(), 0): {},
				*btcwire.NewOutPoint(TstSpendingTx.Sha(), 1): {},
			},
			unmined: map[btcwire.ShaHash]struct{}{
				*TstSpendingTx.Sha(): {},
			},
		},
		{
			name: "confirm signed tx",
			f: func(s *Store) (*Store, error) {
				TstSpendingTx.SetIndex(TstSignedTxIndex)
				r, err := s.InsertTx(TstSpendingTx, TstSignedTxBlockDetails)
				if err != nil {
					return nil, err
				}

				_, err = r.ToJSON("", 100, &btcnet.MainNetParams)
				if err != nil {
					return nil, err
				}
				return s, nil
			},
			bal: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value),
			unc: 0,
			unspents: map[btcwire.OutPoint]struct{}{
				*btcwire.NewOutPoint(TstSpendingTx.Sha(), 0): {},
				*btcwire.NewOutPoint(TstSpendingTx.Sha(), 1): {},
			},
			unmined: map[btcwire.ShaHash]struct{}{},
		},
		{
			name: "rollback after spending tx",
			f: func(s *Store) (*Store, error) {
				err := s.Rollback(TstSignedTxBlockDetails.Height + 1)
				if err != nil {
					return nil, err
				}
				return s, nil
			},
			bal: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value),
			unc: 0,
			unspents: map[btcwire.OutPoint]struct{}{
				*btcwire.NewOutPoint(TstSpendingTx.Sha(), 0): {},
				*btcwire.NewOutPoint(TstSpendingTx.Sha(), 1): {},
			},
			unmined: map[btcwire.ShaHash]struct{}{},
		},
		{
			name: "rollback spending tx block",
			f: func(s *Store) (*Store, error) {
				err := s.Rollback(TstSignedTxBlockDetails.Height)
				if err != nil {
					return nil, err
				}
				return s, nil
			},
			bal: 0,
			unc: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value),
			unspents: map[btcwire.OutPoint]struct{}{
				*btcwire.NewOutPoint(TstSpendingTx.Sha(), 0): {},
				*btcwire.NewOutPoint(TstSpendingTx.Sha(), 1): {},
			},
			unmined: map[btcwire.ShaHash]struct{}{
				*TstSpendingTx.Sha(): {},
			},
		},
		{
			name: "rollback double spend tx block",
			f: func(s *Store) (*Store, error) {
				err := s.Rollback(TstRecvTxBlockDetails.Height)
				if err != nil {
					return nil, err
				}
				return s, nil
			},
			bal: 0,
			unc: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value),
			unspents: map[btcwire.OutPoint]struct{}{
				*btcwire.NewOutPoint(TstSpendingTx.Sha(), 0): {},
				*btcwire.NewOutPoint(TstSpendingTx.Sha(), 1): {},
			},
			unmined: map[btcwire.ShaHash]struct{}{
				*TstSpendingTx.Sha(): {},
			},
		},
		{
			name: "insert original recv txout",
			f: func(s *Store) (*Store, error) {
				TstRecvTx.SetIndex(TstRecvIndex)
				r, err := s.InsertTx(TstRecvTx, TstRecvTxBlockDetails)
				if err != nil {
					return nil, err
				}

				_, err = r.AddCredit(0, false)
				if err != nil {
					return nil, err
				}

				_, err = r.ToJSON("", 100, &btcnet.MainNetParams)
				if err != nil {
					return nil, err
				}
				return s, nil
			},
			bal: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value),
			unc: 0,
			unspents: map[btcwire.OutPoint]struct{}{
				*btcwire.NewOutPoint(TstRecvTx.Sha(), 0): {},
			},
			unmined: map[btcwire.ShaHash]struct{}{},
		},
	}

	var s *Store
	for _, test := range tests {
		tmpStore, err := test.f(s)
		if err != nil {
			t.Fatalf("%s: got error: %v", test.name, err)
		}
		s = tmpStore
		bal, err := s.Balance(1, TstRecvCurrentHeight)
		if err != nil {
			t.Fatalf("%s: Confirmed Balance() failed: %v", test.name, err)
		}
		if bal != test.bal {
			t.Fatalf("%s: balance mismatch: expected: %d, got: %d", test.name, test.bal, bal)
		}
		unc, err := s.Balance(0, TstRecvCurrentHeight)
		if err != nil {
			t.Fatalf("%s: Unconfirmed Balance() failed: %v", test.name, err)
		}
		unc -= bal
		if unc != test.unc {
			t.Errorf("%s: unconfirmed balance mismatch: expected %d, got %d", test.name, test.unc, unc)
		}

		// Check that unspent outputs match expected.
		unspent, err := s.UnspentOutputs()
		if err != nil {
			t.Fatal(err)
		}
		for _, r := range unspent {
			if r.Spent() {
				t.Errorf("%s: unspent record marked as spent", test.name)
			}

			op := *r.OutPoint()
			if _, ok := test.unspents[op]; !ok {
				t.Errorf("%s: unexpected unspent output: %v", test.name, op)
			}
			delete(test.unspents, op)
		}
		if len(test.unspents) != 0 {
			t.Errorf("%s: missing expected unspent output(s)", test.name)
		}

		// Check that unmined sent txs match expected.
		for _, tx := range s.UnminedDebitTxs() {
			if _, ok := test.unmined[*tx.Sha()]; !ok {
				t.Fatalf("%s: unexpected unmined signed tx: %v", test.name, *tx.Sha())
			}
			delete(test.unmined, *tx.Sha())
		}
		if len(test.unmined) != 0 {
			t.Errorf("%s: missing expected unmined signed tx(s)", test.name)
		}

		// Pass a re-serialized version of the store to each next test.
		buf := new(bytes.Buffer)
		nWritten, err := s.WriteTo(buf)
		if err != nil {
			t.Fatalf("%v: serialization failed: %v (wrote %v bytes)", test.name, err, nWritten)
		}
		if nWritten != int64(buf.Len()) {
			t.Errorf("%v: wrote %v bytes but buffer has %v", test.name, nWritten, buf.Len())
		}
		nRead, err := s.ReadFrom(buf)
		if err != nil {
			t.Fatalf("%v: deserialization failed: %v (read %v bytes after writing %v)",
				test.name, err, nRead, nWritten)
		}
		if nWritten != nRead {
			t.Errorf("%v: number of bytes written (%v) does not match those read (%v)",
				test.name, nWritten, nRead)
		}
	}
}
Beispiel #22
0
// Balance returns the spendable wallet balance (total value of all unspent
// transaction outputs) given a minimum of minConf confirmations, calculated
// at a current chain height of curHeight.  Coinbase outputs are only included
// in the balance if maturity has been reached.
func (s *Store) Balance(minConf int, chainHeight int32) (btcutil.Amount, error) {
	var bal btcutil.Amount

	// Shadow these functions to avoid repeating arguments unnecesarily.
	confirms := func(height int32) int32 {
		return confirms(height, chainHeight)
	}
	confirmed := func(height int32) bool {
		return confirmed(minConf, height, chainHeight)
	}

	for _, b := range s.blocks {
		if confirmed(b.Height) {
			bal += b.amountDeltas.Spendable
			if confirms(b.Height) >= btcchain.CoinbaseMaturity {
				bal += b.amountDeltas.Reward
			}
			continue
		}
		// If there are still blocks that contain debiting transactions,
		// decrement the balance if they spend credits meeting minConf
		// confirmations.
		for _, r := range b.txs {
			if r.debits == nil {
				continue
			}
			for _, prev := range r.debits.spends {
				if !confirmed(prev.BlockHeight) {
					continue
				}
				r, err := s.lookupBlockTx(prev.BlockTxKey)
				if err != nil {
					return 0, err
				}
				v := r.Tx().MsgTx().TxOut[prev.OutputIndex].Value
				bal -= btcutil.Amount(v)
			}
		}
	}

	// Unconfirmed transactions which spend previous credits debit from
	// the returned balance, even with minConf > 0.
	for prev := range s.unconfirmed.spentBlockOutPoints {
		if confirmed(prev.BlockHeight) {
			r, err := s.lookupBlockTx(prev.BlockTxKey)
			if err != nil {
				return 0, err
			}
			v := r.Tx().MsgTx().TxOut[prev.OutputIndex].Value
			bal -= btcutil.Amount(v)
		}
	}

	// If unconfirmed credits are included, tally them as well.
	if minConf == 0 {
		for _, r := range s.unconfirmed.txs {
			for i, c := range r.credits {
				if c == nil {
					continue
				}
				if c.spentBy == nil {
					v := r.Tx().MsgTx().TxOut[i].Value
					bal += btcutil.Amount(v)
				}
			}
		}
	}

	return bal, nil
}
Beispiel #23
0
// Amount returns the amount credited to the account from a transaction output.
func (c Credit) Amount() btcutil.Amount {
	msgTx := c.Tx().MsgTx()
	return btcutil.Amount(msgTx.TxOut[c.OutputIndex].Value)
}
Beispiel #24
0
func (s *Store) moveMinedTx(r *txRecord, block *Block) error {
	log.Infof("Marking unconfirmed transaction %v mined in block %d",
		r.tx.Sha(), block.Height)

	delete(s.unconfirmed.txs, *r.Tx().Sha())

	// Find collection and insert records.  Error out if there are records
	// saved for this block and index.
	key := BlockTxKey{r.Tx().Index(), block.Height}
	b := s.blockCollectionForInserts(block)
	txIndex := uint32(len(b.txs))
	b.txIndexes[key.BlockIndex] = txIndex
	b.txs = append(b.txs, r)

	for _, input := range r.Tx().MsgTx().TxIn {
		delete(s.unconfirmed.previousOutpoints, input.PreviousOutpoint)

		// For all mined transactions with credits spent by this
		// transaction, remove them from the spentBlockOutPoints map
		// (signifying that there is no longer an unconfirmed
		// transaction which spending that credit), and update the
		// credit's spent by tracking with the block key of the
		// newly-mined transaction.
		prev, ok := s.unconfirmed.spentBlockOutPointKeys[input.PreviousOutpoint]
		if !ok {
			continue
		}
		delete(s.unconfirmed.spentBlockOutPointKeys, input.PreviousOutpoint)
		delete(s.unconfirmed.spentBlockOutPoints, prev)
		rr, err := s.lookupBlockTx(prev.BlockTxKey)
		if err != nil {
			return err
		}
		rr.credits[prev.OutputIndex].spentBy = &key
		// debits should already be non-nil
		r.debits.spends = append(r.debits.spends, prev)
	}

	// For each credit in r, if the credit is spent by another unconfirmed
	// transaction, move the spending transaction from spentUnconfirmed
	// (which signifies a transaction spending another unconfirmed tx) to
	// spentBlockTxs (which signifies an unconfirmed transaction spending a
	// confirmed tx) and modify the mined transaction's record to refer to
	// the credit being spent by an unconfirmed transaction.
	//
	// If the credit is not spent, modify the store's unspent bookkeeping
	// maps to include the credit and increment the amount deltas by the
	// credit's value.
	op := btcwire.OutPoint{Hash: *r.Tx().Sha()}
	for i, credit := range r.credits {
		if credit == nil {
			continue
		}
		op.Index = uint32(i)
		outputKey := BlockOutputKey{key, op.Index}
		if rr, ok := s.unconfirmed.spentUnconfirmed[op]; ok {
			delete(s.unconfirmed.spentUnconfirmed, op)
			s.unconfirmed.spentBlockOutPointKeys[op] = outputKey
			s.unconfirmed.spentBlockOutPoints[outputKey] = rr
			credit.spentBy = &BlockTxKey{BlockHeight: -1}
		} else if credit.spentBy == nil {
			// Mark outpoint unspent.
			s.unspent[op] = key

			// Increment spendable amount delta as a result of
			// moving this credit to this block.
			value := r.Tx().MsgTx().TxOut[i].Value
			b.amountDeltas.Spendable += btcutil.Amount(value)
		}
	}

	// If this moved transaction debits from any previous credits, decrement
	// the amount deltas by the total input amount.  Because this
	// transaction was moved from the unconfirmed transaction set, this can
	// never be a coinbase.
	if r.debits != nil {
		b.amountDeltas.Spendable -= r.debits.amount
	}

	return nil
}