// indexUnconfirmedAddresses modifies the unconfirmed (memory-only) address // index to include mappings for the addresses encoded by the passed public key // script to the transaction. // // This function is safe for concurrent access. func (idx *AddrIndex) indexUnconfirmedAddresses(pkScript []byte, tx *btcutil.Tx) { // The error is ignored here since the only reason it can fail is if the // script fails to parse and it was already validated before being // admitted to the mempool. _, addresses, _, _ := txscript.ExtractPkScriptAddrs(pkScript, idx.chainParams) for _, addr := range addresses { // Ignore unsupported address types. addrKey, err := addrToKey(addr) if err != nil { continue } // Add a mapping from the address to the transaction. idx.unconfirmedLock.Lock() addrIndexEntry := idx.txnsByAddr[addrKey] if addrIndexEntry == nil { addrIndexEntry = make(map[chainhash.Hash]*btcutil.Tx) idx.txnsByAddr[addrKey] = addrIndexEntry } addrIndexEntry[*tx.Hash()] = tx // Add a mapping from the transaction to the address. addrsByTxEntry := idx.addrsByTx[*tx.Hash()] if addrsByTxEntry == nil { addrsByTxEntry = make(map[[addrKeySize]byte]struct{}) idx.addrsByTx[*tx.Hash()] = addrsByTxEntry } addrsByTxEntry[addrKey] = struct{}{} idx.unconfirmedLock.Unlock() } }
// getActor returns the actor to which this vout belongs to func (com *Communication) getActor(actors []*Actor, vout *wire.TxOut) (*Actor, error) { // get addrs which own this utxo _, addrs, _, err := txscript.ExtractPkScriptAddrs(vout.PkScript, &chaincfg.SimNetParams) if err != nil { return nil, err } // we're expecting only 1 addr since we created a standard p2pkh tx addr := addrs[0].String() // find which actor this addr belongs to // TODO: could probably be optimized by creating // a global addr -> actor index rather than looking // up each actor addrs for _, actor := range actors { for _, actorAddr := range actor.ownedAddresses { if addr == actorAddr.String() { return actor, nil } } } err = errors.New("cannot find any actor who owns this tx output") return nil, err }
func lookupInputAccount(w *Wallet, details *wtxmgr.TxDetails, deb wtxmgr.DebitRecord) uint32 { // TODO: Debits should record which account(s?) they // debit from so this doesn't need to be looked up. prevOP := &details.MsgTx.TxIn[deb.Index].PreviousOutPoint prev, err := w.TxStore.TxDetails(&prevOP.Hash) if err != nil { log.Errorf("Cannot query previous transaction details for %v: %v", prevOP.Hash, err) return 0 } if prev == nil { log.Errorf("Missing previous transaction %v", prevOP.Hash) return 0 } prevOut := prev.MsgTx.TxOut[prevOP.Index] _, addrs, _, err := txscript.ExtractPkScriptAddrs(prevOut.PkScript, w.chainParams) var inputAcct uint32 if err == nil && len(addrs) > 0 { inputAcct, err = w.Manager.AddrAccount(addrs[0]) } if err != nil { log.Errorf("Cannot fetch account for previous output %v: %v", prevOP, err) inputAcct = 0 } return inputAcct }
// indexPkScript extracts all standard addresses from the passed public key // script and maps each of them to the associated transaction using the passed // map. func (idx *AddrIndex) indexPkScript(data writeIndexData, pkScript []byte, txIdx int) { // Nothing to index if the script is non-standard or otherwise doesn't // contain any addresses. _, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript, idx.chainParams) if err != nil || len(addrs) == 0 { return } for _, addr := range addrs { addrKey, err := addrToKey(addr) if err != nil { // Ignore unsupported address types. continue } // Avoid inserting the transaction more than once. Since the // transactions are indexed serially any duplicates will be // indexed in a row, so checking the most recent entry for the // address is enough to detect duplicates. indexedTxns := data[addrKey] numTxns := len(indexedTxns) if numTxns > 0 && indexedTxns[numTxns-1] == txIdx { continue } indexedTxns = append(indexedTxns, txIdx) data[addrKey] = indexedTxns } }
// This example demonstrates extracting information from a standard public key // script. func ExampleExtractPkScriptAddrs() { // Start with a standard pay-to-pubkey-hash script. scriptHex := "76a914128004ff2fcaf13b2b91eb654b1dc2b674f7ec6188ac" script, err := hex.DecodeString(scriptHex) if err != nil { fmt.Println(err) return } // Extract and print details from the script. scriptClass, addresses, reqSigs, err := txscript.ExtractPkScriptAddrs( script, &chaincfg.MainNetParams) if err != nil { fmt.Println(err) return } fmt.Println("Script Class:", scriptClass) fmt.Println("Addresses:", addresses) fmt.Println("Required Signatures:", reqSigs) // Output: // Script Class: pubkeyhash // Addresses: [12gpXQVcCL2qhTNQgyLVdCFG2Qs2px98nV] // Required Signatures: 1 }
// signMultiSigUTXO signs the P2SH UTXO with the given index by constructing a // script containing all given signatures plus the redeem (multi-sig) script. The // redeem script is obtained by looking up the address of the given P2SH pkScript // on the address manager. // The order of the signatures must match that of the public keys in the multi-sig // script as OP_CHECKMULTISIG expects that. // This function must be called with the manager unlocked. func signMultiSigUTXO(mgr *waddrmgr.Manager, tx *wire.MsgTx, idx int, pkScript []byte, sigs []RawSig) error { class, addresses, _, err := txscript.ExtractPkScriptAddrs(pkScript, mgr.ChainParams()) if err != nil { return newError(ErrTxSigning, "unparseable pkScript", err) } if class != txscript.ScriptHashTy { return newError(ErrTxSigning, fmt.Sprintf("pkScript is not P2SH: %s", class), nil) } redeemScript, err := getRedeemScript(mgr, addresses[0].(*btcutil.AddressScriptHash)) if err != nil { return newError(ErrTxSigning, "unable to retrieve redeem script", err) } class, _, nRequired, err := txscript.ExtractPkScriptAddrs(redeemScript, mgr.ChainParams()) if err != nil { return newError(ErrTxSigning, "unparseable redeem script", err) } if class != txscript.MultiSigTy { return newError(ErrTxSigning, fmt.Sprintf("redeem script is not multi-sig: %v", class), nil) } if len(sigs) < nRequired { errStr := fmt.Sprintf("not enough signatures; need %d but got only %d", nRequired, len(sigs)) return newError(ErrTxSigning, errStr, nil) } // Construct the unlocking script. // Start with an OP_0 because of the bug in bitcoind, then add nRequired signatures. unlockingScript := txscript.NewScriptBuilder().AddOp(txscript.OP_FALSE) for _, sig := range sigs[:nRequired] { unlockingScript.AddData(sig) } // Combine the redeem script and the unlocking script to get the actual signature script. sigScript := unlockingScript.AddData(redeemScript) script, err := sigScript.Script() if err != nil { return newError(ErrTxSigning, "error building sigscript", err) } tx.TxIn[idx].SignatureScript = script if err := validateSigScript(tx, idx, pkScript); err != nil { return err } return nil }
//this is a debug func func get_tx_info(tx *wire.MsgTx, block_index uint64) (source, destination string, btc_amount, fee uint64, data []string) { var bFound bool = false for _, value := range tx.TxOut { nettype := &chaincfg.MainNetParams if conf.MainNet { nettype = &chaincfg.MainNetParams } else { nettype = &chaincfg.RegressionNetParams } _, Address, _, _ := txscript.ExtractPkScriptAddrs(value.PkScript, nettype) if len(Address) != 0 { if Address[0].String() == conf.WISHINGWALLADDRESS { bFound = true continue } } if bFound == true { tempasm, _ := txscript.DisasmString(value.PkScript) message := strings.Split(tempasm, " ") merge := message[1] + message[2] data = append(data, merge) } } if bFound == true { destination = conf.WISHINGWALLADDRESS } else { var temp []string return "", "", 0, 0, temp } //get source address if tx.TxIn[0].PreviousOutPoint.Index == 0 { source = string(block_index) } else { SourceTx, _ := bitcoinchain.GetRawTransaction(tx.TxIn[0].PreviousOutPoint.Hash.String()) if SourceTx == nil { source = "Unkonw" } else { for _, prevalue := range SourceTx.Vout { if prevalue.N == tx.TxIn[0].PreviousOutPoint.Index { source = prevalue.ScriptPubKey.Addresses[0] } } } } return source, destination, 0, 0, data }
func (w *Wallet) findEligibleOutputs(account uint32, minconf int32, bs *waddrmgr.BlockStamp) ([]wtxmgr.Credit, error) { unspent, err := w.TxStore.UnspentOutputs() if err != nil { return nil, err } // TODO: Eventually all of these filters (except perhaps output locking) // should be handled by the call to UnspentOutputs (or similar). // Because one of these filters requires matching the output script to // the desired account, this change depends on making wtxmgr a waddrmgr // dependancy and requesting unspent outputs for a single account. eligible := make([]wtxmgr.Credit, 0, len(unspent)) for i := range unspent { output := &unspent[i] // Only include this output if it meets the required number of // confirmations. Coinbase transactions must have have reached // maturity before their outputs may be spent. if !confirmed(minconf, output.Height, bs.Height) { continue } if output.FromCoinBase { const target = blockchain.CoinbaseMaturity if !confirmed(target, output.Height, bs.Height) { continue } } // Locked unspent outputs are skipped. if w.LockedOutpoint(output.OutPoint) { continue } // 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). class, addrs, _, err := txscript.ExtractPkScriptAddrs( output.PkScript, w.chainParams) if err != nil || class != txscript.PubKeyHashTy { continue } // Only include the output if it is associated with the passed // account. There should only be one address since this is a // P2PKH script. addrAcct, err := w.Manager.AddrAccount(addrs[0]) if err != nil || addrAcct != account { continue } eligible = append(eligible, *output) } return eligible, nil }
func (w *Wallet) findEligibleOutputs(account uint32, minconf int32, bs *waddrmgr.BlockStamp) ([]wtxmgr.Credit, error) { unspent, err := w.TxStore.UnspentOutputs() if err != nil { return nil, err } // TODO: Eventually all of these filters (except perhaps output locking) // should be handled by the call to UnspentOutputs (or similar). // Because one of these filters requires matching the output script to // the desired account, this change depends on making wtxmgr a waddrmgr // dependancy and requesting unspent outputs for a single account. eligible := make([]wtxmgr.Credit, 0, len(unspent)) for i := range unspent { output := &unspent[i] // Only include this output if it meets the required number of // confirmations. Coinbase transactions must have have reached // maturity before their outputs may be spent. if !confirmed(minconf, output.Height, bs.Height) { continue } if output.FromCoinBase { const target = blockchain.CoinbaseMaturity if !confirmed(target, output.Height, bs.Height) { continue } } // Locked unspent outputs are skipped. if w.LockedOutpoint(output.OutPoint) { continue } // Only include the output if it is associated with the passed // account. // // TODO: Handle multisig outputs by determining if enough of the // addresses are controlled. _, addrs, _, err := txscript.ExtractPkScriptAddrs( output.PkScript, w.chainParams) if err != nil || len(addrs) != 1 { continue } addrAcct, err := w.Manager.AddrAccount(addrs[0]) if err != nil || addrAcct != account { continue } eligible = append(eligible, *output) } return eligible, nil }
func NewOutputRecord(txout *wire.TxOut) *OutputRecord { class, addrs, sigs, _ := txscript.ExtractPkScriptAddrs(txout.PkScript, &chaincfg.MainNetParams) record := &OutputRecord{ value: txout.Value, class: uint8(class), sigs: uint8(sigs), addrs: addrs, } return record }
// removeScriptFromAddrIndex dissociates the address encoded by the // passed pkScript from the passed tx in our address based tx index. // // This function MUST be called with the mempool lock held (for writes). func (mp *txMemPool) removeScriptFromAddrIndex(pkScript []byte, tx *btcutil.Tx) error { _, addresses, _, err := txscript.ExtractPkScriptAddrs(pkScript, activeNetParams.Params) if err != nil { txmpLog.Errorf("Unable to extract encoded addresses from script "+ "for addrindex (addrindex): %v", err) return err } for _, addr := range addresses { delete(mp.addrindex[addr.EncodeAddress()], *tx.Sha()) } return nil }
func lookupOutputChain(w *Wallet, details *wtxmgr.TxDetails, cred wtxmgr.CreditRecord) (account uint32, internal bool) { output := details.MsgTx.TxOut[cred.Index] _, addrs, _, err := txscript.ExtractPkScriptAddrs(output.PkScript, w.chainParams) var ma waddrmgr.ManagedAddress if err == nil && len(addrs) > 0 { ma, err = w.Manager.Address(addrs[0]) } if err != nil { log.Errorf("Cannot fetch account for wallet output: %v", err) } else { account = ma.Account() internal = ma.Internal() } return }
// indexScriptByAddress alters our address index by indexing the payment address // encoded by the passed scriptPubKey to the passed transaction. // // This function MUST be called with the mempool lock held (for writes). func (mp *txMemPool) indexScriptAddressToTx(pkScript []byte, tx *btcutil.Tx) error { _, addresses, _, err := txscript.ExtractPkScriptAddrs(pkScript, activeNetParams.Params) if err != nil { txmpLog.Errorf("Unable to extract encoded addresses from script "+ "for addrindex: %v", err) return err } for _, addr := range addresses { if mp.addrindex[addr.EncodeAddress()] == nil { mp.addrindex[addr.EncodeAddress()] = make(map[wire.ShaHash]struct{}) } mp.addrindex[addr.EncodeAddress()][*tx.Sha()] = struct{}{} } return nil }
// ListAddressTransactions returns a slice of objects with details about // recorded transactions to or from any address belonging to a set. This is // intended to be used for listaddresstransactions RPC replies. func (w *Wallet) ListAddressTransactions(pkHashes map[string]struct{}) ( []btcjson.ListTransactionsResult, error) { txList := []btcjson.ListTransactionsResult{} // Get current block. The block height used for calculating // the number of tx confirmations. syncBlock := w.Manager.SyncedTo() err := w.TxStore.RangeTransactions(0, -1, func(details []wtxmgr.TxDetails) (bool, error) { loopDetails: for i := range details { detail := &details[i] for _, cred := range detail.Credits { pkScript := detail.MsgTx.TxOut[cred.Index].PkScript _, addrs, _, err := txscript.ExtractPkScriptAddrs( pkScript, w.chainParams) if err != nil || len(addrs) != 1 { continue } apkh, ok := addrs[0].(*btcutil.AddressPubKeyHash) if !ok { continue } _, ok = pkHashes[string(apkh.ScriptAddress())] if !ok { continue } jsonResults := ListTransactions(detail, syncBlock.Height, w.chainParams) if err != nil { return false, err } txList = append(txList, jsonResults...) continue loopDetails } } return false, nil }) return txList, err }
// signMsgTx sets the SignatureScript for every item in msgtx.TxIn. // It must be called every time a msgtx is changed. // Only P2PKH outputs are supported at this point. func signMsgTx(msgtx *wire.MsgTx, prevOutputs []wtxmgr.Credit, mgr *waddrmgr.Manager, chainParams *chaincfg.Params) error { if len(prevOutputs) != len(msgtx.TxIn) { return fmt.Errorf( "Number of prevOutputs (%d) does not match number of tx inputs (%d)", len(prevOutputs), len(msgtx.TxIn)) } for i, output := range prevOutputs { // Errors don't matter here, as we only consider the // case where len(addrs) == 1. _, addrs, _, _ := txscript.ExtractPkScriptAddrs(output.PkScript, chainParams) if len(addrs) != 1 { continue } apkh, ok := addrs[0].(*btcutil.AddressPubKeyHash) if !ok { return ErrUnsupportedTransactionType } ai, err := mgr.Address(apkh) if err != nil { return fmt.Errorf("cannot get address info: %v", err) } pka := ai.(waddrmgr.ManagedPubKeyAddress) privkey, err := pka.PrivKey() if err != nil { return fmt.Errorf("cannot get private key: %v", err) } sigscript, err := txscript.SignatureScript(msgtx, i, output.PkScript, txscript.SigHashAll, privkey, ai.Compressed()) if err != nil { return fmt.Errorf("cannot create sigscript: %s", err) } msgtx.TxIn[i].SignatureScript = sigscript } return nil }
// addTestTx adds an output spendable by our test wallet, marked as included in // 'block'. func addTestTx(w *LightningWallet, rec *wtxmgr.TxRecord, block *wtxmgr.BlockMeta) error { err := w.TxStore.InsertTx(rec, block) if err != nil { return err } // Check every output to determine whether it is controlled by a wallet // key. If so, mark the output as a credit. for i, output := range rec.MsgTx.TxOut { _, addrs, _, err := txscript.ExtractPkScriptAddrs(output.PkScript, ActiveNetParams) if err != nil { // Non-standard outputs are skipped. continue } for _, addr := range addrs { ma, err := w.Manager.Address(addr) if err == nil { err = w.TxStore.AddCredit(rec, block, uint32(i), ma.Internal()) if err != nil { return err } err = w.Manager.MarkUsed(addr) if err != nil { return err } continue } // Missing addresses are skipped. Other errors should // be propagated. if !waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { return err } } } return nil }
func totalBalances(w *Wallet, m map[uint32]btcutil.Amount) error { unspent, err := w.TxStore.UnspentOutputs() if err != nil { return err } for i := range unspent { output := &unspent[i] var outputAcct uint32 _, addrs, _, err := txscript.ExtractPkScriptAddrs( output.PkScript, w.chainParams) if err == nil && len(addrs) > 0 { outputAcct, err = w.Manager.AddrAccount(addrs[0]) } if err == nil { _, ok := m[outputAcct] if ok { m[outputAcct] += output.Amount } } } return nil }
// TotalReceivedForAddr iterates through a wallet's transaction history, // returning the total amount of bitcoins received for a single wallet // address. func (w *Wallet) TotalReceivedForAddr(addr btcutil.Address, minConf int32) (btcutil.Amount, error) { syncBlock := w.Manager.SyncedTo() var ( addrStr = addr.EncodeAddress() amount btcutil.Amount stopHeight int32 ) if minConf > 0 { stopHeight = syncBlock.Height - minConf + 1 } else { stopHeight = -1 } err := w.TxStore.RangeTransactions(0, stopHeight, func(details []wtxmgr.TxDetails) (bool, error) { for i := range details { detail := &details[i] for _, cred := range detail.Credits { pkScript := detail.MsgTx.TxOut[cred.Index].PkScript _, addrs, _, err := txscript.ExtractPkScriptAddrs( pkScript, w.chainParams) // An error creating addresses from the output script only // indicates a non-standard script, so ignore this credit. if err != nil { continue } for _, a := range addrs { if addrStr == a.EncodeAddress() { amount += cred.Amount break } } } } return false, nil }) return amount, err }
func NewTxOut(txoutraw []byte) (txout *TxOut, offset int) { txout = new(TxOut) txout.Value = binary.LittleEndian.Uint64(txoutraw[0:8]) offset = 8 pkscript, pkscriptsize := DecodeVariableLengthInteger(txoutraw[offset:]) offset += pkscriptsize txout.Pkscript = txoutraw[offset : offset+pkscript] offset += pkscript _, addrhash, _, err := txscript.ExtractPkScriptAddrs(txout.Pkscript, &chaincfg.MainNetParams) if err != nil { return } if len(addrhash) != 0 { txout.Addr = addrhash[0].EncodeAddress() } else { txout.Addr = "" } return }
// CalculateAccountBalance sums the amounts of all unspent transaction // outputs to the given account of a wallet and returns the balance. func (w *Wallet) CalculateAccountBalance(account uint32, confirms int32) (btcutil.Amount, error) { var bal btcutil.Amount // Get current block. The block height used for calculating // the number of tx confirmations. syncBlock := w.Manager.SyncedTo() unspent, err := w.TxStore.UnspentOutputs() if err != nil { return 0, err } for i := range unspent { output := &unspent[i] if !confirmed(confirms, output.Height, syncBlock.Height) { continue } if output.FromCoinBase { const target = blockchain.CoinbaseMaturity if !confirmed(target, output.Height, syncBlock.Height) { continue } } var outputAcct uint32 _, addrs, _, err := txscript.ExtractPkScriptAddrs( output.PkScript, w.chainParams) if err == nil && len(addrs) > 0 { outputAcct, err = w.Manager.AddrAccount(addrs[0]) } if err == nil && outputAcct == account { bal += output.Amount } } return bal, nil }
// groupCreditsByAddr converts a slice of credits to a map from the string // representation of an encoded address to the unspent outputs associated with // that address. func groupCreditsByAddr(credits []wtxmgr.Credit, chainParams *chaincfg.Params) ( map[string][]wtxmgr.Credit, error) { addrMap := make(map[string][]wtxmgr.Credit) for _, c := range credits { _, addrs, _, err := txscript.ExtractPkScriptAddrs(c.PkScript, chainParams) if err != nil { return nil, newError(ErrInputSelection, "failed to obtain input address", err) } // As our credits are all P2SH we should never have more than one // address per credit, so let's error out if that assumption is // violated. if len(addrs) != 1 { return nil, newError(ErrInputSelection, "input doesn't have exactly one address", nil) } encAddr := addrs[0].EncodeAddress() if v, ok := addrMap[encAddr]; ok { addrMap[encAddr] = append(v, c) } else { addrMap[encAddr] = []wtxmgr.Credit{c} } } return addrMap, nil }
// TotalReceivedForAccount iterates through a wallet's transaction history, // returning the total amount of bitcoins received for a single wallet // account. func (w *Wallet) TotalReceivedForAccount(account uint32, minConf int32) (btcutil.Amount, int32, error) { syncBlock := w.Manager.SyncedTo() var ( amount btcutil.Amount lastConf int32 // Confs of the last matching transaction. stopHeight int32 ) if minConf > 0 { stopHeight = syncBlock.Height - minConf + 1 } else { stopHeight = -1 } err := w.TxStore.RangeTransactions(0, stopHeight, func(details []wtxmgr.TxDetails) (bool, error) { for i := range details { detail := &details[i] for _, cred := range detail.Credits { pkScript := detail.MsgTx.TxOut[cred.Index].PkScript var outputAcct uint32 _, addrs, _, err := txscript.ExtractPkScriptAddrs( pkScript, w.chainParams) if err == nil && len(addrs) > 0 { outputAcct, err = w.Manager.AddrAccount(addrs[0]) } if err == nil && outputAcct == account { amount += cred.Amount lastConf = confirms(detail.Block.Height, syncBlock.Height) } } } return false, nil }) return amount, lastConf, err }
// ListUnspent returns a slice of objects representing the unspent wallet // transactions fitting the given criteria. The confirmations will be more than // minconf, less than maxconf and if addresses is populated only the addresses // contained within it will be considered. If we know nothing about a // transaction an empty array will be returned. func (w *Wallet) ListUnspent(minconf, maxconf int32, addresses map[string]struct{}) ([]*btcjson.ListUnspentResult, error) { syncBlock := w.Manager.SyncedTo() filter := len(addresses) != 0 unspent, err := w.TxStore.UnspentOutputs() if err != nil { return nil, err } sort.Sort(sort.Reverse(creditSlice(unspent))) defaultAccountName, err := w.Manager.AccountName(waddrmgr.DefaultAccountNum) if err != nil { return nil, err } results := make([]*btcjson.ListUnspentResult, 0, len(unspent)) for i := range unspent { output := &unspent[i] // Outputs with fewer confirmations than the minimum or more // confs than the maximum are excluded. confs := confirms(output.Height, syncBlock.Height) if confs < minconf || confs > maxconf { continue } // Only mature coinbase outputs are included. if output.FromCoinBase { const target = blockchain.CoinbaseMaturity if !confirmed(target, output.Height, syncBlock.Height) { continue } } // Exclude locked outputs from the result set. if w.LockedOutpoint(output.OutPoint) { continue } // Lookup the associated account for the output. Use the // default account name in case there is no associated account // for some reason, although this should never happen. // // This will be unnecessary once transactions and outputs are // grouped under the associated account in the db. acctName := defaultAccountName sc, addrs, _, err := txscript.ExtractPkScriptAddrs( output.PkScript, w.chainParams) if err != nil { continue } if len(addrs) > 0 { acct, err := w.Manager.AddrAccount(addrs[0]) if err == nil { s, err := w.Manager.AccountName(acct) if err == nil { acctName = s } } } if filter { for _, addr := range addrs { _, ok := addresses[addr.EncodeAddress()] if ok { goto include } } continue } include: // At the moment watch-only addresses are not supported, so all // recorded outputs that are not multisig are "spendable". // Multisig outputs are only "spendable" if all keys are // controlled by this wallet. // // TODO: Each case will need updates when watch-only addrs // is added. For P2PK, P2PKH, and P2SH, the address must be // looked up and not be watching-only. For multisig, all // pubkeys must belong to the manager with the associated // private key (currently it only checks whether the pubkey // exists, since the private key is required at the moment). var spendable bool scSwitch: switch sc { case txscript.PubKeyHashTy: spendable = true case txscript.PubKeyTy: spendable = true case txscript.ScriptHashTy: spendable = true case txscript.MultiSigTy: for _, a := range addrs { _, err := w.Manager.Address(a) if err == nil { continue } if waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { break scSwitch } return nil, err } spendable = true } result := &btcjson.ListUnspentResult{ TxID: output.OutPoint.Hash.String(), Vout: output.OutPoint.Index, Account: acctName, ScriptPubKey: hex.EncodeToString(output.PkScript), Amount: output.Amount.ToBTC(), Confirmations: int64(confs), Spendable: spendable, } // BUG: this should be a JSON array so that all // addresses can be included, or removed (and the // caller extracts addresses from the pkScript). if len(addrs) > 0 { result.Address = addrs[0].EncodeAddress() } results = append(results, result) } return results, nil }
// TestExtractPkScriptAddrs ensures that extracting the type, addresses, and // number of required signatures from PkScripts works as intended. func TestExtractPkScriptAddrs(t *testing.T) { tests := []struct { name string script []byte addrs []btcutil.Address reqSigs int class txscript.ScriptClass }{ { name: "standard p2pk with compressed pubkey (0x02)", script: decodeHex("2102192d74d0cb94344c9569c2e7790157" + "3d8d7903c3ebec3a957724895dca52c6b4ac"), addrs: []btcutil.Address{ newAddressPubKey(decodeHex("02192d74d0cb94344" + "c9569c2e77901573d8d7903c3ebec3a95772" + "4895dca52c6b4")), }, reqSigs: 1, class: txscript.PubKeyTy, }, { name: "standard p2pk with uncompressed pubkey (0x04)", script: decodeHex("410411db93e1dcdb8a016b49840f8c53bc" + "1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb" + "84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643" + "f656b412a3ac"), addrs: []btcutil.Address{ newAddressPubKey(decodeHex("0411db93e1dcdb8a0" + "16b49840f8c53bc1eb68a382e97b1482ecad" + "7b148a6909a5cb2e0eaddfb84ccf9744464f" + "82e160bfa9b8b64f9d4c03f999b8643f656b" + "412a3")), }, reqSigs: 1, class: txscript.PubKeyTy, }, { name: "standard p2pk with hybrid pubkey (0x06)", script: decodeHex("4106192d74d0cb94344c9569c2e7790157" + "3d8d7903c3ebec3a957724895dca52c6b40d45264838" + "c0bd96852662ce6a847b197376830160c6d2eb5e6a4c" + "44d33f453eac"), addrs: []btcutil.Address{ newAddressPubKey(decodeHex("06192d74d0cb94344" + "c9569c2e77901573d8d7903c3ebec3a95772" + "4895dca52c6b40d45264838c0bd96852662c" + "e6a847b197376830160c6d2eb5e6a4c44d33" + "f453e")), }, reqSigs: 1, class: txscript.PubKeyTy, }, { name: "standard p2pk with compressed pubkey (0x03)", script: decodeHex("2103b0bd634234abbb1ba1e986e884185c" + "61cf43e001f9137f23c2c409273eb16e65ac"), addrs: []btcutil.Address{ newAddressPubKey(decodeHex("03b0bd634234abbb1" + "ba1e986e884185c61cf43e001f9137f23c2c" + "409273eb16e65")), }, reqSigs: 1, class: txscript.PubKeyTy, }, { name: "2nd standard p2pk with uncompressed pubkey (0x04)", script: decodeHex("4104b0bd634234abbb1ba1e986e884185c" + "61cf43e001f9137f23c2c409273eb16e6537a576782e" + "ba668a7ef8bd3b3cfb1edb7117ab65129b8a2e681f3c" + "1e0908ef7bac"), addrs: []btcutil.Address{ newAddressPubKey(decodeHex("04b0bd634234abbb1" + "ba1e986e884185c61cf43e001f9137f23c2c" + "409273eb16e6537a576782eba668a7ef8bd3" + "b3cfb1edb7117ab65129b8a2e681f3c1e090" + "8ef7b")), }, reqSigs: 1, class: txscript.PubKeyTy, }, { name: "standard p2pk with hybrid pubkey (0x07)", script: decodeHex("4107b0bd634234abbb1ba1e986e884185c" + "61cf43e001f9137f23c2c409273eb16e6537a576782e" + "ba668a7ef8bd3b3cfb1edb7117ab65129b8a2e681f3c" + "1e0908ef7bac"), addrs: []btcutil.Address{ newAddressPubKey(decodeHex("07b0bd634234abbb1" + "ba1e986e884185c61cf43e001f9137f23c2c" + "409273eb16e6537a576782eba668a7ef8bd3" + "b3cfb1edb7117ab65129b8a2e681f3c1e090" + "8ef7b")), }, reqSigs: 1, class: txscript.PubKeyTy, }, { name: "standard p2pkh", script: decodeHex("76a914ad06dd6ddee55cbca9a9e3713bd7" + "587509a3056488ac"), addrs: []btcutil.Address{ newAddressPubKeyHash(decodeHex("ad06dd6ddee55" + "cbca9a9e3713bd7587509a30564")), }, reqSigs: 1, class: txscript.PubKeyHashTy, }, { name: "standard p2sh", script: decodeHex("a91463bcc565f9e68ee0189dd5cc67f1b0" + "e5f02f45cb87"), addrs: []btcutil.Address{ newAddressScriptHash(decodeHex("63bcc565f9e68" + "ee0189dd5cc67f1b0e5f02f45cb")), }, reqSigs: 1, class: txscript.ScriptHashTy, }, // from real tx 60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1, vout 0 { name: "standard 1 of 2 multisig", script: decodeHex("514104cc71eb30d653c0c3163990c47b97" + "6f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473" + "e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11" + "fcdd0d348ac4410461cbdcc5409fb4b4d42b51d33381" + "354d80e550078cb532a34bfa2fcfdeb7d76519aecc62" + "770f5b0e4ef8551946d8a540911abe3e7854a26f39f5" + "8b25c15342af52ae"), addrs: []btcutil.Address{ newAddressPubKey(decodeHex("04cc71eb30d653c0c" + "3163990c47b976f3fb3f37cccdcbedb169a1" + "dfef58bbfbfaff7d8a473e7e2e6d317b87ba" + "fe8bde97e3cf8f065dec022b51d11fcdd0d3" + "48ac4")), newAddressPubKey(decodeHex("0461cbdcc5409fb4b" + "4d42b51d33381354d80e550078cb532a34bf" + "a2fcfdeb7d76519aecc62770f5b0e4ef8551" + "946d8a540911abe3e7854a26f39f58b25c15" + "342af")), }, reqSigs: 1, class: txscript.MultiSigTy, }, // from real tx d646f82bd5fbdb94a36872ce460f97662b80c3050ad3209bef9d1e398ea277ab, vin 1 { name: "standard 2 of 3 multisig", script: decodeHex("524104cb9c3c222c5f7a7d3b9bd152f363" + "a0b6d54c9eb312c4d4f9af1e8551b6c421a6a4ab0e29" + "105f24de20ff463c1c91fcf3bf662cdde4783d4799f7" + "87cb7c08869b4104ccc588420deeebea22a7e900cc8b" + "68620d2212c374604e3487ca08f1ff3ae12bdc639514" + "d0ec8612a2d3c519f084d9a00cbbe3b53d071e9b09e7" + "1e610b036aa24104ab47ad1939edcb3db65f7fedea62" + "bbf781c5410d3f22a7a3a56ffefb2238af8627363bdf" + "2ed97c1f89784a1aecdb43384f11d2acc64443c7fc29" + "9cef0400421a53ae"), addrs: []btcutil.Address{ newAddressPubKey(decodeHex("04cb9c3c222c5f7a7" + "d3b9bd152f363a0b6d54c9eb312c4d4f9af1" + "e8551b6c421a6a4ab0e29105f24de20ff463" + "c1c91fcf3bf662cdde4783d4799f787cb7c0" + "8869b")), newAddressPubKey(decodeHex("04ccc588420deeebe" + "a22a7e900cc8b68620d2212c374604e3487c" + "a08f1ff3ae12bdc639514d0ec8612a2d3c51" + "9f084d9a00cbbe3b53d071e9b09e71e610b0" + "36aa2")), newAddressPubKey(decodeHex("04ab47ad1939edcb3" + "db65f7fedea62bbf781c5410d3f22a7a3a56" + "ffefb2238af8627363bdf2ed97c1f89784a1" + "aecdb43384f11d2acc64443c7fc299cef040" + "0421a")), }, reqSigs: 2, class: txscript.MultiSigTy, }, // The below are nonstandard script due to things such as // invalid pubkeys, failure to parse, and not being of a // standard form. { name: "p2pk with uncompressed pk missing OP_CHECKSIG", script: decodeHex("410411db93e1dcdb8a016b49840f8c53bc" + "1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb" + "84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643" + "f656b412a3"), addrs: nil, reqSigs: 0, class: txscript.NonStandardTy, }, { name: "valid signature from a sigscript - no addresses", script: decodeHex("47304402204e45e16932b8af514961a1d3" + "a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220" + "181522ec8eca07de4860a4acdd12909d831cc56cbbac" + "4622082221a8768d1d0901"), addrs: nil, reqSigs: 0, class: txscript.NonStandardTy, }, // Note the technically the pubkey is the second item on the // stack, but since the address extraction intentionally only // works with standard PkScripts, this should not return any // addresses. { name: "valid sigscript to reedeem p2pk - no addresses", script: decodeHex("493046022100ddc69738bf2336318e4e04" + "1a5a77f305da87428ab1606f023260017854350ddc02" + "2100817af09d2eec36862d16009852b7e3a0f6dd7659" + "8290b7834e1453660367e07a014104cd4240c198e125" + "23b6f9cb9f5bed06de1ba37e96a1bbd13745fcf9d11c" + "25b1dff9a519675d198804ba9962d3eca2d5937d58e5" + "a75a71042d40388a4d307f887d"), addrs: nil, reqSigs: 0, class: txscript.NonStandardTy, }, // from real tx 691dd277dc0e90a462a3d652a1171686de49cf19067cd33c7df0392833fb986a, vout 0 // invalid public keys { name: "1 of 3 multisig with invalid pubkeys", script: decodeHex("51411c2200007353455857696b696c6561" + "6b73204361626c6567617465204261636b75700a0a63" + "61626c65676174652d3230313031323034313831312e" + "377a0a0a446f41776e6c6f61642074686520666f6c6c" + "6f77696e67207472616e73616374696f6e7320776974" + "68205361746f736869204e616b616d6f746f27732064" + "6f776e6c6f61416420746f6f6c2077686963680a6361" + "6e20626520666f756e6420696e207472616e73616374" + "696f6e20366335336364393837313139656637393764" + "35616463636453ae"), addrs: []btcutil.Address{}, reqSigs: 1, class: txscript.MultiSigTy, }, // from real tx: 691dd277dc0e90a462a3d652a1171686de49cf19067cd33c7df0392833fb986a, vout 44 // invalid public keys { name: "1 of 3 multisig with invalid pubkeys 2", script: decodeHex("5141346333656332353963373464616365" + "36666430383862343463656638630a63363662633139" + "39366338623934613338313162333635363138666531" + "65396231623541366361636365393933613339383861" + "34363966636336643664616266640a32363633636661" + "39636634633033633630396335393363336539316665" + "64653730323921313233646434326432353633396433" + "38613663663530616234636434340a00000053ae"), addrs: []btcutil.Address{}, reqSigs: 1, class: txscript.MultiSigTy, }, { name: "empty script", script: []byte{}, addrs: nil, reqSigs: 0, class: txscript.NonStandardTy, }, { name: "script that does not parse", script: []byte{txscript.OP_DATA_45}, addrs: nil, reqSigs: 0, class: txscript.NonStandardTy, }, } t.Logf("Running %d tests.", len(tests)) for i, test := range tests { class, addrs, reqSigs, err := txscript.ExtractPkScriptAddrs( test.script, &chaincfg.MainNetParams) if err != nil { } if !reflect.DeepEqual(addrs, test.addrs) { t.Errorf("ExtractPkScriptAddrs #%d (%s) unexpected "+ "addresses\ngot %v\nwant %v", i, test.name, addrs, test.addrs) continue } if reqSigs != test.reqSigs { t.Errorf("ExtractPkScriptAddrs #%d (%s) unexpected "+ "number of required signatures - got %d, "+ "want %d", i, test.name, reqSigs, test.reqSigs) continue } if class != test.class { t.Errorf("ExtractPkScriptAddrs #%d (%s) unexpected "+ "script type - got %s, want %s", i, test.name, class, test.class) continue } } }
func makeTxSummary(w *Wallet, details *wtxmgr.TxDetails) TransactionSummary { serializedTx := details.SerializedTx if serializedTx == nil { var buf bytes.Buffer err := details.MsgTx.Serialize(&buf) if err != nil { log.Errorf("Transaction serialization: %v", err) } serializedTx = buf.Bytes() } var fee btcutil.Amount if len(details.Debits) == len(details.MsgTx.TxIn) { for _, deb := range details.Debits { fee += deb.Amount } for _, txOut := range details.MsgTx.TxOut { fee -= btcutil.Amount(txOut.Value) } } var inputs []TransactionSummaryInput if len(details.Debits) != 0 { inputs = make([]TransactionSummaryInput, len(details.Debits)) for i, d := range details.Debits { inputs[i] = TransactionSummaryInput{ Index: d.Index, PreviousAccount: lookupInputAccount(w, details, d), PreviousAmount: d.Amount, } } } outputs := make([]TransactionSummaryOutput, 0, len(details.MsgTx.TxOut)) var credIndex int for i, txOut := range details.MsgTx.TxOut { mine := len(details.Credits) > credIndex && details.Credits[credIndex].Index == uint32(i) output := TransactionSummaryOutput{ Index: uint32(i), Amount: btcutil.Amount(txOut.Value), Mine: mine, } if mine { acct, internal := lookupOutputChain(w, details, details.Credits[credIndex]) output.Account = acct output.Internal = internal credIndex++ } else { _, addresses, _, err := txscript.ExtractPkScriptAddrs(txOut.PkScript, w.chainParams) if err == nil { output.Addresses = addresses } } outputs = append(outputs, output) } return TransactionSummary{ Hash: &details.Hash, Transaction: serializedTx, MyInputs: inputs, Outputs: outputs, Fee: fee, Timestamp: details.Received.Unix(), } }
// ListTransactions creates a object that may be marshalled to a response result // for a listtransactions RPC. // // TODO: This should be moved out of this package into the main package's // rpcserver.go, along with everything that requires this. func ListTransactions(details *wtxmgr.TxDetails, syncHeight int32, net *chaincfg.Params) []btcjson.ListTransactionsResult { var ( blockHashStr string blockTime int64 confirmations int64 ) if details.Block.Height != -1 { blockHashStr = details.Block.Hash.String() blockTime = details.Block.Time.Unix() confirmations = int64(confirms(details.Block.Height, syncHeight)) } results := []btcjson.ListTransactionsResult{} txHashStr := details.Hash.String() received := details.Received.Unix() generated := blockchain.IsCoinBaseTx(&details.MsgTx) recvCat := RecvCategory(details, syncHeight).String() send := len(details.Debits) != 0 // Fee can only be determined if every input is a debit. var feeF64 float64 if len(details.Debits) == len(details.MsgTx.TxIn) { var debitTotal btcutil.Amount for _, deb := range details.Debits { debitTotal += deb.Amount } var outputTotal btcutil.Amount for _, output := range details.MsgTx.TxOut { outputTotal += btcutil.Amount(output.Value) } // Note: The actual fee is debitTotal - outputTotal. However, // this RPC reports negative numbers for fees, so the inverse // is calculated. feeF64 = (outputTotal - debitTotal).ToBTC() } outputs: for i, output := range details.MsgTx.TxOut { // Determine if this output is a credit, and if so, determine // its spentness. var isCredit bool var spentCredit bool for _, cred := range details.Credits { if cred.Index == uint32(i) { // Change outputs are ignored. if cred.Change { continue outputs } isCredit = true spentCredit = cred.Spent break } } var address string _, addrs, _, _ := txscript.ExtractPkScriptAddrs(output.PkScript, net) if len(addrs) == 1 { address = addrs[0].EncodeAddress() } amountF64 := btcutil.Amount(output.Value).ToBTC() result := btcjson.ListTransactionsResult{ // Fields left zeroed: // InvolvesWatchOnly // Account // BlockIndex // // Fields set below: // Category // Amount // Fee Address: address, Vout: uint32(i), Confirmations: confirmations, Generated: generated, BlockHash: blockHashStr, BlockTime: blockTime, TxID: txHashStr, WalletConflicts: []string{}, Time: received, TimeReceived: received, } // Add a received/generated/immature result if this is a credit. // If the output was spent, create a second result under the // send category with the inverse of the output amount. It is // therefore possible that a single output may be included in // the results set zero, one, or two times. // // Since credits are not saved for outputs that are not // controlled by this wallet, all non-credits from transactions // with debits are grouped under the send category. if send || spentCredit { result.Category = "send" result.Amount = -amountF64 result.Fee = &feeF64 results = append(results, result) } if isCredit { result.Category = recvCat result.Amount = amountF64 result.Fee = nil results = append(results, result) } } return results }
func (w *Wallet) addRelevantTx(rec *wtxmgr.TxRecord, block *wtxmgr.BlockMeta) error { // TODO: The transaction store and address manager need to be updated // together, but each operate under different namespaces and are changed // under new transactions. This is not error safe as we lose // transaction semantics. // // I'm unsure of the best way to solve this. Some possible solutions // and drawbacks: // // 1. Open write transactions here and pass the handle to every // waddrmr and wtxmgr method. This complicates the caller code // everywhere, however. // // 2. Move the wtxmgr namespace into the waddrmgr namespace, likely // under its own bucket. This entire function can then be moved // into the waddrmgr package, which updates the nested wtxmgr. // This removes some of separation between the components. // // 3. Use multiple wtxmgrs, one for each account, nested in the // waddrmgr namespace. This still provides some sort of logical // separation (transaction handling remains in another package, and // is simply used by waddrmgr), but may result in duplicate // transactions being saved if they are relevant to multiple // accounts. // // 4. Store wtxmgr-related details under the waddrmgr namespace, but // solve the drawback of #3 by splitting wtxmgr to save entire // transaction records globally for all accounts, with // credit/debit/balance tracking per account. Each account would // also save the relevant transaction hashes and block incidence so // the full transaction can be loaded from the waddrmgr // transactions bucket. This currently seems like the best // solution. // At the moment all notified transactions are assumed to actually be // relevant. This assumption will not hold true when SPV support is // added, but until then, simply insert the transaction because there // should either be one or more relevant inputs or outputs. err := w.TxStore.InsertTx(rec, block) if err != nil { return err } // Check every output to determine whether it is controlled by a wallet // key. If so, mark the output as a credit. for i, output := range rec.MsgTx.TxOut { _, addrs, _, err := txscript.ExtractPkScriptAddrs(output.PkScript, w.chainParams) if err != nil { // Non-standard outputs are skipped. continue } for _, addr := range addrs { ma, err := w.Manager.Address(addr) if err == nil { // TODO: Credits should be added with the // account they belong to, so wtxmgr is able to // track per-account balances. err = w.TxStore.AddCredit(rec, block, uint32(i), ma.Internal()) if err != nil { return err } err = w.Manager.MarkUsed(addr) if err != nil { return err } log.Debugf("Marked address %v used", addr) continue } // Missing addresses are skipped. Other errors should // be propagated. if !waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { return err } } } // TODO: Notify connected clients of the added transaction. bs, err := w.chainSvr.BlockStamp() if err == nil { w.notifyBalances(bs.Height) } return nil }
func (s *walletServer) FundTransaction(ctx context.Context, req *pb.FundTransactionRequest) ( *pb.FundTransactionResponse, error) { // TODO: A predicate function for selecting outputs should be created // and passed to a database view of just a particular account's utxos to // prevent reading every unspent transaction output from every account // into memory at once. syncBlock := s.wallet.Manager.SyncedTo() outputs, err := s.wallet.TxStore.UnspentOutputs() if err != nil { return nil, translateError(err) } selectedOutputs := make([]*pb.FundTransactionResponse_PreviousOutput, 0, len(outputs)) var totalAmount btcutil.Amount for i := range outputs { output := &outputs[i] if !confirmed(req.RequiredConfirmations, output.Height, syncBlock.Height) { continue } target := int32(s.wallet.ChainParams().CoinbaseMaturity) if !req.IncludeImmatureCoinbases && output.FromCoinBase && !confirmed(target, output.Height, syncBlock.Height) { continue } _, addrs, _, err := txscript.ExtractPkScriptAddrs( output.PkScript, s.wallet.ChainParams()) if err != nil || len(addrs) == 0 { // Cannot determine which account this belongs to // without a valid address. Fix this by saving // outputs per account (per-account wtxmgr). continue } outputAcct, err := s.wallet.Manager.AddrAccount(addrs[0]) if err != nil { return nil, translateError(err) } if outputAcct != req.Account { continue } selectedOutputs = append(selectedOutputs, &pb.FundTransactionResponse_PreviousOutput{ TransactionHash: output.OutPoint.Hash[:], OutputIndex: output.Index, Amount: int64(output.Amount), PkScript: output.PkScript, ReceiveTime: output.Received.Unix(), FromCoinbase: output.FromCoinBase, }) totalAmount += output.Amount if req.TargetAmount != 0 && totalAmount > btcutil.Amount(req.TargetAmount) { break } } var changeScript []byte if req.IncludeChangeScript && totalAmount > btcutil.Amount(req.TargetAmount) { changeAddr, err := s.wallet.NewChangeAddress(req.Account) if err != nil { return nil, translateError(err) } changeScript, err = txscript.PayToAddrScript(changeAddr) if err != nil { return nil, translateError(err) } } return &pb.FundTransactionResponse{ SelectedOutputs: selectedOutputs, TotalAmount: int64(totalAmount), ChangePkScript: changeScript, }, nil }
// handleFundingCounterPartyFunds processes the second workflow step for the // lifetime of a channel reservation. Upon completion, the reservation will // carry a completed funding transaction (minus the counterparty's input // signatures), both versions of the commitment transaction, and our signature // for their version of the commitment transaction. func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) { l.limboMtx.Lock() pendingReservation, ok := l.fundingLimbo[req.pendingFundingID] l.limboMtx.Unlock() if !ok { req.err <- fmt.Errorf("attempted to update non-existant funding state") return } // Grab the mutex on the ChannelReservation to ensure thead-safety pendingReservation.Lock() defer pendingReservation.Unlock() // Create a blank, fresh transaction. Soon to be a complete funding // transaction which will allow opening a lightning channel. pendingReservation.partialState.FundingTx = wire.NewMsgTx() fundingTx := pendingReservation.partialState.FundingTx // Some temporary variables to cut down on the resolution verbosity. pendingReservation.theirContribution = req.contribution theirContribution := req.contribution ourContribution := pendingReservation.ourContribution // First, add all multi-party inputs to the transaction // TODO(roasbeef); handle case that tx doesn't exist, fake input // TODO(roasbeef): validate SPV proof from other side if in SPV mode. // * actually, pure SPV would need fraud proofs right? must prove input // is unspent // * or, something like getutxo? for _, ourInput := range ourContribution.Inputs { fundingTx.AddTxIn(ourInput) } for _, theirInput := range theirContribution.Inputs { fundingTx.AddTxIn(theirInput) } // Next, add all multi-party outputs to the transaction. This includes // change outputs for both side. for _, ourChangeOutput := range ourContribution.ChangeOutputs { fundingTx.AddTxOut(ourChangeOutput) } for _, theirChangeOutput := range theirContribution.ChangeOutputs { fundingTx.AddTxOut(theirChangeOutput) } ourKey := pendingReservation.partialState.MultiSigKey theirKey := theirContribution.MultiSigKey // Finally, add the 2-of-2 multi-sig output which will set up the lightning // channel. channelCapacity := int64(pendingReservation.partialState.Capacity) redeemScript, multiSigOut, err := fundMultiSigOut(ourKey.PubKey().SerializeCompressed(), theirKey.SerializeCompressed(), channelCapacity) if err != nil { req.err <- err return } // Register intent for notifications related to the funding output. // This'll allow us to properly track the number of confirmations the // funding tx has once it has been broadcasted. lastBlock := l.Manager.SyncedTo() scriptAddr, err := l.Manager.ImportScript(redeemScript, &lastBlock) if err != nil { req.err <- err return } if err := l.rpc.NotifyReceived([]btcutil.Address{scriptAddr.Address()}); err != nil { req.err <- err return } pendingReservation.partialState.FundingRedeemScript = redeemScript fundingTx.AddTxOut(multiSigOut) // Sort the transaction. Since both side agree to a cannonical // ordering, by sorting we no longer need to send the entire // transaction. Only signatures will be exchanged. txsort.InPlaceSort(pendingReservation.partialState.FundingTx) // Next, sign all inputs that are ours, collecting the signatures in // order of the inputs. pendingReservation.ourFundingSigs = make([][]byte, 0, len(ourContribution.Inputs)) for i, txIn := range fundingTx.TxIn { // Does the wallet know about the txin? txDetail, _ := l.TxStore.TxDetails(&txIn.PreviousOutPoint.Hash) if txDetail == nil { continue } // Is this our txin? TODO(roasbeef): assumes all inputs are P2PKH... prevIndex := txIn.PreviousOutPoint.Index prevOut := txDetail.TxRecord.MsgTx.TxOut[prevIndex] _, addrs, _, _ := txscript.ExtractPkScriptAddrs(prevOut.PkScript, ActiveNetParams) apkh, ok := addrs[0].(*btcutil.AddressPubKeyHash) if !ok { req.err <- btcwallet.ErrUnsupportedTransactionType return } ai, err := l.Manager.Address(apkh) if err != nil { req.err <- fmt.Errorf("cannot get address info: %v", err) return } pka := ai.(waddrmgr.ManagedPubKeyAddress) privkey, err := pka.PrivKey() if err != nil { req.err <- fmt.Errorf("cannot get private key: %v", err) return } sigscript, err := txscript.SignatureScript(pendingReservation.partialState.FundingTx, i, prevOut.PkScript, txscript.SigHashAll, privkey, ai.Compressed()) if err != nil { req.err <- fmt.Errorf("cannot create sigscript: %s", err) return } fundingTx.TxIn[i].SignatureScript = sigscript pendingReservation.ourFundingSigs = append(pendingReservation.ourFundingSigs, sigscript) } // Initialize an empty sha-chain for them, tracking the current pending // revocation hash (we don't yet know the pre-image so we can't add it // to the chain). pendingReservation.partialState.TheirShaChain = shachain.New() pendingReservation.partialState.TheirCurrentRevocation = theirContribution.RevocationHash // Grab the hash of the current pre-image in our chain, this is needed // for our commitment tx. // TODO(roasbeef): grab partial state above to avoid long attr chain ourCurrentRevokeHash := pendingReservation.ourContribution.RevocationHash // Create the txIn to our commitment transaction. In the process, we // need to locate the index of the multi-sig output on the funding tx // since the outputs are cannonically sorted. fundingNTxid := fundingTx.TxSha() // NOTE: assumes testnet-L _, multiSigIndex := findScriptOutputIndex(fundingTx, multiSigOut.PkScript) fundingTxIn := wire.NewTxIn(wire.NewOutPoint(&fundingNTxid, multiSigIndex), nil) // With the funding tx complete, create both commitment transactions. initialBalance := ourContribution.FundingAmount pendingReservation.fundingLockTime = theirContribution.CsvDelay ourCommitKey := ourContribution.CommitKey theirCommitKey := theirContribution.CommitKey ourCommitTx, err := createCommitTx(fundingTxIn, ourCommitKey, theirCommitKey, ourCurrentRevokeHash[:], theirContribution.CsvDelay, initialBalance, initialBalance) if err != nil { req.err <- err return } theirCommitTx, err := createCommitTx(fundingTxIn, theirCommitKey, ourCommitKey, theirContribution.RevocationHash[:], theirContribution.CsvDelay, initialBalance, initialBalance) if err != nil { req.err <- err return } // Sort both transactions according to the agreed upon cannonical // ordering. This lets us skip sending the entire transaction over, // instead we'll just send signatures. txsort.InPlaceSort(ourCommitTx) txsort.InPlaceSort(theirCommitTx) // Record newly available information witin the open channel state. pendingReservation.partialState.CsvDelay = theirContribution.CsvDelay pendingReservation.partialState.TheirDeliveryAddress = theirContribution.DeliveryAddress pendingReservation.partialState.ChanID = fundingNTxid pendingReservation.partialState.TheirCommitKey = theirCommitKey pendingReservation.partialState.TheirCommitTx = theirCommitTx pendingReservation.partialState.OurCommitTx = ourCommitTx // Generate a signature for their version of the initial commitment // transaction. sigTheirCommit, err := txscript.RawTxInSignature(theirCommitTx, 0, redeemScript, txscript.SigHashAll, ourKey) if err != nil { req.err <- err return } pendingReservation.ourCommitmentSig = sigTheirCommit req.err <- nil }
// testAddrIndexOperations ensures that all normal operations concerning // the optional address index function correctly. func testAddrIndexOperations(t *testing.T, db database.Db, newestBlock *btcutil.Block, newestSha *wire.ShaHash, newestBlockIdx int32) { // Metadata about the current addr index state should be unset. sha, height, err := db.FetchAddrIndexTip() if err != database.ErrAddrIndexDoesNotExist { t.Fatalf("Address index metadata shouldn't be in db, hasn't been built up yet.") } var zeroHash wire.ShaHash if !sha.IsEqual(&zeroHash) { t.Fatalf("AddrIndexTip wrong hash got: %s, want %s", sha, &zeroHash) } if height != -1 { t.Fatalf("Addrindex not built up, yet a block index tip has been set to: %d.", height) } // Test enforcement of constraints for "limit" and "skip" var fakeAddr btcutil.Address _, err = db.FetchTxsForAddr(fakeAddr, -1, 0) if err == nil { t.Fatalf("Negative value for skip passed, should return an error") } _, err = db.FetchTxsForAddr(fakeAddr, 0, -1) if err == nil { t.Fatalf("Negative value for limit passed, should return an error") } // Simple test to index outputs(s) of the first tx. testIndex := make(database.BlockAddrIndex) testTx, err := newestBlock.Tx(0) if err != nil { t.Fatalf("Block has no transactions, unable to test addr "+ "indexing, err %v", err) } // Extract the dest addr from the tx. _, testAddrs, _, err := txscript.ExtractPkScriptAddrs(testTx.MsgTx().TxOut[0].PkScript, &chaincfg.MainNetParams) if err != nil { t.Fatalf("Unable to decode tx output, err %v", err) } // Extract the hash160 from the output script. var hash160Bytes [ripemd160.Size]byte testHash160 := testAddrs[0].(*btcutil.AddressPubKey).AddressPubKeyHash().ScriptAddress() copy(hash160Bytes[:], testHash160[:]) // Create a fake index. blktxLoc, _ := newestBlock.TxLoc() testIndex[hash160Bytes] = []*wire.TxLoc{&blktxLoc[0]} // Insert our test addr index into the DB. err = db.UpdateAddrIndexForBlock(newestSha, newestBlockIdx, testIndex) if err != nil { t.Fatalf("UpdateAddrIndexForBlock: failed to index"+ " addrs for block #%d (%s) "+ "err %v", newestBlockIdx, newestSha, err) } // Chain Tip of address should've been updated. assertAddrIndexTipIsUpdated(db, t, newestSha, newestBlockIdx) // Check index retrieval. txReplies, err := db.FetchTxsForAddr(testAddrs[0], 0, 1000) if err != nil { t.Fatalf("FetchTxsForAddr failed to correctly fetch txs for an "+ "address, err %v", err) } // Should have one reply. if len(txReplies) != 1 { t.Fatalf("Failed to properly index tx by address.") } // Our test tx and indexed tx should have the same sha. indexedTx := txReplies[0] if !bytes.Equal(indexedTx.Sha.Bytes(), testTx.Sha().Bytes()) { t.Fatalf("Failed to fetch proper indexed tx. Expected sha %v, "+ "fetched %v", testTx.Sha(), indexedTx.Sha) } // Shut down DB. db.Sync() db.Close() // Re-Open, tip still should be updated to current height and sha. db, err = database.OpenDB("leveldb", "tstdbopmode") if err != nil { t.Fatalf("Unable to re-open created db, err %v", err) } assertAddrIndexTipIsUpdated(db, t, newestSha, newestBlockIdx) // Delete the entire index. err = db.DeleteAddrIndex() if err != nil { t.Fatalf("Couldn't delete address index, err %v", err) } // Former index should no longer exist. txReplies, err = db.FetchTxsForAddr(testAddrs[0], 0, 1000) if err != nil { t.Fatalf("Unable to fetch transactions for address: %v", err) } if len(txReplies) != 0 { t.Fatalf("Address index was not successfully deleted. "+ "Should have 0 tx's indexed, %v were returned.", len(txReplies)) } // Tip should be blanked out. if _, _, err := db.FetchAddrIndexTip(); err != database.ErrAddrIndexDoesNotExist { t.Fatalf("Address index was not fully deleted.") } }