Skip to content

Commit

Permalink
Implement the debuglevel JSON-RPC method
Browse files Browse the repository at this point in the history
This allows debug levels to be made more or less verbose at runtime, without
requiring an application restart to change the config.

The semantics and usage is identical to dcrd's debuglevel method.
  • Loading branch information
jrick committed Dec 16, 2024
1 parent 2e8bb31 commit f1e84cf
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 2 deletions.
15 changes: 14 additions & 1 deletion internal/rpc/jsonrpc/config.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2013-2015 The btcsuite developers
// Copyright (c) 2013-2024 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

Expand Down Expand Up @@ -29,4 +29,17 @@ type Options struct {
VSPPubKey string
VSPMaxFee dcrutil.Amount
Dial func(ctx context.Context, network, addr string) (net.Conn, error)

Loggers Loggers
}

// Loggers provides access to manage all application subsystem loggers.
type Loggers interface {
// Subsystems returns all of the application logging subsystem names.
Subsystems() []string

// SetLevels sets all loggers, or a specific logger (when levelSpec is
// prefixed by the subsystem and an equals sign) to a particular debug
// level.
SetLevels(levelSpec string) error
}
18 changes: 18 additions & 0 deletions internal/rpc/jsonrpc/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ var handlers = map[string]handler{
"createnewaccount": {fn: (*Server).createNewAccount},
"createrawtransaction": {fn: (*Server).createRawTransaction},
"createsignature": {fn: (*Server).createSignature},
"debuglevel": {fn: (*Server).debugLevel},
"disapprovepercent": {fn: (*Server).disapprovePercent},
"discoverusage": {fn: (*Server).discoverUsage},
"dumpprivkey": {fn: (*Server).dumpPrivKey},
Expand Down Expand Up @@ -791,6 +792,23 @@ func (s *Server) createSignature(ctx context.Context, icmd any) (any, error) {
}, nil
}

func (s *Server) debugLevel(ctx context.Context, icmd any) (any, error) {
cmd := icmd.(*types.DebugLevelCmd)

if cmd.LevelSpec == "show" {
return fmt.Sprintf("Supported subsystems %v",
s.cfg.Loggers.Subsystems()), nil
}

err := s.cfg.Loggers.SetLevels(cmd.LevelSpec)
if err != nil {
return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter,
"invalid debug level %v: %v", cmd.LevelSpec, err)
}

return "Done.", nil
}

