mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-10 12:16:49 +00:00
Compare commits
17 Commits
ripple/se/
...
3.2.0-b6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79308705c5 | ||
|
|
e24de65f42 | ||
|
|
7fdaa0a5ef | ||
|
|
795dc5e364 | ||
|
|
f6fd5ddb0a | ||
|
|
afcf6fbcdc | ||
|
|
28cc20c816 | ||
|
|
a830ab10ef | ||
|
|
8c0080020f | ||
|
|
9cb0740673 | ||
|
|
242ce3e9e4 | ||
|
|
a5d238e7d4 | ||
|
|
9cb049276d | ||
|
|
93ac1aa7aa | ||
|
|
d9a3af8207 | ||
|
|
8d1083e5ea | ||
|
|
1e45d363c5 |
@@ -191,8 +191,11 @@ CheckOptions:
|
||||
readability-identifier-naming.ParameterCase: camelBack
|
||||
readability-identifier-naming.FunctionCase: camelBack
|
||||
readability-identifier-naming.MemberCase: camelBack
|
||||
readability-identifier-naming.PrivateMemberCase: camelBack
|
||||
readability-identifier-naming.PrivateMemberSuffix: _
|
||||
readability-identifier-naming.ProtectedMemberCase: camelBack
|
||||
readability-identifier-naming.ProtectedMemberSuffix: _
|
||||
readability-identifier-naming.PublicMemberCase: camelBack
|
||||
readability-identifier-naming.PublicMemberSuffix: ""
|
||||
readability-identifier-naming.GlobalFunctionIgnoredRegexp: "^(to_string|hash_append|tuple_hash)$"
|
||||
|
||||
|
||||
4
.github/workflows/reusable-package.yml
vendored
4
.github/workflows/reusable-package.yml
vendored
@@ -58,6 +58,7 @@ jobs:
|
||||
|
||||
package:
|
||||
needs: [generate-matrix, generate-version]
|
||||
if: ${{ github.event.repository.visibility == 'public' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
|
||||
@@ -88,8 +89,7 @@ jobs:
|
||||
run: ./package/build_pkg.sh
|
||||
|
||||
- name: Upload package artifact
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
if: ${{ github.event.repository.visibility == 'public' }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}-pkg-${{ needs.generate-version.outputs.version }}
|
||||
path: |
|
||||
|
||||
9
BUILD.md
9
BUILD.md
@@ -427,16 +427,19 @@ install ccache --version 4.11.3 --allow-downgrade`.
|
||||
Single-config generators:
|
||||
|
||||
```
|
||||
cmake --build .
|
||||
cmake --build . --parallel N
|
||||
```
|
||||
|
||||
Multi-config generators:
|
||||
|
||||
```
|
||||
cmake --build . --config Release
|
||||
cmake --build . --config Debug
|
||||
cmake --build . --config Release --parallel N
|
||||
cmake --build . --config Debug --parallel N
|
||||
```
|
||||
|
||||
Replace the `--parallel` parameter N with the desired number of parallel jobs. A common starting point is half of the number of available CPU
|
||||
cores.
|
||||
|
||||
5. Test xrpld.
|
||||
|
||||
Single-config generators:
|
||||
|
||||
@@ -93,7 +93,6 @@ find_package(OpenSSL REQUIRED)
|
||||
find_package(secp256k1 REQUIRED)
|
||||
find_package(SOCI REQUIRED)
|
||||
find_package(SQLite3 REQUIRED)
|
||||
find_package(wasmi REQUIRED)
|
||||
find_package(xxHash REQUIRED)
|
||||
|
||||
target_link_libraries(
|
||||
|
||||
@@ -1466,10 +1466,7 @@ admin = 127.0.0.1
|
||||
protocol = http
|
||||
|
||||
[port_peer]
|
||||
# Many servers still use the legacy port of 51235, so for backward-compatibility
|
||||
# we maintain that port number here. However, for new servers we recommend
|
||||
# changing this to the default port of 2459.
|
||||
port = 51235
|
||||
port = 2459
|
||||
ip = 0.0.0.0
|
||||
# alternatively, to accept connections on IPv4 + IPv6, use:
|
||||
#ip = ::
|
||||
|
||||
@@ -67,7 +67,6 @@ target_link_libraries(
|
||||
Xrpl::opts
|
||||
Xrpl::syslibs
|
||||
secp256k1::secp256k1
|
||||
wasmi::wasmi
|
||||
xrpl.libpb
|
||||
xxHash::xxhash
|
||||
$<$<BOOL:${voidstar}>:antithesis-sdk-cpp>
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
"requires": [
|
||||
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1777558780.503",
|
||||
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1765850149.987",
|
||||
|
||||
"wasmi/1.0.9#1fecdab9b90c96698eb35ea99ca4f5cb%1772227278.324",
|
||||
"sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1776096494.149",
|
||||
"soci/4.0.3#fe32b9ad5eb47e79ab9e45a68f363945%1774450067.231",
|
||||
"snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1765850147.878",
|
||||
|
||||
@@ -34,7 +34,6 @@ class Xrpl(ConanFile):
|
||||
"openssl/3.6.2",
|
||||
"secp256k1/0.7.1",
|
||||
"soci/4.0.3",
|
||||
"wasmi/1.0.9",
|
||||
"zlib/1.3.2",
|
||||
]
|
||||
|
||||
@@ -215,7 +214,6 @@ class Xrpl(ConanFile):
|
||||
"soci::soci",
|
||||
"secp256k1::secp256k1",
|
||||
"sqlite3::sqlite",
|
||||
"wasmi::wasmi",
|
||||
"xxhash::xxhash",
|
||||
"zlib::zlib",
|
||||
]
|
||||
|
||||
@@ -7,8 +7,6 @@ ignorePaths:
|
||||
- cmake/**
|
||||
- LICENSE.md
|
||||
- .clang-tidy
|
||||
- src/test/app/wasm_fixtures/**/*.wat
|
||||
- src/test/app/wasm_fixtures/*.c
|
||||
language: en
|
||||
allowCompoundWords: true # TODO (#6334)
|
||||
ignoreRandomStrings: true
|
||||
@@ -67,7 +65,6 @@ words:
|
||||
- Btrfs
|
||||
- Buildx
|
||||
- canonicality
|
||||
- cdylib
|
||||
- changespq
|
||||
- checkme
|
||||
- choco
|
||||
@@ -270,7 +267,6 @@ words:
|
||||
- STATSDCOLLECTOR
|
||||
- stissue
|
||||
- stnum
|
||||
- stnumber
|
||||
- stobj
|
||||
- stobject
|
||||
- stpath
|
||||
|
||||
@@ -181,14 +181,14 @@ private:
|
||||
beast::insight::Collector::ptr const& collector)
|
||||
: hook(collector->makeHook(handler))
|
||||
, size(collector->makeGauge(prefix, "size"))
|
||||
, hit_rate(collector->makeGauge(prefix, "hit_rate"))
|
||||
, hitRate(collector->makeGauge(prefix, "hit_rate"))
|
||||
|
||||
{
|
||||
}
|
||||
|
||||
beast::insight::Hook hook;
|
||||
beast::insight::Gauge size;
|
||||
beast::insight::Gauge hit_rate;
|
||||
beast::insight::Gauge hitRate;
|
||||
|
||||
std::size_t hits{0};
|
||||
std::size_t misses{0};
|
||||
@@ -197,16 +197,16 @@ private:
|
||||
class KeyOnlyEntry
|
||||
{
|
||||
public:
|
||||
clock_type::time_point last_access;
|
||||
clock_type::time_point lastAccess;
|
||||
|
||||
explicit KeyOnlyEntry(clock_type::time_point const& lastAccess) : last_access(lastAccess)
|
||||
explicit KeyOnlyEntry(clock_type::time_point const& lastAccess) : lastAccess(lastAccess)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
touch(clock_type::time_point const& now)
|
||||
{
|
||||
last_access = now;
|
||||
lastAccess = now;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -214,10 +214,10 @@ private:
|
||||
{
|
||||
public:
|
||||
shared_weak_combo_pointer_type ptr;
|
||||
clock_type::time_point last_access;
|
||||
clock_type::time_point lastAccess;
|
||||
|
||||
ValueEntry(clock_type::time_point const& lastAccess, shared_pointer_type const& ptr)
|
||||
: ptr(ptr), last_access(lastAccess)
|
||||
: ptr(ptr), lastAccess(lastAccess)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -246,7 +246,7 @@ private:
|
||||
void
|
||||
touch(clock_type::time_point const& now)
|
||||
{
|
||||
last_access = now;
|
||||
lastAccess = now;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -286,13 +286,13 @@ private:
|
||||
std::string name_;
|
||||
|
||||
// Desired number of cache entries (0 = ignore)
|
||||
int const target_size_;
|
||||
int const targetSize_;
|
||||
|
||||
// Desired maximum cache age
|
||||
clock_type::duration const target_age_;
|
||||
clock_type::duration const targetAge_;
|
||||
|
||||
// Number of items cached
|
||||
int cache_count_{0};
|
||||
int cacheCount_{0};
|
||||
cache_type cache_; // Hold strong reference to recent objects
|
||||
std::uint64_t hits_{0};
|
||||
std::uint64_t misses_{0};
|
||||
|
||||
@@ -34,8 +34,8 @@ inline TaggedCache<
|
||||
, clock_(clock)
|
||||
, stats_(name, std::bind(&TaggedCache::collectMetrics, this), collector)
|
||||
, name_(name)
|
||||
, target_size_(size)
|
||||
, target_age_(expiration)
|
||||
, targetSize_(size)
|
||||
, targetAge_(expiration)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
|
||||
getCacheSize() const
|
||||
{
|
||||
std::scoped_lock const lock(mutex_);
|
||||
return cache_count_;
|
||||
return cacheCount_;
|
||||
}
|
||||
|
||||
template <
|
||||
@@ -139,7 +139,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
|
||||
{
|
||||
std::scoped_lock const lock(mutex_);
|
||||
cache_.clear();
|
||||
cache_count_ = 0;
|
||||
cacheCount_ = 0;
|
||||
}
|
||||
|
||||
template <
|
||||
@@ -157,7 +157,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
|
||||
{
|
||||
std::scoped_lock const lock(mutex_);
|
||||
cache_.clear();
|
||||
cache_count_ = 0;
|
||||
cacheCount_ = 0;
|
||||
hits_ = 0;
|
||||
misses_ = 0;
|
||||
}
|
||||
@@ -213,21 +213,21 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
|
||||
{
|
||||
std::scoped_lock const lock(mutex_);
|
||||
|
||||
if (target_size_ == 0 || (static_cast<int>(cache_.size()) <= target_size_))
|
||||
if (targetSize_ == 0 || (static_cast<int>(cache_.size()) <= targetSize_))
|
||||
{
|
||||
whenExpire = now - target_age_;
|
||||
whenExpire = now - targetAge_;
|
||||
}
|
||||
else
|
||||
{
|
||||
whenExpire = now - (target_age_ * target_size_ / cache_.size());
|
||||
whenExpire = now - (targetAge_ * targetSize_ / cache_.size());
|
||||
|
||||
clock_type::duration const minimumAge(std::chrono::seconds(1));
|
||||
if (whenExpire > (now - minimumAge))
|
||||
whenExpire = now - minimumAge;
|
||||
|
||||
JLOG(journal_.trace())
|
||||
<< name_ << " is growing fast " << cache_.size() << " of " << target_size_
|
||||
<< " aging at " << (now - whenExpire).count() << " of " << target_age_.count();
|
||||
<< name_ << " is growing fast " << cache_.size() << " of " << targetSize_
|
||||
<< " aging at " << (now - whenExpire).count() << " of " << targetAge_.count();
|
||||
}
|
||||
|
||||
std::vector<std::thread> workers;
|
||||
@@ -242,7 +242,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
|
||||
for (std::thread& worker : workers)
|
||||
worker.join();
|
||||
|
||||
cache_count_ -= allRemovals;
|
||||
cacheCount_ -= allRemovals;
|
||||
}
|
||||
// At this point allStuffToSweep will go out of scope outside the lock
|
||||
// and decrement the reference count on each strong pointer.
|
||||
@@ -280,7 +280,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
|
||||
|
||||
if (entry.isCached())
|
||||
{
|
||||
--cache_count_;
|
||||
--cacheCount_;
|
||||
entry.ptr.convertToWeak();
|
||||
ret = true;
|
||||
}
|
||||
@@ -317,7 +317,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
|
||||
std::piecewise_construct,
|
||||
std::forward_as_tuple(key),
|
||||
std::forward_as_tuple(clock_.now(), data));
|
||||
++cache_count_;
|
||||
++cacheCount_;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -366,12 +366,12 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
|
||||
data = cachedData;
|
||||
}
|
||||
|
||||
++cache_count_;
|
||||
++cacheCount_;
|
||||
return true;
|
||||
}
|
||||
|
||||
entry.ptr = data;
|
||||
++cache_count_;
|
||||
++cacheCount_;
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -477,7 +477,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
|
||||
auto [it, inserted] = cache_.emplace(
|
||||
std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(now));
|
||||
if (!inserted)
|
||||
it->second.last_access = now;
|
||||
it->second.lastAccess = now;
|
||||
return inserted;
|
||||
}
|
||||
|
||||
@@ -626,7 +626,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
|
||||
if (entry.isCached())
|
||||
{
|
||||
// independent of cache size, so not counted as a hit
|
||||
++cache_count_;
|
||||
++cacheCount_;
|
||||
entry.touch(clock_.now());
|
||||
return entry.ptr.getStrong();
|
||||
}
|
||||
@@ -658,7 +658,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
|
||||
if (total != 0)
|
||||
hitRate = (hits_ * 100) / total;
|
||||
}
|
||||
stats_.hit_rate.set(hitRate);
|
||||
stats_.hitRate.set(hitRate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -706,7 +706,7 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
|
||||
++cit;
|
||||
}
|
||||
}
|
||||
else if (cit->second.last_access <= whenExpire)
|
||||
else if (cit->second.lastAccess <= whenExpire)
|
||||
{
|
||||
// strong, expired
|
||||
++cacheRemovals;
|
||||
@@ -773,12 +773,12 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
|
||||
auto cit = partition.begin();
|
||||
while (cit != partition.end())
|
||||
{
|
||||
if (cit->second.last_access > now)
|
||||
if (cit->second.lastAccess > now)
|
||||
{
|
||||
cit->second.last_access = now;
|
||||
cit->second.lastAccess = now;
|
||||
++cit;
|
||||
}
|
||||
else if (cit->second.last_access <= whenExpire)
|
||||
else if (cit->second.lastAccess <= whenExpire)
|
||||
{
|
||||
cit = partition.erase(cit);
|
||||
}
|
||||
|
||||
@@ -24,20 +24,20 @@ namespace xrpl {
|
||||
template <class EF>
|
||||
class ScopeExit
|
||||
{
|
||||
EF exit_function_;
|
||||
bool execute_on_destruction_{true};
|
||||
EF exitFunction_;
|
||||
bool executeOnDestruction_{true};
|
||||
|
||||
public:
|
||||
~ScopeExit()
|
||||
{
|
||||
if (execute_on_destruction_)
|
||||
exit_function_();
|
||||
if (executeOnDestruction_)
|
||||
exitFunction_();
|
||||
}
|
||||
|
||||
ScopeExit(ScopeExit&& rhs) noexcept(
|
||||
std::is_nothrow_move_constructible_v<EF> || std::is_nothrow_copy_constructible_v<EF>)
|
||||
: exit_function_{std::forward<EF>(rhs.exit_function_)}
|
||||
, execute_on_destruction_{rhs.execute_on_destruction_}
|
||||
: exitFunction_{std::forward<EF>(rhs.exitFunction_)}
|
||||
, executeOnDestruction_{rhs.executeOnDestruction_}
|
||||
{
|
||||
rhs.release();
|
||||
}
|
||||
@@ -51,7 +51,7 @@ public:
|
||||
std::enable_if_t<
|
||||
!std::is_same_v<std::remove_cv_t<EFP>, ScopeExit> &&
|
||||
std::is_constructible_v<EF, EFP>>* = 0) noexcept
|
||||
: exit_function_{std::forward<EFP>(f)}
|
||||
: exitFunction_{std::forward<EFP>(f)}
|
||||
{
|
||||
static_assert(std::is_nothrow_constructible_v<EF, decltype(std::forward<EFP>(f))>);
|
||||
}
|
||||
@@ -59,7 +59,7 @@ public:
|
||||
void
|
||||
release() noexcept
|
||||
{
|
||||
execute_on_destruction_ = false;
|
||||
executeOnDestruction_ = false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -69,22 +69,22 @@ ScopeExit(EF) -> ScopeExit<EF>;
|
||||
template <class EF>
|
||||
class ScopeFail
|
||||
{
|
||||
EF exit_function_;
|
||||
bool execute_on_destruction_{true};
|
||||
int uncaught_on_creation_{std::uncaught_exceptions()};
|
||||
EF exitFunction_;
|
||||
bool executeOnDestruction_{true};
|
||||
int uncaughtOnCreation_{std::uncaught_exceptions()};
|
||||
|
||||
public:
|
||||
~ScopeFail()
|
||||
{
|
||||
if (execute_on_destruction_ && std::uncaught_exceptions() > uncaught_on_creation_)
|
||||
exit_function_();
|
||||
if (executeOnDestruction_ && std::uncaught_exceptions() > uncaughtOnCreation_)
|
||||
exitFunction_();
|
||||
}
|
||||
|
||||
ScopeFail(ScopeFail&& rhs) noexcept(
|
||||
std::is_nothrow_move_constructible_v<EF> || std::is_nothrow_copy_constructible_v<EF>)
|
||||
: exit_function_{std::forward<EF>(rhs.exit_function_)}
|
||||
, execute_on_destruction_{rhs.execute_on_destruction_}
|
||||
, uncaught_on_creation_{rhs.uncaught_on_creation_}
|
||||
: exitFunction_{std::forward<EF>(rhs.exitFunction_)}
|
||||
, executeOnDestruction_{rhs.executeOnDestruction_}
|
||||
, uncaughtOnCreation_{rhs.uncaughtOnCreation_}
|
||||
{
|
||||
rhs.release();
|
||||
}
|
||||
@@ -98,7 +98,7 @@ public:
|
||||
std::enable_if_t<
|
||||
!std::is_same_v<std::remove_cv_t<EFP>, ScopeFail> &&
|
||||
std::is_constructible_v<EF, EFP>>* = 0) noexcept
|
||||
: exit_function_{std::forward<EFP>(f)}
|
||||
: exitFunction_{std::forward<EFP>(f)}
|
||||
{
|
||||
static_assert(std::is_nothrow_constructible_v<EF, decltype(std::forward<EFP>(f))>);
|
||||
}
|
||||
@@ -106,7 +106,7 @@ public:
|
||||
void
|
||||
release() noexcept
|
||||
{
|
||||
execute_on_destruction_ = false;
|
||||
executeOnDestruction_ = false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -116,22 +116,22 @@ ScopeFail(EF) -> ScopeFail<EF>;
|
||||
template <class EF>
|
||||
class ScopeSuccess
|
||||
{
|
||||
EF exit_function_;
|
||||
bool execute_on_destruction_{true};
|
||||
int uncaught_on_creation_{std::uncaught_exceptions()};
|
||||
EF exitFunction_;
|
||||
bool executeOnDestruction_{true};
|
||||
int uncaughtOnCreation_{std::uncaught_exceptions()};
|
||||
|
||||
public:
|
||||
~ScopeSuccess() noexcept(noexcept(exit_function_()))
|
||||
~ScopeSuccess() noexcept(noexcept(exitFunction_()))
|
||||
{
|
||||
if (execute_on_destruction_ && std::uncaught_exceptions() <= uncaught_on_creation_)
|
||||
exit_function_();
|
||||
if (executeOnDestruction_ && std::uncaught_exceptions() <= uncaughtOnCreation_)
|
||||
exitFunction_();
|
||||
}
|
||||
|
||||
ScopeSuccess(ScopeSuccess&& rhs) noexcept(
|
||||
std::is_nothrow_move_constructible_v<EF> || std::is_nothrow_copy_constructible_v<EF>)
|
||||
: exit_function_{std::forward<EF>(rhs.exit_function_)}
|
||||
, execute_on_destruction_{rhs.execute_on_destruction_}
|
||||
, uncaught_on_creation_{rhs.uncaught_on_creation_}
|
||||
: exitFunction_{std::forward<EF>(rhs.exitFunction_)}
|
||||
, executeOnDestruction_{rhs.executeOnDestruction_}
|
||||
, uncaughtOnCreation_{rhs.uncaughtOnCreation_}
|
||||
{
|
||||
rhs.release();
|
||||
}
|
||||
@@ -146,14 +146,14 @@ public:
|
||||
!std::is_same_v<std::remove_cv_t<EFP>, ScopeSuccess> &&
|
||||
std::is_constructible_v<EF, EFP>>* =
|
||||
0) noexcept(std::is_nothrow_constructible_v<EF, EFP> || std::is_nothrow_constructible_v<EF, EFP&>)
|
||||
: exit_function_{std::forward<EFP>(f)}
|
||||
: exitFunction_{std::forward<EFP>(f)}
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
release() noexcept
|
||||
{
|
||||
execute_on_destruction_ = false;
|
||||
executeOnDestruction_ = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -77,8 +77,8 @@ private:
|
||||
|
||||
std::ostream& os_;
|
||||
Results results_;
|
||||
SuiteResults suite_results_;
|
||||
CaseResults case_results_;
|
||||
SuiteResults suiteResults_;
|
||||
CaseResults caseResults_;
|
||||
|
||||
public:
|
||||
Reporter(Reporter const&) = delete;
|
||||
@@ -196,22 +196,22 @@ template <class Unused>
|
||||
void
|
||||
Reporter<Unused>::onSuiteBegin(SuiteInfo const& info)
|
||||
{
|
||||
suite_results_ = SuiteResults{info.fullName()};
|
||||
suiteResults_ = SuiteResults{info.fullName()};
|
||||
}
|
||||
|
||||
template <class Unused>
|
||||
void
|
||||
Reporter<Unused>::onSuiteEnd()
|
||||
{
|
||||
results_.add(suite_results_);
|
||||
results_.add(suiteResults_);
|
||||
}
|
||||
|
||||
template <class Unused>
|
||||
void
|
||||
Reporter<Unused>::onCaseBegin(std::string const& name)
|
||||
{
|
||||
case_results_ = CaseResults(name);
|
||||
os_ << suite_results_.name << (case_results_.name.empty() ? "" : (" " + case_results_.name))
|
||||
caseResults_ = CaseResults(name);
|
||||
os_ << suiteResults_.name << (caseResults_.name.empty() ? "" : (" " + caseResults_.name))
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
@@ -219,23 +219,23 @@ template <class Unused>
|
||||
void
|
||||
Reporter<Unused>::onCaseEnd()
|
||||
{
|
||||
suite_results_.add(case_results_);
|
||||
suiteResults_.add(caseResults_);
|
||||
}
|
||||
|
||||
template <class Unused>
|
||||
void
|
||||
Reporter<Unused>::onPass()
|
||||
{
|
||||
++case_results_.total;
|
||||
++caseResults_.total;
|
||||
}
|
||||
|
||||
template <class Unused>
|
||||
void
|
||||
Reporter<Unused>::onFail(std::string const& reason)
|
||||
{
|
||||
++case_results_.failed;
|
||||
++case_results_.total;
|
||||
os_ << "#" << case_results_.total << " failed" << (reason.empty() ? "" : ": ") << reason
|
||||
++caseResults_.failed;
|
||||
++caseResults_.total;
|
||||
os_ << "#" << caseResults_.total << " failed" << (reason.empty() ? "" : ": ") << reason
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ inline bool
|
||||
JobQueue::Coro::post()
|
||||
{
|
||||
{
|
||||
std::scoped_lock const lk(mutex_run_);
|
||||
std::scoped_lock const lk(mutexRun_);
|
||||
running_ = true;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ JobQueue::Coro::post()
|
||||
}
|
||||
|
||||
// The coroutine will not run. Clean up running_.
|
||||
std::scoped_lock const lk(mutex_run_);
|
||||
std::scoped_lock const lk(mutexRun_);
|
||||
running_ = false;
|
||||
cv_.notify_all();
|
||||
return false;
|
||||
@@ -68,7 +68,7 @@ inline void
|
||||
JobQueue::Coro::resume()
|
||||
{
|
||||
{
|
||||
std::scoped_lock const lk(mutex_run_);
|
||||
std::scoped_lock const lk(mutexRun_);
|
||||
running_ = true;
|
||||
}
|
||||
{
|
||||
@@ -92,7 +92,7 @@ JobQueue::Coro::resume()
|
||||
}
|
||||
detail::getLocalValues().release();
|
||||
detail::getLocalValues().reset(saved);
|
||||
std::scoped_lock const lk(mutex_run_);
|
||||
std::scoped_lock const lk(mutexRun_);
|
||||
running_ = false;
|
||||
cv_.notify_all();
|
||||
}
|
||||
@@ -127,7 +127,7 @@ JobQueue::Coro::expectEarlyExit()
|
||||
inline void
|
||||
JobQueue::Coro::join()
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(mutex_run_);
|
||||
std::unique_lock<std::mutex> lk(mutexRun_);
|
||||
cv_.wait(lk, [this]() { return !running_; });
|
||||
}
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ private:
|
||||
std::function<void()> job_;
|
||||
std::shared_ptr<LoadEvent> loadEvent_;
|
||||
std::string name_;
|
||||
clock_type::time_point queue_time_;
|
||||
clock_type::time_point queueTime_;
|
||||
};
|
||||
|
||||
using JobCounter = ClosureCounter<void>;
|
||||
|
||||
@@ -52,7 +52,7 @@ public:
|
||||
std::string name_;
|
||||
bool running_{false};
|
||||
std::mutex mutex_;
|
||||
std::mutex mutex_run_;
|
||||
std::mutex mutexRun_;
|
||||
std::condition_variable cv_;
|
||||
boost::coroutines2::coroutine<void>::push_type* yield_{};
|
||||
boost::coroutines2::coroutine<void>::pull_type coro_;
|
||||
@@ -246,7 +246,7 @@ private:
|
||||
// Statistics tracking
|
||||
perf::PerfLog& perfLog_;
|
||||
beast::insight::Collector::ptr collector_;
|
||||
beast::insight::Gauge job_count_;
|
||||
beast::insight::Gauge jobCount_;
|
||||
beast::insight::Hook hook_;
|
||||
|
||||
std::condition_variable cv_;
|
||||
|
||||
@@ -161,7 +161,7 @@ public:
|
||||
* While the JSON spec doesn't explicitly disallow this, you should avoid
|
||||
* calling this method twice with the same tag for the same object.
|
||||
*
|
||||
* If CHECK_JSON_WRITER is defined, this function throws an exception if if
|
||||
* If CHECK_JSON_WRITER is defined, this function throws an exception if
|
||||
* the tag you use has already been used in this object.
|
||||
*/
|
||||
template <typename Type>
|
||||
|
||||
@@ -9,7 +9,7 @@ class BookDirs
|
||||
private:
|
||||
ReadView const* view_ = nullptr;
|
||||
uint256 const root_;
|
||||
uint256 const next_quality_;
|
||||
uint256 const nextQuality_;
|
||||
uint256 const key_;
|
||||
std::shared_ptr<SLE const> sle_ = nullptr;
|
||||
unsigned int entry_ = 0;
|
||||
@@ -67,15 +67,15 @@ private:
|
||||
friend class BookDirs;
|
||||
|
||||
const_iterator(ReadView const& view, uint256 const& root, uint256 const& dirKey)
|
||||
: view_(&view), root_(root), key_(dirKey), cur_key_(dirKey)
|
||||
: view_(&view), root_(root), key_(dirKey), curKey_(dirKey)
|
||||
{
|
||||
}
|
||||
|
||||
ReadView const* view_ = nullptr;
|
||||
uint256 root_;
|
||||
uint256 next_quality_;
|
||||
uint256 nextQuality_;
|
||||
uint256 key_;
|
||||
uint256 cur_key_;
|
||||
uint256 curKey_;
|
||||
std::shared_ptr<SLE const> sle_;
|
||||
unsigned int entry_ = 0;
|
||||
uint256 index_;
|
||||
|
||||
@@ -76,7 +76,7 @@ private:
|
||||
|
||||
// monotonic_resource_ must outlive `items_`. Make a pointer so it may be
|
||||
// easily moved.
|
||||
std::unique_ptr<boost::container::pmr::monotonic_buffer_resource> monotonic_resource_;
|
||||
std::unique_ptr<boost::container::pmr::monotonic_buffer_resource> monotonicResource_;
|
||||
txs_map txs_;
|
||||
Rules rules_;
|
||||
LedgerHeader header_;
|
||||
|
||||
@@ -57,7 +57,7 @@ isVaultPseudoAccountFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
MPTIssue const& mptShare,
|
||||
int depth);
|
||||
std::uint8_t depth);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isLPTokenFrozen(
|
||||
|
||||
@@ -22,14 +22,14 @@ public:
|
||||
static constexpr size_t kInitialBufferSize = kilobytes(256);
|
||||
|
||||
RawStateTable()
|
||||
: monotonic_resource_{std::make_unique<boost::container::pmr::monotonic_buffer_resource>(
|
||||
: monotonicResource_{std::make_unique<boost::container::pmr::monotonic_buffer_resource>(
|
||||
kInitialBufferSize)}
|
||||
, items_{monotonic_resource_.get()} {};
|
||||
, items_{monotonicResource_.get()} {};
|
||||
|
||||
RawStateTable(RawStateTable const& rhs)
|
||||
: monotonic_resource_{std::make_unique<boost::container::pmr::monotonic_buffer_resource>(
|
||||
: monotonicResource_{std::make_unique<boost::container::pmr::monotonic_buffer_resource>(
|
||||
kInitialBufferSize)}
|
||||
, items_{rhs.items_, monotonic_resource_.get()}
|
||||
, items_{rhs.items_, monotonicResource_.get()}
|
||||
, dropsDestroyed_{rhs.dropsDestroyed_} {};
|
||||
|
||||
RawStateTable(RawStateTable&&) = default;
|
||||
@@ -101,7 +101,7 @@ private:
|
||||
boost::container::pmr::polymorphic_allocator<std::pair<key_type const, SleAction>>>;
|
||||
// monotonic_resource_ must outlive `items_`. Make a pointer so it may be
|
||||
// easily moved.
|
||||
std::unique_ptr<boost::container::pmr::monotonic_buffer_resource> monotonic_resource_;
|
||||
std::unique_ptr<boost::container::pmr::monotonic_buffer_resource> monotonicResource_;
|
||||
items_t items_;
|
||||
|
||||
XRPAmount dropsDestroyed_{0};
|
||||
|
||||
@@ -4,8 +4,38 @@
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/st.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Broker cover preclaim precision guard (fixCleanup3_2_0).
|
||||
*
|
||||
* Prevents a "silent sub-ULP no-op" where a deposit, withdrawal, or clawback
|
||||
* amount is so small that it rounds to zero at `sfCoverAvailable`'s scale.
|
||||
* Without this guard, both the pseudo trust-line and `sfCoverAvailable` would
|
||||
* identically absorb the rounded zero, resulting in a successful transaction
|
||||
* (tesSUCCESS) where no funds actually moved.
|
||||
*
|
||||
* @param view Read view (rules used for amendment gating).
|
||||
* @param sleBroker The loan broker SLE (read-only).
|
||||
* @param vaultAsset The underlying vault asset (the broker's cover asset).
|
||||
* @param amount The effective subtraction/addition amount.
|
||||
* @param j Journal for logging.
|
||||
* @param logPrefix Transactor name for log diagnostics.
|
||||
*
|
||||
* @return `tecPRECISION_LOSS` if the request rounds to zero at cover scale.
|
||||
* `tesSUCCESS` if the amendment is disabled or the request is safely supra-ULP.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
canApplyToBrokerCover(
|
||||
ReadView const& view,
|
||||
SLE::const_ref sleBroker,
|
||||
Asset const& vaultAsset,
|
||||
STAmount const& amount,
|
||||
beast::Journal j,
|
||||
std::string_view logPrefix);
|
||||
|
||||
// Lending protocol has dependencies, so capture them here.
|
||||
bool
|
||||
checkLendingProtocolDependencies(Rules const& rules, STTx const& tx);
|
||||
|
||||
@@ -27,14 +27,18 @@ isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue);
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth = 0);
|
||||
isFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
MPTIssue const& mptIssue,
|
||||
std::uint8_t depth = 0);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isAnyFrozen(
|
||||
ReadView const& view,
|
||||
std::initializer_list<AccountID> const& accounts,
|
||||
MPTIssue const& mptIssue,
|
||||
int depth = 0);
|
||||
std::uint8_t depth = 0);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
@@ -88,7 +92,7 @@ requireAuth(
|
||||
MPTIssue const& mptIssue,
|
||||
AccountID const& account,
|
||||
AuthType authType = AuthType::Legacy,
|
||||
int depth = 0);
|
||||
std::uint8_t depth = 0);
|
||||
|
||||
/** Enforce account has MPToken to match its authorization.
|
||||
*
|
||||
@@ -104,22 +108,68 @@ enforceMPTokenAuthorization(
|
||||
XRPAmount const& priorBalance,
|
||||
beast::Journal j);
|
||||
|
||||
/** Check if the destination account is allowed
|
||||
* to receive MPT. Return tecNO_AUTH if it doesn't
|
||||
* and tesSUCCESS otherwise.
|
||||
/** Resolve the underlying asset of a vault share.
|
||||
*
|
||||
* Reads sfReferenceHolding from @p sleShareIssuance to determine which
|
||||
* asset the vault wraps. @p sleHolding must be the SLE that
|
||||
* sfReferenceHolding points to — either an ltMPTOKEN (returns its
|
||||
* MPTIssue) or an ltRIPPLE_STATE (returns its low/high Issue).
|
||||
*
|
||||
* @pre Both SLEs must exist and @p sleHolding must be of type ltMPTOKEN
|
||||
* or ltRIPPLE_STATE. Passing any other type is undefined behaviour.
|
||||
* @param sleShareIssuance MPTokenIssuance SLE for the vault share token.
|
||||
* @param sleHolding SLE referenced by sfReferenceHolding.
|
||||
* @return The underlying Asset (MPTIssue or Issue).
|
||||
*/
|
||||
[[nodiscard]] Asset
|
||||
assetOfHolding(SLE const& sleShareIssuance, SLE const& sleHolding);
|
||||
|
||||
/** Check whether @p to may receive the given MPT from @p from.
|
||||
*
|
||||
* The check passes when any of the following is true:
|
||||
* - @p waive is WaiveMPTCanTransfer::Yes (recovery-path exemption), or
|
||||
* - @p from or @p to is the issuer, or
|
||||
* - lsfMPTCanTransfer is set on the MPTokenIssuance.
|
||||
*
|
||||
* For vault shares (MPTokenIssuances that carry sfReferenceHolding) the
|
||||
* check recurses into the underlying asset's transferability. This
|
||||
* recursion is defensive; vault-of-vault-shares is rejected at vault
|
||||
* creation, so in practice depth never exceeds 1.
|
||||
*
|
||||
* @param view Ledger state to read from.
|
||||
* @param mptIssue The MPT issuance being transferred.
|
||||
* @param from Sending account.
|
||||
* @param to Receiving account.
|
||||
* @param waive WaiveMPTCanTransfer::Yes skips the lsfMPTCanTransfer
|
||||
* check. Use for recovery paths (e.g. unwinding SAV or
|
||||
* Lending Protocol positions after an issuer revokes
|
||||
* transferability).
|
||||
* @param depth Recursion depth; bounded at kMaxAssetCheckDepth.
|
||||
* @return tesSUCCESS if the transfer is allowed, tecNO_AUTH otherwise.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
canTransfer(
|
||||
ReadView const& view,
|
||||
MPTIssue const& mptIssue,
|
||||
AccountID const& from,
|
||||
AccountID const& to);
|
||||
AccountID const& to,
|
||||
WaiveMPTCanTransfer waive = WaiveMPTCanTransfer::No,
|
||||
std::uint8_t depth = 0);
|
||||
|
||||
/** Check if Asset can be traded on DEX. return tecNO_PERMISSION
|
||||
* if it doesn't and tesSUCCESS otherwise.
|
||||
/** Check whether @p asset may be traded on the DEX.
|
||||
*
|
||||
* For IOU assets the check delegates to the existing offer/AMM freeze
|
||||
* logic. For MPT assets it checks lsfMPTCanTrade on the MPTokenIssuance.
|
||||
* Vault shares recurse into the underlying asset's tradability via
|
||||
* sfReferenceHolding; depth is bounded at kMaxAssetCheckDepth.
|
||||
*
|
||||
* @param view Ledger state to read from.
|
||||
* @param asset The asset to check.
|
||||
* @param depth Recursion depth; bounded at kMaxAssetCheckDepth.
|
||||
* @return tesSUCCESS if trading is allowed, tecNO_PERMISSION otherwise.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
canTrade(ReadView const& view, Asset const& asset);
|
||||
canTrade(ReadView const& view, Asset const& asset, std::uint8_t depth = 0);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
|
||||
@@ -93,7 +93,7 @@ isFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
|
||||
// Overload with depth parameter for uniformity with MPTIssue version.
|
||||
// The depth parameter is ignored for IOUs since they don't have vault recursion.
|
||||
[[nodiscard]] inline bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, Issue const& issue, int /*depth*/)
|
||||
isFrozen(ReadView const& view, AccountID const& account, Issue const& issue, std::uint8_t /*depth*/)
|
||||
{
|
||||
return isFrozen(view, account, issue);
|
||||
}
|
||||
@@ -110,7 +110,7 @@ isDeepFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Issue const& issue,
|
||||
int = 0 /*ignored*/)
|
||||
std::uint8_t = 0 /*ignored*/)
|
||||
{
|
||||
return isDeepFrozen(view, account, issue.currency, issue.account);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,15 @@ enum class WaiveTransferFee : bool { No = false, Yes };
|
||||
/** Controls whether accountSend is allowed to overflow OutstandingAmount **/
|
||||
enum class AllowMPTOverflow : bool { No = false, Yes };
|
||||
|
||||
/** Controls whether canTransfer enforces lsfMPTCanTransfer on MPTs.
|
||||
*
|
||||
* Default is No (enforce). Use Yes at call sites that must remain available
|
||||
* even when an MPT issuer has cleared lsfMPTCanTransfer - for example,
|
||||
* unwinding existing positions in SAV or the Lending Protocol. Has no
|
||||
* effect on the IOU branch of canTransfer.
|
||||
*/
|
||||
enum class WaiveMPTCanTransfer : bool { No = false, Yes };
|
||||
|
||||
/* Check if MPToken (for MPT) or trust line (for IOU) exists:
|
||||
* - StrongAuth - before checking if authorization is required
|
||||
* - WeakAuth
|
||||
@@ -63,7 +72,11 @@ isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const&
|
||||
* purely defensive, as we currently do not allow such vaults to be created.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth = 0);
|
||||
isFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Asset const& asset,
|
||||
std::uint8_t depth = 0);
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkFrozen(ReadView const& view, AccountID const& account, Issue const& issue);
|
||||
@@ -85,14 +98,14 @@ isAnyFrozen(
|
||||
ReadView const& view,
|
||||
std::initializer_list<AccountID> const& accounts,
|
||||
Asset const& asset,
|
||||
int depth = 0);
|
||||
std::uint8_t depth = 0);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isDeepFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
MPTIssue const& mptIssue,
|
||||
int depth = 0);
|
||||
std::uint8_t depth = 0);
|
||||
|
||||
/**
|
||||
* isFrozen check is recursive for MPT shares in a vault, descending to
|
||||
@@ -100,7 +113,11 @@ isDeepFrozen(
|
||||
* purely defensive, as we currently do not allow such vaults to be created.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth = 0);
|
||||
isDeepFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Asset const& asset,
|
||||
std::uint8_t depth = 0);
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
|
||||
@@ -234,7 +251,13 @@ requireAuth(
|
||||
AuthType authType = AuthType::Legacy);
|
||||
|
||||
[[nodiscard]] TER
|
||||
canTransfer(ReadView const& view, Asset const& asset, AccountID const& from, AccountID const& to);
|
||||
canTransfer(
|
||||
ReadView const& view,
|
||||
Asset const& asset,
|
||||
AccountID const& from,
|
||||
AccountID const& to,
|
||||
WaiveMPTCanTransfer waive = WaiveMPTCanTransfer::No,
|
||||
std::uint8_t depth = 0);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
|
||||
@@ -21,13 +21,13 @@ public:
|
||||
bool sslVerify,
|
||||
beast::Journal j,
|
||||
boost::asio::ssl::context_base::method method = boost::asio::ssl::context::sslv23)
|
||||
: ssl_context_{method}, j_(j), verify_{sslVerify}
|
||||
: sslContext_{method}, j_(j), verify_{sslVerify}
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
|
||||
if (sslVerifyFile.empty())
|
||||
{
|
||||
registerSSLCerts(ssl_context_, ec, j_);
|
||||
registerSSLCerts(sslContext_, ec, j_);
|
||||
|
||||
if (ec && sslVerifyDir.empty())
|
||||
{
|
||||
@@ -37,12 +37,12 @@ public:
|
||||
}
|
||||
else
|
||||
{
|
||||
ssl_context_.load_verify_file(sslVerifyFile);
|
||||
sslContext_.load_verify_file(sslVerifyFile);
|
||||
}
|
||||
|
||||
if (!sslVerifyDir.empty())
|
||||
{
|
||||
ssl_context_.add_verify_path(sslVerifyDir, ec);
|
||||
sslContext_.add_verify_path(sslVerifyDir, ec);
|
||||
|
||||
if (ec)
|
||||
{
|
||||
@@ -55,7 +55,7 @@ public:
|
||||
boost::asio::ssl::context&
|
||||
context()
|
||||
{
|
||||
return ssl_context_;
|
||||
return sslContext_;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
@@ -153,7 +153,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
boost::asio::ssl::context ssl_context_;
|
||||
boost::asio::ssl::context sslContext_;
|
||||
beast::Journal const j_;
|
||||
bool const verify_;
|
||||
};
|
||||
|
||||
@@ -140,8 +140,8 @@ private:
|
||||
using issue_hasher = std::hash<xrpl::Issue>;
|
||||
using mptissue_hasher = std::hash<xrpl::MPTIssue>;
|
||||
|
||||
issue_hasher m_issue_hasher_;
|
||||
mptissue_hasher m_mptissue_hasher_;
|
||||
issue_hasher mIssueHasher_;
|
||||
mptissue_hasher mMptissueHasher_;
|
||||
|
||||
public:
|
||||
explicit hash() = default;
|
||||
@@ -151,11 +151,11 @@ public:
|
||||
{
|
||||
return asset.visit(
|
||||
[&](xrpl::Issue const& issue) {
|
||||
value_type const result(m_issue_hasher_(issue));
|
||||
value_type const result(mIssueHasher_(issue));
|
||||
return result;
|
||||
},
|
||||
[&](xrpl::MPTIssue const& issue) {
|
||||
value_type const result(m_mptissue_hasher_(issue));
|
||||
value_type const result(mMptissueHasher_(issue));
|
||||
return result;
|
||||
});
|
||||
}
|
||||
@@ -170,8 +170,8 @@ private:
|
||||
using asset_hasher = std::hash<xrpl::Asset>;
|
||||
using uint256_hasher = xrpl::uint256::hasher;
|
||||
|
||||
asset_hasher issue_hasher_;
|
||||
uint256_hasher uint256_hasher_;
|
||||
asset_hasher issueHasher_;
|
||||
uint256_hasher uint256Hasher_;
|
||||
|
||||
public:
|
||||
hash() = default;
|
||||
@@ -182,11 +182,11 @@ public:
|
||||
value_type
|
||||
operator()(argument_type const& value) const
|
||||
{
|
||||
value_type result(issue_hasher_(value.in));
|
||||
boost::hash_combine(result, issue_hasher_(value.out));
|
||||
value_type result(issueHasher_(value.in));
|
||||
boost::hash_combine(result, issueHasher_(value.out));
|
||||
|
||||
if (value.domain)
|
||||
boost::hash_combine(result, uint256_hasher_(*value.domain));
|
||||
boost::hash_combine(result, uint256Hasher_(*value.domain));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -172,24 +172,24 @@ struct ErrorInfo
|
||||
{
|
||||
// Default ctor needed to produce an empty std::array during constexpr eval.
|
||||
constexpr ErrorInfo()
|
||||
: code(RpcUnknown), token("unknown"), message("An unknown error code."), http_status(200)
|
||||
: code(RpcUnknown), token("unknown"), message("An unknown error code."), httpStatus(200)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr ErrorInfo(ErrorCodeI code, char const* token, char const* message)
|
||||
: code(code), token(token), message(message), http_status(200)
|
||||
: code(code), token(token), message(message), httpStatus(200)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr ErrorInfo(ErrorCodeI code, char const* token, char const* message, int httpStatus)
|
||||
: code(code), token(token), message(message), http_status(httpStatus)
|
||||
: code(code), token(token), message(message), httpStatus(httpStatus)
|
||||
{
|
||||
}
|
||||
|
||||
ErrorCodeI code;
|
||||
json::StaticString token;
|
||||
json::StaticString message;
|
||||
int http_status;
|
||||
int httpStatus;
|
||||
};
|
||||
|
||||
/** Returns an ErrorInfo that reflects the error code. */
|
||||
|
||||
@@ -251,9 +251,6 @@ constexpr std::uint8_t kVaultMaximumIouScale = 18;
|
||||
* another vault; counted from 0 */
|
||||
constexpr std::uint8_t kMaxAssetCheckDepth = 5;
|
||||
|
||||
/** Maximum length of a Data field in Escrow object that can be updated by WASM code. */
|
||||
constexpr std::size_t kMaxWasmDataLength = 1 * 1024; // 1KB
|
||||
|
||||
/** A ledger index. */
|
||||
using LedgerIndex = std::uint32_t;
|
||||
|
||||
|
||||
@@ -365,8 +365,8 @@ using SF_XCHAIN_BRIDGE = TypedField<STXChainBridge>;
|
||||
#define UNTYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) extern SField const sfName;
|
||||
#define TYPED_SFIELD(sfName, stiSuffix, fieldValue, ...) extern SF_##stiSuffix const sfName;
|
||||
|
||||
extern SField const kSfInvalid;
|
||||
extern SField const kSfGeneric;
|
||||
extern SField const sfInvalid; // NOLINT(readability-identifier-naming)
|
||||
extern SField const sfGeneric; // NOLINT(readability-identifier-naming)
|
||||
|
||||
#include <xrpl/protocol/detail/sfields.macro>
|
||||
|
||||
|
||||
@@ -184,6 +184,24 @@ public:
|
||||
[[nodiscard]] STAmount const&
|
||||
value() const noexcept;
|
||||
|
||||
/**
|
||||
* Checks if this amount evaluates to zero when constrained to a specific
|
||||
* accounting scale.
|
||||
*
|
||||
* For XRP and MPT `roundToScale` is a no-op, returns true only when the amount itself is zero.
|
||||
* The `scale` argument is ignored in that case.
|
||||
* For IOU, the amount is rounded to the given scale using Number::RoundingMode::ToNearest mode
|
||||
* and the result is checked for zero; if `scale <= exponent()`, `roundToScale` short-circuits
|
||||
* and returns the value unchanged, so this returns false for any non-zero amount.
|
||||
*
|
||||
* @param scale The target accounting scale to evaluate against.
|
||||
* @return `true` if this amount rounds to zero at the given scale, `false` otherwise.
|
||||
*
|
||||
* @see roundToScale
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isZeroAtScale(int scale) const;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
//
|
||||
// Operators
|
||||
|
||||
@@ -24,7 +24,7 @@ public:
|
||||
STBlob(SField const& f, void const* data, std::size_t size);
|
||||
STBlob(SField const& f, Buffer&& b);
|
||||
STBlob(SField const& n);
|
||||
STBlob(SerialIter&, SField const& name = kSfGeneric);
|
||||
STBlob(SerialIter&, SField const& name = sfGeneric);
|
||||
|
||||
[[nodiscard]] std::size_t
|
||||
size() const;
|
||||
|
||||
@@ -21,8 +21,8 @@ class STPathElement final : public CountedObject<STPathElement>
|
||||
PathAsset assetID_;
|
||||
AccountID issuerID_;
|
||||
|
||||
bool is_offer_;
|
||||
std::size_t hash_value_;
|
||||
bool isOffer_;
|
||||
std::size_t hashValue_;
|
||||
|
||||
public:
|
||||
// Bitwise values (typeCurrency | typeMPT)
|
||||
@@ -235,9 +235,9 @@ private:
|
||||
|
||||
// ------------ STPathElement ------------
|
||||
|
||||
inline STPathElement::STPathElement() : type_(TypeNone), is_offer_(true)
|
||||
inline STPathElement::STPathElement() : type_(TypeNone), isOffer_(true)
|
||||
{
|
||||
hash_value_ = getHash(*this);
|
||||
hashValue_ = getHash(*this);
|
||||
}
|
||||
|
||||
inline STPathElement::STPathElement(
|
||||
@@ -248,11 +248,11 @@ inline STPathElement::STPathElement(
|
||||
{
|
||||
if (!account)
|
||||
{
|
||||
is_offer_ = true;
|
||||
isOffer_ = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
is_offer_ = false;
|
||||
isOffer_ = false;
|
||||
accountID_ = *account;
|
||||
type_ |= TypeAccount;
|
||||
XRPL_ASSERT(
|
||||
@@ -272,7 +272,7 @@ inline STPathElement::STPathElement(
|
||||
XRPL_ASSERT(issuerID_ != noAccount(), "xrpl::STPathElement::STPathElement : issuer is set");
|
||||
}
|
||||
|
||||
hash_value_ = getHash(*this);
|
||||
hashValue_ = getHash(*this);
|
||||
}
|
||||
|
||||
inline STPathElement::STPathElement(
|
||||
@@ -284,9 +284,9 @@ inline STPathElement::STPathElement(
|
||||
, accountID_(account)
|
||||
, assetID_(asset)
|
||||
, issuerID_(issuer)
|
||||
, is_offer_(isXRP(accountID_))
|
||||
, isOffer_(isXRP(accountID_))
|
||||
{
|
||||
if (!is_offer_)
|
||||
if (!isOffer_)
|
||||
type_ |= TypeAccount;
|
||||
|
||||
if (forceAsset || !isXRP(assetID_))
|
||||
@@ -295,7 +295,7 @@ inline STPathElement::STPathElement(
|
||||
if (!isXRP(issuer))
|
||||
type_ |= TypeIssuer;
|
||||
|
||||
hash_value_ = getHash(*this);
|
||||
hashValue_ = getHash(*this);
|
||||
}
|
||||
|
||||
inline STPathElement::STPathElement(
|
||||
@@ -307,12 +307,12 @@ inline STPathElement::STPathElement(
|
||||
, accountID_(account)
|
||||
, assetID_(asset)
|
||||
, issuerID_(issuer)
|
||||
, is_offer_(isXRP(accountID_))
|
||||
, isOffer_(isXRP(accountID_))
|
||||
{
|
||||
assetID_.visit(
|
||||
[&](Currency const&) { type_ = type_ & (~Type::TypeMpt); },
|
||||
[&](MPTID const&) { type_ = type_ & (~Type::TypeCurrency); });
|
||||
hash_value_ = getHash(*this);
|
||||
hashValue_ = getHash(*this);
|
||||
}
|
||||
|
||||
inline auto
|
||||
@@ -324,7 +324,7 @@ STPathElement::getNodeType() const
|
||||
inline bool
|
||||
STPathElement::isOffer() const
|
||||
{
|
||||
return is_offer_;
|
||||
return isOffer_;
|
||||
}
|
||||
|
||||
inline bool
|
||||
@@ -404,7 +404,7 @@ STPathElement::getIssuerID() const
|
||||
inline bool
|
||||
STPathElement::operator==(STPathElement const& t) const
|
||||
{
|
||||
return (type_ & TypeAccount) == (t.type_ & TypeAccount) && hash_value_ == t.hash_value_ &&
|
||||
return (type_ & TypeAccount) == (t.type_ & TypeAccount) && hashValue_ == t.hashValue_ &&
|
||||
accountID_ == t.accountID_ && assetID_ == t.assetID_ && issuerID_ == t.issuerID_;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ enum class TxnSql : char {
|
||||
class STTx final : public STObject, public CountedObject<STTx>
|
||||
{
|
||||
uint256 tid_;
|
||||
TxType tx_type_;
|
||||
TxType txType_;
|
||||
|
||||
public:
|
||||
static constexpr std::size_t kMinMultiSigners = 1;
|
||||
@@ -187,7 +187,7 @@ inline STTx::STTx(SerialIter&& sit) // NOLINT(cppcoreguidelines-rvalue-referenc
|
||||
inline TxType
|
||||
STTx::getTxnType() const
|
||||
{
|
||||
return tx_type_;
|
||||
return txType_;
|
||||
}
|
||||
|
||||
inline Blob
|
||||
|
||||
@@ -127,9 +127,7 @@ enum TEMcodes : TERUnderlyingType {
|
||||
temARRAY_TOO_LARGE,
|
||||
temBAD_TRANSFER_FEE,
|
||||
temINVALID_INNER_BATCH,
|
||||
|
||||
temBAD_MPT,
|
||||
temBAD_WASM,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -400,6 +400,7 @@ LEDGER_ENTRY(ltMPTOKEN_ISSUANCE, 0x007e, MPTokenIssuance, mpt_issuance, ({
|
||||
{sfPreviousTxnLgrSeq, SoeRequired},
|
||||
{sfDomainID, SoeOptional},
|
||||
{sfMutableFlags, SoeDefault},
|
||||
{sfReferenceHolding, SoeOptional},
|
||||
}))
|
||||
|
||||
/** A ledger object which tracks MPToken
|
||||
@@ -591,7 +592,7 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({
|
||||
// LoanBroker.ManagementFeeRate
|
||||
// The unrounded true total fee still owed to the broker.
|
||||
//
|
||||
// Note the the "True" values may differ significantly from the tracked
|
||||
// Note the "True" values may differ significantly from the tracked
|
||||
// rounded values.
|
||||
{sfPaymentRemaining, SoeDefault},
|
||||
{sfPeriodicPayment, SoeRequired},
|
||||
|
||||
@@ -205,6 +205,7 @@ TYPED_SFIELD(sfParentBatchID, UINT256, 36)
|
||||
TYPED_SFIELD(sfLoanBrokerID, UINT256, 37,
|
||||
SField::kSmdPseudoAccount | SField::kSmdDefault)
|
||||
TYPED_SFIELD(sfLoanID, UINT256, 38)
|
||||
TYPED_SFIELD(sfReferenceHolding, UINT256, 39)
|
||||
|
||||
// number (common)
|
||||
TYPED_SFIELD(sfNumber, NUMBER, 1)
|
||||
|
||||
@@ -688,6 +688,7 @@ TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix,
|
||||
({
|
||||
{sfLedgerFixType, SoeRequired},
|
||||
{sfOwner, SoeOptional},
|
||||
{sfBookDirectory, SoeOptional},
|
||||
}))
|
||||
|
||||
/** This transaction type creates a MPTokensIssuance instance */
|
||||
|
||||
@@ -278,6 +278,30 @@ public:
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfMutableFlags);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfReferenceHolding (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT256::type::value_type>
|
||||
getReferenceHolding() const
|
||||
{
|
||||
if (hasReferenceHolding())
|
||||
return this->sle_->at(sfReferenceHolding);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfReferenceHolding is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasReferenceHolding() const
|
||||
{
|
||||
return this->sle_->isFieldPresent(sfReferenceHolding);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -469,6 +493,17 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfReferenceHolding (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
MPTokenIssuanceBuilder&
|
||||
setReferenceHolding(std::decay_t<typename SF_UINT256::type::value_type> const& value)
|
||||
{
|
||||
object_[sfReferenceHolding] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the completed MPTokenIssuance wrapper.
|
||||
* @param index The ledger entry index.
|
||||
|
||||
@@ -83,6 +83,32 @@ public:
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfOwner);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get sfBookDirectory (SoeOptional)
|
||||
* @return The field value, or std::nullopt if not present.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
protocol_autogen::Optional<SF_UINT256::type::value_type>
|
||||
getBookDirectory() const
|
||||
{
|
||||
if (hasBookDirectory())
|
||||
{
|
||||
return this->tx_->at(sfBookDirectory);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sfBookDirectory is present.
|
||||
* @return True if the field is present, false otherwise.
|
||||
*/
|
||||
[[nodiscard]]
|
||||
bool
|
||||
hasBookDirectory() const
|
||||
{
|
||||
return this->tx_->isFieldPresent(sfBookDirectory);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -149,6 +175,17 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set sfBookDirectory (SoeOptional)
|
||||
* @return Reference to this builder for method chaining.
|
||||
*/
|
||||
LedgerStateFixBuilder&
|
||||
setBookDirectory(std::decay_t<typename SF_UINT256::type::value_type> const& value)
|
||||
{
|
||||
object_[sfBookDirectory] = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Build and return the LedgerStateFix wrapper.
|
||||
* @param publicKey The public key for signing.
|
||||
|
||||
@@ -21,7 +21,7 @@ struct Entry : public beast::List<Entry>::Node
|
||||
@param now Construction time of Entry.
|
||||
*/
|
||||
explicit Entry(clock_type::time_point const now)
|
||||
: refcount(0), local_balance(now), remote_balance(0)
|
||||
: refcount(0), localBalance(now), remoteBalance(0)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ struct Entry : public beast::List<Entry>::Node
|
||||
int
|
||||
balance(clock_type::time_point const now)
|
||||
{
|
||||
return local_balance.value(now) + remote_balance;
|
||||
return localBalance.value(now) + remoteBalance;
|
||||
}
|
||||
|
||||
// Add a charge and return normalized balance
|
||||
@@ -54,7 +54,7 @@ struct Entry : public beast::List<Entry>::Node
|
||||
int
|
||||
add(int charge, clock_type::time_point const now)
|
||||
{
|
||||
return local_balance.add(charge, now) + remote_balance;
|
||||
return localBalance.add(charge, now) + remoteBalance;
|
||||
}
|
||||
|
||||
// The public key of the peer
|
||||
@@ -67,10 +67,10 @@ struct Entry : public beast::List<Entry>::Node
|
||||
int refcount;
|
||||
|
||||
// Exponentially decaying balance of resource consumption
|
||||
DecayingSample<kDecayWindowSeconds, clock_type> local_balance;
|
||||
DecayingSample<kDecayWindowSeconds, clock_type> localBalance;
|
||||
|
||||
// Normalized balance contribution from imports
|
||||
int remote_balance;
|
||||
int remoteBalance;
|
||||
|
||||
// Time of the last warning
|
||||
clock_type::time_point lastWarningTime;
|
||||
|
||||
@@ -25,11 +25,11 @@ struct Key
|
||||
std::size_t
|
||||
operator()(Key const& v) const
|
||||
{
|
||||
return addr_hash_(v.address);
|
||||
return addrHash_(v.address);
|
||||
}
|
||||
|
||||
private:
|
||||
beast::Uhash<> addr_hash_;
|
||||
beast::Uhash<> addrHash_;
|
||||
};
|
||||
|
||||
struct KeyEqual
|
||||
|
||||
@@ -194,34 +194,34 @@ public:
|
||||
|
||||
for (auto& inboundEntry : inbound_)
|
||||
{
|
||||
int const localBalance = inboundEntry.local_balance.value(now);
|
||||
if ((localBalance + inboundEntry.remote_balance) >= threshold)
|
||||
int const localBalance = inboundEntry.localBalance.value(now);
|
||||
if ((localBalance + inboundEntry.remoteBalance) >= threshold)
|
||||
{
|
||||
json::Value& entry = (ret[inboundEntry.toString()] = json::ValueType::Object);
|
||||
entry[jss::local] = localBalance;
|
||||
entry[jss::remote] = inboundEntry.remote_balance;
|
||||
entry[jss::remote] = inboundEntry.remoteBalance;
|
||||
entry[jss::type] = "inbound";
|
||||
}
|
||||
}
|
||||
for (auto& outboundEntry : outbound_)
|
||||
{
|
||||
int const localBalance = outboundEntry.local_balance.value(now);
|
||||
if ((localBalance + outboundEntry.remote_balance) >= threshold)
|
||||
int const localBalance = outboundEntry.localBalance.value(now);
|
||||
if ((localBalance + outboundEntry.remoteBalance) >= threshold)
|
||||
{
|
||||
json::Value& entry = (ret[outboundEntry.toString()] = json::ValueType::Object);
|
||||
entry[jss::local] = localBalance;
|
||||
entry[jss::remote] = outboundEntry.remote_balance;
|
||||
entry[jss::remote] = outboundEntry.remoteBalance;
|
||||
entry[jss::type] = "outbound";
|
||||
}
|
||||
}
|
||||
for (auto& adminEntry : admin_)
|
||||
{
|
||||
int const localBalance = adminEntry.local_balance.value(now);
|
||||
if ((localBalance + adminEntry.remote_balance) >= threshold)
|
||||
int const localBalance = adminEntry.localBalance.value(now);
|
||||
if ((localBalance + adminEntry.remoteBalance) >= threshold)
|
||||
{
|
||||
json::Value& entry = (ret[adminEntry.toString()] = json::ValueType::Object);
|
||||
entry[jss::local] = localBalance;
|
||||
entry[jss::remote] = adminEntry.remote_balance;
|
||||
entry[jss::remote] = adminEntry.remoteBalance;
|
||||
entry[jss::type] = "admin";
|
||||
}
|
||||
}
|
||||
@@ -242,7 +242,7 @@ public:
|
||||
for (auto& inboundEntry : inbound_)
|
||||
{
|
||||
Gossip::Item item;
|
||||
item.balance = inboundEntry.local_balance.value(now);
|
||||
item.balance = inboundEntry.localBalance.value(now);
|
||||
if (item.balance >= kMinimumGossipBalance)
|
||||
{
|
||||
item.address = inboundEntry.key->address;
|
||||
@@ -278,7 +278,7 @@ public:
|
||||
Import::Item item;
|
||||
item.balance = gossipItem.balance;
|
||||
item.consumer = newInboundEndpoint(gossipItem.address);
|
||||
item.consumer.entry().remote_balance += item.balance;
|
||||
item.consumer.entry().remoteBalance += item.balance;
|
||||
next.items.push_back(item);
|
||||
}
|
||||
}
|
||||
@@ -295,14 +295,14 @@ public:
|
||||
Import::Item item;
|
||||
item.balance = gossipItem.balance;
|
||||
item.consumer = newInboundEndpoint(gossipItem.address);
|
||||
item.consumer.entry().remote_balance += item.balance;
|
||||
item.consumer.entry().remoteBalance += item.balance;
|
||||
next.items.push_back(item);
|
||||
}
|
||||
|
||||
Import& prev(resultIt->second);
|
||||
for (auto& item : prev.items)
|
||||
{
|
||||
item.consumer.entry().remote_balance -= item.balance;
|
||||
item.consumer.entry().remoteBalance -= item.balance;
|
||||
}
|
||||
|
||||
std::swap(next, prev);
|
||||
@@ -345,7 +345,7 @@ public:
|
||||
for (auto itemIter(import.items.begin()); itemIter != import.items.end();
|
||||
++itemIter)
|
||||
{
|
||||
itemIter->consumer.entry().remote_balance -= itemIter->balance;
|
||||
itemIter->consumer.entry().remoteBalance -= itemIter->balance;
|
||||
}
|
||||
|
||||
iter = importTable_.erase(iter);
|
||||
@@ -520,8 +520,8 @@ public:
|
||||
item["count"] = entry.refcount;
|
||||
item["name"] = entry.toString();
|
||||
item["balance"] = entry.balance(now);
|
||||
if (entry.remote_balance != 0)
|
||||
item["remote_balance"] = entry.remote_balance;
|
||||
if (entry.remoteBalance != 0)
|
||||
item["remote_balance"] = entry.remoteBalance;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ struct Handoff
|
||||
bool moved = false;
|
||||
|
||||
// If response is set, this determines the keep alive
|
||||
bool keep_alive = false;
|
||||
bool keepAlive = false;
|
||||
|
||||
// When set, this will be sent back
|
||||
std::shared_ptr<Writer> response;
|
||||
|
||||
@@ -30,19 +30,19 @@ struct Port
|
||||
boost::asio::ip::address ip;
|
||||
std::uint16_t port = 0;
|
||||
std::set<std::string, boost::beast::iless> protocol;
|
||||
std::vector<boost::asio::ip::network_v4> admin_nets_v4;
|
||||
std::vector<boost::asio::ip::network_v6> admin_nets_v6;
|
||||
std::vector<boost::asio::ip::network_v4> secure_gateway_nets_v4;
|
||||
std::vector<boost::asio::ip::network_v6> secure_gateway_nets_v6;
|
||||
std::vector<boost::asio::ip::network_v4> adminNetsV4;
|
||||
std::vector<boost::asio::ip::network_v6> adminNetsV6;
|
||||
std::vector<boost::asio::ip::network_v4> secureGatewayNetsV4;
|
||||
std::vector<boost::asio::ip::network_v6> secureGatewayNetsV6;
|
||||
std::string user;
|
||||
std::string password;
|
||||
std::string admin_user;
|
||||
std::string admin_password;
|
||||
std::string ssl_key;
|
||||
std::string ssl_cert;
|
||||
std::string ssl_chain;
|
||||
std::string ssl_ciphers;
|
||||
boost::beast::websocket::permessage_deflate pmd_options;
|
||||
std::string adminUser;
|
||||
std::string adminPassword;
|
||||
std::string sslKey;
|
||||
std::string sslCert;
|
||||
std::string sslChain;
|
||||
std::string sslCiphers;
|
||||
boost::beast::websocket::permessage_deflate pmdOptions;
|
||||
std::shared_ptr<boost::asio::ssl::context> context;
|
||||
|
||||
// How many incoming connections are allowed on this
|
||||
@@ -50,7 +50,7 @@ struct Port
|
||||
int limit = 0;
|
||||
|
||||
// Websocket disconnects if send queue exceeds this limit
|
||||
std::uint16_t ws_queue_limit{};
|
||||
std::uint16_t wsQueueLimit{};
|
||||
|
||||
// Returns `true` if any websocket protocols are specified
|
||||
[[nodiscard]] bool
|
||||
@@ -78,22 +78,22 @@ struct ParsedPort
|
||||
std::set<std::string, boost::beast::iless> protocol;
|
||||
std::string user;
|
||||
std::string password;
|
||||
std::string admin_user;
|
||||
std::string admin_password;
|
||||
std::string ssl_key;
|
||||
std::string ssl_cert;
|
||||
std::string ssl_chain;
|
||||
std::string ssl_ciphers;
|
||||
boost::beast::websocket::permessage_deflate pmd_options;
|
||||
std::string adminUser;
|
||||
std::string adminPassword;
|
||||
std::string sslKey;
|
||||
std::string sslCert;
|
||||
std::string sslChain;
|
||||
std::string sslCiphers;
|
||||
boost::beast::websocket::permessage_deflate pmdOptions;
|
||||
int limit = 0;
|
||||
std::uint16_t ws_queue_limit{};
|
||||
std::uint16_t wsQueueLimit{};
|
||||
|
||||
std::optional<boost::asio::ip::address> ip;
|
||||
std::optional<std::uint16_t> port;
|
||||
std::vector<boost::asio::ip::network_v4> admin_nets_v4;
|
||||
std::vector<boost::asio::ip::network_v6> admin_nets_v6;
|
||||
std::vector<boost::asio::ip::network_v4> secure_gateway_nets_v4;
|
||||
std::vector<boost::asio::ip::network_v6> secure_gateway_nets_v6;
|
||||
std::vector<boost::asio::ip::network_v4> adminNetsV4;
|
||||
std::vector<boost::asio::ip::network_v6> adminNetsV6;
|
||||
std::vector<boost::asio::ip::network_v4> secureGatewayNetsV4;
|
||||
std::vector<boost::asio::ip::network_v6> secureGatewayNetsV6;
|
||||
};
|
||||
|
||||
void
|
||||
|
||||
@@ -58,13 +58,13 @@ protected:
|
||||
Handler& handler_;
|
||||
boost::asio::executor_work_guard<boost::asio::executor> work_;
|
||||
boost::asio::strand<boost::asio::executor> strand_;
|
||||
endpoint_type remote_address_;
|
||||
endpoint_type remoteAddress_;
|
||||
beast::Journal const journal_;
|
||||
|
||||
std::string id_;
|
||||
std::size_t nid_;
|
||||
|
||||
boost::asio::streambuf read_buf_;
|
||||
boost::asio::streambuf readBuf_;
|
||||
http_request_type message_;
|
||||
std::vector<Buffer> wq_;
|
||||
std::vector<Buffer> wq2_;
|
||||
@@ -73,9 +73,9 @@ protected:
|
||||
bool complete_ = false;
|
||||
boost::system::error_code ec_;
|
||||
|
||||
int request_count_ = 0;
|
||||
std::size_t bytes_in_ = 0;
|
||||
std::size_t bytes_out_ = 0;
|
||||
int requestCount_ = 0;
|
||||
std::size_t bytesIn_ = 0;
|
||||
std::size_t bytesOut_ = 0;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
@@ -151,7 +151,7 @@ protected:
|
||||
beast::IP::Endpoint
|
||||
remoteAddress() override
|
||||
{
|
||||
return beast::IPAddressConversion::fromAsio(remote_address_);
|
||||
return beast::IPAddressConversion::fromAsio(remoteAddress_);
|
||||
}
|
||||
|
||||
http_request_type&
|
||||
@@ -191,23 +191,23 @@ BaseHTTPPeer<Handler, Impl>::BaseHTTPPeer(
|
||||
, handler_(handler)
|
||||
, work_(boost::asio::make_work_guard(executor))
|
||||
, strand_(boost::asio::make_strand(executor))
|
||||
, remote_address_(std::move(remoteAddress))
|
||||
, remoteAddress_(std::move(remoteAddress))
|
||||
, journal_(journal)
|
||||
{
|
||||
read_buf_.commit(
|
||||
boost::asio::buffer_copy(read_buf_.prepare(boost::asio::buffer_size(buffers)), buffers));
|
||||
readBuf_.commit(
|
||||
boost::asio::buffer_copy(readBuf_.prepare(boost::asio::buffer_size(buffers)), buffers));
|
||||
static std::atomic<int> kSid;
|
||||
nid_ = ++kSid;
|
||||
id_ = std::string("#") + std::to_string(nid_) + " ";
|
||||
JLOG(journal_.trace()) << id_ << "accept: " << remote_address_.address();
|
||||
JLOG(journal_.trace()) << id_ << "accept: " << remoteAddress_.address();
|
||||
}
|
||||
|
||||
template <class Handler, class Impl>
|
||||
BaseHTTPPeer<Handler, Impl>::~BaseHTTPPeer()
|
||||
{
|
||||
handler_.onClose(session(), ec_);
|
||||
JLOG(journal_.trace()) << id_ << "destroyed: " << request_count_
|
||||
<< ((request_count_ == 1) ? " request" : " requests");
|
||||
JLOG(journal_.trace()) << id_ << "destroyed: " << requestCount_
|
||||
<< ((requestCount_ == 1) ? " request" : " requests");
|
||||
}
|
||||
|
||||
template <class Handler, class Impl>
|
||||
@@ -245,7 +245,7 @@ BaseHTTPPeer<Handler, Impl>::startTimer()
|
||||
boost::beast::get_lowest_layer(impl().stream_)
|
||||
.expires_after(
|
||||
std::chrono::seconds(
|
||||
remote_address_.address().is_loopback() ? kTimeoutSecondsLocal : kTimeoutSeconds));
|
||||
remoteAddress_.address().is_loopback() ? kTimeoutSecondsLocal : kTimeoutSeconds));
|
||||
}
|
||||
|
||||
// Convenience for discarding the error code
|
||||
@@ -274,7 +274,7 @@ BaseHTTPPeer<Handler, Impl>::doRead(yield_context doYield)
|
||||
complete_ = false;
|
||||
error_code ec;
|
||||
startTimer();
|
||||
boost::beast::http::async_read(impl().stream_, read_buf_, message_, doYield[ec]);
|
||||
boost::beast::http::async_read(impl().stream_, readBuf_, message_, doYield[ec]);
|
||||
cancelTimer();
|
||||
if (ec == boost::beast::http::error::end_of_stream)
|
||||
return doClose();
|
||||
@@ -296,7 +296,7 @@ BaseHTTPPeer<Handler, Impl>::onWrite(error_code const& ec, std::size_t bytesTran
|
||||
return onTimer();
|
||||
if (ec)
|
||||
return fail(ec, "write");
|
||||
bytes_out_ += bytesTransferred;
|
||||
bytesOut_ += bytesTransferred;
|
||||
{
|
||||
std::scoped_lock const lock(mutex_);
|
||||
wq2_.clear();
|
||||
|
||||
@@ -27,7 +27,7 @@ protected:
|
||||
|
||||
Port const& port_;
|
||||
Handler& handler_;
|
||||
endpoint_type remote_address_;
|
||||
endpoint_type remoteAddress_;
|
||||
beast::WrappedSink sink_;
|
||||
beast::Journal const j_;
|
||||
|
||||
@@ -65,7 +65,7 @@ BasePeer<Handler, Impl>::BasePeer(
|
||||
beast::Journal journal)
|
||||
: port_(port)
|
||||
, handler_(handler)
|
||||
, remote_address_(std::move(remoteAddress))
|
||||
, remoteAddress_(std::move(remoteAddress))
|
||||
, sink_(
|
||||
journal.sink(),
|
||||
[] {
|
||||
|
||||
@@ -42,15 +42,15 @@ private:
|
||||
/// The socket has been closed, or will close after the next write
|
||||
/// finishes. Do not do any more writes, and don't try to close
|
||||
/// again.
|
||||
bool do_close_ = false;
|
||||
bool doClose_ = false;
|
||||
boost::beast::websocket::close_reason cr_;
|
||||
waitable_timer timer_;
|
||||
bool close_on_timer_ = false;
|
||||
bool ping_active_ = false;
|
||||
bool closeOnTimer_ = false;
|
||||
bool pingActive_ = false;
|
||||
boost::beast::websocket::ping_data payload_;
|
||||
error_code ec_;
|
||||
std::function<void(boost::beast::websocket::frame_type, boost::beast::string_view)>
|
||||
control_callback_;
|
||||
controlCallback_;
|
||||
|
||||
public:
|
||||
template <class Body, class Headers>
|
||||
@@ -85,7 +85,7 @@ public:
|
||||
[[nodiscard]] boost::asio::ip::tcp::endpoint const&
|
||||
remoteEndpoint() const override
|
||||
{
|
||||
return this->remote_address_;
|
||||
return this->remoteAddress_;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -173,14 +173,14 @@ BaseWSPeer<Handler, Impl>::run()
|
||||
{
|
||||
if (!strand_.running_in_this_thread())
|
||||
return post(strand_, std::bind(&BaseWSPeer::run, impl().shared_from_this()));
|
||||
impl().ws_.set_option(port().pmd_options);
|
||||
impl().ws_.set_option(port().pmdOptions);
|
||||
// Must manage the control callback memory outside of the `control_callback`
|
||||
// function
|
||||
control_callback_ =
|
||||
controlCallback_ =
|
||||
std::bind(&BaseWSPeer::onPingPong, this, std::placeholders::_1, std::placeholders::_2);
|
||||
impl().ws_.control_callback(control_callback_);
|
||||
impl().ws_.control_callback(controlCallback_);
|
||||
startTimer();
|
||||
close_on_timer_ = true;
|
||||
closeOnTimer_ = true;
|
||||
impl().ws_.set_option(boost::beast::websocket::stream_base::decorator([](auto& res) {
|
||||
res.set(boost::beast::http::field::server, BuildInfo::getFullVersionString());
|
||||
}));
|
||||
@@ -198,9 +198,9 @@ BaseWSPeer<Handler, Impl>::send(std::shared_ptr<WSMsg> w)
|
||||
{
|
||||
if (!strand_.running_in_this_thread())
|
||||
return post(strand_, std::bind(&BaseWSPeer::send, impl().shared_from_this(), std::move(w)));
|
||||
if (do_close_)
|
||||
if (doClose_)
|
||||
return;
|
||||
if (wq_.size() > port().ws_queue_limit)
|
||||
if (wq_.size() > port().wsQueueLimit)
|
||||
{
|
||||
cr_.code = safeCast<decltype(cr_.code)>(boost::beast::websocket::close_code::policy_error);
|
||||
cr_.reason = "Policy error: client is too slow.";
|
||||
@@ -227,9 +227,9 @@ BaseWSPeer<Handler, Impl>::close(boost::beast::websocket::close_reason const& re
|
||||
{
|
||||
if (!strand_.running_in_this_thread())
|
||||
return post(strand_, [self = impl().shared_from_this(), reason] { self->close(reason); });
|
||||
if (do_close_)
|
||||
if (doClose_)
|
||||
return;
|
||||
do_close_ = true;
|
||||
doClose_ = true;
|
||||
if (wq_.empty())
|
||||
{
|
||||
impl().ws_.async_close(
|
||||
@@ -260,7 +260,7 @@ BaseWSPeer<Handler, Impl>::onWsHandshake(error_code const& ec)
|
||||
{
|
||||
if (ec)
|
||||
return fail(ec, "on_ws_handshake");
|
||||
close_on_timer_ = false;
|
||||
closeOnTimer_ = false;
|
||||
doRead();
|
||||
}
|
||||
|
||||
@@ -313,7 +313,7 @@ BaseWSPeer<Handler, Impl>::onWriteFin(error_code const& ec)
|
||||
if (ec)
|
||||
return fail(ec, "write_fin");
|
||||
wq_.pop_front();
|
||||
if (do_close_)
|
||||
if (doClose_)
|
||||
{
|
||||
impl().ws_.async_close(
|
||||
cr_,
|
||||
@@ -409,7 +409,7 @@ BaseWSPeer<Handler, Impl>::onPing(error_code const& ec)
|
||||
{
|
||||
if (ec == boost::asio::error::operation_aborted)
|
||||
return;
|
||||
ping_active_ = false;
|
||||
pingActive_ = false;
|
||||
if (!ec)
|
||||
return;
|
||||
fail(ec, "on_ping");
|
||||
@@ -426,7 +426,7 @@ BaseWSPeer<Handler, Impl>::onPingPong(
|
||||
boost::beast::string_view const p(payload_.begin());
|
||||
if (payload == p)
|
||||
{
|
||||
close_on_timer_ = false;
|
||||
closeOnTimer_ = false;
|
||||
JLOG(this->j_.trace()) << "got matching pong";
|
||||
}
|
||||
else
|
||||
@@ -444,11 +444,11 @@ BaseWSPeer<Handler, Impl>::onTimer(error_code ec)
|
||||
return;
|
||||
if (!ec)
|
||||
{
|
||||
if (!close_on_timer_ || !ping_active_)
|
||||
if (!closeOnTimer_ || !pingActive_)
|
||||
{
|
||||
startTimer();
|
||||
close_on_timer_ = true;
|
||||
ping_active_ = true;
|
||||
closeOnTimer_ = true;
|
||||
pingActive_ = true;
|
||||
// cryptographic is probably overkill..
|
||||
beast::rngfill(payload_.begin(), payload_.size(), cryptoPrng());
|
||||
impl().ws_.async_ping(
|
||||
|
||||
@@ -61,7 +61,7 @@ private:
|
||||
boost::asio::io_context& ioc_;
|
||||
stream_type stream_;
|
||||
socket_type& socket_;
|
||||
endpoint_type remote_address_;
|
||||
endpoint_type remoteAddress_;
|
||||
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
|
||||
beast::Journal const j_;
|
||||
|
||||
@@ -90,15 +90,15 @@ private:
|
||||
acceptor_type acceptor_;
|
||||
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
|
||||
bool ssl_{
|
||||
port_.protocol.count("https") > 0 || port_.protocol.count("wss") > 0 ||
|
||||
port_.protocol.count("wss2") > 0 || port_.protocol.count("peer") > 0};
|
||||
port_.protocol.contains("https") || port_.protocol.contains("wss") ||
|
||||
port_.protocol.contains("wss2") || port_.protocol.contains("peer")};
|
||||
bool plain_{
|
||||
port_.protocol.count("http") > 0 || port_.protocol.count("ws") > 0 ||
|
||||
(port_.protocol.count("ws2") != 0u)};
|
||||
port_.protocol.contains("http") || port_.protocol.contains("ws") ||
|
||||
(port_.protocol.contains("ws2"))};
|
||||
static constexpr std::chrono::milliseconds kInitialAcceptDelay{50};
|
||||
static constexpr std::chrono::milliseconds kMaxAcceptDelay{2000};
|
||||
std::chrono::milliseconds accept_delay_{kInitialAcceptDelay};
|
||||
boost::asio::steady_timer backoff_timer_;
|
||||
std::chrono::milliseconds acceptDelay_{kInitialAcceptDelay};
|
||||
boost::asio::steady_timer backoffTimer_;
|
||||
static constexpr double kFreeFdThreshold = 0.70;
|
||||
|
||||
struct FDStats
|
||||
@@ -164,7 +164,7 @@ Door<Handler>::Detector::Detector(
|
||||
, ioc_(ioc)
|
||||
, stream_(std::move(stream))
|
||||
, socket_(stream_.socket())
|
||||
, remote_address_(std::move(remoteAddress))
|
||||
, remoteAddress_(std::move(remoteAddress))
|
||||
, strand_(boost::asio::make_strand(ioc_))
|
||||
, j_(j)
|
||||
{
|
||||
@@ -199,18 +199,18 @@ Door<Handler>::Detector::doDetect(boost::asio::yield_context doYield)
|
||||
if (ssl)
|
||||
{
|
||||
if (auto sp = ios().template emplace<SSLHTTPPeer<Handler>>(
|
||||
port_, handler_, ioc_, j_, remote_address_, buf.data(), std::move(stream_)))
|
||||
port_, handler_, ioc_, j_, remoteAddress_, buf.data(), std::move(stream_)))
|
||||
sp->run();
|
||||
return;
|
||||
}
|
||||
if (auto sp = ios().template emplace<PlainHTTPPeer<Handler>>(
|
||||
port_, handler_, ioc_, j_, remote_address_, buf.data(), std::move(stream_)))
|
||||
port_, handler_, ioc_, j_, remoteAddress_, buf.data(), std::move(stream_)))
|
||||
sp->run();
|
||||
return;
|
||||
}
|
||||
if (ec != boost::asio::error::operation_aborted)
|
||||
{
|
||||
JLOG(j_.trace()) << "Error detecting ssl: " << ec.message() << " from " << remote_address_;
|
||||
JLOG(j_.trace()) << "Error detecting ssl: " << ec.message() << " from " << remoteAddress_;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,7 +279,7 @@ Door<Handler>::Door(
|
||||
, ioc_(ioContext)
|
||||
, acceptor_(ioContext)
|
||||
, strand_(boost::asio::make_strand(ioContext))
|
||||
, backoff_timer_(ioContext)
|
||||
, backoffTimer_(ioContext)
|
||||
{
|
||||
reOpen();
|
||||
}
|
||||
@@ -302,7 +302,7 @@ Door<Handler>::close()
|
||||
return boost::asio::post(
|
||||
strand_, std::bind(&Door<Handler>::close, this->shared_from_this()));
|
||||
}
|
||||
backoff_timer_.cancel();
|
||||
backoffTimer_.cancel();
|
||||
error_code ec;
|
||||
acceptor_.close(ec);
|
||||
}
|
||||
@@ -338,11 +338,11 @@ Door<Handler>::doAccept(boost::asio::yield_context doYield)
|
||||
{
|
||||
if (shouldThrottleForFds())
|
||||
{
|
||||
backoff_timer_.expires_after(accept_delay_);
|
||||
backoffTimer_.expires_after(acceptDelay_);
|
||||
boost::system::error_code tec;
|
||||
backoff_timer_.async_wait(doYield[tec]);
|
||||
accept_delay_ = std::min(accept_delay_ * 2, kMaxAcceptDelay);
|
||||
JLOG(j_.warn()) << "Throttling do_accept for " << accept_delay_.count() << "ms.";
|
||||
backoffTimer_.async_wait(doYield[tec]);
|
||||
acceptDelay_ = std::min(acceptDelay_ * 2, kMaxAcceptDelay);
|
||||
JLOG(j_.warn()) << "Throttling do_accept for " << acceptDelay_.count() << "ms.";
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -360,13 +360,13 @@ Door<Handler>::doAccept(boost::asio::yield_context doYield)
|
||||
ec == boost::asio::error::no_buffer_space)
|
||||
{
|
||||
JLOG(j_.warn()) << "accept: Too many open files. Pausing for "
|
||||
<< accept_delay_.count() << "ms.";
|
||||
<< acceptDelay_.count() << "ms.";
|
||||
|
||||
backoff_timer_.expires_after(accept_delay_);
|
||||
backoffTimer_.expires_after(acceptDelay_);
|
||||
boost::system::error_code tec;
|
||||
backoff_timer_.async_wait(doYield[tec]);
|
||||
backoffTimer_.async_wait(doYield[tec]);
|
||||
|
||||
accept_delay_ = std::min(accept_delay_ * 2, kMaxAcceptDelay);
|
||||
acceptDelay_ = std::min(acceptDelay_ * 2, kMaxAcceptDelay);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -375,7 +375,7 @@ Door<Handler>::doAccept(boost::asio::yield_context doYield)
|
||||
continue;
|
||||
}
|
||||
|
||||
accept_delay_ = kInitialAcceptDelay;
|
||||
acceptDelay_ = kInitialAcceptDelay;
|
||||
|
||||
if (ssl_ && plain_)
|
||||
{
|
||||
|
||||
@@ -82,7 +82,7 @@ template <class Handler>
|
||||
void
|
||||
PlainHTTPPeer<Handler>::run()
|
||||
{
|
||||
if (!this->handler_.onAccept(this->session(), this->remote_address_))
|
||||
if (!this->handler_.onAccept(this->session(), this->remoteAddress_))
|
||||
{
|
||||
util::spawn(this->strand_, std::bind(&PlainHTTPPeer::doClose, this->shared_from_this()));
|
||||
return;
|
||||
@@ -103,7 +103,7 @@ PlainHTTPPeer<Handler>::websocketUpgrade()
|
||||
auto ws = this->ios().template emplace<PlainWSPeer<Handler>>(
|
||||
this->port_,
|
||||
this->handler_,
|
||||
this->remote_address_,
|
||||
this->remoteAddress_,
|
||||
std::move(this->message_),
|
||||
std::move(stream_),
|
||||
this->journal_);
|
||||
@@ -114,20 +114,20 @@ template <class Handler>
|
||||
void
|
||||
PlainHTTPPeer<Handler>::doRequest()
|
||||
{
|
||||
++this->request_count_;
|
||||
++this->requestCount_;
|
||||
auto const what =
|
||||
this->handler_.onHandoff(this->session(), std::move(this->message_), this->remote_address_);
|
||||
this->handler_.onHandoff(this->session(), std::move(this->message_), this->remoteAddress_);
|
||||
if (what.moved)
|
||||
return;
|
||||
boost::system::error_code ec;
|
||||
if (what.response)
|
||||
{
|
||||
// half-close on Connection: close
|
||||
if (!what.keep_alive)
|
||||
if (!what.keepAlive)
|
||||
socket_.shutdown(socket_type::shutdown_receive, ec);
|
||||
if (ec)
|
||||
return this->fail(ec, "request");
|
||||
return this->write(what.response, what.keep_alive);
|
||||
return this->write(what.response, what.keepAlive);
|
||||
}
|
||||
|
||||
// Perform half-close when Connection: close and not SSL
|
||||
|
||||
@@ -26,7 +26,7 @@ private:
|
||||
using yield_context = boost::asio::yield_context;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
std::unique_ptr<stream_type> stream_ptr_;
|
||||
std::unique_ptr<stream_type> streamPtr_;
|
||||
stream_type& stream_;
|
||||
socket_type& socket_;
|
||||
|
||||
@@ -80,8 +80,8 @@ SSLHTTPPeer<Handler>::SSLHTTPPeer(
|
||||
journal,
|
||||
remoteAddress,
|
||||
buffers)
|
||||
, stream_ptr_(std::make_unique<stream_type>(middle_type(std::move(stream)), *port.context))
|
||||
, stream_(*stream_ptr_)
|
||||
, streamPtr_(std::make_unique<stream_type>(middle_type(std::move(stream)), *port.context))
|
||||
, stream_(*streamPtr_)
|
||||
, socket_(stream_.next_layer().socket())
|
||||
{
|
||||
}
|
||||
@@ -91,7 +91,7 @@ template <class Handler>
|
||||
void
|
||||
SSLHTTPPeer<Handler>::run()
|
||||
{
|
||||
if (!this->handler_.onAccept(this->session(), this->remote_address_))
|
||||
if (!this->handler_.onAccept(this->session(), this->remoteAddress_))
|
||||
{
|
||||
util::spawn(this->strand_, std::bind(&SSLHTTPPeer::doClose, this->shared_from_this()));
|
||||
return;
|
||||
@@ -110,9 +110,9 @@ SSLHTTPPeer<Handler>::websocketUpgrade()
|
||||
auto ws = this->ios().template emplace<SSLWSPeer<Handler>>(
|
||||
this->port_,
|
||||
this->handler_,
|
||||
this->remote_address_,
|
||||
this->remoteAddress_,
|
||||
std::move(this->message_),
|
||||
std::move(this->stream_ptr_),
|
||||
std::move(this->streamPtr_),
|
||||
this->journal_);
|
||||
return ws;
|
||||
}
|
||||
@@ -124,8 +124,8 @@ SSLHTTPPeer<Handler>::doHandshake(yield_context doYield)
|
||||
boost::system::error_code ec;
|
||||
stream_.set_verify_mode(boost::asio::ssl::verify_none);
|
||||
this->startTimer();
|
||||
this->read_buf_.consume(
|
||||
stream_.async_handshake(stream_type::server, this->read_buf_.data(), doYield[ec]));
|
||||
this->readBuf_.consume(
|
||||
stream_.async_handshake(stream_type::server, this->readBuf_.data(), doYield[ec]));
|
||||
this->cancelTimer();
|
||||
if (ec == boost::beast::error::timeout)
|
||||
return this->onTimer();
|
||||
@@ -148,13 +148,13 @@ template <class Handler>
|
||||
void
|
||||
SSLHTTPPeer<Handler>::doRequest()
|
||||
{
|
||||
++this->request_count_;
|
||||
++this->requestCount_;
|
||||
auto const what = this->handler_.onHandoff(
|
||||
this->session(), std::move(stream_ptr_), std::move(this->message_), this->remote_address_);
|
||||
this->session(), std::move(streamPtr_), std::move(this->message_), this->remoteAddress_);
|
||||
if (what.moved)
|
||||
return;
|
||||
if (what.response)
|
||||
return this->write(what.response, what.keep_alive);
|
||||
return this->write(what.response, what.keepAlive);
|
||||
// legacy
|
||||
this->handler_.onRequest(this->session());
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ class SSLWSPeer : public BaseWSPeer<Handler, SSLWSPeer<Handler>>,
|
||||
using stream_type = boost::beast::ssl_stream<socket_type>;
|
||||
using waitable_timer = boost::asio::basic_waitable_timer<clock_type>;
|
||||
|
||||
std::unique_ptr<stream_type> stream_ptr_;
|
||||
std::unique_ptr<stream_type> streamPtr_;
|
||||
boost::beast::websocket::stream<stream_type&> ws_;
|
||||
|
||||
public:
|
||||
@@ -61,8 +61,8 @@ SSLWSPeer<Handler>::SSLWSPeer(
|
||||
remoteEndpoint,
|
||||
std::move(request),
|
||||
journal)
|
||||
, stream_ptr_(std::move(streamPtr))
|
||||
, ws_(*stream_ptr_)
|
||||
, streamPtr_(std::move(streamPtr))
|
||||
, ws_(*streamPtr_)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ private:
|
||||
|
||||
Handler& handler_;
|
||||
beast::Journal const j_;
|
||||
boost::asio::io_context& io_context_;
|
||||
boost::asio::io_context& ioContext_;
|
||||
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
|
||||
std::optional<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_;
|
||||
|
||||
@@ -104,7 +104,7 @@ public:
|
||||
boost::asio::io_context&
|
||||
getIoContext()
|
||||
{
|
||||
return io_context_;
|
||||
return ioContext_;
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -122,9 +122,9 @@ ServerImpl<Handler>::ServerImpl(
|
||||
beast::Journal journal)
|
||||
: handler_(handler)
|
||||
, j_(journal)
|
||||
, io_context_(ioContext)
|
||||
, strand_(boost::asio::make_strand(io_context_))
|
||||
, work_(std::in_place, boost::asio::make_work_guard(io_context_))
|
||||
, ioContext_(ioContext)
|
||||
, strand_(boost::asio::make_strand(ioContext_))
|
||||
, work_(std::in_place, boost::asio::make_work_guard(ioContext_))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ ServerImpl<Handler>::ports(std::vector<Port> const& ports)
|
||||
{
|
||||
ports_.push_back(port);
|
||||
auto& internalPort = ports_.back();
|
||||
if (auto sp = ios_.emplace<Door<Handler>>(handler_, io_context_, internalPort, j_))
|
||||
if (auto sp = ios_.emplace<Door<Handler>>(handler_, ioContext_, internalPort, j_))
|
||||
{
|
||||
list_.push_back(sp);
|
||||
|
||||
|
||||
@@ -231,7 +231,7 @@ The `fetchNodeNT()` method goes through three phases:
|
||||
will be 0.
|
||||
|
||||
2. If the node is not in the TreeNodeCache, we attempt to locate the node
|
||||
in the historic data stored by the data base. The call to to
|
||||
in the historic data stored by the data base. The call to
|
||||
`fetchNodeFromDB(hash)` does that work for us.
|
||||
|
||||
3. Finally if a filter exists, we check if it can supply the node. This is
|
||||
|
||||
27
include/xrpl/tx/invariants/DirectoryInvariant.h
Normal file
27
include/xrpl/tx/invariants/DirectoryInvariant.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class ValidBookDirectory
|
||||
{
|
||||
bool badBookDirectory_ = false;
|
||||
hash_set<uint256> rootIndexes_;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/tx/invariants/AMMInvariant.h>
|
||||
#include <xrpl/tx/invariants/DirectoryInvariant.h>
|
||||
#include <xrpl/tx/invariants/FreezeInvariant.h>
|
||||
#include <xrpl/tx/invariants/LoanBrokerInvariant.h>
|
||||
#include <xrpl/tx/invariants/LoanInvariant.h>
|
||||
@@ -393,6 +394,7 @@ using InvariantChecks = std::tuple<
|
||||
ValidMPTIssuance,
|
||||
ValidPermissionedDomain,
|
||||
ValidPermissionedDEX,
|
||||
ValidBookDirectory,
|
||||
ValidAMM,
|
||||
NoModifiedUnmodifiableFields,
|
||||
ValidPseudoAccounts,
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -20,6 +23,18 @@ class ValidMPTIssuance
|
||||
// MPToken by an issuer
|
||||
bool mptCreatedByIssuer_ = false;
|
||||
|
||||
/// sfReferenceHolding is intended to be set exactly once at vault
|
||||
/// creation and immutable thereafter; true when that rule was violated.
|
||||
bool referenceHoldingSetOnCreate_ = false;
|
||||
|
||||
/// True when sfReferenceHolding was mutated on an existing MPTokenIssuance.
|
||||
bool referenceHoldingMutated_ = false;
|
||||
|
||||
/// MPTokens and RippleStates deleted during apply. finalize() checks each
|
||||
/// holder's AccountRoot to detect vault pseudo-account holdings deleted
|
||||
/// outside VaultDelete. All these checks are gated on fixCleanup3_2_0.
|
||||
std::vector<std::shared_ptr<SLE const>> deletedHoldings_;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
@@ -10,9 +10,10 @@ namespace xrpl {
|
||||
|
||||
class ValidPermissionedDEX
|
||||
{
|
||||
bool regularOffers_ = false;
|
||||
bool badHybridsOld_ = false; // pre-fixCleanup3_1_3: missing field/domain or size > 1
|
||||
bool badHybrids_ = false; // post-fixCleanup3_1_3: also catches size == 0 (size != 1)
|
||||
bool regularOffersOld_ = false; // pre-fixCleanup3_2_0: also flags deleted offers
|
||||
bool regularOffers_ = false; // post-fixCleanup3_2_0: excludes deleted offers
|
||||
bool badHybridsOld_ = false; // pre-fixCleanup3_1_3: missing field/domain or size > 1
|
||||
bool badHybrids_ = false; // post-fixCleanup3_1_3: also catches size == 0 (size != 1)
|
||||
hash_set<uint256> domains_;
|
||||
|
||||
public:
|
||||
|
||||
@@ -85,6 +85,7 @@ private:
|
||||
Keylet const& offerIndex,
|
||||
STAmount const& saTakerPays,
|
||||
STAmount const& saTakerGets,
|
||||
std::uint64_t openRate,
|
||||
std::function<void(SLE::ref, std::optional<uint256>)> const& setDir);
|
||||
};
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ class LedgerStateFix : public Transactor
|
||||
public:
|
||||
enum class FixType : std::uint16_t {
|
||||
NfTokenPageLink = 1,
|
||||
BookExchangeRate = 2,
|
||||
};
|
||||
|
||||
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
|
||||
|
||||
@@ -6,23 +6,28 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// NOLINTBEGIN(readability-redundant-member-init)
|
||||
struct MPTCreateArgs
|
||||
{
|
||||
std::optional<XRPAmount> priorBalance;
|
||||
AccountID const& account;
|
||||
std::uint32_t sequence = 0;
|
||||
std::uint32_t flags = 0;
|
||||
std::optional<std::uint64_t> maxAmount =
|
||||
std::nullopt; // NOLINT(readability-redundant-member-init)
|
||||
std::optional<std::uint8_t> assetScale =
|
||||
std::nullopt; // NOLINT(readability-redundant-member-init)
|
||||
std::optional<std::uint16_t> transferFee =
|
||||
std::nullopt; // NOLINT(readability-redundant-member-init)
|
||||
std::optional<std::uint64_t> maxAmount = std::nullopt;
|
||||
std::optional<std::uint8_t> assetScale = std::nullopt;
|
||||
std::optional<std::uint16_t> transferFee = std::nullopt;
|
||||
std::optional<Slice> const& metadata{};
|
||||
std::optional<uint256> domainId = std::nullopt; // NOLINT(readability-redundant-member-init)
|
||||
std::optional<std::uint32_t> mutableFlags =
|
||||
std::nullopt; // NOLINT(readability-redundant-member-init)
|
||||
std::optional<uint256> domainId = std::nullopt;
|
||||
std::optional<std::uint32_t> mutableFlags = std::nullopt;
|
||||
// Set only by callers that issue an MPT representing a wrapped asset
|
||||
// (e.g. VaultCreate's share token). The keylet must point to an
|
||||
// existing MPToken or RippleState owned by `account`. Surfaces on
|
||||
// the resulting MPTokenIssuance via the optional sfReferenceHolding
|
||||
// field. Used by readers (canTransfer, canTrade, freezing) to
|
||||
// inherit the underlying asset's transferability.
|
||||
std::optional<uint256> referenceHolding = std::nullopt;
|
||||
};
|
||||
// NOLINTEND(readability-redundant-member-init)
|
||||
|
||||
class MPTokenIssuanceCreate : public Transactor
|
||||
{
|
||||
|
||||
@@ -1,516 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace wasm_float {
|
||||
|
||||
std::string
|
||||
floatToString(Slice const& data);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromIntImpl(int64_t x, int32_t mode);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromUintImpl(uint64_t x, int32_t mode);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromSTAmountImpl(STAmount const& x, int32_t mode);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromSTNumberImpl(STNumber const& x, int32_t mode);
|
||||
|
||||
Expected<int64_t, HostFunctionError>
|
||||
floatToIntImpl(Slice const& x, int32_t mode);
|
||||
|
||||
Expected<FloatPair, HostFunctionError>
|
||||
floatToMantExpImpl(Slice const& x);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromMantExpImpl(int64_t mantissa, int32_t exponent, int32_t mode);
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
floatCompareImpl(Slice const& x, Slice const& y);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatAddImpl(Slice const& x, Slice const& y, int32_t mode);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatSubtractImpl(Slice const& x, Slice const& y, int32_t mode);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatMultiplyImpl(Slice const& x, Slice const& y, int32_t mode);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatDivideImpl(Slice const& x, Slice const& y, int32_t mode);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatRootImpl(Slice const& x, int32_t n, int32_t mode);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatPowerImpl(Slice const& x, int32_t n, int32_t mode);
|
||||
|
||||
} // namespace wasm_float
|
||||
|
||||
// Intended to work only through wasm runtime. Don't call them directly, except with unit tests
|
||||
class HostFunctions
|
||||
{
|
||||
protected:
|
||||
RTOptRef rt_;
|
||||
beast::Journal j_;
|
||||
|
||||
public:
|
||||
HostFunctions(beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) : j_(j)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
setRT(WasmRuntimeWrapper& rt)
|
||||
{
|
||||
rt_ = rt;
|
||||
}
|
||||
|
||||
void
|
||||
resetRT()
|
||||
{
|
||||
rt_ = std::nullopt;
|
||||
}
|
||||
|
||||
[[nodiscard]] WasmRuntimeWrapper&
|
||||
getRT() const
|
||||
{
|
||||
if (!rt_)
|
||||
Throw<std::logic_error>("Wasm runtime not set");
|
||||
return rt_->get();
|
||||
}
|
||||
|
||||
[[nodiscard]] beast::Journal
|
||||
getJournal() const
|
||||
{
|
||||
return j_;
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
|
||||
[[nodiscard]] virtual bool
|
||||
checkSelf() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual Expected<std::uint32_t, HostFunctionError>
|
||||
getLedgerSqn() const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<std::uint32_t, HostFunctionError>
|
||||
getParentLedgerTime() const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Hash, HostFunctionError>
|
||||
getParentLedgerHash() const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<uint32_t, HostFunctionError>
|
||||
getBaseFee() const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
isAmendmentEnabled(uint256 const& amendmentId) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
isAmendmentEnabled(std::string_view const& amendmentName) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
cacheLedgerObj(uint256 const& objId, int32_t cacheIdx)
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
getTxField(SField const& fname) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
getCurrentLedgerObjField(SField const& fname) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
getLedgerObjField(int32_t cacheIdx, SField const& fname) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
getTxNestedField(FieldLocator const& locator) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
getCurrentLedgerObjNestedField(FieldLocator const& locator) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
getLedgerObjNestedField(int32_t cacheIdx, FieldLocator const& locator) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
getTxArrayLen(SField const& fname) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
getCurrentLedgerObjArrayLen(SField const& fname) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
getTxNestedArrayLen(FieldLocator const& locator) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
getCurrentLedgerObjNestedArrayLen(FieldLocator const& locator) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
getLedgerObjNestedArrayLen(int32_t cacheIdx, FieldLocator const& locator) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
updateData(Slice const& data)
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
checkSignature(Slice const& message, Slice const& signature, Slice const& pubkey) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Hash, HostFunctionError>
|
||||
computeSha512HalfHash(Slice const& data) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
accountKeylet(AccountID const& account) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
ammKeylet(Asset const& issue1, Asset const& issue2) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
checkKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
credentialKeylet(AccountID const& subject, AccountID const& issuer, Slice const& credentialType)
|
||||
const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
didKeylet(AccountID const& account) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
delegateKeylet(AccountID const& account, AccountID const& authorize) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
depositPreauthKeylet(AccountID const& account, AccountID const& authorize) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
escrowKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
lineKeylet(AccountID const& account1, AccountID const& account2, Currency const& currency) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
mptIssuanceKeylet(AccountID const& issuer, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
mptokenKeylet(MPTID const& mptid, AccountID const& holder) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
nftOfferKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
offerKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
oracleKeylet(AccountID const& account, std::uint32_t docId) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
paychanKeylet(AccountID const& account, AccountID const& destination, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
permissionedDomainKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
signersKeylet(AccountID const& account) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
ticketKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
vaultKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
getNFT(AccountID const& account, uint256 const& nftId) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
getNFTIssuer(uint256 const& nftId) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<std::uint32_t, HostFunctionError>
|
||||
getNFTTaxon(uint256 const& nftId) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
getNFTFlags(uint256 const& nftId) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
getNFTTransferFee(uint256 const& nftId) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<std::uint32_t, HostFunctionError>
|
||||
getNFTSerial(uint256 const& nftId) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
trace(std::string_view const& msg, Slice const& data, bool asHex) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
traceNum(std::string_view const& msg, int64_t data) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
traceAccount(std::string_view const& msg, AccountID const& account) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
traceFloat(std::string_view const& msg, Slice const& data) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
traceAmount(std::string_view const& msg, STAmount const& amount) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatFromInt(int64_t x, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatFromUint(uint64_t x, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatFromSTAmount(STAmount const& x, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatFromSTNumber(STNumber const& x, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<int64_t, HostFunctionError>
|
||||
floatToInt(Slice const& x, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<FloatPair, HostFunctionError>
|
||||
floatToMantExp(Slice const& x) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatFromMantExp(int64_t mantissa, int32_t exponent, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
floatCompare(Slice const& x, Slice const& y) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatAdd(Slice const& x, Slice const& y, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatSubtract(Slice const& x, Slice const& y, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatMultiply(Slice const& x, Slice const& y, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatDivide(Slice const& x, Slice const& y, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatRoot(Slice const& x, int32_t n, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatPower(Slice const& x, int32_t n, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Internal);
|
||||
}
|
||||
|
||||
virtual ~HostFunctions() = default;
|
||||
// LCOV_EXCL_STOP
|
||||
};
|
||||
|
||||
using HFRef = std::reference_wrapper<HostFunctions>;
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,283 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/ApplyContext.h>
|
||||
#include <xrpl/tx/wasm/HostFunc.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// Intended to work only through wasm runtime. Don't call them directly, except with unit tests
|
||||
class WasmHostFunctionsImpl : public HostFunctions
|
||||
{
|
||||
ApplyContext& ctx_;
|
||||
|
||||
Keylet leKey_;
|
||||
mutable std::optional<std::shared_ptr<SLE const>> currentLedgerObj_;
|
||||
|
||||
static int constexpr maxCache = 256;
|
||||
std::array<std::shared_ptr<SLE const>, maxCache> cache_;
|
||||
|
||||
std::optional<Bytes> data_;
|
||||
|
||||
public:
|
||||
Expected<std::shared_ptr<SLE const>, HostFunctionError>
|
||||
getCurrentLedgerObj() const
|
||||
{
|
||||
if (!currentLedgerObj_)
|
||||
currentLedgerObj_ = ctx_.view().read(leKey_);
|
||||
if (*currentLedgerObj_)
|
||||
return *currentLedgerObj_;
|
||||
return Unexpected(HostFunctionError::LedgerObjNotFound);
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
normalizeCacheIndex(int32_t cacheIdx) const
|
||||
{
|
||||
--cacheIdx;
|
||||
if (cacheIdx < 0 || cacheIdx >= maxCache)
|
||||
return Unexpected(HostFunctionError::SlotOutRange);
|
||||
if (!cache_[cacheIdx])
|
||||
return Unexpected(HostFunctionError::EmptySlot);
|
||||
return cacheIdx;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void
|
||||
log(std::string_view const& msg, F&& dataFn) const
|
||||
{
|
||||
#ifdef DEBUG_OUTPUT
|
||||
auto& j = std::cerr;
|
||||
#else
|
||||
if (!getJournal().active(beast::Severity::Trace))
|
||||
return;
|
||||
auto j = getJournal().trace();
|
||||
#endif
|
||||
j << "WasmTrace[" << toShortString(leKey_.key) << "]: " << msg << " " << dataFn();
|
||||
|
||||
#ifdef DEBUG_OUTPUT
|
||||
j << std::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
public:
|
||||
WasmHostFunctionsImpl(ApplyContext& ct, Keylet const& leKey)
|
||||
: HostFunctions(ct.journal), ctx_(ct), leKey_(leKey)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
checkSelf() const override
|
||||
{
|
||||
return !currentLedgerObj_ && !data_ &&
|
||||
std::ranges::none_of(cache_, [](auto const& p) { return !!p; });
|
||||
}
|
||||
|
||||
std::optional<Bytes> const&
|
||||
getData() const
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
getLedgerSqn() const override;
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
getParentLedgerTime() const override;
|
||||
|
||||
Expected<Hash, HostFunctionError>
|
||||
getParentLedgerHash() const override;
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
getBaseFee() const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
isAmendmentEnabled(uint256 const& amendmentId) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
isAmendmentEnabled(std::string_view const& amendmentName) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
cacheLedgerObj(uint256 const& objId, int32_t cacheIdx) override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getTxField(SField const& fname) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getCurrentLedgerObjField(SField const& fname) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getLedgerObjField(int32_t cacheIdx, SField const& fname) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getTxNestedField(FieldLocator const& locator) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getCurrentLedgerObjNestedField(FieldLocator const& locator) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getLedgerObjNestedField(int32_t cacheIdx, FieldLocator const& locator) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getTxArrayLen(SField const& fname) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getCurrentLedgerObjArrayLen(SField const& fname) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getTxNestedArrayLen(FieldLocator const& locator) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getCurrentLedgerObjNestedArrayLen(FieldLocator const& locator) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getLedgerObjNestedArrayLen(int32_t cacheIdx, FieldLocator const& locator) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
updateData(Slice const& data) override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
checkSignature(Slice const& message, Slice const& signature, Slice const& pubkey)
|
||||
const override;
|
||||
|
||||
Expected<Hash, HostFunctionError>
|
||||
computeSha512HalfHash(Slice const& data) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
accountKeylet(AccountID const& account) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
ammKeylet(Asset const& issue1, Asset const& issue2) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
checkKeylet(AccountID const& account, std::uint32_t seq) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
credentialKeylet(AccountID const& subject, AccountID const& issuer, Slice const& credentialType)
|
||||
const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
didKeylet(AccountID const& account) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
delegateKeylet(AccountID const& account, AccountID const& authorize) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
depositPreauthKeylet(AccountID const& account, AccountID const& authorize) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
escrowKeylet(AccountID const& account, std::uint32_t seq) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
lineKeylet(AccountID const& account1, AccountID const& account2, Currency const& currency)
|
||||
const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
mptIssuanceKeylet(AccountID const& issuer, std::uint32_t seq) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
mptokenKeylet(MPTID const& mptid, AccountID const& holder) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
nftOfferKeylet(AccountID const& account, std::uint32_t seq) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
offerKeylet(AccountID const& account, std::uint32_t seq) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
oracleKeylet(AccountID const& account, std::uint32_t docId) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
paychanKeylet(AccountID const& account, AccountID const& destination, std::uint32_t seq)
|
||||
const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
permissionedDomainKeylet(AccountID const& account, std::uint32_t seq) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
signersKeylet(AccountID const& account) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
ticketKeylet(AccountID const& account, std::uint32_t seq) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
vaultKeylet(AccountID const& account, std::uint32_t seq) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getNFT(AccountID const& account, uint256 const& nftId) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getNFTIssuer(uint256 const& nftId) const override;
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
getNFTTaxon(uint256 const& nftId) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getNFTFlags(uint256 const& nftId) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getNFTTransferFee(uint256 const& nftId) const override;
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
getNFTSerial(uint256 const& nftId) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
trace(std::string_view const& msg, Slice const& data, bool asHex) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
traceNum(std::string_view const& msg, int64_t data) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
traceAccount(std::string_view const& msg, AccountID const& account) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
traceFloat(std::string_view const& msg, Slice const& data) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
traceAmount(std::string_view const& msg, STAmount const& amount) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromInt(int64_t x, int32_t mode) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromUint(uint64_t x, int32_t mode) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromSTAmount(STAmount const& x, int32_t mode) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromSTNumber(STNumber const& x, int32_t mode) const override;
|
||||
|
||||
Expected<int64_t, HostFunctionError>
|
||||
floatToInt(Slice const& x, int32_t mode) const override;
|
||||
|
||||
Expected<FloatPair, HostFunctionError>
|
||||
floatToMantExp(Slice const& x) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromMantExp(int64_t mantissa, int32_t exponent, int32_t mode) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
floatCompare(Slice const& x, Slice const& y) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatAdd(Slice const& x, Slice const& y, int32_t mode) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatSubtract(Slice const& x, Slice const& y, int32_t mode) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatMultiply(Slice const& x, Slice const& y, int32_t mode) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatDivide(Slice const& x, Slice const& y, int32_t mode) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatRoot(Slice const& x, int32_t n, int32_t mode) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatPower(Slice const& x, int32_t n, int32_t mode) const override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,254 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/wasm/WasmImportsHelper.h>
|
||||
|
||||
#include <wasm.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
#define WASM_CB_PARAMS_LIST void *env, wasm_val_vec_t const *params, wasm_val_vec_t *results
|
||||
#define WASM_SECONDARY_CB_PARAMS_LIST \
|
||||
HostFunctions &hf, wasm_val_vec_t const *params, wasm_val_vec_t *results
|
||||
|
||||
wasm_trap_t* HostFuncMain_wrap(WASM_CB_PARAMS_LIST);
|
||||
|
||||
using getLedgerSqn_proto = int32_t(uint8_t*, int32_t);
|
||||
wasm_trap_t* getLedgerSqn_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getParentLedgerTime_proto = int32_t(uint8_t*, int32_t);
|
||||
wasm_trap_t* getParentLedgerTime_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getParentLedgerHash_proto = int32_t(uint8_t*, int32_t);
|
||||
wasm_trap_t* getParentLedgerHash_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getBaseFee_proto = int32_t(uint8_t*, int32_t);
|
||||
wasm_trap_t* getBaseFee_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using isAmendmentEnabled_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t* isAmendmentEnabled_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using cacheLedgerObj_proto = int32_t(uint8_t const*, int32_t, int32_t);
|
||||
wasm_trap_t* cacheLedgerObj_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getTxField_proto = int32_t(int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getTxField_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getCurrentLedgerObjField_proto = int32_t(int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getCurrentLedgerObjField_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getLedgerObjField_proto = int32_t(int32_t, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getLedgerObjField_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getTxNestedField_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getTxNestedField_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getCurrentLedgerObjNestedField_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getCurrentLedgerObjNestedField_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getLedgerObjNestedField_proto = int32_t(int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getLedgerObjNestedField_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getTxArrayLen_proto = int32_t(int32_t);
|
||||
wasm_trap_t* getTxArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getCurrentLedgerObjArrayLen_proto = int32_t(int32_t);
|
||||
wasm_trap_t* getCurrentLedgerObjArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getLedgerObjArrayLen_proto = int32_t(int32_t, int32_t);
|
||||
wasm_trap_t* getLedgerObjArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getTxNestedArrayLen_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t* getTxNestedArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getCurrentLedgerObjNestedArrayLen_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t* getCurrentLedgerObjNestedArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getLedgerObjNestedArrayLen_proto = int32_t(int32_t, uint8_t const*, int32_t);
|
||||
wasm_trap_t* getLedgerObjNestedArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using updateData_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t* updateData_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using checkSignature_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t const*, int32_t);
|
||||
wasm_trap_t* checkSignature_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using computeSha512HalfHash_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* computeSha512HalfHash_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using accountKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* accountKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using ammKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* ammKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using checkKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* checkKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using credentialKeylet_proto = int32_t(
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t*,
|
||||
int32_t);
|
||||
wasm_trap_t* credentialKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using delegateKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* delegateKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using depositPreauthKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* depositPreauthKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using didKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* didKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using escrowKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* escrowKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using lineKeylet_proto = int32_t(
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t*,
|
||||
int32_t);
|
||||
wasm_trap_t* lineKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using mptIssuanceKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* mptIssuanceKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using mptokenKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* mptokenKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using nftOfferKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* nftOfferKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using offerKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* offerKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using oracleKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* oracleKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using paychanKeylet_proto = int32_t(
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t*,
|
||||
int32_t);
|
||||
wasm_trap_t* paychanKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using permissionedDomainKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* permissionedDomainKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using signersKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* signersKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using ticketKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* ticketKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using vaultKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* vaultKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getNFT_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getNFT_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getNFTIssuer_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getNFTIssuer_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getNFTTaxon_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getNFTTaxon_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getNFTFlags_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t* getNFTFlags_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getNFTTransferFee_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t* getNFTTransferFee_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getNFTSerial_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getNFTSerial_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using trace_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, int32_t);
|
||||
wasm_trap_t* trace_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using traceNum_proto = int32_t(uint8_t const*, int32_t, int64_t);
|
||||
wasm_trap_t* traceNum_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using traceAccount_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t);
|
||||
wasm_trap_t* traceAccount_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using traceFloat_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t);
|
||||
wasm_trap_t* traceFloat_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using traceAmount_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t);
|
||||
wasm_trap_t* traceAmount_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatFromInt_proto = int32_t(int64_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatFromInt_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatFromUint_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatFromUint_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatFromSTAmount_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatFromSTAmount_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatFromSTNumber_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatFromSTNumber_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatToInt_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatToInt_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatToMantExp_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* floatToMantExp_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatFromMantExp_proto = int32_t(int64_t, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatFromMantExp_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatCompare_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t);
|
||||
wasm_trap_t* floatCompare_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatAdd_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatAdd_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatSubtract_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatSubtract_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatMultiply_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatMultiply_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatDivide_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatDivide_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatRoot_proto = int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatRoot_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatPower_proto = int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatPower_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,189 +0,0 @@
|
||||
# WASM Module for Programmable Escrows
|
||||
|
||||
This module provides WebAssembly (WASM) execution capabilities for programmable
|
||||
escrows on the XRP Ledger. When an escrow is finished, the WASM code runs to
|
||||
determine whether the escrow conditions are met, enabling custom programmable
|
||||
logic for escrow release conditions.
|
||||
|
||||
For the full specification, see
|
||||
[XLS-0102: WASM VM](https://xls.xrpl.org/xls/XLS-0102-wasm-vm.html).
|
||||
|
||||
## Architecture
|
||||
|
||||
The module follows a layered architecture:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ WasmEngine (WasmVM.h) │
|
||||
│ runEscrowWasm(), preflightEscrowWasm() │
|
||||
│ Host function registration │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ WasmiEngine (WasmiVM.h) │
|
||||
│ Low-level wasmi interpreter integration │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ HostFuncWrapper │ HostFuncImpl │
|
||||
│ C-style WASM bridges │ C++ implementations │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ HostFunc (Interface) │
|
||||
│ Abstract base class for host functions │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Key Components
|
||||
|
||||
- **`WasmVM.h` / `detail/WasmVM.cpp`** - High-level facade providing:
|
||||
- `WasmEngine` singleton that wraps the underlying WASM interpreter
|
||||
- `runEscrowWasm()` - Execute WASM code for escrow finish
|
||||
- `preflightEscrowWasm()` - Validate WASM code during preflight
|
||||
- `createWasmImport()` - Register all host functions
|
||||
|
||||
- **`WasmiVM.h` / `detail/WasmiVM.cpp`** - Low-level integration with the
|
||||
[wasmi](https://github.com/wasmi-labs/wasmi) WebAssembly interpreter:
|
||||
- `WasmiEngine` - Manages WASM modules, instances, and execution
|
||||
- Memory management and gas metering
|
||||
- Function invocation and result handling
|
||||
|
||||
- **`HostFunc.h`** - Abstract `HostFunctions` base class defining the interface
|
||||
for all callable host functions. Each method returns
|
||||
`Expected<T, HostFunctionError>`.
|
||||
|
||||
- **`HostFuncImpl.h` / `detail/HostFuncImpl*.cpp`** - Concrete
|
||||
`WasmHostFunctionsImpl` class that implements host functions with access to
|
||||
`ApplyContext` for ledger state queries. Implementation split across files:
|
||||
- `HostFuncImpl.cpp` - Core utilities (updateData, checkSignature, etc.)
|
||||
- `HostFuncImplFloat.cpp` - Float/number arithmetic operations
|
||||
- `HostFuncImplGetter.cpp` - Field access (transaction, ledger objects)
|
||||
- `HostFuncImplKeylet.cpp` - Keylet construction functions
|
||||
- `HostFuncImplLedgerHeader.cpp` - Ledger header info access
|
||||
- `HostFuncImplNFT.cpp` - NFT-related queries
|
||||
- `HostFuncImplTrace.cpp` - Debugging/tracing functions
|
||||
|
||||
- **`HostFuncWrapper.h` / `detail/HostFuncWrapper.cpp`** - C-style wrapper
|
||||
functions that bridge WASM calls to C++ `HostFunctions` methods. Each host
|
||||
function has:
|
||||
- A `_proto` type alias defining the function signature
|
||||
- A `_wrap` function that extracts parameters and calls the implementation
|
||||
|
||||
- **`ParamsHelper.h`** - Utilities for WASM parameter handling:
|
||||
- `WASM_IMPORT_FUNC` / `WASM_IMPORT_FUNC2` macros for registration
|
||||
- `wasmParams()` helper for building parameter vectors
|
||||
- Type conversion between WASM and C++ types
|
||||
|
||||
## Host Functions
|
||||
|
||||
Host functions allow WASM code to interact with the XRP Ledger. They are
|
||||
organized into categories:
|
||||
|
||||
- **Ledger Information** - Access ledger sequence, timestamps, hashes, fees
|
||||
- **Transaction & Ledger Object Access** - Read fields from the transaction
|
||||
and ledger objects (including the current escrow object)
|
||||
- **Keylet Construction** - Build keylets to look up various ledger object types
|
||||
- **Cryptography** - Signature verification and hashing
|
||||
- **Float Arithmetic** - Mathematical operations for amount calculations
|
||||
- **NFT Operations** - Query NFT properties
|
||||
- **Tracing/Debugging** - Log messages for debugging
|
||||
|
||||
For the complete list of available host functions, their WASM names, and gas
|
||||
costs, see the [XLS-0102 specification](https://xls.xrpl.org/xls/XLS-0102-wasm-vm.html)
|
||||
or `detail/WasmVM.cpp` where they are registered via `WASM_IMPORT_FUNC2` macros.
|
||||
For method signatures, see `HostFunc.h`.
|
||||
|
||||
## Gas Model
|
||||
|
||||
Each host function has an associated gas cost. The gas cost is specified when
|
||||
registering the function in `detail/WasmVM.cpp`:
|
||||
|
||||
```cpp
|
||||
WASM_IMPORT_FUNC2(i, getLedgerSqn, "get_ledger_sqn", hfs, 60);
|
||||
// ^^ gas cost
|
||||
```
|
||||
|
||||
WASM execution is metered, and if the gas limit is exceeded, execution fails.
|
||||
|
||||
## Entry Point
|
||||
|
||||
The WASM module must export a function with the name defined by
|
||||
`ESCROW_FUNCTION_NAME` (currently `"finish"`). This function:
|
||||
|
||||
- Takes no parameters (or parameters passed via host function calls)
|
||||
- Returns an `int32_t`:
|
||||
- `1` (or positive): Escrow conditions are met, allow finish
|
||||
- `0` (or negative): Escrow conditions are not met, reject finish
|
||||
|
||||
## Adding a New Host Function
|
||||
|
||||
To add a new host function, follow these steps:
|
||||
|
||||
### 1. Add to HostFunc.h (Base Class)
|
||||
|
||||
Add a virtual method declaration with a default implementation that returns an
|
||||
error:
|
||||
|
||||
```cpp
|
||||
virtual Expected<ReturnType, HostFunctionError>
|
||||
myNewFunction(ParamType1 param1, ParamType2 param2)
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Add to HostFuncImpl.h (Declaration)
|
||||
|
||||
Add the method override declaration in `WasmHostFunctionsImpl`:
|
||||
|
||||
```cpp
|
||||
Expected<ReturnType, HostFunctionError>
|
||||
myNewFunction(ParamType1 param1, ParamType2 param2) override;
|
||||
```
|
||||
|
||||
### 3. Implement in detail/HostFuncImpl\*.cpp
|
||||
|
||||
Add the implementation in the appropriate file:
|
||||
|
||||
```cpp
|
||||
Expected<ReturnType, HostFunctionError>
|
||||
WasmHostFunctionsImpl::myNewFunction(ParamType1 param1, ParamType2 param2)
|
||||
{
|
||||
// Implementation using ctx (ApplyContext) for ledger access
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Add Wrapper to HostFuncWrapper.h
|
||||
|
||||
Add the prototype and wrapper declaration:
|
||||
|
||||
```cpp
|
||||
using myNewFunction_proto = int32_t(uint8_t const*, int32_t, ...);
|
||||
wasm_trap_t*
|
||||
myNewFunction_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
```
|
||||
|
||||
### 5. Implement Wrapper in detail/HostFuncWrapper.cpp
|
||||
|
||||
Implement the C-style wrapper that bridges WASM to C++:
|
||||
|
||||
```cpp
|
||||
wasm_trap_t*
|
||||
myNewFunction_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results)
|
||||
{
|
||||
// Extract parameters from params
|
||||
// Call hfs->myNewFunction(...)
|
||||
// Set results and return
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Register in WasmVM.cpp
|
||||
|
||||
Add the function registration in `setCommonHostFunctions()` or
|
||||
`createWasmImport()`:
|
||||
|
||||
```cpp
|
||||
WASM_IMPORT_FUNC2(i, myNewFunction, "my_new_function", hfs, 100);
|
||||
// ^^ WASM name ^^ gas cost
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> New host functions MUST be amendment-gated in `WasmVM.cpp`.
|
||||
> Wrap the registration in an amendment check to ensure the function is only
|
||||
> available after the corresponding amendment is enabled on the network.
|
||||
@@ -1,211 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
using Bytes = std::vector<std::uint8_t>;
|
||||
using Hash = xrpl::uint256;
|
||||
using FloatPair = std::pair<int64_t, int32_t>;
|
||||
|
||||
enum class HostFunctionError : int32_t {
|
||||
Internal = -1,
|
||||
FieldNotFound = -2,
|
||||
BufferTooSmall = -3,
|
||||
NoArray = -4,
|
||||
NotLeafField = -5,
|
||||
LocatorMalformed = -6,
|
||||
SlotOutRange = -7,
|
||||
SlotsFull = -8,
|
||||
EmptySlot = -9,
|
||||
LedgerObjNotFound = -10,
|
||||
Decoding = -11,
|
||||
DataFieldTooLarge = -12,
|
||||
PointerOutOfBounds = -13,
|
||||
NoMemExported = -14,
|
||||
InvalidParams = -15,
|
||||
InvalidAccount = -16,
|
||||
InvalidField = -17,
|
||||
IndexOutOfBounds = -18,
|
||||
FloatInputMalformed = -19,
|
||||
FloatComputationError = -20,
|
||||
NoRuntime = -21,
|
||||
OutOfGas = -22,
|
||||
OutOfTransferLimit = -23,
|
||||
};
|
||||
|
||||
enum class WasmTypes { WtI32, WtI64 };
|
||||
|
||||
struct Wmem
|
||||
{
|
||||
std::uint8_t* p = nullptr;
|
||||
std::size_t s = 0;
|
||||
|
||||
Wmem() = default;
|
||||
Wmem(void* ptr, std::size_t size) : p(reinterpret_cast<std::uint8_t*>(ptr)), s(size)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct WasmResult
|
||||
{
|
||||
T result;
|
||||
int64_t cost;
|
||||
};
|
||||
using EscrowResult = WasmResult<int32_t>;
|
||||
|
||||
class FieldLocator
|
||||
{
|
||||
int32_t const* ptr_ = nullptr;
|
||||
uint32_t size_ = 0;
|
||||
std::vector<int32_t> buf_;
|
||||
|
||||
public:
|
||||
FieldLocator(std::vector<int32_t>&& buf)
|
||||
: ptr_(&buf[0]), size_(buf.size()), buf_(std::move(buf))
|
||||
{
|
||||
}
|
||||
|
||||
FieldLocator(int32_t const* ptr, uint32_t const size) : ptr_(ptr), size_(size)
|
||||
{
|
||||
}
|
||||
|
||||
FieldLocator(FieldLocator const&) = delete;
|
||||
FieldLocator&
|
||||
operator=(FieldLocator const&) = delete;
|
||||
FieldLocator(FieldLocator&&) = default;
|
||||
FieldLocator&
|
||||
operator=(FieldLocator&&) = default;
|
||||
|
||||
int32_t
|
||||
operator[](unsigned i) const
|
||||
{
|
||||
if (i >= size_)
|
||||
Throw<std::runtime_error>("index out of bounds");
|
||||
return ptr_[i];
|
||||
}
|
||||
|
||||
[[nodiscard]] uint32_t
|
||||
size() const
|
||||
{
|
||||
return size_;
|
||||
}
|
||||
|
||||
[[nodiscard]] int32_t const*
|
||||
data() const
|
||||
{
|
||||
return ptr_;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
empty() const
|
||||
{
|
||||
return size_ == 0;
|
||||
}
|
||||
};
|
||||
|
||||
class WasmRuntimeWrapper
|
||||
{
|
||||
public:
|
||||
virtual ~WasmRuntimeWrapper() = default;
|
||||
|
||||
virtual Wmem
|
||||
getMem() = 0;
|
||||
|
||||
virtual std::int64_t
|
||||
getGas() = 0;
|
||||
|
||||
virtual std::int64_t
|
||||
setGas(std::int64_t gas) = 0;
|
||||
};
|
||||
using RTOptRef = std::optional<std::reference_wrapper<WasmRuntimeWrapper>>;
|
||||
|
||||
struct WasmParam
|
||||
{
|
||||
// We are not supporting float/double
|
||||
|
||||
WasmTypes type = WasmTypes::WtI32;
|
||||
union
|
||||
{
|
||||
std::int32_t i32;
|
||||
std::int64_t i64 = 0;
|
||||
} of;
|
||||
};
|
||||
|
||||
template <class... Types>
|
||||
inline void
|
||||
wasmParamsHlp(std::vector<WasmParam>& v, std::int32_t p, Types&&... args)
|
||||
{
|
||||
v.push_back({.type = WasmTypes::WtI32, .of = {.i32 = p}});
|
||||
wasmParamsHlp(v, std::forward<Types>(args)...);
|
||||
}
|
||||
|
||||
template <class... Types>
|
||||
inline void
|
||||
wasmParamsHlp(std::vector<WasmParam>& v, std::int64_t p, Types&&... args)
|
||||
{
|
||||
v.push_back({.type = WasmTypes::WtI64, .of = {.i64 = p}});
|
||||
wasmParamsHlp(v, std::forward<Types>(args)...);
|
||||
}
|
||||
|
||||
inline void
|
||||
wasmParamsHlp(std::vector<WasmParam>& v)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
template <class... Types>
|
||||
inline std::vector<WasmParam>
|
||||
wasmParams(Types&&... args)
|
||||
{
|
||||
std::vector<WasmParam> v;
|
||||
v.reserve(sizeof...(args));
|
||||
wasmParamsHlp(v, std::forward<Types>(args)...);
|
||||
return v;
|
||||
}
|
||||
|
||||
template <typename T, size_t Size = sizeof(T)>
|
||||
constexpr T
|
||||
adjustWasmEndianessHlp(T x)
|
||||
{
|
||||
static_assert(std::is_integral_v<T>, "Only integral types");
|
||||
if constexpr (Size > 1)
|
||||
{
|
||||
using U = std::make_unsigned_t<T>;
|
||||
U u = static_cast<U>(x);
|
||||
U const low = (u & 0xFF) << ((Size - 1) << 3);
|
||||
u = adjustWasmEndianessHlp<U, Size - 1>(u >> 8);
|
||||
return static_cast<T>(low | u);
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
template <typename T, size_t Size = sizeof(T)>
|
||||
constexpr T
|
||||
adjustWasmEndianess(T x)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
static_assert(std::is_integral_v<T>, "Only integral types");
|
||||
if constexpr (std::endian::native == std::endian::big)
|
||||
{
|
||||
return adjustWasmEndianessHlp(x);
|
||||
}
|
||||
return x;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
constexpr int32_t
|
||||
hfErrorToInt(HostFunctionError e)
|
||||
{
|
||||
return static_cast<int32_t>(e);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,128 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/tx/wasm/HostFunc.h>
|
||||
|
||||
#include <boost/function_types/function_arity.hpp>
|
||||
#include <boost/function_types/parameter_types.hpp>
|
||||
#include <boost/function_types/result_type.hpp>
|
||||
#include <boost/mpl/vector.hpp>
|
||||
|
||||
#include <wasm.h>
|
||||
|
||||
namespace bft = boost::function_types;
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
using wasmSecondaryCbFuncType =
|
||||
wasm_trap_t*(HostFunctions&, wasm_val_vec_t const*, wasm_val_vec_t*);
|
||||
|
||||
struct WasmImportFunc
|
||||
{
|
||||
std::string_view name;
|
||||
std::optional<WasmTypes> result;
|
||||
std::vector<WasmTypes> params;
|
||||
|
||||
wasmSecondaryCbFuncType* wrap = nullptr;
|
||||
uint32_t gas = 0;
|
||||
};
|
||||
|
||||
using WasmUserData = std::pair<HFRef, WasmImportFunc>;
|
||||
// string - import function name
|
||||
using ImportVec = std::unordered_map<std::string_view, WasmUserData>;
|
||||
|
||||
template <int N, int C, typename Mpl>
|
||||
void
|
||||
WasmImpArgs(WasmImportFunc& e)
|
||||
{
|
||||
if constexpr (N < C)
|
||||
{
|
||||
using at = typename boost::mpl::at_c<Mpl, N>::type;
|
||||
if constexpr (std::is_pointer_v<at>)
|
||||
{
|
||||
e.params.push_back(WasmTypes::WtI32);
|
||||
}
|
||||
else if constexpr (std::is_same_v<at, std::int32_t>)
|
||||
{
|
||||
e.params.push_back(WasmTypes::WtI32);
|
||||
}
|
||||
else if constexpr (std::is_same_v<at, std::int64_t>)
|
||||
{
|
||||
e.params.push_back(WasmTypes::WtI64);
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(std::is_pointer_v<at>, "Unsupported argument type");
|
||||
}
|
||||
|
||||
return WasmImpArgs<N + 1, C, Mpl>(e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
template <typename>
|
||||
inline constexpr bool wasmDependentFalse = false;
|
||||
|
||||
template <typename Rt>
|
||||
void
|
||||
WasmImpRet(WasmImportFunc& e)
|
||||
{
|
||||
if constexpr (std::is_pointer_v<Rt>)
|
||||
{
|
||||
e.result = WasmTypes::WtI32;
|
||||
}
|
||||
else if constexpr (std::is_same_v<Rt, std::int32_t>)
|
||||
{
|
||||
e.result = WasmTypes::WtI32;
|
||||
}
|
||||
else if constexpr (std::is_same_v<Rt, std::int64_t>)
|
||||
{
|
||||
e.result = WasmTypes::WtI64;
|
||||
}
|
||||
else if constexpr (std::is_void_v<Rt>)
|
||||
{
|
||||
e.result.reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(wasmDependentFalse<Rt>, "Unsupported return type");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void
|
||||
WasmImpFuncHelper(WasmImportFunc& e)
|
||||
{
|
||||
using rt = typename bft::result_type<F>::type;
|
||||
using pt = typename bft::parameter_types<F>::type;
|
||||
// typename boost::mpl::at_c<mpl, N>::type
|
||||
|
||||
WasmImpRet<rt>(e);
|
||||
WasmImpArgs<0, bft::function_arity<F>::value, pt>(e);
|
||||
// WasmImpWrap(e, std::forward<F>(f));
|
||||
}
|
||||
|
||||
// imp_name - string literal, must have static lifetime
|
||||
template <typename F>
|
||||
void
|
||||
WasmImpFunc(
|
||||
ImportVec& v,
|
||||
std::string_view impName,
|
||||
wasmSecondaryCbFuncType* fWrap,
|
||||
HostFunctions& hf,
|
||||
uint32_t gas = 0)
|
||||
{
|
||||
WasmImportFunc e;
|
||||
e.name = impName;
|
||||
e.wrap = fWrap;
|
||||
e.gas = gas;
|
||||
WasmImpFuncHelper<F>(e);
|
||||
v.emplace(impName, std::make_pair(HFRef(hf), std::move(e)));
|
||||
}
|
||||
|
||||
#define WASM_IMPORT_FUNC(v, f, ...) WasmImpFunc<f##_proto>(v, #f, &f##_wrap, ##__VA_ARGS__)
|
||||
|
||||
// n - string literal name, must have static lifetime
|
||||
#define WASM_IMPORT_FUNC2(v, f, n, ...) WasmImpFunc<f##_proto>(v, n, &f##_wrap, ##__VA_ARGS__)
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,89 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/wasm/HostFunc.h>
|
||||
#include <xrpl/tx/wasm/WasmImportsHelper.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
std::string_view inline constexpr wEnv = "env";
|
||||
std::string_view inline constexpr wHostLib = "host_lib";
|
||||
std::string_view inline constexpr wMem = "memory";
|
||||
std::string_view inline constexpr wStore = "store";
|
||||
std::string_view inline constexpr wLoad = "load";
|
||||
std::string_view inline constexpr wSize = "size";
|
||||
std::string_view inline constexpr wAlloc = "allocate";
|
||||
std::string_view inline constexpr wDealloc = "deallocate";
|
||||
std::string_view inline constexpr wProcExit = "proc_exit";
|
||||
|
||||
std::string_view inline constexpr escrowFunctionName = "finish";
|
||||
|
||||
uint32_t inline constexpr maxPages = 128; // 8MB = 64KB*128
|
||||
|
||||
class WasmiEngine;
|
||||
|
||||
class WasmEngine
|
||||
{
|
||||
std::unique_ptr<WasmiEngine> const impl_;
|
||||
|
||||
WasmEngine();
|
||||
|
||||
public:
|
||||
WasmEngine(WasmEngine const&) = delete;
|
||||
WasmEngine(WasmEngine&&) = delete;
|
||||
WasmEngine&
|
||||
operator=(WasmEngine const&) = delete;
|
||||
WasmEngine&
|
||||
operator=(WasmEngine&&) = delete;
|
||||
|
||||
static WasmEngine&
|
||||
instance();
|
||||
|
||||
Expected<WasmResult<int32_t>, TER>
|
||||
run(Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
int64_t gasLimit,
|
||||
std::string_view funcName = {},
|
||||
std::vector<WasmParam> const& params = {},
|
||||
ImportVec const& imports = {},
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()});
|
||||
|
||||
NotTEC
|
||||
check(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params = {},
|
||||
ImportVec const& imports = {},
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()});
|
||||
|
||||
// Host functions helper functionality
|
||||
void*
|
||||
newTrap(std::string const& txt = std::string());
|
||||
|
||||
[[nodiscard]] beast::Journal
|
||||
getJournal() const;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ImportVec
|
||||
createWasmImport(HostFunctions& hfs);
|
||||
|
||||
Expected<EscrowResult, TER>
|
||||
runEscrowWasm(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
int64_t gasLimit,
|
||||
std::string_view funcName = escrowFunctionName,
|
||||
std::vector<WasmParam> const& params = {});
|
||||
|
||||
NotTEC
|
||||
preflightEscrowWasm(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
std::string_view funcName = escrowFunctionName,
|
||||
std::vector<WasmParam> const& params = {});
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,434 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/tx/wasm/WasmVM.h>
|
||||
|
||||
#include <wasm.h>
|
||||
#include <wasmi.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
template <class T, void (*Create)(T*, size_t), void (*Destroy)(T*)>
|
||||
class WasmVec
|
||||
{
|
||||
using TD = std::remove_pointer_t<decltype(T::data)>;
|
||||
T vec_;
|
||||
|
||||
public:
|
||||
WasmVec(size_t s = 0) : vec_ WASM_EMPTY_VEC
|
||||
{
|
||||
if (s > 0)
|
||||
Create(&vec_, s); // zeroes memory
|
||||
}
|
||||
|
||||
~WasmVec()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
WasmVec(WasmVec const&) = delete;
|
||||
WasmVec&
|
||||
operator=(WasmVec const&) = delete;
|
||||
|
||||
WasmVec(WasmVec&& other) noexcept : vec_ WASM_EMPTY_VEC
|
||||
{
|
||||
*this = std::move(other);
|
||||
}
|
||||
|
||||
WasmVec&
|
||||
operator=(WasmVec&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
clear();
|
||||
vec_ = other.vec_;
|
||||
other.vec_ = WASM_EMPTY_VEC;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void
|
||||
clear()
|
||||
{
|
||||
Destroy(&vec_); // call destructor for every elements too
|
||||
vec_ = WASM_EMPTY_VEC;
|
||||
}
|
||||
|
||||
T
|
||||
release()
|
||||
{
|
||||
T result = vec_;
|
||||
vec_ = WASM_EMPTY_VEC;
|
||||
return result;
|
||||
}
|
||||
|
||||
T*
|
||||
get()
|
||||
{
|
||||
return &vec_;
|
||||
}
|
||||
|
||||
[[nodiscard]] T const*
|
||||
get() const
|
||||
{
|
||||
return &vec_;
|
||||
}
|
||||
|
||||
TD&
|
||||
operator[](size_t i)
|
||||
{
|
||||
if (i >= vec_.size)
|
||||
Throw<std::runtime_error>("Out of bound");
|
||||
return vec_.data[i];
|
||||
}
|
||||
|
||||
TD const&
|
||||
operator[](size_t i) const
|
||||
{
|
||||
if (i >= vec_.size)
|
||||
Throw<std::runtime_error>("Out of bound");
|
||||
return vec_.data[i];
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t
|
||||
size() const
|
||||
{
|
||||
return vec_.size;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
empty() const
|
||||
{
|
||||
return vec_.size == 0u;
|
||||
}
|
||||
};
|
||||
|
||||
using WasmValtypeVec =
|
||||
WasmVec<wasm_valtype_vec_t, &wasm_valtype_vec_new_uninitialized, &wasm_valtype_vec_delete>;
|
||||
using WasmValVec = WasmVec<wasm_val_vec_t, &wasm_val_vec_new_uninitialized, &wasm_val_vec_delete>;
|
||||
using WasmExternVec =
|
||||
WasmVec<wasm_extern_vec_t, &wasm_extern_vec_new_uninitialized, &wasm_extern_vec_delete>;
|
||||
using WasmExporttypeVec = WasmVec<
|
||||
wasm_exporttype_vec_t,
|
||||
&wasm_exporttype_vec_new_uninitialized,
|
||||
&wasm_exporttype_vec_delete>;
|
||||
using WasmImporttypeVec = WasmVec<
|
||||
wasm_importtype_vec_t,
|
||||
&wasm_importtype_vec_new_uninitialized,
|
||||
&wasm_importtype_vec_delete>;
|
||||
|
||||
struct WasmiResult
|
||||
{
|
||||
WasmValVec r;
|
||||
bool f{false}; // failure flag
|
||||
|
||||
WasmiResult(unsigned n = 0) : r(n)
|
||||
{
|
||||
}
|
||||
|
||||
WasmiResult() = delete;
|
||||
~WasmiResult() = default;
|
||||
WasmiResult(WasmiResult&& o) = default;
|
||||
WasmiResult&
|
||||
operator=(WasmiResult&& o) = default;
|
||||
};
|
||||
|
||||
using ModulePtr = std::unique_ptr<wasm_module_t, decltype(&wasm_module_delete)>;
|
||||
using InstancePtr = std::unique_ptr<wasm_instance_t, decltype(&wasm_instance_delete)>;
|
||||
using EnginePtr = std::unique_ptr<wasm_engine_t, decltype(&wasm_engine_delete)>;
|
||||
using StorePtr = std::unique_ptr<wasm_store_t, decltype(&wasm_store_delete)>;
|
||||
|
||||
using FuncInfo = std::pair<wasm_func_t const*, wasm_functype_t const*>;
|
||||
|
||||
class InstanceWrapper
|
||||
{
|
||||
wasm_store_t* store_ = nullptr;
|
||||
WasmExternVec exports_;
|
||||
mutable int memIdx_ = -1;
|
||||
InstancePtr instance_;
|
||||
beast::Journal j_ = beast::Journal(beast::Journal::getNullSink());
|
||||
|
||||
private:
|
||||
static InstancePtr
|
||||
init(
|
||||
StorePtr& s,
|
||||
ModulePtr& m,
|
||||
WasmExternVec& expt,
|
||||
WasmExternVec const& imports,
|
||||
beast::Journal j);
|
||||
|
||||
public:
|
||||
InstanceWrapper() : instance_(nullptr, &wasm_instance_delete) {};
|
||||
|
||||
InstanceWrapper(InstanceWrapper const&) = delete;
|
||||
|
||||
InstanceWrapper(InstanceWrapper&& o) : instance_(nullptr, &wasm_instance_delete)
|
||||
{
|
||||
*this = std::move(o); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
InstanceWrapper(StorePtr& s, ModulePtr& m, WasmExternVec const& imports, beast::Journal j)
|
||||
: store_(s.get()), instance_(init(s, m, exports_, imports, j)), j_(j)
|
||||
{
|
||||
}
|
||||
|
||||
InstanceWrapper&
|
||||
operator=(InstanceWrapper&& o);
|
||||
|
||||
InstanceWrapper&
|
||||
operator=(InstanceWrapper const&) = delete;
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return static_cast<bool>(instance_);
|
||||
}
|
||||
|
||||
FuncInfo
|
||||
getFunc(std::string_view funcName, WasmExporttypeVec const& exportTypes) const;
|
||||
|
||||
Wmem
|
||||
getMem() const;
|
||||
|
||||
std::int64_t
|
||||
getGas() const;
|
||||
|
||||
std::int64_t
|
||||
setGas(std::int64_t) const;
|
||||
};
|
||||
|
||||
class ModuleWrapper
|
||||
{
|
||||
ModulePtr module_;
|
||||
InstanceWrapper instanceWrap_;
|
||||
WasmExporttypeVec exportTypes_;
|
||||
beast::Journal j_ = beast::Journal(beast::Journal::getNullSink());
|
||||
|
||||
public:
|
||||
// LCOV_EXCL_START
|
||||
ModuleWrapper() : module_(nullptr, &wasm_module_delete)
|
||||
{
|
||||
}
|
||||
|
||||
ModuleWrapper(ModuleWrapper&& o) : module_(nullptr, &wasm_module_delete)
|
||||
{
|
||||
*this = std::move(o);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
ModuleWrapper&
|
||||
operator=(ModuleWrapper&& o);
|
||||
ModuleWrapper(
|
||||
StorePtr& s,
|
||||
Bytes const& wasmBin,
|
||||
bool instantiate,
|
||||
ImportVec const& imports,
|
||||
beast::Journal j);
|
||||
~ModuleWrapper() = default;
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return instanceWrap_;
|
||||
}
|
||||
|
||||
FuncInfo
|
||||
getFunc(std::string_view funcName) const
|
||||
{
|
||||
return instanceWrap_.getFunc(funcName, exportTypes_);
|
||||
}
|
||||
|
||||
wasm_functype_t*
|
||||
getFuncType(std::string_view funcName) const;
|
||||
|
||||
Wmem
|
||||
getMem() const
|
||||
{
|
||||
return instanceWrap_.getMem();
|
||||
}
|
||||
|
||||
InstanceWrapper&
|
||||
getInstance(int i = 0)
|
||||
{
|
||||
return instanceWrap_;
|
||||
}
|
||||
|
||||
InstanceWrapper const&
|
||||
getInstance(int i = 0) const
|
||||
{
|
||||
return instanceWrap_;
|
||||
}
|
||||
|
||||
int
|
||||
addInstance(StorePtr& s, WasmExternVec const& imports)
|
||||
{
|
||||
instanceWrap_ = {s, module_, imports, j_};
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::int64_t
|
||||
getGas() const
|
||||
{
|
||||
return instanceWrap_ ? instanceWrap_.getGas() : -1;
|
||||
}
|
||||
|
||||
private:
|
||||
static ModulePtr
|
||||
init(StorePtr& s, Bytes const& wasmBin, beast::Journal j);
|
||||
|
||||
WasmExternVec
|
||||
buildImports(StorePtr& s, ImportVec const& imports) const;
|
||||
};
|
||||
|
||||
class WasmiEngine
|
||||
{
|
||||
EnginePtr engine_;
|
||||
StorePtr store_;
|
||||
std::unique_ptr<ModuleWrapper> moduleWrap_;
|
||||
beast::Journal j_ = beast::Journal(beast::Journal::getNullSink());
|
||||
|
||||
std::mutex m_; // 1 instance mutex
|
||||
|
||||
public:
|
||||
WasmiEngine() : engine_(init()), store_(nullptr, &wasm_store_delete)
|
||||
{
|
||||
}
|
||||
|
||||
~WasmiEngine() = default;
|
||||
|
||||
static EnginePtr
|
||||
init();
|
||||
|
||||
Expected<WasmResult<int32_t>, TER>
|
||||
run(Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
int64_t gas,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
ImportVec const& imports,
|
||||
beast::Journal j);
|
||||
|
||||
NotTEC
|
||||
check(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
ImportVec const& imports,
|
||||
beast::Journal j);
|
||||
|
||||
[[nodiscard]] std::int64_t
|
||||
getGas() const
|
||||
{
|
||||
return moduleWrap_ ? moduleWrap_->getGas() : -1; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// Host functions helper functionality
|
||||
wasm_trap_t*
|
||||
newTrap(std::string const& msg);
|
||||
|
||||
// LCOV_EXCL_START
|
||||
[[nodiscard]] beast::Journal
|
||||
getJournal() const
|
||||
{
|
||||
return j_;
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
private:
|
||||
[[nodiscard]] InstanceWrapper&
|
||||
getRT(int m = 0, int i = 0) const
|
||||
{
|
||||
if (!moduleWrap_)
|
||||
Throw<std::runtime_error>("no module");
|
||||
return moduleWrap_->getInstance(i);
|
||||
}
|
||||
|
||||
[[nodiscard]] Wmem
|
||||
getMem() const
|
||||
{
|
||||
return moduleWrap_ ? moduleWrap_->getMem() : Wmem();
|
||||
}
|
||||
|
||||
Expected<WasmResult<int32_t>, TER>
|
||||
runHlp(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
int64_t gas,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
ImportVec const& imports,
|
||||
beast::Journal j);
|
||||
|
||||
NotTEC
|
||||
checkHlp(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
ImportVec const& imports,
|
||||
beast::Journal j);
|
||||
|
||||
int
|
||||
addModule(Bytes const& wasmCode, bool instantiate, ImportVec const& imports, int64_t gas);
|
||||
void
|
||||
clearModules();
|
||||
|
||||
// int addInstance();
|
||||
|
||||
int32_t
|
||||
runFunc(std::string_view const funcName, int32_t p);
|
||||
|
||||
int32_t
|
||||
makeModule(Bytes const& wasmCode, WasmExternVec const& imports = {});
|
||||
|
||||
[[nodiscard]] FuncInfo
|
||||
getFunc(std::string_view funcName) const
|
||||
{
|
||||
return moduleWrap_->getFunc(funcName);
|
||||
}
|
||||
|
||||
static std::vector<wasm_val_t>
|
||||
convertParams(std::vector<WasmParam> const& params);
|
||||
|
||||
static int
|
||||
compareParamTypes(wasm_valtype_vec_t const* ftp, std::vector<wasm_val_t> const& p);
|
||||
|
||||
static void
|
||||
addParam(std::vector<wasm_val_t>& in, int32_t p);
|
||||
static void
|
||||
addParam(std::vector<wasm_val_t>& in, int64_t p);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(std::string_view func, Types&&... args);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(FuncInfo const& f, Types&&... args);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(FuncInfo const& f, std::vector<wasm_val_t>& in);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(FuncInfo const& f, std::vector<wasm_val_t>& in, std::int32_t p, Types&&... args);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(FuncInfo const& f, std::vector<wasm_val_t>& in, std::int64_t p, Types&&... args);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(
|
||||
FuncInfo const& f,
|
||||
std::vector<wasm_val_t>& in,
|
||||
uint8_t const* d,
|
||||
int32_t sz,
|
||||
Types&&... args);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(FuncInfo const& f, std::vector<wasm_val_t>& in, Bytes const& p, Types&&... args);
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -108,7 +108,7 @@ public:
|
||||
|
||||
beast::Journal journal;
|
||||
|
||||
boost::asio::io_context& io_context;
|
||||
boost::asio::io_context& ioContext;
|
||||
boost::asio::strand<boost::asio::io_context::executor_type> strand;
|
||||
boost::asio::ip::tcp::resolver resolver;
|
||||
|
||||
@@ -116,7 +116,7 @@ public:
|
||||
std::mutex mut;
|
||||
bool asyncHandlersCompleted{true};
|
||||
|
||||
std::atomic<bool> stop_called;
|
||||
std::atomic<bool> stopCalled;
|
||||
std::atomic<bool> stopped;
|
||||
|
||||
// Represents a unit of work for the resolver to do
|
||||
@@ -138,10 +138,10 @@ public:
|
||||
|
||||
ResolverAsioImpl(boost::asio::io_context& ioContext, beast::Journal journal)
|
||||
: journal(journal)
|
||||
, io_context(ioContext)
|
||||
, ioContext(ioContext)
|
||||
, strand(boost::asio::make_strand(ioContext))
|
||||
, resolver(ioContext)
|
||||
, stop_called(false)
|
||||
, stopCalled(false)
|
||||
, stopped(true)
|
||||
{
|
||||
}
|
||||
@@ -172,7 +172,7 @@ public:
|
||||
start() override
|
||||
{
|
||||
XRPL_ASSERT(stopped == true, "xrpl::ResolverAsioImpl::start : stopped");
|
||||
XRPL_ASSERT(stop_called == false, "xrpl::ResolverAsioImpl::start : not stopping");
|
||||
XRPL_ASSERT(stopCalled == false, "xrpl::ResolverAsioImpl::start : not stopping");
|
||||
|
||||
if (stopped.exchange(false))
|
||||
{
|
||||
@@ -187,10 +187,10 @@ public:
|
||||
void
|
||||
stopAsync() override
|
||||
{
|
||||
if (!stop_called.exchange(true))
|
||||
if (!stopCalled.exchange(true))
|
||||
{
|
||||
boost::asio::dispatch(
|
||||
io_context,
|
||||
ioContext,
|
||||
boost::asio::bind_executor(
|
||||
strand, std::bind(&ResolverAsioImpl::doStop, this, CompletionCounter(this))));
|
||||
|
||||
@@ -213,13 +213,13 @@ public:
|
||||
void
|
||||
resolve(std::vector<std::string> const& names, HandlerType const& handler) override
|
||||
{
|
||||
XRPL_ASSERT(stop_called == false, "xrpl::ResolverAsioImpl::resolve : not stopping");
|
||||
XRPL_ASSERT(stopCalled == false, "xrpl::ResolverAsioImpl::resolve : not stopping");
|
||||
XRPL_ASSERT(!names.empty(), "xrpl::ResolverAsioImpl::resolve : names non-empty");
|
||||
|
||||
// TODO NIKB use rvalue references to construct and move
|
||||
// reducing cost.
|
||||
boost::asio::dispatch(
|
||||
io_context,
|
||||
ioContext,
|
||||
boost::asio::bind_executor(
|
||||
strand,
|
||||
std::bind(
|
||||
@@ -231,7 +231,7 @@ public:
|
||||
void
|
||||
doStop(CompletionCounter)
|
||||
{
|
||||
XRPL_ASSERT(stop_called == true, "xrpl::ResolverAsioImpl::doStop : stopping");
|
||||
XRPL_ASSERT(stopCalled == true, "xrpl::ResolverAsioImpl::doStop : stopping");
|
||||
|
||||
if (!stopped.exchange(true))
|
||||
{
|
||||
@@ -270,7 +270,7 @@ public:
|
||||
handler(name, addresses);
|
||||
|
||||
boost::asio::post(
|
||||
io_context,
|
||||
ioContext,
|
||||
boost::asio::bind_executor(
|
||||
strand, std::bind(&ResolverAsioImpl::doWork, this, CompletionCounter(this))));
|
||||
}
|
||||
@@ -324,7 +324,7 @@ public:
|
||||
void
|
||||
doWork(CompletionCounter)
|
||||
{
|
||||
if (stop_called)
|
||||
if (stopCalled)
|
||||
return;
|
||||
|
||||
// We don't have any work to do at this time
|
||||
@@ -346,7 +346,7 @@ public:
|
||||
JLOG(journal.error()) << "Unable to parse '" << name << "'";
|
||||
|
||||
boost::asio::post(
|
||||
io_context,
|
||||
ioContext,
|
||||
boost::asio::bind_executor(
|
||||
strand, std::bind(&ResolverAsioImpl::doWork, this, CompletionCounter(this))));
|
||||
|
||||
@@ -371,7 +371,7 @@ public:
|
||||
{
|
||||
XRPL_ASSERT(!names.empty(), "xrpl::ResolverAsioImpl::doResolve : names non-empty");
|
||||
|
||||
if (!stop_called)
|
||||
if (!stopCalled)
|
||||
{
|
||||
work.emplace_back(names, handler);
|
||||
|
||||
@@ -381,7 +381,7 @@ public:
|
||||
if (!work.empty())
|
||||
{
|
||||
boost::asio::post(
|
||||
io_context,
|
||||
ioContext,
|
||||
boost::asio::bind_executor(
|
||||
strand,
|
||||
std::bind(&ResolverAsioImpl::doWork, this, CompletionCounter(this))));
|
||||
|
||||
@@ -164,7 +164,7 @@ public:
|
||||
private:
|
||||
std::shared_ptr<StatsDCollectorImp> impl_;
|
||||
std::string name_;
|
||||
GaugeImpl::value_type last_value_{0};
|
||||
GaugeImpl::value_type lastValue_{0};
|
||||
GaugeImpl::value_type value_{0};
|
||||
bool dirty_{false};
|
||||
};
|
||||
@@ -209,7 +209,7 @@ private:
|
||||
Journal journal_;
|
||||
IP::Endpoint address_;
|
||||
std::string prefix_;
|
||||
boost::asio::io_context io_context_;
|
||||
boost::asio::io_context ioContext_;
|
||||
std::optional<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_;
|
||||
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
|
||||
boost::asio::basic_waitable_timer<std::chrono::steady_clock> timer_;
|
||||
@@ -232,10 +232,10 @@ public:
|
||||
: journal_(journal)
|
||||
, address_(std::move(address))
|
||||
, prefix_(std::move(prefix))
|
||||
, work_(boost::asio::make_work_guard(io_context_))
|
||||
, strand_(boost::asio::make_strand(io_context_))
|
||||
, timer_(io_context_)
|
||||
, socket_(io_context_)
|
||||
, work_(boost::asio::make_work_guard(ioContext_))
|
||||
, strand_(boost::asio::make_strand(ioContext_))
|
||||
, timer_(ioContext_)
|
||||
, socket_(ioContext_)
|
||||
, thread_(&StatsDCollectorImp::run, this)
|
||||
{
|
||||
}
|
||||
@@ -306,7 +306,7 @@ public:
|
||||
boost::asio::io_context&
|
||||
getIoContext()
|
||||
{
|
||||
return io_context_;
|
||||
return ioContext_;
|
||||
}
|
||||
|
||||
std::string const&
|
||||
@@ -325,7 +325,7 @@ public:
|
||||
postBuffer(std::string&& buffer)
|
||||
{
|
||||
boost::asio::dispatch(
|
||||
io_context_,
|
||||
ioContext_,
|
||||
boost::asio::bind_executor(
|
||||
strand_, std::bind(&StatsDCollectorImp::doPostBuffer, this, std::move(buffer))));
|
||||
}
|
||||
@@ -465,14 +465,14 @@ public:
|
||||
|
||||
setTimer();
|
||||
|
||||
io_context_.run();
|
||||
ioContext_.run();
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-unused-return-value)
|
||||
socket_.shutdown(boost::asio::ip::udp::socket::shutdown_send, ec);
|
||||
|
||||
socket_.close();
|
||||
|
||||
io_context_.poll();
|
||||
ioContext_.poll();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -628,9 +628,9 @@ StatsDGaugeImpl::doSet(GaugeImpl::value_type value)
|
||||
{
|
||||
value_ = value;
|
||||
|
||||
if (value_ != last_value_)
|
||||
if (value_ != lastValue_)
|
||||
{
|
||||
last_value_ = value_;
|
||||
lastValue_ = value_;
|
||||
dirty_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ Job::Job(
|
||||
std::uint64_t index,
|
||||
LoadMonitor& lm,
|
||||
std::function<void()> const& job)
|
||||
: type_(type), jobIndex_(index), job_(job), name_(name), queue_time_(clock_type::now())
|
||||
: type_(type), jobIndex_(index), job_(job), name_(name), queueTime_(clock_type::now())
|
||||
{
|
||||
loadEvent_ = std::make_shared<LoadEvent>(std::ref(lm), name, false);
|
||||
}
|
||||
@@ -39,7 +39,7 @@ Job::getType() const
|
||||
Job::clock_type::time_point const&
|
||||
Job::queueTime() const
|
||||
{
|
||||
return queue_time_;
|
||||
return queueTime_;
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -36,7 +36,7 @@ JobQueue::JobQueue(
|
||||
JLOG(journal_.info()) << "Using " << threadCount << " threads";
|
||||
|
||||
hook_ = collector_->makeHook(std::bind(&JobQueue::collect, this));
|
||||
job_count_ = collector_->makeGauge("job_count");
|
||||
jobCount_ = collector_->makeGauge("job_count");
|
||||
|
||||
{
|
||||
std::scoped_lock const lock(mutex_);
|
||||
@@ -66,7 +66,7 @@ void
|
||||
JobQueue::collect()
|
||||
{
|
||||
std::scoped_lock const lock(mutex_);
|
||||
job_count_ = jobSet_.size();
|
||||
jobCount_ = jobSet_.size();
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@@ -14,8 +14,8 @@ namespace xrpl {
|
||||
BookDirs::BookDirs(ReadView const& view, Book const& book)
|
||||
: view_(&view)
|
||||
, root_(keylet::page(getBookBase(book)).key)
|
||||
, next_quality_(getQualityNext(root_))
|
||||
, key_(view_->succ(root_, next_quality_).value_or(beast::kZero))
|
||||
, nextQuality_(getQualityNext(root_))
|
||||
, key_(view_->succ(root_, nextQuality_).value_or(beast::kZero))
|
||||
{
|
||||
XRPL_ASSERT(root_ != beast::kZero, "xrpl::BookDirs::BookDirs : nonzero root");
|
||||
if (key_ != beast::kZero)
|
||||
@@ -35,7 +35,7 @@ BookDirs::begin() const -> BookDirs::const_iterator
|
||||
auto it = BookDirs::const_iterator(*view_, root_, key_);
|
||||
if (key_ != beast::kZero)
|
||||
{
|
||||
it.next_quality_ = next_quality_;
|
||||
it.nextQuality_ = nextQuality_;
|
||||
it.sle_ = sle_;
|
||||
it.entry_ = entry_;
|
||||
it.index_ = index_;
|
||||
@@ -59,7 +59,7 @@ BookDirs::const_iterator::operator==(BookDirs::const_iterator const& other) cons
|
||||
view_ == other.view_ && root_ == other.root_,
|
||||
"xrpl::BookDirs::const_iterator::operator== : views and roots are "
|
||||
"matching");
|
||||
return entry_ == other.entry_ && cur_key_ == other.cur_key_ && index_ == other.index_;
|
||||
return entry_ == other.entry_ && curKey_ == other.curKey_ && index_ == other.index_;
|
||||
}
|
||||
|
||||
BookDirs::const_iterator::reference
|
||||
@@ -78,18 +78,18 @@ BookDirs::const_iterator::operator++()
|
||||
using beast::kZero;
|
||||
|
||||
XRPL_ASSERT(index_ != kZero, "xrpl::BookDirs::const_iterator::operator++ : nonzero index");
|
||||
if (!cdirNext(*view_, cur_key_, sle_, entry_, index_))
|
||||
if (!cdirNext(*view_, curKey_, sle_, entry_, index_))
|
||||
{
|
||||
if (index_ == 0)
|
||||
cur_key_ = view_->succ(++cur_key_, next_quality_).value_or(kZero);
|
||||
curKey_ = view_->succ(++curKey_, nextQuality_).value_or(kZero);
|
||||
|
||||
if (index_ != 0 || cur_key_ == kZero)
|
||||
if (index_ != 0 || curKey_ == kZero)
|
||||
{
|
||||
cur_key_ = key_;
|
||||
curKey_ = key_;
|
||||
entry_ = 0;
|
||||
index_ = kZero;
|
||||
}
|
||||
else if (!cdirFirst(*view_, cur_key_, sle_, entry_, index_))
|
||||
else if (!cdirFirst(*view_, curKey_, sle_, entry_, index_))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::BookDirs::const_iterator::operator++ : directory is empty");
|
||||
|
||||
@@ -78,9 +78,9 @@ public:
|
||||
OpenView::OpenView(OpenView const& rhs)
|
||||
: ReadView(rhs)
|
||||
, TxsRawView(rhs)
|
||||
, monotonic_resource_{std::make_unique<boost::container::pmr::monotonic_buffer_resource>(
|
||||
, monotonicResource_{std::make_unique<boost::container::pmr::monotonic_buffer_resource>(
|
||||
kInitialBufferSize)}
|
||||
, txs_{rhs.txs_, monotonic_resource_.get()}
|
||||
, txs_{rhs.txs_, monotonicResource_.get()}
|
||||
, rules_{rhs.rules_}
|
||||
, header_{rhs.header_}
|
||||
, base_{rhs.base_}
|
||||
@@ -89,9 +89,9 @@ OpenView::OpenView(OpenView const& rhs)
|
||||
, open_{rhs.open_} {};
|
||||
|
||||
OpenView::OpenView(OpenLedgerT, ReadView const* base, Rules rules, std::shared_ptr<void const> hold)
|
||||
: monotonic_resource_{
|
||||
: monotonicResource_{
|
||||
std::make_unique<boost::container::pmr::monotonic_buffer_resource>(kInitialBufferSize)}
|
||||
, txs_{monotonic_resource_.get()}
|
||||
, txs_{monotonicResource_.get()}
|
||||
, rules_(std::move(rules))
|
||||
, header_(base->header())
|
||||
, base_(base)
|
||||
@@ -105,9 +105,9 @@ OpenView::OpenView(OpenLedgerT, ReadView const* base, Rules rules, std::shared_p
|
||||
}
|
||||
|
||||
OpenView::OpenView(ReadView const* base, std::shared_ptr<void const> hold)
|
||||
: monotonic_resource_{
|
||||
: monotonicResource_{
|
||||
std::make_unique<boost::container::pmr::monotonic_buffer_resource>(kInitialBufferSize)}
|
||||
, txs_{monotonic_resource_.get()}
|
||||
, txs_{monotonicResource_.get()}
|
||||
, rules_(base->rules())
|
||||
, header_(base->header())
|
||||
, base_(base)
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
@@ -57,19 +58,45 @@ isVaultPseudoAccountFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
MPTIssue const& mptShare,
|
||||
int depth)
|
||||
std::uint8_t depth)
|
||||
{
|
||||
if (!view.rules().enabled(featureSingleAssetVault))
|
||||
return false;
|
||||
|
||||
if (depth >= kMaxAssetCheckDepth)
|
||||
return true; // LCOV_EXCL_LINE
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::View::isVaultPseudoAccountFrozen : reached asset check depth");
|
||||
return true;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const mptIssuance = view.read(keylet::mptIssuance(mptShare.getMptID()));
|
||||
if (mptIssuance == nullptr)
|
||||
return false; // zero MPToken won't block deletion of MPTokenIssuance
|
||||
|
||||
auto const issuer = mptIssuance->getAccountID(sfIssuer);
|
||||
|
||||
// Post-fixCleanup3_2_0: vault shares carry sfReferenceHolding pointing
|
||||
// to the vault pseudo's MPToken or RippleState for the underlying.
|
||||
// Read it to derive the underlying asset and recurse, skipping the
|
||||
// issuer-account-then-vault chain. Pre-amendment shares (no field)
|
||||
// fall back to the chain lookup below.
|
||||
if (mptIssuance->isFieldPresent(sfReferenceHolding))
|
||||
{
|
||||
auto const sleHolding =
|
||||
view.read(keylet::unchecked(mptIssuance->getFieldH256(sfReferenceHolding)));
|
||||
if (!sleHolding)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::isVaultPseudoAccountFrozen : dangling sfReferenceHolding");
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
return isAnyFrozen(
|
||||
view, {issuer, account}, assetOfHolding(*mptIssuance, *sleHolding), depth + 1);
|
||||
}
|
||||
|
||||
auto const mptIssuer = view.read(keylet::account(issuer));
|
||||
if (mptIssuer == nullptr)
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -24,10 +25,42 @@
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
[[nodiscard]] TER
|
||||
canApplyToBrokerCover(
|
||||
ReadView const& view,
|
||||
SLE::const_ref sleBroker,
|
||||
Asset const& vaultAsset,
|
||||
STAmount const& amount,
|
||||
beast::Journal j,
|
||||
std::string_view logPrefix)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
sleBroker && sleBroker->getType() == ltLOAN_BROKER,
|
||||
"xrpl::canApplyToBrokerCover : valid LoanBroker sle");
|
||||
XRPL_ASSERT(vaultAsset == amount.asset(), "xrpl::canApplyToBrokerCover : valid asset");
|
||||
|
||||
if (!view.rules().enabled(fixCleanup3_2_0))
|
||||
return tesSUCCESS;
|
||||
|
||||
if (amount == beast::kZero)
|
||||
return tecPRECISION_LOSS;
|
||||
|
||||
int const coverScale = scale(sleBroker->at(sfCoverAvailable), vaultAsset);
|
||||
if (amount.isZeroAtScale(coverScale))
|
||||
{
|
||||
JLOG(j.warn()) << logPrefix << ": amount " << amount.getFullText()
|
||||
<< " rounds to zero at cover scale " << coverScale;
|
||||
return tecPRECISION_LOSS;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
bool
|
||||
checkLendingProtocolDependencies(Rules const& rules, STTx const& tx)
|
||||
{
|
||||
@@ -1058,11 +1091,22 @@ computePaymentComponents(
|
||||
rules, periodicPayment, periodicRate, paymentRemaining - 1, managementFeeRate);
|
||||
|
||||
// Round the target to the loan's scale to match how actual loan values
|
||||
// are stored.
|
||||
// are stored. With fixCleanup3_2_0 enabled, principal is rounded upward
|
||||
// and interest downward so that at coarse scale principal sticks at the
|
||||
// floor (until the final payment clears it) while interest absorbs each
|
||||
// periodic payment. Without the amendment the pre-existing round-to-
|
||||
// nearest behavior is preserved (which can hit the "Partial principal
|
||||
// payment" assertion on degenerate integer-scale loans).
|
||||
bool const fixCleanup320Enabled = rules.enabled(fixCleanup3_2_0);
|
||||
Number::RoundingMode const principalRounding =
|
||||
fixCleanup320Enabled ? Number::RoundingMode::Upward : Number::getround();
|
||||
Number::RoundingMode const interestRounding =
|
||||
fixCleanup320Enabled ? Number::RoundingMode::Downward : Number::getround();
|
||||
LoanState const roundedTarget = LoanState{
|
||||
.valueOutstanding = roundToAsset(asset, trueTarget.valueOutstanding, scale),
|
||||
.principalOutstanding = roundToAsset(asset, trueTarget.principalOutstanding, scale),
|
||||
.interestDue = roundToAsset(asset, trueTarget.interestDue, scale),
|
||||
.principalOutstanding =
|
||||
roundToAsset(asset, trueTarget.principalOutstanding, scale, principalRounding),
|
||||
.interestDue = roundToAsset(asset, trueTarget.interestDue, scale, interestRounding),
|
||||
.managementFeeDue = roundToAsset(asset, trueTarget.managementFeeDue, scale)};
|
||||
|
||||
// Get the current actual loan state from the ledger values
|
||||
|
||||
@@ -55,7 +55,11 @@ isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue cons
|
||||
}
|
||||
|
||||
bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth)
|
||||
isFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
MPTIssue const& mptIssue,
|
||||
std::uint8_t depth)
|
||||
{
|
||||
return isGlobalFrozen(view, mptIssue) || isIndividualFrozen(view, account, mptIssue) ||
|
||||
isVaultPseudoAccountFrozen(view, account, mptIssue, depth);
|
||||
@@ -66,7 +70,7 @@ isAnyFrozen(
|
||||
ReadView const& view,
|
||||
std::initializer_list<AccountID> const& accounts,
|
||||
MPTIssue const& mptIssue,
|
||||
int depth)
|
||||
std::uint8_t depth)
|
||||
{
|
||||
if (isGlobalFrozen(view, mptIssue))
|
||||
return true;
|
||||
@@ -303,7 +307,7 @@ requireAuth(
|
||||
MPTIssue const& mptIssue,
|
||||
AccountID const& account,
|
||||
AuthType authType,
|
||||
int depth)
|
||||
std::uint8_t depth)
|
||||
{
|
||||
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
|
||||
auto const sleIssuance = view.read(mptID);
|
||||
@@ -321,7 +325,12 @@ requireAuth(
|
||||
if (featureSAVEnabled)
|
||||
{
|
||||
if (depth >= kMaxAssetCheckDepth)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::MPTokenHelpers::requireAuth : reached asset check depth");
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// requireAuth is recursive if the issuer is a vault pseudo-account
|
||||
auto const sleIssuer = view.read(keylet::account(mptIssuer));
|
||||
@@ -489,28 +498,88 @@ enforceMPTokenAuthorization(
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
[[nodiscard]] Asset
|
||||
assetOfHolding(SLE const& sleShareIssuance, SLE const& sleHolding)
|
||||
{
|
||||
XRPL_ASSERT_PARTS(
|
||||
sleHolding.getType() == ltRIPPLE_STATE || sleHolding.getType() == ltMPTOKEN,
|
||||
"xrpl::assetOfHolding",
|
||||
"unexpected holding type");
|
||||
XRPL_ASSERT_PARTS(
|
||||
sleShareIssuance.getType() == ltMPTOKEN_ISSUANCE,
|
||||
"xrpl::assetOfHolding",
|
||||
"not SLE MPTokenIssuance");
|
||||
|
||||
if (sleHolding.getType() == ltMPTOKEN)
|
||||
return MPTIssue{sleHolding.getFieldH192(sfMPTokenIssuanceID)};
|
||||
|
||||
auto const vaultPseudo = sleShareIssuance.at(sfIssuer);
|
||||
auto const lowLimit = sleHolding.getFieldAmount(sfLowLimit);
|
||||
auto const highLimit = sleHolding.getFieldAmount(sfHighLimit);
|
||||
auto const& iouIssuer =
|
||||
(lowLimit.getIssuer() != vaultPseudo) ? lowLimit.getIssuer() : highLimit.getIssuer();
|
||||
return Issue{lowLimit.get<Issue>().currency, iouIssuer};
|
||||
}
|
||||
|
||||
TER
|
||||
canTransfer(
|
||||
ReadView const& view,
|
||||
MPTIssue const& mptIssue,
|
||||
AccountID const& from,
|
||||
AccountID const& to)
|
||||
AccountID const& to,
|
||||
WaiveMPTCanTransfer waive,
|
||||
std::uint8_t depth)
|
||||
{
|
||||
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
|
||||
auto const sleIssuance = view.read(mptID);
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
auto const issuer = (*sleIssuance)[sfIssuer];
|
||||
if (waive == WaiveMPTCanTransfer::Yes || from == issuer || to == issuer)
|
||||
return tesSUCCESS;
|
||||
|
||||
if (!sleIssuance->isFlag(lsfMPTCanTransfer))
|
||||
return TER{tecNO_AUTH};
|
||||
|
||||
// Post-fixCleanup3_2_0: vault shares carry sfReferenceHolding pointing
|
||||
// to the vault pseudo's MPToken or RippleState for the underlying asset.
|
||||
// Third-party transfers inherit the underlying's transferability.
|
||||
// Issuer-involving transfers and waived callers returned tesSUCCESS above.
|
||||
//
|
||||
// The recursive call always passes WaiveMPTCanTransfer::No so that
|
||||
// a waived outer caller does not transitively unlock the underlying.
|
||||
if (view.rules().enabled(fixCleanup3_2_0) && sleIssuance->isFieldPresent(sfReferenceHolding))
|
||||
{
|
||||
if (from != (*sleIssuance)[sfIssuer] && to != (*sleIssuance)[sfIssuer])
|
||||
return TER{tecNO_AUTH};
|
||||
// Defensive depth bound on the inheritance recursion. Unreachable
|
||||
// in practice (vault-of-vault-shares is forbidden at VaultCreate).
|
||||
if (depth >= kMaxAssetCheckDepth)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::MPTokenHelpers::canTransfer : reached asset check depth");
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const sleHolding =
|
||||
view.read(keylet::unchecked(sleIssuance->getFieldH256(sfReferenceHolding)));
|
||||
if (!sleHolding)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
return canTransfer(
|
||||
view,
|
||||
assetOfHolding(*sleIssuance, *sleHolding),
|
||||
from,
|
||||
to,
|
||||
WaiveMPTCanTransfer::No,
|
||||
depth + 1);
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
canTrade(ReadView const& view, Asset const& asset)
|
||||
canTrade(ReadView const& view, Asset const& asset, std::uint8_t depth)
|
||||
{
|
||||
return asset.visit(
|
||||
[&](Issue const&) -> TER { return tesSUCCESS; },
|
||||
@@ -520,6 +589,31 @@ canTrade(ReadView const& view, Asset const& asset)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
if (!sleIssuance->isFlag(lsfMPTCanTrade))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Post-fixCleanup3_2_0: vault shares inherit the underlying
|
||||
// asset's tradability. A share whose underlying has been
|
||||
// removed from trading cannot itself be placed on the DEX.
|
||||
if (view.rules().enabled(fixCleanup3_2_0) &&
|
||||
sleIssuance->isFieldPresent(sfReferenceHolding))
|
||||
{
|
||||
// Defensive depth bound on the inheritance recursion.
|
||||
// Unreachable in practice (vault-of-vault-shares
|
||||
// forbidden at VaultCreate).
|
||||
if (depth >= kMaxAssetCheckDepth)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::MPTokenHelpers::canTrade : reached asset check depth");
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
auto const sleHolding =
|
||||
view.read(keylet::unchecked(sleIssuance->getFieldH256(sfReferenceHolding)));
|
||||
if (!sleHolding)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
return canTrade(view, assetOfHolding(*sleIssuance, *sleHolding), depth + 1);
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
});
|
||||
}
|
||||
@@ -892,7 +986,12 @@ issuerSelfDebitHookMPT(ApplyView& view, MPTIssue const& issue, std::uint64_t amo
|
||||
}
|
||||
|
||||
static TER
|
||||
checkMPTAllowed(ReadView const& view, TxType txType, Asset const& asset, AccountID const& accountID)
|
||||
checkMPTAllowed(
|
||||
ReadView const& view,
|
||||
TxType txType,
|
||||
Asset const& asset,
|
||||
AccountID const& accountID,
|
||||
std::uint8_t depth = 0)
|
||||
{
|
||||
if (!asset.holds<MPTIssue>())
|
||||
return tesSUCCESS;
|
||||
@@ -914,17 +1013,15 @@ checkMPTAllowed(ReadView const& view, TxType txType, Asset const& asset, Account
|
||||
if (!issuanceSle)
|
||||
return tecOBJECT_NOT_FOUND; // LCOV_EXCL_LINE
|
||||
|
||||
auto const flags = issuanceSle->getFlags();
|
||||
|
||||
if ((flags & lsfMPTLocked) != 0u)
|
||||
if (issuanceSle->isFlag(lsfMPTLocked))
|
||||
return tecLOCKED; // LCOV_EXCL_LINE
|
||||
// Offer crossing and Payment
|
||||
if ((flags & lsfMPTCanTrade) == 0)
|
||||
if (!issuanceSle->isFlag(lsfMPTCanTrade))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (accountID != issuer)
|
||||
{
|
||||
if ((flags & lsfMPTCanTransfer) == 0)
|
||||
if (!issuanceSle->isFlag(lsfMPTCanTransfer))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
auto const mptSle = view.read(keylet::mptoken(issuanceKey.key, accountID));
|
||||
@@ -935,6 +1032,34 @@ checkMPTAllowed(ReadView const& view, TxType txType, Asset const& asset, Account
|
||||
|
||||
if (mptSle->isFlag(lsfMPTLocked))
|
||||
return tecLOCKED;
|
||||
|
||||
// Post-fixCleanup3_2_0: vault shares inherit the underlying
|
||||
// asset's checks here too. Without this, a share could be
|
||||
// placed on the AMM, in an Offer, or in a Check even after
|
||||
// the issuer has restricted the underlying. Mirrors the
|
||||
// canTransfer / canTrade inheritance for path-find-adjacent
|
||||
// operations that don't go through canTransfer directly.
|
||||
if (view.rules().enabled(fixCleanup3_2_0) &&
|
||||
issuanceSle->isFieldPresent(sfReferenceHolding))
|
||||
{
|
||||
// Defensive depth bound on the inheritance recursion.
|
||||
// Reachable only post-fixCleanup3_2_0 and unreachable in
|
||||
// practice (vault-of-vault-shares forbidden at VaultCreate).
|
||||
if (depth >= kMaxAssetCheckDepth)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::MPTokenHelpers::checkMPTAllowed : reached asset check depth");
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
auto const sleHolding =
|
||||
view.read(keylet::unchecked(issuanceSle->getFieldH256(sfReferenceHolding)));
|
||||
if (!sleHolding)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
return checkMPTAllowed(
|
||||
view, txType, assetOfHolding(*issuanceSle, *sleHolding), accountID, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
|
||||
@@ -63,7 +63,7 @@ isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const&
|
||||
}
|
||||
|
||||
bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth)
|
||||
isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, std::uint8_t depth)
|
||||
{
|
||||
return std::visit(
|
||||
[&](auto const& issue) { return isFrozen(view, account, issue, depth); }, asset.value());
|
||||
@@ -107,7 +107,7 @@ isAnyFrozen(
|
||||
ReadView const& view,
|
||||
std::initializer_list<AccountID> const& accounts,
|
||||
Asset const& asset,
|
||||
int depth)
|
||||
std::uint8_t depth)
|
||||
{
|
||||
return asset.visit(
|
||||
[&](Issue const& issue) { return isAnyFrozen(view, accounts, issue); },
|
||||
@@ -115,7 +115,11 @@ isAnyFrozen(
|
||||
}
|
||||
|
||||
bool
|
||||
isDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth)
|
||||
isDeepFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
MPTIssue const& mptIssue,
|
||||
std::uint8_t depth)
|
||||
{
|
||||
// Unlike IOUs, frozen / locked MPTs are not allowed to send or receive
|
||||
// funds, so checking "deep frozen" is the same as checking "frozen".
|
||||
@@ -123,7 +127,7 @@ isDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mpt
|
||||
}
|
||||
|
||||
bool
|
||||
isDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth)
|
||||
isDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset, std::uint8_t depth)
|
||||
{
|
||||
return std::visit(
|
||||
[&](auto const& issue) { return isDeepFrozen(view, account, issue, depth); },
|
||||
@@ -500,13 +504,19 @@ requireAuth(ReadView const& view, Asset const& asset, AccountID const& account,
|
||||
}
|
||||
|
||||
TER
|
||||
canTransfer(ReadView const& view, Asset const& asset, AccountID const& from, AccountID const& to)
|
||||
canTransfer(
|
||||
ReadView const& view,
|
||||
Asset const& asset,
|
||||
AccountID const& from,
|
||||
AccountID const& to,
|
||||
WaiveMPTCanTransfer waive,
|
||||
std::uint8_t depth)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
|
||||
return canTransfer(view, issue, from, to);
|
||||
return asset.visit(
|
||||
[&](MPTIssue const& issue) -> TER {
|
||||
return canTransfer(view, issue, from, to, waive, depth);
|
||||
},
|
||||
asset.value());
|
||||
[&](Issue const& issue) -> TER { return canTransfer(view, issue, from, to); });
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace {
|
||||
//------------------------------------------------------------------------------
|
||||
// clang-format off
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
char const* const versionString = "3.3.0-b0"
|
||||
char const* const versionString = "3.2.0-b6"
|
||||
// clang-format on
|
||||
;
|
||||
|
||||
|
||||
@@ -212,7 +212,7 @@ containsError(json::Value const& json)
|
||||
int
|
||||
errorCodeHttpStatus(ErrorCodeI code)
|
||||
{
|
||||
return getErrorInfo(code).http_status;
|
||||
return getErrorInfo(code).httpStatus;
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
|
||||
@@ -53,8 +53,8 @@ TypedField<T>::TypedField(PrivateAccessTagT pat, Args&&... args)
|
||||
##__VA_ARGS__);
|
||||
|
||||
// SFields which, for historical reasons, do not follow naming conventions.
|
||||
SField const kSfInvalid(access, -1, "");
|
||||
SField const kSfGeneric(access, 0, "Generic");
|
||||
SField const sfInvalid(access, -1, "");
|
||||
SField const sfGeneric(access, 0, "Generic");
|
||||
// The following two fields aren't used anywhere, but they break tests/have
|
||||
// downstream effects.
|
||||
SField const kSfHash(access, STI_UINT256, 257, "hash");
|
||||
@@ -121,7 +121,7 @@ SField::getField(int code)
|
||||
{
|
||||
return *(it->second);
|
||||
}
|
||||
return kSfInvalid;
|
||||
return sfInvalid;
|
||||
}
|
||||
|
||||
int
|
||||
@@ -149,7 +149,7 @@ SField::getField(std::string const& fieldName)
|
||||
{
|
||||
return *(it->second);
|
||||
}
|
||||
return kSfInvalid;
|
||||
return sfInvalid;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -1151,7 +1151,7 @@ amountFromJsonNoThrow(STAmount& result, json::Value const& jvSource)
|
||||
{
|
||||
try
|
||||
{
|
||||
result = amountFromJson(kSfGeneric, jvSource);
|
||||
result = amountFromJson(sfGeneric, jvSource);
|
||||
return true;
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
@@ -1505,7 +1505,7 @@ roundToScale(STAmount const& value, std::int32_t scale, Number::RoundingMode rou
|
||||
STAmount const referenceValue{value.asset(), STAmount::kMinValue, scale, value.negative()};
|
||||
|
||||
NumberRoundModeGuard const mg(rounding);
|
||||
// With an IOU, the the result of addition will be truncated to the
|
||||
// With an IOU, the result of addition will be truncated to the
|
||||
// precision of the larger value, which in this case is referenceValue. Then
|
||||
// remove the reference value via subtraction, and we're left with the
|
||||
// rounded value.
|
||||
@@ -1738,4 +1738,9 @@ divRoundStrict(STAmount const& num, STAmount const& den, Asset const& asset, boo
|
||||
return divRoundImpl<NumberRoundModeGuard>(num, den, asset, roundUp);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
STAmount::isZeroAtScale(int scale) const
|
||||
{
|
||||
return roundToScale(*this, scale, Number::RoundingMode::ToNearest).signum() == 0;
|
||||
}
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
STBase::STBase() : fName_(&kSfGeneric)
|
||||
STBase::STBase() : fName_(&sfGeneric)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -258,7 +258,7 @@ parseUInt16(
|
||||
safeCast<typename STResult::value_type>(static_cast<Integer>(
|
||||
TxFormats::getInstance().findTypeByName(strValue))));
|
||||
|
||||
if (*name == kSfGeneric)
|
||||
if (*name == sfGeneric)
|
||||
name = &sfTransaction;
|
||||
}
|
||||
else if (field == sfLedgerEntryType)
|
||||
@@ -268,7 +268,7 @@ parseUInt16(
|
||||
safeCast<typename STResult::value_type>(static_cast<Integer>(
|
||||
LedgerFormats::getInstance().findTypeByName(strValue))));
|
||||
|
||||
if (*name == kSfGeneric)
|
||||
if (*name == sfGeneric)
|
||||
name = &sfLedgerEntry;
|
||||
}
|
||||
else
|
||||
@@ -361,7 +361,7 @@ parseLeaf(
|
||||
auto const& field = SField::getField(fieldName);
|
||||
|
||||
// checked in parseObject
|
||||
if (field == kSfInvalid)
|
||||
if (field == sfInvalid)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
error = unknownField(jsonName, fieldName);
|
||||
@@ -1013,7 +1013,7 @@ parseObject(
|
||||
json::Value const& value = json[fieldName];
|
||||
auto const& field = SField::getField(fieldName);
|
||||
|
||||
if (field == kSfInvalid)
|
||||
if (field == sfInvalid)
|
||||
{
|
||||
error = unknownField(jsonName, fieldName);
|
||||
return std::nullopt;
|
||||
@@ -1144,7 +1144,7 @@ parseArray(
|
||||
std::string const memberName(json[i].getMemberNames()[0]);
|
||||
auto const& nameField(SField::getField(memberName));
|
||||
|
||||
if (nameField == kSfInvalid)
|
||||
if (nameField == sfInvalid)
|
||||
{
|
||||
error = unknownField(jsonName, memberName);
|
||||
return std::nullopt;
|
||||
@@ -1189,7 +1189,7 @@ parseArray(
|
||||
STParsedJSONObject::STParsedJSONObject(std::string const& name, json::Value const& json)
|
||||
{
|
||||
using namespace STParsedJSONDetail;
|
||||
object = parseObject(name, json, kSfGeneric, 0, error);
|
||||
object = parseObject(name, json, sfGeneric, 0, error);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -69,8 +69,8 @@ getTxFormat(TxType type)
|
||||
|
||||
STTx::STTx(STObject&& object) : STObject(std::move(object))
|
||||
{
|
||||
tx_type_ = safeCast<TxType>(getFieldU16(sfTransactionType));
|
||||
applyTemplate(getTxFormat(tx_type_)->getSOTemplate()); // may throw
|
||||
txType_ = safeCast<TxType>(getFieldU16(sfTransactionType));
|
||||
applyTemplate(getTxFormat(txType_)->getSOTemplate()); // may throw
|
||||
tid_ = getHash(HashPrefix::TransactionId);
|
||||
}
|
||||
|
||||
@@ -84,9 +84,9 @@ STTx::STTx(SerialIter& sit) : STObject(sfTransaction)
|
||||
if (set(sit))
|
||||
Throw<std::runtime_error>("Transaction contains an object terminator");
|
||||
|
||||
tx_type_ = safeCast<TxType>(getFieldU16(sfTransactionType));
|
||||
txType_ = safeCast<TxType>(getFieldU16(sfTransactionType));
|
||||
|
||||
applyTemplate(getTxFormat(tx_type_)->getSOTemplate()); // May throw
|
||||
applyTemplate(getTxFormat(txType_)->getSOTemplate()); // May throw
|
||||
tid_ = getHash(HashPrefix::TransactionId);
|
||||
}
|
||||
|
||||
@@ -99,9 +99,9 @@ STTx::STTx(TxType type, std::function<void(STObject&)> assembler) : STObject(sfT
|
||||
|
||||
assembler(*this);
|
||||
|
||||
tx_type_ = safeCast<TxType>(getFieldU16(sfTransactionType));
|
||||
txType_ = safeCast<TxType>(getFieldU16(sfTransactionType));
|
||||
|
||||
if (tx_type_ != type)
|
||||
if (txType_ != type)
|
||||
logicError("Transaction type was mutated during assembly");
|
||||
|
||||
tid_ = getHash(HashPrefix::TransactionId);
|
||||
@@ -380,7 +380,7 @@ STTx::getMetaSQL(
|
||||
static boost::format const kBfTrans("('%s', '%s', '%s', '%d', '%d', '%c', %s, %s)");
|
||||
std::string rTxn = sqlBlobLiteral(rawTxn.peekData());
|
||||
|
||||
auto format = TxFormats::getInstance().findByType(tx_type_);
|
||||
auto format = TxFormats::getInstance().findByType(txType_);
|
||||
XRPL_ASSERT(format, "xrpl::STTx::getMetaSQL : non-null type format");
|
||||
|
||||
return str(
|
||||
@@ -535,8 +535,10 @@ STTx::checkMultiSign(Rules const& rules, STObject const& sigObject) const
|
||||
{
|
||||
// Used inside the loop in multiSignHelper to enforce that
|
||||
// the account owner may not multisign for themselves.
|
||||
// For delegated transactions sfDelegate is the account whose signer list is checked,
|
||||
// the delegate account itself can not be among the signers.
|
||||
auto const txnAccountID =
|
||||
&sigObject != this ? std::nullopt : std::optional<AccountID>(getAccountID(sfAccount));
|
||||
&sigObject != this ? std::nullopt : std::optional<AccountID>(getFeePayer());
|
||||
|
||||
// We can ease the computational load inside the loop a bit by
|
||||
// pre-constructing part of the data that we hash. Fill a Serializer
|
||||
|
||||
@@ -199,7 +199,6 @@ transResults()
|
||||
MAKE_ERROR(temARRAY_TOO_LARGE, "Malformed: Array is too large."),
|
||||
MAKE_ERROR(temBAD_TRANSFER_FEE, "Malformed: Transfer fee is outside valid range."),
|
||||
MAKE_ERROR(temINVALID_INNER_BATCH, "Malformed: Invalid inner batch transaction."),
|
||||
MAKE_ERROR(temBAD_WASM, "Malformed: Provided WASM code is invalid."),
|
||||
|
||||
MAKE_ERROR(terRETRY, "Retry transaction."),
|
||||
MAKE_ERROR(terFUNDS_SPENT, "DEPRECATED."),
|
||||
|
||||
@@ -195,7 +195,7 @@ AttestationClaim::message(
|
||||
std::uint64_t claimID,
|
||||
std::optional<AccountID> const& dst)
|
||||
{
|
||||
STObject o{kSfGeneric};
|
||||
STObject o{sfGeneric};
|
||||
// Serialize in SField order to make python serializers easier to write
|
||||
o[sfXChainClaimID] = claimID;
|
||||
o[sfAmount] = sendingAmount;
|
||||
@@ -332,7 +332,7 @@ AttestationCreateAccount::message(
|
||||
std::uint64_t createCount,
|
||||
AccountID const& dst)
|
||||
{
|
||||
STObject o{kSfGeneric};
|
||||
STObject o{sfGeneric};
|
||||
// Serialize in SField order to make python serializers easier to write
|
||||
o[sfXChainAccountCreateCount] = createCount;
|
||||
o[sfAmount] = sendingAmount;
|
||||
|
||||
@@ -90,7 +90,7 @@ deserializeManifest(Slice s, beast::Journal journal)
|
||||
try
|
||||
{
|
||||
SerialIter sit{s};
|
||||
STObject st{sit, kSfGeneric};
|
||||
STObject st{sit, sfGeneric};
|
||||
|
||||
st.applyTemplate(kManifestFormat);
|
||||
|
||||
@@ -193,7 +193,7 @@ logMftAct(
|
||||
bool
|
||||
Manifest::verify() const
|
||||
{
|
||||
STObject st(kSfGeneric);
|
||||
STObject st(sfGeneric);
|
||||
SerialIter sit(serialized.data(), serialized.size());
|
||||
st.set(sit);
|
||||
|
||||
@@ -213,7 +213,7 @@ Manifest::verify() const
|
||||
uint256
|
||||
Manifest::hash() const
|
||||
{
|
||||
STObject st(kSfGeneric);
|
||||
STObject st(sfGeneric);
|
||||
SerialIter sit(serialized.data(), serialized.size());
|
||||
st.set(sit);
|
||||
return st.getHash(HashPrefix::Manifest);
|
||||
@@ -240,7 +240,7 @@ Manifest::revoked(std::uint32_t sequence)
|
||||
std::optional<Blob>
|
||||
Manifest::getSignature() const
|
||||
{
|
||||
STObject st(kSfGeneric);
|
||||
STObject st(sfGeneric);
|
||||
SerialIter sit(serialized.data(), serialized.size());
|
||||
st.set(sit);
|
||||
if (!get(st, sfSignature))
|
||||
@@ -251,7 +251,7 @@ Manifest::getSignature() const
|
||||
Blob
|
||||
Manifest::getMasterSignature() const
|
||||
{
|
||||
STObject st(kSfGeneric);
|
||||
STObject st(sfGeneric);
|
||||
SerialIter sit(serialized.data(), serialized.size());
|
||||
st.set(sit);
|
||||
return st.getFieldVL(sfMasterSignature);
|
||||
|
||||
@@ -44,30 +44,30 @@ operator<<(std::ostream& os, Port const& p)
|
||||
{
|
||||
os << "'" << p.name << "' (ip=" << p.ip << ":" << p.port << ", ";
|
||||
|
||||
if (!p.admin_nets_v4.empty() || !p.admin_nets_v6.empty())
|
||||
if (!p.adminNetsV4.empty() || !p.adminNetsV6.empty())
|
||||
{
|
||||
os << "admin nets:";
|
||||
for (auto const& net : p.admin_nets_v4)
|
||||
for (auto const& net : p.adminNetsV4)
|
||||
{
|
||||
os << net.to_string();
|
||||
os << ", ";
|
||||
}
|
||||
for (auto const& net : p.admin_nets_v6)
|
||||
for (auto const& net : p.adminNetsV6)
|
||||
{
|
||||
os << net.to_string();
|
||||
os << ", ";
|
||||
}
|
||||
}
|
||||
|
||||
if (!p.secure_gateway_nets_v4.empty() || !p.secure_gateway_nets_v6.empty())
|
||||
if (!p.secureGatewayNetsV4.empty() || !p.secureGatewayNetsV6.empty())
|
||||
{
|
||||
os << "secure_gateway nets:";
|
||||
for (auto const& net : p.secure_gateway_nets_v4)
|
||||
for (auto const& net : p.secureGatewayNetsV4)
|
||||
{
|
||||
os << net.to_string();
|
||||
os << ", ";
|
||||
}
|
||||
for (auto const& net : p.secure_gateway_nets_v6)
|
||||
for (auto const& net : p.secureGatewayNetsV6)
|
||||
{
|
||||
os << net.to_string();
|
||||
os << ", ";
|
||||
@@ -265,10 +265,10 @@ parsePort(ParsedPort& port, Section const& section, std::ostream& log)
|
||||
{
|
||||
try
|
||||
{
|
||||
port.ws_queue_limit = beast::lexicalCastThrow<std::uint16_t>(*optResult);
|
||||
port.wsQueueLimit = beast::lexicalCastThrow<std::uint16_t>(*optResult);
|
||||
|
||||
// Queue must be greater than 0
|
||||
if (port.ws_queue_limit == 0)
|
||||
if (port.wsQueueLimit == 0)
|
||||
Throw<std::exception>();
|
||||
}
|
||||
catch (std::exception const&)
|
||||
@@ -281,32 +281,31 @@ parsePort(ParsedPort& port, Section const& section, std::ostream& log)
|
||||
else
|
||||
{
|
||||
// Default Websocket send queue size limit
|
||||
port.ws_queue_limit = 100;
|
||||
port.wsQueueLimit = 100;
|
||||
}
|
||||
}
|
||||
|
||||
populate(section, "admin", log, port.admin_nets_v4, port.admin_nets_v6);
|
||||
populate(
|
||||
section, "secure_gateway", log, port.secure_gateway_nets_v4, port.secure_gateway_nets_v6);
|
||||
populate(section, "admin", log, port.adminNetsV4, port.adminNetsV6);
|
||||
populate(section, "secure_gateway", log, port.secureGatewayNetsV4, port.secureGatewayNetsV6);
|
||||
|
||||
set(port.user, "user", section);
|
||||
set(port.password, "password", section);
|
||||
set(port.admin_user, "admin_user", section);
|
||||
set(port.admin_password, "admin_password", section);
|
||||
set(port.ssl_key, "ssl_key", section);
|
||||
set(port.ssl_cert, "ssl_cert", section);
|
||||
set(port.ssl_chain, "ssl_chain", section);
|
||||
set(port.ssl_ciphers, "ssl_ciphers", section);
|
||||
set(port.adminUser, "admin_user", section);
|
||||
set(port.adminPassword, "admin_password", section);
|
||||
set(port.sslKey, "ssl_key", section);
|
||||
set(port.sslCert, "ssl_cert", section);
|
||||
set(port.sslChain, "ssl_chain", section);
|
||||
set(port.sslCiphers, "ssl_ciphers", section);
|
||||
|
||||
port.pmd_options.server_enable = section.valueOr("permessage_deflate", true);
|
||||
port.pmd_options.client_max_window_bits = section.valueOr("client_max_window_bits", 15);
|
||||
port.pmd_options.server_max_window_bits = section.valueOr("server_max_window_bits", 15);
|
||||
port.pmd_options.client_no_context_takeover =
|
||||
port.pmdOptions.server_enable = section.valueOr("permessage_deflate", true);
|
||||
port.pmdOptions.client_max_window_bits = section.valueOr("client_max_window_bits", 15);
|
||||
port.pmdOptions.server_max_window_bits = section.valueOr("server_max_window_bits", 15);
|
||||
port.pmdOptions.client_no_context_takeover =
|
||||
section.valueOr("client_no_context_takeover", false);
|
||||
port.pmd_options.server_no_context_takeover =
|
||||
port.pmdOptions.server_no_context_takeover =
|
||||
section.valueOr("server_no_context_takeover", false);
|
||||
port.pmd_options.compLevel = section.valueOr("compress_level", 8);
|
||||
port.pmd_options.memLevel = section.valueOr("memory_level", 4);
|
||||
port.pmdOptions.compLevel = section.valueOr("compress_level", 8);
|
||||
port.pmdOptions.memLevel = section.valueOr("memory_level", 4);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
96
src/libxrpl/tx/invariants/DirectoryInvariant.cpp
Normal file
96
src/libxrpl/tx/invariants/DirectoryInvariant.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
#include <xrpl/tx/invariants/DirectoryInvariant.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] bool
|
||||
isRootBookDirectory(SLE const& dir)
|
||||
{
|
||||
// Child page keys do not encode book quality.
|
||||
return dir.isFieldPresent(sfExchangeRate) || dir.isFieldPresent(sfTakerPaysCurrency) ||
|
||||
dir.isFieldPresent(sfTakerPaysIssuer) || dir.isFieldPresent(sfTakerPaysMPT) ||
|
||||
dir.isFieldPresent(sfTakerGetsCurrency) || dir.isFieldPresent(sfTakerGetsIssuer) ||
|
||||
dir.isFieldPresent(sfTakerGetsMPT) || dir.isFieldPresent(sfDomainID);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
badExchangeRate(SLE const& dir)
|
||||
{
|
||||
return isRootBookDirectory(dir) &&
|
||||
(!dir.isFieldPresent(sfExchangeRate) ||
|
||||
dir.getFieldU64(sfExchangeRate) != getQuality(dir.key()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void
|
||||
ValidBookDirectory::visitEntry(
|
||||
bool,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
// New root directories must have matching exchange-rate metadata. New
|
||||
// child directories must point to an existing root.
|
||||
|
||||
// Only validate newly-created directories; LedgerStateFix handles legacy
|
||||
// bad exchange-rate metadata.
|
||||
if (badBookDirectory_ || before || !after || after->getType() != ltDIR_NODE)
|
||||
return;
|
||||
|
||||
auto const rootIndex = after->getFieldH256(sfRootIndex);
|
||||
if (after->key() == rootIndex && !badBookDirectory_)
|
||||
{
|
||||
badBookDirectory_ = badBookDirectory_ || badExchangeRate(*after);
|
||||
return;
|
||||
}
|
||||
|
||||
rootIndexes_.insert(rootIndex);
|
||||
}
|
||||
|
||||
bool
|
||||
ValidBookDirectory::finalize(
|
||||
STTx const&,
|
||||
TER const,
|
||||
XRPAmount const,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
if (!view.rules().enabled(fixCleanup3_2_0))
|
||||
return true;
|
||||
|
||||
if (badBookDirectory_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: book directory exchange rate "
|
||||
"does not match directory quality";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto const& rootIndex : rootIndexes_)
|
||||
{
|
||||
auto const root = view.read(Keylet(ltDIR_NODE, rootIndex));
|
||||
if (!root)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: book directory root missing";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -5,11 +5,13 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
@@ -30,6 +32,13 @@ ValidMPTIssuance::visitEntry(
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
// The sfReferenceHolding tracking and the deleted-holding capture are
|
||||
// only meaningful post-fixCleanup3_2_0 (the field is never set
|
||||
// pre-amendment, and the holding-deletion rule does not apply).
|
||||
// Skip both blocks when the amendment is off so we avoid wasted work
|
||||
// on the hot path.
|
||||
bool const fix320Enabled = isFeatureEnabled(fixCleanup3_2_0);
|
||||
|
||||
if (after && after->getType() == ltMPTOKEN_ISSUANCE)
|
||||
{
|
||||
if (isDelete)
|
||||
@@ -39,6 +48,21 @@ ValidMPTIssuance::visitEntry(
|
||||
else if (!before)
|
||||
{
|
||||
mptIssuancesCreated_++;
|
||||
if (fix320Enabled && after->isFieldPresent(sfReferenceHolding))
|
||||
referenceHoldingSetOnCreate_ = true;
|
||||
}
|
||||
else if (fix320Enabled)
|
||||
{
|
||||
// Modified issuance: detect any change to sfReferenceHolding.
|
||||
bool const beforePresent = before->isFieldPresent(sfReferenceHolding);
|
||||
bool const afterPresent = after->isFieldPresent(sfReferenceHolding);
|
||||
if (beforePresent != afterPresent ||
|
||||
(afterPresent &&
|
||||
before->getFieldH256(sfReferenceHolding) !=
|
||||
after->getFieldH256(sfReferenceHolding)))
|
||||
{
|
||||
referenceHoldingMutated_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +71,8 @@ ValidMPTIssuance::visitEntry(
|
||||
if (isDelete)
|
||||
{
|
||||
mptokensDeleted_++;
|
||||
if (fix320Enabled)
|
||||
deletedHoldings_.push_back(after);
|
||||
}
|
||||
else if (!before)
|
||||
{
|
||||
@@ -56,6 +82,11 @@ ValidMPTIssuance::visitEntry(
|
||||
mptCreatedByIssuer_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Capture deleted RippleState SLEs so finalize() can verify none of
|
||||
// them were owned by a vault pseudo-account outside VaultDelete.
|
||||
if (fix320Enabled && isDelete && after && after->getType() == ltRIPPLE_STATE)
|
||||
deletedHoldings_.push_back(after);
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -68,6 +99,63 @@ ValidMPTIssuance::finalize(
|
||||
{
|
||||
auto const& rules = view.rules();
|
||||
bool const mptV2Enabled = rules.enabled(featureMPTokensV2);
|
||||
|
||||
// Post-fixCleanup3_2_0:
|
||||
// - sfReferenceHolding is set only by VaultCreate at share-issuance
|
||||
// creation, and is immutable thereafter.
|
||||
// - A vault pseudo-account's MPToken or RippleState may only be
|
||||
// deleted by VaultDelete; the share's sfReferenceHolding pointer
|
||||
// must not dangle outside that controlled lifecycle.
|
||||
if (rules.enabled(fixCleanup3_2_0))
|
||||
{
|
||||
bool invariantPasses = true;
|
||||
if (referenceHoldingMutated_)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: sfReferenceHolding was modified "
|
||||
"on an existing MPTokenIssuance";
|
||||
invariantPasses = false;
|
||||
}
|
||||
if (referenceHoldingSetOnCreate_ && tx.getTxnType() != ttVAULT_CREATE)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: sfReferenceHolding set on a new "
|
||||
"MPTokenIssuance by a non-VaultCreate transaction";
|
||||
invariantPasses = false;
|
||||
}
|
||||
if (!deletedHoldings_.empty() && tx.getTxnType() != ttVAULT_DELETE)
|
||||
{
|
||||
auto const isVaultPseudo = [&](AccountID const& acct) {
|
||||
auto const sle = view.read(keylet::account(acct));
|
||||
return sle && sle->isFieldPresent(sfVaultID);
|
||||
};
|
||||
for (auto const& sleHolding : deletedHoldings_)
|
||||
{
|
||||
bool offending = false;
|
||||
if (sleHolding->getType() == ltMPTOKEN)
|
||||
{
|
||||
offending = isVaultPseudo(sleHolding->at(sfAccount));
|
||||
}
|
||||
else // ltRIPPLE_STATE
|
||||
{
|
||||
auto const lowLimit = sleHolding->getFieldAmount(sfLowLimit);
|
||||
auto const highLimit = sleHolding->getFieldAmount(sfHighLimit);
|
||||
// Each limit's STAmount.issuer is the COUNTERPARTY of
|
||||
// that side's owner: lowLimit's issuer is the high
|
||||
// account, highLimit's issuer is the low account.
|
||||
offending =
|
||||
isVaultPseudo(lowLimit.getIssuer()) || isVaultPseudo(highLimit.getIssuer());
|
||||
}
|
||||
if (offending)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: vault pseudo-account holding "
|
||||
"deleted by a non-VaultDelete transaction";
|
||||
invariantPasses = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!invariantPasses)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isTesSuccess(result) || (mptV2Enabled && result == tecINCOMPLETE))
|
||||
{
|
||||
[[maybe_unused]]
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace xrpl {
|
||||
|
||||
void
|
||||
ValidPermissionedDEX::visitEntry(
|
||||
bool,
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
@@ -38,7 +38,9 @@ ValidPermissionedDEX::visitEntry(
|
||||
}
|
||||
else
|
||||
{
|
||||
regularOffers_ = true;
|
||||
regularOffersOld_ = true;
|
||||
if (!isDelete)
|
||||
regularOffers_ = true;
|
||||
}
|
||||
|
||||
// pre-fixCleanup3_1_3: hybrid offer missing domain, missing
|
||||
@@ -100,7 +102,9 @@ ValidPermissionedDEX::finalize(
|
||||
}
|
||||
}
|
||||
|
||||
if (regularOffers_)
|
||||
bool const hasRegularOffers =
|
||||
view.rules().enabled(fixCleanup3_2_0) ? regularOffers_ : regularOffersOld_;
|
||||
if (hasRegularOffers)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: domain transaction"
|
||||
" affected regular offers";
|
||||
|
||||
@@ -547,6 +547,7 @@ OfferCreate::applyHybrid(
|
||||
Keylet const& offerKey,
|
||||
STAmount const& saTakerPays,
|
||||
STAmount const& saTakerGets,
|
||||
std::uint64_t openRate,
|
||||
std::function<void(SLE::ref, std::optional<uint256>)> const& setDir)
|
||||
{
|
||||
if (!sleOffer->isFieldPresent(sfDomainID))
|
||||
@@ -558,7 +559,7 @@ OfferCreate::applyHybrid(
|
||||
// if offer is hybrid, need to also place into open offer dir
|
||||
Book const book{saTakerPays.asset(), saTakerGets.asset(), std::nullopt};
|
||||
|
||||
auto dir = keylet::quality(keylet::kBook(book), getRate(saTakerGets, saTakerPays));
|
||||
auto dir = keylet::quality(keylet::kBook(book), openRate);
|
||||
bool const bookExists = sb.exists(dir);
|
||||
|
||||
auto const bookNode = sb.dirAppend(dir, offerKey, [&](SLE::ref sle) {
|
||||
@@ -924,8 +925,16 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
||||
// if it's a hybrid offer, set hybrid flag, and create an open dir
|
||||
if (bHybrid)
|
||||
{
|
||||
// Pre-fixCleanup3_2_0: the open-book directory quality was computed
|
||||
// from post-crossing amounts, which may differ from the original rate
|
||||
// due to rounding in rate preservation. Post-fixCleanup3_2_0: use the
|
||||
// original placement rate so the open-book directory quality matches
|
||||
// the domain-book directory.
|
||||
auto const openRate = ctx_.view().rules().enabled(fixCleanup3_2_0)
|
||||
? uRate
|
||||
: getRate(saTakerGets, saTakerPays);
|
||||
auto const res =
|
||||
applyHybrid(sb, sleOffer, offerIndex, saTakerPays, saTakerGets, setBookDir);
|
||||
applyHybrid(sb, sleOffer, offerIndex, saTakerPays, saTakerGets, openRate, setBookDir);
|
||||
if (!isTesSuccess(res))
|
||||
return {res, true}; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
@@ -291,6 +291,10 @@ LoanBrokerCoverClawback::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
STAmount const& clawAmount = *findClawAmount;
|
||||
|
||||
if (auto const ret = canApplyToBrokerCover(
|
||||
ctx.view, sleBroker, vaultAsset, clawAmount, ctx.j, "LoanBrokerCoverClawback"))
|
||||
return ret;
|
||||
|
||||
// Explicitly check the balance of the trust line / MPT to make sure the
|
||||
// balance is actually there. It should always match `sfCoverAvailable`, so
|
||||
// if there isn't, this is an internal error.
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#include <xrpl/tx/transactors/lending/LoanBrokerCoverDeposit.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/ledger/helpers/LendingHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
@@ -87,6 +89,29 @@ LoanBrokerCoverDeposit::preclaim(PreclaimContext const& ctx)
|
||||
if (auto const ret = requireAuth(ctx.view, vaultAsset, account, AuthType::StrongAuth))
|
||||
return ret;
|
||||
|
||||
// Deposit must round the amount Downward to cover scale and then reuse that rounded
|
||||
// value for the actual transfer in doApply — otherwise implicit round-to-nearest during
|
||||
// `sfCoverAvailable +=` could credit the broker more than the depositor paid Computing it
|
||||
// here in preclaim lets us reject sub-cover-scale dust early with tecPRECISION_LOSS instead of
|
||||
// failing only in doApply.
|
||||
bool const fix320Enabled = ctx.view.rules().enabled(fixCleanup3_2_0);
|
||||
auto const roundedAmount = [&]() -> STAmount {
|
||||
if (!fix320Enabled)
|
||||
return tx[sfAmount];
|
||||
|
||||
return roundToScale(
|
||||
tx[sfAmount],
|
||||
scale(sleBroker->at(sfCoverAvailable), vaultAsset),
|
||||
Number::RoundingMode::Downward);
|
||||
}();
|
||||
|
||||
if (fix320Enabled && roundedAmount == beast::kZero)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "LoanBrokerCoverDeposit: deposit amount: " << tx[sfAmount]
|
||||
<< " is zero at loan broker scale";
|
||||
return tecPRECISION_LOSS;
|
||||
}
|
||||
|
||||
if (accountHolds(
|
||||
ctx.view,
|
||||
account,
|
||||
@@ -94,7 +119,7 @@ LoanBrokerCoverDeposit::preclaim(PreclaimContext const& ctx)
|
||||
FreezeHandling::ZeroIfFrozen,
|
||||
AuthHandling::ZeroIfUnauthorized,
|
||||
ctx.j,
|
||||
SpendableHandling::FullBalance) < amount)
|
||||
SpendableHandling::FullBalance) < roundedAmount)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
return tesSUCCESS;
|
||||
@@ -106,8 +131,6 @@ LoanBrokerCoverDeposit::doApply()
|
||||
auto const& tx = ctx_.tx;
|
||||
|
||||
auto const brokerID = tx[sfLoanBrokerID];
|
||||
auto const amount = tx[sfAmount];
|
||||
|
||||
auto broker = view().peek(keylet::loanbroker(brokerID));
|
||||
if (!broker)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
@@ -117,9 +140,32 @@ LoanBrokerCoverDeposit::doApply()
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const vaultAsset = vault->at(sfAsset);
|
||||
|
||||
auto const brokerPseudoID = broker->at(sfAccount);
|
||||
|
||||
// Re-round here (matches preclaim) so the same cover-scale-quantized
|
||||
// value drives both the trustline transfer and the cover increment;
|
||||
// see the rationale comment in preclaim.
|
||||
bool const fix320Enabled = view().rules().enabled(fixCleanup3_2_0);
|
||||
auto const amount = [&]() -> STAmount {
|
||||
if (!fix320Enabled)
|
||||
return tx[sfAmount];
|
||||
|
||||
return roundToScale(
|
||||
tx[sfAmount],
|
||||
scale(broker->at(sfCoverAvailable), vaultAsset),
|
||||
Number::RoundingMode::Downward);
|
||||
}();
|
||||
|
||||
// We validated zero-amount in preclaim, if we ended up with zero now, fail hard.
|
||||
if (amount == beast::kZero)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.error()) << "LoanBrokerCoverDeposit: deposit amount: " << tx[sfAmount]
|
||||
<< " is zero";
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// Transfer assets from depositor to pseudo-account.
|
||||
if (auto ter =
|
||||
accountSend(view(), accountID_, brokerPseudoID, amount, j_, WaiveTransferFee::Yes))
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/LendingHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
@@ -93,10 +94,20 @@ LoanBrokerCoverWithdraw::preclaim(PreclaimContext const& ctx)
|
||||
if (amount.asset() != vaultAsset)
|
||||
return tecWRONG_ASSET;
|
||||
|
||||
// Helper handles both IOU and MPT correctly without explicit branching.
|
||||
if (auto const ret = canApplyToBrokerCover(
|
||||
ctx.view, sleBroker, vaultAsset, amount, ctx.j, "LoanBrokerCoverWithdraw"))
|
||||
return ret;
|
||||
|
||||
// The broker's pseudo-account is the source of funds.
|
||||
auto const pseudoAccountID = sleBroker->at(sfAccount);
|
||||
// Cannot transfer a non-transferable Asset
|
||||
if (auto const ret = canTransfer(ctx.view, vaultAsset, pseudoAccountID, dstAcct))
|
||||
// Post-fixCleanup3_2_0: cover withdraw is a recovery path that bypasses
|
||||
// the lsfMPTCanTransfer flag check, so an issuer cannot trap a broker's
|
||||
// first-loss capital. Other transferability checks (IOU NoRipple, freeze,
|
||||
// requireAuth) still apply.
|
||||
auto const waive = ctx.view.rules().enabled(fixCleanup3_2_0) ? WaiveMPTCanTransfer::Yes
|
||||
: WaiveMPTCanTransfer::No;
|
||||
if (auto const ret = canTransfer(ctx.view, vaultAsset, pseudoAccountID, dstAcct, waive))
|
||||
return ret;
|
||||
|
||||
// Withdrawal to a 3rd party destination account is essentially a transfer.
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
@@ -12,24 +14,72 @@
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace {
|
||||
|
||||
using FixType = LedgerStateFix::FixType;
|
||||
|
||||
std::array<std::pair<FixType, SField const*>, 2> const kLedgerFixFields = {{
|
||||
{FixType::NfTokenPageLink, &sfOwner},
|
||||
{FixType::BookExchangeRate, &sfBookDirectory},
|
||||
}};
|
||||
|
||||
[[nodiscard]] SField const*
|
||||
fixField(FixType const fixType)
|
||||
{
|
||||
auto const iter = std::ranges::find_if(
|
||||
kLedgerFixFields, [fixType](auto const& entry) { return entry.first == fixType; });
|
||||
|
||||
if (iter == kLedgerFixFields.end())
|
||||
return nullptr; // LCOV_EXCL_LINE
|
||||
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
hasUnexpectedFixField(STTx const& tx, SField const& expected)
|
||||
{
|
||||
return std::ranges::any_of(kLedgerFixFields, [&tx, &expected](auto const& entry) {
|
||||
auto const field = entry.second;
|
||||
return field != &expected && tx.isFieldPresent(*field);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
NotTEC
|
||||
LedgerStateFix::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
switch (static_cast<FixType>(ctx.tx[sfLedgerFixType]))
|
||||
auto const fixType = static_cast<FixType>(ctx.tx[sfLedgerFixType]);
|
||||
|
||||
switch (fixType)
|
||||
{
|
||||
case FixType::NfTokenPageLink:
|
||||
if (!ctx.tx.isFieldPresent(sfOwner))
|
||||
return temINVALID;
|
||||
break;
|
||||
|
||||
case FixType::BookExchangeRate:
|
||||
if (!ctx.rules.enabled(fixCleanup3_2_0))
|
||||
return temDISABLED;
|
||||
break;
|
||||
|
||||
default:
|
||||
return tefINVALID_LEDGER_FIX_TYPE;
|
||||
}
|
||||
|
||||
auto const expectedField = fixField(fixType);
|
||||
if (expectedField == nullptr)
|
||||
return tefINVALID_LEDGER_FIX_TYPE; // LCOV_EXCL_LINE
|
||||
|
||||
// Each fix type allows exactly one fix-specific field.
|
||||
if (!ctx.tx.isFieldPresent(*expectedField) || hasUnexpectedFixField(ctx.tx, *expectedField))
|
||||
return temINVALID;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -53,6 +103,24 @@ LedgerStateFix::preclaim(PreclaimContext const& ctx)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
if (static_cast<FixType>(ctx.tx[sfLedgerFixType]) == FixType::BookExchangeRate)
|
||||
{
|
||||
auto const dirKey = ctx.tx.getFieldH256(sfBookDirectory);
|
||||
auto const sle = ctx.view.read(Keylet(ltDIR_NODE, dirKey));
|
||||
if (!sle)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// Must be the first page of a book directory (has sfExchangeRate).
|
||||
if (!sle->isFieldPresent(sfExchangeRate))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// ExchangeRate is already correct, nothing to fix.
|
||||
if (getQuality(sle->key()) == sle->getFieldU64(sfExchangeRate))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// preflight is supposed to verify that only valid FixTypes get to preclaim.
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
@@ -68,6 +136,18 @@ LedgerStateFix::doApply()
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
if (static_cast<FixType>(ctx_.tx[sfLedgerFixType]) == FixType::BookExchangeRate)
|
||||
{
|
||||
auto const dirKey = ctx_.tx.getFieldH256(sfBookDirectory);
|
||||
auto sle = view().peek(Keylet(ltDIR_NODE, dirKey));
|
||||
if (!sle)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
sle->setFieldU64(sfExchangeRate, getQuality(sle->key()));
|
||||
view().update(sle);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// preflight is supposed to verify that only valid FixTypes get to doApply.
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
@@ -49,6 +50,11 @@ MPTokenIssuanceCreate::getFlagsMask(PreflightContext const& ctx)
|
||||
NotTEC
|
||||
MPTokenIssuanceCreate::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
// sfReferenceHolding is set only internally by VaultCreate. Reject
|
||||
// any user-submitted MPTokenIssuanceCreate that attempts to carry it.
|
||||
if (ctx.rules.enabled(fixCleanup3_2_0) && ctx.tx.isFieldPresent(sfReferenceHolding))
|
||||
return temMALFORMED;
|
||||
|
||||
// If the mutable flags field is included, at least one flag must be
|
||||
// specified.
|
||||
if (auto const mutableFlags = ctx.tx[~sfMutableFlags]; mutableFlags &&
|
||||
@@ -141,6 +147,22 @@ MPTokenIssuanceCreate::create(ApplyView& view, beast::Journal journal, MPTCreate
|
||||
if (args.mutableFlags)
|
||||
(*mptIssuance)[sfMutableFlags] = *args.mutableFlags;
|
||||
|
||||
if (args.referenceHolding)
|
||||
{
|
||||
// Defensive: the holding must already exist and be of an
|
||||
// expected type. Callers (currently only VaultCreate)
|
||||
// populate this after the pseudo-account's MPToken /
|
||||
// RippleState has been installed. A missing holding here
|
||||
// would dangle the pointer and is a programmer error.
|
||||
auto const sleHolding = view.read(keylet::unchecked(*args.referenceHolding));
|
||||
if (!sleHolding)
|
||||
return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
auto const type = sleHolding->getType();
|
||||
if (type != ltMPTOKEN && type != ltRIPPLE_STATE)
|
||||
return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE
|
||||
(*mptIssuance)[sfReferenceHolding] = *args.referenceHolding;
|
||||
}
|
||||
|
||||
view.insert(mptIssuance);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
#include <xrpl/tx/transactors/vault/VaultCreate.h>
|
||||
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -162,9 +164,9 @@ VaultCreate::doApply()
|
||||
auto maybePseudo = createPseudoAccount(view(), vault->key(), sfVaultID);
|
||||
if (!maybePseudo)
|
||||
return maybePseudo.error(); // LCOV_EXCL_LINE
|
||||
auto& pseudo = *maybePseudo;
|
||||
auto pseudoId = pseudo->at(sfAccount);
|
||||
auto asset = tx[sfAsset];
|
||||
auto const& pseudo = *maybePseudo;
|
||||
AccountID const pseudoId = pseudo->at(sfAccount);
|
||||
auto const asset = tx[sfAsset];
|
||||
|
||||
if (auto ter = addEmptyHolding(view(), pseudoId, preFeeBalance_, asset, j_); !isTesSuccess(ter))
|
||||
return ter;
|
||||
@@ -182,13 +184,24 @@ VaultCreate::doApply()
|
||||
// Note, here we are **not** creating an MPToken for the assets held in
|
||||
// the vault. That MPToken or TrustLine/RippleState is created above, in
|
||||
// addEmptyHolding. Here we are creating MPTokenIssuance for the shares
|
||||
// in the vault
|
||||
auto maybeShare = MPTokenIssuanceCreate::create(
|
||||
// in the vault.
|
||||
//
|
||||
// Post-fixCleanup3_2_0: surface the vault pseudo's holding (MPToken
|
||||
// for MPT, RippleState for IOU) on the share via sfReferenceHolding.
|
||||
// XRP underlyings leave it unset.
|
||||
auto const referenceHolding = [&]() -> std::optional<uint256> {
|
||||
if (!view().rules().enabled(fixCleanup3_2_0) || asset.native())
|
||||
return std::nullopt;
|
||||
return asset.holds<MPTIssue>()
|
||||
? keylet::mptoken(asset.get<MPTIssue>().getMptID(), pseudoId).key
|
||||
: keylet::line(pseudoId, asset.get<Issue>()).key;
|
||||
}();
|
||||
auto const maybeShare = MPTokenIssuanceCreate::create(
|
||||
view(),
|
||||
j_,
|
||||
{
|
||||
.priorBalance = std::nullopt,
|
||||
.account = pseudoId->value(),
|
||||
.account = pseudoId,
|
||||
.sequence = 1,
|
||||
.flags = mptFlags,
|
||||
.assetScale = scale,
|
||||
@@ -196,6 +209,7 @@ VaultCreate::doApply()
|
||||
.metadata = tx[~sfMPTokenMetadata],
|
||||
.domainId = tx[~sfDomainID],
|
||||
.mutableFlags = std::nullopt,
|
||||
.referenceHolding = referenceHolding,
|
||||
});
|
||||
if (!maybeShare)
|
||||
return maybeShare.error(); // LCOV_EXCL_LINE
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user