diff --git a/internal/rpchelp/helpdescs_en_US.go b/internal/rpchelp/helpdescs_en_US.go index ec32bcb2c..14195a460 100644 --- a/internal/rpchelp/helpdescs_en_US.go +++ b/internal/rpchelp/helpdescs_en_US.go @@ -594,6 +594,10 @@ var helpDescsEnUS = map[string]string{ "redeemmultisigouts-toaddress": "Address to look for (if not internal addresses).", "redeemmultisigouts-fromscraddress": "Input script hash address.", + // RemoveImported help. + "removeimported--synopsis": "Removes the provided imported script or private key from the wallet.", + "removeimported-data": "The imported script or private key to remove.", + // RescanWallet help. "rescanwallet--synopsis": "Rescan the block chain for wallet data, blocking until the rescan completes or exits with an error", "rescanwallet-beginheight": "The height of the first block to begin the rescan from", diff --git a/internal/rpchelp/methods.go b/internal/rpchelp/methods.go index 17bfd7fe4..ae89575cf 100644 --- a/internal/rpchelp/methods.go +++ b/internal/rpchelp/methods.go @@ -77,6 +77,7 @@ var Methods = []struct { {"redeemmultisigout", []interface{}{(*types.RedeemMultiSigOutResult)(nil)}}, {"redeemmultisigouts", []interface{}{(*types.RedeemMultiSigOutResult)(nil)}}, {"renameaccount", nil}, + {"removeimported", nil}, {"rescanwallet", nil}, {"revoketickets", nil}, {"sendfrom", returnsString}, diff --git a/rpc/jsonrpc/methods.go b/rpc/jsonrpc/methods.go index 35aaee8f7..21a3f0072 100644 --- a/rpc/jsonrpc/methods.go +++ b/rpc/jsonrpc/methods.go @@ -41,9 +41,9 @@ import ( // API version constants const ( - jsonrpcSemverString = "6.2.0" + jsonrpcSemverString = "6.3.0" jsonrpcSemverMajor = 6 - jsonrpcSemverMinor = 2 + jsonrpcSemverMinor = 3 jsonrpcSemverPatch = 0 ) @@ -119,6 +119,7 @@ var handlers = map[string]handler{ "sweepaccount": {fn: (*Server).sweepAccount}, "redeemmultisigout": {fn: (*Server).redeemMultiSigOut}, "redeemmultisigouts": {fn: (*Server).redeemMultiSigOuts}, + "removeimported": {fn: (*Server).removeImported}, "stakepooluserinfo": {fn: (*Server).stakePoolUserInfo}, "ticketsforaddress": {fn: (*Server).ticketsForAddress}, "validateaddress": {fn: (*Server).validateAddress}, @@ -2426,6 +2427,42 @@ func (s *Server) redeemMultiSigOuts(ctx context.Context, icmd interface{}) (inte return types.RedeemMultiSigOutsResult{Results: rmsoResults}, nil } +// removeImported purges the provided imported script or private key from the wallet. +func (s *Server) removeImported(ctx context.Context, icmd interface{}) (interface{}, error) { + cmd := icmd.(*types.RemoveImportedCmd) + w, ok := s.walletLoader.LoadedWallet() + if !ok { + return nil, errUnloadedWallet + } + + // Parse the imported entity as a private key and remove the associated address. + wif, wErr := dcrutil.DecodeWIF(cmd.Data, w.ChainParams().PrivateKeyID) + if wErr == nil { + serializedPubKey := wif.SerializePubKey() + pubKeyHash := dcrutil.Hash160(serializedPubKey) + addr, err := dcrutil.NewAddressPubKeyHash(pubKeyHash, w.ChainParams(), + dcrec.STEcdsaSecp256k1) + if err != nil { + return nil, + rpcErrorf(dcrjson.ErrRPCInvalidAddressOrKey, + "unable to generate address from pubkey hash: %v", err) + } + + err = w.RemoveAddress(addr) + return nil, err + } + + // Parse the imported entity as a script and remove the associated address. + addr, err := dcrutil.NewAddressScriptHash([]byte(cmd.Data), w.ChainParams()) + if err != nil { + return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, + "invalid private key or script provided") + } + + err = w.RemoveAddress(addr) + return nil, err +} + // rescanWallet initiates a rescan of the block chain for wallet data, blocking // until the rescan completes or exits with an error. func (s *Server) rescanWallet(ctx context.Context, icmd interface{}) (interface{}, error) { diff --git a/rpc/jsonrpc/rpcserverhelp.go b/rpc/jsonrpc/rpcserverhelp.go index 050555e22..93969a437 100644 --- a/rpc/jsonrpc/rpcserverhelp.go +++ b/rpc/jsonrpc/rpcserverhelp.go @@ -54,6 +54,7 @@ func helpDescsEnUS() map[string]string { "redeemmultisigout": "redeemmultisigout \"hash\" index tree (\"address\")\n\nTakes the input and constructs a P2PKH paying to the specified address.\n\nArguments:\n1. hash (string, required) Hash of the input transaction\n2. index (numeric, required) Idx of the input transaction\n3. tree (numeric, required) Tree the transaction is on.\n4. address (string, optional) Address to pay to.\n\nResult:\n{\n \"hex\": \"value\", (string) Resulting hash.\n \"complete\": true|false, (boolean) Shows if opperation was completed.\n \"errors\": [{ (array of object) Any errors generated.\n \"txid\": \"value\", (string) The transaction hash of the referenced previous output\n \"vout\": n, (numeric) The output index of the referenced previous output\n \"scriptSig\": \"value\", (string) The hex-encoded signature script\n \"sequence\": n, (numeric) Script sequence number\n \"error\": \"value\", (string) Verification or signing error related to the input\n },...], \n} \n", "redeemmultisigouts": "redeemmultisigouts \"fromscraddress\" (\"toaddress\" number)\n\nTakes a hash, looks up all unspent outpoints and generates list artially signed transactions spending to either an address specified or internal addresses\n\nArguments:\n1. fromscraddress (string, required) Input script hash address.\n2. toaddress (string, optional) Address to look for (if not internal addresses).\n3. number (numeric, optional) Number of outpoints found.\n\nResult:\n{\n \"hex\": \"value\", (string) Resulting hash.\n \"complete\": true|false, (boolean) Shows if opperation was completed.\n \"errors\": [{ (array of object) Any errors generated.\n \"txid\": \"value\", (string) The transaction hash of the referenced previous output\n \"vout\": n, (numeric) The output index of the referenced previous output\n \"scriptSig\": \"value\", (string) The hex-encoded signature script\n \"sequence\": n, (numeric) Script sequence number\n \"error\": \"value\", (string) Verification or signing error related to the input\n },...], \n} \n", "renameaccount": "renameaccount \"oldaccount\" \"newaccount\"\n\nRenames an account.\n\nArguments:\n1. oldaccount (string, required) The old account name to rename\n2. newaccount (string, required) The new name for the account\n\nResult:\nNothing\n", + "removeimported": "removeimported \"data\"\n\nRemoves the provided imported script or private key from the wallet.\n\nArguments:\n1. data (string, required) The imported script or private key to remove.\n\nResult:\nNothing\n", "rescanwallet": "rescanwallet (beginheight=0)\n\nRescan the block chain for wallet data, blocking until the rescan completes or exits with an error\n\nArguments:\n1. beginheight (numeric, optional, default=0) The height of the first block to begin the rescan from\n\nResult:\nNothing\n", "revoketickets": "revoketickets\n\nRequests the wallet create revovactions for any previously missed tickets. Wallet must be unlocked.\n\nArguments:\nNone\n\nResult:\nNothing\n", "sendfrom": "sendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 \"comment\" \"commentto\")\n\nDEPRECATED -- Authors, signs, and sends a transaction that outputs some amount to a payment address.\nA change output is automatically included to send extra output value back to the original account.\n\nArguments:\n1. fromaccount (string, required) Account to pick unspent outputs from\n2. toaddress (string, required) Address to pay\n3. amount (numeric, required) Amount to send to the payment address valued in decred\n4. minconf (numeric, optional, default=1) Minimum number of block confirmations required before a transaction output is eligible to be spent\n5. comment (string, optional) Unused\n6. commentto (string, optional) Unused\n\nResult:\n\"value\" (string) The transaction hash of the sent transaction\n", @@ -84,4 +85,4 @@ var localeHelpDescs = map[string]func() map[string]string{ "en_US": helpDescsEnUS, } -var requestUsages = "abandontransaction \"hash\"\naccountaddressindex \"account\" branch\naccountsyncaddressindex \"account\" branch index\naddmultisigaddress nrequired [\"key\",...] (\"account\")\naddticket \"tickethex\"\nconsolidate inputs (\"account\" \"address\")\ncreatemultisig nrequired [\"key\",...]\ncreatenewaccount \"account\"\ndumpprivkey \"address\"\ngeneratevote \"blockhash\" height \"tickethash\" votebits \"votebitsext\"\ngetaccountaddress \"account\"\ngetaccount \"address\"\ngetaddressesbyaccount \"account\"\ngetbalance (\"account\" minconf=1)\ngetbestblockhash\ngetbestblock\ngetblockcount\ngetblockhash index\ngetinfo\ngetmasterpubkey (\"account\")\ngetmultisigoutinfo \"hash\" index\ngetnewaddress (\"account\" \"gappolicy\")\ngetrawchangeaddress (\"account\")\ngetreceivedbyaccount \"account\" (minconf=1)\ngetreceivedbyaddress \"address\" (minconf=1)\ngetstakeinfo\ngetticketfee\ngettickets includeimmature\ngettransaction \"txid\" (includewatchonly=false)\ngetunconfirmedbalance (\"account\")\ngetvotechoices\ngetwalletfee\nhelp (\"command\")\nimportprivkey \"privkey\" (\"label\" rescan=true scanfrom)\nimportscript \"hex\" (rescan=true scanfrom)\nlistaccounts (minconf=1)\nlistaddresstransactions [\"address\",...] (\"account\")\nlistalltransactions (\"account\")\nlistlockunspent\nlistreceivedbyaccount (minconf=1 includeempty=false includewatchonly=false)\nlistreceivedbyaddress (minconf=1 includeempty=false includewatchonly=false)\nlistscripts\nlistsinceblock (\"blockhash\" targetconfirmations=1 includewatchonly=false)\nlisttransactions (\"account\" count=10 from=0 includewatchonly=false)\nlistunspent (minconf=1 maxconf=9999999 [\"address\",...])\nlockunspent unlock [{\"amount\":n.nnn,\"txid\":\"value\",\"vout\":n,\"tree\":n},...]\npurchaseticket \"fromaccount\" spendlimit (minconf=1 \"ticketaddress\" numtickets \"pooladdress\" poolfees expiry \"comment\" ticketfee)\nredeemmultisigout \"hash\" index tree (\"address\")\nredeemmultisigouts \"fromscraddress\" (\"toaddress\" number)\nrenameaccount \"oldaccount\" \"newaccount\"\nrescanwallet (beginheight=0)\nrevoketickets\nsendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 \"comment\" \"commentto\")\nsendmany \"fromaccount\" {\"address\":amount,...} (minconf=1 \"comment\")\nsendtoaddress \"address\" amount (\"comment\" \"commentto\")\nsendtomultisig \"fromaccount\" amount [\"pubkey\",...] (nrequired=1 minconf=1 \"comment\")\nsetticketfee fee\nsettxfee amount\nsetvotechoice \"agendaid\" \"choiceid\"\nsignmessage \"address\" \"message\"\nsignrawtransaction \"rawtx\" ([{\"txid\":\"value\",\"vout\":n,\"tree\":n,\"scriptpubkey\":\"value\",\"redeemscript\":\"value\"},...] [\"privkey\",...] flags=\"ALL\")\nsignrawtransactions [\"rawtx\",...] (send=true)\nstakepooluserinfo \"user\"\nsweepaccount \"sourceaccount\" \"destinationaddress\" (requiredconfirmations feeperkb)\nticketsforaddress \"address\"\nvalidateaddress \"address\"\nverifymessage \"address\" \"signature\" \"message\"\nversion\nwalletinfo\nwalletislocked\nwalletlock\nwalletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\nwalletpassphrase \"passphrase\" timeout" +var requestUsages = "abandontransaction \"hash\"\naccountaddressindex \"account\" branch\naccountsyncaddressindex \"account\" branch index\naddmultisigaddress nrequired [\"key\",...] (\"account\")\naddticket \"tickethex\"\nconsolidate inputs (\"account\" \"address\")\ncreatemultisig nrequired [\"key\",...]\ncreatenewaccount \"account\"\ndumpprivkey \"address\"\ngeneratevote \"blockhash\" height \"tickethash\" votebits \"votebitsext\"\ngetaccountaddress \"account\"\ngetaccount \"address\"\ngetaddressesbyaccount \"account\"\ngetbalance (\"account\" minconf=1)\ngetbestblockhash\ngetbestblock\ngetblockcount\ngetblockhash index\ngetinfo\ngetmasterpubkey (\"account\")\ngetmultisigoutinfo \"hash\" index\ngetnewaddress (\"account\" \"gappolicy\")\ngetrawchangeaddress (\"account\")\ngetreceivedbyaccount \"account\" (minconf=1)\ngetreceivedbyaddress \"address\" (minconf=1)\ngetstakeinfo\ngetticketfee\ngettickets includeimmature\ngettransaction \"txid\" (includewatchonly=false)\ngetunconfirmedbalance (\"account\")\ngetvotechoices\ngetwalletfee\nhelp (\"command\")\nimportprivkey \"privkey\" (\"label\" rescan=true scanfrom)\nimportscript \"hex\" (rescan=true scanfrom)\nlistaccounts (minconf=1)\nlistaddresstransactions [\"address\",...] (\"account\")\nlistalltransactions (\"account\")\nlistlockunspent\nlistreceivedbyaccount (minconf=1 includeempty=false includewatchonly=false)\nlistreceivedbyaddress (minconf=1 includeempty=false includewatchonly=false)\nlistscripts\nlistsinceblock (\"blockhash\" targetconfirmations=1 includewatchonly=false)\nlisttransactions (\"account\" count=10 from=0 includewatchonly=false)\nlistunspent (minconf=1 maxconf=9999999 [\"address\",...])\nlockunspent unlock [{\"amount\":n.nnn,\"txid\":\"value\",\"vout\":n,\"tree\":n},...]\npurchaseticket \"fromaccount\" spendlimit (minconf=1 \"ticketaddress\" numtickets \"pooladdress\" poolfees expiry \"comment\" ticketfee)\nredeemmultisigout \"hash\" index tree (\"address\")\nredeemmultisigouts \"fromscraddress\" (\"toaddress\" number)\nrenameaccount \"oldaccount\" \"newaccount\"\nremoveimported \"data\"\nrescanwallet (beginheight=0)\nrevoketickets\nsendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 \"comment\" \"commentto\")\nsendmany \"fromaccount\" {\"address\":amount,...} (minconf=1 \"comment\")\nsendtoaddress \"address\" amount (\"comment\" \"commentto\")\nsendtomultisig \"fromaccount\" amount [\"pubkey\",...] (nrequired=1 minconf=1 \"comment\")\nsetticketfee fee\nsettxfee amount\nsetvotechoice \"agendaid\" \"choiceid\"\nsignmessage \"address\" \"message\"\nsignrawtransaction \"rawtx\" ([{\"txid\":\"value\",\"vout\":n,\"tree\":n,\"scriptpubkey\":\"value\",\"redeemscript\":\"value\"},...] [\"privkey\",...] flags=\"ALL\")\nsignrawtransactions [\"rawtx\",...] (send=true)\nstakepooluserinfo \"user\"\nsweepaccount \"sourceaccount\" \"destinationaddress\" (requiredconfirmations feeperkb)\nticketsforaddress \"address\"\nvalidateaddress \"address\"\nverifymessage \"address\" \"signature\" \"message\"\nversion\nwalletinfo\nwalletislocked\nwalletlock\nwalletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\nwalletpassphrase \"passphrase\" timeout" diff --git a/rpc/jsonrpc/types/methods.go b/rpc/jsonrpc/types/methods.go index 9912d4039..7353646ba 100644 --- a/rpc/jsonrpc/types/methods.go +++ b/rpc/jsonrpc/types/methods.go @@ -899,6 +899,11 @@ func NewRenameAccountCmd(oldAccount, newAccount string) *RenameAccountCmd { } } +// RemoveImportedCmd defines the removeimported JSON-RPC command. +type RemoveImportedCmd struct { + Data string +} + // RescanWalletCmd describes the rescanwallet JSON-RPC request and parameters. type RescanWalletCmd struct { BeginHeight *int `jsonrpcdefault:"0"` @@ -1326,6 +1331,7 @@ func init() { {"redeemmultisigout", (*RedeemMultiSigOutCmd)(nil)}, {"redeemmultisigouts", (*RedeemMultiSigOutsCmd)(nil)}, {"renameaccount", (*RenameAccountCmd)(nil)}, + {"removeimported", (*RemoveImportedCmd)(nil)}, {"rescanwallet", (*RescanWalletCmd)(nil)}, {"revoketickets", (*RevokeTicketsCmd)(nil)}, {"sendfrom", (*SendFromCmd)(nil)}, diff --git a/wallet/udb/addressdb.go b/wallet/udb/addressdb.go index 48a161868..c1ac090bb 100644 --- a/wallet/udb/addressdb.go +++ b/wallet/udb/addressdb.go @@ -8,6 +8,7 @@ package udb import ( "crypto/sha256" "encoding/binary" + "fmt" "time" "github.com/decred/dcrwallet/errors" @@ -796,6 +797,34 @@ func putAddrAccountIndex(ns walletdb.ReadWriteBucket, account uint32, addrHash [ return nil } +// removeAddrAccountIndex removes the provided key to the address account index of the database. +func removeAddrAccountIndex(ns walletdb.ReadWriteBucket, addrHash []byte) error { + bucket := ns.NestedReadWriteBucket(addrAcctIdxBucketName) + accountB := bucket.Get(addrHash) + + if accountB != nil { + msg := fmt.Sprintf("no account value found for address hash %x", addrHash) + return errors.E(errors.IO, msg) + } + + err := bucket.Delete(addrHash) + if err != nil { + return errors.E(errors.IO, err) + } + + abkt := bucket.NestedReadWriteBucket(accountB) + if err != nil { + return errors.E(errors.IO, err) + } + + err = abkt.Delete(addrHash) + if err != nil { + return errors.E(errors.IO, err) + } + + return nil +} + // putAccountRow stores the provided account information to the database. This // is used a common base for storing the various account types. func putAccountRow(ns walletdb.ReadWriteBucket, account uint32, row *dbAccountRow) error { @@ -1093,6 +1122,18 @@ func putAddress(ns walletdb.ReadWriteBucket, addressID []byte, row *dbAddressRow return putAddrAccountIndex(ns, row.account, addrHash[:]) } +// removeAddress removes the provided address id. +func removeAddress(ns walletdb.ReadWriteBucket, addressID []byte) error { + bucket := ns.NestedReadWriteBucket(addrBucketName) + addrHash := sha256.Sum256(addressID) + err := bucket.Delete(addrHash[:]) + if err != nil { + return errors.E(errors.IO, err) + } + + return removeAddrAccountIndex(ns, addrHash[:]) +} + // putChainedAddress stores the provided chained address information to the // database. func putChainedAddress(ns walletdb.ReadWriteBucket, addressID []byte, account uint32, diff --git a/wallet/udb/addressmanager.go b/wallet/udb/addressmanager.go index dfec398c0..32aa6a52c 100644 --- a/wallet/udb/addressmanager.go +++ b/wallet/udb/addressmanager.go @@ -1261,6 +1261,11 @@ func (m *Manager) ImportScript(ns walletdb.ReadWriteBucket, script []byte) (Mana return newScriptAddress(m, ImportedAddrAccount, scriptHash) } +// RemoveAddress removes the provided address from the manager. +func (m *Manager) RemoveAddress(ns walletdb.ReadWriteBucket, addressID []byte) error { + return removeAddress(ns, addressID) +} + // IsLocked returns whether or not the address managed is locked. When it is // unlocked, the decryption key needed to decrypt private keys used for signing // is in memory. diff --git a/wallet/udb/txdb.go b/wallet/udb/txdb.go index 27785eacf..7a52785ad 100644 --- a/wallet/udb/txdb.go +++ b/wallet/udb/txdb.go @@ -1359,6 +1359,19 @@ func extractRawUnminedTx(v []byte) []byte { return v[8:] } +func fetchRawUnminedTxRecordPkScript(v []byte, index uint32, scrLoc uint32, scrLen uint32) ([]byte, error) { + scrLocInt := int(scrLoc) + scrLenInt := int(scrLen) + if scrLocInt > len(v)-1 || scrLocInt+scrLenInt > len(v) { + return nil, errors.E(errors.IO, errors.Errorf( + "invalid script offset %d for unmined tx", scrLocInt)) + } + + pkScript := make([]byte, scrLenInt) + copy(pkScript, v[scrLocInt:scrLocInt+scrLenInt]) + return pkScript, nil +} + // Unmined transaction credits use the canonical serialization format: // // [0:32] Transaction hash (32 bytes) @@ -1704,6 +1717,15 @@ func putTxScript(ns walletdb.ReadWriteBucket, script []byte) error { return nil } +func deleteTxScript(ns walletdb.ReadWriteBucket, script []byte) error { + k := keyTxScript(script) + err := ns.NestedReadWriteBucket(bucketScripts).Delete(k) + if err != nil { + return errors.E(errors.IO, err) + } + return nil +} + func existsTxScript(ns walletdb.ReadBucket, hash []byte) []byte { vOrig := ns.NestedReadBucket(bucketScripts).Get(hash) if vOrig == nil { @@ -2390,3 +2412,113 @@ func upgradeToVersion3(ns walletdb.ReadWriteBucket, chainParams *chaincfg.Params return nil } + +// RemovePkScriptCreditsAndDebits removes all credits associated with the +// provided pkScript as well as debits that spend those credits. +func RemovePkScriptCreditsAndDebits(ns walletdb.ReadWriteBucket, pkScript []byte) error { + // Fetch all mined credits associated with the provided script. + credits := make(map[string]dcrutil.Amount, 0) + cc := ns.NestedReadBucket(bucketCredits).ReadCursor() + for k, v := cc.First(); k != nil; k, v = cc.Next() { + recK := extractRawCreditTxRecordKey(k) + recV := existsRawTxRecord(ns, recK) + index := extractRawCreditIndex(k) + scrLoc := fetchRawCreditScriptOffset(v) + scrLen := fetchRawCreditScriptLength(v) + scr, err := fetchRawTxRecordPkScript(recK, recV, index, scrLoc, scrLen) + if err != nil { + return err + } + + if !bytes.Equal(scr, pkScript) { + continue + } + + amount, err := fetchRawCreditAmount(v) + if err != nil { + return err + } + + credits[string(k)] = amount + } + + // Fetch and delete debits that spend credits associated with the + // provided script. + debits := make([][]byte, 0) + dc := ns.NestedReadBucket(bucketDebits).ReadCursor() + for k, v := dc.First(); k != nil; k, v = dc.Next() { + ck := extractRawDebitCreditKey(v) + for key, _ := range credits { + if !bytes.Equal(ck, []byte(key)) { + continue + } + + debits = append(debits, k) + } + } + + for _, k := range debits { + err := ns.NestedReadWriteBucket(bucketDebits).Delete(k) + if err != nil { + return err + } + } + + // Delete all credits associated with the provided script and update the + // mined wallet balance. + bal, err := fetchMinedBalance(ns) + if err != nil { + return err + } + + for k, amt := range credits { + bal -= amt + err := ns.NestedReadWriteBucket(bucketCredits).Delete([]byte(k)) + if err != nil { + return err + } + } + + return putMinedBalance(ns, bal) +} + +// RemovePkScriptUnminedCredits removes all unmined credits associated with +// the provided pkScript. +func RemovePkScriptUnminedCredits(tx walletdb.ReadWriteTx, pkScript []byte) error { + // Fetch all unmined credits associated with the provided script. + unminedCredits := make([][]byte, 0) + ns := tx.ReadWriteBucket(wtxmgrBucketKey) + uc := ns.NestedReadBucket(bucketUnminedCredits).ReadCursor() + for k, v := uc.First(); k != nil; k, v = uc.Next() { + hash := extractRawUnminedCreditTxHash(k) + unmined := existsRawUnmined(ns, hash) + unminedV := extractRawUnminedTx(unmined) + index, err := fetchRawUnminedCreditIndex(k) + if err != nil { + return err + } + + scrLoc := fetchRawUnminedCreditScriptOffset(v) + scrLen := fetchRawUnminedCreditScriptLength(v) + scr, err := fetchRawUnminedTxRecordPkScript(unminedV, index, scrLoc, scrLen) + if err != nil { + return err + } + + if !bytes.Equal(scr, pkScript) { + continue + } + + unminedCredits = append(unminedCredits, k) + } + + // Delete all unmined credits associated with the provided script. + for _, k := range unminedCredits { + err := ns.NestedReadWriteBucket(bucketUnminedCredits).Delete(k) + if err != nil { + return err + } + } + + return nil +} diff --git a/wallet/udb/txmined.go b/wallet/udb/txmined.go index dcacc377e..cde08f347 100644 --- a/wallet/udb/txmined.go +++ b/wallet/udb/txmined.go @@ -3600,6 +3600,11 @@ func (s *Store) InsertTxScript(ns walletdb.ReadWriteBucket, script []byte) error return putTxScript(ns, script) } +// DeleteTxScript purges a transaction script from the database. +func (s *Store) DeleteTxScript(ns walletdb.ReadWriteBucket, script []byte) error { + return deleteTxScript(ns, script) +} + // GetTxScript fetches a transaction script from the database using // the RIPEMD160 hash as a key. func (s *Store) GetTxScript(ns walletdb.ReadBucket, hash []byte) ([]byte, error) { diff --git a/wallet/wallet.go b/wallet/wallet.go index ce70cc920..b4bb9d5c0 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -3163,7 +3163,58 @@ func (w *Wallet) ImportScript(ctx context.Context, rs []byte) error { return nil } -// RedeemScriptCopy returns a copy of a redeem script to redeem outputs paid to +// RemoveAddress removes the provided address from the wallet. +func (w *Wallet) RemoveAddress(addr dcrutil.Address) error { + const op errors.Op = "wallet.RemoveAddress" + err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { + addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) + txmgrNs := tx.ReadWriteBucket(wtxmgrNamespaceKey) + + maddr, err := w.Manager.Address(addrmgrNs, addr) + if err != nil { + return errors.E(op, err) + } + + if !maddr.Imported() { + return errors.E(op, fmt.Sprintf("cannot remove unimported "+ + "address %s from wallet", maddr.Address().String())) + } + + // Remove associated credits and debits of the address. + pkScript, err := txscript.PayToAddrScript(addr) + if err != nil { + return errors.E(op, err) + } + + err = udb.RemovePkScriptCreditsAndDebits(txmgrNs, pkScript) + if err != nil { + return errors.E(op, err) + } + + err = udb.RemovePkScriptUnminedCredits(tx, pkScript) + if err != nil { + return errors.E(op, err) + } + + addrID := addr.ScriptAddress() + + switch addr.(type) { + case *dcrutil.AddressScriptHash: + err = w.TxStore.DeleteTxScript(txmgrNs, addrID) + if err != nil { + return err + } + } + + return w.Manager.RemoveAddress(addrmgrNs, addrID) + }) + if err != nil { + return errors.E(op, err) + } + return nil +} + +// RedeemScriptCopy returns a copy of a redeem script0 to redeem outputs paid to // a P2SH address. func (w *Wallet) RedeemScriptCopy(addr dcrutil.Address) ([]byte, error) { const op errors.Op = "wallet.RedeemScriptCopy"