Skip to content

Upgrade FSC to v0.20.0 - #2357

Closed
sid200727 wants to merge 5 commits into
LFDT-Panurus:mainfrom
sid200727:fix-2351-bump-fsc-v0.20.0
Closed

sid200727 wants to merge 5 commits into
LFDT-Panurus:mainfrom
sid200727:fix-2351-bump-fsc-v0.20.0

Conversation

@sid200727

@sid200727 sid200727 commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Closes #2351

Second of two PRs for the FSC bump. Stacks on #2356, which takes us from v0.18.0 to v0.19.0, merge that one first, or this diff will show both bumps.

The runtime panic

FSC #1700 removed HasNext() from iterators.Empty. It was never part of the Iterator interface that guarantees only Next() and Close(),but the slice and permutation iterators still have it, which is presumably how the pattern crept in here:

if err == nil && it.(interface{ HasNext() bool }).HasNext() {

cachedFetcher returns a permutation on a cache hit and an empty iterator on a miss. After the upgrade, every cache miss panics. This is production code in mixedFetcher.UnspentTokensIteratorBy, and it compiles cleanly, only the test run catches it.

The fix

cachedFetcher already knows the outcome: the ok from cache.Get. It now reports that alongside the iterator, through an unexported unspentTokensIteratorBy. UnspentTokensIteratorBy stays a thin wrapper over it, so the TokenFetcher interface and its generated mocks are untouched, and mixedFetcher no longer inspects an iterator it has no portable way to inspect.

I kept it to cachedFetcher deliberately, widening the interface method would have meant regenerating mocks/token_fetcher.go and updating the stubs in manager_test.go, ratelimit_test.go, selector_test.go and selector_iterator_test.go, which felt out of proportion for this.

Other breaking changes in this range

FSC #1711 (squirrel removal) also lands here. As confirmed during that PR's review, it needs no source changes on our side, go mod tidy just drops the stale indirect requirements.

Testing

-> All eight modules build, go vet and golangci-lint (v2.12.2, the pinned version) clean
-> Verified the panic's provenance: sherdlock panics on v0.20.0 without this fix, and does not on v0.19.0, so the break arrives with this bump
-> The remaining sherdlock failures and the identity/config TestTranslatePath failure are pre-existing on main and unrelated

Possibly worth a follow-up

The same assertion pattern may exist elsewhere in Panurus, or in other FSC consumers. I only checked sherdlock because that's what failed.
Happy to open a separate issue if that seems worth sweeping for.

Comment thread cmd/tokengen/go.mod Outdated
Comment thread token/services/selector/sherdlock/fetcher.go
Comment thread token/services/selector/sherdlock/fetcher_test.go
@adecaro adecaro self-assigned this Sep 11, 2026
@adecaro adecaro added this to the Q3/26 milestone Sep 11, 2026
Bumps github.com/hyperledger-labs/fabric-smart-client from v0.18.0 to
v0.19.0 across all eight modules.

Three breaking changes land in this range, none of which reach Panurus:

- fix(fabricx)! (FSC LFDT-Panurus#1673) changes ledger.Provider.Context and the
  NewLedger/NewManager return signatures. Panurus imports the fabricx
  finality package for its ListenerManager type but never calls the
  changed methods; the provider is only received as a parameter and
  passed through to wiring.
- refactor(fabric)! (FSC LFDT-Panurus#1668) removes private chaincode support, which
  has no call sites here.
- fix(membership)! (FSC LFDT-Panurus#1663) adds an error return to GetMSPIDs and
  IsIdemixMSP, neither of which is called here.

All eight modules build and vet clean. The sherdlock test failures
present on this branch are pre-existing on main and unrelated.

Refs LFDT-Panurus#2351

Signed-off-by: Siddhi Khandelwal <siddhi.200727@gmail.com>
Bumps github.com/hyperledger-labs/fabric-smart-client from v0.19.0 to
v0.20.0 across all eight modules, and fixes a runtime panic the upgrade
surfaces.

FSC LFDT-Panurus#1700 removed HasNext() from iterators.Empty. It was never part of
the Iterator interface -- that guarantees only Next and Close -- but the
slice and permutation iterators still have it, so sherdlock had come to
depend on it through a type assertion:

    if err == nil && it.(interface{ HasNext() bool }).HasNext() {

cachedFetcher returns a permutation on a cache hit and an empty iterator
on a miss, so after the upgrade every cache miss panics. This is
production code in mixedFetcher.UnspentTokensIteratorBy, and it compiles
cleanly, so only the test run catches it.

cachedFetcher already knows the outcome -- the ok from cache.Get -- so
it now reports that alongside the iterator through an unexported
unspentTokensIteratorBy. UnspentTokensIteratorBy stays a thin wrapper,
leaving the TokenFetcher interface and its generated mocks untouched,
and mixedFetcher no longer inspects an iterator it cannot portably
inspect.

FSC LFDT-Panurus#1711 (squirrel removal) also lands in this range and needs no
source changes here; go mod tidy drops the stale indirect requirements.

All eight modules build, vet and lint clean. The remaining sherdlock
test failures are pre-existing on main and unrelated.

Refs LFDT-Panurus#2351

Signed-off-by: Siddhi Khandelwal <siddhi.200727@gmail.com>
@adecaro
adecaro force-pushed the fix-2351-bump-fsc-v0.20.0 branch from 42216d1 to 141a87a Compare September 11, 2026 08:53

@adecaro adecaro left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: does this fully address #2351?

@sid200727

Approve, with non-blocking comments — the panic fix is correct and verified, two points from @Effi-S's earlier review rounds remain open at this commit.

Checked out fix-2351-bump-fsc-v0.20.0 at 141a87a and verified:

go build ./... (each of the 9 modules individually)         -> clean, all 9
go vet ./token/services/selector/sherdlock/...               -> clean
go test -count=1 ./token/services/selector/sherdlock/...      -> pass
go test -race -count=1 ./token/services/selector/sherdlock/...-> pass, no races
golangci-lint run ./token/services/selector/sherdlock/...     -> 0 issues
revert fetcher.go fix + re-run tests                          -> panics exactly as described

I also read the vendored FSC v0.20.0 source directly to confirm iterators.Empty's empty[K] struct really did drop HasNext() after FSC PR #1700, and grepped the repo for other callers of the same fragile it.(interface{ HasNext() bool }) pattern — found none outside this file.

Reverting fetcher.go's mixedFetcher.UnspentTokensIteratorBy back to the pre-fix type assertion and re-running the suite panics with panic: interface conversion: *iterators.empty[...] is not interface { HasNext() bool }: missing method HasNext at fetcher.go:110, so the existing test suite pins the bug rather than the diff inventing new coverage.

CI has not run yet on this commit — all checks except DCO show pending (fork PR awaiting workflow authorization) — so none of the above is corroborated by CI; all verification here is from local runs.

What it fixes

# Finding Verified
2351.1 FSC v0.20.0 removed HasNext() from the empty iterator, causing a panic on cache miss ✅ confirmed against vendored FSC source, reproduced the panic by reverting the fix
2351.2 Fix avoids the panic without widening the public TokenFetcher interface/mocks ✅ confirmed — unspentTokensIteratorBy is unexported, UnspentTokensIteratorBy signature unchanged

Both rows are solid; the underlying fix is small, correct, and test-covered.

Non-blocking

  • Still open from your earlier review: fabric-smart-client/integration is left at v0.18.0 in every go.mod that references it (e.g. cmd/tokengen/go.mod:18), even though the root FSC module moved to v0.20.0 here. I confirmed integration/v0.20.0 genuinely exists upstream via git ls-remote --tags, so this isn't a "can't go further" case.
  • Still open from your earlier review: the cache-hit invariant (cached=true implies the cached slice is non-empty) is enforced by construction in groupTokensByKey/updateCache, not at the unspentTokensIteratorBy boundary itself, and the updated test (fetcher_test.go:176-186) now only asserts the cached flag rather than the iterator's actual content — so a future regression that violates the invariant would pass silently.
  • Hygiene: PR body says "Refs #2351" rather than Fixes #2351/Closes #2351, so GitHub won't auto-link or close the issue on merge.

Recommendation

Good to merge as-is; the two open threads from @Effi-S are worth resolving in this PR or a fast follow-up, and the Fixes #2351 wording is a one-line fix to the PR body whenever convenient.

return collections.NewEmptyIterator[*token2.UnspentTokenInWallet](), false, nil
}

// isCacheOverused checks if the cache has been queried too many times since the last refresh.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Design note: cache-hit invariant (cached=true implies non-empty) is enforced by construction elsewhere, not at this boundary.

cached here is just whether f.cache.Get found the key — it doesn't check the iterator actually has tokens. Today that's safe because groupTokensByKey (fetcher.go:294-308) only appends to m[key] when a token is found, and updateCache (fetcher.go:312-334) deletes any stale key not present in the newest batch — so no key can ever map to an empty slice. But nothing at this boundary enforces that; it's implicit in two other functions.

This is the same concern @Effi-S raised on this line in the prior review round — still open at this commit, restating with the concrete trace of where the invariant actually lives.

Suggestion: add a short comment here documenting the invariant, or assert on it directly (e.g. treat a cache hit with an empty slice as a bug rather than trusting it can't happen).

it, cached, err := fetcher.unspentTokensIteratorBy(ctx, "wallet1", "USD")

require.NoError(t, err)
assert.NotNil(t, it)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should fix: this now only asserts the cached flag, not that the iterator actually yields a token.

The old assertion (it.(interface{ HasNext() bool }).HasNext()) confirmed the cache hit actually produced tokens. The new one (assert.True(t, cached, ...)) would still pass if a future change let cached=true coincide with an empty iterator — exactly @Effi-S's concern from the prior review round on this test.

Suggestion: additionally assert on the iterator's content, e.g. tok, err := it.Next(); require.NoError(t, err); assert.NotNil(t, tok), so a regression in the invariant discussed on fetcher.go:362-372 would actually fail this test.

Comment thread cmd/tokengen/go.mod Outdated
@sid200727

Copy link
Copy Markdown
Contributor Author

Thank you @adecaro for the detailed review, and for reproducing the panic against the vendored source, that's more verification than I'd done myself.
Taking all three: the boundary guard on the cache-hit invariant, the fabric-smart-client/integration bump across the eight go.mod files, and Refs → Closes in the PR body. The first two are the same points @Effi-S raised earlier and I should have addressed them in the previous round.

@sid200727

Copy link
Copy Markdown
Contributor Author

Bumped fabric-smart-client/integration, it turned out to need more than the version line. FSC #1642 refactored the topology package, so Chaincode.Policy is gone (we were setting SignaturePolicy to the same value anyway), AddNamespaceWithUnanimity is replaced by AddNamespace with a topology.Unanimity policy, and AddNamespace now takes a typed EndorsementPolicy plus options. Three call sites, all migrated. Only go build verifies these locally the integration suites need a full network.

Bumps fabric-smart-client/integration to v0.20.0 alongside the root
module. It was left at v0.18.0 in the first pass because the version
grep matched only the root module path.

That submodule carries its own breaking changes, which the root module's
conventional-commit markers do not cover. FSC LFDT-Panurus#1642 extracted namespace
and policy handling out of topology.go: Chaincode.Policy is gone
(SignaturePolicy already carried the same value here),
AddNamespaceWithUnanimity is replaced by AddNamespace with a
topology.Unanimity policy, and AddNamespace now takes a typed
EndorsementPolicy and functional options rather than a rule string and
variadic peers. A verbatim Signature policy carries no orgs, so
AddNamespace cannot derive peers from it; the custom-policy path passes
them explicitly via WithPeers to keep the previous behaviour.

Also enforces the cache-hit invariant that mixedFetcher relies on.
groupTokensByKey only creates a key when it finds a token, so a cached
key cannot map to an empty slice today, but nothing at the read boundary
checked it and permutatableIterator offers no way to check emptiness
without consuming the iterator. updateCache now refuses to store an
empty entry, and the cache-hit test asserts the iterator yields a token
rather than only that the flag was set.

Refs LFDT-Panurus#2351

Signed-off-by: Siddhi Khandelwal <siddhi.200727@gmail.com>
FSC LFDT-Panurus#1642 re-signatured Platform.UpdateChaincode from
(name, version, path, file string) to
(name, version string, opts ...topology.NamespaceOption). tcc.go declares
a local fabricPlatform interface asserting the old form, so the type
assertion in GenericBackend.Fabric compiled but panicked at runtime:

  interface conversion: *fabric.Platform is not cc.fabricPlatform:
  missing method UpdateChaincode

The interface now declares the new signature, and the call site passes
path and file as WithLegacyChaincode and WithPackageFile.

Also swept the rest of the integration tree for interfaces asserted
against FSC types. The only other one, deleteVaultPlatform in
support.go, declares DeleteVault(string), which is unchanged in v0.20.0
and asserted with comma-ok in any case.

Refs LFDT-Panurus#2351

Signed-off-by: Siddhi Khandelwal <siddhi.200727@gmail.com>
FSC LFDT-Panurus#1724 moved the fabric-x tool lookup into a fabric-x subdirectory of
FAB_BINS, so the two toolchains can be installed side by side without
their identically named configtxgen and cryptogen overwriting each
other. The lookup is a subdirectory rather than a fallback: a missing
fabric-x tool is an error instead of a silent run of fabric's.

The fxconfig and configtxgen targets still installed into $(FAB_BINS),
so after the v0.20.0 bump every fabricx suite panicked during artifact
generation with 'could not find configtxgen in FAB_BINS directory
.../bin/fabric-x'. They now install into $(FABRIC_X_BINS), mirroring
FSC's own Makefile.

Refs LFDT-Panurus#2351

Signed-off-by: Siddhi Khandelwal <siddhi.200727@gmail.com>
@adecaro

adecaro commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Hi @sid200727 , if it is okay, I'll close this. There were many things to change to make the integration tests passing again.
I have fixed what was necessary in #2360.

Thanks for you effort. I learned from it 🙏

@sid200727

Copy link
Copy Markdown
Contributor Author

Of course, go ahead and close it. That's the right call, the integration side needed a working fabric-x environment to iterate on, and CI round-trips weren't a sensible way to get there.

Glad the sherdlock fix and the UpdateChaincode / FABRIC_X_BINS findings were useful.
Thank you @adecaro @Effi-S for picking it up, and for the reviews along the way, I learned a lot from them too 🙏

@adecaro

adecaro commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

thanks @sid200727 🙏

@adecaro adecaro closed this Sep 14, 2026
@adecaro
adecaro deleted the fix-2351-bump-fsc-v0.20.0 branch September 14, 2026 08:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Upgrade FSC to v0.21.0

3 participants