Skip to content

Commit c0da077

Browse files
committed
Merge branch 'feature/sanitizers-ci' of github.com:godexsoft/clio into feature/sanitizers-ci
2 parents 83b80a6 + e5258e5 commit c0da077

29 files changed

+1127
-91
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ Below are some useful docs to learn more about Clio.
2828
**For Developers**:
2929

3030
- [How to build Clio](./docs/build-clio.md)
31-
- [Metrics and static analysis](./docs/metrics-and-static-analysis.md)
3231
- [Coverage report](./docs/coverage-report.md)
3332

3433
**For Operators**:

conanfile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class Clio(ConanFile):
2828
'protobuf/3.21.9',
2929
'grpc/1.50.1',
3030
'openssl/1.1.1u',
31-
'xrpl/2.4.0-b1',
31+
'xrpl/2.4.0-b3',
3232
'zlib/1.3.1',
3333
'libbacktrace/cci.20210118'
3434
]

docs/build-clio.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,20 @@ Sometimes, during development, you need to build against a custom version of `li
181181
4. Build Clio as you would have before.
182182
183183
See [Building Clio](#building-clio) for details.
184+
185+
## Using `clang-tidy` for static analysis
186+
187+
The minimum [clang-tidy](https://clang.llvm.org/extra/clang-tidy/) version required is 19.0.
188+
189+
Clang-tidy can be run by Cmake when building the project. To achieve this, you just need to provide the option `-o lint=True` for the `conan install` command:
190+
191+
```sh
192+
conan install .. --output-folder . --build missing --settings build_type=Release -o tests=True -o lint=True
193+
```
194+
195+
By default Cmake will try to find `clang-tidy` automatically in your system.
196+
To force Cmake to use your desired binary, set the `CLIO_CLANG_TIDY_BIN` environment variable to the path of the `clang-tidy` binary. For example:
197+
198+
```sh
199+
export CLIO_CLANG_TIDY_BIN=/opt/homebrew/opt/llvm@19/bin/clang-tidy
200+
```

docs/metrics-and-static-analysis.md

Lines changed: 0 additions & 30 deletions
This file was deleted.

docs/run-clio.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,15 @@ Clio will fallback to hardcoded defaults when these values are not specified in
8080

8181
> [!TIP]
8282
> See the [example-config.json](../docs/examples/config/example-config.json) for more details.
83+
84+
## Prometheus metrics collection
85+
86+
Clio natively supports [Prometheus](https://prometheus.io/) metrics collection. It accepts Prometheus requests on the port configured in the `server` section of the config.
87+
88+
Prometheus metrics are enabled by default, and replies to `/metrics` are compressed. To disable compression, and have human readable metrics, add `"prometheus": { "enabled": true, "compress_reply": false }` to Clio's config.
89+
90+
To completely disable Prometheus metrics add `"prometheus": { "enabled": false }` to Clio's config.
91+
92+
It is important to know that Clio responds to Prometheus request only if they are admin requests. If you are using the admin password feature, the same password should be provided in the Authorization header of Prometheus requests.
93+
94+
You can find an example docker-compose file, with Prometheus and Grafana configs, in [examples/infrastructure](../docs/examples/infrastructure/).

src/data/AmendmentCenter.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ struct Amendments {
132132
REGISTER(fixAMMv1_2);
133133
REGISTER(AMMClawback);
134134
REGISTER(Credentials);
135+
REGISTER(DynamicNFT);
136+
REGISTER(PermissionedDomains);
135137

136138
// Obsolete but supported by libxrpl
137139
REGISTER(CryptoConditionsSuite);

src/data/CassandraBackend.hpp

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,6 @@ class BasicCassandraBackend : public BackendInterface {
624624
return seq;
625625
}
626626
LOG(log_.debug()) << "Could not fetch ledger object sequence - no rows";
627-
628627
} else {
629628
LOG(log_.error()) << "Could not fetch ledger object sequence: " << res.error();
630629
}
@@ -948,28 +947,35 @@ class BasicCassandraBackend : public BackendInterface {
948947
statements.reserve(data.size() * 3);
949948

950949
for (NFTsData const& record : data) {
951-
statements.push_back(
952-
schema_->insertNFT.bind(record.tokenID, record.ledgerSequence, record.owner, record.isBurned)
953-
);
950+
if (!record.onlyUriChanged) {
951+
statements.push_back(
952+
schema_->insertNFT.bind(record.tokenID, record.ledgerSequence, record.owner, record.isBurned)
953+
);
954954

955-
// If `uri` is set (and it can be set to an empty uri), we know this
956-
// is a net-new NFT. That is, this NFT has not been seen before by
957-
// us _OR_ it is in the extreme edge case of a re-minted NFT ID with
958-
// the same NFT ID as an already-burned token. In this case, we need
959-
// to record the URI and link to the issuer_nf_tokens table.
960-
if (record.uri) {
961-
statements.push_back(schema_->insertIssuerNFT.bind(
962-
ripple::nft::getIssuer(record.tokenID),
963-
static_cast<uint32_t>(ripple::nft::getTaxon(record.tokenID)),
964-
record.tokenID
965-
));
955+
// If `uri` is set (and it can be set to an empty uri), we know this
956+
// is a net-new NFT. That is, this NFT has not been seen before by
957+
// us _OR_ it is in the extreme edge case of a re-minted NFT ID with
958+
// the same NFT ID as an already-burned token. In this case, we need
959+
// to record the URI and link to the issuer_nf_tokens table.
960+
if (record.uri) {
961+
statements.push_back(schema_->insertIssuerNFT.bind(
962+
ripple::nft::getIssuer(record.tokenID),
963+
static_cast<uint32_t>(ripple::nft::getTaxon(record.tokenID)),
964+
record.tokenID
965+
));
966+
statements.push_back(
967+
schema_->insertNFTURI.bind(record.tokenID, record.ledgerSequence, record.uri.value())
968+
);
969+
}
970+
} else {
971+
// only uri changed, we update the uri table only
966972
statements.push_back(
967973
schema_->insertNFTURI.bind(record.tokenID, record.ledgerSequence, record.uri.value())
968974
);
969975
}
970976
}
971977

972-
executor_.write(std::move(statements));
978+
executor_.writeEach(std::move(statements));
973979
}
974980

975981
void

src/data/DBHelpers.hpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ struct NFTsData {
107107
ripple::AccountID owner;
108108
std::optional<ripple::Blob> uri;
109109
bool isBurned = false;
110+
bool onlyUriChanged = false; // Whether only the URI was changed
110111

111112
/**
112113
* @brief Construct a new NFTsData object
@@ -170,6 +171,23 @@ struct NFTsData {
170171
: tokenID(tokenID), ledgerSequence(ledgerSequence), owner(owner), uri(uri)
171172
{
172173
}
174+
175+
/**
176+
* @brief Construct a new NFTsData object with only the URI changed
177+
*
178+
* @param tokenID The token ID
179+
* @param meta The transaction metadata
180+
* @param uri The new URI
181+
*
182+
*/
183+
NFTsData(ripple::uint256 const& tokenID, ripple::TxMeta const& meta, ripple::Blob const& uri)
184+
: tokenID(tokenID)
185+
, ledgerSequence(meta.getLgrSeq())
186+
, transactionIndex(meta.getIndex())
187+
, uri(uri)
188+
, onlyUriChanged(true)
189+
{
190+
}
173191
};
174192

175193
/**

src/data/cassandra/impl/ExecutionStrategy.hpp

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include <boost/asio/spawn.hpp>
3636
#include <boost/json/object.hpp>
3737

38+
#include <algorithm>
3839
#include <atomic>
3940
#include <chrono>
4041
#include <condition_variable>
@@ -192,10 +193,24 @@ class DefaultExecutionStrategy {
192193
template <typename... Args>
193194
void
194195
write(PreparedStatementType const& preparedStatement, Args&&... args)
196+
{
197+
auto statement = preparedStatement.bind(std::forward<Args>(args)...);
198+
write(std::move(statement));
199+
}
200+
201+
/**
202+
* @brief Non-blocking query execution used for writing data.
203+
*
204+
* Retries forever with retry policy specified by @ref AsyncExecutor
205+
*
206+
* @param statement Statement to execute
207+
* @throw DatabaseTimeout on timeout
208+
*/
209+
void
210+
write(StatementType&& statement)
195211
{
196212
auto const startTime = std::chrono::steady_clock::now();
197213

198-
auto statement = preparedStatement.bind(std::forward<Args>(args)...);
199214
incrementOutstandingRequestCount();
200215

201216
counters_->registerWriteStarted();
@@ -251,6 +266,21 @@ class DefaultExecutionStrategy {
251266
});
252267
}
253268

269+
/**
270+
* @brief Non-blocking query execution used for writing data. Constrast with write, this method does not execute
271+
* the statements in a batch.
272+
*
273+
* Retries forever with retry policy specified by @ref AsyncExecutor.
274+
*
275+
* @param statements Vector of statements to execute
276+
* @throw DatabaseTimeout on timeout
277+
*/
278+
void
279+
writeEach(std::vector<StatementType>&& statements)
280+
{
281+
std::ranges::for_each(std::move(statements), [this](auto& statement) { this->write(std::move(statement)); });
282+
}
283+
254284
/**
255285
* @brief Coroutine-based query execution used for reading data.
256286
*

src/etl/NFTHelpers.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,17 @@
4747

4848
namespace etl {
4949

50+
std::pair<std::vector<NFTTransactionsData>, std::optional<NFTsData>>
51+
getNftokenModifyData(ripple::TxMeta const& txMeta, ripple::STTx const& sttx)
52+
{
53+
auto const tokenID = sttx.getFieldH256(ripple::sfNFTokenID);
54+
// note: sfURI is optional, if it is absent, we will update the uri as empty string
55+
return {
56+
{NFTTransactionsData(sttx.getFieldH256(ripple::sfNFTokenID), txMeta, sttx.getTransactionID())},
57+
NFTsData(tokenID, txMeta, sttx.getFieldVL(ripple::sfURI))
58+
};
59+
}
60+
5061
std::pair<std::vector<NFTTransactionsData>, std::optional<NFTsData>>
5162
getNFTokenMintData(ripple::TxMeta const& txMeta, ripple::STTx const& sttx)
5263
{
@@ -166,7 +177,7 @@ getNFTokenBurnData(ripple::TxMeta const& txMeta, ripple::STTx const& sttx)
166177
node.peekAtField(ripple::sfPreviousFields).downcast<ripple::STObject>();
167178
if (previousFields.isFieldPresent(ripple::sfNFTokens))
168179
prevNFTs = previousFields.getFieldArray(ripple::sfNFTokens);
169-
} else if (!prevNFTs && node.getFName() == ripple::sfDeletedNode) {
180+
} else if (node.getFName() == ripple::sfDeletedNode) {
170181
prevNFTs =
171182
node.peekAtField(ripple::sfFinalFields).downcast<ripple::STObject>().getFieldArray(ripple::sfNFTokens);
172183
}
@@ -336,6 +347,9 @@ getNFTDataFromTx(ripple::TxMeta const& txMeta, ripple::STTx const& sttx)
336347
case ripple::TxType::ttNFTOKEN_CREATE_OFFER:
337348
return getNFTokenCreateOfferData(txMeta, sttx);
338349

350+
case ripple::TxType::ttNFTOKEN_MODIFY:
351+
return getNftokenModifyData(txMeta, sttx);
352+
339353
default:
340354
return {{}, {}};
341355
}

0 commit comments

Comments
 (0)