// handleNotifyNewTXs implements the notifynewtxs command extension for // websocket connections. func handleNotifyNewTXs(s *rpcServer, cmd btcjson.Cmd, wallet walletChan) error { id := cmd.Id() reply := &btcjson.Reply{Id: &id} notifyCmd, ok := cmd.(*btcws.NotifyNewTXsCmd) if !ok { return btcjson.ErrInternal } for _, addr := range notifyCmd.Addresses { addr, err := btcutil.DecodeAddr(addr) if err != nil { return fmt.Errorf("cannot decode address: %v", err) } // TODO(jrick) Notifing for non-P2PKH addresses is currently // unsuported. if _, ok := addr.(*btcutil.AddressPubKeyHash); !ok { return fmt.Errorf("address is not P2PKH: %v", addr.EncodeAddress()) } s.ws.AddTxRequest(wallet, addr.EncodeAddress()) } mreply, _ := json.Marshal(reply) wallet <- mreply return nil }
// DumpPrivKey handles a dumpprivkey request with the private key // for a single address, or an appropiate error if the wallet // is locked. func DumpPrivKey(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { // Type assert icmd to access parameters. cmd, ok := icmd.(*btcjson.DumpPrivKeyCmd) if !ok { return nil, &btcjson.ErrInternal } addr, err := btcutil.DecodeAddr(cmd.Address) if err != nil { return nil, &btcjson.ErrInvalidAddressOrKey } switch key, err := accountstore.DumpWIFPrivateKey(addr); err { case nil: // Key was found. return key, nil case wallet.ErrWalletLocked: // Address was found, but the private key isn't // accessible. return nil, &btcjson.ErrWalletUnlockNeeded default: // all other non-nil errors e := btcjson.Error{ Code: btcjson.ErrWallet.Code, Message: err.Error(), } return nil, &e } }
// handleNotifyNewTXs implements the notifynewtxs command extension for // websocket connections. func handleNotifyNewTXs(s *rpcServer, icmd btcjson.Cmd, c handlerChans) (interface{}, *btcjson.Error) { cmd, ok := icmd.(*btcws.NotifyNewTXsCmd) if !ok { return nil, &btcjson.ErrInternal } for _, addrStr := range cmd.Addresses { addr, err := btcutil.DecodeAddr(addrStr) if err != nil { e := btcjson.Error{ Code: btcjson.ErrInvalidAddressOrKey.Code, Message: fmt.Sprintf("Invalid address or key: %v", addrStr), } return nil, &e } // TODO(jrick) Notifing for non-P2PKH addresses is currently // unsuported. if _, ok := addr.(*btcutil.AddressPubKeyHash); !ok { e := btcjson.Error{ Code: btcjson.ErrInvalidAddressOrKey.Code, Message: fmt.Sprintf("Invalid address or key: %v", addr.EncodeAddress()), } return nil, &e } s.ws.AddTxRequest(c.n, addr.EncodeAddress()) } return nil, nil }
// ListAddressTransactions handles a listaddresstransactions request by // returning an array of maps with details of spent and received wallet // transactions. The form of the reply is identical to listtransactions, // but the array elements are limited to transaction details which are // about the addresess included in the request. func ListAddressTransactions(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { // Type assert icmd to access parameters. cmd, ok := icmd.(*btcws.ListAddressTransactionsCmd) if !ok { return nil, &btcjson.ErrInternal } a, err := accountstore.Account(cmd.Account) switch err { case nil: break case ErrAcctNotExist: return nil, &btcjson.ErrWalletInvalidAccountName default: // all other non-nil errors e := btcjson.Error{ Code: btcjson.ErrWallet.Code, Message: err.Error(), } return nil, &e } // Decode addresses. pkHashMap := make(map[string]struct{}) for _, addrStr := range cmd.Addresses { addr, err := btcutil.DecodeAddr(addrStr) if err != nil { return nil, &btcjson.ErrInvalidAddressOrKey } apkh, ok := addr.(*btcutil.AddressPubKeyHash) if !ok || apkh.Net() != cfg.Net() { return nil, &btcjson.ErrInvalidAddressOrKey } pkHashMap[string(addr.ScriptAddress())] = struct{}{} } txList, err := a.ListAddressTransactions(pkHashMap) if err != nil { e := btcjson.Error{ Code: btcjson.ErrWallet.Code, Message: err.Error(), } return nil, &e } return txList, nil }
// GetAccount handles a getaccount request by returning the account name // associated with a single address. func GetAccount(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { // Type assert icmd to access parameters. cmd, ok := icmd.(*btcjson.GetAccountCmd) if !ok { return nil, &btcjson.ErrInternal } // Is address valid? addr, err := btcutil.DecodeAddr(cmd.Address) if err != nil { return nil, &btcjson.ErrInvalidAddressOrKey } var net btcwire.BitcoinNet switch a := addr.(type) { case *btcutil.AddressPubKeyHash: net = a.Net() case *btcutil.AddressScriptHash: net = a.Net() default: return nil, &btcjson.ErrInvalidAddressOrKey } if net != cfg.Net() { return nil, &btcjson.ErrInvalidAddressOrKey } // Look up account which holds this address. aname, err := LookupAccountByAddress(cmd.Address) if err == ErrNotFound { e := btcjson.Error{ Code: btcjson.ErrInvalidAddressOrKey.Code, Message: "Address not found in wallet", } return nil, &e } return aname, nil }
// GetAddressBalance handles a getaddressbalance extension request by // returning the current balance (sum of unspent transaction output amounts) // for a single address. func GetAddressBalance(icmd btcjson.Cmd) (interface{}, *btcjson.Error) { // Type assert icmd to access parameters. cmd, ok := icmd.(*btcws.GetAddressBalanceCmd) if !ok { return nil, &btcjson.ErrInternal } // Is address valid? addr, err := btcutil.DecodeAddr(cmd.Address) if err != nil { return nil, &btcjson.ErrInvalidAddressOrKey } apkh, ok := addr.(*btcutil.AddressPubKeyHash) if !ok || apkh.Net() != cfg.Net() { return nil, &btcjson.ErrInvalidAddressOrKey } // Look up account which holds this address. aname, err := LookupAccountByAddress(cmd.Address) if err == ErrNotFound { e := btcjson.Error{ Code: btcjson.ErrInvalidAddressOrKey.Code, Message: "Address not found in wallet", } return nil, &e } // Get the account which holds the address in the request. // This should not fail, so if it does, return an internal // error to the frontend. a, err := accountstore.Account(aname) if err != nil { return nil, &btcjson.ErrInternal } bal := a.CalculateAddressBalance(apkh, int(cmd.Minconf)) return bal, nil }
func TestAddresses(t *testing.T) { tests := []struct { name string addr string valid bool canDecode bool result btcutil.Address f func() (btcutil.Address, error) net btcwire.BitcoinNet }{ // Positive P2PKH tests. { name: "mainnet p2pkh", addr: "1MirQ9bwyQcGVJPwKUgapu5ouK2E2Ey4gX", valid: true, canDecode: true, result: btcutil.TstAddressPubKeyHash( [ripemd160.Size]byte{ 0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc, 0xc5, 0x4c, 0xe7, 0xd2, 0xa4, 0x91, 0xbb, 0x4a, 0x0e, 0x84}, btcwire.MainNet), f: func() (btcutil.Address, error) { pkHash := []byte{ 0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc, 0xc5, 0x4c, 0xe7, 0xd2, 0xa4, 0x91, 0xbb, 0x4a, 0x0e, 0x84} return btcutil.NewAddressPubKeyHash(pkHash, btcwire.MainNet) }, net: btcwire.MainNet, }, { name: "mainnet p2pkh 2", addr: "12MzCDwodF9G1e7jfwLXfR164RNtx4BRVG", valid: true, canDecode: true, result: btcutil.TstAddressPubKeyHash( [ripemd160.Size]byte{ 0x0e, 0xf0, 0x30, 0x10, 0x7f, 0xd2, 0x6e, 0x0b, 0x6b, 0xf4, 0x05, 0x12, 0xbc, 0xa2, 0xce, 0xb1, 0xdd, 0x80, 0xad, 0xaa}, btcwire.MainNet), f: func() (btcutil.Address, error) { pkHash := []byte{ 0x0e, 0xf0, 0x30, 0x10, 0x7f, 0xd2, 0x6e, 0x0b, 0x6b, 0xf4, 0x05, 0x12, 0xbc, 0xa2, 0xce, 0xb1, 0xdd, 0x80, 0xad, 0xaa} return btcutil.NewAddressPubKeyHash(pkHash, btcwire.MainNet) }, net: btcwire.MainNet, }, { name: "testnet p2pkh", addr: "mrX9vMRYLfVy1BnZbc5gZjuyaqH3ZW2ZHz", valid: true, canDecode: true, result: btcutil.TstAddressPubKeyHash( [ripemd160.Size]byte{ 0x78, 0xb3, 0x16, 0xa0, 0x86, 0x47, 0xd5, 0xb7, 0x72, 0x83, 0xe5, 0x12, 0xd3, 0x60, 0x3f, 0x1f, 0x1c, 0x8d, 0xe6, 0x8f}, btcwire.TestNet3), f: func() (btcutil.Address, error) { pkHash := []byte{ 0x78, 0xb3, 0x16, 0xa0, 0x86, 0x47, 0xd5, 0xb7, 0x72, 0x83, 0xe5, 0x12, 0xd3, 0x60, 0x3f, 0x1f, 0x1c, 0x8d, 0xe6, 0x8f} return btcutil.NewAddressPubKeyHash(pkHash, btcwire.TestNet3) }, net: btcwire.TestNet3, }, // Negative P2PKH tests. { name: "p2pkh wrong byte identifier/net", addr: "MrX9vMRYLfVy1BnZbc5gZjuyaqH3ZW2ZHz", valid: false, canDecode: true, f: func() (btcutil.Address, error) { pkHash := []byte{ 0x78, 0xb3, 0x16, 0xa0, 0x86, 0x47, 0xd5, 0xb7, 0x72, 0x83, 0xe5, 0x12, 0xd3, 0x60, 0x3f, 0x1f, 0x1c, 0x8d, 0xe6, 0x8f} return btcutil.NewAddressPubKeyHash(pkHash, btcwire.TestNet) }, }, { name: "p2pkh wrong hash length", addr: "", valid: false, canDecode: true, f: func() (btcutil.Address, error) { pkHash := []byte{ 0x00, 0x0e, 0xf0, 0x30, 0x10, 0x7f, 0xd2, 0x6e, 0x0b, 0x6b, 0xf4, 0x05, 0x12, 0xbc, 0xa2, 0xce, 0xb1, 0xdd, 0x80, 0xad, 0xaa} return btcutil.NewAddressPubKeyHash(pkHash, btcwire.MainNet) }, }, { name: "p2pkh bad checksum", addr: "1MirQ9bwyQcGVJPwKUgapu5ouK2E2Ey4gY", valid: false, canDecode: true, }, // Positive P2SH tests. { // Taken from transactions: // output: 3c9018e8d5615c306d72397f8f5eef44308c98fb576a88e030c25456b4f3a7ac // input: 837dea37ddc8b1e3ce646f1a656e79bbd8cc7f558ac56a169626d649ebe2a3ba. name: "mainnet p2sh", addr: "3QJmV3qfvL9SuYo34YihAf3sRCW3qSinyC", valid: true, canDecode: true, result: btcutil.TstAddressScriptHash( [ripemd160.Size]byte{ 0xf8, 0x15, 0xb0, 0x36, 0xd9, 0xbb, 0xbc, 0xe5, 0xe9, 0xf2, 0xa0, 0x0a, 0xbd, 0x1b, 0xf3, 0xdc, 0x91, 0xe9, 0x55, 0x10}, btcwire.MainNet), f: func() (btcutil.Address, error) { script := []byte{ 0x52, 0x41, 0x04, 0x91, 0xbb, 0xa2, 0x51, 0x09, 0x12, 0xa5, 0xbd, 0x37, 0xda, 0x1f, 0xb5, 0xb1, 0x67, 0x30, 0x10, 0xe4, 0x3d, 0x2c, 0x6d, 0x81, 0x2c, 0x51, 0x4e, 0x91, 0xbf, 0xa9, 0xf2, 0xeb, 0x12, 0x9e, 0x1c, 0x18, 0x33, 0x29, 0xdb, 0x55, 0xbd, 0x86, 0x8e, 0x20, 0x9a, 0xac, 0x2f, 0xbc, 0x02, 0xcb, 0x33, 0xd9, 0x8f, 0xe7, 0x4b, 0xf2, 0x3f, 0x0c, 0x23, 0x5d, 0x61, 0x26, 0xb1, 0xd8, 0x33, 0x4f, 0x86, 0x41, 0x04, 0x86, 0x5c, 0x40, 0x29, 0x3a, 0x68, 0x0c, 0xb9, 0xc0, 0x20, 0xe7, 0xb1, 0xe1, 0x06, 0xd8, 0xc1, 0x91, 0x6d, 0x3c, 0xef, 0x99, 0xaa, 0x43, 0x1a, 0x56, 0xd2, 0x53, 0xe6, 0x92, 0x56, 0xda, 0xc0, 0x9e, 0xf1, 0x22, 0xb1, 0xa9, 0x86, 0x81, 0x8a, 0x7c, 0xb6, 0x24, 0x53, 0x2f, 0x06, 0x2c, 0x1d, 0x1f, 0x87, 0x22, 0x08, 0x48, 0x61, 0xc5, 0xc3, 0x29, 0x1c, 0xcf, 0xfe, 0xf4, 0xec, 0x68, 0x74, 0x41, 0x04, 0x8d, 0x24, 0x55, 0xd2, 0x40, 0x3e, 0x08, 0x70, 0x8f, 0xc1, 0xf5, 0x56, 0x00, 0x2f, 0x1b, 0x6c, 0xd8, 0x3f, 0x99, 0x2d, 0x08, 0x50, 0x97, 0xf9, 0x97, 0x4a, 0xb0, 0x8a, 0x28, 0x83, 0x8f, 0x07, 0x89, 0x6f, 0xba, 0xb0, 0x8f, 0x39, 0x49, 0x5e, 0x15, 0xfa, 0x6f, 0xad, 0x6e, 0xdb, 0xfb, 0x1e, 0x75, 0x4e, 0x35, 0xfa, 0x1c, 0x78, 0x44, 0xc4, 0x1f, 0x32, 0x2a, 0x18, 0x63, 0xd4, 0x62, 0x13, 0x53, 0xae} return btcutil.NewAddressScriptHash(script, btcwire.MainNet) }, net: btcwire.MainNet, }, { // Taken from transactions: // output: b0539a45de13b3e0403909b8bd1a555b8cbe45fd4e3f3fda76f3a5f52835c29d // input: (not yet redeemed at time test was written) name: "mainnet p2sh 2", addr: "3NukJ6fYZJ5Kk8bPjycAnruZkE5Q7UW7i8", valid: true, canDecode: true, result: btcutil.TstAddressScriptHash( [ripemd160.Size]byte{ 0xe8, 0xc3, 0x00, 0xc8, 0x79, 0x86, 0xef, 0xa8, 0x4c, 0x37, 0xc0, 0x51, 0x99, 0x29, 0x01, 0x9e, 0xf8, 0x6e, 0xb5, 0xb4}, btcwire.MainNet), f: func() (btcutil.Address, error) { hash := []byte{ 0xe8, 0xc3, 0x00, 0xc8, 0x79, 0x86, 0xef, 0xa8, 0x4c, 0x37, 0xc0, 0x51, 0x99, 0x29, 0x01, 0x9e, 0xf8, 0x6e, 0xb5, 0xb4} return btcutil.NewAddressScriptHashFromHash(hash, btcwire.MainNet) }, net: btcwire.MainNet, }, { // Taken from bitcoind base58_keys_valid. name: "testnet p2sh", addr: "2NBFNJTktNa7GZusGbDbGKRZTxdK9VVez3n", valid: true, canDecode: true, result: btcutil.TstAddressScriptHash( [ripemd160.Size]byte{ 0xc5, 0x79, 0x34, 0x2c, 0x2c, 0x4c, 0x92, 0x20, 0x20, 0x5e, 0x2c, 0xdc, 0x28, 0x56, 0x17, 0x04, 0x0c, 0x92, 0x4a, 0x0a}, btcwire.TestNet3), f: func() (btcutil.Address, error) { hash := []byte{ 0xc5, 0x79, 0x34, 0x2c, 0x2c, 0x4c, 0x92, 0x20, 0x20, 0x5e, 0x2c, 0xdc, 0x28, 0x56, 0x17, 0x04, 0x0c, 0x92, 0x4a, 0x0a} return btcutil.NewAddressScriptHashFromHash(hash, btcwire.TestNet3) }, net: btcwire.TestNet3, }, // Negative P2SH tests. { name: "p2sh wrong hash length", addr: "", valid: false, canDecode: true, f: func() (btcutil.Address, error) { hash := []byte{ 0x00, 0xf8, 0x15, 0xb0, 0x36, 0xd9, 0xbb, 0xbc, 0xe5, 0xe9, 0xf2, 0xa0, 0x0a, 0xbd, 0x1b, 0xf3, 0xdc, 0x91, 0xe9, 0x55, 0x10} return btcutil.NewAddressScriptHashFromHash(hash, btcwire.MainNet) }, }, { name: "p2sh wrong byte identifier/net", addr: "0NBFNJTktNa7GZusGbDbGKRZTxdK9VVez3n", valid: false, canDecode: true, f: func() (btcutil.Address, error) { hash := []byte{ 0xc5, 0x79, 0x34, 0x2c, 0x2c, 0x4c, 0x92, 0x20, 0x20, 0x5e, 0x2c, 0xdc, 0x28, 0x56, 0x17, 0x04, 0x0c, 0x92, 0x4a, 0x0a} return btcutil.NewAddressScriptHashFromHash(hash, btcwire.TestNet) }, }, // Positive P2PK tests. { name: "mainnet p2pk compressed (0x02)", addr: "13CG6SJ3yHUXo4Cr2RY4THLLJrNFuG3gUg", valid: true, canDecode: false, result: btcutil.TstAddressPubKey( []byte{ 0x02, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, 0x4c, 0x95, 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, 0x3d, 0x8d, 0x79, 0x03, 0xc3, 0xeb, 0xec, 0x3a, 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, 0x52, 0xc6, 0xb4}, btcutil.PKFCompressed, btcwire.MainNet), f: func() (btcutil.Address, error) { serializedPubKey := []byte{ 0x02, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, 0x4c, 0x95, 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, 0x3d, 0x8d, 0x79, 0x03, 0xc3, 0xeb, 0xec, 0x3a, 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, 0x52, 0xc6, 0xb4} return btcutil.NewAddressPubKey(serializedPubKey, btcwire.MainNet) }, net: btcwire.MainNet, }, { name: "mainnet p2pk compressed (0x03)", addr: "15sHANNUBSh6nDp8XkDPmQcW6n3EFwmvE6", valid: true, canDecode: false, result: btcutil.TstAddressPubKey( []byte{ 0x03, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, 0x1b, 0xa1, 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, 0x61, 0xcf, 0x43, 0xe0, 0x01, 0xf9, 0x13, 0x7f, 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, 0xb1, 0x6e, 0x65}, btcutil.PKFCompressed, btcwire.MainNet), f: func() (btcutil.Address, error) { serializedPubKey := []byte{ 0x03, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, 0x1b, 0xa1, 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, 0x61, 0xcf, 0x43, 0xe0, 0x01, 0xf9, 0x13, 0x7f, 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, 0xb1, 0x6e, 0x65} return btcutil.NewAddressPubKey(serializedPubKey, btcwire.MainNet) }, net: btcwire.MainNet, }, { name: "mainnet p2pk uncompressed (0x04)", addr: "12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S", valid: true, canDecode: false, result: btcutil.TstAddressPubKey( []byte{ 0x04, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, 0x01, 0x6b, 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, 0x1e, 0xb6, 0x8a, 0x38, 0x2e, 0x97, 0xb1, 0x48, 0x2e, 0xca, 0xd7, 0xb1, 0x48, 0xa6, 0x90, 0x9a, 0x5c, 0xb2, 0xe0, 0xea, 0xdd, 0xfb, 0x84, 0xcc, 0xf9, 0x74, 0x44, 0x64, 0xf8, 0x2e, 0x16, 0x0b, 0xfa, 0x9b, 0x8b, 0x64, 0xf9, 0xd4, 0xc0, 0x3f, 0x99, 0x9b, 0x86, 0x43, 0xf6, 0x56, 0xb4, 0x12, 0xa3}, btcutil.PKFUncompressed, btcwire.MainNet), f: func() (btcutil.Address, error) { serializedPubKey := []byte{ 0x04, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, 0x01, 0x6b, 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, 0x1e, 0xb6, 0x8a, 0x38, 0x2e, 0x97, 0xb1, 0x48, 0x2e, 0xca, 0xd7, 0xb1, 0x48, 0xa6, 0x90, 0x9a, 0x5c, 0xb2, 0xe0, 0xea, 0xdd, 0xfb, 0x84, 0xcc, 0xf9, 0x74, 0x44, 0x64, 0xf8, 0x2e, 0x16, 0x0b, 0xfa, 0x9b, 0x8b, 0x64, 0xf9, 0xd4, 0xc0, 0x3f, 0x99, 0x9b, 0x86, 0x43, 0xf6, 0x56, 0xb4, 0x12, 0xa3} return btcutil.NewAddressPubKey(serializedPubKey, btcwire.MainNet) }, net: btcwire.MainNet, }, { name: "mainnet p2pk hybrid (0x06)", addr: "1Ja5rs7XBZnK88EuLVcFqYGMEbBitzchmX", valid: true, canDecode: false, result: btcutil.TstAddressPubKey( []byte{ 0x06, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, 0x4c, 0x95, 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, 0x3d, 0x8d, 0x79, 0x03, 0xc3, 0xeb, 0xec, 0x3a, 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, 0x52, 0xc6, 0xb4, 0x0d, 0x45, 0x26, 0x48, 0x38, 0xc0, 0xbd, 0x96, 0x85, 0x26, 0x62, 0xce, 0x6a, 0x84, 0x7b, 0x19, 0x73, 0x76, 0x83, 0x01, 0x60, 0xc6, 0xd2, 0xeb, 0x5e, 0x6a, 0x4c, 0x44, 0xd3, 0x3f, 0x45, 0x3e}, btcutil.PKFHybrid, btcwire.MainNet), f: func() (btcutil.Address, error) { serializedPubKey := []byte{ 0x06, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, 0x4c, 0x95, 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, 0x3d, 0x8d, 0x79, 0x03, 0xc3, 0xeb, 0xec, 0x3a, 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, 0x52, 0xc6, 0xb4, 0x0d, 0x45, 0x26, 0x48, 0x38, 0xc0, 0xbd, 0x96, 0x85, 0x26, 0x62, 0xce, 0x6a, 0x84, 0x7b, 0x19, 0x73, 0x76, 0x83, 0x01, 0x60, 0xc6, 0xd2, 0xeb, 0x5e, 0x6a, 0x4c, 0x44, 0xd3, 0x3f, 0x45, 0x3e} return btcutil.NewAddressPubKey(serializedPubKey, btcwire.MainNet) }, net: btcwire.MainNet, }, { name: "mainnet p2pk hybrid (0x07)", addr: "1ExqMmf6yMxcBMzHjbj41wbqYuqoX6uBLG", valid: true, canDecode: false, result: btcutil.TstAddressPubKey( []byte{ 0x07, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, 0x1b, 0xa1, 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, 0x61, 0xcf, 0x43, 0xe0, 0x01, 0xf9, 0x13, 0x7f, 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, 0xb1, 0x6e, 0x65, 0x37, 0xa5, 0x76, 0x78, 0x2e, 0xba, 0x66, 0x8a, 0x7e, 0xf8, 0xbd, 0x3b, 0x3c, 0xfb, 0x1e, 0xdb, 0x71, 0x17, 0xab, 0x65, 0x12, 0x9b, 0x8a, 0x2e, 0x68, 0x1f, 0x3c, 0x1e, 0x09, 0x08, 0xef, 0x7b}, btcutil.PKFHybrid, btcwire.MainNet), f: func() (btcutil.Address, error) { serializedPubKey := []byte{ 0x07, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, 0x1b, 0xa1, 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, 0x61, 0xcf, 0x43, 0xe0, 0x01, 0xf9, 0x13, 0x7f, 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, 0xb1, 0x6e, 0x65, 0x37, 0xa5, 0x76, 0x78, 0x2e, 0xba, 0x66, 0x8a, 0x7e, 0xf8, 0xbd, 0x3b, 0x3c, 0xfb, 0x1e, 0xdb, 0x71, 0x17, 0xab, 0x65, 0x12, 0x9b, 0x8a, 0x2e, 0x68, 0x1f, 0x3c, 0x1e, 0x09, 0x08, 0xef, 0x7b} return btcutil.NewAddressPubKey(serializedPubKey, btcwire.MainNet) }, net: btcwire.MainNet, }, { name: "testnet p2pk compressed (0x02)", addr: "mhiDPVP2nJunaAgTjzWSHCYfAqxxrxzjmo", valid: true, canDecode: false, result: btcutil.TstAddressPubKey( []byte{ 0x02, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, 0x4c, 0x95, 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, 0x3d, 0x8d, 0x79, 0x03, 0xc3, 0xeb, 0xec, 0x3a, 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, 0x52, 0xc6, 0xb4}, btcutil.PKFCompressed, btcwire.TestNet3), f: func() (btcutil.Address, error) { serializedPubKey := []byte{ 0x02, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, 0x4c, 0x95, 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, 0x3d, 0x8d, 0x79, 0x03, 0xc3, 0xeb, 0xec, 0x3a, 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, 0x52, 0xc6, 0xb4} return btcutil.NewAddressPubKey(serializedPubKey, btcwire.TestNet3) }, net: btcwire.TestNet3, }, { name: "testnet p2pk compressed (0x03)", addr: "mkPETRTSzU8MZLHkFKBmbKppxmdw9qT42t", valid: true, canDecode: false, result: btcutil.TstAddressPubKey( []byte{ 0x03, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, 0x1b, 0xa1, 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, 0x61, 0xcf, 0x43, 0xe0, 0x01, 0xf9, 0x13, 0x7f, 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, 0xb1, 0x6e, 0x65}, btcutil.PKFCompressed, btcwire.TestNet3), f: func() (btcutil.Address, error) { serializedPubKey := []byte{ 0x03, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, 0x1b, 0xa1, 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, 0x61, 0xcf, 0x43, 0xe0, 0x01, 0xf9, 0x13, 0x7f, 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, 0xb1, 0x6e, 0x65} return btcutil.NewAddressPubKey(serializedPubKey, btcwire.TestNet3) }, net: btcwire.TestNet3, }, { name: "testnet p2pk uncompressed (0x04)", addr: "mh8YhPYEAYs3E7EVyKtB5xrcfMExkkdEMF", valid: true, canDecode: false, result: btcutil.TstAddressPubKey( []byte{ 0x04, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, 0x01, 0x6b, 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, 0x1e, 0xb6, 0x8a, 0x38, 0x2e, 0x97, 0xb1, 0x48, 0x2e, 0xca, 0xd7, 0xb1, 0x48, 0xa6, 0x90, 0x9a, 0x5c, 0xb2, 0xe0, 0xea, 0xdd, 0xfb, 0x84, 0xcc, 0xf9, 0x74, 0x44, 0x64, 0xf8, 0x2e, 0x16, 0x0b, 0xfa, 0x9b, 0x8b, 0x64, 0xf9, 0xd4, 0xc0, 0x3f, 0x99, 0x9b, 0x86, 0x43, 0xf6, 0x56, 0xb4, 0x12, 0xa3}, btcutil.PKFUncompressed, btcwire.TestNet3), f: func() (btcutil.Address, error) { serializedPubKey := []byte{ 0x04, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, 0x01, 0x6b, 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, 0x1e, 0xb6, 0x8a, 0x38, 0x2e, 0x97, 0xb1, 0x48, 0x2e, 0xca, 0xd7, 0xb1, 0x48, 0xa6, 0x90, 0x9a, 0x5c, 0xb2, 0xe0, 0xea, 0xdd, 0xfb, 0x84, 0xcc, 0xf9, 0x74, 0x44, 0x64, 0xf8, 0x2e, 0x16, 0x0b, 0xfa, 0x9b, 0x8b, 0x64, 0xf9, 0xd4, 0xc0, 0x3f, 0x99, 0x9b, 0x86, 0x43, 0xf6, 0x56, 0xb4, 0x12, 0xa3} return btcutil.NewAddressPubKey(serializedPubKey, btcwire.TestNet3) }, net: btcwire.TestNet3, }, { name: "testnet p2pk hybrid (0x06)", addr: "my639vCVzbDZuEiX44adfTUg6anRomZLEP", valid: true, canDecode: false, result: btcutil.TstAddressPubKey( []byte{ 0x06, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, 0x4c, 0x95, 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, 0x3d, 0x8d, 0x79, 0x03, 0xc3, 0xeb, 0xec, 0x3a, 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, 0x52, 0xc6, 0xb4, 0x0d, 0x45, 0x26, 0x48, 0x38, 0xc0, 0xbd, 0x96, 0x85, 0x26, 0x62, 0xce, 0x6a, 0x84, 0x7b, 0x19, 0x73, 0x76, 0x83, 0x01, 0x60, 0xc6, 0xd2, 0xeb, 0x5e, 0x6a, 0x4c, 0x44, 0xd3, 0x3f, 0x45, 0x3e}, btcutil.PKFHybrid, btcwire.TestNet3), f: func() (btcutil.Address, error) { serializedPubKey := []byte{ 0x06, 0x19, 0x2d, 0x74, 0xd0, 0xcb, 0x94, 0x34, 0x4c, 0x95, 0x69, 0xc2, 0xe7, 0x79, 0x01, 0x57, 0x3d, 0x8d, 0x79, 0x03, 0xc3, 0xeb, 0xec, 0x3a, 0x95, 0x77, 0x24, 0x89, 0x5d, 0xca, 0x52, 0xc6, 0xb4, 0x0d, 0x45, 0x26, 0x48, 0x38, 0xc0, 0xbd, 0x96, 0x85, 0x26, 0x62, 0xce, 0x6a, 0x84, 0x7b, 0x19, 0x73, 0x76, 0x83, 0x01, 0x60, 0xc6, 0xd2, 0xeb, 0x5e, 0x6a, 0x4c, 0x44, 0xd3, 0x3f, 0x45, 0x3e} return btcutil.NewAddressPubKey(serializedPubKey, btcwire.TestNet3) }, net: btcwire.TestNet3, }, { name: "testnet p2pk hybrid (0x07)", addr: "muUnepk5nPPrxUTuTAhRqrpAQuSWS5fVii", valid: true, canDecode: false, result: btcutil.TstAddressPubKey( []byte{ 0x07, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, 0x1b, 0xa1, 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, 0x61, 0xcf, 0x43, 0xe0, 0x01, 0xf9, 0x13, 0x7f, 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, 0xb1, 0x6e, 0x65, 0x37, 0xa5, 0x76, 0x78, 0x2e, 0xba, 0x66, 0x8a, 0x7e, 0xf8, 0xbd, 0x3b, 0x3c, 0xfb, 0x1e, 0xdb, 0x71, 0x17, 0xab, 0x65, 0x12, 0x9b, 0x8a, 0x2e, 0x68, 0x1f, 0x3c, 0x1e, 0x09, 0x08, 0xef, 0x7b}, btcutil.PKFHybrid, btcwire.TestNet3), f: func() (btcutil.Address, error) { serializedPubKey := []byte{ 0x07, 0xb0, 0xbd, 0x63, 0x42, 0x34, 0xab, 0xbb, 0x1b, 0xa1, 0xe9, 0x86, 0xe8, 0x84, 0x18, 0x5c, 0x61, 0xcf, 0x43, 0xe0, 0x01, 0xf9, 0x13, 0x7f, 0x23, 0xc2, 0xc4, 0x09, 0x27, 0x3e, 0xb1, 0x6e, 0x65, 0x37, 0xa5, 0x76, 0x78, 0x2e, 0xba, 0x66, 0x8a, 0x7e, 0xf8, 0xbd, 0x3b, 0x3c, 0xfb, 0x1e, 0xdb, 0x71, 0x17, 0xab, 0x65, 0x12, 0x9b, 0x8a, 0x2e, 0x68, 0x1f, 0x3c, 0x1e, 0x09, 0x08, 0xef, 0x7b} return btcutil.NewAddressPubKey(serializedPubKey, btcwire.TestNet3) }, net: btcwire.TestNet3, }, } for _, test := range tests { var decoded btcutil.Address var err error if test.canDecode { // Decode addr and compare error against valid. decoded, err = btcutil.DecodeAddr(test.addr) if (err == nil) != test.valid { t.Errorf("%v: decoding test failed", test.name) return } } else { // The address can't be decoded directly, so instead // call the creation function. decoded, err = test.f() if (err == nil) != test.valid { t.Errorf("%v: creation test failed", test.name) return } } // If decoding succeeded, encode again and compare against the original. if err == nil { encoded := decoded.EncodeAddress() // Compare encoded addr against the original encoding. if test.addr != encoded { t.Errorf("%v: decoding and encoding produced different addressess: %v != %v", test.name, test.addr, encoded) return } // Perform type-specific calculations. var saddr []byte var net btcwire.BitcoinNet switch d := decoded.(type) { case *btcutil.AddressPubKeyHash: saddr = btcutil.TstAddressSAddr(encoded) // Net is not part of the Address interface and // must be calculated here. net = d.Net() case *btcutil.AddressScriptHash: saddr = btcutil.TstAddressSAddr(encoded) // Net is not part of the Address interface and // must be calculated here. net = d.Net() case *btcutil.AddressPubKey: // Ignore the error here since the script // address is checked below. saddr, _ = hex.DecodeString(d.String()) // Net is not part of the Address interface and // must be calculated here. net = d.Net() } // Check script address. if !bytes.Equal(saddr, decoded.ScriptAddress()) { t.Errorf("%v: script addresses do not match:\n%x != \n%x", test.name, saddr, decoded.ScriptAddress()) return } // Check networks. This check always succeeds for non-P2PKH and // non-P2SH addresses as both nets will be Go's default zero value. if net != test.net { t.Errorf("%v: calculated network does not match expected", test.name) return } } if !test.valid { // If address is invalid, but a creation function exists, // verify that it returns a nil addr and non-nil error. if test.f != nil { _, err := test.f() if err == nil { t.Errorf("%v: address is invalid but creating new address succeeded", test.name) return } } continue } // Valid test, compare address created with f against expected result. addr, err := test.f() if err != nil { t.Errorf("%v: address is valid but creating new address failed with error %v", test.name, err) return } if !reflect.DeepEqual(addr, test.result) { t.Errorf("%v: created address does not match expected result", test.name) return } } }
// handleCreateRawTransaction handles createrawtransaction commands. func handleCreateRawTransaction(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) { c := cmd.(*btcjson.CreateRawTransactionCmd) // Add all transaction inputs to a new transaction after performing // some validty checks. mtx := btcwire.NewMsgTx() for _, input := range c.Inputs { txHash, err := btcwire.NewShaHashFromStr(input.Txid) if err != nil { return nil, btcjson.ErrDecodeHexString } if input.Vout < 0 { return nil, btcjson.Error{ Code: btcjson.ErrInvalidParameter.Code, Message: "Invalid parameter, vout must be positive", } } prevOut := btcwire.NewOutPoint(txHash, uint32(input.Vout)) txIn := btcwire.NewTxIn(prevOut, []byte{}) mtx.AddTxIn(txIn) } // Add all transaction outputs to the transaction after performing // some validty checks. for encodedAddr, amount := range c.Amounts { // Ensure amount is in the valid range for monetary amounts. if amount <= 0 || amount > btcutil.MaxSatoshi { return nil, btcjson.Error{ Code: btcjson.ErrType.Code, Message: "Invalid amount", } } // Decode the provided address. addr, err := btcutil.DecodeAddr(encodedAddr) if err != nil { return nil, btcjson.Error{ Code: btcjson.ErrInvalidAddressOrKey.Code, Message: btcjson.ErrInvalidAddressOrKey.Message + ": " + err.Error(), } } // Ensure the address is one of the supported types and that // the network encoded with the address matches the network the // server is currently on. net := s.server.btcnet switch addr := addr.(type) { case *btcutil.AddressPubKeyHash: net = addr.Net() case *btcutil.AddressScriptHash: net = addr.Net() default: return nil, btcjson.ErrInvalidAddressOrKey } if net != s.server.btcnet { return nil, btcjson.Error{ Code: btcjson.ErrInvalidAddressOrKey.Code, Message: fmt.Sprintf("%s: %q", btcjson.ErrInvalidAddressOrKey.Message, encodedAddr), } } // Create a new script which pays to the provided address. pkScript, err := btcscript.PayToAddrScript(addr) if err != nil { return nil, btcjson.Error{ Code: btcjson.ErrInternal.Code, Message: err.Error(), } } txOut := btcwire.NewTxOut(amount, pkScript) mtx.AddTxOut(txOut) } // Return the serialized and hex-encoded transaction. mtxHex, err := messageToHex(mtx) if err != nil { return nil, err } return mtxHex, 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]int64, minconf int) (*CreatedTx, error) { // Create a new transaction which will include all input scripts. msgtx := btcwire.NewMsgTx() // Calculate minimum amount needed for inputs. var amt int64 for _, v := range pairs { // Error out if any amount is negative. if v <= 0 { return nil, ErrNonPositiveAmount } amt += v } // outputs is a tx.Pair slice representing each output that is created // by the transaction. outputs := make([]tx.Pair, 0, len(pairs)+1) // Add outputs to new tx. for addrStr, amt := range pairs { addr, err := btcutil.DecodeAddr(addrStr) 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) // Create amount, address pair and add to outputs. out := tx.Pair{ Amount: amt, PubkeyHash: addr.ScriptAddress(), } outputs = append(outputs, out) } // 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() // 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.AddressPubKeyHash var btcspent int64 var selectedInputs []*tx.Utxo var finalChangeUtxo *tx.Utxo // Get the number of satoshis to increment fee by when searching for // the minimum tx fee needed. fee := int64(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(a.UtxoStore, uint64(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. var changeUtxo *tx.Utxo change := btcin - uint64(amt+fee) if change > 0 { // Create a new address to spend leftover outputs to. // 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. MarkAddressForAccount(changeAddr.EncodeAddress(), a.Name()) } // 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)) changeUtxo = &tx.Utxo{ Amt: change, Out: tx.OutPoint{ // Hash is unset (zeroed) here and must be filled in // with the transaction hash of the complete // transaction. Index: uint32(len(pairs)), }, Height: -1, Subscript: pkScript, } copy(changeUtxo.AddrHash[:], changeAddr.ScriptAddress()) } // Selected unspent outputs become new transaction's inputs. for _, ip := range inputs { msgtx.AddTxIn(btcwire.NewTxIn((*btcwire.OutPoint)(&ip.Out), nil)) } for i, ip := range inputs { // Error is ignored as the length and network checks can never fail // for these inputs. addr, _ := btcutil.NewAddressPubKeyHash(ip.AddrHash[:], a.Wallet.Net()) privkey, err := a.AddressKey(addr) if err == wallet.ErrWalletLocked { return nil, wallet.ErrWalletLocked } else if err != nil { return nil, fmt.Errorf("cannot get address key: %v", err) } ai, err := a.AddressInfo(addr) if err != nil { return nil, fmt.Errorf("cannot get address info: %v", err) } sigscript, err := btcscript.SignatureScript(msgtx, i, ip.Subscript, 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 { // Fill Tx hash of change outpoint with transaction hash. if changeUtxo != nil { txHash, err := msgtx.TxSha() if err != nil { return nil, fmt.Errorf("cannot create transaction hash: %s", err) } copy(changeUtxo.Out.Hash[:], txHash[:]) // Add change to outputs. out := tx.Pair{ Amount: int64(change), PubkeyHash: changeAddr.ScriptAddress(), Change: true, } outputs = append(outputs, out) finalChangeUtxo = changeUtxo } selectedInputs = inputs btcspent = int64(btcin) 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].Subscript, 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) } } txid, err := msgtx.TxSha() if err != nil { return nil, fmt.Errorf("cannot create txid for created tx: %v", err) } buf := new(bytes.Buffer) msgtx.BtcEncode(buf, btcwire.ProtocolVersion) info := &CreatedTx{ rawTx: buf.Bytes(), txid: txid, time: time.Now(), inputs: selectedInputs, outputs: outputs, btcspent: btcspent, fee: fee, changeAddr: changeAddr, changeUtxo: finalChangeUtxo, } return info, nil }
// NtfnProcessedTx handles the btcws.ProcessedTxNtfn notification. func NtfnProcessedTx(n btcjson.Cmd, marshaled []byte) { ptn, ok := n.(*btcws.ProcessedTxNtfn) if !ok { log.Errorf("%v handler: unexpected type", n.Method()) return } // Create useful types from the JSON strings. receiver, err := btcutil.DecodeAddr(ptn.Receiver) if err != nil { log.Errorf("%v handler: error parsing receiver: %v", n.Method(), err) return } txID, err := btcwire.NewShaHashFromStr(ptn.TxID) if err != nil { log.Errorf("%v handler: error parsing txid: %v", n.Method(), err) return } blockHash, err := btcwire.NewShaHashFromStr(ptn.BlockHash) if err != nil { log.Errorf("%v handler: error parsing block hash: %v", n.Method(), err) return } pkscript, err := hex.DecodeString(ptn.PkScript) if err != nil { log.Errorf("%v handler: error parsing pkscript: %v", n.Method(), err) return } // Lookup account for address in result. aname, err := LookupAccountByAddress(ptn.Receiver) if err == ErrNotFound { log.Warnf("Received rescan result for unknown address %v", ptn.Receiver) return } a, err := accountstore.Account(aname) if err == ErrAcctNotExist { log.Errorf("Missing account for rescaned address %v", ptn.Receiver) } // Create RecvTx to add to tx history. t := &tx.RecvTx{ TxID: *txID, TxOutIdx: ptn.TxOutIndex, TimeReceived: time.Now().Unix(), BlockHeight: ptn.BlockHeight, BlockHash: *blockHash, BlockIndex: int32(ptn.BlockIndex), BlockTime: ptn.BlockTime, Amount: ptn.Amount, ReceiverHash: receiver.ScriptAddress(), } // 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. req := SendTxHistSyncRequest{ txid: *txID, response: make(chan SendTxHistSyncResponse), } SendTxHistSyncChans.access <- req resp := <-req.response if resp.ok { // Wait until send history has been recorded. <-resp.c SendTxHistSyncChans.remove <- *txID } // Record the tx history. a.TxStore.Lock() a.TxStore.s.InsertRecvTx(t) a.TxStore.Unlock() a.ScheduleTxStoreWrite() // 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. recvTxOP := btcwire.NewOutPoint(txID, ptn.TxOutIndex) previouslyNotifiedReq := NotifiedRecvTxRequest{ op: *recvTxOP, response: make(chan NotifiedRecvTxResponse), } NotifiedRecvTxChans.access <- previouslyNotifiedReq if <-previouslyNotifiedReq.response { NotifyMinedTx <- t NotifiedRecvTxChans.remove <- *recvTxOP } else { // Notify frontends of new recv tx and mark as notified. NotifiedRecvTxChans.add <- *recvTxOP NotifyNewTxDetails(frontendNotificationMaster, a.Name(), t.TxInfo(a.Name(), ptn.BlockHeight, a.Wallet.Net())[0]) } if !ptn.Spent { u := &tx.Utxo{ Amt: uint64(ptn.Amount), Height: ptn.BlockHeight, Subscript: pkscript, } copy(u.Out.Hash[:], txID[:]) u.Out.Index = uint32(ptn.TxOutIndex) copy(u.AddrHash[:], receiver.ScriptAddress()) copy(u.BlockHash[:], blockHash[:]) a.UtxoStore.Lock() a.UtxoStore.s.Insert(u) a.UtxoStore.Unlock() a.ScheduleUtxoStoreWrite() // If this notification came from mempool, notify frontends of // the new unconfirmed balance immediately. Otherwise, wait until // the blockconnected notifiation is processed. if u.Height == -1 { bal := a.CalculateBalance(0) - a.CalculateBalance(1) NotifyWalletBalanceUnconfirmed(frontendNotificationMaster, a.name, bal) } } // Notify frontends of new account balance. confirmed := a.CalculateBalance(1) unconfirmed := a.CalculateBalance(0) - confirmed NotifyWalletBalance(frontendNotificationMaster, a.name, confirmed) NotifyWalletBalanceUnconfirmed(frontendNotificationMaster, a.name, unconfirmed) }