Merge remote-tracking branch 'upstream/develop' into mergeDevelop

This commit is contained in:
Peter Chen
2026-06-15 08:48:34 -07:00
14 changed files with 659 additions and 378 deletions

View File

@@ -27,6 +27,19 @@ def get_cmake_args(build_type: str, extra_args: str) -> str:
return " ".join(args)
def runs_on_event(exclude_event_types: list[str], event: str | None) -> bool:
"""Whether a config should run for the current event.
'exclude_event_types' is a list of GitHub event names (e.g.
["pull_request"]) on which the config should NOT run; an empty list means
the config runs on every event. When no event is given (event is None), no
filtering is applied.
"""
if event is None:
return True
return event not in exclude_event_types
# ---------------------------------------------------------------------------
# Input types — shapes of the JSON config files
# ---------------------------------------------------------------------------
@@ -43,6 +56,9 @@ class LinuxConfig:
suffix: str = ""
extra_cmake_args: str = ""
image: str = "" # only used by package_configs entries
# List of GitHub event names (e.g. "pull_request") on which this config
# should NOT run. Empty means it runs on every event.
exclude_event_types: list[str] = dataclasses.field(default_factory=list)
@dataclasses.dataclass
@@ -77,6 +93,9 @@ class PlatformConfig:
build_type: list[str]
build_only: bool = False # if true, skip tests (e.g. macos/Windows Debug)
extra_cmake_args: str = ""
# List of GitHub event names (e.g. "pull_request") on which this config
# should NOT run. Empty means it runs on every event.
exclude_event_types: list[str] = dataclasses.field(default_factory=list)
def __post_init__(self) -> None:
if isinstance(self.build_type, str):
@@ -151,16 +170,21 @@ _ARCHS: dict[str, Architecture] = {
}
def expand_linux_matrix(linux: LinuxFile) -> list[MatrixEntry]:
def expand_linux_matrix(
linux: LinuxFile, event: str | None = None
) -> list[MatrixEntry]:
"""Expand a LinuxFile into a flat list of matrix entries.
Each config entry is expanded over the cross-product of its
compiler, build_type, sanitizers, and architecture lists.
compiler, build_type, sanitizers, and architecture lists. Configs that
exclude the current event are skipped.
"""
entries: list[MatrixEntry] = []
for distro, configs in linux.configs.items():
for cfg in configs:
if not runs_on_event(cfg.exclude_event_types, event):
continue
# An empty sanitizers list means "one entry with no sanitizer".
effective_sanitizers = cfg.sanitizers or [""]
effective_archs = {arch: _ARCHS[arch] for arch in cfg.arch}
@@ -218,13 +242,20 @@ def expand_linux_packaging(linux: LinuxFile) -> list[PackagingEntry]:
return entries
def expand_platform_matrix(pf: PlatformFile) -> list[MatrixEntry]:
"""Expand a PlatformFile (macOS or Windows) into matrix entries."""
def expand_platform_matrix(
pf: PlatformFile, event: str | None = None
) -> list[MatrixEntry]:
"""Expand a PlatformFile (macOS or Windows) into matrix entries.
Configs that exclude the current event are skipped.
"""
platform_name, arch = pf.platform.split("/")
is_windows = platform_name == "windows"
entries: list[MatrixEntry] = []
for cfg in pf.configs:
if not runs_on_event(cfg.exclude_event_types, event):
continue
for build_type in cfg.build_type:
entries.append(
MatrixEntry(
@@ -262,6 +293,14 @@ if __name__ == "__main__":
help="Emit the Linux packaging matrix instead of the build/test matrix.",
action="store_true",
)
parser.add_argument(
"-e",
"--event",
help="The GitHub event name that triggered the workflow (e.g. 'push', "
"'pull_request'). Configs are filtered by their 'event_type'. If "
"omitted, no filtering is applied.",
default=None,
)
args = parser.parse_args()
matrix: list[MatrixEntry] | list[PackagingEntry] = []
@@ -270,12 +309,16 @@ if __name__ == "__main__":
matrix = expand_linux_packaging(LinuxFile.load(THIS_DIR / "linux.json"))
else:
if args.config in ("linux", None):
matrix += expand_linux_matrix(LinuxFile.load(THIS_DIR / "linux.json"))
matrix += expand_linux_matrix(
LinuxFile.load(THIS_DIR / "linux.json"), args.event
)
if args.config in ("macos", None):
matrix += expand_platform_matrix(PlatformFile.load(THIS_DIR / "macos.json"))
matrix += expand_platform_matrix(
PlatformFile.load(THIS_DIR / "macos.json"), args.event
)
if args.config in ("windows", None):
matrix += expand_platform_matrix(
PlatformFile.load(THIS_DIR / "windows.json")
PlatformFile.load(THIS_DIR / "windows.json"), args.event
)
print(f"matrix={json.dumps({'include': [dataclasses.asdict(e) for e in matrix]})}")

View File

@@ -10,7 +10,7 @@
{
"compiler": ["gcc", "clang"],
"build_type": ["Debug"],
"build_type": ["Debug", "Release"],
"arch": ["amd64"],
"sanitizers": ["address", "undefinedbehavior"]
},
@@ -41,7 +41,8 @@
"build_type": ["Debug"],
"arch": ["amd64"],
"suffix": "unity",
"extra_cmake_args": "-Dunity=ON"
"extra_cmake_args": "-Dunity=ON",
"exclude_event_types": ["pull_request"]
}
],

