惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
博客园 - 【当耐特】
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - Franky
罗磊的独立博客
T
Tenable Blog
N
News and Events Feed by Topic
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Security Archives - TechRepublic
Security Archives - TechRepublic
博客园 - 司徒正美
L
LINUX DO - 最新话题
AI
AI
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
S
Secure Thoughts
IT之家
IT之家
aimingoo的专栏
aimingoo的专栏
Last Week in AI
Last Week in AI
W
WeLiveSecurity
博客园_首页
Forbes - Security
Forbes - Security
博客园 - 聂微东
宝玉的分享
宝玉的分享
Google DeepMind News
Google DeepMind News
V
Visual Studio Blog
Microsoft Security Blog
Microsoft Security Blog
Vercel News
Vercel News
小众软件
小众软件
Webroot Blog
Webroot Blog
V2EX - 技术
V2EX - 技术
博客园 - 叶小钗
T
The Exploit Database - CXSecurity.com
TaoSecurity Blog
TaoSecurity Blog
L
Lohrmann on Cybersecurity
I
InfoQ
J
Java Code Geeks
P
Privacy International News Feed
Spread Privacy
Spread Privacy
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
B
Blog RSS Feed
阮一峰的网络日志
阮一峰的网络日志
D
Docker
P
Proofpoint News Feed
B
Blog
Cisco Talos Blog
Cisco Talos Blog
M
MIT News - Artificial intelligence
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
T
Threat Research - Cisco Blogs
云风的 BLOG
云风的 BLOG
Recent Announcements
Recent Announcements

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Migrating Hyperledger Fabric Chaincode to Fabric-X: A Working Proof-of-Concept
Aaradhy Chin · 2026-05-10 · via DEV Community

A working PoC, what changes in the programming model, and what I learned getting public review from an FSC core maintainer along the way.


If you maintain a Hyperledger Fabric chaincode in production today, there's a question quietly hovering over your roadmap: what happens to my application on Fabric-X?

Fabric-X — Hyperledger's next-generation re-architecture — keeps the governance model, the X.509 PKI, and the execute-order-validate transaction lifecycle. But it removes three things you've probably built habits around: the monolithic peer, the SmartBFT/Raft orderer, and chaincode itself. Business logic that used to live inside a Go chaincode binary now lives inside views running on Fabric-Smart-Client (FSC) nodes.

So: what does your chaincode look like in this new world?

I built a working proof-of-concept that answers that question for the canonical asset-transfer-basic Go chaincode — the one every Fabric tutorial starts with. All eight chaincode methods are ported to FSC views. The example boots a real Fabric-X network via FSC's NWO test harness. Along the way I designed receiver-acceptance into asset transfers, surfaced the Query Service's range-query gap honestly, and produced a tutorial-style README for chaincode developers who have never touched FSC.

The numbers, for the curious: 5 FSC nodes, 3 organisations, 1 namespace, 11 views, 18 files, 1,929 lines. Compiles cleanly. ginkgo --dry-run passes.

This post is the story of how I got there, the design decisions that mattered, and what I learned from doing all of it in public.


Why this PoC exists

I'd been deep-reading the Hyperledger and LFDT codebases preparing for the 2026 LFDT mentorship cycle. Walking through hyperledger-labs/fabric-smart-client, I noticed integration/fabricx/ already had simple, iou, multiendorsement, and deployment examples. There was a clear template. What was missing was the migration tutorial — the example that takes a chaincode every Fabric developer recognises and walks them through the rewrite.

The mentorship issue — LFDT mentorship #59: Exploring Chaincode Support for Hyperledger Fabric-X — was asking for exactly that. Better, an earlier applicant (@aaradhychinche-alt) had already started a stub PoC and gotten public review from one of the mentors: Marcus Brandenburger (IBM Research Zurich, Fabric-Smart-Client core maintainer).

Marcus's feedback on that earlier attempt is short and surgical. It shaped most of the design choices below:

"I really like the idea of having a 'tutorial style' example here that illustrates new users how to implement an existing 'chaincode' to a FSC-based view application."

"While I believe the initial version can be very simple — I think we should include a real FSC + FabricX network in the initial version … it's just more fun :)"

