// 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 }
// 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 }
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 }
// 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 }
// 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 }
// 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 }
// 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 }
// 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 }
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") } }
// 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 }
// 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 }
// 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 }
// 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 }
// Value returns the value of the Coin func (c *SimpleCoin) Value() btcutil.Amount { return btcutil.Amount(c.txOut().Value) }
// 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 }
// 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) } }
// 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 }
{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")
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 }
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 }
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) } } }
// 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 }
// 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) }
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 }