// dumpSStxHashes dumps the hashes of all owned SStxs for some address. func (s *StakeStore) dumpSStxHashesForAddress(ns walletdb.ReadBucket, addr dcrutil.Address) ([]chainhash.Hash, error) { // Extract the HASH160 script hash; if it's not 20 bytes // long, return an error. hash160 := addr.ScriptAddress() if len(hash160) != 20 { str := "stake store is closed" return nil, stakeStoreError(ErrInput, str, nil) } _, addrIsP2SH := addr.(*dcrutil.AddressScriptHash) allTickets := s.dumpSStxHashes() var ticketsForAddr []chainhash.Hash // Access the database and store the result locally. for _, h := range allTickets { thisHash160, p2sh, err := fetchSStxRecordSStxTicketHash160(ns, &h) if err != nil { str := "failure getting ticket 0th out script hashes from db" return nil, stakeStoreError(ErrDatabase, str, err) } if addrIsP2SH != p2sh { continue } if bytes.Equal(hash160, thisHash160) { ticketsForAddr = append(ticketsForAddr, h) } } return ticketsForAddr, nil }
// TicketsWithAddress returns a slice of ticket hashes that are currently live // corresponding to the given address. // // This function is safe for concurrent access. func (b *BlockChain) TicketsWithAddress(address dcrutil.Address) ([]chainhash.Hash, error) { b.chainLock.RLock() sn := b.bestNode.stakeNode b.chainLock.RUnlock() tickets := sn.LiveTickets() var ticketsWithAddr []chainhash.Hash err := b.db.View(func(dbTx database.Tx) error { var err error for _, hash := range tickets { utxo, err := dbFetchUtxoEntry(dbTx, &hash) if err != nil { return err } _, addrs, _, err := txscript.ExtractPkScriptAddrs(txscript.DefaultScriptVersion, utxo.PkScriptByIndex(0), b.chainParams) if addrs[0].EncodeAddress() == address.EncodeAddress() { ticketsWithAddr = append(ticketsWithAddr, hash) } } return err }) if err != nil { return nil, err } return ticketsWithAddr, nil }
// dumpSStxHashes dumps the hashes of all owned SStxs for some address. func (s *StakeStore) dumpSStxHashesForAddress(addr dcrutil.Address) ([]chainhash.Hash, error) { // Extract the HASH160 script hash; if it's not 20 bytes // long, return an error. scriptHash := addr.ScriptAddress() if len(scriptHash) != 20 { str := "stake store is closed" return nil, stakeStoreError(ErrInput, str, nil) } var err error allTickets := s.dumpSStxHashes() var ticketsForAddr []chainhash.Hash // Access the database and store the result locally. err = s.namespace.View(func(tx walletdb.Tx) error { var err error var thisScrHash []byte for _, h := range allTickets { thisScrHash, err = fetchSStxRecordSStxTicketScriptHash(tx, &h) if err != nil { return err } if bytes.Equal(scriptHash, thisScrHash) { ticketsForAddr = append(ticketsForAddr, h) } } return nil }) if err != nil { str := "failure getting ticket 0th out script hashes from db" return nil, stakeStoreError(ErrDatabase, str, err) } return ticketsForAddr, nil }
// GetLiveTicketsForAddress gets all currently active tickets for a given // address. func (tmdb *TicketDB) GetLiveTicketsForAddress( address dcrutil.Address) ([]chainhash.Hash, error) { tmdb.mtx.Lock() defer tmdb.mtx.Unlock() var ltfa []chainhash.Hash for i := 0; i < BucketsSize; i++ { for _, ticket := range tmdb.maps.ticketMap[i] { // Load the ticket from the database and find the address that it's // going to. txReply, err := tmdb.database.FetchTxBySha(&ticket.SStxHash) if err != nil { return nil, err } _, addr, _, err := txscript.ExtractPkScriptAddrs(txReply[0].Tx.TxOut[0].Version, txReply[0].Tx.TxOut[0].PkScript, tmdb.chainParams) if err != nil { return nil, err } // Compare the HASH160 result and see if it's equal. if bytes.Equal(addr[0].ScriptAddress(), address.ScriptAddress()) { ltfa = append(ltfa, ticket.SStxHash) } } } return ltfa, nil }
// SearchRawTransactionsAsync returns an instance of a type that can be used to // get the result of the RPC at some future time by invoking the Receive // function on the returned instance. // // See SearchRawTransactions for the blocking version and more details. func (c *Client) SearchRawTransactionsAsync(address dcrutil.Address, skip, count int, reverse bool, filterAddrs []string) FutureSearchRawTransactionsResult { addr := address.EncodeAddress() verbose := dcrjson.Int(0) cmd := dcrjson.NewSearchRawTransactionsCmd(addr, verbose, &skip, &count, nil, &reverse, &filterAddrs) return c.sendCmd(cmd) }
// stakePoolUserInfo returns the stake pool user information for a given stake // pool user, keyed to their P2SH voting address. func (s *StakeStore) stakePoolUserInfo(ns walletdb.ReadBucket, user dcrutil.Address) (*StakePoolUser, error) { _, isScriptHash := user.(*dcrutil.AddressScriptHash) _, isP2PKH := user.(*dcrutil.AddressPubKeyHash) if !(isScriptHash || isP2PKH) { str := fmt.Sprintf("user %v is invalid", user.EncodeAddress()) return nil, stakeStoreError(ErrBadPoolUserAddr, str, nil) } scriptHashB := user.ScriptAddress() scriptHash := new([20]byte) copy(scriptHash[:], scriptHashB) stakePoolUser := new(StakePoolUser) // Catch missing user errors below and blank out the stake // pool user information for the section if the user has // no entries. missingValidTickets, missingInvalidTickets := false, false userTickets, fetchErrVal := fetchStakePoolUserTickets(ns, *scriptHash) if fetchErrVal != nil { stakeMgrErr, is := fetchErrVal.(StakeStoreError) if is { missingValidTickets = stakeMgrErr.ErrorCode == ErrPoolUserTicketsNotFound } else { return nil, fetchErrVal } } if missingValidTickets { userTickets = make([]*PoolTicket, 0) } invalTickets, fetchErrInval := fetchStakePoolUserInvalTickets(ns, *scriptHash) if fetchErrInval != nil { stakeMgrErr, is := fetchErrInval.(StakeStoreError) if is { missingInvalidTickets = stakeMgrErr.ErrorCode == ErrPoolUserInvalTcktsNotFound } else { return nil, fetchErrInval } } if missingInvalidTickets { invalTickets = make([]*chainhash.Hash, 0) } stakePoolUser.Tickets = userTickets stakePoolUser.InvalidTickets = invalTickets return stakePoolUser, nil }
// updateStakePoolUserInvalTickets updates the list of invalid stake pool // tickets for a given user. If the ticket does not currently exist in the // database, it adds it. func (s *StakeStore) updateStakePoolUserInvalTickets(ns walletdb.ReadWriteBucket, user dcrutil.Address, ticket *chainhash.Hash) error { _, isScriptHash := user.(*dcrutil.AddressScriptHash) _, isP2PKH := user.(*dcrutil.AddressPubKeyHash) if !(isScriptHash || isP2PKH) { str := fmt.Sprintf("user %v is invalid", user.EncodeAddress()) return stakeStoreError(ErrBadPoolUserAddr, str, nil) } scriptHashB := user.ScriptAddress() scriptHash := new([20]byte) copy(scriptHash[:], scriptHashB) return updateStakePoolInvalUserTickets(ns, *scriptHash, ticket) }
// SearchRawTransactionsVerboseAsync returns an instance of a type that can be // used to get the result of the RPC at some future time by invoking the Receive // function on the returned instance. // // See SearchRawTransactionsVerbose for the blocking version and more details. func (c *Client) SearchRawTransactionsVerboseAsync(address dcrutil.Address, skip, count int, includePrevOut bool, reverse bool, filterAddrs *[]string) FutureSearchRawTransactionsVerboseResult { addr := address.EncodeAddress() verbose := dcrjson.Int(1) var prevOut *int if includePrevOut { prevOut = dcrjson.Int(1) } cmd := dcrjson.NewSearchRawTransactionsCmd(addr, verbose, &skip, &count, prevOut, &reverse, filterAddrs) return c.sendCmd(cmd) }
// PayToSSRtx creates a new script to pay a transaction output to a // public key hash, but tags the output with OP_SSRTX. For use in constructing // valid SSRtx. func PayToSSRtx(addr dcrutil.Address) ([]byte, error) { if addr == nil { return nil, ErrUnsupportedAddress } // Only pay to pubkey hash and pay to script hash are // supported. scriptType := PubKeyHashTy switch addr := addr.(type) { case *dcrutil.AddressPubKeyHash: if addr.DSA(addr.Net()) != chainec.ECTypeSecp256k1 { return nil, ErrUnsupportedAddress } break case *dcrutil.AddressScriptHash: scriptType = ScriptHashTy break default: return nil, ErrUnsupportedAddress } hash := addr.ScriptAddress() if scriptType == PubKeyHashTy { return NewScriptBuilder().AddOp(OP_SSRTX).AddOp(OP_DUP). AddOp(OP_HASH160).AddData(hash).AddOp(OP_EQUALVERIFY). AddOp(OP_CHECKSIG).Script() } return NewScriptBuilder().AddOp(OP_SSRTX).AddOp(OP_HASH160). AddData(hash).AddOp(OP_EQUAL).Script() }
func (s *walletServer) NextAddress(ctx context.Context, req *pb.NextAddressRequest) ( *pb.NextAddressResponse, error) { var ( addr dcrutil.Address err error ) switch req.Kind { case pb.NextAddressRequest_BIP0044_EXTERNAL: addr, err = s.wallet.NewAddress(req.Account, waddrmgr.ExternalBranch) if err != nil { return nil, translateError(err) } case pb.NextAddressRequest_BIP0044_INTERNAL: addr, err = s.wallet.NewAddress(req.Account, waddrmgr.InternalBranch) if err != nil { return nil, translateError(err) } default: return nil, grpc.Errorf(codes.InvalidArgument, "kind=%v", req.Kind) } if err != nil { return nil, translateError(err) } pubKey, err := s.wallet.PubKeyForAddress(addr) if err != nil { return nil, translateError(err) } pubKeyAddr, err := dcrutil.NewAddressSecpPubKey(pubKey.Serialize(), s.wallet.ChainParams()) if err != nil { return nil, translateError(err) } return &pb.NextAddressResponse{ Address: addr.EncodeAddress(), PublicKey: pubKeyAddr.String(), }, nil }
// GenerateSStxAddrPush generates an OP_RETURN push for SSGen payment addresses in // an SStx. func GenerateSStxAddrPush(addr dcrutil.Address, amount dcrutil.Amount, limits uint16) ([]byte, error) { if addr == nil { return nil, ErrUnsupportedAddress } // Only pay to pubkey hash and pay to script hash are // supported. scriptType := PubKeyHashTy switch addr := addr.(type) { case *dcrutil.AddressPubKeyHash: if addr.DSA(addr.Net()) != chainec.ECTypeSecp256k1 { return nil, ErrUnsupportedAddress } break case *dcrutil.AddressScriptHash: scriptType = ScriptHashTy break default: return nil, ErrUnsupportedAddress } // Prefix dataPushes := []byte{ 0x6a, // OP_RETURN 0x1e, // OP_DATA_30 } hash := addr.ScriptAddress() amountBuffer := make([]byte, 8) binary.LittleEndian.PutUint64(amountBuffer, uint64(amount)) // Set the bit flag indicating pay to script hash. if scriptType == ScriptHashTy { amountBuffer[7] |= 1 << 7 } limitsBuffer := make([]byte, 2) binary.LittleEndian.PutUint16(limitsBuffer, limits) // Concatenate the prefix, pubkeyhash, and amount. addrOut := append(dataPushes, hash...) addrOut = append(addrOut, amountBuffer...) addrOut = append(addrOut, limitsBuffer...) return addrOut, nil }
// PayToAddrScript creates a new script to pay a transaction output to a the // specified address. func PayToAddrScript(addr dcrutil.Address) ([]byte, error) { switch addr := addr.(type) { case *dcrutil.AddressPubKeyHash: if addr == nil { return nil, ErrUnsupportedAddress } switch addr.DSA(addr.Net()) { case chainec.ECTypeSecp256k1: return payToPubKeyHashScript(addr.ScriptAddress()) case chainec.ECTypeEdwards: return payToPubKeyHashEdwardsScript(addr.ScriptAddress()) case chainec.ECTypeSecSchnorr: return payToPubKeyHashSchnorrScript(addr.ScriptAddress()) } case *dcrutil.AddressScriptHash: if addr == nil { return nil, ErrUnsupportedAddress } return payToScriptHashScript(addr.ScriptAddress()) case *dcrutil.AddressSecpPubKey: if addr == nil { return nil, ErrUnsupportedAddress } return payToPubKeyScript(addr.ScriptAddress()) case *dcrutil.AddressEdwardsPubKey: if addr == nil { return nil, ErrUnsupportedAddress } return payToEdwardsPubKeyScript(addr.ScriptAddress()) case *dcrutil.AddressSecSchnorrPubKey: if addr == nil { return nil, ErrUnsupportedAddress } return payToSchnorrPubKeyScript(addr.ScriptAddress()) } return nil, ErrUnsupportedAddress }
// SearchRawTransactionsVerboseAsync returns an instance of a type that can be // used to get the result of the RPC at some future time by invoking the Receive // function on the returned instance. // // See SearchRawTransactionsVerbose for the blocking version and more details. func (c *Client) SearchRawTransactionsVerboseAsync(address dcrutil.Address, skip, count int) FutureSearchRawTransactionsVerboseResult { addr := address.EncodeAddress() verbose := dcrjson.Int(1) cmd := dcrjson.NewSearchRawTransactionsCmd(addr, verbose, &skip, &count) return c.sendCmd(cmd) }
// ExistsAddressAsync returns an instance of a type that can be used to get the // result of the RPC at some future time by invoking the Receive function on the // returned instance. func (c *Client) ExistsAddressAsync(address dcrutil.Address) FutureExistsAddressResult { cmd := dcrjson.NewExistsAddressCmd(address.EncodeAddress()) return c.sendCmd(cmd) }
// FetchTxsForAddr looks up and returns all transactions which either // spend from a previously created output of the passed address, or // create a new output locked to the passed address. The, `limit` parameter // should be the max number of transactions to be returned. Additionally, if the // caller wishes to seek forward in the results some amount, the 'seek' // represents how many results to skip. func (db *LevelDb) FetchTxsForAddr(addr dcrutil.Address, skip int, limit int) ([]*database.TxListReply, error) { db.dbLock.Lock() defer db.dbLock.Unlock() // Enforce constraints for skip and limit. if skip < 0 { return nil, errors.New("offset for skip must be positive") } if limit < 1 { return nil, errors.New("value for limit must be positive") } // Parse address type, bailing on an unknown type. var addrKey []byte switch addr := addr.(type) { case *dcrutil.AddressPubKeyHash: hash160 := addr.Hash160() addrKey = hash160[:] case *dcrutil.AddressScriptHash: hash160 := addr.Hash160() addrKey = hash160[:] case *dcrutil.AddressSecpPubKey: hash160 := addr.AddressPubKeyHash().Hash160() addrKey = hash160[:] case *dcrutil.AddressEdwardsPubKey: hash160 := addr.AddressPubKeyHash().Hash160() addrKey = hash160[:] case *dcrutil.AddressSecSchnorrPubKey: hash160 := addr.AddressPubKeyHash().Hash160() addrKey = hash160[:] default: return nil, database.ErrUnsupportedAddressType } // Create the prefix for our search. addrPrefix := make([]byte, 23, 23) copy(addrPrefix[0:3], addrIndexKeyPrefix) copy(addrPrefix[3:23], addrKey) iter := db.lDb.NewIterator(bytesPrefix(addrPrefix), nil) for skip != 0 { iter.Next() skip-- } // Iterate through all address indexes that match the targeted prefix. var replies []*database.TxListReply var rawIndex [database.AddrIndexKeySize]byte for iter.Next() { if limit == 0 { break } copy(rawIndex[:], iter.Key()) addrIndex := unpackTxIndex(rawIndex) tx, blkSha, blkHeight, _, err := db.fetchTxDataByLoc( int64(addrIndex.Height), int(addrIndex.TxOffset), int(addrIndex.TxLen), []byte{}) if err != nil { log.Warnf("tx listed in addrindex record not found, height: %v"+ " offset: %v, len: %v", addrIndex.Height, addrIndex.TxOffset, addrIndex.TxLen) limit-- continue } var txSha chainhash.Hash if tx != nil { txSha = tx.TxSha() } txReply := &database.TxListReply{Sha: &txSha, Tx: tx, BlkSha: blkSha, Height: blkHeight, TxSpent: []bool{}, Err: err} replies = append(replies, txReply) limit-- } iter.Release() if err := iter.Error(); err != nil { return nil, err } return replies, nil }
// FetchTxsForAddr looks up and returns all transactions which either // spend from a previously created output of the passed address, or // create a new output locked to the passed address. The, `limit` parameter // should be the max number of transactions to be returned. Additionally, if the // caller wishes to seek forward in the results some amount, the 'seek' // represents how many results to skip. func (db *LevelDb) FetchTxsForAddr(addr dcrutil.Address, skip int, limit int, reverse bool) ([]*database.TxListReply, int, error) { db.dbLock.Lock() defer db.dbLock.Unlock() // Enforce constraints for skip and limit. if skip < 0 { return nil, 0, errors.New("offset for skip must be positive") } if limit < 0 { return nil, 0, errors.New("value for limit must be positive") } // Parse address type, bailing on an unknown type. var addrKey []byte switch addr := addr.(type) { case *dcrutil.AddressPubKeyHash: hash160 := addr.Hash160() addrKey = hash160[:] case *dcrutil.AddressScriptHash: hash160 := addr.Hash160() addrKey = hash160[:] case *dcrutil.AddressSecpPubKey: hash160 := addr.AddressPubKeyHash().Hash160() addrKey = hash160[:] case *dcrutil.AddressEdwardsPubKey: hash160 := addr.AddressPubKeyHash().Hash160() addrKey = hash160[:] case *dcrutil.AddressSecSchnorrPubKey: hash160 := addr.AddressPubKeyHash().Hash160() addrKey = hash160[:] default: return nil, 0, database.ErrUnsupportedAddressType } // Create the prefix for our search. addrPrefix := make([]byte, 23, 23) copy(addrPrefix[0:3], addrIndexKeyPrefix) copy(addrPrefix[3:23], addrKey) iter := db.lDb.NewIterator(bytesPrefix(addrPrefix), nil) skipped := 0 if reverse { // Go to the last element if reverse iterating. iter.Last() // Skip "one past" the last element so the loops below don't // miss the last element due to Prev() being called first. // We can safely ignore iterator exhaustion since the loops // below will see there's no keys anyway. iter.Next() } for skip != 0 && advanceIterator(iter, reverse) { skip-- skipped++ } // Iterate through all address indexes that match the targeted prefix. var replies []*database.TxListReply var rawIndex [database.AddrIndexKeySize]byte for advanceIterator(iter, reverse) && limit != 0 { copy(rawIndex[:], iter.Key()) addrIndex := unpackTxIndex(rawIndex) tx, blkSha, blkHeight, _, err := db.fetchTxDataByLoc( int64(addrIndex.Height), int(addrIndex.TxOffset), int(addrIndex.TxLen), []byte{}) if err != nil { log.Warnf("tx listed in addrindex record not found, height: %v"+ " offset: %v, len: %v", addrIndex.Height, addrIndex.TxOffset, addrIndex.TxLen) limit-- continue } var txSha chainhash.Hash if tx != nil { txSha = tx.TxSha() } txReply := &database.TxListReply{Sha: &txSha, Tx: tx, BlkSha: blkSha, Height: blkHeight, TxSpent: []bool{}, Err: err} replies = append(replies, txReply) limit-- } iter.Release() if err := iter.Error(); err != nil { return nil, 0, err } return replies, skipped, nil }
// initialize initializes an address pool for usage by loading the latest // unused address from the blockchain itself. func (a *addressPool) initialize(branch uint32, w *Wallet) error { a.addresses = make([]string, 0) a.mutex = new(sync.Mutex) a.wallet = w a.branch = branch var err error // Retrieve the last addresses from wallet closing and storing. lastExtAddr, lastIntAddr, err := w.Manager.LastUsedAddresses() if err != nil { return err } var lastSavedAddr dcrutil.Address // Get the last managed address for the account and branch. var lastIndex uint32 if branch == waddrmgr.InternalBranch { _, lastIndex, err = a.wallet.Manager.LastInternalAddress(waddrmgr.DefaultAccountNum) if err != nil { return err } lastSavedAddr = lastIntAddr } if branch == waddrmgr.ExternalBranch { _, lastIndex, err = a.wallet.Manager.LastExternalAddress(waddrmgr.DefaultAccountNum) if err != nil { return err } lastSavedAddr = lastExtAddr } // Get the actual last index as recorded in the blockchain. traversed := 0 actualLastIndex := lastIndex for actualLastIndex != 0 && traversed != addressPoolBuffer { addr, err := a.wallet.Manager.GetAddress(actualLastIndex, waddrmgr.DefaultAccountNum, branch) if err != nil { return err } // Start with the address on tip if address reuse is disabled. if !w.addressReuse { // If address reuse is disabled, we compare to the last // stored address. if lastSavedAddr != nil { lsaH160 := lastSavedAddr.Hash160() thisH160 := addr.Hash160() if *lsaH160 == *thisH160 { // We actually append this address because the // LastUsedAddresses function in Manager actually // stores the next to-be-used address rather than // the last used address. See Close below. a.addresses = append([]string{addr.EncodeAddress()}, a.addresses...) break } } } else { // Otherwise, search the blockchain for the last actually used // address. exists, err := a.wallet.existsAddressOnChain(addr) if err != nil { return err } if exists { break } } // Insert this unused address into the cache. a.addresses = append([]string{addr.EncodeAddress()}, a.addresses...) actualLastIndex-- traversed++ } a.cursor = 0 return nil }
// addrToKey converts known address types to an addrindex key. An error is // returned for unsupported types. func addrToKey(addr dcrutil.Address, params *chaincfg.Params) ([addrKeySize]byte, error) { switch addr := addr.(type) { case *dcrutil.AddressPubKeyHash: switch addr.DSA(params) { case chainec.ECTypeSecp256k1: var result [addrKeySize]byte result[0] = addrKeyTypePubKeyHash copy(result[1:], addr.Hash160()[:]) return result, nil case chainec.ECTypeEdwards: var result [addrKeySize]byte result[0] = addrKeyTypePubKeyHashEdwards copy(result[1:], addr.Hash160()[:]) return result, nil case chainec.ECTypeSecSchnorr: var result [addrKeySize]byte result[0] = addrKeyTypePubKeyHashSchnorr copy(result[1:], addr.Hash160()[:]) return result, nil } case *dcrutil.AddressScriptHash: var result [addrKeySize]byte result[0] = addrKeyTypeScriptHash copy(result[1:], addr.Hash160()[:]) return result, nil case *dcrutil.AddressSecpPubKey: var result [addrKeySize]byte result[0] = addrKeyTypePubKeyHash copy(result[1:], addr.AddressPubKeyHash().Hash160()[:]) return result, nil case *dcrutil.AddressEdwardsPubKey: var result [addrKeySize]byte result[0] = addrKeyTypePubKeyHashEdwards copy(result[1:], addr.AddressPubKeyHash().Hash160()[:]) return result, nil case *dcrutil.AddressSecSchnorrPubKey: var result [addrKeySize]byte result[0] = addrKeyTypePubKeyHashSchnorr copy(result[1:], addr.AddressPubKeyHash().Hash160()[:]) return result, nil } return [addrKeySize]byte{}, errUnsupportedAddressType }