"Let's start with defining a topology. Who are the FSC nodes/apps? You need at least one FSC node to execute some business logic and being responsible to endorse the transactions… We can be creative here."

"You should move forward with this and start porting the Asset Transfer logic into views; setup fsc nodes; think about how to spinnup a fabric-x network (for example, you could use our integration test suite (nwo))."

That feedback is gold for an applicant. It told me three things specifically:

  1. The PoC needs a real Fabric-X network, not a single-process simulation.
  2. The PoC needs a deliberate topology, not implicit roles.
  3. The right harness is NWO, FSC's integration framework.

I built the PoC against those three constraints.


The topology — and why these five nodes

The earlier PoC simulated everything in a single Go process with an in-memory map. Mine doesn't. Five FSC nodes, three Fabric organisations, one Fabric-X namespace approved by Org1 under unanimity:

node org role description
issuer Org1 initiator InitLedger, CreateAsset
endorser Org1 approver validates every state-changing tx — the chaincode replacement
auditor Org1 observer responder on every state-changing tx
alice Org2 owner initiates Update / Delete / Transfer; receives transfers
bob Org3 owner same shape as alice

Why these five and not, say, two?

Three reasons.

1. The endorser is the chaincode replacement. In classical Fabric, the chaincode binary on the endorsing peer is what said "yes, this CreateAsset is valid; here is my signature." In FSC-on-Fabric-X that role becomes a responder view on a dedicated FSC node. By giving it its own node, and only its own node, the scv2.WithApproverRole() option, the parallel is one-to-one and obvious to a reader.

2. Splitting issuer from owners is a security upgrade. Classical chaincode lets anyone with rights to invoke the chaincode write any Owner string on an asset. In my topology, only the issuer can create assets and only the asset's current owner can update or transfer it. The auditor witnesses every state change. None of those guarantees were available in the chaincode model without writing them into the chaincode body — and even then, they relied on the endorsing peer running the binary you expected.

3. The auditor pattern is interesting in its own right. Auditors on classical Fabric usually retrofitted themselves by emitting chaincode events and scraping them off-chain. In FSC the auditor is just an FSC node registered as a responder on every state-changing initiator view. Every transfer, every create, every delete pulls the auditor into the endorsement collection automatically. That's a topology-level concern, not application code.

           Client tests / CLI
                  │
   ┌──────────────┼──────────────┐
   ▼              ▼              ▼
issuer          alice           bob
   │              │              │
   └──────┬───────┴──────┬───────┘  ← CollectEndorsements
          │              │
          ▼              ▼
      endorser        auditor       (responders)
          │
          ▼
   Fabric-X Arma orderer  →  Sidecar → Coordinator → Validator-Committer
          │                                  │
          └──────────────────────────────────┴──→ Query Service (reads)

Enter fullscreen mode Exit fullscreen mode


The eight chaincode methods, mapped

The original chaincode is 194 lines and exposes eight methods. Each one becomes one or more FSC views.

Chaincode method FSC view(s) Notes
InitLedger InitLedgerView One transaction, six AddOutput calls
CreateAsset CreateAssetView No inputs, one output; existence check at endorser
ReadAsset ReadAssetView Query-only, no transaction
UpdateAsset UpdateAssetView AddInputByLinearID + AddOutput
DeleteAsset DeleteAssetView Input with no matching output → state deletion
AssetExists AssetExistsView Query-only, returns bool
TransferAsset TransferAssetView + TransferAssetReceiverView Receiver-acceptance pattern
GetAllAssets GetAllAssetsView Range queries unsupported on Fabric-X — explicit-ID-list

Two of those rows deserve their own section.


The architectural highlight: receiver acceptance on transfer

The chaincode TransferAsset mutates the asset's Owner field and PutStates. The new owner has no say in whether they receive the asset:

// classical chaincode
func (s *SmartContract) TransferAsset(ctx, id, newOwner string) (string, error) {
    asset, _ := s.ReadAsset(ctx, id)
    oldOwner := asset.Owner
    asset.Owner = newOwner   // <-- new owner did not consent to this
    json, _ := json.Marshal(asset)
    return oldOwner, ctx.GetStub().PutState(id, json)
}

