// createVinList returns a slice of JSON objects for the inputs of the passed // transaction. func createVinList(mtx *btcwire.MsgTx) ([]btcjson.Vin, error) { tx := btcutil.NewTx(mtx) vinList := make([]btcjson.Vin, len(mtx.TxIn)) for i, v := range mtx.TxIn { if btcchain.IsCoinBase(tx) { vinList[i].Coinbase = hex.EncodeToString(v.SignatureScript) } else { vinList[i].Txid = v.PreviousOutpoint.Hash.String() vinList[i].Vout = int(v.PreviousOutpoint.Index) disbuf, err := btcscript.DisasmString(v.SignatureScript) if err != nil { return nil, btcjson.Error{ Code: btcjson.ErrInternal.Code, Message: err.Error(), } } vinList[i].ScriptSig = new(btcjson.ScriptSig) vinList[i].ScriptSig.Asm = disbuf vinList[i].ScriptSig.Hex = hex.EncodeToString(v.SignatureScript) } vinList[i].Sequence = v.Sequence } return vinList, nil }
// createCoinbaseTx returns a coinbase transaction paying an appropriate subsidy // based on the passed block height to the provided address. When the address // is nil, the coinbase transaction will instead be redeemable by anyone. // // See the comment for NewBlockTemplate for more information about why the nil // address handling is useful. func createCoinbaseTx(coinbaseScript []byte, nextBlockHeight int64, addr btcutil.Address) (*btcutil.Tx, error) { // Create the script to pay to the provided payment address if one was // specified. Otherwise create a script that allows the coinbase to be // redeemable by anyone. var pkScript []byte if addr != nil { var err error pkScript, err = btcscript.PayToAddrScript(addr) if err != nil { return nil, err } } else { scriptBuilder := btcscript.NewScriptBuilder() pkScript = scriptBuilder.AddOp(btcscript.OP_TRUE).Script() } tx := btcwire.NewMsgTx() tx.AddTxIn(&btcwire.TxIn{ // Coinbase transactions have no inputs, so previous outpoint is // zero hash and max index. PreviousOutPoint: *btcwire.NewOutPoint(&btcwire.ShaHash{}, btcwire.MaxPrevOutIndex), SignatureScript: coinbaseScript, Sequence: btcwire.MaxTxInSequenceNum, }) tx.AddTxOut(&btcwire.TxOut{ Value: btcchain.CalcBlockSubsidy(nextBlockHeight, activeNetParams.Params), PkScript: pkScript, }) return btcutil.NewTx(tx), nil }
// Receive waits for the response promised by the future and returns a // transaction given its hash. func (r FutureGetRawTransactionResult) Receive() (*btcutil.Tx, error) { res, err := receiveFuture(r) if err != nil { return nil, err } // Unmarshal result as a string. var txHex string err = json.Unmarshal(res, &txHex) if err != nil { return nil, err } // Decode the serialized transaction hex to raw bytes. serializedTx, err := hex.DecodeString(txHex) if err != nil { return nil, err } // Deserialize the transaction and return it. var msgTx btcwire.MsgTx if err := msgTx.Deserialize(bytes.NewReader(serializedTx)); err != nil { return nil, err } return btcutil.NewTx(&msgTx), nil }
// TestTx tests the API for Tx. func TestTx(t *testing.T) { testTx := Block100000.Transactions[0] tx := btcutil.NewTx(testTx) // Ensure we get the same data back out. if msgTx := tx.MsgTx(); !reflect.DeepEqual(msgTx, testTx) { t.Errorf("MsgTx: mismatched MsgTx - got %v, want %v", spew.Sdump(msgTx), spew.Sdump(testTx)) } // Ensure transaction index set and get work properly. wantIndex := 0 tx.SetIndex(0) if gotIndex := tx.Index(); gotIndex != wantIndex { t.Errorf("Index: mismatched index - got %v, want %v", gotIndex, wantIndex) } // Hash for block 100,000 transaction 0. wantShaStr := "8c14f0db3df150123e6f3dbbf30f8b955a8249b62ac1d1ff16284aefa3d06d87" wantSha, err := btcwire.NewShaHashFromStr(wantShaStr) if err != nil { t.Errorf("NewShaHashFromStr: %v", err) } // Request the sha multiple times to test generation and caching. for i := 0; i < 2; i++ { sha := tx.Sha() if !sha.IsEqual(wantSha) { t.Errorf("Sha #%d mismatched sha - got %v, want %v", i, sha, wantSha) } } }
// Receive waits for the response promised by the future and returns a // transaction given its hash. func (r FutureGetRawTransactionResult) Receive() (*btcutil.Tx, error) { reply, err := receiveFuture(r) if err != nil { return nil, err } // Ensure the returned data is the expected type. txHex, ok := reply.(string) if !ok { return nil, fmt.Errorf("unexpected response type for "+ "getrawtransaction (verbose=0): %T\n", reply) } // Decode the serialized transaction hex to raw bytes. serializedTx, err := hex.DecodeString(txHex) if err != nil { return nil, err } // Deserialize the transaction and return it. var msgTx btcwire.MsgTx if err := msgTx.Deserialize(bytes.NewReader(serializedTx)); err != nil { return nil, err } return btcutil.NewTx(&msgTx), nil }
// fetchTxStoreMain fetches transaction data about the provided set of // transactions from the point of view of the end of the main chain. It takes // a flag which specifies whether or not fully spent transaction should be // included in the results. func fetchTxStoreMain(db btcdb.Db, txSet map[btcwire.ShaHash]struct{}, includeSpent bool) TxStore { // Just return an empty store now if there are no requested hashes. txStore := make(TxStore) if len(txSet) == 0 { return txStore } // The transaction store map needs to have an entry for every requested // transaction. By default, all the transactions are marked as missing. // Each entry will be filled in with the appropriate data below. txList := make([]*btcwire.ShaHash, 0, len(txSet)) for hash := range txSet { hashCopy := hash txStore[hash] = &TxData{Hash: &hashCopy, Err: btcdb.ErrTxShaMissing} txList = append(txList, &hashCopy) } // Ask the database (main chain) for the list of transactions. This // will return the information from the point of view of the end of the // main chain. Choose whether or not to include fully spent // transactions depending on the passed flag. fetchFunc := db.FetchUnSpentTxByShaList if includeSpent { fetchFunc = db.FetchTxByShaList } txReplyList := fetchFunc(txList) for _, txReply := range txReplyList { // Lookup the existing results entry to modify. Skip // this reply if there is no corresponding entry in // the transaction store map which really should not happen, but // be safe. txD, ok := txStore[*txReply.Sha] if !ok { continue } // Fill in the transaction details. A copy is used here since // there is no guarantee the returned data isn't cached and // this code modifies the data. A bug caused by modifying the // cached data would likely be difficult to track down and could // cause subtle errors, so avoid the potential altogether. txD.Err = txReply.Err if txReply.Err == nil { txD.Tx = btcutil.NewTx(txReply.Tx) txD.BlockHeight = txReply.Height txD.Spent = make([]bool, len(txReply.TxSpent)) copy(txD.Spent, txReply.TxSpent) } } return txStore }
// handleTxMsg is invoked when a peer receives a tx bitcoin message. It blocks // until the bitcoin transaction has been fully processed. Unlock the block // handler this does not serialize all transactions through a single thread // transactions don't rely on the previous one in a linear fashion like blocks. func (p *peer) handleTxMsg(msg *btcwire.MsgTx) { // Add the transaction to the known inventory for the peer. // Convert the raw MsgTx to a btcutil.Tx which provides some convenience // methods and things such as hash caching. tx := btcutil.NewTx(msg) iv := btcwire.NewInvVect(btcwire.InvTypeTx, tx.Sha()) p.addKnownInventory(iv) // Queue the transaction up to be handled by the block manager and // intentionally block further receives until the transaction is fully // processed and known good or bad. This helps prevent a malicious peer // from queueing up a bunch of bad transactions before disconnecting (or // being disconnected) and wasting memory. p.server.blockManager.QueueTx(tx, p) <-p.txProcessed }
// handleSendRawTransaction implements the sendrawtransaction command. func handleSendRawTransaction(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) { c := cmd.(*btcjson.SendRawTransactionCmd) // Deserialize and send off to tx relay serializedTx, err := hex.DecodeString(c.HexTx) if err != nil { return nil, btcjson.ErrDecodeHexString } msgtx := btcwire.NewMsgTx() err = msgtx.Deserialize(bytes.NewBuffer(serializedTx)) if err != nil { err := btcjson.Error{ Code: btcjson.ErrDeserialization.Code, Message: "TX decode failed", } return nil, err } tx := btcutil.NewTx(msgtx) err = s.server.txMemPool.ProcessTransaction(tx, false) if err != nil { // When the error is a rule error, it means the transaction was // simply rejected as opposed to something actually going wrong, // so log it as such. Otherwise, something really did go wrong, // so log it as an actual error. In both cases, a JSON-RPC // error is returned to the client with the deserialization // error code (to match bitcoind behavior). if _, ok := err.(TxRuleError); ok { rpcsLog.Debugf("Rejected transaction %v: %v", tx.Sha(), err) } else { rpcsLog.Errorf("Failed to process transaction %v: %v", tx.Sha(), err) } err = btcjson.Error{ Code: btcjson.ErrDeserialization.Code, Message: fmt.Sprintf("TX rejected: %v", err), } return nil, err } // We keep track of all the sendrawtransaction request txs so that we // can rebroadcast them if they don't make their way into a block. iv := btcwire.NewInvVect(btcwire.InvTypeTx, tx.Sha()) s.server.AddRebroadcastInventory(iv) return tx.Sha().String(), nil }
// handleSendRawTransaction implements the sendrawtransaction command. func handleSendRawTransaction(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) { c := cmd.(*btcjson.SendRawTransactionCmd) // Deserialize and send off to tx relay serializedTx, err := hex.DecodeString(c.HexTx) if err != nil { return nil, btcjson.ErrDecodeHexString } msgtx := btcwire.NewMsgTx() err = msgtx.Deserialize(bytes.NewBuffer(serializedTx)) if err != nil { err := btcjson.Error{ Code: btcjson.ErrDeserialization.Code, Message: "Unable to deserialize raw tx", } return nil, err } tx := btcutil.NewTx(msgtx) err = s.server.txMemPool.ProcessTransaction(tx) if err != nil { // When the error is a rule error, it means the transaction was // simply rejected as opposed to something actually going wrong, // so log it as such. Otherwise, something really did go wrong, // so log it as an actual error. if _, ok := err.(TxRuleError); ok { log.Debugf("RPCS: Rejected transaction %v: %v", tx.Sha(), err) } else { log.Errorf("RPCS: Failed to process transaction %v: %v", tx.Sha(), err) } err = btcjson.Error{ Code: btcjson.ErrDeserialization.Code, Message: "Failed to process transaction", } return nil, err } // If called from websocket code, add a mined tx hashes // request. if walletNotification != nil { s.ws.requests.AddMinedTxRequest(walletNotification, tx.Sha()) } return tx.Sha().String(), nil }
// TestCheckSerializedHeight tests the checkSerializedHeight function with // various serialized heights and also does negative tests to ensure errors // and handled properly. func TestCheckSerializedHeight(t *testing.T) { // Create an empty coinbase template to be used in the tests below. coinbaseOutpoint := btcwire.NewOutPoint(&btcwire.ShaHash{}, math.MaxUint32) coinbaseTx := btcwire.NewMsgTx() coinbaseTx.Version = 2 coinbaseTx.AddTxIn(btcwire.NewTxIn(coinbaseOutpoint, nil)) // tests := []struct { sigScript []byte // Serialized data wantHeight int64 // Expected height err error // Expected error type }{ // No serialized height length. {[]byte{}, 0, btcchain.RuleError("")}, // Serialized height length with no height bytes. {[]byte{0x02}, 0, btcchain.RuleError("")}, // Serialized height length with too few height bytes. {[]byte{0x02, 0x4a}, 0, btcchain.RuleError("")}, // Serialized height that needs 2 bytes to encode. {[]byte{0x02, 0x4a, 0x52}, 21066, nil}, // Serialized height that needs 2 bytes to encode, but backwards // endianness. {[]byte{0x02, 0x4a, 0x52}, 19026, btcchain.RuleError("")}, // Serialized height that needs 3 bytes to encode. {[]byte{0x03, 0x40, 0x0d, 0x03}, 200000, nil}, // Serialized height that needs 3 bytes to encode, but backwards // endianness. {[]byte{0x03, 0x40, 0x0d, 0x03}, 1074594560, btcchain.RuleError("")}, } t.Logf("Running %d tests", len(tests)) for i, test := range tests { msgTx := coinbaseTx.Copy() msgTx.TxIn[0].SignatureScript = test.sigScript tx := btcutil.NewTx(msgTx) err := btcchain.TstCheckSerializedHeight(tx, test.wantHeight) if reflect.TypeOf(err) != reflect.TypeOf(test.err) { t.Errorf("checkSerializedHeight #%d wrong error type "+ "got: %v <%T>, want: %T", i, err, err, test.err) continue } } }
// parseChainTxNtfnParams parses out the transaction and optional details about // the block it's mined in from the parameters of recvtx and redeemingtx // notifications. func parseChainTxNtfnParams(params []json.RawMessage) (*btcutil.Tx, *btcws.BlockDetails, error) { if len(params) == 0 || len(params) > 2 { return nil, nil, wrongNumParams(len(params)) } // Unmarshal first parameter as a string. var txHex string err := json.Unmarshal(params[0], &txHex) if err != nil { return nil, nil, err } // If present, unmarshal second optional parameter as the block details // JSON object. var block *btcws.BlockDetails if len(params) > 1 { err = json.Unmarshal(params[1], &block) if err != nil { return nil, nil, err } } // Hex decode and deserialize the transaction. serializedTx, err := hex.DecodeString(txHex) if err != nil { return nil, nil, err } var msgTx btcwire.MsgTx err = msgTx.Deserialize(bytes.NewReader(serializedTx)) if err != nil { return nil, nil, err } // TODO: Change recvtx and redeemingtx callback signatures to use // nicer types for details about the block (block sha as a // btcwire.ShaHash, block time as a time.Time, etc.). return btcutil.NewTx(&msgTx), block, nil }
// handleSendRawTransaction implements the sendrawtransaction command. func handleSendRawTransaction(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) { c := cmd.(*btcjson.SendRawTransactionCmd) // Deserialize and send off to tx relay serializedTx, err := hex.DecodeString(c.HexTx) if err != nil { return nil, btcjson.ErrDecodeHexString } msgtx := btcwire.NewMsgTx() err = msgtx.Deserialize(bytes.NewBuffer(serializedTx)) if err != nil { err := btcjson.Error{ Code: btcjson.ErrDeserialization.Code, Message: "TX decode failed", } return nil, err } tx := btcutil.NewTx(msgtx) err = s.server.txMemPool.ProcessTransaction(tx) if err != nil { // When the error is a rule error, it means the transaction was // simply rejected as opposed to something actually going wrong, // so log it as such. Otherwise, something really did go wrong, // so log it as an actual error. if _, ok := err.(TxRuleError); ok { rpcsLog.Debugf("Rejected transaction %v: %v", tx.Sha(), err) } else { rpcsLog.Errorf("Failed to process transaction %v: %v", tx.Sha(), err) err = btcjson.Error{ Code: btcjson.ErrDeserialization.Code, Message: "TX rejected", } return nil, err } } return tx.Sha().String(), nil }
// createCoinbaseTx returns a coinbase transaction paying an appropriate subsidy // based on the passed block height to the passed public key. It also accepts // an extra nonce value for the signature script. This extra nonce helps ensure // the transaction is not a duplicate transaction (paying the same value to the // same public key address would otherwise be an identical transaction for // block version 1). func createCoinbaseTx(coinbaseScript []byte, nextBlockHeight int64, addr btcutil.Address) (*btcutil.Tx, error) { // Create a script to pay to the specific address. pkScript, err := btcscript.PayToAddrScript(addr) if err != nil { return nil, err } tx := btcwire.NewMsgTx() tx.AddTxIn(&btcwire.TxIn{ // Coinbase transactions have no inputs, so previous outpoint is // zero hash and max index. PreviousOutpoint: *btcwire.NewOutPoint(&btcwire.ShaHash{}, btcwire.MaxPrevOutIndex), SignatureScript: coinbaseScript, Sequence: btcwire.MaxTxInSequenceNum, }) tx.AddTxOut(&btcwire.TxOut{ Value: btcchain.CalcBlockSubsidy(nextBlockHeight, activeNetParams.Params), PkScript: pkScript, }) return btcutil.NewTx(tx), nil }
// 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 }
func getSignatures(maxHeigth int64, log btclog.Logger, db btcdb.Db) chan *rData { heigthChan := make(chan int64) blockChan := make(chan *btcutil.Block) sigChan := make(chan *rData) go func() { for h := int64(0); h < maxHeigth; h++ { heigthChan <- h } close(heigthChan) }() var blockWg sync.WaitGroup for i := 0; i <= 10; i++ { blockWg.Add(1) go func() { for h := range heigthChan { sha, err := db.FetchBlockShaByHeight(h) if err != nil { log.Warnf("failed FetchBlockShaByHeight(%v): %v", h, err) return } blk, err := db.FetchBlockBySha(sha) if err != nil { log.Warnf("failed FetchBlockBySha(%v) - h %v: %v", sha, h, err) return } blockChan <- blk } blockWg.Done() }() } go func() { blockWg.Wait() close(blockChan) }() var sigWg sync.WaitGroup for i := 0; i <= 10; i++ { sigWg.Add(1) go func() { for blk := range blockChan { mblk := blk.MsgBlock() for i, tx := range mblk.Transactions { if btcchain.IsCoinBase(btcutil.NewTx(tx)) { continue } for t, txin := range tx.TxIn { dataSlice, err := btcscript.PushedData(txin.SignatureScript) if err != nil { continue } for d, data := range dataSlice { signature, err := btcec.ParseSignature(data, btcec.S256()) if err != nil { continue } sigChan <- &rData{ sig: signature, H: blk.Height(), Tx: i, TxIn: t, Data: d, } } } } } sigWg.Done() }() } go func() { sigWg.Wait() close(sigChan) }() return sigChan }
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) } } }
// 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) } }
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 }
// 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 }