View File

@@ -9,7 +9,8 @@
{
"build_type": "Debug",
"extra_cmake_args": "-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
"build_only": true
"build_only": true,
"exclude_event_types": ["pull_request"]
}
]
}

View File

@@ -3,6 +3,10 @@
"runner": ["self-hosted", "Windows", "devbox"],
"configs": [
{ "build_type": "Release" },
{ "build_type": "Debug", "build_only": true }
{
"build_type": "Debug",
"build_only": true,
"exclude_event_types": ["pull_request"]
}
]
}

View File

@@ -35,4 +35,5 @@ jobs:
id: generate
env:
GENERATE_CONFIG: ${{ inputs.os != '' && format('--config={0}', inputs.os) || '' }}
run: ./generate.py ${GENERATE_CONFIG} >>"${GITHUB_OUTPUT}"
GENERATE_EVENT: ${{ github.event_name }}
run: ./generate.py ${GENERATE_CONFIG} --event="${GENERATE_EVENT}" >>"${GITHUB_OUTPUT}"

View File

@@ -52,52 +52,50 @@ include(default)
{% endif %}
{# Frame pointer required for meaningful stack traces; -O1 for reasonable performance #}
{% set compile_flags = ["-fno-omit-frame-pointer", "-O1"] %}
{% set sanitizer_compiler_flags = ["-fno-omit-frame-pointer", "-O1"] %}
{% if compiler == "gcc" %}
{# Suppress false positive warnings with GCC #}
{% set _ = compile_flags.append("-Wno-stringop-overflow") %}
{% set _ = sanitizer_compiler_flags.append("-Wno-stringop-overflow") %}
{% set relocation_flags = [] %}
{% if arch == "x86_64" and enable_asan %}
{# Large code model prevents relocation errors in instrumented ASAN binaries #}
{% set _ = compile_flags.append("-mcmodel=large") %}
{% set _ = sanitizer_compiler_flags.append("-mcmodel=large") %}
{% set _ = relocation_flags.append("-mcmodel=large") %}
{% elif enable_tsan %}
{# GCC doesn't support atomic_thread_fence with TSAN; suppress warnings #}
{% set _ = compile_flags.append("-Wno-tsan") %}
{% set _ = sanitizer_compiler_flags.append("-Wno-tsan") %}
{% if arch == "x86_64" %}
{# Medium code model for TSAN; large is incompatible #}
{% set _ = compile_flags.append("-mcmodel=medium") %}
{% set _ = sanitizer_compiler_flags.append("-mcmodel=medium") %}
{% set _ = relocation_flags.append("-mcmodel=medium") %}
{% endif %}
{% endif %}
{% set fsanitize = "-fsanitize=" ~ ",".join(sanitizer_types) %}
{% set _ = compile_flags.append(fsanitize) %}
{% set _ = sanitizer_compiler_flags.append(fsanitize) %}
{% set _ = relocation_flags.append(fsanitize) %}
{% set sanitizer_compiler_flags = " ".join(compile_flags) %}
{% set sanitizer_linker_flags = " ".join(relocation_flags) %}
{% set sanitizer_linker_flags = relocation_flags %}
{% elif compiler == "clang" or compiler == "apple-clang" %}
{% set fsanitize = "-fsanitize=" ~ ",".join(sanitizer_types) %}
{% set _ = compile_flags.append(fsanitize) %}
{% set _ = sanitizer_compiler_flags.append(fsanitize) %}
{% set sanitizer_compiler_flags = " ".join(compile_flags) %}
{% set sanitizer_linker_flags = fsanitize %}
{% set sanitizer_linker_flags = [fsanitize] %}
{% endif %}
[conf]
tools.build:defines+={{defines}}
tools.build:cxxflags+=['{{sanitizer_compiler_flags}}']
tools.build:sharedlinkflags+=['{{sanitizer_linker_flags}}']
tools.build:exelinkflags+=['{{sanitizer_linker_flags}}']
tools.build:cxxflags+={{sanitizer_compiler_flags}}
tools.build:sharedlinkflags+={{sanitizer_linker_flags}}
tools.build:exelinkflags+={{sanitizer_linker_flags}}
tools.info.package_id:confs+=["tools.build:cxxflags", "tools.build:exelinkflags", "tools.build:sharedlinkflags", "tools.build:defines"]
# &: means "apply only to the consumer/root package"
&:tools.cmake.cmaketoolchain:extra_variables={"SANITIZERS": "{{sanitizers}}", "SANITIZERS_COMPILER_FLAGS": "{{sanitizer_compiler_flags}}", "SANITIZERS_LINKER_FLAGS": "{{sanitizer_linker_flags}}"}
&:tools.cmake.cmaketoolchain:extra_variables={"SANITIZERS": "{{sanitizers}}", "SANITIZERS_COMPILER_FLAGS": "{{sanitizer_compiler_flags | join(' ')}}", "SANITIZERS_LINKER_FLAGS": "{{sanitizer_linker_flags | join(' ')}}"}
[options]
{% if enable_asan %}

View File

@@ -238,8 +238,10 @@ words:
- pyenv
- pyparsing
- qalloc
- qbsprofile
- queuable
- Raphson
- rcflags
- replayer
- rerandomization
- rerandomized

13
flake.lock generated
View File

@@ -2,17 +2,18 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1780749050,
"narHash": "sha256-3av0pIjlOWQ6rDbNOmpUSvbNnJkGORQKKjb4LtCZsIY=",
"lastModified": 1781173989,
"narHash": "sha256-fnzKKPvS+oieI/pTzotA5tkoM47EB1NpaBcgk4R97hE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a799d3e3886da994fa307f817a6bc705ae538eeb",
"rev": "8c91a71d13451abc40eb9dae8910f972f979852f",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-unstable",
"type": "indirect"
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-custom-glibc": {

View File

@@ -1,7 +1,7 @@
{
description = "Nix related things for xrpld";
inputs = {
nixpkgs.url = "nixpkgs/nixos-unstable";
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
# nixpkgs snapshot (2020-06-30) that shipped glibc 2.31 as the primary
# version — matches the system libc on Ubuntu 20.04 LTS. Imported
# manually (flake = false) because this revision predates nixpkgs'

View File

@@ -15,6 +15,7 @@
// Add new amendments to the top of this list.
// Keep it sorted in reverse chronological order.
XRPL_FEATURE(ConfidentialTransfer, Supported::No, VoteBehavior::DefaultNo)
XRPL_FIX (Cleanup3_3_0, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FIX (Cleanup3_2_0, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(MPTokensV2, Supported::No, VoteBehavior::DefaultNo)
XRPL_FIX (Cleanup3_1_3, Supported::Yes, VoteBehavior::DefaultYes)

View File

@@ -17,6 +17,7 @@
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Book.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/MPTAmount.h>
@@ -249,7 +250,13 @@ TOfferStreamBase<TIn, TOut>::step()
continue;
}
if (entry->isFieldPresent(sfDomainID) &&
// Pre-fixCleanup3_3_0: validate domain membership for any book.
// Post-fixCleanup3_3_0: only validate when walking a domain book.
// Hybrid offers carry sfDomainID but also participate in the open
// book; expiry of the owner's domain credential should not evict
// the offer from the open book.
if ((!view_.rules().enabled(fixCleanup3_3_0) || book_.domain.has_value()) &&
entry->isFieldPresent(sfDomainID) &&
!permissioned_dex::offerInDomain(
view_, entry->key(), entry->getFieldH256(sfDomainID), j_))
{

File diff suppressed because it is too large Load Diff

View File

@@ -3,12 +3,21 @@
#include <test/jtx/Oracle.h>
#include <test/jtx/amount.h>
#include <xrpld/app/ledger/OpenLedger.h>
#include <xrpl/basics/Number.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/OpenView.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/jss.h>
#include <cstdlib>
#include <memory>
#include <optional>
#include <string>
#include <vector>
@@ -312,11 +321,91 @@ public:
}
}
void
testNullTxReadMeta()
{
testcase("Null txRead metadata");
using namespace jtx;
// Verify that iteratePriceData handles a null txRead result
// gracefully (returns early) rather than crashing with a
// nullptr dereference. This simulates local data corruption
// where a transaction referenced by sfPreviousTxnID is missing
// from the ledger's transaction map.
Env env(*this);
auto const baseFee = static_cast<int>(env.current()->fees().base.drops());
Account const owner{"owner"};
env.fund(XRP(1'000), owner);
// Create oracle with XRP/USD and XRP/EUR
Oracle oracle(
env,
{.owner = owner,
.series = {{"XRP", "USD", 740, 1}, {"XRP", "EUR", 840, 1}},
.fee = baseFee});
// Update oracle to only have XRP/EUR, pushing XRP/USD into
// history. iteratePriceData will need to read historical tx
// metadata to find the XRP/USD price.
oracle.set(UpdateArg{.series = {{"XRP", "EUR", 850, 1}}, .fee = baseFee});
OraclesData const oracles{{owner, oracle.documentID()}};
// Precondition: with an uncorrupted oracle, the historical
// traversal must succeed and produce a price for XRP/USD.
// This proves the test reaches iteratePriceData's history
// path; without it, a future change that breaks the setup
// could turn the post-corruption assertion into a vacuous
// pass (objectNotFound is reachable from many unrelated
// code paths).
{
auto const ret = Oracle::aggregatePrice(env, "XRP", "USD", oracles);
BEAST_EXPECT(!ret.isMember(jss::error));
BEAST_EXPECT(ret.isMember(jss::median));
}
// Simulate data corruption: modify the oracle SLE in the open
// ledger to have a bogus sfPreviousTxnID that doesn't exist in
// any ledger. sfPreviousTxnLgrSeq still points to a valid closed
// ledger, so getLedgerBySeq succeeds but txRead returns null.
auto const oracleKeylet = keylet::oracle(owner, oracle.documentID());
uint256 const bogusTxnID{0xABCABCAB};
bool const modified = env.app().getOpenLedger().modify(
[&oracleKeylet, &bogusTxnID](OpenView& view, beast::Journal) -> bool {
auto const sle = view.read(oracleKeylet);
if (!sle)
return false;
auto replacement = std::make_shared<SLE>(*sle, sle->key());
replacement->setFieldH256(sfPreviousTxnID, bogusTxnID);
view.rawReplace(replacement);
return true;
});
// Confirm the injection actually took effect: modify must
// report success, and re-reading the SLE must show the
// bogus hash. Otherwise the failure-mode assertion below
// would not be exercising the null-txRead path at all.
BEAST_EXPECT(modified);
if (auto const sle = env.current()->read(oracleKeylet); BEAST_EXPECT(sle))
BEAST_EXPECT(sle->getFieldH256(sfPreviousTxnID) == bogusTxnID);
// Query for XRP/USD using the "current" (open) ledger.
// The oracle SLE now has a bogus sfPreviousTxnID. The current
// oracle only has EUR, so iteratePriceData will try to read
// history. txRead returns null for the bogus hash, and the
// null check should cause a graceful early return instead of
// a nullptr dereference.
auto const ret = Oracle::aggregatePrice(env, "XRP", "USD", oracles);
BEAST_EXPECT(ret[jss::error].asString() == "objectNotFound");
}
void
run() override
{
testErrors();
testRpc();
testNullTxReadMeta();
}
};

View File

@@ -58,7 +58,6 @@
#include <xrpl/resource/Disposition.h>
#include <xrpl/resource/Fees.h>
#include <xrpl/resource/Gossip.h>
#include <xrpl/server/Handoff.h>
#include <xrpl/server/LoadFeeTrack.h>
#include <xrpl/server/NetworkOPs.h>
#include <xrpl/shamap/SHAMapNodeID.h>
@@ -68,6 +67,7 @@
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/completion_condition.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/strand.hpp>
@@ -392,13 +392,15 @@ PeerImp::removeTxQueue(uint256 const& hash)
void
PeerImp::charge(Resource::Charge const& fee, std::string const& context)
{
if ((usage_.charge(fee, context) == Resource::Disposition::Drop) &&
usage_.disconnect(pJournal_) && strand_.running_in_this_thread())
{
// Sever the connection
overlay_.incPeerDisconnectCharges();
fail("charge: Resources");
}
dispatch(strand_, [this, self = shared_from_this(), fee, context]() {
if (usage_.charge(fee, context) == Resource::Disposition::Drop &&
usage_.disconnect(pJournal_))
{
// Sever the connection.
overlay_.incPeerDisconnectCharges();
fail("charge: Resources");
}
});
}
//------------------------------------------------------------------------------