# Appendix: Debugging Guide

## Troubleshooting and Diagnostic Techniques

[← Back to Building and Integrating Custom RPC Handlers](/core-dev-bootcamp/module07.md)

***

## Introduction

Even with careful coding, issues will arise during RPC handler development. This guide provides systematic approaches to diagnosing and fixing problems, from compilation errors to runtime issues to performance bottlenecks.

The key is having the right tools and techniques to isolate the problem quickly.

***

## Common Compilation Errors

### Error 1: Undeclared Identifiers

**Error Message**:

```
error: 'jss::my_field' was not declared in this scope
```

**Root Causes**:

* Typo in JSON string constant name
* Using string literal instead of jss:: constant
* Missing include file

**Solution**:

```cpp
// WRONG - String literal
result["my_field"] = value;

// CORRECT - Use jss:: constant
result[jss::my_field] = value;

// If jss::my_field doesn't exist, add to jss.h or define:
namespace ripple { namespace jss {
    constexpr auto my_field = "myField";
}} // ripple::jss
```

**Prevention**:

* Always use jss:: constants from xrpl/protocol/jss.h
* Check spelling carefully
* Refer to existing handlers for correct constant names

***

### Error 2: Type Mismatch in RPC Functions

**Error Message**:

```
error: no matching function for call to 'rpcError'
```

**Root Causes**:

* Wrong parameter types
* Missing required parameters
* Template specialization issues

**Solution**:

```cpp
// WRONG - Wrong return type
result[jss::error] = rpcError(rpcINVALID_PARAMS);  // This is Json::Value, not bool

// CORRECT - Return directly
return rpcError(rpcINVALID_PARAMS);

// WRONG - Missing error code
return rpcError("Missing account");

// CORRECT - Include error code
return rpcError(rpcINVALID_PARAMS, "Missing account");
```

***

### Error 3: Const Correctness Issues

**Error Message**:

```
error: invalid conversion from 'const ripple::AccountID*' to 'ripple::AccountID*'
```

**Root Causes**:

* Discarding const qualifier
* Passing const reference where non-const expected
* Missing const in function signature

**Solution**:

```cpp
// WRONG - Losing const
auto const account = *accountOpt;
// account is const AccountID, can't be modified
auto nonConst = const_cast<AccountID*>(&account);  // DON'T DO THIS

// CORRECT - Keep const where needed
auto const account = *accountOpt;
auto const sle = ledger->read(keylet::account(account));

// WRONG - Function signature drops const
void processAccount(AccountID& account);

// CORRECT - Mark as const
void processAccount(AccountID const& account);
```

***

### Error 4: Missing Include Files

**Error Message**:

```
error: 'parseBase58' was not declared in this scope
error: 'RPC::lookupLedger' was not declared in this scope
```

**Solution**:

```cpp
// Add required includes at top of handler file:
#include <xrpld/app/main/Application.h>
#include <xrpld/rpc/Context.h>
#include <xrpld/rpc/detail/RPCHelpers.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/Blob.h>
```

**Checklist**:

* RPCHelpers.h for helper functions
* Context.h for RPC::JsonContext
* ErrorCodes.h for error constants
* jss.h for JSON string constants
* base\_uint.h for uint256 and similar
* AccountID.h if using AccountID directly

***

## Runtime Debugging Techniques

### Technique 1: Strategic Logging

**Adding Debug Output**:

```cpp
#include <xrpld/core/XRPLedger.h>  // For JLOG

Json::Value doMyHandler(RPC::JsonContext& context)
{
    JLOG(context.app.journal("RPC").debug())
        << "doMyHandler called with params: "
        << context.params.toStyledString();

    if (!context.params.isMember(jss::account)) {
        JLOG(context.app.journal("RPC").warning())
            << "Missing account parameter";
        return rpcError(rpcINVALID_PARAMS);
    }

    auto const account = parseBase58<AccountID>(
        context.params[jss::account].asString());

    if (!account) {
        JLOG(context.app.journal("RPC").warning())
            << "Failed to parse account: "
            << context.params[jss::account].asString();
        return rpcError(rpcACT_MALFORMED);
    }

    JLOG(context.app.journal("RPC").debug())
        << "Successfully parsed account: " << to_string(*account);

    return buildResponse(*account, ledger);
}
```

