xrpld 关系数据库接口(Relational Database Interface)全解析:从[relational_db]配置到 SQLite 节点数据库实现
【免费下载链接】rippledDecentralized cryptocurrency blockchain daemon implementing the XRP Ledger protocol in C++项目地址: https://gitcode.com/GitHub_Trending/ri/rippled
导读
本文以 src/xrpld/app/rdb/README.md 为核心,系统讲解 XRP Ledger 守护进程(xrpld)中关系数据库接口(Relational Database Interface)的设计原则、配置方式、目录结构与源码实现。读完本文,你将掌握:[relational_db]配置段如何选择后端数据库、RelationalDatabase抽象基类与SQLiteDatabase派生类的关系、节点数据库(ledger.db / transaction.db)的建表与 PRAGMA 调优参数,以及 PeerFinder、State、Vacuum、Wallet 等辅助数据库在代码库中的实际落点,可直接对照源码继续深入。
模块定位与核心设计原则
关系数据库接口(Relational Database Interface)是 xrpld 中负责持久化账本、交易、账户交易记录等结构化数据的统一抽象层。它在整个 xrpld 架构中处于承上启下的位置:共识与交易处理流程产出的已验证账本(validated ledger)与交易数据,最终都要落入关系数据库;而account_tx、ledger等 RPC 的查询能力,也依赖这一层提供的读取接口。
其设计原则可以归纳为两条(见 README):
- SQL 集中存放:所有硬编码的 SQL 语句都应存放在
xrpld/app/rdb目录下的文件中。除测试模块外,xrpld 中任何其他文件都不得新增硬编码 SQL。这一约束让所有数据库访问语句可以被统一审查、统一维护,避免 SQL 散落在业务代码各处。 - 抽象基类 + 派生实现:基类
RelationalDatabase被多个派生类继承,每个派生类为一种具体的数据库系统(如 SQLite、PostgreSQL)提供操作接口,从而把“数据库方言差异”隔离在派生类内部。
从代码层面看,这两条原则在 RelationalDatabase.h(抽象基类)与 SQLiteDatabase.h(当前唯一的派生实现)中得到了落实。
概览:接口的三层结构
按照 README 的 Overview 描述,整个接口分为三个层次:
- 主数据存储层:抽象接口
RelationalDatabase被SQLiteDatabase(README 撰写时还存在PostgresDatabase)继承,用于操作软件的主数据存储,即保存交易、账户、账本等核心数据的数据库; - 辅助函数层:
detail目录下的文件提供补充函数,供上述派生类访问底层数据库; - 次级数据库访问层:接口顶层剩余的文件,供软件各模块访问各类次级关系数据库(如 PeerFinder 数据库)。
需要特别说明的是:README 中提到的PostgresDatabase在当前仓库代码中已经不存在(全仓库搜索仅能在 README 文本中命中),当前实际生效的派生实现只有SQLiteDatabase一个。这一点在“类的现状与演变”一节会详细展开。
配置:[relational_db]配置段
README 指出,配置段[relational_db]下有一个名为backend的属性,其值用于指定节点数据库(node databases)使用哪种数据库实现。目前该属性的唯一合法取值是sqlite:
[relational_db] backend=sqlite几点实操说明:
- 该配置段决定的是节点数据库(node databases,即保存账本与交易主数据的那组数据库)的后端选择;
- 需要指出的是,当前仓库的示例配置文件 cfg/xrpld-example.cfg 中并未出现
[relational_db]段,说明该段是可选的,缺省即使用默认的 SQLite 后端; - 从源码看,
SQLiteDatabase的成员useTxTables_(见 SQLiteDatabase.h)控制是否启用交易相关表;当其为 false 时,getTransactionsMinLedgerSeq、getAccountTransactionsMinLedgerSeq、deleteTransactionByLedgerSeq等交易表操作会直接返回空值(见 SQLiteDatabase.cpp),这解释了为什么某些节点可以只保留账本数据而不启用交易表。
目录结构与源文件
README 给出的目录结构以 2021 年 11 月为时间点。为了对比,先完整保留原文档的结构:
src/xrpld/app/rdb/ ├── backend │ ├── detail │ │ ├── Node.cpp │ │ ├── Node.h │ │ └── SQLiteDatabase.cpp │ └── SQLiteDatabase.h ├── detail │ ├── PeerFinder.cpp │ ├── RelationalDatabase.cpp │ ├── State.cpp │ ├── Vacuum.cpp │ └── Wallet.cpp ├── PeerFinder.h ├── RelationalDatabase.h ├── README.md ├── State.h ├── Vacuum.h └── Wallet.h当前仓库(2026 年)的实际目录结构如下,其中发生了明显的文件迁移,阅读源码时请以当前结构为准:
src/xrpld/app/rdb/ ├── backend/ │ ├── detail/ │ │ ├── Node.cpp │ │ ├── Node.h │ │ └── SQLiteDatabase.cpp │ └── SQLiteDatabase.h ├── detail/ │ └── PeerFinder.cpp ├── PeerFinder.h └── README.md迁移后的相关文件实际落点:
| 原 README 中的位置 | 当前实际位置 |
|---|---|
RelationalDatabase.h | include/xrpl/rdb/RelationalDatabase.h |
State.[h\|cpp] | include/xrpl/server/State.h、src/libxrpl/server/State.cpp |
Vacuum.[h\|cpp] | include/xrpl/server/Vacuum.h、src/libxrpl/server/Vacuum.cpp |
Wallet.[h\|cpp] | include/xrpl/server/Wallet.h、src/libxrpl/server/Wallet.cpp |
RelationalDatabase.cpp(内含静态方法init) | 已由 SQLiteDatabase.h 中的工厂函数setupRelationalDatabase取代 |
文件内容一览
README 中的“File Contents”表格完整列出每个文件负责的内容,这是理解模块分工的关键索引,整理如下:
| 文件 | 内容 |
|---|---|
Node.[h\|cpp] | 定义/实现SQLiteDatabase用于与 SQLite 节点数据库交互的方法 |
SQLiteDatabase.[h\|cpp] | 定义/实现类SQLiteDatabase/SQLiteDatabaseImp,继承自RelationalDatabase,用于操作主数据存储 |
PeerFinder.[h\|cpp] | 定义/实现与 PeerFinder SQLite 数据库交互的方法 |
RelationalDatabase.cpp | 实现静态方法RelationalDatabase::init,用于初始化RelationalDatabase实例 |
RelationalDatabase.h | 定义抽象类RelationalDatabase,即关系数据库接口的主类 |
State.[h\|cpp] | 定义/实现与 State SQLite 数据库交互的方法,涉及账本删除与数据库轮换(ledger deletion and database rotation) |
Vacuum.[h\|cpp] | 定义/实现对 SQLite 数据库执行VACUUM操作的方法 |
Wallet.[h\|cpp] | 定义/实现与 Wallet SQLite 数据库交互的方法 |
对照当前源码,上述各文件的核心函数签名均可验证(详见后文各节)。
核心类:抽象的RelationalDatabase
抽象类RelationalDatabase是关系数据库接口的主类,定义于同名头文件 include/xrpl/rdb/RelationalDatabase.h 中。README 描述其具备以下特征:
- 提供静态方法
init(),调用时根据系统配置创建某个派生类的具体实例; - 除
init()之外的所有方法均为虚方法(virtual),由派生类实现; - 派生类包括
SQLiteDatabase与PostgresDatabase(后者已在当前代码中移除)。
从当前源码看,README 中描述的静态工厂职责已由 SQLiteDatabase.h 末尾声明的自由函数承担:
/** * @brief setupRelationalDatabase Creates and returns a SQLiteDatabase * instance based on configuration. It's recommended to use it as * a singleton, but it's not enforced (e.g. if you have more than one * database). */ SQLiteDatabase setupRelationalDatabase(ServiceRegistry& registry, Config const& config, JobQueue& jobQueue);RelationalDatabase定义了一组覆盖面很广的纯虚接口,按功能可分为以下几类:
- 账本查询:
getMinLedgerSeq/getMaxLedgerSeq(L139-L148)、getLedgerInfoByIndex、getNewestLedgerInfo、getLedgerInfoByHash、getHashByIndex、getHashesByIndex(单账本与区间批量两种重载,L180-L203); - 交易查询:
getTxHistory(返回最近 20 笔交易,L212)、getTransaction(支持按账本区间判定TxSearched::All/Some/Unknown,L452); - 账户交易分页查询:
getOldestAccountTxs/getNewestAccountTxs、对应的二进制变体getOldestAccountTxsB/getNewestAccountTxsB,以及基于 marker 的分页方法oldestAccountTxPage/newestAccountTxPage/oldestAccountTxPageB/newestAccountTxPageB(L329-L435); - 数据删除:
deleteTransactionByLedgerSeq、deleteBeforeLedgerSeq、deleteTransactionsBeforeLedgerSeq、deleteAccountTransactionsBeforeLedgerSeq(用于账本裁剪,L236-L263); - 统计与空间占用:
getTransactionCount、getAccountTransactionCount、getLedgerCountMinMax、getKBUsedAll/getKBUsedLedger/getKBUsedTransaction; - 写入与生命周期:
saveValidatedLedger(持久化已验证账本,L295)、closeLedgerDB、closeTransactionDB。
接口还定义了若干与查询语义强相关的数据结构,理解这些结构有助于读懂整个查询链:
LedgerHashPair:账本哈希与其父账本哈希的配对(L36-L40);LedgerRange:账本序号区间min/max(L42-L46);AccountTxMarker:分页游标,由ledgerSeq与txnSeq组成(L74-L78);AccountTxOptions/AccountTxPageOptions:查询参数集合,包含账户、账本区间(min/max 为 0 表示该方向无界)、offset、limit、是否不限量bUnlimited等(L80-L101);DelegateFilter与DelegateType:用于account_tx中按委托关系过滤的枚举与结构(Actor表示他人代签、Authorizer表示本账户代他人签名,L51-L62);LedgerSpecifier:账本定位符,支持账本区间、快捷方式、序号、哈希四种形态(L110)。
SQLite 实现:SQLiteDatabase与节点数据库
SQLiteDatabase是当前唯一的派生实现(final类,见 SQLiteDatabase.h),它完整覆写了RelationalDatabase的所有纯虚方法。其私有成员揭示了节点数据库的组织方式:
private: std::reference_wrapper<ServiceRegistry> registry_; bool useTxTables_; beast::Journal j_; std::unique_ptr<DatabaseCon> ledgerDb_, txdb_;ledgerDb_与txdb_分别是账本数据库与交易数据库的句柄,通过checkoutLedger()/checkoutTransaction()取出soci::session使用(L465-L480);makeLedgerDBs负责按DatabaseCon::Setup与DatabaseCon::CheckpointerSetup打开这两类数据库,实际工作委托给detail::makeLedgerDBs(见 SQLiteDatabase.cpp);- 构造函数签名
SQLiteDatabase(ServiceRegistry& registry, Config const& config, JobQueue& jobQueue)(L391)表明数据库的创建依赖配置对象、服务注册表与任务队列(数据库后台写入由 JobQueue 驱动)。
数据库初始化与建表:DBInit.h中的 SQL 与 PRAGMA
节点数据库的表结构 DDL 与连接级调优参数集中在 include/xrpl/rdb/DBInit.h,这是“所有 SQL 集中在 rdb 模块”原则的典型体现:
PRAGMA 设置以函数形式暴露(L20-L36),避免未替换的格式化模板被静默忽略:
[[nodiscard]] inline std::string commonDbPragmaJournal(std::string_view journalMode) { return std::format("PRAGMA journal_mode={};", journalMode); } [[nodiscard]] inline std::string commonDbPragmaSync(std::string_view synchronous) { return std::format("PRAGMA synchronous={};", synchronous); } [[nodiscard]] inline std::string commonDbPragmaTemp(std::string_view tempStore) { return std::format("PRAGMA temp_store={};", tempStore); }其中journal_mode、synchronous、temp_store是 SQLite 三个最关键的 I/O 与可靠性旋钮:journal_mode控制 WAL/delete 等日志模式,synchronous控制 fsync 频率(OFF/NORMAL/FULL 对应不同的崩溃安全级别),temp_store控制临时表存放位置。头文件注释还给出了一条安全策略:如果配置的账本历史量达到kSqliteTuningCutoff = 10'000'000(L43)以上(含全量历史节点),使用任何较低安全性的 SQLite 调优设置都会记录警告——因为如此体量的数据一旦遇到罕见故障,将极难恢复。
账本数据库ledger.db(L46)的建表脚本kLgrDbInit(L48-L68)核心 DDL 如下:
CREATE TABLE IF NOT EXISTS Ledgers ( LedgerHash CHARACTER(64) PRIMARY KEY, LedgerSeq BIGINT UNSIGNED, PrevHash CHARACTER(64), TotalCoins BIGINT UNSIGNED, ClosingTime BIGINT UNSIGNED, PrevClosingTime BIGINT UNSIGNED, CloseTimeRes BIGINT UNSIGNED, CloseFlags BIGINT UNSIGNED, AccountSetHash CHARACTER(64), TransSetHash CHARACTER(64) ); CREATE INDEX IF NOT EXISTS SeqLedger ON Ledgers(LedgerSeq);可见账本表以LedgerHash(64 字符十六进制哈希)为主键,并用LedgerSeq建立索引以支撑按序号的快速检索;同时该脚本会清理历史遗留的Validations表(“Old table and indexes no longer needed”)。
交易数据库transaction.db(L73)的kTxDbInit(L75)则创建Transactions表(TransID主键、TransType等字段)与账户交易相关表,后续部分以同样的方式继续展开。
数据库方法的三分类
README 将接口提供的方法归纳为三类,这一分类与代码结构完全对应:
类别一:供软件各组件使用的 SQLite 自由函数
这些方法统一以soci::session作为参数以建立与 SQLite 数据库的连接,定义并实现于PeerFinder.[h|cpp]、State.[h|cpp]、Vacuum.[h|cpp]、Wallet.[h|cpp]。它们不依赖RelationalDatabase实例,而是面向具体业务库的“工具型”入口:
- PeerFinder 数据库(当前位于 src/xrpld/app/rdb/PeerFinder.h 与 src/xrpld/app/rdb/detail/PeerFinder.cpp)提供
initPeerFinderDB(初始化并打开连接)、updatePeerFinderDB(按 schema 版本升级)、readPeerFinderDB(遍历全部条目并回调)、savePeerFinderDB(批量保存对等节点条目,见 L20-L47)。这些数据来自peer_finder::Store::Entry,服务于节点发现与网络拓扑维护; - State 数据库(当前位于 include/xrpl/server/State.h)负责账本删除与数据库轮换相关状态;
- Vacuum 数据库操作(当前位于 include/xrpl/server/Vacuum.h)提供唯一入口
doVacuumDB(DatabaseCon::Setup const& setup, beast::Journal j)(L15),负责“创建、初始化并对数据库执行清理”,对应 SQLite 的VACUUM操作,用于回收碎片空间; - Wallet 数据库(当前位于 include/xrpl/server/Wallet.h)负责钱包(密钥/账户)相关数据的存取。
类别二:仅由SQLiteDatabaseImp使用的节点数据库自由函数
定义于Node.[h|cpp](当前位于 src/xrpld/app/rdb/backend/detail/Node.h 与 src/xrpld/app/rdb/backend/detail/Node.cpp)。与类别一不同,这些方法不面向客户端直接调用,而是由RelationalDatabase的派生实例(即SQLiteDatabase)内部调用,用于操作节点存储(node store)拥有的 SQLite 数据库。
Node.h中的核心元素包括:
- 枚举
TableType { Ledgers, Transactions, AccountTransactions }与配套计数kTableTypeCount = 3(L35-L36),统一标识三类主数据表; makeLedgerDBs(L54):打开账本与交易数据库,返回DatabasePairValid(含两个unique_ptr<DatabaseCon>与成功标志);- 泛化查询/删除助手:
getMinLedgerSeq、getMaxLedgerSeq、deleteByLedgerSeq、deleteBeforeLedgerSeq、getRows等,均以soci::session&加TableType定位具体表(L67-L100)。
这一设计带来的直接收益是:SQLiteDatabase.cpp中的每个公共方法都极薄——先existsLedger()/existsTransaction()判断句柄是否存在,再checkoutLedger()/checkoutTransaction()取出 session,最后一行调用detail::层函数完成实际 SQL。例如 SQLiteDatabase.cpp 的getMinLedgerSeq就体现了这个三步模式。
类别三:RelationalDatabase/SQLiteDatabase/PostgresDatabase的成员函数
用于访问节点存储(node store)。即第二节列出的账本查询、交易查询、账户交易分页、删除、统计等纯虚接口及其在SQLiteDatabase中的覆写实现。这一层是 RPC 层与数据库之间的正式“服务契约”,业务代码只依赖RelationalDatabase指针,不感知具体后端。
类层次与现状演变:从 README 到当前代码
README(成文于 2021 年 11 月)描述的类层次为:
RelationalDatabase(抽象基类,含静态工厂 init()) ├── SQLiteDatabase └── PostgresDatabase结合当前仓库的实际代码,可以梳理出如下演变(均为仓库内可验证的事实):
PostgresDatabase已不存在:全仓库搜索仅能在 README 文本中命中PostgresDatabase,无任何头文件或实现文件,说明该派生类已从代码库移除,当前只有SQLiteDatabase一个实现;- 静态工厂改名换位:README 描述的
RelationalDatabase::init()及RelationalDatabase.cpp已不在src/xrpld/app/rdb目录中,取而代之的是 SQLiteDatabase.h 中的setupRelationalDatabase工厂函数,职责一致(按配置创建具体实例,建议以单例使用); - 文件迁移:
RelationalDatabase.h上移至include/xrpl/rdb/;State/Vacuum/Wallet三个模块迁移至xrpl/server命名空间(include/xrpl/server/与src/libxrpl/server/);SQLiteDatabase的实现下沉到backend/子目录,与Node模块并列。
这种“接口头文件进公共 include、实现按模块归位”的演化,使得抽象接口可以被libxrpl与xrpld两侧共享,而具体数据库操作仍集中在rdb目录,保持了 README 最初确立的“SQL 集中于一处”的原则。
总结:如何在你的节点上使用这一层
对运行和维护 xrpld 节点的用户而言,本文涉及的技术点可以转化为三个可落地的操作:
- 配置后端:如需显式指定节点数据库后端,在配置文件中加入
[relational_db]段并设置backend=sqlite(当前唯一合法值);该段为可选项,缺省即 SQLite; - 理解数据文件:节点数据库对应
ledger.db(账本表Ledgers,含LedgerSeq索引)与transaction.db(交易表Transactions及账户交易表),建表脚本见 DBInit.h,数据库连接管理见 DatabaseCon.h 与 src/libxrpl/rdb/DatabaseCon.cpp; - 追踪查询链路:任何涉及账本/交易/账户历史的功能(如
account_tx、ledger、txRPC),其数据访问都收敛到RelationalDatabase接口 →SQLiteDatabase覆写 →detail::Node层 SQL 这条调用链上,可分别阅读 RelationalDatabase.h、SQLiteDatabase.cpp 与 Node.cpp 逐层深入。
如需进一步研读,建议按以下顺序展开:先读 README 把握模块边界,再对照 RelationalDatabase.h 理解接口全集,最后以 SQLiteDatabase.cpp 为入口追踪具体方法的实现细节。
【免费下载链接】rippledDecentralized cryptocurrency blockchain daemon implementing the XRP Ledger protocol in C++项目地址: https://gitcode.com/GitHub_Trending/ri/rippled
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考