Enter fullscreen mode Exit fullscreen mode

That works because the chaincode is the only thing signing the transaction body. Anyone who can produce a valid endorsement can transfer assets to anyone — there is no per-key endorser identity at chaincode level.

The FSC version makes the receiver a participant. Here is the heart of TransferAssetView:

// Collect endorsements: receiver first, so the receiver's refusal
// short-circuits before we bother the endorser; then the endorser
// (chaincode logic); then the auditor.
_, err = viewCtx.RunView(state.NewCollectEndorsementsView(
    tx,
    t.NewOwner,    // ← receiver-acceptance: new owner must sign
    t.Endorser,
    t.Auditor,
))

Enter fullscreen mode Exit fullscreen mode

The new owner's FSC node runs TransferAssetReceiverView (registered as a responder in the topology). It inspects the proposed transaction and signs only if it accepts:

func (r *TransferAssetReceiverView) Call(viewCtx view.Context) (interface{}, error) {
    tx, err := state.ReceiveTransaction(viewCtx)
    // Real production code: hook business rules here — credit checks,
    // AML, anti-self-dealing, etc.
    out := &states.Asset{}
    tx.GetOutputAt(0, out)
    assert.NotEmpty(out.Owner, "Receiver: refusing transfer with empty Owner")
    return viewCtx.RunView(state.NewEndorseView(tx))
}

Enter fullscreen mode Exit fullscreen mode

This is a strict tightening of the chaincode security model. A buggy or malicious initiator can no longer force an asset onto an unwilling new owner. Receiver acceptance is part of the protocol. The chaincode could not express this; FSC views give it to us almost for free.

The migration is, in places, an opportunity to do better than the original.


The architectural sharp edge: GetStateByRange is gone

Classical chaincode walks every key in its namespace with ctx.GetStub().GetStateByRange("", ""). The Fabric-X Query Service does not support range queries. This is not a temporary limitation — it is documented in the FSC platform code itself:

// platform/fabricx/core/vault/vault.go
// GetStateRange returns an error as range queries are not
// supported by the QueryService.
func (qe *queryExecutor) GetStateRange(...) (..., error) {
    return nil, errors.New("GetStateRange not supported by VaultX QueryService")
}

Enter fullscreen mode Exit fullscreen mode

That's the migration's most important sharp edge. A tutorial that doesn't surface it is doing the reader a disservice. Three workarounds, in increasing order of complexity:

1. Explicit-ID-list (what my PoC does)

The caller passes the IDs to resolve. qs.GetStates(...) does a single-round-trip multi-key fetch. Suits registry-style apps where the caller already tracks issued IDs. Zero on-chain bookkeeping.

2. Index-key pattern

A dedicated key holds the JSON-encoded list of live asset IDs. Every Create / Delete also updates the index. The endorser must check the index update is internally consistent. Cost: one extra output per state-changing transaction.

3. Off-chain index

An FSC node subscribes to namespace commit events via finality listeners and maintains a local view. Best for analytics-style queries; weakest consistency on cold start.

My PoC implements pattern 1, names the trade-off explicitly in the view's godoc, and documents all three in the README. Honesty is part of tutorial quality.


The endorser, where the conceptual centre lives

This is the file I'm proudest of.

In classical Fabric, every chaincode function had its own existence check, its own ownership check, its own no-op-transfer guard. Those checks ran on the endorsing peer. The chaincode binary was the only thing that could produce a valid endorsement, so the checks were trusted.

In my FSC version, the same checks live in a single EndorserView on the endorser FSC node, dispatched on the transaction's command name:

switch cmd := tx.Commands().At(0); cmd.Name {
case "init":      // 0 inputs, 6 outputs, ascending IDs, none pre-existing
case "create":    // 0 inputs, 1 output, output's ID does not exist
case "update":    // 1 input,  1 output, IDs match
case "delete":    // 1 input,  0 outputs
case "transfer":  // 1 input,  1 output, only Owner changes
}

Enter fullscreen mode Exit fullscreen mode

The mapping from chaincode method to command is one-to-one. Adding a new chaincode method means adding one new case. The validation logic stays in one place, gets one place to evolve, and is testable in isolation.

