// addrToKey converts known address types to an addrindex key. An error is // returned for unsupported types. func addrToKey(addr btcutil.Address) ([addrKeySize]byte, error) { switch addr := addr.(type) { case *btcutil.AddressPubKeyHash: var result [addrKeySize]byte result[0] = addrKeyTypePubKeyHash copy(result[1:], addr.Hash160()[:]) return result, nil case *btcutil.AddressScriptHash: var result [addrKeySize]byte result[0] = addrKeyTypeScriptHash copy(result[1:], addr.Hash160()[:]) return result, nil case *btcutil.AddressPubKey: var result [addrKeySize]byte result[0] = addrKeyTypePubKeyHash copy(result[1:], addr.AddressPubKeyHash().Hash160()[:]) return result, nil } return [addrKeySize]byte{}, errUnsupportedAddressType }
// FetchTxsForAddr looks up and returns all transactions which either // spend from a previously created output of the passed address, or // create a new output locked to the passed address. The, `limit` parameter // should be the max number of transactions to be returned. Additionally, if the // caller wishes to seek forward in the results some amount, the 'seek' // represents how many results to skip. func (db *LevelDb) FetchTxsForAddr(addr btcutil.Address, skip int, limit int, reverse bool) ([]*database.TxListReply, int, error) { db.dbLock.Lock() defer db.dbLock.Unlock() // Enforce constraints for skip and limit. if skip < 0 { return nil, 0, errors.New("offset for skip must be positive") } if limit < 0 { return nil, 0, errors.New("value for limit must be positive") } // Parse address type, bailing on an unknown type. var addrKey []byte switch addr := addr.(type) { case *btcutil.AddressPubKeyHash: hash160 := addr.Hash160() addrKey = hash160[:] case *btcutil.AddressScriptHash: hash160 := addr.Hash160() addrKey = hash160[:] case *btcutil.AddressPubKey: hash160 := addr.AddressPubKeyHash().Hash160() addrKey = hash160[:] default: return nil, 0, database.ErrUnsupportedAddressType } // Create the prefix for our search. addrPrefix := make([]byte, 23, 23) copy(addrPrefix[0:3], addrIndexKeyPrefix) copy(addrPrefix[3:23], addrKey) iter := db.lDb.NewIterator(bytesPrefix(addrPrefix), nil) skipped := 0 if reverse { // Go to the last element if reverse iterating. iter.Last() // Skip "one past" the last element so the loops below don't // miss the last element due to Prev() being called first. // We can safely ignore iterator exhaustion since the loops // below will see there's no keys anyway. iter.Next() } for skip != 0 && advanceIterator(iter, reverse) { skip-- skipped++ } // Iterate through all address indexes that match the targeted prefix. var replies []*database.TxListReply var rawIndex [12]byte for advanceIterator(iter, reverse) && limit != 0 { copy(rawIndex[:], iter.Key()[23:35]) addrIndex := unpackTxIndex(rawIndex) tx, blkSha, blkHeight, _, err := db.fetchTxDataByLoc(addrIndex.blkHeight, addrIndex.txoffset, addrIndex.txlen, []byte{}) if err != nil { // Eat a possible error due to a potential re-org. continue } txSha := tx.TxSha() txReply := &database.TxListReply{Sha: &txSha, Tx: tx, BlkSha: blkSha, Height: blkHeight, TxSpent: []bool{}, Err: err} replies = append(replies, txReply) limit-- } iter.Release() if err := iter.Error(); err != nil { return nil, 0, err } return replies, skipped, nil }
// FetchTxsForAddr looks up and returns all transactions which either // spend from a previously created output of the passed address, or // create a new output locked to the passed address. The, `limit` parameter // should be the max number of transactions to be returned. Additionally, if the // caller wishes to seek forward in the results some amount, the 'seek' // represents how many results to skip. func (db *LevelDb) FetchTxsForAddr(addr btcutil.Address, skip int, limit int) ([]*database.TxListReply, error) { db.dbLock.Lock() defer db.dbLock.Unlock() // Enforce constraints for skip and limit. if skip < 0 { return nil, errors.New("offset for skip must be positive") } if limit < 0 { return nil, errors.New("value for limit must be positive") } // Parse address type, bailing on an unknown type. var addrKey []byte switch addr := addr.(type) { case *btcutil.AddressPubKeyHash: hash160 := addr.Hash160() addrKey = hash160[:] case *btcutil.AddressScriptHash: hash160 := addr.Hash160() addrKey = hash160[:] case *btcutil.AddressPubKey: hash160 := addr.AddressPubKeyHash().Hash160() addrKey = hash160[:] default: return nil, database.ErrUnsupportedAddressType } // Create the prefix for our search. addrPrefix := make([]byte, 23, 23) copy(addrPrefix[0:3], addrIndexKeyPrefix) copy(addrPrefix[3:23], addrKey) iter := db.lDb.NewIterator(bytesPrefix(addrPrefix), nil) for skip != 0 && iter.Next() { skip-- } // Iterate through all address indexes that match the targeted prefix. var replies []*database.TxListReply var rawIndex [12]byte for iter.Next() && limit != 0 { copy(rawIndex[:], iter.Key()[23:35]) addrIndex := unpackTxIndex(rawIndex) tx, blkSha, blkHeight, _, err := db.fetchTxDataByLoc(addrIndex.blkHeight, addrIndex.txoffset, addrIndex.txlen, []byte{}) if err != nil { // Eat a possible error due to a potential re-org. continue } txSha := tx.TxSha() txReply := &database.TxListReply{Sha: &txSha, Tx: tx, BlkSha: blkSha, Height: blkHeight, TxSpent: []bool{}, Err: err} replies = append(replies, txReply) limit-- } iter.Release() if err := iter.Error(); err != nil { return nil, err } return replies, nil }
func (w *LibbitcoinWallet) Spend(amount int64, addr btc.Address, feeLevel bitcoin.FeeLevel) error { // Check for dust script, _ := txscript.PayToAddrScript(addr) if txrules.IsDustAmount(btc.Amount(amount), len(script), txrules.DefaultRelayFeePerKb) { return errors.New("Amount is below dust threshold") } var additionalPrevScripts map[wire.OutPoint][]byte var additionalKeysByAddress map[string]*btc.WIF // Create input source coinMap := w.gatherCoins() coins := make([]coinset.Coin, 0, len(coinMap)) for k := range coinMap { coins = append(coins, k) } inputSource := func(target btc.Amount) (total btc.Amount, inputs []*wire.TxIn, scripts [][]byte, err error) { // TODO: maybe change the coin selection algorithm? We're using min coins right now because // TODO: we don't know the number of confirmations on each coin without querying the libbitcoin server. coinSelector := coinset.MinNumberCoinSelector{MaxInputs: 10000, MinChangeAmount: btc.Amount(10000)} coins, err := coinSelector.CoinSelect(target, coins) if err != nil { return total, inputs, scripts, errors.New("insuffient funds") } additionalPrevScripts = make(map[wire.OutPoint][]byte) additionalKeysByAddress = make(map[string]*btc.WIF) for _, c := range coins.Coins() { total += c.Value() outpoint := wire.NewOutPoint(c.Hash(), c.Index()) in := wire.NewTxIn(outpoint, []byte{}) in.Sequence = 0 // Opt-in RBF so we can bump fees inputs = append(inputs, in) additionalPrevScripts[*outpoint] = c.PkScript() key := coinMap[c] addr, _ := btc.NewAddressPubKey(key.PublicKey().Key, w.params) pk, _ := btcec.PrivKeyFromBytes(btcec.S256(), key.Key) wif, _ := btc.NewWIF(pk, w.params, true) additionalKeysByAddress[addr.AddressPubKeyHash().EncodeAddress()] = wif } return total, inputs, scripts, nil } // Get the fee per kilobyte feePerKB := int64(w.getFeePerByte(feeLevel)) * 1000 // outputs out := wire.NewTxOut(amount, script) // Create change source changeSource := func() ([]byte, error) { addr := w.GetCurrentAddress(bitcoin.CHANGE) script, err := txscript.PayToAddrScript(addr) if err != nil { return []byte{}, err } return script, nil } authoredTx, err := txauthor.NewUnsignedTransaction([]*wire.TxOut{out}, btc.Amount(feePerKB), inputSource, changeSource) if err != nil { return err } // BIP 69 sorting txsort.InPlaceSort(authoredTx.Tx) // Sign tx getKey := txscript.KeyClosure(func(addr btc.Address) ( *btcec.PrivateKey, bool, error) { addrStr := addr.EncodeAddress() wif := additionalKeysByAddress[addrStr] return wif.PrivKey, wif.CompressPubKey, nil }) getScript := txscript.ScriptClosure(func( addr btc.Address) ([]byte, error) { return []byte{}, nil }) for i, txIn := range authoredTx.Tx.TxIn { prevOutScript := additionalPrevScripts[txIn.PreviousOutPoint] script, err := txscript.SignTxOutput(w.params, authoredTx.Tx, i, prevOutScript, txscript.SigHashAll, getKey, getScript, txIn.SignatureScript) if err != nil { return errors.New("Failed to sign transaction") } txIn.SignatureScript = script } // Broadcast tx to bitcoin network serializedTx := new(bytes.Buffer) authoredTx.Tx.Serialize(serializedTx) w.Client.Broadcast(serializedTx.Bytes(), func(i interface{}, err error) { if err == nil { log.Infof("Broadcast tx %s to bitcoin network\n", authoredTx.Tx.TxSha().String()) } else { log.Errorf("Failed to broadcast tx, reason: %s\n", err) } }) // Update the db w.ProcessTransaction(btc.NewTx(authoredTx.Tx), 0) return nil }