commit 59a07069b193cbc143d8d6fc16a9a965b03aae6b
parent 74c7d4e4292895f73338c769995bfe3c7b3c20d1
Author: maydayv7 <maydayv7@gmail.com>
Date: Fri, 3 Oct 2025 23:19:08 +0530
feat(chaincode): Determinism + Minor Errors
Diffstat:
4 files changed, 68 insertions(+), 58 deletions(-)
diff --git a/README.md b/README.md
@@ -5,7 +5,7 @@ The model is described [here](./docs/MODEL.md).
---
-To run the app, follow all sections in order:
+To run the app for the first time, follow all sections in order:
### A. Backend
@@ -27,6 +27,8 @@ curl -sSLO https://raw.githubusercontent.com/hyperledger/fabric/main/scripts/ins
./install-fabric.sh
```
+Ensure the `config` directory is present inside `fabric-samples`
+
3. Start Fabric `test-network` and create channel:
```
@@ -35,6 +37,14 @@ cd fabric-samples/test-network
./network.sh up createChannel -ca
```
+After initial setup, you can use the following commands:
+
+```
+./network.sh up # Start network
+./network.sh down # Reset everything -> Need to setup again
+
+```
+
4. Deploy Chaincode:
```
@@ -49,7 +59,7 @@ cd fabric-samples/test-network
cp organizations/peerOrganizations/org1.example.com/connection-org1.json /path/to/MATIRU/backend/connection-org1.json
cd /path/to/MATIRU/backend
mkdir -p wallet
-node scripts/addToWallet.js org1 /path/to/fabric-samples/TEST-NETWORK/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp ./wallet USER_NAME
+node addToWallet.js org1 /path/to/fabric-samples/TEST-NETWORK/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp ./wallet USER_NAME
```
6. The `.env` file must be created in `backend` like so:
@@ -60,7 +70,7 @@ PORT=4000
# JWT
JWT_SECRET=some_secret
-TOKEN_EXPIRES_IN=1h
+TOKEN_EXPIRES_IN=24h
# Fabric Connection
CCP_PATH=connection-org1.json
diff --git a/chaincode/index.js b/chaincode/index.js
@@ -1,7 +1,6 @@
"use strict";
const { Contract } = require("fabric-contract-api");
-const { v4: uuidv4 } = require("uuid");
class ProduceContract extends Contract {
// Helper: throw if missing
@@ -17,10 +16,23 @@ class ProduceContract extends Contract {
await ctx.stub.putState(id, Buffer.from(JSON.stringify(obj)));
}
- // Utility: create action item
- _actionItem(action, location, owner, meta) {
+ // Helper: deterministic timestamp from tx context
+ _txTimestampISO(ctx) {
+ const ts = ctx.stub.getTxTimestamp();
+ // ts.seconds may be a Long object in some environments
+ const seconds =
+ typeof ts.seconds === "object" &&
+ typeof ts.seconds.toNumber === "function"
+ ? ts.seconds.toNumber()
+ : Number(ts.seconds);
+ const millis = seconds * 1000 + Math.floor((ts.nanos || 0) / 1e6);
+ return new Date(millis).toISOString();
+ }
+
+ // Utility: create deterministic action item (uses tx timestamp)
+ _actionItem(ctx, action, location, owner, meta) {
return {
- timestamp: new Date().toISOString(),
+ timestamp: this._txTimestampISO(ctx),
action,
currentLocation: location || "",
currentOwner: owner || "",
@@ -35,10 +47,11 @@ class ProduceContract extends Contract {
// 1. registerProduce(farmerId, details)
async registerProduce(ctx, farmerId, detailsStr) {
- const details = JSON.parse(detailsStr);
- const id = `PRODUCE-${uuidv4()}`;
- const now = new Date().toISOString();
+ const details = JSON.parse(detailsStr || "{}");
+ const txId = ctx.stub.getTxID();
+ const now = this._txTimestampISO(ctx);
+ const id = `PRODUCE-${txId}`;
const produce = {
id,
parentId: null,
@@ -62,12 +75,11 @@ class ProduceContract extends Contract {
};
produce.actionHistory.push(
- this._actionItem("REGISTER", produce.currentLocation, farmerId, {
+ this._actionItem(ctx, "REGISTER", produce.currentLocation, farmerId, {
note: details.note || "",
})
);
- // Save produce
await this._putState(ctx, id, produce);
// Update farmer profile (simple)
@@ -89,8 +101,6 @@ class ProduceContract extends Contract {
// 2. updateLocation(produceId, actorId, inTransitOrNewLocation)
async updateLocation(ctx, produceId, actorId, newLocationOrInTransit) {
const produce = await this._getState(ctx, produceId);
- const now = new Date().toISOString();
-
if (newLocationOrInTransit === "In Transit") {
produce.status = "In Transit";
} else {
@@ -99,7 +109,7 @@ class ProduceContract extends Contract {
}
produce.actionHistory.push(
- this._actionItem("MOVE", produce.currentLocation, actorId, {})
+ this._actionItem(ctx, "MOVE", produce.currentLocation, actorId, {})
);
await this._putState(ctx, produceId, produce);
return produce;
@@ -112,7 +122,9 @@ class ProduceContract extends Contract {
produce.notAvailableReason = reason || "";
produce.status = newStatus || "Removed";
produce.actionHistory.push(
- this._actionItem("REMOVED", produce.currentLocation, actorId, { reason })
+ this._actionItem(ctx, "REMOVED", produce.currentLocation, actorId, {
+ reason,
+ })
);
await this._putState(ctx, produceId, produce);
return produce;
@@ -120,7 +132,7 @@ class ProduceContract extends Contract {
// 4. inspectProduce(produceId, inspectorId, qualityUpdate)
async inspectProduce(ctx, produceId, inspectorId, qualityUpdateStr) {
- const qualityUpdate = JSON.parse(qualityUpdateStr);
+ const qualityUpdate = JSON.parse(qualityUpdateStr || "{}");
const produce = await this._getState(ctx, produceId);
if (qualityUpdate.quality !== undefined)
@@ -132,6 +144,7 @@ class ProduceContract extends Contract {
produce.actionHistory.push(
this._actionItem(
+ ctx,
"INSPECT",
produce.currentLocation,
inspectorId,
@@ -144,7 +157,7 @@ class ProduceContract extends Contract {
produce.notAvailableReason = qualityUpdate.reason || "Failed Inspection";
produce.status = "Failed Inspection";
produce.actionHistory.push(
- this._actionItem("REMOVED", produce.currentLocation, inspectorId, {
+ this._actionItem(ctx, "REMOVED", produce.currentLocation, inspectorId, {
reason: produce.notAvailableReason,
})
);
@@ -156,7 +169,7 @@ class ProduceContract extends Contract {
// 5. updateDetails(produceId, actorId, details) -> Only 'currentOwner'
async updateDetails(ctx, produceId, actorId, detailsStr) {
- const details = JSON.parse(detailsStr);
+ const details = JSON.parse(detailsStr || "{}");
const produce = await this._getState(ctx, produceId);
if (produce.currentOwner !== actorId) {
@@ -170,7 +183,13 @@ class ProduceContract extends Contract {
produce.totalPrice = (produce.pricePerUnit || 0) * (produce.qty || 0);
produce.actionHistory.push(
- this._actionItem("UPDATED", produce.currentLocation, actorId, details)
+ this._actionItem(
+ ctx,
+ "UPDATED",
+ produce.currentLocation,
+ actorId,
+ details
+ )
);
await this._putState(ctx, produceId, produce);
return produce;
@@ -192,7 +211,7 @@ class ProduceContract extends Contract {
produce.qty = produce.qty - qty;
produce.totalPrice = (produce.pricePerUnit || 0) * produce.qty;
- const childId = `PRODUCE-${uuidv4()}`;
+ const childId = `${produce.id}-${ctx.stub.getTxID()}-CHILD`;
const child = JSON.parse(JSON.stringify(produce));
child.id = childId;
child.parentId = produce.id;
@@ -201,13 +220,13 @@ class ProduceContract extends Contract {
child.totalPrice = (child.pricePerUnit || 0) * qty;
child.actionHistory = child.actionHistory || [];
child.actionHistory.push(
- this._actionItem("SPLIT", produce.currentLocation, ownerId, { qty })
+ this._actionItem(ctx, "SPLIT", produce.currentLocation, ownerId, { qty })
);
produce.children = produce.children || [];
produce.children.push(childId);
produce.actionHistory.push(
- this._actionItem("SPLIT", produce.currentLocation, ownerId, {
+ this._actionItem(ctx, "SPLIT", produce.currentLocation, ownerId, {
createdChild: childId,
qty,
})
@@ -224,7 +243,7 @@ class ProduceContract extends Contract {
const qty = parseFloat(qtyStr);
const salePrice = parseFloat(salePriceStr);
const produce = await this._getState(ctx, produceId);
- const now = new Date().toISOString();
+ const now = this._txTimestampISO(ctx);
const currentOwner = produce.currentOwner;
if (!produce.isAvailable) {
@@ -247,7 +266,7 @@ class ProduceContract extends Contract {
const child = splitRes.child;
child.currentOwner = newOwnerId;
child.actionHistory.push(
- this._actionItem("SALE", child.currentLocation, newOwnerId, {
+ this._actionItem(ctx, "SALE", child.currentLocation, newOwnerId, {
qty,
salePrice,
})
@@ -267,7 +286,7 @@ class ProduceContract extends Contract {
// full transfer
produce.currentOwner = newOwnerId;
produce.actionHistory.push(
- this._actionItem("SALE", produce.currentLocation, newOwnerId, {
+ this._actionItem(ctx, "SALE", produce.currentLocation, newOwnerId, {
qty,
salePrice,
})
@@ -298,7 +317,7 @@ class ProduceContract extends Contract {
paymentRef
) {
const produce = await this._getState(ctx, produceId);
- const now = new Date().toISOString();
+ const now = this._txTimestampISO(ctx);
produce.saleHistory = produce.saleHistory || [];
if (produce.saleHistory.length === 0) {
@@ -323,17 +342,19 @@ class ProduceContract extends Contract {
produce.actionHistory.push(
this._actionItem(
+ ctx,
"PAYMENT",
produce.currentLocation,
produce.currentOwner,
- { transactionId, paymentStatus, paymentMethod, paymentRef }
+ {
+ transactionId,
+ paymentStatus,
+ paymentMethod,
+ paymentRef,
+ }
)
);
- // Optional: store sensitive payment details in private data collection (PDCPrices)
- // Example:
- // await ctx.stub.putPrivateData('PDCPrices', `PAY-${produceId}`, Buffer.from(JSON.stringify({ transactionId, paymentMethod, paymentRef, salePrice: produce.saleHistory.slice(-1)[0].salePrice })));
-
await this._putState(ctx, produceId, produce);
return produce;
}
@@ -366,7 +387,7 @@ class ProduceContract extends Contract {
// Governance functions
async registerUser(ctx, role, detailsStr) {
const details = JSON.parse(detailsStr || "{}");
- const id = details.id || `USER-${uuidv4()}`;
+ const id = details.id || `USER-${ctx.stub.getTxID()}`;
const key = `${role.toUpperCase()}-${id}`;
const user = {
role,
@@ -401,4 +422,4 @@ class ProduceContract extends Contract {
}
}
-module.exports = ProduceContract;
+module.exports.contracts = [ProduceContract];
diff --git a/chaincode/package-lock.json b/chaincode/package-lock.json
@@ -9,11 +9,7 @@
"version": "1.0.0",
"dependencies": {
"fabric-contract-api": "^2.5.8",
- "fabric-shim": "^2.5.8",
- "uuid": "^13.0.0"
- },
- "engines": {
- "node": ">=12"
+ "fabric-shim": "^2.5.8"
}
},
"node_modules/@colors/colors": {
@@ -708,19 +704,6 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
- "node_modules/uuid": {
- "version": "13.0.0",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz",
- "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==",
- "funding": [
- "https://github.com/sponsors/broofa",
- "https://github.com/sponsors/ctavan"
- ],
- "license": "MIT",
- "bin": {
- "uuid": "dist-node/bin/uuid"
- }
- },
"node_modules/winston": {
"version": "3.18.3",
"resolved": "https://registry.npmjs.org/winston/-/winston-3.18.3.tgz",
diff --git a/chaincode/package.json b/chaincode/package.json
@@ -4,14 +4,10 @@
"description": "Matiru produce contract",
"main": "index.js",
"scripts": {
- "start": "node index.js"
- },
- "engines": {
- "node": ">=12"
+ "start": "fabric-chaincode-node start"
},
"dependencies": {
"fabric-contract-api": "^2.5.8",
- "fabric-shim": "^2.5.8",
- "uuid": "^13.0.0"
+ "fabric-shim": "^2.5.8"
}
}