A subtle bonus: the endorser additionally requires that on transfer, Color, Size, and AppraisedValue do not change. The chaincode TransferAsset only updated Owner, but nothing prevented a buggy client from also mutating Size. FSC + namespace endorsement lets us tighten that.

Migration as upgrade, again.


The Asset struct: byte-equivalent on purpose

One small but useful design decision — the Asset struct preserves the chaincode's exact JSON shape:

type Asset struct {
    AppraisedValue int    `json:"AppraisedValue"`
    Color          string `json:"Color"`
    ID             string `json:"ID"`
    Owner          string `json:"Owner"`
    Size           int    `json:"Size"`
}

Enter fullscreen mode Exit fullscreen mode

Same field names, same field tags, same field order. This means:

  • An off-chain indexer that watched the chaincode-era state can read post-migration state without re-tooling.
  • An app that hashes on-chain payloads gets the same hash before and after.
  • Tests that assert on payload bytes don't need to be rewritten.

The only addition is a GetLinearID() method on the struct (FSC's state package needs it to derive the world-state key from an object). It returns a.ID. Migration friction: zero.

The tutorial's first pleasant surprise: the on-chain payload doesn't have to change. The migration is about who runs what code, not what data lives on the ledger.


What it actually feels like to run

End-to-end, the test reads top-to-bottom as a tutorial. Each By(...) block names a chaincode method and the equivalent FSC view:

By("InitLedger — chaincode wrote 6 assets via PutState; FSC writes 6 outputs in one tx")
initLedger(s.II)

By("ReadAsset — chaincode used GetState; FSC uses Query Service")
a1 := readAsset(s.II, IssuerNode, "asset1")

By("CreateAsset — happy path: brand-new ID")
createAsset(s.II, IssuerNode, &states.Asset{...})

By("CreateAsset — negative path: duplicate ID is rejected by the endorser")
createAssetExpectFail(s.II, IssuerNode, &states.Asset{...})

By("TransferAsset — chaincode mutated Owner unilaterally; FSC requires receiver acceptance")
old := transferAsset(s.II, AliceNode, "asset7", "bob", BobNode)
Expect(old).To(Equal("alice"))

Enter fullscreen mode Exit fullscreen mode

A reviewer reading this test learns the migration story by reading down the page. Negative paths sit next to happy paths so a future contributor sees what each view rejects, not just what it accepts. Tutorial-as-test is one of the patterns I'll keep using.


What I learned (technical)

A non-exhaustive list of things I now actually understand that I only sort-of understood three weeks ago:

  1. Why Fabric-X dropped chaincode. It wasn't a fashion choice. The chaincode-as-a-process model bottlenecks at endorsement; serialised transaction simulation is exactly the wrong shape if you're trying to push 200k+ TPS. Replacing the binary boundary with a P2P negotiation between FSC nodes lets the orderer batch and shard far more aggressively.

  2. What "view" actually means in FSC. A view is a Go object that implements Call(ctx) (interface{}, error). That's it. The framework gives you helpers for the common shapes — state.NewTransaction, state.NewCollectEndorsementsView, state.NewOrderingAndFinalityWithTimeoutView, queryservice.GetQueryService, finality listeners. The view's job is to compose those helpers into a sensible per-method workflow.

  3. Initiator vs responder is a topology decision, not a code decision. The same view code can be played as an initiator on one node and a responder on another. The topology decides who runs what.

  4. The Fabric-X namespace is the new endorsement-policy boundary. What used to be endorsement_policy in chaincode lifecycle is now AddNamespaceWithUnanimity(...) plus the RegisterResponder topology. Same idea, different surface.

  5. Range queries are not coming back in their classical form. The Query Service is fundamentally a batched key-fetch. If your application depended on rich CouchDB queries, you have real architectural work ahead.

  6. NWO is unreasonably good. It's basically a one-line "start me a real Fabric-X network with these orgs and these FSC nodes" harness. Five of the trickiest pages of Fabric setup get reduced to a topology declaration. I want this on every distributed-systems project I work on.


What I learned (the human side)

Three things stand out.

1. Mentor feedback is a gift you can read like an instruction manual

Marcus Brandenburger's review on the earlier applicant's PoC was two paragraphs. Each sentence in those paragraphs maps to a concrete code-level decision in mine.

  • "Tutorial style" → 407-line README with a side-by-side migration mapping table.
  • "Real FSC + FabricX network" → NWO-driven chaincode_to_fsc_test.go.
  • "Define a topology" → an explicit five-node, three-org topology.go with a Mermaid diagram.
  • "Use NWO" → the test boots Arma + the Fabric-X-Committer microservices for real.

When a maintainer takes the time to write public feedback, the right response is to internalise it line by line and answer it with code.

2. Reading other people's working code is the fastest way to learn a framework

I didn't invent the structure of any of the views in this PoC. The shape of CreateAssetView came from integration/fabricx/simple/views/create.go. The receiver-acceptance pattern came from integration/fabric/iou/views/lender.go. The endorser dispatch came from integration/fabric/iou/views/approver.go.

Mimicry-then-deviation is more efficient than first-principles design when you're new to a framework — and it produces code maintainers can read at a glance.

3. Tutorial writing is its own engineering discipline

Writing the README took almost as long as writing the views. Every diagram had to be diffable Mermaid. Every code snippet had to render cleanly in GitHub Flavored Markdown. Every section had to answer "what does the reader want next?" rather than "what do I want to say next?"

The audience profile — a Hyperledger Fabric chaincode developer with three years of experience who has never used FSC — sits at my elbow on every paragraph I write. Without that profile the writing drifts into self-indulgence; with it the writing stays useful.


What working in public taught me

Everything I've done so far has been on the record — issue threads, repo branches, README PRs. That's how the LFDT community works, and it changes the shape of the work in three useful ways.

1. You write for the next person, not just the mentor

The mentor reads this once. The next applicant, the next maintainer, the next adopter will read it many times. So the README is structured for them. The negative-path tests are commented for them. The EndorserView godoc names the chaincode method each case corresponds to so that someone porting from the chaincode side sees the bridge instantly.

2. You build on top of other people's work, on the record

The earlier applicant's PoC is acknowledged in my proposal. Marcus's review is quoted verbatim. The NWO patterns are cited to the file that taught me each one. Open-source is an iterated game; you don't get to pretend you started from scratch.

3. Speed of iteration is set by the mentor's bandwidth, not yours

I learned to package my asks as small, decision-shaped questions: "Q1 — On the docs IA: do you want goal-oriented nav, or is the legacy split preserved?" rather than open-ended "thoughts?".

Decision-shaped questions get answered in minutes; open-ended ones can hang for weeks. That's a discipline I'll carry into every collaboration with a busy maintainer.


What I'd do next

The PoC as it stands answers the migration question for asset-transfer-basic. Two natural follow-ons:

  1. Port asset-transfer-private-data. Fabric-X has no private data collections; the choice between "separate namespace per privacy boundary" and "hash-on-ledger / body-via-P2P" is the most architecturally interesting question that asset-transfer-basic doesn't surface. The mentorship's full plan includes this as the second demo.

  2. Build a reusable index-key range-scan helper. If every team migrating off GetStateByRange has to invent the same index-key pattern, that's exactly the right shape for a small reusable view in the FSC platform repo.

A bonus I'd love to do: a meetup talk and a recorded walkthrough. If you'd like to host that, get in touch.


Acknowledgments

  • Marcus Brandenburger (IBM Research Zurich) for the kind of public review that teaches the next applicant exactly what good looks like.
  • Maria Munaro and Samuel Venzi (GoLedger) for serving as mentors on this issue.
  • The FSC and Fabric-X maintainers at IBM Research and across the LFDT community, whose integration/fabricx/{simple,iou,multiendorsement,deployment} examples were my primary teachers.
  • The earlier applicant (@aaradhychinche-alt) whose initial PoC opened the issue and gave the community something concrete to react to.

Links


If you're a chaincode developer who has been quietly wondering whether your applications have a future on Fabric-X, the answer is yes — and the migration is, in places, an opportunity to do better than the original. I'd love to hear what you'd port next.

Comments and corrections welcome on the GitHub issue or via the LFDT Discord.