-
-
Notifications
You must be signed in to change notification settings - Fork 369
getusedcoinstags rpc fixed #1685
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughRefactors RPC getusedcoinstags to read mobile-specific spend tags. Adds mobileUsedLTags storage, manages it in CSparkState.Reset/AddSpend/RemoveSpend, and exposes GetSpendsMobile(). RPC iteration/storage uses std::vector<std::pair<GroupElement,int>> while element serialization format is unchanged. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Client
participant RPC as getusedcoinstags
participant State as CSparkState
Client->>RPC: Request used coin tags (mobile)
RPC->>State: GetSpendsMobile()
State-->>RPC: vector<(GroupElement,int)>
RPC-->>Client: Serialized tags
rect rgba(230,240,255,0.6)
note over State: Spend lifecycle updates mobileUsedLTags
participant Wallet
Wallet->>State: AddSpend(tag, mobile=true)
State-->>State: mobileUsedLTags.push_back((tag,idx))
Wallet->>State: RemoveSpend(tag, mobile=true)
State-->>State: erase tag from mobileUsedLTags
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (3 warnings)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used🧬 Code graph analysis (1)src/spark/state.cpp (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
🔇 Additional comments (4)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (6)
src/spark/state.h (2)
240-241: Add thread-safety note for accessor.GetSpendsMobile() exposes internal state; callers must hold cs_main while accessing/copying to avoid races. Please add a brief comment mirroring other getters.
267-269: Consider determinism and removal cost trade-offs.Storing a second structure is fine for mobile, but be aware:
- Order depends on insertion sites; ensure upstream insertion order is deterministic network-wide.
- RemoveSpend is O(N). Acceptable for rare reorgs; otherwise consider an index map for O(1) removal.
src/rpc/misc.cpp (2)
1551-1559: Ordering semantics: ensure network-wide determinism.startNumber assumes a stable, globally consistent order. With a vector this is insertion order; verify upstream insertion is deterministic across nodes, or sort by a canonical key (e.g., primitives::GetLTagHash). See suggestion on ConnectBlockSpark.
1590-1616: Unify with mobile ordering in tags+txids RPC.getusedcoinstagstxhashes still uses GetSpends() (unordered_map) with startNumber filtering, which is non-deterministic. Use GetSpendsMobile() and guard with -mobile for consistency.
- std::unordered_map<GroupElement, int, spark::CLTagHash> tags; + std::vector<std::pair<GroupElement, int>> tags; std::unordered_map<uint256, uint256> ltagTxhash; { LOCK(cs_main); - tags = sparkState->GetSpends(); + if (!GetBoolArg("-mobile", false)) { + throw std::runtime_error(std::string("Please rerun Firo with -mobile ")); + } + tags = sparkState->GetSpendsMobile(); ltagTxhash = sparkState->GetSpendTxIds(); } - int i = 0; - for ( auto it = tags.begin(); it != tags.end(); ++it, ++i) { + int i = 0; + for (auto it = tags.begin(); it != tags.end(); ++it, ++i) { if (cmp::less((tags.size() - i - 1), startNumber)) continue; std::vector<unsigned char> serialized; serialized.resize(34); - it->first.serialize(serialized.data()); + it->first.serialize(serialized.data()); std::vector<UniValue> data; data.push_back(EncodeBase64(serialized.data(), 34)); uint256 txid; - uint256 ltagHash = primitives::GetLTagHash(it->first); + uint256 ltagHash = primitives::GetLTagHash(it->first); if (ltagTxhash.count(ltagHash) > 0) txid = ltagTxhash[ltagHash]; data.push_back(EncodeBase64(txid.begin(), txid.size())); UniValue entity(UniValue::VARR); entity.push_backV(data); serializedTagsTxIds.push_back(entity); }src/spark/state.cpp (2)
1093-1101: Make insertion order deterministic across nodes.mobileUsedLTags preserves insertion order, but AddSpend() is fed from pblock->sparkTxInfo->spentLTags, which is an unordered_map. Within a block with multiple spends, iteration order may differ by platform/build, breaking clients that rely on startNumber.
Sort the per-block spends in ConnectBlockSpark before calling AddSpend():
- BOOST_FOREACH (auto& lTag, pblock->sparkTxInfo->spentLTags) { - pindexNew->spentLTags.insert(lTag); - sparkState.AddSpend(lTag.first, lTag.second); - } + // Ensure deterministic order for downstream mobile sync + std::vector<std::pair<GroupElement,int>> orderedSpends( + pblock->sparkTxInfo->spentLTags.begin(), pblock->sparkTxInfo->spentLTags.end()); + std::sort(orderedSpends.begin(), orderedSpends.end(), + [](auto const& a, auto const& b){ + return primitives::GetLTagHash(a.first) < primitives::GetLTagHash(b.first); + }); + for (auto const& lTag : orderedSpends) { + pindexNew->spentLTags.insert(lTag); + sparkState.AddSpend(lTag.first, lTag.second); + }Add at file top if needed:
+#include <algorithm>
1108-1115: Prefer erase-remove to avoid iterator invalidation and remove duplicates if any.Current loop erases the first match; use erase-remove for clarity and safety.
- if (GetBoolArg("-mobile", false)) { - for (auto tag = mobileUsedLTags.begin(); tag != mobileUsedLTags.end(); tag++) { - if (tag->first == lTag) { - mobileUsedLTags.erase(tag); - break; - } - } - } + if (GetBoolArg("-mobile", false)) { + mobileUsedLTags.erase( + std::remove_if(mobileUsedLTags.begin(), mobileUsedLTags.end(), + [&](const std::pair<GroupElement,int>& p){ return p.first == lTag; }), + mobileUsedLTags.end()); + }And include:
+#include <algorithm>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/rpc/misc.cpp(1 hunks)src/spark/state.cpp(4 hunks)src/spark/state.h(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/spark/state.h (1)
src/libspark/spend_transaction.h (1)
vector(57-97)
src/spark/state.cpp (1)
src/spark/primitives.cpp (2)
tag(43-49)tag(43-43)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: build-guix-aarch64-linux-gnu
- GitHub Check: build-guix-x86_64-apple-darwin
- GitHub Check: build-windows-cmake
- GitHub Check: build-guix-x86_64-w64-mingw32
- GitHub Check: build-guix-arm64-apple-darwin
- GitHub Check: build-guix-x86_64-linux-gnu
- GitHub Check: Create Source Distribution
- GitHub Check: build-mac-cmake
- GitHub Check: build-linux-cmake
🔇 Additional comments (2)
src/spark/state.cpp (2)
959-960: LGTM: reset covers mobile vector too.
1494-1496: LGTM: accessor matches new storage.
| std::vector<std::pair<GroupElement, int>> tags; | ||
| { | ||
| LOCK(cs_main); | ||
| tags = sparkState->GetSpends(); | ||
| tags = sparkState->GetSpendsMobile(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard RPC behind -mobile or provide a fallback.
tags now come from GetSpendsMobile(), which is only populated when -mobile=true. Without a guard, this RPC silently returns empty arrays on nodes started without -mobile. Align with other mobile RPCs and fail fast (or fallback to GetSpends()).
Suggested fix: fail if -mobile is not set.
spark::CSparkState* sparkState = spark::CSparkState::GetState();
+ if (!GetBoolArg("-mobile", false)) {
+ throw std::runtime_error(std::string("Please rerun Firo with -mobile "));
+ }
std::vector<std::pair<GroupElement, int>> tags;
{
LOCK(cs_main);
tags = sparkState->GetSpendsMobile();
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| std::vector<std::pair<GroupElement, int>> tags; | |
| { | |
| LOCK(cs_main); | |
| tags = sparkState->GetSpends(); | |
| tags = sparkState->GetSpendsMobile(); | |
| } | |
| // Acquire the Spark state | |
| spark::CSparkState* sparkState = spark::CSparkState::GetState(); | |
| // Ensure mobile mode is enabled, otherwise fail fast | |
| if (!GetBoolArg("-mobile", false)) { | |
| throw std::runtime_error(std::string("Please rerun Firo with -mobile ")); | |
| } | |
| std::vector<std::pair<GroupElement, int>> tags; | |
| { | |
| LOCK(cs_main); | |
| tags = sparkState->GetSpendsMobile(); | |
| } |
src/spark/state.cpp
Outdated
|
|
||
| void CSparkState::RemoveSpend(const GroupElement& lTag) { | ||
| auto iter = usedLTags.find(lTag); | ||
| if (GetBoolArg("-mobile", false)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It makes sense to move the whole mobile part behind if (iter != usedLTags.end()) check to avoid long loop if there is no tag in our state
In Spark state we keep used coin tags inside a unordered_map,
For mobile we have an api to get that container in sectors, as the order is not predefined and can differ across servers,
We had sync issues, on mobile.
To solve that we add one more container with fixed order, in case -mobile flag is passed, and we use that container in RPC call.