// 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 := btcscript.ExtractPkScriptAddrs( script, &btcnet.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 }
// createVoutList returns a slice of JSON objects for the outputs of the passed // transaction. func createVoutList(mtx *btcwire.MsgTx, net btcwire.BitcoinNet) ([]btcjson.Vout, error) { voutList := make([]btcjson.Vout, len(mtx.TxOut)) for i, v := range mtx.TxOut { voutList[i].N = i voutList[i].Value = float64(v.Value) / float64(btcutil.SatoshiPerBitcoin) disbuf, err := btcscript.DisasmString(v.PkScript) if err != nil { return nil, btcjson.Error{ Code: btcjson.ErrInternal.Code, Message: err.Error(), } } voutList[i].ScriptPubKey.Asm = disbuf voutList[i].ScriptPubKey.Hex = hex.EncodeToString(v.PkScript) // Ignore the error here since an error means the script // couldn't parse and there is no additional information about // it anyways. scriptClass, addrs, reqSigs, _ := btcscript.ExtractPkScriptAddrs(v.PkScript, net) voutList[i].ScriptPubKey.Type = scriptClass.String() voutList[i].ScriptPubKey.ReqSigs = reqSigs if addrs == nil { voutList[i].ScriptPubKey.Addresses = nil } else { voutList[i].ScriptPubKey.Addresses = make([]string, len(addrs)) for j, addr := range addrs { voutList[i].ScriptPubKey.Addresses[j] = addr.EncodeAddress() } } } return voutList, nil }
// Addresses parses the pubkey script, extracting all addresses for a // standard script. func (c Credit) Addresses(net *btcnet.Params) (btcscript.ScriptClass, []btcutil.Address, int, error) { msgTx := c.Tx().MsgTx() pkScript := msgTx.TxOut[c.OutputIndex].PkScript return btcscript.ExtractPkScriptAddrs(pkScript, net) }
// Addresses parses the pubkey script, extracting all addresses for a // standard script. func (c *Credit) Addresses(net btcwire.BitcoinNet) (btcscript.ScriptClass, []btcutil.Address, int, error) { msgTx := c.Tx().MsgTx() pkScript := msgTx.TxOut[c.OutputIndex].PkScript return btcscript.ExtractPkScriptAddrs(pkScript, net) }
// NotifyForTxOuts iterates through all outputs of a tx, performing any // necessary notifications for wallets. If a non-nil block is passed, // additional block information is passed with the notifications. func (s *rpcServer) NotifyForTxOuts(tx *btcutil.Tx, block *btcutil.Block) { // Nothing to do if nobody is listening for transaction notifications. if len(s.ws.txNotifications) == 0 { return } for i, txout := range tx.MsgTx().TxOut { _, addrs, _, err := btcscript.ExtractPkScriptAddrs( txout.PkScript, s.server.btcnet) if err != nil { continue } for _, addr := range addrs { // Only support pay-to-pubkey-hash right now. if _, ok := addr.(*btcutil.AddressPubKeyHash); !ok { continue } encodedAddr := addr.EncodeAddress() if idlist, ok := s.ws.txNotifications[encodedAddr]; ok { for e := idlist.Front(); e != nil; e = e.Next() { n := e.Value.(ntfnChan) ntfn := &btcws.ProcessedTxNtfn{ Receiver: encodedAddr, TxID: tx.Sha().String(), TxOutIndex: uint32(i), Amount: txout.Value, PkScript: hex.EncodeToString(txout.PkScript), // TODO(jrick): hardcoding unspent is WRONG and needs // to be either calculated from other block txs, or dropped. Spent: false, } if block != nil { blkhash, err := block.Sha() if err != nil { rpcsLog.Error("Error getting block sha; dropping Tx notification") break } ntfn.BlockHeight = int32(block.Height()) ntfn.BlockHash = blkhash.String() ntfn.BlockIndex = tx.Index() ntfn.BlockTime = block.MsgBlock().Header.Timestamp.Unix() } else { ntfn.BlockHeight = -1 ntfn.BlockIndex = -1 } n <- ntfn } } } } }
func ExtractPkScriptAddrs(scriptHex string) { script, err := hex.DecodeString(scriptHex) handle(err) // Extract and print details from the script. scriptClass, addresses, reqSigs, err := btcscript.ExtractPkScriptAddrs(script, &btcnet.MainNetParams) handle(err) fmt.Println("Script Class:", scriptClass) fmt.Println("Addresses:", addresses) fmt.Println("Required Signatures:", reqSigs) }
// ToJSON returns a slice of objects that may be marshaled as a JSON array // of JSON objects for a listtransactions RPC reply. func (c *Credit) ToJSON(account string, chainHeight int32, net btcwire.BitcoinNet) (btcjson.ListTransactionsResult, error) { msgTx := c.Tx().MsgTx() txout := msgTx.TxOut[c.OutputIndex] var address string _, addrs, _, _ := btcscript.ExtractPkScriptAddrs(txout.PkScript, net) if len(addrs) == 1 { address = addrs[0].EncodeAddress() } var category string switch { case c.IsCoinbase(): if c.Confirmed(btcchain.CoinbaseMaturity, chainHeight) { category = "generate" } else { category = "immature" } default: category = "receive" } result := btcjson.ListTransactionsResult{ Account: account, Category: category, Address: address, Amount: btcutil.Amount(txout.Value).ToUnit(btcutil.AmountBTC), TxID: c.Tx().Sha().String(), Time: c.received.Unix(), TimeReceived: c.received.Unix(), WalletConflicts: []string{}, } if c.BlockHeight != -1 { b, err := c.s.lookupBlock(c.BlockHeight) if err != nil { return btcjson.ListTransactionsResult{}, err } result.BlockHash = b.Hash.String() result.BlockIndex = int64(c.Tx().Index()) result.BlockTime = b.Time.Unix() result.Confirmations = int64(c.Confirmations(chainHeight)) } return result, nil }
func GetAddrs(pkScript []byte) (ret []Address, scriptClass btcscript.ScriptClass) { // Extract the address from the script pub key scriptClass, addrs, _, _ := btcscript.ExtractPkScriptAddrs(pkScript, btcwire.MainNet) // Check each output address and if there's an address going to the exodus address // we add it to tx slice for _, addr := range addrs { // Script address returns the public key if it's a multi sig if scriptClass == btcscript.MultiSigTy { publicKey := hex.EncodeToString(addr.ScriptAddress()) ret = append(ret, Address{Addr: addr.EncodeAddress(), Raw: []byte(publicKey)}) } else { ret = append(ret, Address{Addr: addr.EncodeAddress(), Raw: addr.ScriptAddress()}) } } return }
// handleDecodeScript handles decodescript commands. func handleDecodeScript(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) { c := cmd.(*btcjson.DecodeScriptCmd) // Convert the hex script to bytes. script, err := hex.DecodeString(c.HexScript) if err != nil { return nil, btcjson.Error{ Code: btcjson.ErrInvalidParameter.Code, Message: fmt.Sprintf("argument must be hexadecimal "+ "string (not %q)", c.HexScript), } } // The disassembled string will contain [error] inline if the script // doesn't fully parse, so ignore the error here. disbuf, _ := btcscript.DisasmString(script) // Get information about the script. // Ignore the error here since an error means the script couldn't parse // and there is no additinal information about it anyways. net := s.server.btcnet scriptClass, addrs, reqSigs, _ := btcscript.ExtractPkScriptAddrs(script, net) addresses := make([]string, len(addrs)) for i, addr := range addrs { addresses[i] = addr.EncodeAddress() } // Convert the script itself to a pay-to-script-hash address. p2sh, err := btcutil.NewAddressScriptHash(script, net) if err != nil { return nil, btcjson.Error{ Code: btcjson.ErrInternal.Code, Message: err.Error(), } } // Generate and return the reply. reply := btcjson.DecodeScriptResult{ Asm: disbuf, ReqSigs: reqSigs, Type: scriptClass.String(), Addresses: addresses, P2sh: p2sh.EncodeAddress(), } return reply, nil }
// ToJSON returns a slice of objects that may be marshaled as a JSON array // of JSON objects for a listtransactions RPC reply. func (d *Debits) ToJSON(account string, chainHeight int32, net btcwire.BitcoinNet) ([]btcjson.ListTransactionsResult, error) { msgTx := d.Tx().MsgTx() reply := make([]btcjson.ListTransactionsResult, 0, len(msgTx.TxOut)) for _, txOut := range msgTx.TxOut { address := "" _, addrs, _, _ := btcscript.ExtractPkScriptAddrs(txOut.PkScript, net) if len(addrs) == 1 { address = addrs[0].EncodeAddress() } result := btcjson.ListTransactionsResult{ Account: account, Address: address, Category: "send", Amount: btcutil.Amount(-txOut.Value).ToUnit(btcutil.AmountBTC), Fee: d.Fee().ToUnit(btcutil.AmountBTC), TxID: d.Tx().Sha().String(), Time: d.txRecord.received.Unix(), TimeReceived: d.txRecord.received.Unix(), WalletConflicts: []string{}, } if d.BlockHeight != -1 { b, err := d.s.lookupBlock(d.BlockHeight) if err != nil { return nil, err } result.BlockHash = b.Hash.String() result.BlockIndex = int64(d.Tx().Index()) result.BlockTime = b.Time.Unix() result.Confirmations = int64(d.Confirmations(chainHeight)) } reply = append(reply, result) } return reply, nil }
// ToJSON returns a slice of objects that may be marshaled as a JSON array // of JSON objects for a listtransactions RPC reply. func (c *Credit) ToJSON(account string, chainHeight int32, net *btcnet.Params) (btcjson.ListTransactionsResult, error) { msgTx := c.Tx().MsgTx() txout := msgTx.TxOut[c.OutputIndex] var address string _, addrs, _, _ := btcscript.ExtractPkScriptAddrs(txout.PkScript, net) if len(addrs) == 1 { address = addrs[0].EncodeAddress() } result := btcjson.ListTransactionsResult{ Account: account, Category: c.Category(chainHeight).String(), Address: address, Amount: btcutil.Amount(txout.Value).ToUnit(btcutil.AmountBTC), TxID: c.Tx().Sha().String(), Time: c.received.Unix(), TimeReceived: c.received.Unix(), WalletConflicts: []string{}, } if c.BlockHeight != -1 { b, err := c.s.lookupBlock(c.BlockHeight) if err != nil { return btcjson.ListTransactionsResult{}, err } result.BlockHash = b.Hash.String() result.BlockIndex = int64(c.Tx().Index()) result.BlockTime = b.Time.Unix() result.Confirmations = int64(c.Confirmations(chainHeight)) } return result, nil }
// NtfnRecvTx handles the btcws.RecvTxNtfn notification. func NtfnRecvTx(n btcjson.Cmd) error { rtx, ok := n.(*btcws.RecvTxNtfn) if !ok { return fmt.Errorf("%v handler: unexpected type", n.Method()) } bs, err := GetCurBlock() if err != nil { return fmt.Errorf("%v handler: cannot get current block: %v", n.Method(), err) } rawTx, err := hex.DecodeString(rtx.HexTx) if err != nil { return fmt.Errorf("%v handler: bad hexstring: %v", n.Method(), err) } tx, err := btcutil.NewTxFromBytes(rawTx) if err != nil { return fmt.Errorf("%v handler: bad transaction bytes: %v", n.Method(), err) } block, txIdx, err := parseBlock(rtx.Block) if err != nil { return fmt.Errorf("%v handler: bad block: %v", n.Method(), err) } tx.SetIndex(txIdx) // For transactions originating from this wallet, the sent tx history should // be recorded before the received history. If wallet created this tx, wait // for the sent history to finish being recorded before continuing. // // TODO(jrick) this is wrong due to tx malleability. Cannot safely use the // txsha as an identifier. req := SendTxHistSyncRequest{ txsha: *tx.Sha(), response: make(chan SendTxHistSyncResponse), } SendTxHistSyncChans.access <- req resp := <-req.response if resp.ok { // Wait until send history has been recorded. <-resp.c SendTxHistSyncChans.remove <- *tx.Sha() } // For every output, find all accounts handling that output address (if any) // and record the received txout. for outIdx, txout := range tx.MsgTx().TxOut { var accounts []*Account _, addrs, _, _ := btcscript.ExtractPkScriptAddrs(txout.PkScript, cfg.Net()) for _, addr := range addrs { a, err := AcctMgr.AccountByAddress(addr) if err != nil { continue } accounts = append(accounts, a) } for _, a := range accounts { txr, err := a.TxStore.InsertTx(tx, block) if err != nil { return err } cred, err := txr.AddCredit(uint32(outIdx), false) if err != nil { return err } AcctMgr.ds.ScheduleTxStoreWrite(a) // Notify frontends of tx. If the tx is unconfirmed, it is always // notified and the outpoint is marked as notified. If the outpoint // has already been notified and is now in a block, a txmined notifiction // should be sent once to let frontends that all previous send/recvs // for this unconfirmed tx are now confirmed. op := *cred.OutPoint() previouslyNotifiedReq := NotifiedRecvTxRequest{ op: op, response: make(chan NotifiedRecvTxResponse), } NotifiedRecvTxChans.access <- previouslyNotifiedReq if <-previouslyNotifiedReq.response { NotifiedRecvTxChans.remove <- op } else { // Notify frontends of new recv tx and mark as notified. NotifiedRecvTxChans.add <- op ltr, err := cred.ToJSON(a.Name(), bs.Height, a.Wallet.Net()) if err != nil { return err } NotifyNewTxDetails(allClients, a.Name(), ltr) } // Notify frontends of new account balance. confirmed := a.CalculateBalance(1) unconfirmed := a.CalculateBalance(0) - confirmed NotifyWalletBalance(allClients, a.name, confirmed) NotifyWalletBalanceUnconfirmed(allClients, a.name, unconfirmed) } } return 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 btcscript.ScriptClass }{ { name: "standard p2pk with compressed pubkey (0x02)", script: decodeHex("2102192d74d0cb94344c9569c2e7790157" + "3d8d7903c3ebec3a957724895dca52c6b4ac"), addrs: []btcutil.Address{ newAddressPubKey(decodeHex("02192d74d0cb94344" + "c9569c2e77901573d8d7903c3ebec3a95772" + "4895dca52c6b4")), }, reqSigs: 1, class: btcscript.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: btcscript.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: btcscript.PubKeyTy, }, { name: "standard p2pk with compressed pubkey (0x03)", script: decodeHex("2103b0bd634234abbb1ba1e986e884185c" + "61cf43e001f9137f23c2c409273eb16e65ac"), addrs: []btcutil.Address{ newAddressPubKey(decodeHex("03b0bd634234abbb1" + "ba1e986e884185c61cf43e001f9137f23c2c" + "409273eb16e65")), }, reqSigs: 1, class: btcscript.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: btcscript.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: btcscript.PubKeyTy, }, { name: "standard p2pkh", script: decodeHex("76a914ad06dd6ddee55cbca9a9e3713bd7" + "587509a3056488ac"), addrs: []btcutil.Address{ newAddressPubKeyHash(decodeHex("ad06dd6ddee55" + "cbca9a9e3713bd7587509a30564")), }, reqSigs: 1, class: btcscript.PubKeyHashTy, }, { name: "standard p2sh", script: decodeHex("a91463bcc565f9e68ee0189dd5cc67f1b0" + "e5f02f45cb87"), addrs: []btcutil.Address{ newAddressScriptHash(decodeHex("63bcc565f9e68" + "ee0189dd5cc67f1b0e5f02f45cb")), }, reqSigs: 1, class: btcscript.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: btcscript.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: btcscript.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: btcscript.NonStandardTy, }, { name: "valid signature from a sigscript - no addresses", script: decodeHex("47304402204e45e16932b8af514961a1d3" + "a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220" + "181522ec8eca07de4860a4acdd12909d831cc56cbbac" + "4622082221a8768d1d0901"), addrs: nil, reqSigs: 0, class: btcscript.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: btcscript.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: btcscript.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: btcscript.MultiSigTy, }, { name: "empty script", script: []byte{}, addrs: nil, reqSigs: 0, class: btcscript.NonStandardTy, }, { name: "script that does not parse", script: []byte{btcscript.OP_DATA_45}, addrs: nil, reqSigs: 0, class: btcscript.NonStandardTy, }, } t.Logf("Running %d tests.", len(tests)) for i, test := range tests { class, addrs, reqSigs, err := btcscript.ExtractPkScriptAddrs( test.script, &btcnet.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 (n recvTx) handleNotification() error { block, txIdx, err := parseBlock(n.block) if err != nil { return InvalidNotificationError{err} } n.tx.SetIndex(txIdx) bs, err := GetCurBlock() if err != nil { return fmt.Errorf("cannot get current block: %v", err) } AcctMgr.Grab() defer AcctMgr.Release() // For every output, if it pays to a wallet address, insert the // transaction into the store (possibly moving it from unconfirmed to // confirmed), and add a credit record if one does not already exist. var txr *txstore.TxRecord txInserted := false for i, txout := range n.tx.MsgTx().TxOut { // Errors don't matter here. If addrs is nil, the range below // does nothing. _, addrs, _, _ := btcscript.ExtractPkScriptAddrs(txout.PkScript, activeNet.Params) for _, addr := range addrs { a, err := AcctMgr.AccountByAddress(addr) if err != nil { continue // try next address, if any } if !txInserted { txr, err = a.TxStore.InsertTx(n.tx, block) if err != nil { return err } txInserted = true } // Insert and notify websocket clients of the credit if it is // not a duplicate, otherwise, check the next txout if the // credit has already been inserted. if txr.HasCredit(i) { break } cred, err := txr.AddCredit(uint32(i), false) if err != nil { return err } AcctMgr.ds.ScheduleTxStoreWrite(a) ltr, err := cred.ToJSON(a.Name(), bs.Height, a.Wallet.Net()) if err != nil { return err } server.NotifyNewTxDetails(a.Name(), ltr) break // check whether next txout is a wallet txout } } server.NotifyBalances() return nil }
// handleRescan implements the rescan command extension for websocket // connections. func handleRescan(s *rpcServer, cmd btcjson.Cmd, wallet walletChan) error { rescanCmd, ok := cmd.(*btcws.RescanCmd) if !ok { return btcjson.ErrInternal } if len(rescanCmd.Addresses) == 1 { rpcsLog.Info("Beginning rescan for 1 address.") } else { rpcsLog.Infof("Beginning rescan for %v addresses.", len(rescanCmd.Addresses)) } minblock := int64(rescanCmd.BeginBlock) maxblock := int64(rescanCmd.EndBlock) // FetchHeightRange may not return a complete list of block shas for // the given range, so fetch range as many times as necessary. for { blkshalist, err := s.server.db.FetchHeightRange(minblock, maxblock) if err != nil { return err } if len(blkshalist) == 0 { break } for i := range blkshalist { blk, err := s.server.db.FetchBlockBySha(&blkshalist[i]) if err != nil { rpcsLog.Errorf("Error looking up block sha: %v", err) return err } for _, tx := range blk.Transactions() { var txReply *btcdb.TxListReply txouts: for txOutIdx, txout := range tx.MsgTx().TxOut { _, addrs, _, err := btcscript.ExtractPkScriptAddrs( txout.PkScript, s.server.btcnet) if err != nil { continue txouts } for i, addr := range addrs { encodedAddr := addr.EncodeAddress() if _, ok := rescanCmd.Addresses[encodedAddr]; ok { // TODO(jrick): This lookup is expensive and can be avoided // if the wallet is sent the previous outpoints for all inputs // of the tx, so any can removed from the utxo set (since // they are, as of this tx, now spent). if txReply == nil { txReplyList, err := s.server.db.FetchTxBySha(tx.Sha()) if err != nil { rpcsLog.Errorf("Tx Sha %v not found by db.", tx.Sha()) continue txouts } for i := range txReplyList { if txReplyList[i].Height == blk.Height() { txReply = txReplyList[i] break } } } ntfn := &btcws.ProcessedTxNtfn{ Receiver: encodedAddr, Amount: txout.Value, TxID: tx.Sha().String(), TxOutIndex: uint32(txOutIdx), PkScript: hex.EncodeToString(txout.PkScript), BlockHash: blkshalist[i].String(), BlockHeight: int32(blk.Height()), BlockIndex: tx.Index(), BlockTime: blk.MsgBlock().Header.Timestamp.Unix(), Spent: txReply.TxSpent[txOutIdx], } mntfn, _ := ntfn.MarshalJSON() wallet <- mntfn } } } } } if maxblock-minblock > int64(len(blkshalist)) { minblock += int64(len(blkshalist)) } else { break } } rpcsLog.Info("Finished rescan") id := cmd.Id() response := &btcjson.Reply{ Id: &id, Result: nil, Error: nil, } mresponse, _ := json.Marshal(response) wallet <- mresponse return nil }
// rescanBlock rescans all transactions in a single block. This is a // helper function for handleRescan. func rescanBlock(s *rpcServer, cmd *btcws.RescanCmd, c handlerChans, blk *btcutil.Block) { for _, tx := range blk.Transactions() { var txReply *btcdb.TxListReply txouts: for txOutIdx, txout := range tx.MsgTx().TxOut { _, addrs, _, err := btcscript.ExtractPkScriptAddrs( txout.PkScript, s.server.btcnet) if err != nil { continue txouts } for _, addr := range addrs { encodedAddr := addr.EncodeAddress() if _, ok := cmd.Addresses[encodedAddr]; !ok { continue } // TODO(jrick): This lookup is expensive and can be avoided // if the wallet is sent the previous outpoints for all inputs // of the tx, so any can removed from the utxo set (since // they are, as of this tx, now spent). if txReply == nil { txReplyList, err := s.server.db.FetchTxBySha(tx.Sha()) if err != nil { rpcsLog.Errorf("Tx Sha %v not found by db", tx.Sha()) continue txouts } for i := range txReplyList { if txReplyList[i].Height == blk.Height() { txReply = txReplyList[i] break } } } // Sha never errors. blksha, _ := blk.Sha() ntfn := &btcws.ProcessedTxNtfn{ Receiver: encodedAddr, Amount: txout.Value, TxID: tx.Sha().String(), TxOutIndex: uint32(txOutIdx), PkScript: hex.EncodeToString(txout.PkScript), BlockHash: blksha.String(), BlockHeight: int32(blk.Height()), BlockIndex: tx.Index(), BlockTime: blk.MsgBlock().Header.Timestamp.Unix(), Spent: txReply.TxSpent[txOutIdx], } select { case <-c.disconnected: return default: c.n <- ntfn } } } } }