Skip to content

Commit 5c59df4

Browse files
authored
rpcdaemon: implement erigon_cacheCheck (#2792)
* rpcdaemon: implement erigon_cacheCheck * fix shadowing * fix copyright and doc * update rpc-tests tag
1 parent c790ee5 commit 5c59df4

9 files changed

+185
-3
lines changed

.github/workflows/rpc-integration-tests.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
- name: Checkout RPC Tests Repository & Install Requirements
3434
run: |
3535
rm -rf ${{runner.workspace}}/rpc-tests
36-
git -c advice.detachedHead=false clone --depth 1 --branch v1.51.0 https://github.com/erigontech/rpc-tests ${{runner.workspace}}/rpc-tests
36+
git -c advice.detachedHead=false clone --depth 1 --branch v1.53.0 https://github.com/erigontech/rpc-tests ${{runner.workspace}}/rpc-tests
3737
cd ${{runner.workspace}}/rpc-tests
3838
pip3 install -r requirements.txt --break-system-packages
3939

docs/JSON-RPC-API.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ The following table shows the current [JSON RPC API](https://eth.wiki/json-rpc/A
152152
| erigon_watchTheBurn | Yes | | Yes | |
153153
| erigon_nodeInfo | Yes | | Yes | |
154154
| erigon_blockNumber | Yes | | Yes | |
155-
| erigon_cacheCheck | - | not yet implemented | | |
155+
| erigon_cacheCheck | Yes | | Yes | |
156156
| erigon_getLatestLogs | Yes | | Yes | |
157157
| | | | | |
158158
| bor_getSnapshot | - | not yet implemented | | |

silkworm/db/kv/api/state_cache.cpp

+75
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,68 @@ void CoherentStateCache::on_new_block(const api::StateChangeSet& state_changes_s
134134
root->ready_cond_var.notify_all();
135135
}
136136

137+
Task<StateCache::ValidationResult> CoherentStateCache::validate_current_root(Transaction& tx) {
138+
StateCache::ValidationResult validation_result{.enabled = true};
139+
140+
const StateVersionId current_state_version_id = co_await get_db_state_version(tx);
141+
validation_result.latest_state_version_id = current_state_version_id;
142+
// If the latest version id in the cache is not the same as the db or one below it
143+
// then the cache will be a new one for the next call so return early
144+
if (current_state_version_id > latest_state_version_id_) {
145+
validation_result.latest_state_behind = true;
146+
co_return validation_result;
147+
}
148+
const auto root = co_await wait_for_root_ready(latest_state_version_id_);
149+
150+
bool clear_cache{false};
151+
const auto get_address_domain = [](const auto& key) {
152+
return key.size() == kAddressLength ? db::table::kAccountDomain : db::table::kStorageDomain;
153+
};
154+
const auto compare_cache = [&](auto& cache, bool is_code) -> Task<std::pair<bool, std::vector<Bytes>>> {
155+
bool cancelled{false};
156+
std::vector<Bytes> keys;
157+
if (cache.empty()) {
158+
co_return std::make_pair(cancelled, keys);
159+
}
160+
auto kv_node = cache.extract(cache.begin());
161+
if (!kv_node.empty()) {
162+
co_return std::make_pair(cancelled, keys);
163+
}
164+
KeyValue kv{kv_node.value()};
165+
const auto domain = is_code ? db::table::kCodeDomain : get_address_domain(kv.key);
166+
const GetLatestResult result = co_await tx.get_latest({.table = std::string{domain}, .key = kv.key});
167+
if (!result.success) {
168+
co_return std::make_pair(cancelled, keys);
169+
}
170+
if (result.value != kv.key) {
171+
keys.push_back(kv.key);
172+
clear_cache = true;
173+
}
174+
co_return std::make_pair(cancelled, keys);
175+
};
176+
177+
auto [cache, code_cache] = clone_caches(root);
178+
179+
auto [cancelled_1, keys] = co_await compare_cache(cache, /*is_code=*/false);
180+
if (cancelled_1) {
181+
validation_result.request_canceled = true;
182+
co_return validation_result;
183+
}
184+
validation_result.state_keys_out_of_sync = std::move(keys);
185+
auto [cancelled_2, code_keys] = co_await compare_cache(code_cache, /*is_code=*/true);
186+
if (cancelled_2) {
187+
validation_result.request_canceled = true;
188+
co_return validation_result;
189+
}
190+
validation_result.code_keys_out_of_sync = std::move(code_keys);
191+
192+
if (clear_cache) {
193+
clear_caches(root);
194+
}
195+
validation_result.cache_cleared = true;
196+
co_return validation_result;
197+
}
198+
137199
void CoherentStateCache::process_upsert_change(CoherentStateRoot* root, StateVersionId version_id, const AccountChange& change) {
138200
const auto& address = change.address;
139201
const auto& data_bytes = change.data;
@@ -413,4 +475,17 @@ Task<StateVersionId> CoherentStateCache::get_db_state_version(Transaction& tx) c
413475
co_return endian::load_big_u64(version.data());
414476
}
415477

478+
std::pair<KeyValueSet, KeyValueSet> CoherentStateCache::clone_caches(CoherentStateRoot* root) {
479+
std::scoped_lock write_lock{rw_mutex_};
480+
KeyValueSet cache = root->cache;
481+
KeyValueSet code_cache = root->code_cache;
482+
return {cache, code_cache};
483+
}
484+
485+
void CoherentStateCache::clear_caches(CoherentStateRoot* root) {
486+
std::scoped_lock write_lock{rw_mutex_};
487+
root->cache.clear();
488+
root->code_cache.clear();
489+
}
490+
416491
} // namespace silkworm::db::kv::api

silkworm/db/kv/api/state_cache.hpp

+15
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,17 @@ class StateCache {
6969
virtual uint64_t code_miss_count() const = 0;
7070
virtual uint64_t code_key_count() const = 0;
7171
virtual uint64_t code_eviction_count() const = 0;
72+
73+
struct ValidationResult {
74+
bool request_canceled{false};
75+
bool enabled{false};
76+
bool latest_state_behind{false};
77+
bool cache_cleared{false};
78+
StateVersionId latest_state_version_id{0};
79+
std::vector<Bytes> state_keys_out_of_sync;
80+
std::vector<Bytes> code_keys_out_of_sync;
81+
};
82+
virtual Task<ValidationResult> validate_current_root(Transaction& tx) = 0;
7283
};
7384

7485
using KeyValueSet = absl::btree_set<KeyValue>;
@@ -139,6 +150,8 @@ class CoherentStateCache : public StateCache {
139150
uint64_t code_key_count() const override { return code_key_count_; }
140151
uint64_t code_eviction_count() const override { return code_eviction_count_; }
141152

153+
Task<ValidationResult> validate_current_root(Transaction& tx) override;
154+
142155
private:
143156
friend class CoherentStateView;
144157

@@ -155,6 +168,8 @@ class CoherentStateCache : public StateCache {
155168
void evict_roots(StateVersionId next_version_id);
156169
Task<CoherentStateRoot*> wait_for_root_ready(StateVersionId version_id);
157170
Task<StateVersionId> get_db_state_version(Transaction& tx) const;
171+
std::pair<KeyValueSet, KeyValueSet> clone_caches(CoherentStateRoot* root);
172+
void clear_caches(CoherentStateRoot* root);
158173

159174
CoherentCacheConfig config_;
160175

silkworm/db/test_util/mock_state_cache.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ class MockStateCache : public kv::api::StateCache {
5252
MOCK_METHOD(uint64_t, code_miss_count, (), (const, override));
5353
MOCK_METHOD(uint64_t, code_key_count, (), (const, override));
5454
MOCK_METHOD(uint64_t, code_eviction_count, (), (const, override));
55+
56+
MOCK_METHOD(Task<ValidationResult>, validate_current_root, (kv::api::Transaction&), (override));
5557
};
5658

5759
} // namespace silkworm::db::test_util

silkworm/rpc/commands/erigon_api.cpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include <silkworm/rpc/core/cached_chain.hpp>
3030
#include <silkworm/rpc/core/logs_walker.hpp>
3131
#include <silkworm/rpc/core/receipts.hpp>
32+
#include <silkworm/rpc/json/cache_validation_result.hpp>
3233
#include <silkworm/rpc/json/types.hpp>
3334
#include <silkworm/rpc/protocol/errors.hpp>
3435

@@ -39,7 +40,8 @@ Task<void> ErigonRpcApi::handle_erigon_cache_check(const nlohmann::json& request
3940
auto tx = co_await database_->begin_transaction();
4041

4142
try {
42-
reply = make_json_content(request, to_quantity(0));
43+
const db::kv::api::StateCache::ValidationResult result = co_await state_cache_->validate_current_root(*tx);
44+
reply = make_json_content(request, CacheValidationResult{result});
4345
} catch (const std::exception& e) {
4446
SILK_ERROR << "exception: " << e.what() << " processing request: " << request.dump();
4547
reply = make_json_error(request, kInternalError, e.what());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
Copyright 2025 The Silkworm Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
#include "cache_validation_result.hpp"
18+
19+
namespace silkworm::rpc {
20+
21+
void to_json(nlohmann::json& json, const CacheValidationResult& result) {
22+
json["requestCanceled"] = result.ref.request_canceled;
23+
json["enabled"] = result.ref.enabled;
24+
json["latestStateBehind"] = result.ref.latest_state_behind;
25+
json["cacheCleared"] = result.ref.cache_cleared;
26+
json["latestStateID"] = result.ref.latest_state_version_id;
27+
json["stateKeysOutOfSync"] = result.ref.state_keys_out_of_sync;
28+
json["codeKeysOutOfSync"] = result.ref.code_keys_out_of_sync;
29+
}
30+
31+
} // namespace silkworm::rpc
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
Copyright 2025 The Silkworm Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
#pragma once
18+
19+
#include <nlohmann/json.hpp>
20+
21+
#include "../types/cache_validation_result.hpp"
22+
23+
namespace silkworm::rpc {
24+
25+
void to_json(nlohmann::json& json, const CacheValidationResult& result);
26+
27+
} // namespace silkworm::rpc
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
Copyright 2025 The Silkworm Authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
#pragma once
18+
19+
#include <string>
20+
#include <vector>
21+
22+
#include <silkworm/db/kv/api/state_cache.hpp>
23+
24+
namespace silkworm::rpc {
25+
26+
struct CacheValidationResult {
27+
const db::kv::api::StateCache::ValidationResult& ref;
28+
};
29+
30+
} // namespace silkworm::rpc

0 commit comments

Comments
 (0)