news 2026/9/18 3:59:36

xrpld 关系数据库接口(Relational Database Interface)全解析:从 `[relational_db]` 配置到 SQLite 节点数据库实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
xrpld 关系数据库接口(Relational Database Interface)全解析:从 `[relational_db]` 配置到 SQLite 节点数据库实现

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_txledger等 RPC 的查询能力,也依赖这一层提供的读取接口。

其设计原则可以归纳为两条(见 README):

  1. SQL 集中存放:所有硬编码的 SQL 语句都应存放在xrpld/app/rdb目录下的文件中。除测试模块外,xrpld 中任何其他文件都不得新增硬编码 SQL。这一约束让所有数据库访问语句可以被统一审查、统一维护,避免 SQL 散落在业务代码各处。
  2. 抽象基类 + 派生实现:基类RelationalDatabase被多个派生类继承,每个派生类为一种具体的数据库系统(如 SQLite、PostgreSQL)提供操作接口,从而把“数据库方言差异”隔离在派生类内部。

从代码层面看,这两条原则在 RelationalDatabase.h(抽象基类)与 SQLiteDatabase.h(当前唯一的派生实现)中得到了落实。

概览:接口的三层结构

按照 README 的 Overview 描述,整个接口分为三个层次:

  • 主数据存储层:抽象接口RelationalDatabaseSQLiteDatabase(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 时,getTransactionsMinLedgerSeqgetAccountTransactionsMinLedgerSeqdeleteTransactionByLedgerSeq等交易表操作会直接返回空值(见 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.hinclude/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),由派生类实现;
  • 派生类包括SQLiteDatabasePostgresDatabase(后者已在当前代码中移除)。

从当前源码看,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)、getLedgerInfoByIndexgetNewestLedgerInfogetLedgerInfoByHashgetHashByIndexgetHashesByIndex(单账本与区间批量两种重载,L180-L203);
  • 交易查询getTxHistory(返回最近 20 笔交易,L212)、getTransaction(支持按账本区间判定TxSearched::All/Some/Unknown,L452);
  • 账户交易分页查询getOldestAccountTxs/getNewestAccountTxs、对应的二进制变体getOldestAccountTxsB/getNewestAccountTxsB,以及基于 marker 的分页方法oldestAccountTxPage/newestAccountTxPage/oldestAccountTxPageB/newestAccountTxPageB(L329-L435);
  • 数据删除deleteTransactionByLedgerSeqdeleteBeforeLedgerSeqdeleteTransactionsBeforeLedgerSeqdeleteAccountTransactionsBeforeLedgerSeq(用于账本裁剪,L236-L263);
  • 统计与空间占用getTransactionCountgetAccountTransactionCountgetLedgerCountMinMaxgetKBUsedAll/getKBUsedLedger/getKBUsedTransaction
  • 写入与生命周期saveValidatedLedger(持久化已验证账本,L295)、closeLedgerDBcloseTransactionDB

接口还定义了若干与查询语义强相关的数据结构,理解这些结构有助于读懂整个查询链:

  • LedgerHashPair:账本哈希与其父账本哈希的配对(L36-L40);
  • LedgerRange:账本序号区间min/max(L42-L46);
  • AccountTxMarker:分页游标,由ledgerSeqtxnSeq组成(L74-L78);
  • AccountTxOptions/AccountTxPageOptions:查询参数集合,包含账户、账本区间(min/max 为 0 表示该方向无界)、offset、limit、是否不限量bUnlimited等(L80-L101);
  • DelegateFilterDelegateType:用于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::SetupDatabaseCon::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_modesynchronoustemp_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>与成功标志);
  • 泛化查询/删除助手:getMinLedgerSeqgetMaxLedgerSeqdeleteByLedgerSeqdeleteBeforeLedgerSeqgetRows等,均以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

结合当前仓库的实际代码,可以梳理出如下演变(均为仓库内可验证的事实):

  1. PostgresDatabase已不存在:全仓库搜索仅能在 README 文本中命中PostgresDatabase,无任何头文件或实现文件,说明该派生类已从代码库移除,当前只有SQLiteDatabase一个实现;
  2. 静态工厂改名换位:README 描述的RelationalDatabase::init()RelationalDatabase.cpp已不在src/xrpld/app/rdb目录中,取而代之的是 SQLiteDatabase.h 中的setupRelationalDatabase工厂函数,职责一致(按配置创建具体实例,建议以单例使用);
  3. 文件迁移RelationalDatabase.h上移至include/xrpl/rdb/State/Vacuum/Wallet三个模块迁移至xrpl/server命名空间(include/xrpl/server/src/libxrpl/server/);SQLiteDatabase的实现下沉到backend/子目录,与Node模块并列。

这种“接口头文件进公共 include、实现按模块归位”的演化,使得抽象接口可以被libxrplxrpld两侧共享,而具体数据库操作仍集中在rdb目录,保持了 README 最初确立的“SQL 集中于一处”的原则。

总结:如何在你的节点上使用这一层

对运行和维护 xrpld 节点的用户而言,本文涉及的技术点可以转化为三个可落地的操作:

  1. 配置后端:如需显式指定节点数据库后端,在配置文件中加入[relational_db]段并设置backend=sqlite(当前唯一合法值);该段为可选项,缺省即 SQLite;
  2. 理解数据文件:节点数据库对应ledger.db(账本表Ledgers,含LedgerSeq索引)与transaction.db(交易表Transactions及账户交易表),建表脚本见 DBInit.h,数据库连接管理见 DatabaseCon.h 与 src/libxrpl/rdb/DatabaseCon.cpp;
  3. 追踪查询链路:任何涉及账本/交易/账户历史的功能(如account_txledgertxRPC),其数据访问都收敛到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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 3:58:50

电动汽车充电站选址定容:蒙特卡罗、Voronoi图与排队论实战解析

简介&#xff1a;一份关于电动汽车充电站规划研究的学术PDF&#xff0c;适合电动汽车产业研究人员、电网规划工程师及相关专业师生查阅。资源为单文件PDF&#xff0c;压缩包大小约4.36MB。内容系统梳理了充电站选址定容的优化方法&#xff1a;以社会总成本最小为目标建模&#…

作者头像 李华
网站建设 2026/9/18 3:58:07

pgvector 快速上手:在 PostgreSQL 里存向量、建索引、跑相似查询

pgvector 快速上手&#xff1a;在 PostgreSQL 里存向量、建索引、跑相似查询 【免费下载链接】pgvector Open-source vector similarity search for Postgres 项目地址: https://gitcode.com/GitHub_Trending/pg/pgvector 如果你已经用 PostgreSQL 存业务数据&#xff0…

作者头像 李华
网站建设 2026/9/18 3:57:09

Fluent Journal文件自动化后台批量计算实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 3:56:36

C++观察者模式实战:从原理、代码到工程化避坑指南

观察者模式在C里算得上是最实用的几个设计模式之一。它的核心价值就一句话&#xff1a;当某个对象状态发生变化时&#xff0c;所有依赖它的对象都能自动收到通知。听起来很玄乎&#xff0c;但你每天用的GUI按钮点击、游戏里的成就系统、行情软件的K线刷新&#xff0c;背后都是这…

作者头像 李华
网站建设 2026/9/18 3:56:12

计算机组成原理第七章:控制单元设计核心解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华