// disapprovePercent returns the wallets current disapprove percentage.
func (s *Server) disapprovePercent(ctx context.Context, _ any) (any, error) {
w, ok := s.walletLoader.LoadedWallet()
Expand Down
3 changes: 2 additions & 1 deletion internal/rpc/jsonrpc/rpcserverhelp.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func helpDescsEnUS() map[string]string {
"createnewaccount": "createnewaccount \"account\"\n\nCreates a new account.\nThe wallet must be unlocked for this request to succeed.\n\nArguments:\n1. account (string, required) Name of the new account\n\nResult:\nNothing\n",
"createrawtransaction": "createrawtransaction [{\"amount\":n.nnn,\"txid\":\"value\",\"vout\":n,\"tree\":n},...] {\"address\":amount,...} (locktime expiry)\n\nReturns a new transaction spending the provided inputs and sending to the provided addresses.\nThe transaction inputs are not signed in the created transaction.\nThe signrawtransaction RPC command provided by wallet must be used to sign the resulting transaction.\n\nArguments:\n1. inputs (array of object, required) The inputs to the transaction\n[{\n \"amount\": n.nnn, (numeric) The previous output amount\n \"txid\": \"value\", (string) The transaction hash of the referenced output\n \"vout\": n, (numeric) The output index of the referenced output\n \"tree\": n, (numeric) The tree to generate transaction for\n},...]\n2. amounts (object, required) JSON object with the destination addresses as keys and amounts as values\n{\n \"address\": n.nnn, (object) The destination address as the key and the amount in DCR as the value\n ...\n}\n3. locktime (numeric, optional) Locktime value; a non-zero value will also locktime-activate the inputs\n4. expiry (numeric, optional) Expiry value; a non-zero value when the transaction expiry\n\nResult:\n\"value\" (string) Hex-encoded bytes of the serialized transaction\n",
"createsignature": "createsignature \"address\" inputindex hashtype \"previouspkscript\" \"serializedtransaction\"\n\nGenerate a signature for a transaction input script.\n\nArguments:\n1. address (string, required) The address of the private key to use to create the signature.\n2. inputindex (numeric, required) The index of the transaction input to sign.\n3. hashtype (numeric, required) The signature hash flags to use.\n4. previouspkscript (string, required) The hex encoded previous output script or P2SH redeem script.\n5. serializedtransaction (string, required) The hex encoded transaction to add input signatures to.\n\nResult:\n{\n \"signature\": \"value\", (string) The hex encoded signature.\n \"publickey\": \"value\", (string) The hex encoded serialized compressed pubkey of the address.\n} \n",
"debuglevel": "debuglevel \"levelspec\"\n\nSet all or per-subsystem application log levels.\n\nArguments:\n1. levelspec (string, required) The log level to set all loggers to, optionally prefixed by a subsystem and equals sign to set a specific subsystem's log level, or 'show' to display all subsystems.\n\nResult:\n\"value\" (string) All available subsytems (when levelspec is 'show'), or 'Done.'\n",
"disapprovepercent": "disapprovepercent\n\nReturns the wallet's current block disapprove percent per vote. i.e. 100 means that all votes disapprove the block they are called on. Only used for testing purposes.\n\nArguments:\nNone\n\nResult:\nn (numeric) The disapprove percent. When voting, this percent of votes will randomly disapprove the block they are called on.\n",
"discoverusage": "discoverusage (\"startblock\" discoveraccounts gaplimit)\n\nPerform address and/or account discovery\n\nArguments:\n1. startblock (string, optional) Hash of block to begin discovery from, or null to scan from the genesis block\n2. discoveraccounts (boolean, optional) Perform account discovery in addition to address discovery. Requires unlocked wallet.\n3. gaplimit (numeric, optional) Allowed unused address gap.\n\nResult:\nNothing\n",
"dumpprivkey": "dumpprivkey \"address\"\n\nReturns the private key in WIF encoding that controls some wallet address.\n\nArguments:\n1. address (string, required) The address to return a private key for\n\nResult:\n\"value\" (string) The WIF-encoded private key\n",
Expand Down Expand Up @@ -113,4 +114,4 @@ var localeHelpDescs = map[string]func() map[string]string{
"en_US": helpDescsEnUS,
}

var requestUsages = "abandontransaction \"hash\"\naccountaddressindex \"account\" branch\naccountsyncaddressindex \"account\" branch index\naccountunlocked \"account\"\naddmultisigaddress nrequired [\"key\",...] (\"account\")\naddtransaction \"blockhash\" \"transaction\"\nauditreuse (since)\nconsolidate inputs (\"account\" \"address\")\ncreatemultisig nrequired [\"key\",...]\ncreatenewaccount \"account\"\ncreaterawtransaction [{\"amount\":n.nnn,\"txid\":\"value\",\"vout\":n,\"tree\":n},...] {\"address\":amount,...} (locktime expiry)\ncreatesignature \"address\" inputindex hashtype \"previouspkscript\" \"serializedtransaction\"\ndisapprovepercent\ndiscoverusage (\"startblock\" discoveraccounts gaplimit)\ndumpprivkey \"address\"\nfundrawtransaction \"hexstring\" \"fundaccount\" ({\"changeaddress\":changeaddress,\"feerate\":feerate,\"conftarget\":conftarget})\ngetaccount \"address\"\ngetaccountaddress \"account\"\ngetaddressesbyaccount \"account\"\ngetbalance (\"account\" minconf=1)\ngetbestblock\ngetbestblockhash\ngetblockcount\ngetblockhash index\ngetblockheader \"hash\" (verbose=true)\ngetblock \"hash\" (verbose=true verbosetx=false)\ngetcoinjoinsbyacct\ngetcurrentnet\ngetinfo\ngetmasterpubkey (\"account\")\ngetmultisigoutinfo \"hash\" index\ngetnewaddress (\"account\" \"gappolicy\")\ngetpeerinfo\ngetrawchangeaddress (\"account\")\ngetreceivedbyaccount \"account\" (minconf=1)\ngetreceivedbyaddress \"address\" (minconf=1)\ngetstakeinfo\ngettickets includeimmature\ngettransaction \"txid\" (includewatchonly=false)\ngettxout \"txid\" vout tree (includemempool=true)\ngetunconfirmedbalance (\"account\")\ngetvotechoices (\"tickethash\")\ngetwalletfee\ngetcfilterv2 \"blockhash\"\nhelp (\"command\")\nimportcfiltersv2 startheight [\"filter\",...]\nimportprivkey \"privkey\" (\"label\" rescan=true scanfrom)\nimportpubkey \"pubkey\" (\"label\" rescan=true scanfrom)\nimportscript \"hex\" (rescan=true scanfrom)\nimportxpub \"name\" \"xpub\"\nlistaccounts (minconf=1)\nlistaddresstransactions [\"address\",...] (\"account\")\nlistalltransactions (\"account\")\nlistlockunspent (\"account\")\nlistreceivedbyaccount (minconf=1 includeempty=false includewatchonly=false)\nlistreceivedbyaddress (minconf=1 includeempty=false includewatchonly=false)\nlistsinceblock (\"blockhash\" targetconfirmations=1 includewatchonly=false)\nlisttransactions (\"account\" count=10 from=0 includewatchonly=false)\nlistunspent (minconf=1 maxconf=9999999 [\"address\",...] \"account\")\nlockaccount \"account\"\nlockunspent unlock [{\"amount\":n.nnn,\"txid\":\"value\",\"vout\":n,\"tree\":n},...]\nmixaccount\nmixoutput \"outpoint\"\nprocessunmanagedticket \"tickethash\"\npurchaseticket \"fromaccount\" spendlimit (minconf=1 numtickets=1 expiry \"comment\" dontsigntx)\nredeemmultisigout \"hash\" index tree (\"address\")\nredeemmultisigouts \"fromscraddress\" (\"toaddress\" number)\nrenameaccount \"oldaccount\" \"newaccount\"\nrescanwallet (beginheight=0)\nsendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 \"comment\" \"commentto\")\nsendfromtreasury \"key\" amounts\nsendmany \"fromaccount\" {\"address\":amount,...} (minconf=1 \"comment\")\nsendrawtransaction \"hextx\" (allowhighfees=false)\nsendtoaddress \"address\" amount (\"comment\" \"commentto\")\nsendtomultisig \"fromaccount\" amount [\"pubkey\",...] (nrequired=1 minconf=1 \"comment\")\nsendtotreasury amount\nsetaccountpassphrase \"account\" \"passphrase\"\nsetdisapprovepercent percent\nsettreasurypolicy \"key\" \"policy\" (\"ticket\")\nsettspendpolicy \"hash\" \"policy\" (\"ticket\")\nsettxfee amount\nsetvotechoice \"agendaid\" \"choiceid\" (\"tickethash\")\nsignmessage \"address\" \"message\"\nsignrawtransaction \"rawtx\" ([{\"txid\":\"value\",\"vout\":n,\"tree\":n,\"scriptpubkey\":\"value\",\"redeemscript\":\"value\"},...] [\"privkey\",...] flags=\"ALL\")\nsignrawtransactions [\"rawtx\",...] (send=true)\nspendoutputs \"account\" [\"previousoutpoint\",...] [{\"address\":\"value\",\"amount\":n.nnn},...]\nsweepaccount \"sourceaccount\" \"destinationaddress\" (requiredconfirmations feeperkb)\nsyncstatus\nticketinfo (startheight=0)\ntreasurypolicy (\"key\" \"ticket\")\ntspendpolicy (\"hash\" \"ticket\")\nunlockaccount \"account\" \"passphrase\"\nvalidateaddress \"address\"\nvalidatepredcp0005cf\nverifymessage \"address\" \"signature\" \"message\"\nversion\nwalletinfo\nwalletislocked\nwalletlock\nwalletpassphrase \"passphrase\" timeout\nwalletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\nwalletpubpassphrasechange \"oldpassphrase\" \"newpassphrase\""
var requestUsages = "abandontransaction \"hash\"\naccountaddressindex \"account\" branch\naccountsyncaddressindex \"account\" branch index\naccountunlocked \"account\"\naddmultisigaddress nrequired [\"key\",...] (\"account\")\naddtransaction \"blockhash\" \"transaction\"\nauditreuse (since)\nconsolidate inputs (\"account\" \"address\")\ncreatemultisig nrequired [\"key\",...]\ncreatenewaccount \"account\"\ncreaterawtransaction [{\"amount\":n.nnn,\"txid\":\"value\",\"vout\":n,\"tree\":n},...] {\"address\":amount,...} (locktime expiry)\ncreatesignature \"address\" inputindex hashtype \"previouspkscript\" \"serializedtransaction\"\ndebuglevel \"levelspec\"\ndisapprovepercent\ndiscoverusage (\"startblock\" discoveraccounts gaplimit)\ndumpprivkey \"address\"\nfundrawtransaction \"hexstring\" \"fundaccount\" ({\"changeaddress\":changeaddress,\"feerate\":feerate,\"conftarget\":conftarget})\ngetaccount \"address\"\ngetaccountaddress \"account\"\ngetaddressesbyaccount \"account\"\ngetbalance (\"account\" minconf=1)\ngetbestblock\ngetbestblockhash\ngetblockcount\ngetblockhash index\ngetblockheader \"hash\" (verbose=true)\ngetblock \"hash\" (verbose=true verbosetx=false)\ngetcoinjoinsbyacct\ngetcurrentnet\ngetinfo\ngetmasterpubkey (\"account\")\ngetmultisigoutinfo \"hash\" index\ngetnewaddress (\"account\" \"gappolicy\")\ngetpeerinfo\ngetrawchangeaddress (\"account\")\ngetreceivedbyaccount \"account\" (minconf=1)\ngetreceivedbyaddress \"address\" (minconf=1)\ngetstakeinfo\ngettickets includeimmature\ngettransaction \"txid\" (includewatchonly=false)\ngettxout \"txid\" vout tree (includemempool=true)\ngetunconfirmedbalance (\"account\")\ngetvotechoices (\"tickethash\")\ngetwalletfee\ngetcfilterv2 \"blockhash\"\nhelp (\"command\")\nimportcfiltersv2 startheight [\"filter\",...]\nimportprivkey \"privkey\" (\"label\" rescan=true scanfrom)\nimportpubkey \"pubkey\" (\"label\" rescan=true scanfrom)\nimportscript \"hex\" (rescan=true scanfrom)\nimportxpub \"name\" \"xpub\"\nlistaccounts (minconf=1)\nlistaddresstransactions [\"address\",...] (\"account\")\nlistalltransactions (\"account\")\nlistlockunspent (\"account\")\nlistreceivedbyaccount (minconf=1 includeempty=false includewatchonly=false)\nlistreceivedbyaddress (minconf=1 includeempty=false includewatchonly=false)\nlistsinceblock (\"blockhash\" targetconfirmations=1 includewatchonly=false)\nlisttransactions (\"account\" count=10 from=0 includewatchonly=false)\nlistunspent (minconf=1 maxconf=9999999 [\"address\",...] \"account\")\nlockaccount \"account\"\nlockunspent unlock [{\"amount\":n.nnn,\"txid\":\"value\",\"vout\":n,\"tree\":n},...]\nmixaccount\nmixoutput \"outpoint\"\nprocessunmanagedticket \"tickethash\"\npurchaseticket \"fromaccount\" spendlimit (minconf=1 numtickets=1 expiry \"comment\" dontsigntx)\nredeemmultisigout \"hash\" index tree (\"address\")\nredeemmultisigouts \"fromscraddress\" (\"toaddress\" number)\nrenameaccount \"oldaccount\" \"newaccount\"\nrescanwallet (beginheight=0)\nsendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 \"comment\" \"commentto\")\nsendfromtreasury \"key\" amounts\nsendmany \"fromaccount\" {\"address\":amount,...} (minconf=1 \"comment\")\nsendrawtransaction \"hextx\" (allowhighfees=false)\nsendtoaddress \"address\" amount (\"comment\" \"commentto\")\nsendtomultisig \"fromaccount\" amount [\"pubkey\",...] (nrequired=1 minconf=1 \"comment\")\nsendtotreasury amount\nsetaccountpassphrase \"account\" \"passphrase\"\nsetdisapprovepercent percent\nsettreasurypolicy \"key\" \"policy\" (\"ticket\")\nsettspendpolicy \"hash\" \"policy\" (\"ticket\")\nsettxfee amount\nsetvotechoice \"agendaid\" \"choiceid\" (\"tickethash\")\nsignmessage \"address\" \"message\"\nsignrawtransaction \"rawtx\" ([{\"txid\":\"value\",\"vout\":n,\"tree\":n,\"scriptpubkey\":\"value\",\"redeemscript\":\"value\"},...] [\"privkey\",...] flags=\"ALL\")\nsignrawtransactions [\"rawtx\",...] (send=true)\nspendoutputs \"account\" [\"previousoutpoint\",...] [{\"address\":\"value\",\"amount\":n.nnn},...]\nsweepaccount \"sourceaccount\" \"destinationaddress\" (requiredconfirmations feeperkb)\nsyncstatus\nticketinfo (startheight=0)\ntreasurypolicy (\"key\" \"ticket\")\ntspendpolicy (\"hash\" \"ticket\")\nunlockaccount \"account\" \"passphrase\"\nvalidateaddress \"address\"\nvalidatepredcp0005cf\nverifymessage \"address\" \"signature\" \"message\"\nversion\nwalletinfo\nwalletislocked\nwalletlock\nwalletpassphrase \"passphrase\" timeout\nwalletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\nwalletpubpassphrasechange \"oldpassphrase\" \"newpassphrase\""
5 changes: 5 additions & 0 deletions internal/rpchelp/helpdescs_en_US.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ var helpDescsEnUS = map[string]string{
"createsignature-hashtype": "The signature hash flags to use.",
"createsignature-previouspkscript": "The hex encoded previous output script or P2SH redeem script.",

// DebugLevelCmd help.
"debuglevel--synopsis": "Set all or per-subsystem application log levels.",
"debuglevel-levelspec": "The log level to set all loggers to, optionally prefixed by a subsystem and equals sign to set a specific subsystem's log level, or 'show' to display all subsystems.",
"debuglevel--result0": "All available subsytems (when levelspec is 'show'), or 'Done.'",

// DisapprovePercentCmd help.
"disapprovepercent--synopsis": "Returns the wallet's current block disapprove percent per vote. i.e. 100 means that all votes disapprove the block they are called on. Only used for testing purposes.",
"disapprovepercent--result0": "The disapprove percent. When voting, this percent of votes will randomly disapprove the block they are called on.",
Expand Down
1 change: 1 addition & 0 deletions internal/rpchelp/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ var Methods = []struct {
{"createnewaccount", nil},
{"createrawtransaction", returnsString},
{"createsignature", []any{(*types.CreateSignatureResult)(nil)}},
{"debuglevel", returnsString},
{"disapprovepercent", []any{(*uint32)(nil)}},
{"discoverusage", nil},
{"dumpprivkey", returnsString},
Expand Down
33 changes: 33 additions & 0 deletions log.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ package main

import (
"os"
"strings"

"decred.org/dcrwallet/v5/chain"
"decred.org/dcrwallet/v5/errors"
"decred.org/dcrwallet/v5/internal/loader"
"decred.org/dcrwallet/v5/internal/loggers"
"decred.org/dcrwallet/v5/internal/rpc/jsonrpc"
Expand Down Expand Up @@ -83,6 +85,37 @@ func setLogLevels(logLevel string) {
}
}

// setLogLevelSpec sets all loggers, or a specific logger (when levelSpec is
// prefixed by the subsystem and an equals sign) to a particular log level.
func setLogLevelSpec(levelSpec string) error {
var subsys, level string

equals := strings.Index(levelSpec, "=")
if equals != -1 {
subsys = levelSpec[:equals]
level = levelSpec[equals+1:]
} else {
level = levelSpec
}

if subsys != "" {
if _, ok := subsystemLoggers[subsys]; !ok {
return errors.Errorf("subsystem %q does not exist", subsys)
}
}

if _, ok := slog.LevelFromString(level); !ok {
return errors.Errorf("%q is not a valid log level", level)
}

if subsys != "" {
setLogLevel(subsys, level)
} else {
setLogLevels(level)
}
return nil
}

// fatalf logs a message, flushes the logger, and finally exit the process with
// a non-zero return code.
func fatalf(format string, args ...any) {
Expand Down
2 changes: 2 additions & 0 deletions rpc/jsonrpc/types/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -1306,6 +1306,7 @@ func init() {
// dcrd methods also implemented by dcrwallet
register = []registeredMethod{
{"createrawtransaction", (*CreateRawTransactionCmd)(nil)},
{"debuglevel", (*DebugLevelCmd)(nil)},
{"getbestblock", (*GetBestBlockCmd)(nil)},
{"getbestblockhash", (*GetBestBlockHashCmd)(nil)},
{"getblockcount", (*GetBlockCountCmd)(nil)},
Expand Down Expand Up @@ -1341,6 +1342,7 @@ func init() {
type (
AuthenticateCmd dcrdtypes.AuthenticateCmd
CreateRawTransactionCmd dcrdtypes.CreateRawTransactionCmd
DebugLevelCmd dcrdtypes.DebugLevelCmd
GetBestBlockCmd dcrdtypes.GetBestBlockCmd
GetBestBlockHashCmd dcrdtypes.GetBestBlockHashCmd
GetBlockCountCmd dcrdtypes.GetBlockCountCmd
Expand Down
17 changes: 17 additions & 0 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"time"

Expand Down Expand Up @@ -229,6 +230,21 @@ func generateClientKeyPair(caPriv any, ca *x509.Certificate) (cert, key []byte,
return cert, key, nil
}

type rpcLoggers struct{}

func (rpcLoggers) Subsystems() []string {
s := make([]string, 0, len(subsystemLoggers))
for name := range subsystemLoggers {
s = append(s, name)
}
sort.Strings(s)
return s
}

func (rpcLoggers) SetLevels(levelSpec string) error {
return setLogLevelSpec(levelSpec)
}

func startRPCServers(walletLoader *loader.Loader) (*grpc.Server, *jsonrpc.Server, error) {
var jsonrpcAddrNotifier jsonrpcListenerEventServer
var grpcAddrNotifier grpcListenerEventServer
Expand Down Expand Up @@ -366,6 +382,7 @@ func startRPCServers(walletLoader *loader.Loader) (*grpc.Server, *jsonrpc.Server
VSPMaxFee: cfg.VSPOpts.MaxFee.Amount,
TicketSplitAccount: cfg.TicketSplitAccount,
Dial: cfg.dial,
Loggers: rpcLoggers{},
}
jsonrpcServer = jsonrpc.NewServer(&opts, activeNet.Params, walletLoader, listeners)
for _, lis := range listeners {
Expand Down

0 comments on commit f1e84cf

Please sign in to comment.