**Journal Levels**:

* `fatal()` - Application stopping
* `error()` - Significant problems
* `warning()` - Unexpected but recoverable
* `info()` - General informational
* `debug()` - Detailed diagnostic (hidden in release builds)
* `trace()` - Very detailed (rarely used)

**Viewing Output**:

```bash
# Build with debug logging enabled
./rippled -a --log-level rpc=debug

# Output typically goes to console or rippled.log
tail -f rippled.log | grep "RPC"
```

***

### Technique 2: Using the Debugger (GDB)

**Building for Debugging**:

```bash
cd rippled
git submodule update --init --recursive
mkdir -p build
cd build

# Build with debug symbols
cmake -DCMAKE_BUILD_TYPE=Debug \
      -DCMAKE_CXX_COMPILER=clang++ \
      ..

make rippled
```

**Launching Debugger**:

```bash
# Start rippled under GDB with minimal ledger
cd build
gdb --args ./rippled --standalone

# Or attach to running process
gdb attach <process_id>
```

**GDB Commands**:

```gdb
# Set breakpoint in handler
(gdb) break src/xrpld/rpc/handlers/MyHandler.cpp:45

# Set conditional breakpoint
(gdb) break MyHandler.cpp:45 if account_id == 0x1234

# Run until breakpoint
(gdb) run

# Step into function
(gdb) step

# Step over function
(gdb) next

# Continue execution
(gdb) continue

# Print variable
(gdb) print account
(gdb) print *account
(gdb) print account->field

# Print with pretty formatting
(gdb) set print pretty on

# Examine memory
(gdb) x/32bx account  # 32 bytes in hex format

# Get stack trace
(gdb) backtrace
```

**Common GDB Debugging Pattern**:

```bash
# 1. Set breakpoint at handler entry
(gdb) break doMyHandler

# 2. Run and hit breakpoint
(gdb) run
Breakpoint 1, doMyHandler (context=0x7fff...) at MyHandler.cpp:25

# 3. Print context details
(gdb) print context.params
(gdb) print context.role

# 4. Step through code
(gdb) next
(gdb) print account

# 5. If crash, examine state
(gdb) backtrace
(gdb) frame 0
(gdb) print *ledger
```

***

### Technique 3: Using LLDB (macOS)

**Similar to GDB but with slightly different syntax**:

```bash
# Build for debug (same as GDB)
cmake -DCMAKE_BUILD_TYPE=Debug ...

# Start LLDB
lldb -- ./rippled --standalone
```

**LLDB Commands**:

```lldb
# Set breakpoint
(lldb) breakpoint set --file MyHandler.cpp --line 45

# More concise
(lldb) br set -f MyHandler.cpp -l 45

# Run
(lldb) run

# Step/next/continue (same as GDB)
(lldb) step
(lldb) next
(lldb) continue

# Print variable
(lldb) frame variable account
(lldb) p context.params

# Backtrace
(lldb) bt
```

***

### Technique 4: Analyzing Crashes

**When handler crashes (segmentation fault)**:

```bash
# 1. Get core dump
ulimit -c unlimited

# 2. Run rippled
./rippled --standalone

# 3. When it crashes, analyze core dump
lldb ./rippled -c /cores/core.12345

# 4. In debugger
(lldb) bt                    # See where crash happened
(lldb) frame variable        # Print local variables
(lldb) p pointer_variable    # Check for null pointers
```

**Common Crash Scenarios**:

```cpp
// CRASH 1: Null pointer dereference
auto const sle = ledger->read(keylet);
if (!sle) {
    auto value = sle->getFieldU32(sfBalance);  // CRASH! sle is null
}

// FIX: Check before use
auto const sle = ledger->read(keylet);
if (!sle) {
    return rpcError(rpcACT_NOT_FOUND);
}
auto value = sle->getFieldU32(sfBalance);  // Safe


// CRASH 2: Dereferencing invalid optional
auto const accountOpt = parseBase58<AccountID>(str);
auto const sle = ledger->read(keylet::account(*accountOpt));  // CRASH if empty

// FIX: Validate before deref
auto const accountOpt = parseBase58<AccountID>(str);
if (!accountOpt) {
    return rpcError(rpcACT_MALFORMED);
}
auto const sle = ledger->read(keylet::account(*accountOpt));  // Safe
```

***

## Log Level Configuration

### Setting Log Levels

**In Config File** (`rippled.cfg`):

```ini
[rpc]
port = 5005
ip = 127.0.0.1

[logging]
# General log level
log_level = info

# Specific module levels
rpc = debug
ledger = warning
transaction = info
```

**Command Line**:

```bash
./rippled --log-level rpc=debug --log-level ledger=trace

./rippled -a --log-level rpc=debug  # -a for standalone mode
```

**Common Modules to Debug**:

* `rpc` - RPC handler execution
* `ledger` - Ledger operations
* `transaction` - Transaction processing
* `app` - Application lifecycle
* `protocol` - Protocol messages
* `peer` - Network peer communication

### Interpreting Log Output

**Typical Log Format**:

```
[2024-11-16T10:30:45.123Z] RPC {getAccountInfo:3} Running handler: account_info
[2024-11-16T10:30:45.125Z] RPC {getAccountInfo:3} Query result: success
[2024-11-16T10:30:45.126Z] RPC {getAccountInfo:3} Returning: {"account":"rN7n7...","balance":"1000000"}
```

**Log Levels Mean**:

* `[FATAL]` - Rippled about to stop
* `[ERROR]` - Handler failed, returned error
* `[WARN]` - Unusual condition, but handler succeeded
* `[INFO]` - Normal informational messages
* `[DEBUG]` - Detailed execution trace (very verbose)
* `[TRACE]` - Extremely detailed (rarely used)

***

## Testing Strategies

### Strategy 1: Unit Testing Handlers

**Test File Structure** (`src/test/rpc/handlers/MyHandler_test.cpp`):

```cpp
#include <gtest/gtest.h>
#include <xrpld/rpc/handlers/MyHandler.h>
#include <test/jtx.h>

namespace ripple::test {

class MyHandlerTest : public testing::Test {
protected:
    void SetUp() override {
        // Initialize test fixtures
    }
};

TEST_F(MyHandlerTest, ValidRequest) {
    // Arrange
    Json::Value params;
    params[jss::account] = "rN7n7otQDd6FczFgLdlqtyMVrnPQGEDE8";

    // Act
    auto result = doMyHandler(context);

    // Assert
    ASSERT_FALSE(result.isMember(jss::error));
    ASSERT_TRUE(result.isMember(jss::result));
}

TEST_F(MyHandlerTest, MissingAccountParam) {
    // Arrange
    Json::Value params;  // Empty params

    // Act
    auto result = doMyHandler(context);

    // Assert
    ASSERT_TRUE(result.isMember(jss::error));
    ASSERT_EQ(result[jss::error_code].asInt(), rpcINVALID_PARAMS);
}

TEST_F(MyHandlerTest, MalformedAccount) {
    // Arrange
    Json::Value params;
    params[jss::account] = "invalid-address";

    // Act
    auto result = doMyHandler(context);

    // Assert
    ASSERT_EQ(result[jss::error_code].asInt(), rpcACT_MALFORMED);
}

}  // namespace ripple::test
```

**Running Tests**:

```bash
cd build
cmake -DBUILD_TESTS=ON ..
make

# Run all tests
./rippled-tests

# Run specific test suite
./rippled-tests --gtest_filter="MyHandlerTest.*"

# Run with verbose output
./rippled-tests --gtest_filter="MyHandlerTest.*" -v
```

***

### Strategy 2: Integration Testing

**Testing with Live Ledger**:

```bash
# Terminal 1: Start rippled in test mode
./rippled --standalone --start --log-level rpc=debug

# Terminal 2: Send test requests
curl -s -X POST \
  http://localhost:5005 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "account_info",
    "params": {
      "account": "rN7n7otQDd6FczFgLdlqtyMVrnPQGEDE8"
    }
  }' | jq .

# Watch logs in Terminal 1
tail -f rippled.log | grep RPC
```

***

### Strategy 3: Performance Testing

**Measuring Handler Execution Time**:

```cpp
#include <chrono>

Json::Value doMyHandler(RPC::JsonContext& context)
{
    auto start = std::chrono::high_resolution_clock::now();

    // Handler implementation
    Json::Value result = buildResponse(context);

    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
        end - start);

    JLOG(context.app.journal("RPC").debug())
        << "Handler execution: " << duration.count() << "ms";

    return result;
}
```

***

## Performance Troubleshooting

### Issue: Handler is Slow

**Diagnostic Steps**:

1. **Add timing logs**:

```cpp
JLOG(journal.debug()) << "Starting parameter parsing";
// parse parameters
JLOG(journal.debug()) << "Parameter parsing complete";

JLOG(journal.debug()) << "Starting ledger lookup";
// lookup ledger
JLOG(journal.debug()) << "Ledger lookup complete";

JLOG(journal.debug()) << "Starting ledger queries";
// query ledger
JLOG(journal.debug()) << "Ledger queries complete";
```

2. **Identify bottleneck**:

```bash
# From logs, which step takes longest?
tail -f rippled.log | grep MyHandler
```

3. **Optimize identified bottleneck**:
   * Cache frequently accessed values
   * Minimize ledger reads
   * Use indexed lookups when available
   * Avoid redundant computations

### Issue: High Memory Usage

**Diagnostic**:

```cpp
// Are you creating large intermediate objects?
std::vector<SLE> allAccounts;  // AVOID for large sets

// Better: Stream or use iterators
for (auto const& account : accounts) {
    // Process one at a time
}

// Are you keeping references alive?
auto const& ref = expense_object;  // Keeps reference
// ... lots of code ...
// Reference still alive, can't be freed
```

***

## Checklist: Debug Workflow

When your handler isn't working:

* [ ] **Compile errors?**
  * Check includes
  * Verify jss:: constants exist
  * Check type mismatches
  * See "Common Compilation Errors" section
* [ ] **Crashes immediately?**
  * Use debugger to get stack trace
  * Check for null pointer dereferences
  * Verify optional types before use
* [ ] **Returns wrong result?**
  * Add logging to see parameter values
  * Verify ledger lookup succeeded
  * Check JSON response structure
  * Print intermediate values
* [ ] **Slow execution?**
  * Add timing logs around sections
  * Identify slowest section
  * Minimize ledger operations
  * Cache repeated queries
* [ ] **Inconsistent behavior?**
  * Check error handling paths
  * Verify state assumptions
  * Test with different inputs
  * Check for race conditions (multi-threaded)

***

## Resources

**Debugging Tools**:

* GDB: <https://sourceware.org/gdb/>
* LLDB: <https://lldb.llvm.org/>
* Valgrind: <https://valgrind.org/> (memory debugging)

**Rippled Specific**:

* Source: <https://github.com/XRPLF/rippled>
* Build docs: Rippled documentation
* Community: XRPL Discord #coredev channel

***

## Navigation

[← Back to Appendices](/core-dev-bootcamp/module07/appendices.md) | [Helper Functions](/core-dev-bootcamp/module07/appendices/helper-functions.md)

***

**Related Module Sections**:

* [Implementing Custom Handlers](/core-dev-bootcamp/module07/implementing-custom-handlers.md)
* [Error Handling and Validation](https://github.com/XRPL-Commons/xrpl-trainings/blob/main/core-dev-bootcamp/module07/error-handling-validation.md)
* [Testing RPC Handlers](/core-dev-bootcamp/module07/testing-rpc-handlers.md)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.xrpl-commons.org/core-dev-bootcamp/module07/appendices/debugging-guide.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
