commit ccef1974b433e6c575b7d78feda6826bc1a961f7
parent ba3380a4ffce48f092c067cb35a53ee8ca70dd85
Author: maydayv7 <maydayv7@gmail.com>
Date: Tue, 7 Oct 2025 18:22:31 +0530
feat: Add Fabric network in-tree
Also use different Orgs for users
Diffstat:
59 files changed, 5896 insertions(+), 676 deletions(-)
diff --git a/README.md b/README.md
@@ -18,21 +18,28 @@ cd chaincode
npm install
```
-2. Install Docker, Docker Compose and run the following:
+1. Install Docker, Docker Compose
+2. Install Fabric binaries and Docker images like so:
```
-git clone https://github.com/hyperledger/fabric-samples.git
-curl -sSLO https://raw.githubusercontent.com/hyperledger/fabric/main/scripts/install-fabric.sh && chmod +x install-fabric.sh
-./install-fabric.sh # Install Fabric binaries and Docker images
+cd fabric
+curl -sSLO https://raw.githubusercontent.com/hyperledger/fabric/main/scripts/install-fabric.sh
+chmod +x install-fabric.sh
+./install-fabric.sh
```
-Ensure the `config` directory is present inside `fabric-samples`
+Ensure the `config` directory is present inside `fabric`
-3. Start Fabric `test-network` and create channel:
+1. Start Fabric network:
```
-cd fabric-samples/test-network
+cd network
./network.sh up createChannel -ca -c CHANNEL_NAME
+cd addOrg3
+./addOrg3.sh up -c CHANNEL_NAME
+cd ../addOrg4
+./addOrg4.sh up -c CHANNEL_NAME
+cd ..
```
After initial setup, you can use the following commands:
@@ -48,14 +55,21 @@ After initial setup, you can use the following commands:
./network.sh deployCC -c CHANNEL_NAME -ccn CHAINCODE_NAME -ccp /path/to/chaincode -ccl javascript
```
-5. Sample Wallet Creation:
+5. Wallet Creation:
```
-# From 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
+DIR=../../backend/connections
+mkdir -p $DIR
+cp organizations/peerOrganizations/org1.example.com/connection-org1.json $DIR/connection-org1.json
+cp organizations/peerOrganizations/org2.example.com/connection-org2.json $DIR/connection-org2.json
+cp organizations/peerOrganizations/org3.example.com/connection-org3.json $DIR/connection-org3.json
+cp organizations/peerOrganizations/org4.example.com/connection-org4.json $DIR/connection-org4.json
+cd ../../backend
mkdir -p wallet
-node initWallet.js org1 /path/to/fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp ./wallet USER_NAME
+node initWallet.js org1 ../fabric/network/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp ./wallet admin-org1
+node initWallet.js org2 ../fabric/network/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp ./wallet admin-org2
+node initWallet.js org3 ../fabric/network/organizations/peerOrganizations/org3.example.com/users/Admin@org3.example.com/msp ./wallet admin-org3
+node initWallet.js org4 ../fabric/network/organizations/peerOrganizations/org4.example.com/users/Admin@org4.example.com/msp ./wallet admin-org4
```
6. The `.env` file must be created in `backend` like so:
@@ -69,17 +83,24 @@ JWT_SECRET=some_secret
TOKEN_EXPIRES_IN=24h
# Fabric Connection
-CCP_PATH=connection-org1.json
-WALLET_PATH=./wallet
CHANNEL=CHANNEL_NAME
CHAINCODE=CHAINCODE_NAME
-IDENTITY=USER_NAME
AS_LOCALHOST=true
+WALLET_PATH="./wallet"
+
+IDENTITY_ORG1="admin-org1"
+CCP_PATH_ORG1="./connections/connection-org1.json"
+IDENTITY_ORG2="admin-org2"
+CCP_PATH_ORG2="./connections/connection-org2.json"
+IDENTITY_ORG3="admin-org3"
+CCP_PATH_ORG3="./connections/connection-org3.json"
+IDENTITY_ORG4="admin-org4"
+CCP_PATH_ORG4="./connections/connection-org4.json"
```
Make sure the port is not blocked by a firewall
-7. Start `backend`:
+1. Start `backend`:
```
npm install
diff --git a/backend/.gitignore b/backend/.gitignore
@@ -1,4 +1,4 @@
users.json
+connections
wallet
-connection-*
uploads
diff --git a/backend/initUsers.js b/backend/initUsers.js
@@ -9,6 +9,7 @@ const { Gateway, Wallets } = require("fabric-network");
const users = [
{
id: "farmer1",
+ org: "Org1",
username: "farmer1",
password: "password",
role: "Farmer",
@@ -18,6 +19,7 @@ const users = [
},
{
id: "distributor1",
+ org: "Org2",
username: "dist1",
password: "password",
role: "Distributor",
@@ -26,6 +28,7 @@ const users = [
},
{
id: "retailer1",
+ org: "Org3",
username: "ret1",
password: "password",
role: "Retailer",
@@ -34,6 +37,7 @@ const users = [
},
{
id: "inspector1",
+ org: "Org4",
username: "insp1",
password: "password",
role: "Inspector",
@@ -48,64 +52,76 @@ async function seedBackendUsers() {
username: u.username,
passwordHash: bcrypt.hashSync(u.password, 10),
role: u.role,
+ org: u.org,
}));
const dest = path.join(__dirname, "users.json");
fs.writeFileSync(dest, JSON.stringify(out, null, 2));
console.log("users.json written to", dest);
console.log("Demo accounts: (username / password)");
- out.forEach((u) => console.log(`${u.username} / password (role: ${u.role})`));
+ out.forEach((u) =>
+ console.log(`${u.username} / password (role: ${u.role}, org: ${u.org})`)
+ );
}
async function seedBlockchainUsers() {
- try {
- const ccpPath = path.resolve(__dirname, process.env.CCP_PATH);
- const ccp = JSON.parse(fs.readFileSync(ccpPath, "utf8"));
+ const walletPath = path.resolve(__dirname, process.env.WALLET_PATH);
+ const wallet = await Wallets.newFileSystemWallet(walletPath);
- const walletPath = path.resolve(__dirname, process.env.WALLET_PATH);
- const wallet = await Wallets.newFileSystemWallet(walletPath);
+ for (const orgNum of [1, 2, 3, 4]) {
+ const orgName = `Org${orgNum}`;
+ const ccpPath = path.resolve(
+ __dirname,
+ process.env[`CCP_PATH_ORG${orgNum}`]
+ );
+ const identityLabel = process.env[`IDENTITY_ORG${orgNum}`];
- const identity = await wallet.get(process.env.IDENTITY);
+ console.log(`Processing users for ${orgName}`);
+ const identity = await wallet.get(identityLabel);
if (!identity) {
console.error(
- `Identity "${process.env.IDENTITY}" not found in wallet: ${walletPath}`
+ `Identity "${identityLabel}" not found in wallet. Run initWallet.js first.`
);
- console.error("Run initWallet.js first");
- return;
+ continue;
}
+ const ccp = JSON.parse(fs.readFileSync(ccpPath, "utf8"));
const gateway = new Gateway();
- await gateway.connect(ccp, {
- wallet,
- identity: process.env.IDENTITY,
- discovery: {
- enabled: true,
- asLocalhost: process.env.AS_LOCALHOST === "true",
- },
- });
- const network = await gateway.getNetwork(process.env.CHANNEL);
- const contract = network.getContract(process.env.CHAINCODE);
+ try {
+ await gateway.connect(ccp, {
+ wallet,
+ identity: identityLabel,
+ discovery: {
+ enabled: true,
+ asLocalhost: process.env.AS_LOCALHOST === "true",
+ },
+ });
- for (const u of users) {
- const userKey = `${u.role.toUpperCase()}-${u.id}`;
- try {
- await contract.evaluateTransaction("getUserDetails", userKey);
- console.log(`User ${userKey} already exists, skipping`);
- } catch {
- console.log(`Registering ${u.role}: ${u.id}`);
- await contract.submitTransaction(
- "registerUser",
- u.role,
- JSON.stringify(u)
- );
+ const network = await gateway.getNetwork(process.env.CHANNEL);
+ const contract = network.getContract(process.env.CHAINCODE);
+
+ const orgUsers = users.filter((u) => u.org === orgName);
+ for (const u of orgUsers) {
+ const userKey = `${u.role.toUpperCase()}-${u.id}`;
+ try {
+ await contract.evaluateTransaction("getUserDetails", userKey);
+ console.log(`User ${userKey} already exists, skipping`);
+ } catch {
+ console.log(`Registering ${u.role}: ${u.id} from ${orgName}`);
+ await contract.submitTransaction(
+ "registerUser",
+ u.role,
+ JSON.stringify(u)
+ );
+ }
}
+ } catch (err) {
+ console.error(`Error processing users for ${orgName}:`, err);
+ } finally {
+ gateway.disconnect();
}
-
- console.log("Blockchain users seeded successfully");
- gateway.disconnect();
- } catch (err) {
- console.error("Error seeding blockchain users:", err);
}
+ console.log("\nBlockchain users seeding finished");
}
(async () => {
diff --git a/backend/package.json b/backend/package.json
@@ -1,6 +1,7 @@
{
- "name": "matiru-backend",
+ "name": "matiru-server",
"version": "1.0.0",
+ "description": "Matiru Backend",
"main": "server.js",
"scripts": {
"start": "npx nodemon server.js",
diff --git a/backend/server.js b/backend/server.js
@@ -29,26 +29,50 @@ const storage = multer.diskStorage({
});
const upload = multer({ storage });
-const CCP_PATH = process.env.CCP_PATH;
-const WALLET_PATH = process.env.WALLET_PATH;
-const CHANNEL = process.env.CHANNEL;
-const CHAINCODE = process.env.CHAINCODE;
-const IDENTITY = process.env.IDENTITY;
-const AS_LOCALHOST = process.env.AS_LOCALHOST === "true";
-const JWT_SECRET = process.env.JWT_SECRET;
+// Fabric Network
+const { CHANNEL, CHAINCODE, AS_LOCALHOST, JWT_SECRET, WALLET_PATH } =
+ process.env;
+const orgConfig = {
+ Org1: {
+ ccpPath: process.env.CCP_PATH_ORG1,
+ identity: process.env.IDENTITY_ORG1,
+ },
+ Org2: {
+ ccpPath: process.env.CCP_PATH_ORG2,
+ identity: process.env.IDENTITY_ORG2,
+ },
+ Org3: {
+ ccpPath: process.env.CCP_PATH_ORG3,
+ identity: process.env.IDENTITY_ORG3,
+ },
+ Org4: {
+ ccpPath: process.env.CCP_PATH_ORG4,
+ identity: process.env.IDENTITY_ORG4,
+ },
+};
-async function getContract() {
- if (!CCP_PATH || !WALLET_PATH || !CHANNEL || !CHAINCODE || !IDENTITY)
- throw new Error("Missing Fabric environment variables");
+async function getContract(org) {
+ const config = orgConfig[org];
+ if (!config) throw new Error(`Configuration for ${org} not found.`);
+ if (
+ !config.ccpPath ||
+ !config.identity ||
+ !WALLET_PATH ||
+ !CHANNEL ||
+ !CHAINCODE
+ )
+ throw new Error(`Missing Fabric environment variables for ${org}`);
- const ccp = JSON.parse(fs.readFileSync(path.resolve(CCP_PATH), "utf8"));
+ const ccp = JSON.parse(fs.readFileSync(path.resolve(config.ccpPath), "utf8"));
const wallet = await Wallets.newFileSystemWallet(path.resolve(WALLET_PATH));
const gateway = new Gateway();
+
await gateway.connect(ccp, {
wallet,
- identity: IDENTITY,
- discovery: { enabled: true, asLocalhost: AS_LOCALHOST },
+ identity: config.identity,
+ discovery: { enabled: true, asLocalhost: AS_LOCALHOST === "true" },
});
+
const network = await gateway.getNetwork(CHANNEL);
const contract = network.getContract(CHAINCODE);
return { contract, gateway };
@@ -63,6 +87,7 @@ function authenticateMiddleware(req, res, next) {
req.method === "GET" &&
(req.path.startsWith("/getProduce") || req.path.startsWith("/getUser"))
) {
+ req.user = { org: "Org1" };
return next();
}
@@ -74,6 +99,11 @@ function authenticateMiddleware(req, res, next) {
jwt.verify(token, JWT_SECRET, (err, payload) => {
if (err) return res.status(403).json({ error: "invalid token" });
req.user = payload;
+ if (!req.user.org) {
+ return res
+ .status(403)
+ .json({ error: "invalid token: missing org identifier" });
+ }
next();
});
}
@@ -96,13 +126,13 @@ app.post("/api/auth/login", express.json(), async (req, res) => {
const ok = await bcrypt.compare(password, user.passwordHash);
if (!ok) return res.status(401).json({ error: "invalid credentials" });
const token = jwt.sign(
- { id: user.id, role: user.role, username: user.username },
+ { id: user.id, role: user.role, username: user.username, org: user.org },
JWT_SECRET,
{ expiresIn: process.env.TOKEN_EXPIRES_IN || "1h" }
);
- res.json({ token, id: user.id, role: user.role, username: user.username });
+ res.json({ token, ...user });
} catch (err) {
- console.error("auth error", err);
+ console.error("Auth error:", err);
res.status(500).json({ error: err.message });
}
});
@@ -120,7 +150,7 @@ app.post(
}`;
res.json({ url: fileUrl });
} catch (err) {
- console.error("upload error", err);
+ console.error("Image Upload error:", err);
res.status(500).json({ error: "failed to process image upload" });
}
}
@@ -129,10 +159,27 @@ app.post(
// Functions
const router = express.Router();
+router.post("/registerUser", async (req, res) => {
+ try {
+ const { role, details } = req.body;
+ const { contract, gateway } = await getContract(req.user.org);
+ const result = await contract.submitTransaction(
+ "registerUser",
+ role,
+ JSON.stringify(details)
+ );
+ await gateway.disconnect();
+ return res.json({ success: true, user: JSON.parse(result.toString()) });
+ } catch (err) {
+ console.error("registerUser error:", err);
+ return res.status(500).json({ error: err.message });
+ }
+});
+
router.post("/registerProduce", async (req, res) => {
try {
const { farmerId, details } = req.body;
- const { contract, gateway } = await getContract();
+ const { contract, gateway } = await getContract(req.user.org);
const result = await contract.submitTransaction(
"registerProduce",
farmerId,
@@ -141,7 +188,7 @@ router.post("/registerProduce", async (req, res) => {
await gateway.disconnect();
return res.json({ success: true, produce: JSON.parse(result.toString()) });
} catch (err) {
- console.error(err);
+ console.error("registerProduce error:", err);
return res.status(500).json({ error: err.message });
}
});
@@ -149,7 +196,7 @@ router.post("/registerProduce", async (req, res) => {
router.post("/updateLocation", async (req, res) => {
try {
const { produceId, actorId, newLocation } = req.body;
- const { contract, gateway } = await getContract();
+ const { contract, gateway } = await getContract(req.user.org);
const result = await contract.submitTransaction(
"updateLocation",
produceId,
@@ -159,7 +206,7 @@ router.post("/updateLocation", async (req, res) => {
await gateway.disconnect();
return res.json({ success: true, produce: JSON.parse(result.toString()) });
} catch (err) {
- console.error(err);
+ console.error("updateLocation error:", err);
return res.status(500).json({ error: err.message });
}
});
@@ -167,7 +214,7 @@ router.post("/updateLocation", async (req, res) => {
router.post("/inspectProduce", async (req, res) => {
try {
const { produceId, inspectorId, qualityUpdate } = req.body;
- const { contract, gateway } = await getContract();
+ const { contract, gateway } = await getContract(req.user.org);
const result = await contract.submitTransaction(
"inspectProduce",
produceId,
@@ -177,7 +224,7 @@ router.post("/inspectProduce", async (req, res) => {
await gateway.disconnect();
return res.json({ success: true, produce: JSON.parse(result.toString()) });
} catch (err) {
- console.error(err);
+ console.error("inspectProduce error:", err);
return res.status(500).json({ error: err.message });
}
});
@@ -185,7 +232,7 @@ router.post("/inspectProduce", async (req, res) => {
router.post("/transferOwnership", async (req, res) => {
try {
const { produceId, newOwnerId, qty, salePrice } = req.body;
- const { contract, gateway } = await getContract();
+ const { contract, gateway } = await getContract(req.user.org);
const result = await contract.submitTransaction(
"transferOwnership",
produceId,
@@ -196,7 +243,7 @@ router.post("/transferOwnership", async (req, res) => {
await gateway.disconnect();
return res.json({ success: true, result: JSON.parse(result.toString()) });
} catch (err) {
- console.error(err);
+ console.error("transferOwnership error:", err);
return res.status(500).json({ error: err.message });
}
});
@@ -204,7 +251,7 @@ router.post("/transferOwnership", async (req, res) => {
router.post("/updateDetails", async (req, res) => {
try {
const { produceId, actorId, details } = req.body;
- const { contract, gateway } = await getContract();
+ const { contract, gateway } = await getContract(req.user.org);
const result = await contract.submitTransaction(
"updateDetails",
produceId,
@@ -214,7 +261,7 @@ router.post("/updateDetails", async (req, res) => {
await gateway.disconnect();
return res.json({ success: true, produce: JSON.parse(result.toString()) });
} catch (err) {
- console.error("updateDetails error", err);
+ console.error("updateDetails error:", err);
return res.status(500).json({ error: err.message });
}
});
@@ -222,7 +269,7 @@ router.post("/updateDetails", async (req, res) => {
router.post("/markAsUnavailable", async (req, res) => {
try {
const { produceId, actorId, reason, newStatus } = req.body;
- const { contract, gateway } = await getContract();
+ const { contract, gateway } = await getContract(req.user.org);
const result = await contract.submitTransaction(
"markAsUnavailable",
produceId,
@@ -233,7 +280,7 @@ router.post("/markAsUnavailable", async (req, res) => {
await gateway.disconnect();
return res.json({ success: true, produce: JSON.parse(result.toString()) });
} catch (err) {
- console.error("markAsUnavailable error", err);
+ console.error("markAsUnavailable error:", err);
return res.status(500).json({ error: err.message });
}
});
@@ -241,7 +288,7 @@ router.post("/markAsUnavailable", async (req, res) => {
router.post("/splitProduce", async (req, res) => {
try {
const { produceId, qty, ownerId } = req.body;
- const { contract, gateway } = await getContract();
+ const { contract, gateway } = await getContract(req.user.org);
const result = await contract.submitTransaction(
"splitProduce",
produceId,
@@ -251,7 +298,7 @@ router.post("/splitProduce", async (req, res) => {
await gateway.disconnect();
return res.json({ success: true, split: JSON.parse(result.toString()) });
} catch (err) {
- console.error("splitProduce error", err);
+ console.error("splitProduce error:", err);
return res.status(500).json({ error: err.message });
}
});
@@ -265,7 +312,7 @@ router.post("/recordPayment", async (req, res) => {
paymentMethod,
paymentRef,
} = req.body;
- const { contract, gateway } = await getContract();
+ const { contract, gateway } = await getContract(req.user.org);
const result = await contract.submitTransaction(
"recordPayment",
produceId,
@@ -277,32 +324,16 @@ router.post("/recordPayment", async (req, res) => {
await gateway.disconnect();
return res.json({ success: true, produce: JSON.parse(result.toString()) });
} catch (err) {
- console.error(err);
- return res.status(500).json({ error: err.message });
- }
-});
-
-router.post("/registerUser", async (req, res) => {
- try {
- const { role, details } = req.body;
- const { contract, gateway } = await getContract();
- const result = await contract.submitTransaction(
- "registerUser",
- role,
- JSON.stringify(details)
- );
- await gateway.disconnect();
- return res.json({ success: true, user: JSON.parse(result.toString()) });
- } catch (err) {
- console.error("registerUser error", err);
+ console.error("recordPayment error:", err);
return res.status(500).json({ error: err.message });
}
});
// Queries
+
router.get("/getProduce/:id", async (req, res) => {
try {
- const { contract, gateway } = await getContract();
+ const { contract, gateway } = await getContract(req.user.org);
const result = await contract.evaluateTransaction(
"getProduceById",
req.params.id
@@ -310,14 +341,14 @@ router.get("/getProduce/:id", async (req, res) => {
await gateway.disconnect();
return res.json({ success: true, produce: JSON.parse(result.toString()) });
} catch (err) {
- console.error(err);
+ console.error("getProduceById error:", err);
return res.status(500).json({ error: err.message });
}
});
router.get("/getProduceByOwner/:ownerId", async (req, res) => {
try {
- const { contract, gateway } = await getContract();
+ const { contract, gateway } = await getContract(req.user.org);
const result = await contract.evaluateTransaction(
"getProduceByOwner",
req.params.ownerId
@@ -325,14 +356,14 @@ router.get("/getProduceByOwner/:ownerId", async (req, res) => {
await gateway.disconnect();
return res.json({ success: true, produces: JSON.parse(result.toString()) });
} catch (err) {
- console.error(err);
+ console.error("getProduceByOwner error:", err);
return res.status(500).json({ error: err.message });
}
});
router.get("/getUser/:userKey", async (req, res) => {
try {
- const { contract, gateway } = await getContract();
+ const { contract, gateway } = await getContract(req.user.org);
const result = await contract.evaluateTransaction(
"getUserDetails",
req.params.userKey
@@ -340,7 +371,7 @@ router.get("/getUser/:userKey", async (req, res) => {
await gateway.disconnect();
return res.json({ success: true, user: JSON.parse(result.toString()) });
} catch (err) {
- console.error("getUser error", err);
+ console.error("getUserDetails error:", err);
return res.status(500).json({ error: err.message });
}
});
@@ -349,5 +380,5 @@ app.use("/api", authenticateMiddleware, express.json(), router);
const PORT = process.env.PORT || 4000;
app.listen(PORT, "0.0.0.0", () =>
- console.log(`Backend server listening on ${PORT}`)
+ console.log(`Server listening on port ${PORT}`)
);
diff --git a/chaincode/collections_config.json b/chaincode/collections_config.json
@@ -1,11 +0,0 @@
-[
- {
- "name": "PDCPrices",
- "policy": "OR('Org1MSP.member','Org2MSP.member')",
- "requiredPeerCount": 1,
- "maxPeerCount": 2,
- "blockToLive": 0,
- "memberOnlyRead": true,
- "memberOnlyWrite": true
- }
-]
diff --git a/chaincode/index.js b/chaincode/index.js
@@ -1,540 +0,0 @@
-"use strict";
-
-const { Contract } = require("fabric-contract-api");
-
-class ProduceContract extends Contract {
- // Utilities
- async _getState(ctx, id) {
- const data = await ctx.stub.getState(id);
- if (!data || data.length === 0) {
- throw new Error(`Asset ${id} does not exist`);
- }
- return JSON.parse(data.toString());
- }
-
- async _putState(ctx, id, obj) {
- await ctx.stub.putState(id, Buffer.from(JSON.stringify(obj)));
- }
-
- // 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();
- }
-
- // Deterministic action item
- _actionItem(ctx, action, location, owner, meta) {
- return {
- timestamp: this._txTimestampISO(ctx),
- action,
- currentLocation: location || "",
- currentOwner: owner || "",
- meta: meta || {},
- };
- }
-
- // Find user state by ID
- async _findUserKeyById(ctx, id) {
- if (!id) return null;
- const roles = ["FARMER", "DISTRIBUTOR", "RETAILER", "INSPECTOR"];
- for (const r of roles) {
- const key = `${r}-${id}`;
- const data = await ctx.stub.getState(key);
- if (data && data.length > 0) return key;
- }
- return null;
- }
-
- async _getUserById(ctx, id) {
- const key = await this._findUserKeyById(ctx, id);
- if (!key) return null;
- const data = await ctx.stub.getState(key);
- if (!data || data.length === 0) return null;
- return { key, user: JSON.parse(data.toString()) };
- }
-
- async _addOwnedProduceToUser(ctx, userKey, userObj, produceId) {
- userObj.ownedProduce = userObj.ownedProduce || [];
- if (!userObj.ownedProduce.includes(produceId)) {
- userObj.ownedProduce.push(produceId);
- await this._putState(ctx, userKey, userObj);
- }
- }
-
- async _removeOwnedProduceFromUser(ctx, userKey, userObj, produceId) {
- userObj.ownedProduce = userObj.ownedProduce || [];
- const idx = userObj.ownedProduce.indexOf(produceId);
- if (idx >= 0) {
- userObj.ownedProduce.splice(idx, 1);
- await this._putState(ctx, userKey, userObj);
- }
- }
-
- // Initialize ledger
- async initLedger(ctx) {
- console.info("Ledger initialized");
- }
-
- // Functions
- // Produce Registration
- async registerProduce(ctx, farmerId, detailsStr) {
- const farmerKey = `FARMER-${farmerId}`;
- const farmerState = await ctx.stub.getState(farmerKey);
- if (!farmerState || farmerState.length === 0)
- throw new Error(`Farmer ${farmerId} is not registered`);
- const farmer = JSON.parse(farmerState.toString());
-
- const details = JSON.parse(detailsStr || "{}");
- const txId = ctx.stub.getTxID();
- const now = this._txTimestampISO(ctx);
-
- const id = `PRODUCE-${txId}`;
- const produce = {
- id,
- parentId: null,
- children: [],
- qty: details.qty || 0,
- qtyUnit: details.qtyUnit || "KG",
- pricePerUnit: details.pricePerUnit || 0,
- totalPrice: (details.pricePerUnit || 0) * (details.qty || 0),
- currentOwner: farmerId,
- currentLocation: details.location || "",
- actionHistory: [],
- saleHistory: [],
- cropType: details.cropType || "",
- harvestDate: details.harvestDate || now,
- quality: details.quality || null,
- expiryDate: details.expiryDate || null,
- storageConditions: details.storageConditions || [],
- imageUrl: details.imageUrl || null,
- certification: details.certification || farmer.certification || [],
- status: "Harvested",
- isAvailable: true,
- notAvailableReason: null,
- };
-
- produce.actionHistory.push(
- this._actionItem(ctx, "REGISTER", produce.currentLocation, farmerId, {
- note: details.note || "",
- })
- );
- await this._putState(ctx, id, produce);
-
- const farmerObj = farmer;
- farmerObj.registeredProduce = farmerObj.registeredProduce || [];
- if (!farmerObj.registeredProduce.includes(id))
- farmerObj.registeredProduce.push(id);
- farmerObj.ownedProduce = farmerObj.ownedProduce || [];
- if (!farmerObj.ownedProduce.includes(id)) farmerObj.ownedProduce.push(id);
- await this._putState(ctx, farmerKey, farmerObj);
-
- return produce;
- }
-
- // Update location
- async updateLocation(ctx, produceId, actorId, newLocation) {
- const produce = await this._getState(ctx, produceId);
- if (newLocation === "In Transit") produce.status = "In Transit";
- else {
- produce.currentLocation = newLocation;
- if (produce.status === "Harvested") produce.status = "In Transit";
- }
-
- produce.actionHistory.push(
- this._actionItem(ctx, "MOVE", produce.currentLocation, actorId, {})
- );
- await this._putState(ctx, produceId, produce);
- return produce;
- }
-
- // Mark as unavailable due to some reason
- async markAsUnavailable(ctx, produceId, actorId, reason, newStatus) {
- const produce = await this._getState(ctx, produceId);
- produce.isAvailable = false;
- produce.notAvailableReason = reason || "";
- produce.status = newStatus || "Removed";
- produce.actionHistory.push(
- this._actionItem(ctx, "REMOVED", produce.currentLocation, actorId, {
- reason,
- })
- );
- await this._putState(ctx, produceId, produce);
- return produce;
- }
-
- // Produce Inspection
- async inspectProduce(ctx, produceId, inspectorId, qualityUpdateStr) {
- const qualityUpdate = JSON.parse(qualityUpdateStr || "{}");
- const produce = await this._getState(ctx, produceId);
-
- if (qualityUpdate.quality !== undefined)
- produce.quality = qualityUpdate.quality;
- if (qualityUpdate.expiryDate !== undefined)
- produce.expiryDate = qualityUpdate.expiryDate;
- if (qualityUpdate.storageConditions !== undefined)
- produce.storageConditions = qualityUpdate.storageConditions;
-
- produce.actionHistory.push(
- this._actionItem(
- ctx,
- "INSPECT",
- produce.currentLocation,
- inspectorId,
- qualityUpdate
- )
- );
-
- if (qualityUpdate.failed === true) {
- produce.isAvailable = false;
- produce.notAvailableReason = qualityUpdate.reason || "Failed Inspection";
- produce.status = "Failed Inspection";
- produce.actionHistory.push(
- this._actionItem(ctx, "REMOVED", produce.currentLocation, inspectorId, {
- reason: produce.notAvailableReason,
- })
- );
- }
-
- await this._putState(ctx, produceId, produce);
-
- const inspectorKey = `INSPECTOR-${inspectorId}`;
- const inspectorState = await ctx.stub.getState(inspectorKey);
- if (inspectorState && inspectorState.length > 0) {
- const inspectorObj = JSON.parse(inspectorState.toString());
- inspectorObj.inspectedProduce = inspectorObj.inspectedProduce || [];
- if (!inspectorObj.inspectedProduce.includes(produceId)) {
- inspectorObj.inspectedProduce.push(produceId);
- await this._putState(ctx, inspectorKey, inspectorObj);
- }
- }
-
- return produce;
- }
-
- // Update added details
- async updateDetails(ctx, produceId, actorId, detailsStr) {
- const details = JSON.parse(detailsStr || "{}");
- const produce = await this._getState(ctx, produceId);
-
- if (produce.currentOwner !== actorId)
- throw new Error("Only current owner can update details");
-
- if (details.pricePerUnit !== undefined)
- produce.pricePerUnit = details.pricePerUnit;
- if (details.storageConditions !== undefined)
- produce.storageConditions = details.storageConditions;
- if (details.imageUrl !== undefined) produce.imageUrl = details.imageUrl;
- if (details.certification !== undefined)
- produce.certification = details.certification;
-
- produce.totalPrice = (produce.pricePerUnit || 0) * (produce.qty || 0);
-
- produce.actionHistory.push(
- this._actionItem(
- ctx,
- "UPDATED",
- produce.currentLocation,
- actorId,
- details
- )
- );
- await this._putState(ctx, produceId, produce);
- return produce;
- }
-
- // Split into smaller batches
- async splitProduce(ctx, produceId, qtyStr, ownerId) {
- const qty = parseFloat(qtyStr);
- const produce = await this._getState(ctx, produceId);
-
- if (produce.currentOwner !== ownerId)
- throw new Error("Only current owner can split produce");
- if (qty <= 0 || qty > produce.qty)
- throw new Error("Invalid quantity to split");
-
- produce.qty -= qty;
- produce.totalPrice = (produce.pricePerUnit || 0) * produce.qty;
-
- const childId = `${produce.id}-${ctx.stub.getTxID()}-CHILD`;
- const child = JSON.parse(JSON.stringify(produce));
- child.id = childId;
- child.parentId = produce.id;
- child.children = [];
- child.qty = qty;
- child.totalPrice = (child.pricePerUnit || 0) * qty;
- child.actionHistory = [];
- child.actionHistory.push(
- this._actionItem(ctx, "SPLIT", produce.currentLocation, ownerId, { qty })
- );
-
- produce.children = produce.children || [];
- produce.children.push(childId);
- produce.actionHistory.push(
- this._actionItem(ctx, "SPLIT", produce.currentLocation, ownerId, {
- createdChild: childId,
- qty,
- })
- );
-
- await this._putState(ctx, produce.id, produce);
- await this._putState(ctx, childId, child);
-
- const ownerRes = await this._getUserById(ctx, ownerId);
- if (ownerRes) {
- await this._addOwnedProduceToUser(
- ctx,
- ownerRes.key,
- ownerRes.user,
- childId
- );
- }
-
- return { parent: produce, child };
- }
-
- // Transfer ownership
- async transferOwnership(ctx, produceId, newOwnerId, qtyStr, salePriceStr) {
- const qty = parseFloat(qtyStr);
- const salePrice = parseFloat(salePriceStr);
- const produce = await this._getState(ctx, produceId);
- const now = this._txTimestampISO(ctx);
- const currentOwner = produce.currentOwner;
-
- if (!produce.isAvailable) throw new Error("Asset not available");
- if (qty <= 0 || qty > produce.qty) throw new Error("Invalid qty");
-
- let resultAssetId = produceId;
- if (qty < produce.qty) {
- // Partial Transfer
- const splitRes = await this.splitProduce(
- ctx,
- produceId,
- "" + qty,
- produce.currentOwner
- );
- const child = splitRes.child;
- child.currentOwner = newOwnerId;
- child.actionHistory.push(
- this._actionItem(ctx, "SALE", child.currentLocation, newOwnerId, {
- qty,
- salePrice,
- })
- );
- child.saleHistory = child.saleHistory || [];
- child.saleHistory.push({
- timestamp: now,
- prevOwner: currentOwner,
- newOwner: newOwnerId,
- salePrice,
- qtyBought: qty,
- paymentStatus: "PENDING",
- });
- await this._putState(ctx, child.id, child);
- resultAssetId = child.id;
-
- const prevOwnerRes = await this._getUserById(ctx, currentOwner);
- if (prevOwnerRes) {
- await this._removeOwnedProduceFromUser(
- ctx,
- prevOwnerRes.key,
- prevOwnerRes.user,
- child.id
- );
- }
-
- const newOwnerRes = await this._getUserById(ctx, newOwnerId);
- if (newOwnerRes) {
- await this._addOwnedProduceToUser(
- ctx,
- newOwnerRes.key,
- newOwnerRes.user,
- child.id
- );
- }
- } else {
- // Full Transfer
- produce.currentOwner = newOwnerId;
- produce.actionHistory.push(
- this._actionItem(ctx, "SALE", produce.currentLocation, newOwnerId, {
- qty,
- salePrice,
- })
- );
- produce.saleHistory = produce.saleHistory || [];
- produce.saleHistory.push({
- timestamp: now,
- prevOwner: currentOwner,
- newOwner: newOwnerId,
- salePrice,
- qtyBought: qty,
- paymentStatus: "PENDING",
- });
- await this._putState(ctx, produceId, produce);
- resultAssetId = produceId;
-
- const prevOwnerRes = await this._getUserById(ctx, currentOwner);
- if (prevOwnerRes) {
- await this._removeOwnedProduceFromUser(
- ctx,
- prevOwnerRes.key,
- prevOwnerRes.user,
- produceId
- );
- }
-
- const newOwnerRes = await this._getUserById(ctx, newOwnerId);
- if (newOwnerRes) {
- await this._addOwnedProduceToUser(
- ctx,
- newOwnerRes.key,
- newOwnerRes.user,
- produceId
- );
- }
- }
-
- return { newAssetId: resultAssetId };
- }
-
- // Record payment for transfer
- async recordPayment(
- ctx,
- produceId,
- transactionId,
- paymentStatus,
- paymentMethod,
- paymentRef
- ) {
- const produce = await this._getState(ctx, produceId);
- const now = this._txTimestampISO(ctx);
-
- produce.saleHistory = produce.saleHistory || [];
- if (produce.saleHistory.length === 0) {
- produce.saleHistory.push({
- timestamp: now,
- prevOwner: null,
- newOwner: produce.currentOwner,
- salePrice: produce.totalPrice,
- qtyBought: produce.qty,
- paymentStatus,
- });
- } else {
- const last = produce.saleHistory[produce.saleHistory.length - 1];
- last.paymentStatus = paymentStatus;
- last.paymentMethod = paymentMethod;
- last.paymentRef = paymentRef || transactionId;
- }
-
- produce.paymentStatus = paymentStatus;
- produce.paymentMethod = paymentMethod;
- produce.paymentRef = paymentRef || transactionId;
-
- produce.actionHistory.push(
- this._actionItem(
- ctx,
- "PAYMENT",
- produce.currentLocation,
- produce.currentOwner,
- {
- transactionId,
- paymentStatus,
- paymentMethod,
- paymentRef,
- }
- )
- );
-
- await this._putState(ctx, produceId, produce);
- return produce;
- }
-
- // Queries
- async getProduceById(ctx, produceId) {
- return await this._getState(ctx, produceId);
- }
-
- async getProduceByOwner(ctx, ownerId) {
- const iterator = await ctx.stub.getStateByRange("", "");
- const results = [];
- while (true) {
- const res = await iterator.next();
- if (res.value && res.value.key) {
- if (res.value.key.startsWith("PRODUCE-")) {
- const obj = JSON.parse(res.value.value.toString("utf8"));
- if (obj.currentOwner === ownerId) results.push(obj);
- }
- }
- if (res.done) {
- await iterator.close();
- break;
- }
- }
- return results;
- }
-
- // Governance
- async registerUser(ctx, role, detailsStr) {
- const details = JSON.parse(detailsStr || "{}");
- const id = details.id || `USER-${ctx.stub.getTxID()}`;
- const key = `${role.toUpperCase()}-${id}`;
-
- const base = {
- role,
- id,
- name: details.name || "",
- location: details.location || "",
- walletId: details.walletId || "",
- };
-
- let user = null;
- const roleUpper = role.toUpperCase();
- if (roleUpper === "FARMER") {
- user = {
- ...base,
- registeredProduce: details.registeredProduce || [],
- ownedProduce: details.ownedProduce || [],
- certification: details.certification || [],
- };
- } else if (roleUpper === "DISTRIBUTOR" || roleUpper === "RETAILER") {
- user = {
- ...base,
- ownedProduce: details.ownedProduce || [],
- };
- } else if (roleUpper === "INSPECTOR") {
- user = {
- ...base,
- inspectedProduce: details.inspectedProduce || [],
- };
- }
-
- await this._putState(ctx, key, user);
- return user;
- }
-
- async getUserDetails(ctx, userKey) {
- const data = await ctx.stub.getState(userKey);
- if (!data || data.length === 0)
- throw new Error(`User ${userKey} not found`);
- return JSON.parse(data.toString());
- }
-
- async updateUser(ctx, userKey, detailsStr) {
- const data = await ctx.stub.getState(userKey);
- if (!data || data.length === 0)
- throw new Error(`User ${userKey} not found`);
- const user = JSON.parse(data.toString());
- const details = JSON.parse(detailsStr || "{}");
- Object.assign(user, details);
- await this._putState(ctx, userKey, user);
- return user;
- }
-}
-
-module.exports.contracts = [ProduceContract];
diff --git a/chaincode/package.json b/chaincode/package.json
@@ -1,13 +0,0 @@
-{
- "name": "produce-contract",
- "version": "1.0.0",
- "description": "Matiru produce contract",
- "main": "index.js",
- "scripts": {
- "start": "fabric-chaincode-node start"
- },
- "dependencies": {
- "fabric-contract-api": "^2.5.8",
- "fabric-shim": "^2.5.8"
- }
-}
diff --git a/fabric/.gitignore b/fabric/.gitignore
@@ -0,0 +1 @@
+config
diff --git a/fabric/chaincode/index.js b/fabric/chaincode/index.js
@@ -0,0 +1,569 @@
+"use strict";
+
+const { Contract } = require("fabric-contract-api");
+
+class ProduceContract extends Contract {
+ // Utilities
+ async _getState(ctx, id) {
+ const data = await ctx.stub.getState(id);
+ if (!data || data.length === 0) {
+ throw new Error(`Asset ${id} does not exist`);
+ }
+ return JSON.parse(data.toString());
+ }
+
+ async _putState(ctx, id, obj) {
+ await ctx.stub.putState(id, Buffer.from(JSON.stringify(obj)));
+ }
+
+ // 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();
+ }
+
+ // Deterministic action item
+ _actionItem(ctx, action, location, owner, meta) {
+ return {
+ timestamp: this._txTimestampISO(ctx),
+ action,
+ currentLocation: location || "",
+ currentOwner: owner || "",
+ meta: meta || {},
+ };
+ }
+
+ // Find user state by ID
+ async _findUserKeyById(ctx, id) {
+ if (!id) return null;
+ const roles = ["FARMER", "DISTRIBUTOR", "RETAILER", "INSPECTOR"];
+ for (const r of roles) {
+ const key = `${r}-${id}`;
+ const data = await ctx.stub.getState(key);
+ if (data && data.length > 0) return key;
+ }
+ return null;
+ }
+
+ async _getUserById(ctx, id) {
+ const key = await this._findUserKeyById(ctx, id);
+ if (!key) return null;
+ const data = await ctx.stub.getState(key);
+ if (!data || data.length === 0) return null;
+ return { key, user: JSON.parse(data.toString()) };
+ }
+
+ async _addOwnedProduceToUser(ctx, userKey, userObj, produceId) {
+ userObj.ownedProduce = userObj.ownedProduce || [];
+ if (!userObj.ownedProduce.includes(produceId)) {
+ userObj.ownedProduce.push(produceId);
+ await this._putState(ctx, userKey, userObj);
+ }
+ }
+
+ async _removeOwnedProduceFromUser(ctx, userKey, userObj, produceId) {
+ userObj.ownedProduce = userObj.ownedProduce || [];
+ const idx = userObj.ownedProduce.indexOf(produceId);
+ if (idx >= 0) {
+ userObj.ownedProduce.splice(idx, 1);
+ await this._putState(ctx, userKey, userObj);
+ }
+ }
+
+ // Initialize ledger
+ async initLedger(ctx) {
+ console.info("Ledger initialized");
+ }
+
+ // Functions
+ // Produce Registration
+ async registerProduce(ctx, farmerId, detailsStr) {
+ const clientMspId = ctx.clientIdentity.getMSPID();
+ if (clientMspId !== "Org1MSP") {
+ throw new Error(
+ `Client from ${clientMspId} is not authorized to register produce. Only Org1MSP is allowed.`
+ );
+ }
+
+ const farmerKey = `FARMER-${farmerId}`;
+ const farmerState = await ctx.stub.getState(farmerKey);
+ if (!farmerState || farmerState.length === 0)
+ throw new Error(`Farmer ${farmerId} is not registered`);
+ const farmer = JSON.parse(farmerState.toString());
+
+ const details = JSON.parse(detailsStr || "{}");
+ const txId = ctx.stub.getTxID();
+ const now = this._txTimestampISO(ctx);
+
+ const id = `PRODUCE-${txId}`;
+ const produce = {
+ id,
+ parentId: null,
+ children: [],
+ qty: details.qty || 0,
+ qtyUnit: details.qtyUnit || "KG",
+ pricePerUnit: details.pricePerUnit || 0,
+ totalPrice: (details.pricePerUnit || 0) * (details.qty || 0),
+ currentOwner: farmerId,
+ currentLocation: details.location || "",
+ actionHistory: [],
+ saleHistory: [],
+ cropType: details.cropType || "",
+ harvestDate: details.harvestDate || now,
+ quality: details.quality || null,
+ expiryDate: details.expiryDate || null,
+ storageConditions: details.storageConditions || [],
+ imageUrl: details.imageUrl || null,
+ certification: details.certification || farmer.certification || [],
+ status: "Harvested",
+ isAvailable: true,
+ notAvailableReason: null,
+ };
+
+ produce.actionHistory.push(
+ this._actionItem(ctx, "REGISTER", produce.currentLocation, farmerId, {
+ note: details.note || "",
+ })
+ );
+ await this._putState(ctx, id, produce);
+
+ const farmerObj = farmer;
+ farmerObj.registeredProduce = farmerObj.registeredProduce || [];
+ if (!farmerObj.registeredProduce.includes(id))
+ farmerObj.registeredProduce.push(id);
+ farmerObj.ownedProduce = farmerObj.ownedProduce || [];
+ if (!farmerObj.ownedProduce.includes(id)) farmerObj.ownedProduce.push(id);
+ await this._putState(ctx, farmerKey, farmerObj);
+
+ return produce;
+ }
+
+ // Update location
+ async updateLocation(ctx, produceId, actorId, newLocation) {
+ const produce = await this._getState(ctx, produceId);
+ if (newLocation === "In Transit") produce.status = "In Transit";
+ else {
+ produce.currentLocation = newLocation;
+ if (produce.status === "Harvested") produce.status = "In Transit";
+ }
+
+ produce.actionHistory.push(
+ this._actionItem(ctx, "MOVE", produce.currentLocation, actorId, {})
+ );
+ await this._putState(ctx, produceId, produce);
+ return produce;
+ }
+
+ // Mark as unavailable due to some reason
+ async markAsUnavailable(ctx, produceId, actorId, reason, newStatus) {
+ const produce = await this._getState(ctx, produceId);
+ produce.isAvailable = false;
+ produce.notAvailableReason = reason || "";
+ produce.status = newStatus || "Removed";
+ produce.actionHistory.push(
+ this._actionItem(ctx, "REMOVED", produce.currentLocation, actorId, {
+ reason,
+ })
+ );
+ await this._putState(ctx, produceId, produce);
+ return produce;
+ }
+
+ // Produce Inspection
+ async inspectProduce(ctx, produceId, inspectorId, qualityUpdateStr) {
+ const clientMspId = ctx.clientIdentity.getMSPID();
+ if (clientMspId !== "Org4MSP") {
+ throw new Error(
+ `Client from ${clientMspId} is not authorized to inspect produce. Only Org4MSP is allowed.`
+ );
+ }
+
+ const qualityUpdate = JSON.parse(qualityUpdateStr || "{}");
+ const produce = await this._getState(ctx, produceId);
+
+ if (qualityUpdate.quality !== undefined)
+ produce.quality = qualityUpdate.quality;
+ if (qualityUpdate.expiryDate !== undefined)
+ produce.expiryDate = qualityUpdate.expiryDate;
+ if (qualityUpdate.storageConditions !== undefined)
+ produce.storageConditions = qualityUpdate.storageConditions;
+
+ produce.actionHistory.push(
+ this._actionItem(
+ ctx,
+ "INSPECT",
+ produce.currentLocation,
+ inspectorId,
+ qualityUpdate
+ )
+ );
+
+ if (qualityUpdate.failed === true) {
+ produce.isAvailable = false;
+ produce.notAvailableReason = qualityUpdate.reason || "Failed Inspection";
+ produce.status = "Failed Inspection";
+ produce.actionHistory.push(
+ this._actionItem(ctx, "REMOVED", produce.currentLocation, inspectorId, {
+ reason: produce.notAvailableReason,
+ })
+ );
+ }
+
+ await this._putState(ctx, produceId, produce);
+
+ const inspectorKey = `INSPECTOR-${inspectorId}`;
+ const inspectorState = await ctx.stub.getState(inspectorKey);
+ if (inspectorState && inspectorState.length > 0) {
+ const inspectorObj = JSON.parse(inspectorState.toString());
+ inspectorObj.inspectedProduce = inspectorObj.inspectedProduce || [];
+ if (!inspectorObj.inspectedProduce.includes(produceId)) {
+ inspectorObj.inspectedProduce.push(produceId);
+ await this._putState(ctx, inspectorKey, inspectorObj);
+ }
+ }
+
+ return produce;
+ }
+
+ // Update added details
+ async updateDetails(ctx, produceId, actorId, detailsStr) {
+ const details = JSON.parse(detailsStr || "{}");
+ const produce = await this._getState(ctx, produceId);
+
+ if (produce.currentOwner !== actorId)
+ throw new Error("Only current owner can update details");
+
+ if (details.pricePerUnit !== undefined)
+ produce.pricePerUnit = details.pricePerUnit;
+ if (details.storageConditions !== undefined)
+ produce.storageConditions = details.storageConditions;
+ if (details.imageUrl !== undefined) produce.imageUrl = details.imageUrl;
+ if (details.certification !== undefined)
+ produce.certification = details.certification;
+
+ produce.totalPrice = (produce.pricePerUnit || 0) * (produce.qty || 0);
+
+ produce.actionHistory.push(
+ this._actionItem(
+ ctx,
+ "UPDATED",
+ produce.currentLocation,
+ actorId,
+ details
+ )
+ );
+ await this._putState(ctx, produceId, produce);
+ return produce;
+ }
+
+ // Split into smaller batches
+ async splitProduce(ctx, produceId, qtyStr, ownerId) {
+ const qty = parseFloat(qtyStr);
+ const produce = await this._getState(ctx, produceId);
+
+ if (produce.currentOwner !== ownerId)
+ throw new Error("Only current owner can split produce");
+ if (qty <= 0 || qty > produce.qty)
+ throw new Error("Invalid quantity to split");
+
+ produce.qty -= qty;
+ produce.totalPrice = (produce.pricePerUnit || 0) * produce.qty;
+
+ const childId = `${produce.id}-${ctx.stub.getTxID()}-CHILD`;
+ const child = JSON.parse(JSON.stringify(produce));
+ child.id = childId;
+ child.parentId = produce.id;
+ child.children = [];
+ child.qty = qty;
+ child.totalPrice = (child.pricePerUnit || 0) * qty;
+ child.actionHistory = [];
+ child.actionHistory.push(
+ this._actionItem(ctx, "SPLIT", produce.currentLocation, ownerId, { qty })
+ );
+
+ produce.children = produce.children || [];
+ produce.children.push(childId);
+ produce.actionHistory.push(
+ this._actionItem(ctx, "SPLIT", produce.currentLocation, ownerId, {
+ createdChild: childId,
+ qty,
+ })
+ );
+
+ await this._putState(ctx, produce.id, produce);
+ await this._putState(ctx, childId, child);
+
+ const ownerRes = await this._getUserById(ctx, ownerId);
+ if (ownerRes) {
+ await this._addOwnedProduceToUser(
+ ctx,
+ ownerRes.key,
+ ownerRes.user,
+ childId
+ );
+ }
+
+ return { parent: produce, child };
+ }
+
+ // Transfer ownership
+ async transferOwnership(ctx, produceId, newOwnerId, qtyStr, salePriceStr) {
+ const qty = parseFloat(qtyStr);
+ const salePrice = parseFloat(salePriceStr);
+ const produce = await this._getState(ctx, produceId);
+ const now = this._txTimestampISO(ctx);
+ const currentOwner = produce.currentOwner;
+
+ if (!produce.isAvailable) throw new Error("Asset not available");
+ if (qty <= 0 || qty > produce.qty) throw new Error("Invalid qty");
+
+ let resultAssetId = produceId;
+ if (qty < produce.qty) {
+ // Partial Transfer
+ const splitRes = await this.splitProduce(
+ ctx,
+ produceId,
+ "" + qty,
+ produce.currentOwner
+ );
+ const child = splitRes.child;
+ child.currentOwner = newOwnerId;
+ child.actionHistory.push(
+ this._actionItem(ctx, "SALE", child.currentLocation, newOwnerId, {
+ qty,
+ salePrice,
+ })
+ );
+ child.saleHistory = child.saleHistory || [];
+ child.saleHistory.push({
+ timestamp: now,
+ prevOwner: currentOwner,
+ newOwner: newOwnerId,
+ salePrice,
+ qtyBought: qty,
+ paymentStatus: "PENDING",
+ });
+ await this._putState(ctx, child.id, child);
+ resultAssetId = child.id;
+
+ const prevOwnerRes = await this._getUserById(ctx, currentOwner);
+ if (prevOwnerRes) {
+ await this._removeOwnedProduceFromUser(
+ ctx,
+ prevOwnerRes.key,
+ prevOwnerRes.user,
+ child.id
+ );
+ }
+
+ const newOwnerRes = await this._getUserById(ctx, newOwnerId);
+ if (newOwnerRes) {
+ await this._addOwnedProduceToUser(
+ ctx,
+ newOwnerRes.key,
+ newOwnerRes.user,
+ child.id
+ );
+ }
+ } else {
+ // Full Transfer
+ produce.currentOwner = newOwnerId;
+ produce.actionHistory.push(
+ this._actionItem(ctx, "SALE", produce.currentLocation, newOwnerId, {
+ qty,
+ salePrice,
+ })
+ );
+ produce.saleHistory = produce.saleHistory || [];
+ produce.saleHistory.push({
+ timestamp: now,
+ prevOwner: currentOwner,
+ newOwner: newOwnerId,
+ salePrice,
+ qtyBought: qty,
+ paymentStatus: "PENDING",
+ });
+ await this._putState(ctx, produceId, produce);
+ resultAssetId = produceId;
+
+ const prevOwnerRes = await this._getUserById(ctx, currentOwner);
+ if (prevOwnerRes) {
+ await this._removeOwnedProduceFromUser(
+ ctx,
+ prevOwnerRes.key,
+ prevOwnerRes.user,
+ produceId
+ );
+ }
+
+ const newOwnerRes = await this._getUserById(ctx, newOwnerId);
+ if (newOwnerRes) {
+ await this._addOwnedProduceToUser(
+ ctx,
+ newOwnerRes.key,
+ newOwnerRes.user,
+ produceId
+ );
+ }
+ }
+
+ return { newAssetId: resultAssetId };
+ }
+
+ // Record payment for transfer
+ async recordPayment(
+ ctx,
+ produceId,
+ transactionId,
+ paymentStatus,
+ paymentMethod,
+ paymentRef
+ ) {
+ const produce = await this._getState(ctx, produceId);
+ const now = this._txTimestampISO(ctx);
+
+ produce.saleHistory = produce.saleHistory || [];
+ if (produce.saleHistory.length === 0) {
+ produce.saleHistory.push({
+ timestamp: now,
+ prevOwner: null,
+ newOwner: produce.currentOwner,
+ salePrice: produce.totalPrice,
+ qtyBought: produce.qty,
+ paymentStatus,
+ });
+ } else {
+ const last = produce.saleHistory[produce.saleHistory.length - 1];
+ last.paymentStatus = paymentStatus;
+ last.paymentMethod = paymentMethod;
+ last.paymentRef = paymentRef || transactionId;
+ }
+
+ produce.paymentStatus = paymentStatus;
+ produce.paymentMethod = paymentMethod;
+ produce.paymentRef = paymentRef || transactionId;
+
+ produce.actionHistory.push(
+ this._actionItem(
+ ctx,
+ "PAYMENT",
+ produce.currentLocation,
+ produce.currentOwner,
+ {
+ transactionId,
+ paymentStatus,
+ paymentMethod,
+ paymentRef,
+ }
+ )
+ );
+
+ await this._putState(ctx, produceId, produce);
+ return produce;
+ }
+
+ // Queries
+ async getProduceById(ctx, produceId) {
+ return await this._getState(ctx, produceId);
+ }
+
+ async getProduceByOwner(ctx, ownerId) {
+ const iterator = await ctx.stub.getStateByRange("", "");
+ const results = [];
+ while (true) {
+ const res = await iterator.next();
+ if (res.value && res.value.key) {
+ if (res.value.key.startsWith("PRODUCE-")) {
+ const obj = JSON.parse(res.value.value.toString("utf8"));
+ if (obj.currentOwner === ownerId) results.push(obj);
+ }
+ }
+ if (res.done) {
+ await iterator.close();
+ break;
+ }
+ }
+ return results;
+ }
+
+ // Governance
+ async registerUser(ctx, role, detailsStr) {
+ const clientMspId = ctx.clientIdentity.getMSPID();
+ const details = JSON.parse(detailsStr || "{}");
+ const roleUpper = role.toUpperCase();
+
+ const roleToOrgMap = {
+ FARMER: "Org1MSP",
+ DISTRIBUTOR: "Org2MSP",
+ RETAILER: "Org3MSP",
+ INSPECTOR: "Org4MSP",
+ };
+
+ const expectedMspId = roleToOrgMap[roleUpper];
+ if (!expectedMspId || clientMspId !== expectedMspId)
+ throw new Error(
+ `Client from ${clientMspId} cannot register a ${role}. Expected admin from ${expectedMspId}.`
+ );
+
+ const id = details.id || `USER-${ctx.stub.getTxID()}`;
+ const key = `${roleUpper}-${id}`;
+
+ const base = {
+ role,
+ id,
+ name: details.name || "",
+ location: details.location || "",
+ walletId: details.walletId || "",
+ };
+
+ let user = null;
+ if (roleUpper === "FARMER") {
+ user = {
+ ...base,
+ registeredProduce: details.registeredProduce || [],
+ ownedProduce: details.ownedProduce || [],
+ certification: details.certification || [],
+ };
+ } else if (roleUpper === "DISTRIBUTOR" || roleUpper === "RETAILER") {
+ user = {
+ ...base,
+ ownedProduce: details.ownedProduce || [],
+ };
+ } else if (roleUpper === "INSPECTOR") {
+ user = {
+ ...base,
+ inspectedProduce: details.inspectedProduce || [],
+ };
+ }
+
+ await this._putState(ctx, key, user);
+ return user;
+ }
+
+ async getUserDetails(ctx, userKey) {
+ const data = await ctx.stub.getState(userKey);
+ if (!data || data.length === 0)
+ throw new Error(`User ${userKey} not found`);
+ return JSON.parse(data.toString());
+ }
+
+ async updateUser(ctx, userKey, detailsStr) {
+ const data = await ctx.stub.getState(userKey);
+ if (!data || data.length === 0)
+ throw new Error(`User ${userKey} not found`);
+ const user = JSON.parse(data.toString());
+ const details = JSON.parse(detailsStr || "{}");
+ Object.assign(user, details);
+ await this._putState(ctx, userKey, user);
+ return user;
+ }
+}
+
+module.exports.contracts = [ProduceContract];
diff --git a/chaincode/package-lock.json b/fabric/chaincode/package-lock.json
diff --git a/fabric/chaincode/package.json b/fabric/chaincode/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "matiru-fabric",
+ "description": "Matiru Chaincode",
+ "version": "1.0.0",
+ "main": "index.js",
+ "scripts": {
+ "start": "fabric-chaincode-node start"
+ },
+ "dependencies": {
+ "fabric-contract-api": "^2.5.8",
+ "fabric-shim": "^2.5.8"
+ }
+}
diff --git a/fabric/network/.gitignore b/fabric/network/.gitignore
@@ -0,0 +1,15 @@
+/channel-artifacts/*.tx
+/channel-artifacts/*.block
+/ledgers
+/ledgers-backup
+/channel-artifacts/*.json
+/org3-artifacts/crypto-config/*
+/org4-artifacts/crypto-config/*
+organizations/fabric-ca/ordererOrg/*
+organizations/fabric-ca/org1/*
+organizations/fabric-ca/org2/*
+organizations/ordererOrganizations/*
+organizations/peerOrganizations/*
+system-genesis-block/*
+*.tar.gz
+log.txt
diff --git a/fabric/network/addOrg3/addOrg3.sh b/fabric/network/addOrg3/addOrg3.sh
@@ -0,0 +1,263 @@
+#!/usr/bin/env bash
+#
+# Copyright IBM Corp All Rights Reserved
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+# This script extends the Hyperledger Fabric test network by adding
+# adding a third organization to the network
+#
+
+# prepending $PWD/../bin to PATH to ensure we are picking up the correct binaries
+# this may be commented out to resolve installed version of tools if desired
+export PATH=${PWD}/../../bin:${PWD}:$PATH
+export FABRIC_CFG_PATH=${PWD}
+export VERBOSE=false
+
+. ../scripts/utils.sh
+
+# Print the usage message
+function printHelp () {
+ echo "Usage: "
+ echo " addOrg3.sh up|down|generate [-c <channel name>] [-t <timeout>] [-d <delay>] [-f <docker-compose-file>] [-s <dbtype>]"
+ echo " addOrg3.sh -h|--help (print this message)"
+ echo " <mode> - one of 'up', 'down', or 'generate'"
+ echo " - 'up' - add org3 to the sample network. You need to bring up the test network and create a channel first."
+ echo " - 'down' - bring down the test network and org3 nodes"
+ echo " - 'generate' - generate required certificates and org definition"
+ echo " -c <channel name> - test network channel name (defaults to \"mychannel\")"
+ echo " -ca <use CA> - Use a CA to generate the crypto material"
+ echo " -t <timeout> - CLI timeout duration in seconds (defaults to 10)"
+ echo " -d <delay> - delay duration in seconds (defaults to 3)"
+ echo " -s <dbtype> - the database backend to use: goleveldb (default) or couchdb"
+ echo " -verbose - verbose mode"
+ echo
+ echo "Typically, one would first generate the required certificates and "
+ echo "genesis block, then bring up the network. e.g.:"
+ echo
+ echo " addOrg3.sh generate"
+ echo " addOrg3.sh up"
+ echo " addOrg3.sh up -c mychannel -s couchdb"
+ echo " addOrg3.sh down"
+ echo
+ echo "Taking all defaults:"
+ echo " addOrg3.sh up"
+ echo " addOrg3.sh down"
+}
+
+# We use the cryptogen tool to generate the cryptographic material
+# (x509 certs) for the new org. After we run the tool, the certs will
+# be put in the organizations folder with org1 and org2
+
+# Create Organziation crypto material using cryptogen or CAs
+function generateOrg3() {
+ # Create crypto material using cryptogen
+ if [ "$CRYPTO" == "cryptogen" ]; then
+ which cryptogen
+ if [ "$?" -ne 0 ]; then
+ fatalln "cryptogen tool not found. exiting"
+ fi
+ infoln "Generating certificates using cryptogen tool"
+
+ infoln "Creating Org3 Identities"
+
+ set -x
+ cryptogen generate --config=org3-crypto.yaml --output="../organizations"
+ res=$?
+ { set +x; } 2>/dev/null
+ if [ $res -ne 0 ]; then
+ fatalln "Failed to generate certificates..."
+ fi
+
+ fi
+
+ # Create crypto material using Fabric CA
+ if [ "$CRYPTO" == "Certificate Authorities" ]; then
+ fabric-ca-client version > /dev/null 2>&1
+ if [[ $? -ne 0 ]]; then
+ echo "ERROR! fabric-ca-client binary not found.."
+ echo
+ echo "Follow the instructions in the Fabric docs to install the Fabric Binaries:"
+ echo "https://hyperledger-fabric.readthedocs.io/en/latest/install.html"
+ exit 1
+ fi
+
+ infoln "Generating certificates using Fabric CA"
+ docker-compose -f $COMPOSE_FILE_CA_ORG3 up -d 2>&1
+
+ . fabric-ca/registerEnroll.sh
+
+ sleep 10
+
+ infoln "Creating Org3 Identities"
+ createOrg3
+
+ fi
+
+ infoln "Generating CCP files for Org3"
+ ./ccp-generate.sh
+}
+
+# Generate channel configuration transaction
+function generateOrg3Definition() {
+ which configtxgen
+ if [ "$?" -ne 0 ]; then
+ fatalln "configtxgen tool not found. exiting"
+ fi
+ infoln "Generating Org3 organization definition"
+ export FABRIC_CFG_PATH=$PWD
+ set -x
+ configtxgen -printOrg Org3MSP > ../organizations/peerOrganizations/org3.example.com/org3.json
+ res=$?
+ { set +x; } 2>/dev/null
+ if [ $res -ne 0 ]; then
+ fatalln "Failed to generate Org3 organization definition..."
+ fi
+}
+
+function Org3Up () {
+ # start org3 nodes
+ if [ "${DATABASE}" == "couchdb" ]; then
+ docker-compose -f $COMPOSE_FILE_ORG3 -f $COMPOSE_FILE_COUCH_ORG3 up -d 2>&1
+ else
+ docker-compose -f $COMPOSE_FILE_ORG3 up -d 2>&1
+ fi
+ if [ $? -ne 0 ]; then
+ fatalln "ERROR !!!! Unable to start Org3 network"
+ fi
+}
+
+# Generate the needed certificates, the genesis block and start the network.
+function addOrg3 () {
+ # If the test network is not up, abort
+ if [ ! -d ../organizations/ordererOrganizations ]; then
+ fatalln "ERROR: Please, run ./network.sh up createChannel first."
+ fi
+
+ # generate artifacts if they don't exist
+ if [ ! -d "../organizations/peerOrganizations/org3.example.com" ]; then
+ generateOrg3
+ generateOrg3Definition
+ fi
+
+ infoln "Bringing up Org3 peer"
+ Org3Up
+
+ # Use the CLI container to create the configuration transaction needed to add
+ # Org3 to the network
+ infoln "Generating and submitting config tx to add Org3"
+ docker exec cli ./scripts/org3-scripts/updateChannelConfig.sh $CHANNEL_NAME $CLI_DELAY $CLI_TIMEOUT $VERBOSE
+ if [ $? -ne 0 ]; then
+ fatalln "ERROR !!!! Unable to create config tx"
+ fi
+
+ infoln "Joining Org3 peers to network"
+ docker exec cli ./scripts/org3-scripts/joinChannel.sh $CHANNEL_NAME $CLI_DELAY $CLI_TIMEOUT $VERBOSE
+ if [ $? -ne 0 ]; then
+ fatalln "ERROR !!!! Unable to join Org3 peers to network"
+ fi
+}
+
+# Tear down running network
+function networkDown () {
+ cd ..
+ ./network.sh down
+}
+
+# Using crpto vs CA. default is cryptogen
+CRYPTO="cryptogen"
+# timeout duration - the duration the CLI should wait for a response from
+# another container before giving up
+CLI_TIMEOUT=10
+#default for delay
+CLI_DELAY=3
+# channel name defaults to "mychannel"
+CHANNEL_NAME="mychannel"
+# use this as the docker compose couch file
+COMPOSE_FILE_COUCH_ORG3=docker/docker-compose-couch-org3.yaml
+# use this as the default docker-compose yaml definition
+COMPOSE_FILE_ORG3=docker/docker-compose-org3.yaml
+# certificate authorities compose file
+COMPOSE_FILE_CA_ORG3=docker/docker-compose-ca-org3.yaml
+# database
+DATABASE="leveldb"
+
+# Parse commandline args
+
+## Parse mode
+if [[ $# -lt 1 ]] ; then
+ printHelp
+ exit 0
+else
+ MODE=$1
+ shift
+fi
+
+# parse flags
+
+while [[ $# -ge 1 ]] ; do
+ key="$1"
+ case $key in
+ -h )
+ printHelp
+ exit 0
+ ;;
+ -c )
+ CHANNEL_NAME="$2"
+ shift
+ ;;
+ -ca )
+ CRYPTO="Certificate Authorities"
+ ;;
+ -t )
+ CLI_TIMEOUT="$2"
+ shift
+ ;;
+ -d )
+ CLI_DELAY="$2"
+ shift
+ ;;
+ -s )
+ DATABASE="$2"
+ shift
+ ;;
+ -verbose )
+ VERBOSE=true
+ shift
+ ;;
+ * )
+ errorln "Unknown flag: $key"
+ printHelp
+ exit 1
+ ;;
+ esac
+ shift
+done
+
+
+# Determine whether starting, stopping, restarting or generating for announce
+if [ "$MODE" == "up" ]; then
+ infoln "Adding org3 to channel '${CHANNEL_NAME}' with '${CLI_TIMEOUT}' seconds and CLI delay of '${CLI_DELAY}' seconds and using database '${DATABASE}'"
+ echo
+elif [ "$MODE" == "down" ]; then
+ EXPMODE="Stopping network"
+elif [ "$MODE" == "generate" ]; then
+ EXPMODE="Generating certs and organization definition for Org3"
+else
+ printHelp
+ exit 1
+fi
+
+#Create the network using docker compose
+if [ "${MODE}" == "up" ]; then
+ addOrg3
+elif [ "${MODE}" == "down" ]; then ## Clear the network
+ networkDown
+elif [ "${MODE}" == "generate" ]; then ## Generate Artifacts
+ generateOrg3
+ generateOrg3Definition
+else
+ printHelp
+ exit 1
+fi
diff --git a/fabric/network/addOrg3/ccp-generate.sh b/fabric/network/addOrg3/ccp-generate.sh
@@ -0,0 +1,36 @@
+#!/usr/bin/env bash
+
+function one_line_pem {
+ echo "`awk 'NF {sub(/\\n/, ""); printf "%s\\\\\\\n",$0;}' $1`"
+}
+
+function json_ccp {
+ local PP=$(one_line_pem $4)
+ local CP=$(one_line_pem $5)
+ sed -e "s/\${ORG}/$1/" \
+ -e "s/\${P0PORT}/$2/" \
+ -e "s/\${CAPORT}/$3/" \
+ -e "s#\${PEERPEM}#$PP#" \
+ -e "s#\${CAPEM}#$CP#" \
+ ccp-template.json
+}
+
+function yaml_ccp {
+ local PP=$(one_line_pem $4)
+ local CP=$(one_line_pem $5)
+ sed -e "s/\${ORG}/$1/" \
+ -e "s/\${P0PORT}/$2/" \
+ -e "s/\${CAPORT}/$3/" \
+ -e "s#\${PEERPEM}#$PP#" \
+ -e "s#\${CAPEM}#$CP#" \
+ ccp-template.yaml | sed -e $'s/\\\\n/\\\n /g'
+}
+
+ORG=3
+P0PORT=11051
+CAPORT=11054
+PEERPEM=../organizations/peerOrganizations/org3.example.com/tlsca/tlsca.org3.example.com-cert.pem
+CAPEM=../organizations/peerOrganizations/org3.example.com/ca/ca.org3.example.com-cert.pem
+
+echo "$(json_ccp $ORG $P0PORT $CAPORT $PEERPEM $CAPEM)" > ../organizations/peerOrganizations/org3.example.com/connection-org3.json
+echo "$(yaml_ccp $ORG $P0PORT $CAPORT $PEERPEM $CAPEM)" > ../organizations/peerOrganizations/org3.example.com/connection-org3.yaml
diff --git a/fabric/network/addOrg3/ccp-template.json b/fabric/network/addOrg3/ccp-template.json
@@ -0,0 +1,49 @@
+{
+ "name": "test-network-org${ORG}",
+ "version": "1.0.0",
+ "client": {
+ "organization": "Org${ORG}",
+ "connection": {
+ "timeout": {
+ "peer": {
+ "endorser": "300"
+ }
+ }
+ }
+ },
+ "organizations": {
+ "Org${ORG}": {
+ "mspid": "Org${ORG}MSP",
+ "peers": [
+ "peer0.org${ORG}.example.com"
+ ],
+ "certificateAuthorities": [
+ "ca.org${ORG}.example.com"
+ ]
+ }
+ },
+ "peers": {
+ "peer0.org${ORG}.example.com": {
+ "url": "grpcs://localhost:${P0PORT}",
+ "tlsCACerts": {
+ "pem": "${PEERPEM}"
+ },
+ "grpcOptions": {
+ "ssl-target-name-override": "peer0.org${ORG}.example.com",
+ "hostnameOverride": "peer0.org${ORG}.example.com"
+ }
+ }
+ },
+ "certificateAuthorities": {
+ "ca.org${ORG}.example.com": {
+ "url": "https://localhost:${CAPORT}",
+ "caName": "ca-org${ORG}",
+ "tlsCACerts": {
+ "pem": "${CAPEM}"
+ },
+ "httpOptions": {
+ "verify": false
+ }
+ }
+ }
+}
diff --git a/fabric/network/addOrg3/ccp-template.yaml b/fabric/network/addOrg3/ccp-template.yaml
@@ -0,0 +1,34 @@
+---
+name: test-network-org${ORG}
+version: 1.0.0
+client:
+ organization: Org${ORG}
+ connection:
+ timeout:
+ peer:
+ endorser: '300'
+organizations:
+ Org${ORG}:
+ mspid: Org${ORG}MSP
+ peers:
+ - peer0.org${ORG}.example.com
+ certificateAuthorities:
+ - ca.org${ORG}.example.com
+peers:
+ peer0.org${ORG}.example.com:
+ url: grpcs://localhost:${P0PORT}
+ tlsCACerts:
+ pem: |
+ ${PEERPEM}
+ grpcOptions:
+ ssl-target-name-override: peer0.org${ORG}.example.com
+ hostnameOverride: peer0.org${ORG}.example.com
+certificateAuthorities:
+ ca.org${ORG}.example.com:
+ url: https://localhost:${CAPORT}
+ caName: ca-org${ORG}
+ tlsCACerts:
+ pem: |
+ ${CAPEM}
+ httpOptions:
+ verify: false
diff --git a/fabric/network/addOrg3/configtx.yaml b/fabric/network/addOrg3/configtx.yaml
@@ -0,0 +1,38 @@
+# Copyright IBM Corp. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+---
+################################################################################
+#
+# Section: Organizations
+#
+# - This section defines the different organizational identities which will
+# be referenced later in the configuration.
+#
+################################################################################
+Organizations:
+ - &Org3
+ # DefaultOrg defines the organization which is used in the sampleconfig
+ # of the fabric.git development environment
+ Name: Org3MSP
+
+ # ID to load the MSP definition as
+ ID: Org3MSP
+
+ MSPDir: ../organizations/peerOrganizations/org3.example.com/msp
+
+ Policies:
+ Readers:
+ Type: Signature
+ Rule: "OR('Org3MSP.admin', 'Org3MSP.peer', 'Org3MSP.client')"
+ Writers:
+ Type: Signature
+ Rule: "OR('Org3MSP.admin', 'Org3MSP.client')"
+ Admins:
+ Type: Signature
+ Rule: "OR('Org3MSP.admin')"
+ Endorsement:
+ Type: Signature
+ Rule: "OR('Org3MSP.peer')"
diff --git a/fabric/network/addOrg3/docker/docker-compose-ca-org3.yaml b/fabric/network/addOrg3/docker/docker-compose-ca-org3.yaml
@@ -0,0 +1,27 @@
+# Copyright IBM Corp. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+version: '2'
+
+networks:
+ test:
+ name: fabric_test
+
+services:
+ ca_org3:
+ image: hyperledger/fabric-ca:latest
+ labels:
+ service: hyperledger-fabric
+ environment:
+ - FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server
+ - FABRIC_CA_SERVER_CA_NAME=ca-org3
+ - FABRIC_CA_SERVER_TLS_ENABLED=true
+ - FABRIC_CA_SERVER_PORT=11054
+ ports:
+ - "11054:11054"
+ command: sh -c 'fabric-ca-server start -b admin:adminpw -d'
+ volumes:
+ - ../fabric-ca/org3:/etc/hyperledger/fabric-ca-server
+ container_name: ca_org3
diff --git a/fabric/network/addOrg3/docker/docker-compose-couch-org3.yaml b/fabric/network/addOrg3/docker/docker-compose-couch-org3.yaml
@@ -0,0 +1,42 @@
+# Copyright IBM Corp. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+version: '2'
+
+networks:
+ test:
+ name: fabric_test
+
+services:
+ couchdb4:
+ container_name: couchdb4
+ image: couchdb:3.1.1
+ labels:
+ service: hyperledger-fabric
+ # Populate the COUCHDB_USER and COUCHDB_PASSWORD to set an admin user and password
+ # for CouchDB. This will prevent CouchDB from operating in an "Admin Party" mode.
+ environment:
+ - COUCHDB_USER=admin
+ - COUCHDB_PASSWORD=adminpw
+ # Comment/Uncomment the port mapping if you want to hide/expose the CouchDB service,
+ # for example map it to utilize Fauxton User Interface in dev environments.
+ ports:
+ - "9984:5984"
+ networks:
+ - test
+
+ peer0.org3.example.com:
+ environment:
+ - CORE_LEDGER_STATE_STATEDATABASE=CouchDB
+ - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb4:5984
+ # The CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME and CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD
+ # provide the credentials for ledger to connect to CouchDB. The username and password must
+ # match the username and password set for the associated CouchDB.
+ - CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin
+ - CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=adminpw
+ depends_on:
+ - couchdb4
+ networks:
+ - test
diff --git a/fabric/network/addOrg3/docker/docker-compose-org3.yaml b/fabric/network/addOrg3/docker/docker-compose-org3.yaml
@@ -0,0 +1,52 @@
+# Copyright IBM Corp. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+version: '2'
+
+volumes:
+ peer0.org3.example.com:
+
+networks:
+ test:
+ name: fabric_test
+
+services:
+
+ peer0.org3.example.com:
+ container_name: peer0.org3.example.com
+ image: hyperledger/fabric-peer:latest
+ labels:
+ service: hyperledger-fabric
+ environment:
+ #Generic peer variables
+ - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
+ - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=fabric_test
+ - FABRIC_LOGGING_SPEC=INFO
+ #- FABRIC_LOGGING_SPEC=DEBUG
+ - CORE_PEER_TLS_ENABLED=true
+ - CORE_PEER_PROFILE_ENABLED=true
+ - CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/fabric/tls/server.crt
+ - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/fabric/tls/server.key
+ - CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/tls/ca.crt
+ # Peer specific variabes
+ - CORE_PEER_ID=peer0.org3.example.com
+ - CORE_PEER_ADDRESS=peer0.org3.example.com:11051
+ - CORE_PEER_LISTENADDRESS=0.0.0.0:11051
+ - CORE_PEER_CHAINCODEADDRESS=peer0.org3.example.com:11052
+ - CORE_PEER_CHAINCODELISTENADDRESS=0.0.0.0:11052
+ - CORE_PEER_GOSSIP_BOOTSTRAP=peer0.org3.example.com:11051
+ - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org3.example.com:11051
+ - CORE_PEER_LOCALMSPID=Org3MSP
+ volumes:
+ - /var/run/docker.sock:/host/var/run/docker.sock
+ - ../../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/msp:/etc/hyperledger/fabric/msp
+ - ../../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls:/etc/hyperledger/fabric/tls
+ - peer0.org3.example.com:/var/hyperledger/production
+ working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
+ command: peer node start
+ ports:
+ - 11051:11051
+ networks:
+ - test
diff --git a/fabric/network/addOrg3/fabric-ca/org3/fabric-ca-server-config.yaml b/fabric/network/addOrg3/fabric-ca/org3/fabric-ca-server-config.yaml
@@ -0,0 +1,406 @@
+#############################################################################
+# This is a configuration file for the fabric-ca-server command.
+#
+# COMMAND LINE ARGUMENTS AND ENVIRONMENT VARIABLES
+# ------------------------------------------------
+# Each configuration element can be overridden via command line
+# arguments or environment variables. The precedence for determining
+# the value of each element is as follows:
+# 1) command line argument
+# Examples:
+# a) --port 443
+# To set the listening port
+# b) --ca.keyfile ../mykey.pem
+# To set the "keyfile" element in the "ca" section below;
+# note the '.' separator character.
+# 2) environment variable
+# Examples:
+# a) FABRIC_CA_SERVER_PORT=443
+# To set the listening port
+# b) FABRIC_CA_SERVER_CA_KEYFILE="../mykey.pem"
+# To set the "keyfile" element in the "ca" section below;
+# note the '_' separator character.
+# 3) configuration file
+# 4) default value (if there is one)
+# All default values are shown beside each element below.
+#
+# FILE NAME ELEMENTS
+# ------------------
+# The value of all fields whose name ends with "file" or "files" are
+# name or names of other files.
+# For example, see "tls.certfile" and "tls.clientauth.certfiles".
+# The value of each of these fields can be a simple filename, a
+# relative path, or an absolute path. If the value is not an
+# absolute path, it is interpretted as being relative to the location
+# of this configuration file.
+#
+#############################################################################
+
+# Version of config file
+version: 1.2.0
+
+# Server's listening port (default: 7054)
+port: 11054
+
+# Enables debug logging (default: false)
+debug: false
+
+# Size limit of an acceptable CRL in bytes (default: 512000)
+crlsizelimit: 512000
+
+#############################################################################
+# TLS section for the server's listening port
+#
+# The following types are supported for client authentication: NoClientCert,
+# RequestClientCert, RequireAnyClientCert, VerifyClientCertIfGiven,
+# and RequireAndVerifyClientCert.
+#
+# Certfiles is a list of root certificate authorities that the server uses
+# when verifying client certificates.
+#############################################################################
+tls:
+ # Enable TLS (default: false)
+ enabled: true
+ # TLS for the server's listening port
+ certfile:
+ keyfile:
+ clientauth:
+ type: noclientcert
+ certfiles:
+
+#############################################################################
+# The CA section contains information related to the Certificate Authority
+# including the name of the CA, which should be unique for all members
+# of a blockchain network. It also includes the key and certificate files
+# used when issuing enrollment certificates (ECerts) and transaction
+# certificates (TCerts).
+# The chainfile (if it exists) contains the certificate chain which
+# should be trusted for this CA, where the 1st in the chain is always the
+# root CA certificate.
+#############################################################################
+ca:
+ # Name of this CA
+ name: Org3CA
+ # Key file (is only used to import a private key into BCCSP)
+ keyfile:
+ # Certificate file (default: ca-cert.pem)
+ certfile:
+ # Chain file
+ chainfile:
+
+#############################################################################
+# The gencrl REST endpoint is used to generate a CRL that contains revoked
+# certificates. This section contains configuration options that are used
+# during gencrl request processing.
+#############################################################################
+crl:
+ # Specifies expiration for the generated CRL. The number of hours
+ # specified by this property is added to the UTC time, the resulting time
+ # is used to set the 'Next Update' date of the CRL.
+ expiry: 24h
+
+#############################################################################
+# The registry section controls how the fabric-ca-server does two things:
+# 1) authenticates enrollment requests which contain a username and password
+# (also known as an enrollment ID and secret).
+# 2) once authenticated, retrieves the identity's attribute names and
+# values which the fabric-ca-server optionally puts into TCerts
+# which it issues for transacting on the Hyperledger Fabric blockchain.
+# These attributes are useful for making access control decisions in
+# chaincode.
+# There are two main configuration options:
+# 1) The fabric-ca-server is the registry.
+# This is true if "ldap.enabled" in the ldap section below is false.
+# 2) An LDAP server is the registry, in which case the fabric-ca-server
+# calls the LDAP server to perform these tasks.
+# This is true if "ldap.enabled" in the ldap section below is true,
+# which means this "registry" section is ignored.
+#############################################################################
+registry:
+ # Maximum number of times a password/secret can be reused for enrollment
+ # (default: -1, which means there is no limit)
+ maxenrollments: -1
+
+ # Contains identity information which is used when LDAP is disabled
+ identities:
+ - name: admin
+ pass: adminpw
+ type: client
+ affiliation: ""
+ attrs:
+ hf.Registrar.Roles: "*"
+ hf.Registrar.DelegateRoles: "*"
+ hf.Revoker: true
+ hf.IntermediateCA: true
+ hf.GenCRL: true
+ hf.Registrar.Attributes: "*"
+ hf.AffiliationMgr: true
+
+#############################################################################
+# Database section
+# Supported types are: "sqlite3", "postgres", and "mysql".
+# The datasource value depends on the type.
+# If the type is "sqlite3", the datasource value is a file name to use
+# as the database store. Since "sqlite3" is an embedded database, it
+# may not be used if you want to run the fabric-ca-server in a cluster.
+# To run the fabric-ca-server in a cluster, you must choose "postgres"
+# or "mysql".
+#############################################################################
+db:
+ type: sqlite3
+ datasource: fabric-ca-server.db
+ tls:
+ enabled: false
+ certfiles:
+ client:
+ certfile:
+ keyfile:
+
+#############################################################################
+# LDAP section
+# If LDAP is enabled, the fabric-ca-server calls LDAP to:
+# 1) authenticate enrollment ID and secret (i.e. username and password)
+# for enrollment requests;
+# 2) To retrieve identity attributes
+#############################################################################
+ldap:
+ # Enables or disables the LDAP client (default: false)
+ # If this is set to true, the "registry" section is ignored.
+ enabled: false
+ # The URL of the LDAP server
+ url: ldap://<adminDN>:<adminPassword>@<host>:<port>/<base>
+ # TLS configuration for the client connection to the LDAP server
+ tls:
+ certfiles:
+ client:
+ certfile:
+ keyfile:
+ # Attribute related configuration for mapping from LDAP entries to Fabric CA attributes
+ attribute:
+ # 'names' is an array of strings containing the LDAP attribute names which are
+ # requested from the LDAP server for an LDAP identity's entry
+ names: ['uid','member']
+ # The 'converters' section is used to convert an LDAP entry to the value of
+ # a fabric CA attribute.
+ # For example, the following converts an LDAP 'uid' attribute
+ # whose value begins with 'revoker' to a fabric CA attribute
+ # named "hf.Revoker" with a value of "true" (because the boolean expression
+ # evaluates to true).
+ # converters:
+ # - name: hf.Revoker
+ # value: attr("uid") =~ "revoker*"
+ converters:
+ - name:
+ value:
+ # The 'maps' section contains named maps which may be referenced by the 'map'
+ # function in the 'converters' section to map LDAP responses to arbitrary values.
+ # For example, assume a user has an LDAP attribute named 'member' which has multiple
+ # values which are each a distinguished name (i.e. a DN). For simplicity, assume the
+ # values of the 'member' attribute are 'dn1', 'dn2', and 'dn3'.
+ # Further assume the following configuration.
+ # converters:
+ # - name: hf.Registrar.Roles
+ # value: map(attr("member"),"groups")
+ # maps:
+ # groups:
+ # - name: dn1
+ # value: peer
+ # - name: dn2
+ # value: client
+ # The value of the user's 'hf.Registrar.Roles' attribute is then computed to be
+ # "peer,client,dn3". This is because the value of 'attr("member")' is
+ # "dn1,dn2,dn3", and the call to 'map' with a 2nd argument of
+ # "group" replaces "dn1" with "peer" and "dn2" with "client".
+ maps:
+ groups:
+ - name:
+ value:
+
+#############################################################################
+# Affiliations section. Fabric CA server can be bootstrapped with the
+# affiliations specified in this section. Affiliations are specified as maps.
+# For example:
+# businessunit1:
+# department1:
+# - team1
+# businessunit2:
+# - department2
+# - department3
+#
+# Affiliations are hierarchical in nature. In the above example,
+# department1 (used as businessunit1.department1) is the child of businessunit1.
+# team1 (used as businessunit1.department1.team1) is the child of department1.
+# department2 (used as businessunit2.department2) and department3 (businessunit2.department3)
+# are children of businessunit2.
+# Note: Affiliations are case sensitive except for the non-leaf affiliations
+# (like businessunit1, department1, businessunit2) that are specified in the configuration file,
+# which are always stored in lower case.
+#############################################################################
+affiliations:
+ org1:
+ - department1
+ - department2
+ org2:
+ - department1
+
+#############################################################################
+# Signing section
+#
+# The "default" subsection is used to sign enrollment certificates;
+# the default expiration ("expiry" field) is "8760h", which is 1 year in hours.
+#
+# The "ca" profile subsection is used to sign intermediate CA certificates;
+# the default expiration ("expiry" field) is "43800h" which is 5 years in hours.
+# Note that "isca" is true, meaning that it issues a CA certificate.
+# A maxpathlen of 0 means that the intermediate CA cannot issue other
+# intermediate CA certificates, though it can still issue end entity certificates.
+# (See RFC 5280, section 4.2.1.9)
+#
+# The "tls" profile subsection is used to sign TLS certificate requests;
+# the default expiration ("expiry" field) is "8760h", which is 1 year in hours.
+#############################################################################
+signing:
+ default:
+ usage:
+ - digital signature
+ expiry: 8760h
+ profiles:
+ ca:
+ usage:
+ - cert sign
+ - crl sign
+ expiry: 43800h
+ caconstraint:
+ isca: true
+ maxpathlen: 0
+ tls:
+ usage:
+ - signing
+ - key encipherment
+ - server auth
+ - client auth
+ - key agreement
+ expiry: 8760h
+
+###########################################################################
+# Certificate Signing Request (CSR) section.
+# This controls the creation of the root CA certificate.
+# The expiration for the root CA certificate is configured with the
+# "ca.expiry" field below, whose default value is "131400h" which is
+# 15 years in hours.
+# The pathlength field is used to limit CA certificate hierarchy as described
+# in section 4.2.1.9 of RFC 5280.
+# Examples:
+# 1) No pathlength value means no limit is requested.
+# 2) pathlength == 1 means a limit of 1 is requested which is the default for
+# a root CA. This means the root CA can issue intermediate CA certificates,
+# but these intermediate CAs may not in turn issue other CA certificates
+# though they can still issue end entity certificates.
+# 3) pathlength == 0 means a limit of 0 is requested;
+# this is the default for an intermediate CA, which means it can not issue
+# CA certificates though it can still issue end entity certificates.
+###########################################################################
+csr:
+ cn: ca.org3.example.com
+ names:
+ - C: US
+ ST: "North Carolina"
+ L: "Raleigh"
+ O: org3.example.com
+ OU:
+ hosts:
+ - localhost
+ - org3.example.com
+ ca:
+ expiry: 131400h
+ pathlength: 1
+
+#############################################################################
+# BCCSP (BlockChain Crypto Service Provider) section is used to select which
+# crypto library implementation to use
+#############################################################################
+bccsp:
+ default: SW
+ sw:
+ hash: SHA2
+ security: 256
+ filekeystore:
+ # The directory used for the software file-based keystore
+ keystore: msp/keystore
+
+#############################################################################
+# Multi CA section
+#
+# Each Fabric CA server contains one CA by default. This section is used
+# to configure multiple CAs in a single server.
+#
+# 1) --cacount <number-of-CAs>
+# Automatically generate <number-of-CAs> non-default CAs. The names of these
+# additional CAs are "ca1", "ca2", ... "caN", where "N" is <number-of-CAs>
+# This is particularly useful in a development environment to quickly set up
+# multiple CAs. Note that, this config option is not applicable to intermediate CA server
+# i.e., Fabric CA server that is started with intermediate.parentserver.url config
+# option (-u command line option)
+#
+# 2) --cafiles <CA-config-files>
+# For each CA config file in the list, generate a separate signing CA. Each CA
+# config file in this list MAY contain all of the same elements as are found in
+# the server config file except port, debug, and tls sections.
+#
+# Examples:
+# fabric-ca-server start -b admin:adminpw --cacount 2
+#
+# fabric-ca-server start -b admin:adminpw --cafiles ca/ca1/fabric-ca-server-config.yaml
+# --cafiles ca/ca2/fabric-ca-server-config.yaml
+#
+#############################################################################
+
+cacount:
+
+cafiles:
+
+#############################################################################
+# Intermediate CA section
+#
+# The relationship between servers and CAs is as follows:
+# 1) A single server process may contain or function as one or more CAs.
+# This is configured by the "Multi CA section" above.
+# 2) Each CA is either a root CA or an intermediate CA.
+# 3) Each intermediate CA has a parent CA which is either a root CA or another intermediate CA.
+#
+# This section pertains to configuration of #2 and #3.
+# If the "intermediate.parentserver.url" property is set,
+# then this is an intermediate CA with the specified parent
+# CA.
+#
+# parentserver section
+# url - The URL of the parent server
+# caname - Name of the CA to enroll within the server
+#
+# enrollment section used to enroll intermediate CA with parent CA
+# profile - Name of the signing profile to use in issuing the certificate
+# label - Label to use in HSM operations
+#
+# tls section for secure socket connection
+# certfiles - PEM-encoded list of trusted root certificate files
+# client:
+# certfile - PEM-encoded certificate file for when client authentication
+# is enabled on server
+# keyfile - PEM-encoded key file for when client authentication
+# is enabled on server
+#############################################################################
+intermediate:
+ parentserver:
+ url:
+ caname:
+
+ enrollment:
+ hosts:
+ profile:
+ label:
+
+ tls:
+ certfiles:
+ client:
+ certfile:
+ keyfile:
diff --git a/fabric/network/addOrg3/fabric-ca/registerEnroll.sh b/fabric/network/addOrg3/fabric-ca/registerEnroll.sh
@@ -0,0 +1,87 @@
+#!/usr/bin/env bash
+#
+# Copyright IBM Corp All Rights Reserved
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+function createOrg3 {
+ infoln "Enrolling the CA admin"
+ mkdir -p ../organizations/peerOrganizations/org3.example.com/
+
+ export FABRIC_CA_CLIENT_HOME=${PWD}/../organizations/peerOrganizations/org3.example.com/
+
+ set -x
+ fabric-ca-client enroll -u https://admin:adminpw@localhost:11054 --caname ca-org3 --tls.certfiles "${PWD}/fabric-ca/org3/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ echo 'NodeOUs:
+ Enable: true
+ ClientOUIdentifier:
+ Certificate: cacerts/localhost-11054-ca-org3.pem
+ OrganizationalUnitIdentifier: client
+ PeerOUIdentifier:
+ Certificate: cacerts/localhost-11054-ca-org3.pem
+ OrganizationalUnitIdentifier: peer
+ AdminOUIdentifier:
+ Certificate: cacerts/localhost-11054-ca-org3.pem
+ OrganizationalUnitIdentifier: admin
+ OrdererOUIdentifier:
+ Certificate: cacerts/localhost-11054-ca-org3.pem
+ OrganizationalUnitIdentifier: orderer' > "${PWD}/../organizations/peerOrganizations/org3.example.com/msp/config.yaml"
+
+ infoln "Registering peer0"
+ set -x
+ fabric-ca-client register --caname ca-org3 --id.name peer0 --id.secret peer0pw --id.type peer --tls.certfiles "${PWD}/fabric-ca/org3/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ infoln "Registering user"
+ set -x
+ fabric-ca-client register --caname ca-org3 --id.name user1 --id.secret user1pw --id.type client --tls.certfiles "${PWD}/fabric-ca/org3/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ infoln "Registering the org admin"
+ set -x
+ fabric-ca-client register --caname ca-org3 --id.name org3admin --id.secret org3adminpw --id.type admin --tls.certfiles "${PWD}/fabric-ca/org3/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ infoln "Generating the peer0 msp"
+ set -x
+ fabric-ca-client enroll -u https://peer0:peer0pw@localhost:11054 --caname ca-org3 -M "${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/msp" --csr.hosts peer0.org3.example.com --tls.certfiles "${PWD}/fabric-ca/org3/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ cp "${PWD}/../organizations/peerOrganizations/org3.example.com/msp/config.yaml" "${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/msp/config.yaml"
+
+ infoln "Generating the peer0-tls certificates"
+ set -x
+ fabric-ca-client enroll -u https://peer0:peer0pw@localhost:11054 --caname ca-org3 -M "${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls" --enrollment.profile tls --csr.hosts peer0.org3.example.com --csr.hosts localhost --tls.certfiles "${PWD}/fabric-ca/org3/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+
+ cp "${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/tlscacerts/"* "${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/ca.crt"
+ cp "${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/signcerts/"* "${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/server.crt"
+ cp "${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/keystore/"* "${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/server.key"
+
+ mkdir "${PWD}/../organizations/peerOrganizations/org3.example.com/msp/tlscacerts"
+ cp "${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/tlscacerts/"* "${PWD}/../organizations/peerOrganizations/org3.example.com/msp/tlscacerts/ca.crt"
+
+ mkdir "${PWD}/../organizations/peerOrganizations/org3.example.com/tlsca"
+ cp "${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/tlscacerts/"* "${PWD}/../organizations/peerOrganizations/org3.example.com/tlsca/tlsca.org3.example.com-cert.pem"
+
+ mkdir "${PWD}/../organizations/peerOrganizations/org3.example.com/ca"
+ cp "${PWD}/../organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/msp/cacerts/"* "${PWD}/../organizations/peerOrganizations/org3.example.com/ca/ca.org3.example.com-cert.pem"
+
+ infoln "Generating the user msp"
+ set -x
+ fabric-ca-client enroll -u https://user1:user1pw@localhost:11054 --caname ca-org3 -M "${PWD}/../organizations/peerOrganizations/org3.example.com/users/User1@org3.example.com/msp" --tls.certfiles "${PWD}/fabric-ca/org3/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ cp "${PWD}/../organizations/peerOrganizations/org3.example.com/msp/config.yaml" "${PWD}/../organizations/peerOrganizations/org3.example.com/users/User1@org3.example.com/msp/config.yaml"
+
+ infoln "Generating the org admin msp"
+ set -x
+ fabric-ca-client enroll -u https://org3admin:org3adminpw@localhost:11054 --caname ca-org3 -M "${PWD}/../organizations/peerOrganizations/org3.example.com/users/Admin@org3.example.com/msp" --tls.certfiles "${PWD}/fabric-ca/org3/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ cp "${PWD}/../organizations/peerOrganizations/org3.example.com/msp/config.yaml" "${PWD}/../organizations/peerOrganizations/org3.example.com/users/Admin@org3.example.com/msp/config.yaml"
+}
diff --git a/fabric/network/addOrg3/org3-crypto.yaml b/fabric/network/addOrg3/org3-crypto.yaml
@@ -0,0 +1,21 @@
+# Copyright IBM Corp. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+# ---------------------------------------------------------------------------
+# "PeerOrgs" - Definition of organizations managing peer nodes
+# ---------------------------------------------------------------------------
+PeerOrgs:
+ # ---------------------------------------------------------------------------
+ # Org3
+ # ---------------------------------------------------------------------------
+ - Name: Org3
+ Domain: org3.example.com
+ EnableNodeOUs: true
+ Template:
+ Count: 1
+ SANS:
+ - localhost
+ Users:
+ Count: 1
diff --git a/fabric/network/addOrg4/addOrg4.sh b/fabric/network/addOrg4/addOrg4.sh
@@ -0,0 +1,263 @@
+#!/usr/bin/env bash
+#
+# Copyright IBM Corp All Rights Reserved
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+# This script extends the Hyperledger Fabric test network by adding
+# adding a fourth organization to the network
+#
+
+# prepending $PWD/../bin to PATH to ensure we are picking up the correct binaries
+# this may be commented out to resolve installed version of tools if desired
+export PATH=${PWD}/../../bin:${PWD}:$PATH
+export FABRIC_CFG_PATH=${PWD}
+export VERBOSE=false
+
+. ../scripts/utils.sh
+
+# Print the usage message
+function printHelp () {
+ echo "Usage: "
+ echo " addOrg4.sh up|down|generate [-c <channel name>] [-t <timeout>] [-d <delay>] [-f <docker-compose-file>] [-s <dbtype>]"
+ echo " addOrg4.sh -h|--help (print this message)"
+ echo " <mode> - one of 'up', 'down', or 'generate'"
+ echo " - 'up' - add org4 to the sample network. You need to bring up the test network and create a channel first."
+ echo " - 'down' - bring down the test network and org4 nodes"
+ echo " - 'generate' - generate required certificates and org definition"
+ echo " -c <channel name> - test network channel name (defaults to \"mychannel\")"
+ echo " -ca <use CA> - Use a CA to generate the crypto material"
+ echo " -t <timeout> - CLI timeout duration in seconds (defaults to 10)"
+ echo " -d <delay> - delay duration in seconds (defaults to 3)"
+ echo " -s <dbtype> - the database backend to use: goleveldb (default) or couchdb"
+ echo " -verbose - verbose mode"
+ echo
+ echo "Typically, one would first generate the required certificates and "
+ echo "genesis block, then bring up the network. e.g.:"
+ echo
+ echo " addOrg4.sh generate"
+ echo " addOrg4.sh up"
+ echo " addOrg4.sh up -c mychannel -s couchdb"
+ echo " addOrg4.sh down"
+ echo
+ echo "Taking all defaults:"
+ echo " addOrg4.sh up"
+ echo " addOrg4.sh down"
+}
+
+# We use the cryptogen tool to generate the cryptographic material
+# (x509 certs) for the new org. After we run the tool, the certs will
+# be put in the organizations folder with org1 and org2
+
+# Create Organziation crypto material using cryptogen or CAs
+function generateOrg4() {
+ # Create crypto material using cryptogen
+ if [ "$CRYPTO" == "cryptogen" ]; then
+ which cryptogen
+ if [ "$?" -ne 0 ]; then
+ fatalln "cryptogen tool not found. exiting"
+ fi
+ infoln "Generating certificates using cryptogen tool"
+
+ infoln "Creating Org4 Identities"
+
+ set -x
+ cryptogen generate --config=org4-crypto.yaml --output="../organizations"
+ res=$?
+ { set +x; } 2>/dev/null
+ if [ $res -ne 0 ]; then
+ fatalln "Failed to generate certificates..."
+ fi
+
+ fi
+
+ # Create crypto material using Fabric CA
+ if [ "$CRYPTO" == "Certificate Authorities" ]; then
+ fabric-ca-client version > /dev/null 2>&1
+ if [[ $? -ne 0 ]]; then
+ echo "ERROR! fabric-ca-client binary not found.."
+ echo
+ echo "Follow the instructions in the Fabric docs to install the Fabric Binaries:"
+ echo "https://hyperledger-fabric.readthedocs.io/en/latest/install.html"
+ exit 1
+ fi
+
+ infoln "Generating certificates using Fabric CA"
+ docker-compose -f $COMPOSE_FILE_CA_ORG4 up -d 2>&1
+
+ . fabric-ca/registerEnroll.sh
+
+ sleep 10
+
+ infoln "Creating Org4 Identities"
+ createOrg4
+
+ fi
+
+ infoln "Generating CCP files for Org4"
+ ./ccp-generate.sh
+}
+
+# Generate channel configuration transaction
+function generateOrg4Definition() {
+ which configtxgen
+ if [ "$?" -ne 0 ]; then
+ fatalln "configtxgen tool not found. exiting"
+ fi
+ infoln "Generating Org4 organization definition"
+ export FABRIC_CFG_PATH=$PWD
+ set -x
+ configtxgen -printOrg Org4MSP > ../organizations/peerOrganizations/org4.example.com/org4.json
+ res=$?
+ { set +x; } 2>/dev/null
+ if [ $res -ne 0 ]; then
+ fatalln "Failed to generate Org4 organization definition..."
+ fi
+}
+
+function Org4Up () {
+ # start org4 nodes
+ if [ "${DATABASE}" == "couchdb" ]; then
+ docker-compose -f $COMPOSE_FILE_ORG4 -f $COMPOSE_FILE_COUCH_ORG4 up -d 2>&1
+ else
+ docker-compose -f $COMPOSE_FILE_ORG4 up -d 2>&1
+ fi
+ if [ $? -ne 0 ]; then
+ fatalln "ERROR !!!! Unable to start Org4 network"
+ fi
+}
+
+# Generate the needed certificates, the genesis block and start the network.
+function addOrg4 () {
+ # If the test network is not up, abort
+ if [ ! -d ../organizations/ordererOrganizations ]; then
+ fatalln "ERROR: Please, run ./network.sh up createChannel first."
+ fi
+
+ # generate artifacts if they don't exist
+ if [ ! -d "../organizations/peerOrganizations/org4.example.com" ]; then
+ generateOrg4
+ generateOrg4Definition
+ fi
+
+ infoln "Bringing up Org4 peer"
+ Org4Up
+
+ # Use the CLI container to create the configuration transaction needed to add
+ # Org4 to the network
+ infoln "Generating and submitting config tx to add Org4"
+ docker exec cli ./scripts/org4-scripts/updateChannelConfig.sh $CHANNEL_NAME $CLI_DELAY $CLI_TIMEOUT $VERBOSE
+ if [ $? -ne 0 ]; then
+ fatalln "ERROR !!!! Unable to create config tx"
+ fi
+
+ infoln "Joining Org4 peers to network"
+ docker exec cli ./scripts/org4-scripts/joinChannel.sh $CHANNEL_NAME $CLI_DELAY $CLI_TIMEOUT $VERBOSE
+ if [ $? -ne 0 ]; then
+ fatalln "ERROR !!!! Unable to join Org4 peers to network"
+ fi
+}
+
+# Tear down running network
+function networkDown () {
+ cd ..
+ ./network.sh down
+}
+
+# Using crpto vs CA. default is cryptogen
+CRYPTO="cryptogen"
+# timeout duration - the duration the CLI should wait for a response from
+# another container before giving up
+CLI_TIMEOUT=10
+#default for delay
+CLI_DELAY=3
+# channel name defaults to "mychannel"
+CHANNEL_NAME="mychannel"
+# use this as the docker compose couch file
+COMPOSE_FILE_COUCH_ORG4=docker/docker-compose-couch-org4.yaml
+# use this as the default docker-compose yaml definition
+COMPOSE_FILE_ORG4=docker/docker-compose-org4.yaml
+# certificate authorities compose file
+COMPOSE_FILE_CA_ORG4=docker/docker-compose-ca-org4.yaml
+# database
+DATABASE="leveldb"
+
+# Parse commandline args
+
+## Parse mode
+if [[ $# -lt 1 ]] ; then
+ printHelp
+ exit 0
+else
+ MODE=$1
+ shift
+fi
+
+# parse flags
+
+while [[ $# -ge 1 ]] ; do
+ key="$1"
+ case $key in
+ -h )
+ printHelp
+ exit 0
+ ;;
+ -c )
+ CHANNEL_NAME="$2"
+ shift
+ ;;
+ -ca )
+ CRYPTO="Certificate Authorities"
+ ;;
+ -t )
+ CLI_TIMEOUT="$2"
+ shift
+ ;;
+ -d )
+ CLI_DELAY="$2"
+ shift
+ ;;
+ -s )
+ DATABASE="$2"
+ shift
+ ;;
+ -verbose )
+ VERBOSE=true
+ shift
+ ;;
+ * )
+ errorln "Unknown flag: $key"
+ printHelp
+ exit 1
+ ;;
+ esac
+ shift
+done
+
+
+# Determine whether starting, stopping, restarting or generating for announce
+if [ "$MODE" == "up" ]; then
+ infoln "Adding org4 to channel '${CHANNEL_NAME}' with '${CLI_TIMEOUT}' seconds and CLI delay of '${CLI_DELAY}' seconds and using database '${DATABASE}'"
+ echo
+elif [ "$MODE" == "down" ]; then
+ EXPMODE="Stopping network"
+elif [ "$MODE" == "generate" ]; then
+ EXPMODE="Generating certs and organization definition for Org4"
+else
+ printHelp
+ exit 1
+fi
+
+#Create the network using docker compose
+if [ "${MODE}" == "up" ]; then
+ addOrg4
+elif [ "${MODE}" == "down" ]; then ## Clear the network
+ networkDown
+elif [ "${MODE}" == "generate" ]; then ## Generate Artifacts
+ generateOrg4
+ generateOrg4Definition
+else
+ printHelp
+ exit 1
+fi
diff --git a/fabric/network/addOrg4/ccp-generate.sh b/fabric/network/addOrg4/ccp-generate.sh
@@ -0,0 +1,36 @@
+#!/usr/bin/env bash
+
+function one_line_pem {
+ echo "`awk 'NF {sub(/\\n/, ""); printf "%s\\\\\\\n",$0;}' $1`"
+}
+
+function json_ccp {
+ local PP=$(one_line_pem $4)
+ local CP=$(one_line_pem $5)
+ sed -e "s/\${ORG}/$1/" \
+ -e "s/\${P0PORT}/$2/" \
+ -e "s/\${CAPORT}/$3/" \
+ -e "s#\${PEERPEM}#$PP#" \
+ -e "s#\${CAPEM}#$CP#" \
+ ccp-template.json
+}
+
+function yaml_ccp {
+ local PP=$(one_line_pem $4)
+ local CP=$(one_line_pem $5)
+ sed -e "s/\${ORG}/$1/" \
+ -e "s/\${P0PORT}/$2/" \
+ -e "s/\${CAPORT}/$3/" \
+ -e "s#\${PEERPEM}#$PP#" \
+ -e "s#\${CAPEM}#$CP#" \
+ ccp-template.yaml | sed -e $'s/\\\\n/\\\n /g'
+}
+
+ORG=4
+P0PORT=13051
+CAPORT=13054
+PEERPEM=../organizations/peerOrganizations/org4.example.com/tlsca/tlsca.org4.example.com-cert.pem
+CAPEM=../organizations/peerOrganizations/org4.example.com/ca/ca.org4.example.com-cert.pem
+
+echo "$(json_ccp $ORG $P0PORT $CAPORT $PEERPEM $CAPEM)" > ../organizations/peerOrganizations/org4.example.com/connection-org4.json
+echo "$(yaml_ccp $ORG $P0PORT $CAPORT $PEERPEM $CAPEM)" > ../organizations/peerOrganizations/org4.example.com/connection-org4.yaml
diff --git a/fabric/network/addOrg4/ccp-template.json b/fabric/network/addOrg4/ccp-template.json
@@ -0,0 +1,49 @@
+{
+ "name": "test-network-org${ORG}",
+ "version": "1.0.0",
+ "client": {
+ "organization": "Org${ORG}",
+ "connection": {
+ "timeout": {
+ "peer": {
+ "endorser": "300"
+ }
+ }
+ }
+ },
+ "organizations": {
+ "Org${ORG}": {
+ "mspid": "Org${ORG}MSP",
+ "peers": [
+ "peer0.org${ORG}.example.com"
+ ],
+ "certificateAuthorities": [
+ "ca.org${ORG}.example.com"
+ ]
+ }
+ },
+ "peers": {
+ "peer0.org${ORG}.example.com": {
+ "url": "grpcs://localhost:${P0PORT}",
+ "tlsCACerts": {
+ "pem": "${PEERPEM}"
+ },
+ "grpcOptions": {
+ "ssl-target-name-override": "peer0.org${ORG}.example.com",
+ "hostnameOverride": "peer0.org${ORG}.example.com"
+ }
+ }
+ },
+ "certificateAuthorities": {
+ "ca.org${ORG}.example.com": {
+ "url": "https://localhost:${CAPORT}",
+ "caName": "ca-org${ORG}",
+ "tlsCACerts": {
+ "pem": "${CAPEM}"
+ },
+ "httpOptions": {
+ "verify": false
+ }
+ }
+ }
+}
diff --git a/fabric/network/addOrg4/ccp-template.yaml b/fabric/network/addOrg4/ccp-template.yaml
@@ -0,0 +1,34 @@
+---
+name: test-network-org${ORG}
+version: 1.0.0
+client:
+ organization: Org${ORG}
+ connection:
+ timeout:
+ peer:
+ endorser: '300'
+organizations:
+ Org${ORG}:
+ mspid: Org${ORG}MSP
+ peers:
+ - peer0.org${ORG}.example.com
+ certificateAuthorities:
+ - ca.org${ORG}.example.com
+peers:
+ peer0.org${ORG}.example.com:
+ url: grpcs://localhost:${P0PORT}
+ tlsCACerts:
+ pem: |
+ ${PEERPEM}
+ grpcOptions:
+ ssl-target-name-override: peer0.org${ORG}.example.com
+ hostnameOverride: peer0.org${ORG}.example.com
+certificateAuthorities:
+ ca.org${ORG}.example.com:
+ url: https://localhost:${CAPORT}
+ caName: ca-org${ORG}
+ tlsCACerts:
+ pem: |
+ ${CAPEM}
+ httpOptions:
+ verify: false
diff --git a/fabric/network/addOrg4/configtx.yaml b/fabric/network/addOrg4/configtx.yaml
@@ -0,0 +1,38 @@
+# Copyright IBM Corp. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+---
+################################################################################
+#
+# Section: Organizations
+#
+# - This section defines the different organizational identities which will
+# be referenced later in the configuration.
+#
+################################################################################
+Organizations:
+ - &Org4
+ # DefaultOrg defines the organization which is used in the sampleconfig
+ # of the fabric.git development environment
+ Name: Org4MSP
+
+ # ID to load the MSP definition as
+ ID: Org4MSP
+
+ MSPDir: ../organizations/peerOrganizations/org4.example.com/msp
+
+ Policies:
+ Readers:
+ Type: Signature
+ Rule: "OR('Org4MSP.admin', 'Org4MSP.peer', 'Org4MSP.client')"
+ Writers:
+ Type: Signature
+ Rule: "OR('Org4MSP.admin', 'Org4MSP.client')"
+ Admins:
+ Type: Signature
+ Rule: "OR('Org4MSP.admin')"
+ Endorsement:
+ Type: Signature
+ Rule: "OR('Org4MSP.peer')"
diff --git a/fabric/network/addOrg4/docker/docker-compose-ca-org4.yaml b/fabric/network/addOrg4/docker/docker-compose-ca-org4.yaml
@@ -0,0 +1,27 @@
+# Copyright IBM Corp. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+version: '2'
+
+networks:
+ test:
+ name: fabric_test
+
+services:
+ ca_org4:
+ image: hyperledger/fabric-ca:latest
+ labels:
+ service: hyperledger-fabric
+ environment:
+ - FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server
+ - FABRIC_CA_SERVER_CA_NAME=ca-org4
+ - FABRIC_CA_SERVER_TLS_ENABLED=true
+ - FABRIC_CA_SERVER_PORT=13054
+ ports:
+ - "13054:13054"
+ command: sh -c 'fabric-ca-server start -b admin:adminpw -d'
+ volumes:
+ - ../fabric-ca/org4:/etc/hyperledger/fabric-ca-server
+ container_name: ca_org4
diff --git a/fabric/network/addOrg4/docker/docker-compose-couch-org4.yaml b/fabric/network/addOrg4/docker/docker-compose-couch-org4.yaml
@@ -0,0 +1,42 @@
+# Copyright IBM Corp. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+version: '2'
+
+networks:
+ test:
+ name: fabric_test
+
+services:
+ couchdb5:
+ container_name: couchdb5
+ image: couchdb:3.1.1
+ labels:
+ service: hyperledger-fabric
+ # Populate the COUCHDB_USER and COUCHDB_PASSWORD to set an admin user and password
+ # for CouchDB. This will prevent CouchDB from operating in an "Admin Party" mode.
+ environment:
+ - COUCHDB_USER=admin
+ - COUCHDB_PASSWORD=adminpw
+ # Comment/Uncomment the port mapping if you want to hide/expose the CouchDB service,
+ # for example map it to utilize Fauxton User Interface in dev environments.
+ ports:
+ - "11984:5984"
+ networks:
+ - test
+
+ peer0.org4.example.com:
+ environment:
+ - CORE_LEDGER_STATE_STATEDATABASE=CouchDB
+ - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb5:5984
+ # The CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME and CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD
+ # provide the credentials for ledger to connect to CouchDB. The username and password must
+ # match the username and password set for the associated CouchDB.
+ - CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin
+ - CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=adminpw
+ depends_on:
+ - couchdb5
+ networks:
+ - test
diff --git a/fabric/network/addOrg4/docker/docker-compose-org4.yaml b/fabric/network/addOrg4/docker/docker-compose-org4.yaml
@@ -0,0 +1,52 @@
+# Copyright IBM Corp. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+version: '2'
+
+volumes:
+ peer0.org4.example.com:
+
+networks:
+ test:
+ name: fabric_test
+
+services:
+
+ peer0.org4.example.com:
+ container_name: peer0.org4.example.com
+ image: hyperledger/fabric-peer:latest
+ labels:
+ service: hyperledger-fabric
+ environment:
+ #Generic peer variables
+ - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
+ - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=fabric_test
+ - FABRIC_LOGGING_SPEC=INFO
+ #- FABRIC_LOGGING_SPEC=DEBUG
+ - CORE_PEER_TLS_ENABLED=true
+ - CORE_PEER_PROFILE_ENABLED=true
+ - CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/fabric/tls/server.crt
+ - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/fabric/tls/server.key
+ - CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/tls/ca.crt
+ # Peer specific variabes
+ - CORE_PEER_ID=peer0.org4.example.com
+ - CORE_PEER_ADDRESS=peer0.org4.example.com:13051
+ - CORE_PEER_LISTENADDRESS=0.0.0.0:13051
+ - CORE_PEER_CHAINCODEADDRESS=peer0.org4.example.com:13052
+ - CORE_PEER_CHAINCODELISTENADDRESS=0.0.0.0:13052
+ - CORE_PEER_GOSSIP_BOOTSTRAP=peer0.org4.example.com:13051
+ - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org4.example.com:13051
+ - CORE_PEER_LOCALMSPID=Org4MSP
+ volumes:
+ - /var/run/docker.sock:/host/var/run/docker.sock
+ - ../../organizations/peerOrganizations/org4.example.com/peers/peer0.org4.example.com/msp:/etc/hyperledger/fabric/msp
+ - ../../organizations/peerOrganizations/org4.example.com/peers/peer0.org4.example.com/tls:/etc/hyperledger/fabric/tls
+ - peer0.org4.example.com:/var/hyperledger/production
+ working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
+ command: peer node start
+ ports:
+ - 13051:13051
+ networks:
+ - test
diff --git a/fabric/network/addOrg4/fabric-ca/org4/fabric-ca-server-config.yaml b/fabric/network/addOrg4/fabric-ca/org4/fabric-ca-server-config.yaml
@@ -0,0 +1,406 @@
+#############################################################################
+# This is a configuration file for the fabric-ca-server command.
+#
+# COMMAND LINE ARGUMENTS AND ENVIRONMENT VARIABLES
+# ------------------------------------------------
+# Each configuration element can be overridden via command line
+# arguments or environment variables. The precedence for determining
+# the value of each element is as follows:
+# 1) command line argument
+# Examples:
+# a) --port 443
+# To set the listening port
+# b) --ca.keyfile ../mykey.pem
+# To set the "keyfile" element in the "ca" section below;
+# note the '.' separator character.
+# 2) environment variable
+# Examples:
+# a) FABRIC_CA_SERVER_PORT=443
+# To set the listening port
+# b) FABRIC_CA_SERVER_CA_KEYFILE="../mykey.pem"
+# To set the "keyfile" element in the "ca" section below;
+# note the '_' separator character.
+# 3) configuration file
+# 4) default value (if there is one)
+# All default values are shown beside each element below.
+#
+# FILE NAME ELEMENTS
+# ------------------
+# The value of all fields whose name ends with "file" or "files" are
+# name or names of other files.
+# For example, see "tls.certfile" and "tls.clientauth.certfiles".
+# The value of each of these fields can be a simple filename, a
+# relative path, or an absolute path. If the value is not an
+# absolute path, it is interpretted as being relative to the location
+# of this configuration file.
+#
+#############################################################################
+
+# Version of config file
+version: 1.2.0
+
+# Server's listening port (default: 7054)
+port: 13054
+
+# Enables debug logging (default: false)
+debug: false
+
+# Size limit of an acceptable CRL in bytes (default: 512000)
+crlsizelimit: 512000
+
+#############################################################################
+# TLS section for the server's listening port
+#
+# The following types are supported for client authentication: NoClientCert,
+# RequestClientCert, RequireAnyClientCert, VerifyClientCertIfGiven,
+# and RequireAndVerifyClientCert.
+#
+# Certfiles is a list of root certificate authorities that the server uses
+# when verifying client certificates.
+#############################################################################
+tls:
+ # Enable TLS (default: false)
+ enabled: true
+ # TLS for the server's listening port
+ certfile:
+ keyfile:
+ clientauth:
+ type: noclientcert
+ certfiles:
+
+#############################################################################
+# The CA section contains information related to the Certificate Authority
+# including the name of the CA, which should be unique for all members
+# of a blockchain network. It also includes the key and certificate files
+# used when issuing enrollment certificates (ECerts) and transaction
+# certificates (TCerts).
+# The chainfile (if it exists) contains the certificate chain which
+# should be trusted for this CA, where the 1st in the chain is always the
+# root CA certificate.
+#############################################################################
+ca:
+ # Name of this CA
+ name: Org4CA
+ # Key file (is only used to import a private key into BCCSP)
+ keyfile:
+ # Certificate file (default: ca-cert.pem)
+ certfile:
+ # Chain file
+ chainfile:
+
+#############################################################################
+# The gencrl REST endpoint is used to generate a CRL that contains revoked
+# certificates. This section contains configuration options that are used
+# during gencrl request processing.
+#############################################################################
+crl:
+ # Specifies expiration for the generated CRL. The number of hours
+ # specified by this property is added to the UTC time, the resulting time
+ # is used to set the 'Next Update' date of the CRL.
+ expiry: 24h
+
+#############################################################################
+# The registry section controls how the fabric-ca-server does two things:
+# 1) authenticates enrollment requests which contain a username and password
+# (also known as an enrollment ID and secret).
+# 2) once authenticated, retrieves the identity's attribute names and
+# values which the fabric-ca-server optionally puts into TCerts
+# which it issues for transacting on the Hyperledger Fabric blockchain.
+# These attributes are useful for making access control decisions in
+# chaincode.
+# There are two main configuration options:
+# 1) The fabric-ca-server is the registry.
+# This is true if "ldap.enabled" in the ldap section below is false.
+# 2) An LDAP server is the registry, in which case the fabric-ca-server
+# calls the LDAP server to perform these tasks.
+# This is true if "ldap.enabled" in the ldap section below is true,
+# which means this "registry" section is ignored.
+#############################################################################
+registry:
+ # Maximum number of times a password/secret can be reused for enrollment
+ # (default: -1, which means there is no limit)
+ maxenrollments: -1
+
+ # Contains identity information which is used when LDAP is disabled
+ identities:
+ - name: admin
+ pass: adminpw
+ type: client
+ affiliation: ""
+ attrs:
+ hf.Registrar.Roles: "*"
+ hf.Registrar.DelegateRoles: "*"
+ hf.Revoker: true
+ hf.IntermediateCA: true
+ hf.GenCRL: true
+ hf.Registrar.Attributes: "*"
+ hf.AffiliationMgr: true
+
+#############################################################################
+# Database section
+# Supported types are: "sqlite3", "postgres", and "mysql".
+# The datasource value depends on the type.
+# If the type is "sqlite3", the datasource value is a file name to use
+# as the database store. Since "sqlite3" is an embedded database, it
+# may not be used if you want to run the fabric-ca-server in a cluster.
+# To run the fabric-ca-server in a cluster, you must choose "postgres"
+# or "mysql".
+#############################################################################
+db:
+ type: sqlite3
+ datasource: fabric-ca-server.db
+ tls:
+ enabled: false
+ certfiles:
+ client:
+ certfile:
+ keyfile:
+
+#############################################################################
+# LDAP section
+# If LDAP is enabled, the fabric-ca-server calls LDAP to:
+# 1) authenticate enrollment ID and secret (i.e. username and password)
+# for enrollment requests;
+# 2) To retrieve identity attributes
+#############################################################################
+ldap:
+ # Enables or disables the LDAP client (default: false)
+ # If this is set to true, the "registry" section is ignored.
+ enabled: false
+ # The URL of the LDAP server
+ url: ldap://<adminDN>:<adminPassword>@<host>:<port>/<base>
+ # TLS configuration for the client connection to the LDAP server
+ tls:
+ certfiles:
+ client:
+ certfile:
+ keyfile:
+ # Attribute related configuration for mapping from LDAP entries to Fabric CA attributes
+ attribute:
+ # 'names' is an array of strings containing the LDAP attribute names which are
+ # requested from the LDAP server for an LDAP identity's entry
+ names: ['uid','member']
+ # The 'converters' section is used to convert an LDAP entry to the value of
+ # a fabric CA attribute.
+ # For example, the following converts an LDAP 'uid' attribute
+ # whose value begins with 'revoker' to a fabric CA attribute
+ # named "hf.Revoker" with a value of "true" (because the boolean expression
+ # evaluates to true).
+ # converters:
+ # - name: hf.Revoker
+ # value: attr("uid") =~ "revoker*"
+ converters:
+ - name:
+ value:
+ # The 'maps' section contains named maps which may be referenced by the 'map'
+ # function in the 'converters' section to map LDAP responses to arbitrary values.
+ # For example, assume a user has an LDAP attribute named 'member' which has multiple
+ # values which are each a distinguished name (i.e. a DN). For simplicity, assume the
+ # values of the 'member' attribute are 'dn1', 'dn2', and 'dn3'.
+ # Further assume the following configuration.
+ # converters:
+ # - name: hf.Registrar.Roles
+ # value: map(attr("member"),"groups")
+ # maps:
+ # groups:
+ # - name: dn1
+ # value: peer
+ # - name: dn2
+ # value: client
+ # The value of the user's 'hf.Registrar.Roles' attribute is then computed to be
+ # "peer,client,dn3". This is because the value of 'attr("member")' is
+ # "dn1,dn2,dn3", and the call to 'map' with a 2nd argument of
+ # "group" replaces "dn1" with "peer" and "dn2" with "client".
+ maps:
+ groups:
+ - name:
+ value:
+
+#############################################################################
+# Affiliations section. Fabric CA server can be bootstrapped with the
+# affiliations specified in this section. Affiliations are specified as maps.
+# For example:
+# businessunit1:
+# department1:
+# - team1
+# businessunit2:
+# - department2
+# - department3
+#
+# Affiliations are hierarchical in nature. In the above example,
+# department1 (used as businessunit1.department1) is the child of businessunit1.
+# team1 (used as businessunit1.department1.team1) is the child of department1.
+# department2 (used as businessunit2.department2) and department3 (businessunit2.department3)
+# are children of businessunit2.
+# Note: Affiliations are case sensitive except for the non-leaf affiliations
+# (like businessunit1, department1, businessunit2) that are specified in the configuration file,
+# which are always stored in lower case.
+#############################################################################
+affiliations:
+ org1:
+ - department1
+ - department2
+ org2:
+ - department1
+
+#############################################################################
+# Signing section
+#
+# The "default" subsection is used to sign enrollment certificates;
+# the default expiration ("expiry" field) is "8760h", which is 1 year in hours.
+#
+# The "ca" profile subsection is used to sign intermediate CA certificates;
+# the default expiration ("expiry" field) is "43800h" which is 5 years in hours.
+# Note that "isca" is true, meaning that it issues a CA certificate.
+# A maxpathlen of 0 means that the intermediate CA cannot issue other
+# intermediate CA certificates, though it can still issue end entity certificates.
+# (See RFC 5280, section 4.2.1.9)
+#
+# The "tls" profile subsection is used to sign TLS certificate requests;
+# the default expiration ("expiry" field) is "8760h", which is 1 year in hours.
+#############################################################################
+signing:
+ default:
+ usage:
+ - digital signature
+ expiry: 8760h
+ profiles:
+ ca:
+ usage:
+ - cert sign
+ - crl sign
+ expiry: 43800h
+ caconstraint:
+ isca: true
+ maxpathlen: 0
+ tls:
+ usage:
+ - signing
+ - key encipherment
+ - server auth
+ - client auth
+ - key agreement
+ expiry: 8760h
+
+###########################################################################
+# Certificate Signing Request (CSR) section.
+# This controls the creation of the root CA certificate.
+# The expiration for the root CA certificate is configured with the
+# "ca.expiry" field below, whose default value is "131400h" which is
+# 15 years in hours.
+# The pathlength field is used to limit CA certificate hierarchy as described
+# in section 4.2.1.9 of RFC 5280.
+# Examples:
+# 1) No pathlength value means no limit is requested.
+# 2) pathlength == 1 means a limit of 1 is requested which is the default for
+# a root CA. This means the root CA can issue intermediate CA certificates,
+# but these intermediate CAs may not in turn issue other CA certificates
+# though they can still issue end entity certificates.
+# 3) pathlength == 0 means a limit of 0 is requested;
+# this is the default for an intermediate CA, which means it can not issue
+# CA certificates though it can still issue end entity certificates.
+###########################################################################
+csr:
+ cn: ca.org4.example.com
+ names:
+ - C: US
+ ST: "North Carolina"
+ L: "Raleigh"
+ O: org4.example.com
+ OU:
+ hosts:
+ - localhost
+ - org4.example.com
+ ca:
+ expiry: 131400h
+ pathlength: 1
+
+#############################################################################
+# BCCSP (BlockChain Crypto Service Provider) section is used to select which
+# crypto library implementation to use
+#############################################################################
+bccsp:
+ default: SW
+ sw:
+ hash: SHA2
+ security: 256
+ filekeystore:
+ # The directory used for the software file-based keystore
+ keystore: msp/keystore
+
+#############################################################################
+# Multi CA section
+#
+# Each Fabric CA server contains one CA by default. This section is used
+# to configure multiple CAs in a single server.
+#
+# 1) --cacount <number-of-CAs>
+# Automatically generate <number-of-CAs> non-default CAs. The names of these
+# additional CAs are "ca1", "ca2", ... "caN", where "N" is <number-of-CAs>
+# This is particularly useful in a development environment to quickly set up
+# multiple CAs. Note that, this config option is not applicable to intermediate CA server
+# i.e., Fabric CA server that is started with intermediate.parentserver.url config
+# option (-u command line option)
+#
+# 2) --cafiles <CA-config-files>
+# For each CA config file in the list, generate a separate signing CA. Each CA
+# config file in this list MAY contain all of the same elements as are found in
+# the server config file except port, debug, and tls sections.
+#
+# Examples:
+# fabric-ca-server start -b admin:adminpw --cacount 2
+#
+# fabric-ca-server start -b admin:adminpw --cafiles ca/ca1/fabric-ca-server-config.yaml
+# --cafiles ca/ca2/fabric-ca-server-config.yaml
+#
+#############################################################################
+
+cacount:
+
+cafiles:
+
+#############################################################################
+# Intermediate CA section
+#
+# The relationship between servers and CAs is as follows:
+# 1) A single server process may contain or function as one or more CAs.
+# This is configured by the "Multi CA section" above.
+# 2) Each CA is either a root CA or an intermediate CA.
+# 3) Each intermediate CA has a parent CA which is either a root CA or another intermediate CA.
+#
+# This section pertains to configuration of #2 and #3.
+# If the "intermediate.parentserver.url" property is set,
+# then this is an intermediate CA with the specified parent
+# CA.
+#
+# parentserver section
+# url - The URL of the parent server
+# caname - Name of the CA to enroll within the server
+#
+# enrollment section used to enroll intermediate CA with parent CA
+# profile - Name of the signing profile to use in issuing the certificate
+# label - Label to use in HSM operations
+#
+# tls section for secure socket connection
+# certfiles - PEM-encoded list of trusted root certificate files
+# client:
+# certfile - PEM-encoded certificate file for when client authentication
+# is enabled on server
+# keyfile - PEM-encoded key file for when client authentication
+# is enabled on server
+#############################################################################
+intermediate:
+ parentserver:
+ url:
+ caname:
+
+ enrollment:
+ hosts:
+ profile:
+ label:
+
+ tls:
+ certfiles:
+ client:
+ certfile:
+ keyfile:
diff --git a/fabric/network/addOrg4/fabric-ca/registerEnroll.sh b/fabric/network/addOrg4/fabric-ca/registerEnroll.sh
@@ -0,0 +1,87 @@
+#!/usr/bin/env bash
+#
+# Copyright IBM Corp All Rights Reserved
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+function createOrg4 {
+ infoln "Enrolling the CA admin"
+ mkdir -p ../organizations/peerOrganizations/org4.example.com/
+
+ export FABRIC_CA_CLIENT_HOME=${PWD}/../organizations/peerOrganizations/org4.example.com/
+
+ set -x
+ fabric-ca-client enroll -u https://admin:adminpw@localhost:13054 --caname ca-org4 --tls.certfiles "${PWD}/fabric-ca/org4/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ echo 'NodeOUs:
+ Enable: true
+ ClientOUIdentifier:
+ Certificate: cacerts/localhost-13054-ca-org4.pem
+ OrganizationalUnitIdentifier: client
+ PeerOUIdentifier:
+ Certificate: cacerts/localhost-13054-ca-org4.pem
+ OrganizationalUnitIdentifier: peer
+ AdminOUIdentifier:
+ Certificate: cacerts/localhost-13054-ca-org4.pem
+ OrganizationalUnitIdentifier: admin
+ OrdererOUIdentifier:
+ Certificate: cacerts/localhost-13054-ca-org4.pem
+ OrganizationalUnitIdentifier: orderer' > "${PWD}/../organizations/peerOrganizations/org4.example.com/msp/config.yaml"
+
+ infoln "Registering peer0"
+ set -x
+ fabric-ca-client register --caname ca-org4 --id.name peer0 --id.secret peer0pw --id.type peer --tls.certfiles "${PWD}/fabric-ca/org4/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ infoln "Registering user"
+ set -x
+ fabric-ca-client register --caname ca-org4 --id.name user1 --id.secret user1pw --id.type client --tls.certfiles "${PWD}/fabric-ca/org4/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ infoln "Registering the org admin"
+ set -x
+ fabric-ca-client register --caname ca-org4 --id.name org4admin --id.secret org4adminpw --id.type admin --tls.certfiles "${PWD}/fabric-ca/org4/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ infoln "Generating the peer0 msp"
+ set -x
+ fabric-ca-client enroll -u https://peer0:peer0pw@localhost:13054 --caname ca-org4 -M "${PWD}/../organizations/peerOrganizations/org4.example.com/peers/peer0.org4.example.com/msp" --csr.hosts peer0.org4.example.com --tls.certfiles "${PWD}/fabric-ca/org4/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ cp "${PWD}/../organizations/peerOrganizations/org4.example.com/msp/config.yaml" "${PWD}/../organizations/peerOrganizations/org4.example.com/peers/peer0.org4.example.com/msp/config.yaml"
+
+ infoln "Generating the peer0-tls certificates"
+ set -x
+ fabric-ca-client enroll -u https://peer0:peer0pw@localhost:13054 --caname ca-org4 -M "${PWD}/../organizations/peerOrganizations/org4.example.com/peers/peer0.org4.example.com/tls" --enrollment.profile tls --csr.hosts peer0.org4.example.com --csr.hosts localhost --tls.certfiles "${PWD}/fabric-ca/org4/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+
+ cp "${PWD}/../organizations/peerOrganizations/org4.example.com/peers/peer0.org4.example.com/tls/tlscacerts/"* "${PWD}/../organizations/peerOrganizations/org4.example.com/peers/peer0.org4.example.com/tls/ca.crt"
+ cp "${PWD}/../organizations/peerOrganizations/org4.example.com/peers/peer0.org4.example.com/tls/signcerts/"* "${PWD}/../organizations/peerOrganizations/org4.example.com/peers/peer0.org4.example.com/tls/server.crt"
+ cp "${PWD}/../organizations/peerOrganizations/org4.example.com/peers/peer0.org4.example.com/tls/keystore/"* "${PWD}/../organizations/peerOrganizations/org4.example.com/peers/peer0.org4.example.com/tls/server.key"
+
+ mkdir "${PWD}/../organizations/peerOrganizations/org4.example.com/msp/tlscacerts"
+ cp "${PWD}/../organizations/peerOrganizations/org4.example.com/peers/peer0.org4.example.com/tls/tlscacerts/"* "${PWD}/../organizations/peerOrganizations/org4.example.com/msp/tlscacerts/ca.crt"
+
+ mkdir "${PWD}/../organizations/peerOrganizations/org4.example.com/tlsca"
+ cp "${PWD}/../organizations/peerOrganizations/org4.example.com/peers/peer0.org4.example.com/tls/tlscacerts/"* "${PWD}/../organizations/peerOrganizations/org4.example.com/tlsca/tlsca.org4.example.com-cert.pem"
+
+ mkdir "${PWD}/../organizations/peerOrganizations/org4.example.com/ca"
+ cp "${PWD}/../organizations/peerOrganizations/org4.example.com/peers/peer0.org4.example.com/msp/cacerts/"* "${PWD}/../organizations/peerOrganizations/org4.example.com/ca/ca.org4.example.com-cert.pem"
+
+ infoln "Generating the user msp"
+ set -x
+ fabric-ca-client enroll -u https://user1:user1pw@localhost:13054 --caname ca-org4 -M "${PWD}/../organizations/peerOrganizations/org4.example.com/users/User1@org4.example.com/msp" --tls.certfiles "${PWD}/fabric-ca/org4/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ cp "${PWD}/../organizations/peerOrganizations/org4.example.com/msp/config.yaml" "${PWD}/../organizations/peerOrganizations/org4.example.com/users/User1@org4.example.com/msp/config.yaml"
+
+ infoln "Generating the org admin msp"
+ set -x
+ fabric-ca-client enroll -u https://org4admin:org4adminpw@localhost:13054 --caname ca-org4 -M "${PWD}/../organizations/peerOrganizations/org4.example.com/users/Admin@org4.example.com/msp" --tls.certfiles "${PWD}/fabric-ca/org4/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ cp "${PWD}/../organizations/peerOrganizations/org4.example.com/msp/config.yaml" "${PWD}/../organizations/peerOrganizations/org4.example.com/users/Admin@org4.example.com/msp/config.yaml"
+}
diff --git a/fabric/network/addOrg4/org4-crypto.yaml b/fabric/network/addOrg4/org4-crypto.yaml
@@ -0,0 +1,21 @@
+# Copyright IBM Corp. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+# ---------------------------------------------------------------------------
+# "PeerOrgs" - Definition of organizations managing peer nodes
+# ---------------------------------------------------------------------------
+PeerOrgs:
+ # ---------------------------------------------------------------------------
+ # Org4
+ # ---------------------------------------------------------------------------
+ - Name: Org4
+ Domain: org4.example.com
+ EnableNodeOUs: true
+ Template:
+ Count: 1
+ SANS:
+ - localhost
+ Users:
+ Count: 1
diff --git a/fabric/network/configtx/configtx.yaml b/fabric/network/configtx/configtx.yaml
@@ -0,0 +1,318 @@
+# Copyright IBM Corp. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+---
+################################################################################
+#
+# Section: Organizations
+#
+# - This section defines the different organizational identities which will
+# be referenced later in the configuration.
+#
+################################################################################
+Organizations:
+
+ # SampleOrg defines an MSP using the sampleconfig. It should never be used
+ # in production but may be used as a template for other definitions
+ - &OrdererOrg
+ # DefaultOrg defines the organization which is used in the sampleconfig
+ # of the fabric.git development environment
+ Name: OrdererOrg
+
+ # ID to load the MSP definition as
+ ID: OrdererMSP
+
+ # MSPDir is the filesystem path which contains the MSP configuration
+ MSPDir: ../organizations/ordererOrganizations/example.com/msp
+
+ # Policies defines the set of policies at this level of the config tree
+ # For organization policies, their canonical path is usually
+ # /Channel/<Application|Orderer>/<OrgName>/<PolicyName>
+ Policies:
+ Readers:
+ Type: Signature
+ Rule: "OR('OrdererMSP.member')"
+ Writers:
+ Type: Signature
+ Rule: "OR('OrdererMSP.member')"
+ Admins:
+ Type: Signature
+ Rule: "OR('OrdererMSP.admin')"
+
+ OrdererEndpoints:
+ - orderer.example.com:7050
+
+ - &Org1
+ # DefaultOrg defines the organization which is used in the sampleconfig
+ # of the fabric.git development environment
+ Name: Org1MSP
+
+ # ID to load the MSP definition as
+ ID: Org1MSP
+
+ MSPDir: ../organizations/peerOrganizations/org1.example.com/msp
+
+ # Policies defines the set of policies at this level of the config tree
+ # For organization policies, their canonical path is usually
+ # /Channel/<Application|Orderer>/<OrgName>/<PolicyName>
+ Policies:
+ Readers:
+ Type: Signature
+ Rule: "OR('Org1MSP.admin', 'Org1MSP.peer', 'Org1MSP.client')"
+ Writers:
+ Type: Signature
+ Rule: "OR('Org1MSP.admin', 'Org1MSP.client')"
+ Admins:
+ Type: Signature
+ Rule: "OR('Org1MSP.admin')"
+ Endorsement:
+ Type: Signature
+ Rule: "OR('Org1MSP.peer')"
+
+ - &Org2
+ # DefaultOrg defines the organization which is used in the sampleconfig
+ # of the fabric.git development environment
+ Name: Org2MSP
+
+ # ID to load the MSP definition as
+ ID: Org2MSP
+
+ MSPDir: ../organizations/peerOrganizations/org2.example.com/msp
+
+ # Policies defines the set of policies at this level of the config tree
+ # For organization policies, their canonical path is usually
+ # /Channel/<Application|Orderer>/<OrgName>/<PolicyName>
+ Policies:
+ Readers:
+ Type: Signature
+ Rule: "OR('Org2MSP.admin', 'Org2MSP.peer', 'Org2MSP.client')"
+ Writers:
+ Type: Signature
+ Rule: "OR('Org2MSP.admin', 'Org2MSP.client')"
+ Admins:
+ Type: Signature
+ Rule: "OR('Org2MSP.admin')"
+ Endorsement:
+ Type: Signature
+ Rule: "OR('Org2MSP.peer')"
+
+################################################################################
+#
+# SECTION: Capabilities
+#
+# - This section defines the capabilities of fabric network. This is a new
+# concept as of v1.1.0 and should not be utilized in mixed networks with
+# v1.0.x peers and orderers. Capabilities define features which must be
+# present in a fabric binary for that binary to safely participate in the
+# fabric network. For instance, if a new MSP type is added, newer binaries
+# might recognize and validate the signatures from this type, while older
+# binaries without this support would be unable to validate those
+# transactions. This could lead to different versions of the fabric binaries
+# having different world states. Instead, defining a capability for a channel
+# informs those binaries without this capability that they must cease
+# processing transactions until they have been upgraded. For v1.0.x if any
+# capabilities are defined (including a map with all capabilities turned off)
+# then the v1.0.x peer will deliberately crash.
+#
+################################################################################
+Capabilities:
+ # Channel capabilities apply to both the orderers and the peers and must be
+ # supported by both.
+ # Set the value of the capability to true to require it.
+ Channel: &ChannelCapabilities
+ # V2_0 capability ensures that orderers and peers behave according
+ # to v2.0 channel capabilities. Orderers and peers from
+ # prior releases would behave in an incompatible way, and are therefore
+ # not able to participate in channels at v2.0 capability.
+ # Prior to enabling V2.0 channel capabilities, ensure that all
+ # orderers and peers on a channel are at v2.0.0 or later.
+ V2_0: true
+
+ # Orderer capabilities apply only to the orderers, and may be safely
+ # used with prior release peers.
+ # Set the value of the capability to true to require it.
+ Orderer: &OrdererCapabilities
+ # V2_0 orderer capability ensures that orderers behave according
+ # to v2.0 orderer capabilities. Orderers from
+ # prior releases would behave in an incompatible way, and are therefore
+ # not able to participate in channels at v2.0 orderer capability.
+ # Prior to enabling V2.0 orderer capabilities, ensure that all
+ # orderers on channel are at v2.0.0 or later.
+ V2_0: true
+
+ # Application capabilities apply only to the peer network, and may be safely
+ # used with prior release orderers.
+ # Set the value of the capability to true to require it.
+ Application: &ApplicationCapabilities
+ # V2_0 application capability ensures that peers behave according
+ # to v2.0 application capabilities. Peers from
+ # prior releases would behave in an incompatible way, and are therefore
+ # not able to participate in channels at v2.0 application capability.
+ # Prior to enabling V2.0 application capabilities, ensure that all
+ # peers on channel are at v2.0.0 or later.
+ V2_0: true
+
+################################################################################
+#
+# SECTION: Application
+#
+# - This section defines the values to encode into a config transaction or
+# genesis block for application related parameters
+#
+################################################################################
+Application: &ApplicationDefaults
+
+ # Organizations is the list of orgs which are defined as participants on
+ # the application side of the network
+ Organizations:
+
+ # Policies defines the set of policies at this level of the config tree
+ # For Application policies, their canonical path is
+ # /Channel/Application/<PolicyName>
+ Policies:
+ Readers:
+ Type: ImplicitMeta
+ Rule: "ANY Readers"
+ Writers:
+ Type: ImplicitMeta
+ Rule: "ANY Writers"
+ Admins:
+ Type: ImplicitMeta
+ Rule: "MAJORITY Admins"
+ LifecycleEndorsement:
+ Type: ImplicitMeta
+ Rule: "MAJORITY Endorsement"
+ Endorsement:
+ Type: ImplicitMeta
+ Rule: "MAJORITY Endorsement"
+
+ Capabilities:
+ <<: *ApplicationCapabilities
+################################################################################
+#
+# SECTION: Orderer
+#
+# - This section defines the values to encode into a config transaction or
+# genesis block for orderer related parameters
+#
+################################################################################
+Orderer: &OrdererDefaults
+
+ # Orderer Type: The orderer implementation to start
+ OrdererType: etcdraft
+
+ # Addresses used to be the list of orderer addresses that clients and peers
+ # could connect to. However, this does not allow clients to associate orderer
+ # addresses and orderer organizations which can be useful for things such
+ # as TLS validation. The preferred way to specify orderer addresses is now
+ # to include the OrdererEndpoints item in your org definition
+ Addresses:
+ - orderer.example.com:7050
+
+ EtcdRaft:
+ Consenters:
+ - Host: orderer.example.com
+ Port: 7050
+ ClientTLSCert: ../organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.crt
+ ServerTLSCert: ../organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.crt
+
+ # Batch Timeout: The amount of time to wait before creating a batch
+ BatchTimeout: 2s
+
+ # Batch Size: Controls the number of messages batched into a block
+ BatchSize:
+
+ # Max Message Count: The maximum number of messages to permit in a batch
+ MaxMessageCount: 10
+
+ # Absolute Max Bytes: The absolute maximum number of bytes allowed for
+ # the serialized messages in a batch.
+ AbsoluteMaxBytes: 99 MB
+
+ # Preferred Max Bytes: The preferred maximum number of bytes allowed for
+ # the serialized messages in a batch. A message larger than the preferred
+ # max bytes will result in a batch larger than preferred max bytes.
+ PreferredMaxBytes: 512 KB
+
+ # Organizations is the list of orgs which are defined as participants on
+ # the orderer side of the network
+ Organizations:
+
+ # Policies defines the set of policies at this level of the config tree
+ # For Orderer policies, their canonical path is
+ # /Channel/Orderer/<PolicyName>
+ Policies:
+ Readers:
+ Type: ImplicitMeta
+ Rule: "ANY Readers"
+ Writers:
+ Type: ImplicitMeta
+ Rule: "ANY Writers"
+ Admins:
+ Type: ImplicitMeta
+ Rule: "MAJORITY Admins"
+ # BlockValidation specifies what signatures must be included in the block
+ # from the orderer for the peer to validate it.
+ BlockValidation:
+ Type: ImplicitMeta
+ Rule: "ANY Writers"
+
+################################################################################
+#
+# CHANNEL
+#
+# This section defines the values to encode into a config transaction or
+# genesis block for channel related parameters.
+#
+################################################################################
+Channel: &ChannelDefaults
+ # Policies defines the set of policies at this level of the config tree
+ # For Channel policies, their canonical path is
+ # /Channel/<PolicyName>
+ Policies:
+ # Who may invoke the 'Deliver' API
+ Readers:
+ Type: ImplicitMeta
+ Rule: "ANY Readers"
+ # Who may invoke the 'Broadcast' API
+ Writers:
+ Type: ImplicitMeta
+ Rule: "ANY Writers"
+ # By default, who may modify elements at this config level
+ Admins:
+ Type: ImplicitMeta
+ Rule: "MAJORITY Admins"
+
+ # Capabilities describes the channel level capabilities, see the
+ # dedicated Capabilities section elsewhere in this file for a full
+ # description
+ Capabilities:
+ <<: *ChannelCapabilities
+
+################################################################################
+#
+# Profile
+#
+# - Different configuration profiles may be encoded here to be specified
+# as parameters to the configtxgen tool
+#
+################################################################################
+Profiles:
+
+ TwoOrgsApplicationGenesis:
+ <<: *ChannelDefaults
+ Orderer:
+ <<: *OrdererDefaults
+ Organizations:
+ - *OrdererOrg
+ Capabilities:
+ <<: *OrdererCapabilities
+ Application:
+ <<: *ApplicationDefaults
+ Organizations:
+ - *Org1
+ - *Org2
+ Capabilities:
+ <<: *ApplicationCapabilities
+\ No newline at end of file
diff --git a/fabric/network/docker/docker-compose-ca.yaml b/fabric/network/docker/docker-compose-ca.yaml
@@ -0,0 +1,66 @@
+# Copyright IBM Corp. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+version: '2.4'
+
+networks:
+ test:
+ name: fabric_test
+
+services:
+
+ ca_org1:
+ image: hyperledger/fabric-ca:latest
+ labels:
+ service: hyperledger-fabric
+ environment:
+ - FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server
+ - FABRIC_CA_SERVER_CA_NAME=ca-org1
+ - FABRIC_CA_SERVER_TLS_ENABLED=true
+ - FABRIC_CA_SERVER_PORT=7054
+ ports:
+ - "7054:7054"
+ command: sh -c 'fabric-ca-server start -b admin:adminpw -d'
+ volumes:
+ - ../organizations/fabric-ca/org1:/etc/hyperledger/fabric-ca-server
+ container_name: ca_org1
+ networks:
+ - test
+
+ ca_org2:
+ image: hyperledger/fabric-ca:latest
+ labels:
+ service: hyperledger-fabric
+ environment:
+ - FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server
+ - FABRIC_CA_SERVER_CA_NAME=ca-org2
+ - FABRIC_CA_SERVER_TLS_ENABLED=true
+ - FABRIC_CA_SERVER_PORT=8054
+ ports:
+ - "8054:8054"
+ command: sh -c 'fabric-ca-server start -b admin:adminpw -d'
+ volumes:
+ - ../organizations/fabric-ca/org2:/etc/hyperledger/fabric-ca-server
+ container_name: ca_org2
+ networks:
+ - test
+
+ ca_orderer:
+ image: hyperledger/fabric-ca:latest
+ labels:
+ service: hyperledger-fabric
+ environment:
+ - FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server
+ - FABRIC_CA_SERVER_CA_NAME=ca-orderer
+ - FABRIC_CA_SERVER_TLS_ENABLED=true
+ - FABRIC_CA_SERVER_PORT=9054
+ ports:
+ - "9054:9054"
+ command: sh -c 'fabric-ca-server start -b admin:adminpw -d'
+ volumes:
+ - ../organizations/fabric-ca/ordererOrg:/etc/hyperledger/fabric-ca-server
+ container_name: ca_orderer
+ networks:
+ - test
diff --git a/fabric/network/docker/docker-compose-couch.yaml b/fabric/network/docker/docker-compose-couch.yaml
@@ -0,0 +1,69 @@
+# Copyright IBM Corp. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+version: '2.4'
+
+networks:
+ test:
+ name: fabric_test
+
+services:
+ couchdb0:
+ container_name: couchdb0
+ image: couchdb:3.1.1
+ labels:
+ service: hyperledger-fabric
+ # Populate the COUCHDB_USER and COUCHDB_PASSWORD to set an admin user and password
+ # for CouchDB. This will prevent CouchDB from operating in an "Admin Party" mode.
+ environment:
+ - COUCHDB_USER=admin
+ - COUCHDB_PASSWORD=adminpw
+ # Comment/Uncomment the port mapping if you want to hide/expose the CouchDB service,
+ # for example map it to utilize Fauxton User Interface in dev environments.
+ ports:
+ - "5984:5984"
+ networks:
+ - test
+
+ peer0.org1.example.com:
+ environment:
+ - CORE_LEDGER_STATE_STATEDATABASE=CouchDB
+ - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb0:5984
+ # The CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME and CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD
+ # provide the credentials for ledger to connect to CouchDB. The username and password must
+ # match the username and password set for the associated CouchDB.
+ - CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin
+ - CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=adminpw
+ depends_on:
+ - couchdb0
+
+ couchdb1:
+ container_name: couchdb1
+ image: couchdb:3.1.1
+ labels:
+ service: hyperledger-fabric
+ # Populate the COUCHDB_USER and COUCHDB_PASSWORD to set an admin user and password
+ # for CouchDB. This will prevent CouchDB from operating in an "Admin Party" mode.
+ environment:
+ - COUCHDB_USER=admin
+ - COUCHDB_PASSWORD=adminpw
+ # Comment/Uncomment the port mapping if you want to hide/expose the CouchDB service,
+ # for example map it to utilize Fauxton User Interface in dev environments.
+ ports:
+ - "7984:5984"
+ networks:
+ - test
+
+ peer0.org2.example.com:
+ environment:
+ - CORE_LEDGER_STATE_STATEDATABASE=CouchDB
+ - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb1:5984
+ # The CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME and CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD
+ # provide the credentials for ledger to connect to CouchDB. The username and password must
+ # match the username and password set for the associated CouchDB.
+ - CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin
+ - CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=adminpw
+ depends_on:
+ - couchdb1
diff --git a/fabric/network/docker/docker-compose-test-net.yaml b/fabric/network/docker/docker-compose-test-net.yaml
@@ -0,0 +1,156 @@
+# Copyright IBM Corp. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+version: "2.4"
+
+volumes:
+ orderer.example.com:
+ peer0.org1.example.com:
+ peer0.org2.example.com:
+
+networks:
+ test:
+ name: fabric_test
+
+services:
+ orderer.example.com:
+ container_name: orderer.example.com
+ image: hyperledger/fabric-orderer:latest
+ labels:
+ service: hyperledger-fabric
+ environment:
+ - FABRIC_LOGGING_SPEC=INFO
+ - ORDERER_GENERAL_LISTENADDRESS=0.0.0.0
+ - ORDERER_GENERAL_LISTENPORT=7050
+ - ORDERER_GENERAL_LOCALMSPID=OrdererMSP
+ - ORDERER_GENERAL_LOCALMSPDIR=/var/hyperledger/orderer/msp
+ # enabled TLS
+ - ORDERER_GENERAL_TLS_ENABLED=true
+ - ORDERER_GENERAL_TLS_PRIVATEKEY=/var/hyperledger/orderer/tls/server.key
+ - ORDERER_GENERAL_TLS_CERTIFICATE=/var/hyperledger/orderer/tls/server.crt
+ - ORDERER_GENERAL_TLS_ROOTCAS=[/var/hyperledger/orderer/tls/ca.crt]
+ - ORDERER_KAFKA_TOPIC_REPLICATIONFACTOR=1
+ - ORDERER_KAFKA_VERBOSE=true
+ - ORDERER_GENERAL_CLUSTER_CLIENTCERTIFICATE=/var/hyperledger/orderer/tls/server.crt
+ - ORDERER_GENERAL_CLUSTER_CLIENTPRIVATEKEY=/var/hyperledger/orderer/tls/server.key
+ - ORDERER_GENERAL_CLUSTER_ROOTCAS=[/var/hyperledger/orderer/tls/ca.crt]
+ - ORDERER_GENERAL_BOOTSTRAPMETHOD=none
+ - ORDERER_CHANNELPARTICIPATION_ENABLED=true
+ - ORDERER_ADMIN_TLS_ENABLED=true
+ - ORDERER_ADMIN_TLS_CERTIFICATE=/var/hyperledger/orderer/tls/server.crt
+ - ORDERER_ADMIN_TLS_PRIVATEKEY=/var/hyperledger/orderer/tls/server.key
+ - ORDERER_ADMIN_TLS_ROOTCAS=[/var/hyperledger/orderer/tls/ca.crt]
+ - ORDERER_ADMIN_TLS_CLIENTROOTCAS=[/var/hyperledger/orderer/tls/ca.crt]
+ - ORDERER_ADMIN_LISTENADDRESS=0.0.0.0:7053
+ working_dir: /opt/gopath/src/github.com/hyperledger/fabric
+ command: orderer
+ volumes:
+ - ../system-genesis-block/genesis.block:/var/hyperledger/orderer/orderer.genesis.block
+ - ../organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp:/var/hyperledger/orderer/msp
+ - ../organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/:/var/hyperledger/orderer/tls
+ - orderer.example.com:/var/hyperledger/production/orderer
+ ports:
+ - 7050:7050
+ - 7053:7053
+ networks:
+ - test
+
+ peer0.org1.example.com:
+ container_name: peer0.org1.example.com
+ image: hyperledger/fabric-peer:latest
+ labels:
+ service: hyperledger-fabric
+ environment:
+ #Generic peer variables
+ - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
+ - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=fabric_test
+ - FABRIC_LOGGING_SPEC=INFO
+ #- FABRIC_LOGGING_SPEC=DEBUG
+ - CORE_PEER_TLS_ENABLED=true
+ - CORE_PEER_PROFILE_ENABLED=true
+ - CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/fabric/tls/server.crt
+ - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/fabric/tls/server.key
+ - CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/tls/ca.crt
+ # Peer specific variabes
+ - CORE_PEER_ID=peer0.org1.example.com
+ - CORE_PEER_ADDRESS=peer0.org1.example.com:7051
+ - CORE_PEER_LISTENADDRESS=0.0.0.0:7051
+ - CORE_PEER_CHAINCODEADDRESS=peer0.org1.example.com:7052
+ - CORE_PEER_CHAINCODELISTENADDRESS=0.0.0.0:7052
+ - CORE_PEER_GOSSIP_BOOTSTRAP=peer0.org1.example.com:7051
+ - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org1.example.com:7051
+ - CORE_PEER_LOCALMSPID=Org1MSP
+ volumes:
+ - /var/run/docker.sock:/host/var/run/docker.sock
+ - ../organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp:/etc/hyperledger/fabric/msp
+ - ../organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls:/etc/hyperledger/fabric/tls
+ - peer0.org1.example.com:/var/hyperledger/production
+ working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
+ command: peer node start
+ ports:
+ - 7051:7051
+ networks:
+ - test
+
+ peer0.org2.example.com:
+ container_name: peer0.org2.example.com
+ image: hyperledger/fabric-peer:latest
+ labels:
+ service: hyperledger-fabric
+ environment:
+ #Generic peer variables
+ - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
+ - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=fabric_test
+ - FABRIC_LOGGING_SPEC=INFO
+ #- FABRIC_LOGGING_SPEC=DEBUG
+ - CORE_PEER_TLS_ENABLED=true
+ - CORE_PEER_PROFILE_ENABLED=true
+ - CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/fabric/tls/server.crt
+ - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/fabric/tls/server.key
+ - CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/tls/ca.crt
+ # Peer specific variabes
+ - CORE_PEER_ID=peer0.org2.example.com
+ - CORE_PEER_ADDRESS=peer0.org2.example.com:9051
+ - CORE_PEER_LISTENADDRESS=0.0.0.0:9051
+ - CORE_PEER_CHAINCODEADDRESS=peer0.org2.example.com:9052
+ - CORE_PEER_CHAINCODELISTENADDRESS=0.0.0.0:9052
+ - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org2.example.com:9051
+ - CORE_PEER_GOSSIP_BOOTSTRAP=peer0.org2.example.com:9051
+ - CORE_PEER_LOCALMSPID=Org2MSP
+ volumes:
+ - /var/run/docker.sock:/host/var/run/docker.sock
+ - ../organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp:/etc/hyperledger/fabric/msp
+ - ../organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls:/etc/hyperledger/fabric/tls
+ - peer0.org2.example.com:/var/hyperledger/production
+ working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
+ command: peer node start
+ ports:
+ - 9051:9051
+ networks:
+ - test
+
+ cli:
+ container_name: cli
+ image: hyperledger/fabric-tools:latest
+ labels:
+ service: hyperledger-fabric
+ tty: true
+ stdin_open: true
+ environment:
+ - GOPATH=/opt/gopath
+ - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
+ - FABRIC_LOGGING_SPEC=INFO
+ #- FABRIC_LOGGING_SPEC=DEBUG
+ working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
+ command: /usr/bin/env bash
+ volumes:
+ - /var/run/:/host/var/run/
+ - ../organizations:/opt/gopath/src/github.com/hyperledger/fabric/peer/organizations
+ - ../scripts:/opt/gopath/src/github.com/hyperledger/fabric/peer/scripts/
+ depends_on:
+ - peer0.org1.example.com
+ - peer0.org2.example.com
+ networks:
+ - test
diff --git a/fabric/network/network.sh b/fabric/network/network.sh
@@ -0,0 +1,478 @@
+#!/usr/bin/env bash
+#
+# Copyright IBM Corp All Rights Reserved
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+# This script brings up a Hyperledger Fabric network for testing smart contracts
+# and applications. The test network consists of two organizations with one
+# peer each, and a single node Raft ordering service. Users can also use this
+# script to create a channel deploy a chaincode on the channel
+#
+# prepending $PWD/../bin to PATH to ensure we are picking up the correct binaries
+# this may be commented out to resolve installed version of tools if desired
+export PATH=${PWD}/../bin:$PATH
+export FABRIC_CFG_PATH=${PWD}/configtx
+export VERBOSE=false
+
+. scripts/utils.sh
+
+# Obtain CONTAINER_IDS and remove them
+# This function is called when you bring a network down
+function clearContainers() {
+ infoln "Removing remaining containers"
+ docker rm -f $(docker ps -aq --filter label=service=hyperledger-fabric) 2>/dev/null || true
+ docker rm -f $(docker ps -aq --filter name='dev-peer*') 2>/dev/null || true
+}
+
+# Delete any images that were generated as a part of this setup
+# specifically the following images are often left behind:
+# This function is called when you bring the network down
+function removeUnwantedImages() {
+ infoln "Removing generated chaincode docker images"
+ docker image rm -f $(docker images -aq --filter reference='dev-peer*') 2>/dev/null || true
+}
+
+# Versions of fabric known not to work with the test network
+NONWORKING_VERSIONS="^1\.0\. ^1\.1\. ^1\.2\. ^1\.3\. ^1\.4\."
+
+# Do some basic sanity checking to make sure that the appropriate versions of fabric
+# binaries/images are available. In the future, additional checking for the presence
+# of go or other items could be added.
+function checkPrereqs() {
+ ## Check if your have cloned the peer binaries and configuration files.
+ peer version > /dev/null 2>&1
+
+ if [[ $? -ne 0 || ! -d "../config" ]]; then
+ errorln "Peer binary and configuration files not found.."
+ errorln
+ errorln "Follow the instructions in the Fabric docs to install the Fabric Binaries:"
+ errorln "https://hyperledger-fabric.readthedocs.io/en/latest/install.html"
+ exit 1
+ fi
+ # use the fabric tools container to see if the samples and binaries match your
+ # docker images
+ LOCAL_VERSION=$(peer version | sed -ne 's/ Version: //p')
+ DOCKER_IMAGE_VERSION=$(docker run --rm hyperledger/fabric-tools:latest peer version | sed -ne 's/ Version: //p' | head -1)
+
+ infoln "LOCAL_VERSION=$LOCAL_VERSION"
+ infoln "DOCKER_IMAGE_VERSION=$DOCKER_IMAGE_VERSION"
+
+ if [ "$LOCAL_VERSION" != "$DOCKER_IMAGE_VERSION" ]; then
+ warnln "Local fabric binaries and docker images are out of sync. This may cause problems."
+ fi
+
+ for UNSUPPORTED_VERSION in $NONWORKING_VERSIONS; do
+ infoln "$LOCAL_VERSION" | grep -q $UNSUPPORTED_VERSION
+ if [ $? -eq 0 ]; then
+ fatalln "Local Fabric binary version of $LOCAL_VERSION does not match the versions supported by the test network."
+ fi
+
+ infoln "$DOCKER_IMAGE_VERSION" | grep -q $UNSUPPORTED_VERSION
+ if [ $? -eq 0 ]; then
+ fatalln "Fabric Docker image version of $DOCKER_IMAGE_VERSION does not match the versions supported by the test network."
+ fi
+ done
+
+ ## Check for fabric-ca
+ if [ "$CRYPTO" == "Certificate Authorities" ]; then
+
+ fabric-ca-client version > /dev/null 2>&1
+ if [[ $? -ne 0 ]]; then
+ errorln "fabric-ca-client binary not found.."
+ errorln
+ errorln "Follow the instructions in the Fabric docs to install the Fabric Binaries:"
+ errorln "https://hyperledger-fabric.readthedocs.io/en/latest/install.html"
+ exit 1
+ fi
+ CA_LOCAL_VERSION=$(fabric-ca-client version | sed -ne 's/ Version: //p')
+ CA_DOCKER_IMAGE_VERSION=$(docker run --rm hyperledger/fabric-ca:latest fabric-ca-client version | sed -ne 's/ Version: //p' | head -1)
+ infoln "CA_LOCAL_VERSION=$CA_LOCAL_VERSION"
+ infoln "CA_DOCKER_IMAGE_VERSION=$CA_DOCKER_IMAGE_VERSION"
+
+ if [ "$CA_LOCAL_VERSION" != "$CA_DOCKER_IMAGE_VERSION" ]; then
+ warnln "Local fabric-ca binaries and docker images are out of sync. This may cause problems."
+ fi
+ fi
+}
+
+# Before you can bring up a network, each organization needs to generate the crypto
+# material that will define that organization on the network. Because Hyperledger
+# Fabric is a permissioned blockchain, each node and user on the network needs to
+# use certificates and keys to sign and verify its actions. In addition, each user
+# needs to belong to an organization that is recognized as a member of the network.
+# You can use the Cryptogen tool or Fabric CAs to generate the organization crypto
+# material.
+
+# By default, the sample network uses cryptogen. Cryptogen is a tool that is
+# meant for development and testing that can quickly create the certificates and keys
+# that can be consumed by a Fabric network. The cryptogen tool consumes a series
+# of configuration files for each organization in the "organizations/cryptogen"
+# directory. Cryptogen uses the files to generate the crypto material for each
+# org in the "organizations" directory.
+
+# You can also use Fabric CAs to generate the crypto material. CAs sign the certificates
+# and keys that they generate to create a valid root of trust for each organization.
+# The script uses Docker Compose to bring up three CAs, one for each peer organization
+# and the ordering organization. The configuration file for creating the Fabric CA
+# servers are in the "organizations/fabric-ca" directory. Within the same directory,
+# the "registerEnroll.sh" script uses the Fabric CA client to create the identities,
+# certificates, and MSP folders that are needed to create the test network in the
+# "organizations/ordererOrganizations" directory.
+
+# Create Organization crypto material using cryptogen or CAs
+function createOrgs() {
+ if [ -d "organizations/peerOrganizations" ]; then
+ rm -Rf organizations/peerOrganizations && rm -Rf organizations/ordererOrganizations
+ fi
+
+ # Create crypto material using cryptogen
+ if [ "$CRYPTO" == "cryptogen" ]; then
+ which cryptogen
+ if [ "$?" -ne 0 ]; then
+ fatalln "cryptogen tool not found. exiting"
+ fi
+ infoln "Generating certificates using cryptogen tool"
+
+ infoln "Creating Org1 Identities"
+
+ set -x
+ cryptogen generate --config=./organizations/cryptogen/crypto-config-org1.yaml --output="organizations"
+ res=$?
+ { set +x; } 2>/dev/null
+ if [ $res -ne 0 ]; then
+ fatalln "Failed to generate certificates..."
+ fi
+
+ infoln "Creating Org2 Identities"
+
+ set -x
+ cryptogen generate --config=./organizations/cryptogen/crypto-config-org2.yaml --output="organizations"
+ res=$?
+ { set +x; } 2>/dev/null
+ if [ $res -ne 0 ]; then
+ fatalln "Failed to generate certificates..."
+ fi
+
+ infoln "Creating Orderer Org Identities"
+
+ set -x
+ cryptogen generate --config=./organizations/cryptogen/crypto-config-orderer.yaml --output="organizations"
+ res=$?
+ { set +x; } 2>/dev/null
+ if [ $res -ne 0 ]; then
+ fatalln "Failed to generate certificates..."
+ fi
+
+ fi
+
+ # Create crypto material using Fabric CA
+ if [ "$CRYPTO" == "Certificate Authorities" ]; then
+ infoln "Generating certificates using Fabric CA"
+ docker-compose -f $COMPOSE_FILE_CA up -d 2>&1
+
+ . organizations/fabric-ca/registerEnroll.sh
+
+ while :
+ do
+ if [ ! -f "organizations/fabric-ca/org1/tls-cert.pem" ]; then
+ sleep 1
+ else
+ break
+ fi
+ done
+
+ infoln "Creating Org1 Identities"
+
+ createOrg1
+
+ infoln "Creating Org2 Identities"
+
+ createOrg2
+
+ infoln "Creating Orderer Org Identities"
+
+ createOrderer
+
+ fi
+
+ infoln "Generating CCP files for Org1 and Org2"
+ ./organizations/ccp-generate.sh
+}
+
+# Once you create the organization crypto material, you need to create the
+# genesis block of the application channel.
+
+# The configtxgen tool is used to create the genesis block. Configtxgen consumes a
+# "configtx.yaml" file that contains the definitions for the sample network. The
+# genesis block is defined using the "TwoOrgsApplicationGenesis" profile at the bottom
+# of the file. This profile defines an application channel consisting of our two Peer Orgs.
+# The peer and ordering organizations are defined in the "Profiles" section at the
+# top of the file. As part of each organization profile, the file points to the
+# location of the MSP directory for each member. This MSP is used to create the channel
+# MSP that defines the root of trust for each organization. In essence, the channel
+# MSP allows the nodes and users to be recognized as network members.
+#
+# If you receive the following warning, it can be safely ignored:
+#
+# [bccsp] GetDefault -> WARN 001 Before using BCCSP, please call InitFactories(). Falling back to bootBCCSP.
+#
+# You can ignore the logs regarding intermediate certs, we are not using them in
+# this crypto implementation.
+
+# After we create the org crypto material and the application channel genesis block,
+# we can now bring up the peers and ordering service. By default, the base
+# file for creating the network is "docker-compose-test-net.yaml" in the ``docker``
+# folder. This file defines the environment variables and file mounts that
+# point the crypto material and genesis block that were created in earlier.
+
+# Bring up the peer and orderer nodes using docker compose.
+function networkUp() {
+ checkPrereqs
+ # generate artifacts if they don't exist
+ if [ ! -d "organizations/peerOrganizations" ]; then
+ createOrgs
+ fi
+
+ COMPOSE_FILES="-f ${COMPOSE_FILE_BASE}"
+
+ if [ "${DATABASE}" == "couchdb" ]; then
+ COMPOSE_FILES="${COMPOSE_FILES} -f ${COMPOSE_FILE_COUCH}"
+ fi
+
+ docker-compose ${COMPOSE_FILES} up -d 2>&1
+
+ docker ps -a
+ if [ $? -ne 0 ]; then
+ fatalln "Unable to start network"
+ fi
+}
+
+# call the script to create the channel, join the peers of org1 and org2,
+# and then update the anchor peers for each organization
+function createChannel() {
+ # Bring up the network if it is not already up.
+
+ if [ ! -d "organizations/peerOrganizations" ]; then
+ infoln "Bringing up network"
+ networkUp
+ fi
+
+ # now run the script that creates a channel. This script uses configtxgen once
+ # to create the channel creation transaction and the anchor peer updates.
+ scripts/createChannel.sh $CHANNEL_NAME $CLI_DELAY $MAX_RETRY $VERBOSE
+}
+
+
+## Call the script to deploy a chaincode to the channel
+function deployCC() {
+ scripts/deployCC.sh $CHANNEL_NAME $CC_NAME $CC_SRC_PATH $CC_SRC_LANGUAGE $CC_VERSION $CC_SEQUENCE $CC_INIT_FCN $CC_END_POLICY $CC_COLL_CONFIG $CLI_DELAY $MAX_RETRY $VERBOSE
+
+ if [ $? -ne 0 ]; then
+ fatalln "Deploying chaincode failed"
+ fi
+}
+
+
+# Tear down running network
+function networkDown() {
+ # stop org3 containers also in addition to org1 and org2, in case we were running sample to add org3
+ docker-compose -f $COMPOSE_FILE_BASE -f $COMPOSE_FILE_COUCH -f $COMPOSE_FILE_CA down --volumes --remove-orphans
+ docker-compose -f $COMPOSE_FILE_COUCH_ORG3 -f $COMPOSE_FILE_ORG3 down --volumes --remove-orphans
+ docker-compose -f $COMPOSE_FILE_COUCH_ORG4 -f $COMPOSE_FILE_ORG4 down --volumes --remove-orphans
+
+ # Don't remove the generated artifacts -- note, the ledgers are always removed
+ if [ "$MODE" != "restart" ]; then
+ # Bring down the network, deleting the volumes
+ #Cleanup the chaincode containers
+ clearContainers
+ #Cleanup images
+ removeUnwantedImages
+ # remove orderer block and other channel configuration transactions and certs
+ docker run --rm -v "$(pwd):/data" busybox sh -c 'cd /data && rm -rf system-genesis-block/*.block organizations/peerOrganizations organizations/ordererOrganizations'
+ ## remove fabric ca artifacts
+ docker run --rm -v "$(pwd):/data" busybox sh -c 'cd /data && rm -rf organizations/fabric-ca/org1/msp organizations/fabric-ca/org1/tls-cert.pem organizations/fabric-ca/org1/ca-cert.pem organizations/fabric-ca/org1/IssuerPublicKey organizations/fabric-ca/org1/IssuerRevocationPublicKey organizations/fabric-ca/org1/fabric-ca-server.db'
+ docker run --rm -v "$(pwd):/data" busybox sh -c 'cd /data && rm -rf organizations/fabric-ca/org2/msp organizations/fabric-ca/org2/tls-cert.pem organizations/fabric-ca/org2/ca-cert.pem organizations/fabric-ca/org2/IssuerPublicKey organizations/fabric-ca/org2/IssuerRevocationPublicKey organizations/fabric-ca/org2/fabric-ca-server.db'
+ docker run --rm -v "$(pwd):/data" busybox sh -c 'cd /data && rm -rf organizations/fabric-ca/ordererOrg/msp organizations/fabric-ca/ordererOrg/tls-cert.pem organizations/fabric-ca/ordererOrg/ca-cert.pem organizations/fabric-ca/ordererOrg/IssuerPublicKey organizations/fabric-ca/ordererOrg/IssuerRevocationPublicKey organizations/fabric-ca/ordererOrg/fabric-ca-server.db'
+ docker run --rm -v "$(pwd):/data" busybox sh -c 'cd /data && rm -rf addOrg3/fabric-ca/org3/msp addOrg3/fabric-ca/org3/tls-cert.pem addOrg3/fabric-ca/org3/ca-cert.pem addOrg3/fabric-ca/org3/IssuerPublicKey addOrg3/fabric-ca/org3/IssuerRevocationPublicKey addOrg3/fabric-ca/org3/fabric-ca-server.db'
+ docker run --rm -v "$(pwd):/data" busybox sh -c 'cd /data && rm -rf addOrg4/fabric-ca/org4/msp addOrg4/fabric-ca/org4/tls-cert.pem addOrg4/fabric-ca/org4/ca-cert.pem addOrg4/fabric-ca/org4/IssuerPublicKey addOrg4/fabric-ca/org4/IssuerRevocationPublicKey addOrg4/fabric-ca/org4/fabric-ca-server.db'
+
+ # remove channel and script artifacts
+ docker run --rm -v "$(pwd):/data" busybox sh -c 'cd /data && rm -rf channel-artifacts log.txt *.tar.gz'
+ fi
+}
+
+# Using crpto vs CA. default is cryptogen
+CRYPTO="cryptogen"
+# timeout duration - the duration the CLI should wait for a response from
+# another container before giving up
+MAX_RETRY=8
+# default for delay between commands
+CLI_DELAY=3
+# channel name defaults to "mychannel"
+CHANNEL_NAME="mychannel"
+# chaincode name defaults to "NA"
+CC_NAME="NA"
+# chaincode path defaults to "NA"
+CC_SRC_PATH="NA"
+# endorsement policy defaults to "NA". This would allow chaincodes to use the majority default policy.
+CC_END_POLICY="NA"
+# collection configuration defaults to "NA"
+CC_COLL_CONFIG="NA"
+# chaincode init function defaults to "NA"
+CC_INIT_FCN="NA"
+# use this as the default docker-compose yaml definition
+COMPOSE_FILE_BASE=docker/docker-compose-test-net.yaml
+# docker-compose.yaml file if you are using couchdb
+COMPOSE_FILE_COUCH=docker/docker-compose-couch.yaml
+# certificate authorities compose file
+COMPOSE_FILE_CA=docker/docker-compose-ca.yaml
+# use this as the docker compose couch file for org3
+COMPOSE_FILE_COUCH_ORG3=addOrg3/docker/docker-compose-couch-org3.yaml
+# use this as the default docker-compose yaml definition for org3
+COMPOSE_FILE_ORG3=addOrg3/docker/docker-compose-org3.yaml
+# use this as the docker compose couch file for org4
+COMPOSE_FILE_COUCH_ORG4=addOrg4/docker/docker-compose-couch-org4.yaml
+# use this as the default docker-compose yaml definition for org4
+COMPOSE_FILE_ORG4=addOrg4/docker/docker-compose-org4.yaml
+
+#
+# chaincode language defaults to "NA"
+CC_SRC_LANGUAGE="NA"
+# Chaincode version
+CC_VERSION="1.0"
+# Chaincode definition sequence
+CC_SEQUENCE=1
+# default database
+DATABASE="leveldb"
+
+# Parse commandline args
+
+## Parse mode
+if [[ $# -lt 1 ]] ; then
+ printHelp
+ exit 0
+else
+ MODE=$1
+ shift
+fi
+
+# parse a createChannel subcommand if used
+if [[ $# -ge 1 ]] ; then
+ key="$1"
+ if [[ "$key" == "createChannel" ]]; then
+ export MODE="createChannel"
+ shift
+ fi
+fi
+
+# parse flags
+
+while [[ $# -ge 1 ]] ; do
+ key="$1"
+ case $key in
+ -h )
+ printHelp $MODE
+ exit 0
+ ;;
+ -c )
+ CHANNEL_NAME="$2"
+ shift
+ ;;
+ -ca )
+ CRYPTO="Certificate Authorities"
+ ;;
+ -r )
+ MAX_RETRY="$2"
+ shift
+ ;;
+ -d )
+ CLI_DELAY="$2"
+ shift
+ ;;
+ -s )
+ DATABASE="$2"
+ shift
+ ;;
+ -ccl )
+ CC_SRC_LANGUAGE="$2"
+ shift
+ ;;
+ -ccn )
+ CC_NAME="$2"
+ shift
+ ;;
+ -ccv )
+ CC_VERSION="$2"
+ shift
+ ;;
+ -ccs )
+ CC_SEQUENCE="$2"
+ shift
+ ;;
+ -ccp )
+ CC_SRC_PATH="$2"
+ shift
+ ;;
+ -ccep )
+ CC_END_POLICY="$2"
+ shift
+ ;;
+ -cccg )
+ CC_COLL_CONFIG="$2"
+ shift
+ ;;
+ -cci )
+ CC_INIT_FCN="$2"
+ shift
+ ;;
+ -verbose )
+ VERBOSE=true
+ shift
+ ;;
+ * )
+ errorln "Unknown flag: $key"
+ printHelp
+ exit 1
+ ;;
+ esac
+ shift
+done
+
+# Are we generating crypto material with this command?
+if [ ! -d "organizations/peerOrganizations" ]; then
+ CRYPTO_MODE="with crypto from '${CRYPTO}'"
+else
+ CRYPTO_MODE=""
+fi
+
+# Determine mode of operation and printing out what we asked for
+if [ "$MODE" == "up" ]; then
+ infoln "Starting nodes with CLI timeout of '${MAX_RETRY}' tries and CLI delay of '${CLI_DELAY}' seconds and using database '${DATABASE}' ${CRYPTO_MODE}"
+elif [ "$MODE" == "createChannel" ]; then
+ infoln "Creating channel '${CHANNEL_NAME}'."
+ infoln "If network is not up, starting nodes with CLI timeout of '${MAX_RETRY}' tries and CLI delay of '${CLI_DELAY}' seconds and using database '${DATABASE} ${CRYPTO_MODE}"
+elif [ "$MODE" == "down" ]; then
+ infoln "Stopping network"
+elif [ "$MODE" == "restart" ]; then
+ infoln "Restarting network"
+elif [ "$MODE" == "deployCC" ]; then
+ infoln "deploying chaincode on channel '${CHANNEL_NAME}'"
+else
+ printHelp
+ exit 1
+fi
+
+if [ "${MODE}" == "up" ]; then
+ networkUp
+elif [ "${MODE}" == "createChannel" ]; then
+ createChannel
+elif [ "${MODE}" == "deployCC" ]; then
+ deployCC
+elif [ "${MODE}" == "down" ]; then
+ networkDown
+else
+ printHelp
+ exit 1
+fi
diff --git a/fabric/network/organizations/ccp-generate.sh b/fabric/network/organizations/ccp-generate.sh
@@ -0,0 +1,45 @@
+#!/usr/bin/env bash
+
+function one_line_pem {
+ echo "`awk 'NF {sub(/\\n/, ""); printf "%s\\\\\\\n",$0;}' $1`"
+}
+
+function json_ccp {
+ local PP=$(one_line_pem $4)
+ local CP=$(one_line_pem $5)
+ sed -e "s/\${ORG}/$1/" \
+ -e "s/\${P0PORT}/$2/" \
+ -e "s/\${CAPORT}/$3/" \
+ -e "s#\${PEERPEM}#$PP#" \
+ -e "s#\${CAPEM}#$CP#" \
+ organizations/ccp-template.json
+}
+
+function yaml_ccp {
+ local PP=$(one_line_pem $4)
+ local CP=$(one_line_pem $5)
+ sed -e "s/\${ORG}/$1/" \
+ -e "s/\${P0PORT}/$2/" \
+ -e "s/\${CAPORT}/$3/" \
+ -e "s#\${PEERPEM}#$PP#" \
+ -e "s#\${CAPEM}#$CP#" \
+ organizations/ccp-template.yaml | sed -e $'s/\\\\n/\\\n /g'
+}
+
+ORG=1
+P0PORT=7051
+CAPORT=7054
+PEERPEM=organizations/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem
+CAPEM=organizations/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem
+
+echo "$(json_ccp $ORG $P0PORT $CAPORT $PEERPEM $CAPEM)" > organizations/peerOrganizations/org1.example.com/connection-org1.json
+echo "$(yaml_ccp $ORG $P0PORT $CAPORT $PEERPEM $CAPEM)" > organizations/peerOrganizations/org1.example.com/connection-org1.yaml
+
+ORG=2
+P0PORT=9051
+CAPORT=8054
+PEERPEM=organizations/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem
+CAPEM=organizations/peerOrganizations/org2.example.com/ca/ca.org2.example.com-cert.pem
+
+echo "$(json_ccp $ORG $P0PORT $CAPORT $PEERPEM $CAPEM)" > organizations/peerOrganizations/org2.example.com/connection-org2.json
+echo "$(yaml_ccp $ORG $P0PORT $CAPORT $PEERPEM $CAPEM)" > organizations/peerOrganizations/org2.example.com/connection-org2.yaml
diff --git a/fabric/network/organizations/ccp-template.json b/fabric/network/organizations/ccp-template.json
@@ -0,0 +1,49 @@
+{
+ "name": "test-network-org${ORG}",
+ "version": "1.0.0",
+ "client": {
+ "organization": "Org${ORG}",
+ "connection": {
+ "timeout": {
+ "peer": {
+ "endorser": "300"
+ }
+ }
+ }
+ },
+ "organizations": {
+ "Org${ORG}": {
+ "mspid": "Org${ORG}MSP",
+ "peers": [
+ "peer0.org${ORG}.example.com"
+ ],
+ "certificateAuthorities": [
+ "ca.org${ORG}.example.com"
+ ]
+ }
+ },
+ "peers": {
+ "peer0.org${ORG}.example.com": {
+ "url": "grpcs://localhost:${P0PORT}",
+ "tlsCACerts": {
+ "pem": "${PEERPEM}"
+ },
+ "grpcOptions": {
+ "ssl-target-name-override": "peer0.org${ORG}.example.com",
+ "hostnameOverride": "peer0.org${ORG}.example.com"
+ }
+ }
+ },
+ "certificateAuthorities": {
+ "ca.org${ORG}.example.com": {
+ "url": "https://localhost:${CAPORT}",
+ "caName": "ca-org${ORG}",
+ "tlsCACerts": {
+ "pem": ["${CAPEM}"]
+ },
+ "httpOptions": {
+ "verify": false
+ }
+ }
+ }
+}
diff --git a/fabric/network/organizations/ccp-template.yaml b/fabric/network/organizations/ccp-template.yaml
@@ -0,0 +1,35 @@
+---
+name: test-network-org${ORG}
+version: 1.0.0
+client:
+ organization: Org${ORG}
+ connection:
+ timeout:
+ peer:
+ endorser: '300'
+organizations:
+ Org${ORG}:
+ mspid: Org${ORG}MSP
+ peers:
+ - peer0.org${ORG}.example.com
+ certificateAuthorities:
+ - ca.org${ORG}.example.com
+peers:
+ peer0.org${ORG}.example.com:
+ url: grpcs://localhost:${P0PORT}
+ tlsCACerts:
+ pem: |
+ ${PEERPEM}
+ grpcOptions:
+ ssl-target-name-override: peer0.org${ORG}.example.com
+ hostnameOverride: peer0.org${ORG}.example.com
+certificateAuthorities:
+ ca.org${ORG}.example.com:
+ url: https://localhost:${CAPORT}
+ caName: ca-org${ORG}
+ tlsCACerts:
+ pem:
+ - |
+ ${CAPEM}
+ httpOptions:
+ verify: false
diff --git a/fabric/network/organizations/cryptogen/crypto-config-orderer.yaml b/fabric/network/organizations/cryptogen/crypto-config-orderer.yaml
@@ -0,0 +1,22 @@
+# Copyright IBM Corp. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+# ---------------------------------------------------------------------------
+# "OrdererOrgs" - Definition of organizations managing orderer nodes
+# ---------------------------------------------------------------------------
+OrdererOrgs:
+ # ---------------------------------------------------------------------------
+ # Orderer
+ # ---------------------------------------------------------------------------
+ - Name: Orderer
+ Domain: example.com
+ EnableNodeOUs: true
+ # ---------------------------------------------------------------------------
+ # "Specs" - See PeerOrgs for complete description
+ # ---------------------------------------------------------------------------
+ Specs:
+ - Hostname: orderer
+ SANS:
+ - localhost
diff --git a/fabric/network/organizations/cryptogen/crypto-config-org1.yaml b/fabric/network/organizations/cryptogen/crypto-config-org1.yaml
@@ -0,0 +1,61 @@
+# Copyright IBM Corp. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+
+# ---------------------------------------------------------------------------
+# "PeerOrgs" - Definition of organizations managing peer nodes
+# ---------------------------------------------------------------------------
+PeerOrgs:
+ # ---------------------------------------------------------------------------
+ # Org1
+ # ---------------------------------------------------------------------------
+ - Name: Org1
+ Domain: org1.example.com
+ EnableNodeOUs: true
+ # ---------------------------------------------------------------------------
+ # "Specs"
+ # ---------------------------------------------------------------------------
+ # Uncomment this section to enable the explicit definition of hosts in your
+ # configuration. Most users will want to use Template, below
+ #
+ # Specs is an array of Spec entries. Each Spec entry consists of two fields:
+ # - Hostname: (Required) The desired hostname, sans the domain.
+ # - CommonName: (Optional) Specifies the template or explicit override for
+ # the CN. By default, this is the template:
+ #
+ # "{{.Hostname}}.{{.Domain}}"
+ #
+ # which obtains its values from the Spec.Hostname and
+ # Org.Domain, respectively.
+ # ---------------------------------------------------------------------------
+ # - Hostname: foo # implicitly "foo.org1.example.com"
+ # CommonName: foo27.org5.example.com # overrides Hostname-based FQDN set above
+ # - Hostname: bar
+ # - Hostname: baz
+ # ---------------------------------------------------------------------------
+ # "Template"
+ # ---------------------------------------------------------------------------
+ # Allows for the definition of 1 or more hosts that are created sequentially
+ # from a template. By default, this looks like "peer%d" from 0 to Count-1.
+ # You may override the number of nodes (Count), the starting index (Start)
+ # or the template used to construct the name (Hostname).
+ #
+ # Note: Template and Specs are not mutually exclusive. You may define both
+ # sections and the aggregate nodes will be created for you. Take care with
+ # name collisions
+ # ---------------------------------------------------------------------------
+ Template:
+ Count: 1
+ SANS:
+ - localhost
+ # Start: 5
+ # Hostname: {{.Prefix}}{{.Index}} # default
+ # ---------------------------------------------------------------------------
+ # "Users"
+ # ---------------------------------------------------------------------------
+ # Count: The number of user accounts _in addition_ to Admin
+ # ---------------------------------------------------------------------------
+ Users:
+ Count: 1
diff --git a/fabric/network/organizations/cryptogen/crypto-config-org2.yaml b/fabric/network/organizations/cryptogen/crypto-config-org2.yaml
@@ -0,0 +1,61 @@
+# Copyright IBM Corp. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+# ---------------------------------------------------------------------------
+# "PeerOrgs" - Definition of organizations managing peer nodes
+# ---------------------------------------------------------------------------
+PeerOrgs:
+ # ---------------------------------------------------------------------------
+ # Org2
+ # ---------------------------------------------------------------------------
+ - Name: Org2
+ Domain: org2.example.com
+ EnableNodeOUs: true
+ # ---------------------------------------------------------------------------
+ # "Specs"
+ # ---------------------------------------------------------------------------
+ # Uncomment this section to enable the explicit definition of hosts in your
+ # configuration. Most users will want to use Template, below
+ #
+ # Specs is an array of Spec entries. Each Spec entry consists of two fields:
+ # - Hostname: (Required) The desired hostname, sans the domain.
+ # - CommonName: (Optional) Specifies the template or explicit override for
+ # the CN. By default, this is the template:
+ #
+ # "{{.Hostname}}.{{.Domain}}"
+ #
+ # which obtains its values from the Spec.Hostname and
+ # Org.Domain, respectively.
+ # ---------------------------------------------------------------------------
+ # Specs:
+ # - Hostname: foo # implicitly "foo.org1.example.com"
+ # CommonName: foo27.org5.example.com # overrides Hostname-based FQDN set above
+ # - Hostname: bar
+ # - Hostname: baz
+ # ---------------------------------------------------------------------------
+ # "Template"
+ # ---------------------------------------------------------------------------
+ # Allows for the definition of 1 or more hosts that are created sequentially
+ # from a template. By default, this looks like "peer%d" from 0 to Count-1.
+ # You may override the number of nodes (Count), the starting index (Start)
+ # or the template used to construct the name (Hostname).
+ #
+ # Note: Template and Specs are not mutually exclusive. You may define both
+ # sections and the aggregate nodes will be created for you. Take care with
+ # name collisions
+ # ---------------------------------------------------------------------------
+ Template:
+ Count: 1
+ SANS:
+ - localhost
+ # Start: 5
+ # Hostname: {{.Prefix}}{{.Index}} # default
+ # ---------------------------------------------------------------------------
+ # "Users"
+ # ---------------------------------------------------------------------------
+ # Count: The number of user accounts _in addition_ to Admin
+ # ---------------------------------------------------------------------------
+ Users:
+ Count: 1
diff --git a/fabric/network/organizations/fabric-ca/registerEnroll.sh b/fabric/network/organizations/fabric-ca/registerEnroll.sh
@@ -0,0 +1,226 @@
+#!/usr/bin/env bash
+
+function createOrg1() {
+ infoln "Enrolling the CA admin"
+ mkdir -p organizations/peerOrganizations/org1.example.com/
+
+ export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org1.example.com/
+
+ set -x
+ fabric-ca-client enroll -u https://admin:adminpw@localhost:7054 --caname ca-org1 --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ echo 'NodeOUs:
+ Enable: true
+ ClientOUIdentifier:
+ Certificate: cacerts/localhost-7054-ca-org1.pem
+ OrganizationalUnitIdentifier: client
+ PeerOUIdentifier:
+ Certificate: cacerts/localhost-7054-ca-org1.pem
+ OrganizationalUnitIdentifier: peer
+ AdminOUIdentifier:
+ Certificate: cacerts/localhost-7054-ca-org1.pem
+ OrganizationalUnitIdentifier: admin
+ OrdererOUIdentifier:
+ Certificate: cacerts/localhost-7054-ca-org1.pem
+ OrganizationalUnitIdentifier: orderer' > "${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml"
+
+ infoln "Registering peer0"
+ set -x
+ fabric-ca-client register --caname ca-org1 --id.name peer0 --id.secret peer0pw --id.type peer --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ infoln "Registering user"
+ set -x
+ fabric-ca-client register --caname ca-org1 --id.name user1 --id.secret user1pw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ infoln "Registering the org admin"
+ set -x
+ fabric-ca-client register --caname ca-org1 --id.name org1admin --id.secret org1adminpw --id.type admin --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ infoln "Generating the peer0 msp"
+ set -x
+ fabric-ca-client enroll -u https://peer0:peer0pw@localhost:7054 --caname ca-org1 -M "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp" --csr.hosts peer0.org1.example.com --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ cp "${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp/config.yaml"
+
+ infoln "Generating the peer0-tls certificates"
+ set -x
+ fabric-ca-client enroll -u https://peer0:peer0pw@localhost:7054 --caname ca-org1 -M "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls" --enrollment.profile tls --csr.hosts peer0.org1.example.com --csr.hosts localhost --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ cp "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/tlscacerts/"* "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt"
+ cp "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/signcerts/"* "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/server.crt"
+ cp "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/keystore/"* "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/server.key"
+
+ mkdir -p "${PWD}/organizations/peerOrganizations/org1.example.com/msp/tlscacerts"
+ cp "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/tlscacerts/"* "${PWD}/organizations/peerOrganizations/org1.example.com/msp/tlscacerts/ca.crt"
+
+ mkdir -p "${PWD}/organizations/peerOrganizations/org1.example.com/tlsca"
+ cp "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/tlscacerts/"* "${PWD}/organizations/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem"
+
+ mkdir -p "${PWD}/organizations/peerOrganizations/org1.example.com/ca"
+ cp "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp/cacerts/"* "${PWD}/organizations/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem"
+
+ infoln "Generating the user msp"
+ set -x
+ fabric-ca-client enroll -u https://user1:user1pw@localhost:7054 --caname ca-org1 -M "${PWD}/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ cp "${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/config.yaml"
+
+ infoln "Generating the org admin msp"
+ set -x
+ fabric-ca-client enroll -u https://org1admin:org1adminpw@localhost:7054 --caname ca-org1 -M "${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ cp "${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/config.yaml"
+}
+
+function createOrg2() {
+ infoln "Enrolling the CA admin"
+ mkdir -p organizations/peerOrganizations/org2.example.com/
+
+ export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org2.example.com/
+
+ set -x
+ fabric-ca-client enroll -u https://admin:adminpw@localhost:8054 --caname ca-org2 --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ echo 'NodeOUs:
+ Enable: true
+ ClientOUIdentifier:
+ Certificate: cacerts/localhost-8054-ca-org2.pem
+ OrganizationalUnitIdentifier: client
+ PeerOUIdentifier:
+ Certificate: cacerts/localhost-8054-ca-org2.pem
+ OrganizationalUnitIdentifier: peer
+ AdminOUIdentifier:
+ Certificate: cacerts/localhost-8054-ca-org2.pem
+ OrganizationalUnitIdentifier: admin
+ OrdererOUIdentifier:
+ Certificate: cacerts/localhost-8054-ca-org2.pem
+ OrganizationalUnitIdentifier: orderer' > "${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml"
+
+ infoln "Registering peer0"
+ set -x
+ fabric-ca-client register --caname ca-org2 --id.name peer0 --id.secret peer0pw --id.type peer --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ infoln "Registering user"
+ set -x
+ fabric-ca-client register --caname ca-org2 --id.name user1 --id.secret user1pw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ infoln "Registering the org admin"
+ set -x
+ fabric-ca-client register --caname ca-org2 --id.name org2admin --id.secret org2adminpw --id.type admin --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ infoln "Generating the peer0 msp"
+ set -x
+ fabric-ca-client enroll -u https://peer0:peer0pw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp" --csr.hosts peer0.org2.example.com --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ cp "${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp/config.yaml"
+
+ infoln "Generating the peer0-tls certificates"
+ set -x
+ fabric-ca-client enroll -u https://peer0:peer0pw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls" --enrollment.profile tls --csr.hosts peer0.org2.example.com --csr.hosts localhost --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ cp "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/tlscacerts/"* "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt"
+ cp "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/signcerts/"* "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/server.crt"
+ cp "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/keystore/"* "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/server.key"
+
+ mkdir -p "${PWD}/organizations/peerOrganizations/org2.example.com/msp/tlscacerts"
+ cp "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/tlscacerts/"* "${PWD}/organizations/peerOrganizations/org2.example.com/msp/tlscacerts/ca.crt"
+
+ mkdir -p "${PWD}/organizations/peerOrganizations/org2.example.com/tlsca"
+ cp "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/tlscacerts/"* "${PWD}/organizations/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem"
+
+ mkdir -p "${PWD}/organizations/peerOrganizations/org2.example.com/ca"
+ cp "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp/cacerts/"* "${PWD}/organizations/peerOrganizations/org2.example.com/ca/ca.org2.example.com-cert.pem"
+
+ infoln "Generating the user msp"
+ set -x
+ fabric-ca-client enroll -u https://user1:user1pw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/users/User1@org2.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ cp "${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org2.example.com/users/User1@org2.example.com/msp/config.yaml"
+
+ infoln "Generating the org admin msp"
+ set -x
+ fabric-ca-client enroll -u https://org2admin:org2adminpw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ cp "${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/config.yaml"
+}
+
+function createOrderer() {
+ infoln "Enrolling the CA admin"
+ mkdir -p organizations/ordererOrganizations/example.com
+
+ export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/ordererOrganizations/example.com
+
+ set -x
+ fabric-ca-client enroll -u https://admin:adminpw@localhost:9054 --caname ca-orderer --tls.certfiles "${PWD}/organizations/fabric-ca/ordererOrg/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ echo 'NodeOUs:
+ Enable: true
+ ClientOUIdentifier:
+ Certificate: cacerts/localhost-9054-ca-orderer.pem
+ OrganizationalUnitIdentifier: client
+ PeerOUIdentifier:
+ Certificate: cacerts/localhost-9054-ca-orderer.pem
+ OrganizationalUnitIdentifier: peer
+ AdminOUIdentifier:
+ Certificate: cacerts/localhost-9054-ca-orderer.pem
+ OrganizationalUnitIdentifier: admin
+ OrdererOUIdentifier:
+ Certificate: cacerts/localhost-9054-ca-orderer.pem
+ OrganizationalUnitIdentifier: orderer' > "${PWD}/organizations/ordererOrganizations/example.com/msp/config.yaml"
+
+ infoln "Registering orderer"
+ set -x
+ fabric-ca-client register --caname ca-orderer --id.name orderer --id.secret ordererpw --id.type orderer --tls.certfiles "${PWD}/organizations/fabric-ca/ordererOrg/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ infoln "Registering the orderer admin"
+ set -x
+ fabric-ca-client register --caname ca-orderer --id.name ordererAdmin --id.secret ordererAdminpw --id.type admin --tls.certfiles "${PWD}/organizations/fabric-ca/ordererOrg/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ infoln "Generating the orderer msp"
+ set -x
+ fabric-ca-client enroll -u https://orderer:ordererpw@localhost:9054 --caname ca-orderer -M "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp" --csr.hosts orderer.example.com --csr.hosts localhost --tls.certfiles "${PWD}/organizations/fabric-ca/ordererOrg/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ cp "${PWD}/organizations/ordererOrganizations/example.com/msp/config.yaml" "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/config.yaml"
+
+ infoln "Generating the orderer-tls certificates"
+ set -x
+ fabric-ca-client enroll -u https://orderer:ordererpw@localhost:9054 --caname ca-orderer -M "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls" --enrollment.profile tls --csr.hosts orderer.example.com --csr.hosts localhost --tls.certfiles "${PWD}/organizations/fabric-ca/ordererOrg/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ cp "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/tlscacerts/"* "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt"
+ cp "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/signcerts/"* "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.crt"
+ cp "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/keystore/"* "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.key"
+
+ mkdir -p "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts"
+ cp "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/tlscacerts/"* "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem"
+
+ mkdir -p "${PWD}/organizations/ordererOrganizations/example.com/msp/tlscacerts"
+ cp "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/tlscacerts/"* "${PWD}/organizations/ordererOrganizations/example.com/msp/tlscacerts/tlsca.example.com-cert.pem"
+
+ infoln "Generating the admin msp"
+ set -x
+ fabric-ca-client enroll -u https://ordererAdmin:ordererAdminpw@localhost:9054 --caname ca-orderer -M "${PWD}/organizations/ordererOrganizations/example.com/users/Admin@example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/ordererOrg/tls-cert.pem"
+ { set +x; } 2>/dev/null
+
+ cp "${PWD}/organizations/ordererOrganizations/example.com/msp/config.yaml" "${PWD}/organizations/ordererOrganizations/example.com/users/Admin@example.com/msp/config.yaml"
+}
diff --git a/fabric/network/scripts/configUpdate.sh b/fabric/network/scripts/configUpdate.sh
@@ -0,0 +1,61 @@
+#!/usr/bin/env bash
+#
+# Copyright IBM Corp. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+# import utils
+. scripts/envVar.sh
+
+# fetchChannelConfig <org> <channel_id> <output_json>
+# Writes the current channel config for a given channel to a JSON file
+# NOTE: this must be run in a CLI container since it requires configtxlator
+fetchChannelConfig() {
+ ORG=$1
+ CHANNEL=$2
+ OUTPUT=$3
+
+ setGlobals $ORG
+
+ infoln "Fetching the most recent configuration block for the channel"
+ set -x
+ peer channel fetch config config_block.pb -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com -c $CHANNEL --tls --cafile "$ORDERER_CA"
+ { set +x; } 2>/dev/null
+
+ infoln "Decoding config block to JSON and isolating config to ${OUTPUT}"
+ set -x
+ configtxlator proto_decode --input config_block.pb --type common.Block | jq .data.data[0].payload.data.config >"${OUTPUT}"
+ { set +x; } 2>/dev/null
+}
+
+# createConfigUpdate <channel_id> <original_config.json> <modified_config.json> <output.pb>
+# Takes an original and modified config, and produces the config update tx
+# which transitions between the two
+# NOTE: this must be run in a CLI container since it requires configtxlator
+createConfigUpdate() {
+ CHANNEL=$1
+ ORIGINAL=$2
+ MODIFIED=$3
+ OUTPUT=$4
+
+ set -x
+ configtxlator proto_encode --input "${ORIGINAL}" --type common.Config >original_config.pb
+ configtxlator proto_encode --input "${MODIFIED}" --type common.Config >modified_config.pb
+ configtxlator compute_update --channel_id "${CHANNEL}" --original original_config.pb --updated modified_config.pb >config_update.pb
+ configtxlator proto_decode --input config_update.pb --type common.ConfigUpdate >config_update.json
+ echo '{"payload":{"header":{"channel_header":{"channel_id":"'$CHANNEL'", "type":2}},"data":{"config_update":'$(cat config_update.json)'}}}' | jq . >config_update_in_envelope.json
+ configtxlator proto_encode --input config_update_in_envelope.json --type common.Envelope >"${OUTPUT}"
+ { set +x; } 2>/dev/null
+}
+
+# signConfigtxAsPeerOrg <org> <configtx.pb>
+# Set the peerOrg admin of an org and sign the config update
+signConfigtxAsPeerOrg() {
+ ORG=$1
+ CONFIGTXFILE=$2
+ setGlobals $ORG
+ set -x
+ peer channel signconfigtx -f "${CONFIGTXFILE}"
+ { set +x; } 2>/dev/null
+}
diff --git a/fabric/network/scripts/createChannel.sh b/fabric/network/scripts/createChannel.sh
@@ -0,0 +1,102 @@
+#!/usr/bin/env bash
+
+# imports
+. scripts/envVar.sh
+. scripts/utils.sh
+
+CHANNEL_NAME="$1"
+DELAY="$2"
+MAX_RETRY="$3"
+VERBOSE="$4"
+: ${CHANNEL_NAME:="mychannel"}
+: ${DELAY:="3"}
+: ${MAX_RETRY:="5"}
+: ${VERBOSE:="false"}
+
+if [ ! -d "channel-artifacts" ]; then
+ mkdir channel-artifacts
+fi
+
+createChannelGenesisBlock() {
+ which configtxgen
+ if [ "$?" -ne 0 ]; then
+ fatalln "configtxgen tool not found."
+ fi
+ set -x
+ configtxgen -profile TwoOrgsApplicationGenesis -outputBlock ./channel-artifacts/${CHANNEL_NAME}.block -channelID $CHANNEL_NAME
+ res=$?
+ { set +x; } 2>/dev/null
+ verifyResult $res "Failed to generate channel configuration transaction..."
+}
+
+createChannel() {
+ setGlobals 1
+ # Poll in case the raft leader is not set yet
+ local rc=1
+ local COUNTER=1
+ while [ $rc -ne 0 -a $COUNTER -lt $MAX_RETRY ] ; do
+ sleep $DELAY
+ set -x
+ osnadmin channel join --channelID $CHANNEL_NAME --config-block ./channel-artifacts/${CHANNEL_NAME}.block -o localhost:7053 --ca-file "$ORDERER_CA" --client-cert "$ORDERER_ADMIN_TLS_SIGN_CERT" --client-key "$ORDERER_ADMIN_TLS_PRIVATE_KEY" >&log.txt
+ res=$?
+ { set +x; } 2>/dev/null
+ let rc=$res
+ COUNTER=$(expr $COUNTER + 1)
+ done
+ cat log.txt
+ verifyResult $res "Channel creation failed"
+}
+
+# joinChannel ORG
+joinChannel() {
+ FABRIC_CFG_PATH=$PWD/../config/
+ ORG=$1
+ setGlobals $ORG
+ local rc=1
+ local COUNTER=1
+ ## Sometimes Join takes time, hence retry
+ while [ $rc -ne 0 -a $COUNTER -lt $MAX_RETRY ] ; do
+ sleep $DELAY
+ set -x
+ peer channel join -b $BLOCKFILE >&log.txt
+ res=$?
+ { set +x; } 2>/dev/null
+ let rc=$res
+ COUNTER=$(expr $COUNTER + 1)
+ done
+ cat log.txt
+ verifyResult $res "After $MAX_RETRY attempts, peer0.org${ORG} has failed to join channel '$CHANNEL_NAME' "
+}
+
+setAnchorPeer() {
+ ORG=$1
+ docker exec cli ./scripts/setAnchorPeer.sh $ORG $CHANNEL_NAME
+}
+
+FABRIC_CFG_PATH=${PWD}/configtx
+
+## Create channel genesis block
+infoln "Generating channel genesis block '${CHANNEL_NAME}.block'"
+createChannelGenesisBlock
+
+FABRIC_CFG_PATH=$PWD/../config/
+BLOCKFILE="./channel-artifacts/${CHANNEL_NAME}.block"
+
+## Create channel
+infoln "Creating channel ${CHANNEL_NAME}"
+createChannel
+successln "Channel '$CHANNEL_NAME' created"
+
+## Join all the peers to the channel
+infoln "Joining org1 peer to the channel..."
+joinChannel 1
+infoln "Joining org2 peer to the channel..."
+joinChannel 2
+
+## Set the anchor peers for each org in the channel
+infoln "Setting anchor peer for org1..."
+setAnchorPeer 1
+infoln "Setting anchor peer for org2..."
+setAnchorPeer 2
+
+successln "Channel '$CHANNEL_NAME' joined"
diff --git a/fabric/network/scripts/deployCC.sh b/fabric/network/scripts/deployCC.sh
@@ -0,0 +1,355 @@
+#!/usr/bin/env bash
+
+source scripts/utils.sh
+
+CHANNEL_NAME=${1:-"mychannel"}
+CC_NAME=${2}
+CC_SRC_PATH=${3}
+CC_SRC_LANGUAGE=${4}
+CC_VERSION=${5:-"1.0"}
+CC_SEQUENCE=${6:-"1"}
+CC_INIT_FCN=${7:-"NA"}
+CC_END_POLICY=${8:-"NA"}
+CC_COLL_CONFIG=${9:-"NA"}
+DELAY=${10:-"3"}
+MAX_RETRY=${11:-"5"}
+VERBOSE=${12:-"false"}
+
+println "executing with the following"
+println "- CHANNEL_NAME: ${C_GREEN}${CHANNEL_NAME}${C_RESET}"
+println "- CC_NAME: ${C_GREEN}${CC_NAME}${C_RESET}"
+println "- CC_SRC_PATH: ${C_GREEN}${CC_SRC_PATH}${C_RESET}"
+println "- CC_SRC_LANGUAGE: ${C_GREEN}${CC_SRC_LANGUAGE}${C_RESET}"
+println "- CC_VERSION: ${C_GREEN}${CC_VERSION}${C_RESET}"
+println "- CC_SEQUENCE: ${C_GREEN}${CC_SEQUENCE}${C_RESET}"
+println "- CC_END_POLICY: ${C_GREEN}${CC_END_POLICY}${C_RESET}"
+println "- CC_COLL_CONFIG: ${C_GREEN}${CC_COLL_CONFIG}${C_RESET}"
+println "- CC_INIT_FCN: ${C_GREEN}${CC_INIT_FCN}${C_RESET}"
+println "- DELAY: ${C_GREEN}${DELAY}${C_RESET}"
+println "- MAX_RETRY: ${C_GREEN}${MAX_RETRY}${C_RESET}"
+println "- VERBOSE: ${C_GREEN}${VERBOSE}${C_RESET}"
+
+FABRIC_CFG_PATH=$PWD/../config/
+
+#User has not provided a name
+if [ -z "$CC_NAME" ] || [ "$CC_NAME" = "NA" ]; then
+ fatalln "No chaincode name was provided. Valid call example: ./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-go -ccl go"
+
+# User has not provided a path
+elif [ -z "$CC_SRC_PATH" ] || [ "$CC_SRC_PATH" = "NA" ]; then
+ fatalln "No chaincode path was provided. Valid call example: ./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-go -ccl go"
+
+# User has not provided a language
+elif [ -z "$CC_SRC_LANGUAGE" ] || [ "$CC_SRC_LANGUAGE" = "NA" ]; then
+ fatalln "No chaincode language was provided. Valid call example: ./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-go -ccl go"
+
+## Make sure that the path to the chaincode exists
+elif [ ! -d "$CC_SRC_PATH" ]; then
+ fatalln "Path to chaincode does not exist. Please provide different path."
+fi
+
+CC_SRC_LANGUAGE=$(echo "$CC_SRC_LANGUAGE" | tr [:upper:] [:lower:])
+
+# do some language specific preparation to the chaincode before packaging
+if [ "$CC_SRC_LANGUAGE" = "go" ]; then
+ CC_RUNTIME_LANGUAGE=golang
+
+ infoln "Vendoring Go dependencies at $CC_SRC_PATH"
+ pushd $CC_SRC_PATH
+ GO111MODULE=on go mod vendor
+ popd
+ successln "Finished vendoring Go dependencies"
+
+elif [ "$CC_SRC_LANGUAGE" = "java" ]; then
+ CC_RUNTIME_LANGUAGE=java
+
+ infoln "Compiling Java code..."
+ pushd $CC_SRC_PATH
+ ./gradlew installDist
+ popd
+ successln "Finished compiling Java code"
+ CC_SRC_PATH=$CC_SRC_PATH/build/install/$CC_NAME
+
+elif [ "$CC_SRC_LANGUAGE" = "javascript" ]; then
+ CC_RUNTIME_LANGUAGE=node
+
+elif [ "$CC_SRC_LANGUAGE" = "typescript" ]; then
+ CC_RUNTIME_LANGUAGE=node
+
+ infoln "Compiling TypeScript code into JavaScript..."
+ pushd $CC_SRC_PATH
+ npm install
+ npm run build
+ popd
+ successln "Finished compiling TypeScript code into JavaScript"
+
+else
+ fatalln "The chaincode language ${CC_SRC_LANGUAGE} is not supported by this script. Supported chaincode languages are: go, java, javascript, and typescript"
+ exit 1
+fi
+
+INIT_REQUIRED="--init-required"
+# check if the init fcn should be called
+if [ "$CC_INIT_FCN" = "NA" ]; then
+ INIT_REQUIRED=""
+fi
+
+if [ "$CC_END_POLICY" = "NA" ]; then
+ CC_END_POLICY=""
+else
+ CC_END_POLICY="--signature-policy $CC_END_POLICY"
+fi
+
+if [ "$CC_COLL_CONFIG" = "NA" ]; then
+ CC_COLL_CONFIG=""
+else
+ CC_COLL_CONFIG="--collections-config $CC_COLL_CONFIG"
+fi
+
+# import utils
+. scripts/envVar.sh
+
+packageChaincode() {
+ set -x
+ peer lifecycle chaincode package ${CC_NAME}.tar.gz --path ${CC_SRC_PATH} --lang ${CC_RUNTIME_LANGUAGE} --label ${CC_NAME}_${CC_VERSION} >&log.txt
+ res=$?
+ { set +x; } 2>/dev/null
+ cat log.txt
+ verifyResult $res "Chaincode packaging has failed"
+ successln "Chaincode is packaged"
+}
+
+# installChaincode PEER ORG
+installChaincode() {
+ ORG=$1
+ setGlobals $ORG
+ set -x
+ peer lifecycle chaincode install ${CC_NAME}.tar.gz >&log.txt
+ res=$?
+ { set +x; } 2>/dev/null
+ cat log.txt
+ verifyResult $res "Chaincode installation on peer0.org${ORG} has failed"
+ successln "Chaincode is installed on peer0.org${ORG}"
+}
+
+# queryInstalled PEER ORG
+queryInstalled() {
+ ORG=$1
+ setGlobals $ORG
+ set -x
+ peer lifecycle chaincode queryinstalled >&log.txt
+ res=$?
+ { set +x; } 2>/dev/null
+ cat log.txt
+ PACKAGE_ID=$(sed -n "/${CC_NAME}_${CC_VERSION}/{s/^Package ID: //; s/, Label:.*$//; p;}" log.txt)
+ verifyResult $res "Query installed on peer0.org${ORG} has failed"
+ successln "Query installed successful on peer0.org${ORG} on channel"
+}
+
+# approveForMyOrg VERSION PEER ORG
+approveForMyOrg() {
+ ORG=$1
+ setGlobals $ORG
+ set -x
+ peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$ORDERER_CA" --channelID $CHANNEL_NAME --name ${CC_NAME} --version ${CC_VERSION} --package-id ${PACKAGE_ID} --sequence ${CC_SEQUENCE} ${INIT_REQUIRED} ${CC_END_POLICY} ${CC_COLL_CONFIG} >&log.txt
+ res=$?
+ { set +x; } 2>/dev/null
+ cat log.txt
+ verifyResult $res "Chaincode definition approved on peer0.org${ORG} on channel '$CHANNEL_NAME' failed"
+ successln "Chaincode definition approved on peer0.org${ORG} on channel '$CHANNEL_NAME'"
+}
+
+# checkCommitReadiness VERSION PEER ORG
+checkCommitReadiness() {
+ ORG=$1
+ shift 1
+ setGlobals $ORG
+ infoln "Checking the commit readiness of the chaincode definition on peer0.org${ORG} on channel '$CHANNEL_NAME'..."
+ local rc=1
+ local COUNTER=1
+ # continue to poll
+ # we either get a successful response, or reach MAX RETRY
+ while [ $rc -ne 0 -a $COUNTER -lt $MAX_RETRY ]; do
+ sleep $DELAY
+ infoln "Attempting to check the commit readiness of the chaincode definition on peer0.org${ORG}, Retry after $DELAY seconds."
+ set -x
+ peer lifecycle chaincode checkcommitreadiness --channelID $CHANNEL_NAME --name ${CC_NAME} --version ${CC_VERSION} --sequence ${CC_SEQUENCE} ${INIT_REQUIRED} ${CC_END_POLICY} ${CC_COLL_CONFIG} --output json >&log.txt
+ res=$?
+ { set +x; } 2>/dev/null
+ let rc=0
+ for var in "$@"; do
+ grep "$var" log.txt &>/dev/null || let rc=1
+ done
+ COUNTER=$(expr $COUNTER + 1)
+ done
+ cat log.txt
+ if test $rc -eq 0; then
+ infoln "Checking the commit readiness of the chaincode definition successful on peer0.org${ORG} on channel '$CHANNEL_NAME'"
+ else
+ fatalln "After $MAX_RETRY attempts, Check commit readiness result on peer0.org${ORG} is INVALID!"
+ fi
+}
+
+# commitChaincodeDefinition VERSION PEER ORG (PEER ORG)...
+commitChaincodeDefinition() {
+ parsePeerConnectionParameters $@
+ res=$?
+ verifyResult $res "Invoke transaction failed on channel '$CHANNEL_NAME' due to uneven number of peer and org parameters "
+
+ # while 'peer chaincode' command can get the orderer endpoint from the
+ # peer (if join was successful), let's supply it directly as we know
+ # it using the "-o" option
+ set -x
+ peer lifecycle chaincode commit -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$ORDERER_CA" --channelID $CHANNEL_NAME --name ${CC_NAME} "${PEER_CONN_PARMS[@]}" --version ${CC_VERSION} --sequence ${CC_SEQUENCE} ${INIT_REQUIRED} ${CC_END_POLICY} ${CC_COLL_CONFIG} >&log.txt
+ res=$?
+ { set +x; } 2>/dev/null
+ cat log.txt
+ verifyResult $res "Chaincode definition commit failed on peer0.org${ORG} on channel '$CHANNEL_NAME' failed"
+ successln "Chaincode definition committed on channel '$CHANNEL_NAME'"
+}
+
+# queryCommitted ORG
+queryCommitted() {
+ ORG=$1
+ setGlobals $ORG
+ EXPECTED_RESULT="Version: ${CC_VERSION}, Sequence: ${CC_SEQUENCE}, Endorsement Plugin: escc, Validation Plugin: vscc"
+ infoln "Querying chaincode definition on peer0.org${ORG} on channel '$CHANNEL_NAME'..."
+ local rc=1
+ local COUNTER=1
+ # continue to poll
+ # we either get a successful response, or reach MAX RETRY
+ while [ $rc -ne 0 -a $COUNTER -lt $MAX_RETRY ]; do
+ sleep $DELAY
+ infoln "Attempting to Query committed status on peer0.org${ORG}, Retry after $DELAY seconds."
+ set -x
+ peer lifecycle chaincode querycommitted --channelID $CHANNEL_NAME --name ${CC_NAME} >&log.txt
+ res=$?
+ { set +x; } 2>/dev/null
+ test $res -eq 0 && VALUE=$(cat log.txt | grep -o '^Version: '$CC_VERSION', Sequence: [0-9]*, Endorsement Plugin: escc, Validation Plugin: vscc')
+ test "$VALUE" = "$EXPECTED_RESULT" && let rc=0
+ COUNTER=$(expr $COUNTER + 1)
+ done
+ cat log.txt
+ if test $rc -eq 0; then
+ successln "Query chaincode definition successful on peer0.org${ORG} on channel '$CHANNEL_NAME'"
+ else
+ fatalln "After $MAX_RETRY attempts, Query chaincode definition result on peer0.org${ORG} is INVALID!"
+ fi
+}
+
+chaincodeInvokeInit() {
+ parsePeerConnectionParameters $@
+ res=$?
+ verifyResult $res "Invoke transaction failed on channel '$CHANNEL_NAME' due to uneven number of peer and org parameters "
+
+ # while 'peer chaincode' command can get the orderer endpoint from the
+ # peer (if join was successful), let's supply it directly as we know
+ # it using the "-o" option
+ set -x
+ fcn_call='{"function":"'${CC_INIT_FCN}'","Args":[]}'
+ infoln "invoke fcn call:${fcn_call}"
+ peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$ORDERER_CA" -C $CHANNEL_NAME -n ${CC_NAME} "${PEER_CONN_PARMS[@]}" --isInit -c ${fcn_call} >&log.txt
+ res=$?
+ { set +x; } 2>/dev/null
+ cat log.txt
+ verifyResult $res "Invoke execution on $PEERS failed "
+ successln "Invoke transaction successful on $PEERS on channel '$CHANNEL_NAME'"
+}
+
+chaincodeQuery() {
+ ORG=$1
+ setGlobals $ORG
+ infoln "Querying on peer0.org${ORG} on channel '$CHANNEL_NAME'..."
+ local rc=1
+ local COUNTER=1
+ # continue to poll
+ # we either get a successful response, or reach MAX RETRY
+ while [ $rc -ne 0 -a $COUNTER -lt $MAX_RETRY ]; do
+ sleep $DELAY
+ infoln "Attempting to Query peer0.org${ORG}, Retry after $DELAY seconds."
+ set -x
+ peer chaincode query -C $CHANNEL_NAME -n ${CC_NAME} -c '{"Args":["queryAllCars"]}' >&log.txt
+ res=$?
+ { set +x; } 2>/dev/null
+ let rc=$res
+ COUNTER=$(expr $COUNTER + 1)
+ done
+ cat log.txt
+ if test $rc -eq 0; then
+ successln "Query successful on peer0.org${ORG} on channel '$CHANNEL_NAME'"
+ else
+ fatalln "After $MAX_RETRY attempts, Query result on peer0.org${ORG} is INVALID!"
+ fi
+}
+
+## package the chaincode
+packageChaincode
+
+## Install chaincode on peer0.org1 and peer0.org2
+infoln "Installing chaincode on peer0.org1..."
+installChaincode 1
+infoln "Installing chaincode on peer0.org2..."
+installChaincode 2
+infoln "Installing chaincode on peer0.org3..."
+installChaincode 3
+infoln "Installing chaincode on peer0.org2..."
+installChaincode 4
+
+## query whether the chaincode is installed
+queryInstalled 1
+
+## approve the definition for org1
+approveForMyOrg 1
+
+## check whether the chaincode definition is ready to be committed
+## expect org1 to have approved and org2 not to
+checkCommitReadiness 1 "\"Org1MSP\": true" "\"Org2MSP\": false" "\"Org3MSP\": false" "\"Org4MSP\": false"
+checkCommitReadiness 2 "\"Org1MSP\": true" "\"Org2MSP\": false" "\"Org3MSP\": false" "\"Org4MSP\": false"
+checkCommitReadiness 3 "\"Org1MSP\": true" "\"Org2MSP\": false" "\"Org3MSP\": false" "\"Org4MSP\": false"
+checkCommitReadiness 4 "\"Org1MSP\": true" "\"Org2MSP\": false" "\"Org3MSP\": false" "\"Org4MSP\": false"
+
+## now approve also for org2
+approveForMyOrg 2
+
+## check whether the chaincode definition is ready to be committed
+## expect them both to have approved
+checkCommitReadiness 1 "\"Org1MSP\": true" "\"Org2MSP\": true" "\"Org3MSP\": false" "\"Org4MSP\": false"
+checkCommitReadiness 2 "\"Org1MSP\": true" "\"Org2MSP\": true" "\"Org3MSP\": false" "\"Org4MSP\": false"
+checkCommitReadiness 3 "\"Org1MSP\": true" "\"Org2MSP\": true" "\"Org3MSP\": false" "\"Org4MSP\": false"
+checkCommitReadiness 4 "\"Org1MSP\": true" "\"Org2MSP\": true" "\"Org3MSP\": false" "\"Org4MSP\": false"
+
+approveForMyOrg 3
+
+## check whether the chaincode definition is ready to be committed
+## expect them both to have approved
+checkCommitReadiness 1 "\"Org1MSP\": true" "\"Org2MSP\": true" "\"Org3MSP\": true" "\"Org4MSP\": false"
+checkCommitReadiness 2 "\"Org1MSP\": true" "\"Org2MSP\": true" "\"Org3MSP\": true" "\"Org4MSP\": false"
+checkCommitReadiness 3 "\"Org1MSP\": true" "\"Org2MSP\": true" "\"Org3MSP\": true" "\"Org4MSP\": false"
+checkCommitReadiness 4 "\"Org1MSP\": true" "\"Org2MSP\": true" "\"Org3MSP\": true" "\"Org4MSP\": false"
+
+approveForMyOrg 4
+
+## check whether the chaincode definition is ready to be committed
+## expect them both to have approved
+checkCommitReadiness 1 "\"Org1MSP\": true" "\"Org2MSP\": true" "\"Org3MSP\": true" "\"Org4MSP\": true"
+checkCommitReadiness 2 "\"Org1MSP\": true" "\"Org2MSP\": true" "\"Org3MSP\": true" "\"Org4MSP\": true"
+checkCommitReadiness 3 "\"Org1MSP\": true" "\"Org2MSP\": true" "\"Org3MSP\": true" "\"Org4MSP\": true"
+checkCommitReadiness 4 "\"Org1MSP\": true" "\"Org2MSP\": true" "\"Org3MSP\": true" "\"Org4MSP\": true"
+## now that we know for sure all orgs have approved, commit the definition
+commitChaincodeDefinition 1 2 3 4
+
+## query on both orgs to see that the definition committed successfully
+queryCommitted 1
+queryCommitted 2
+queryCommitted 3
+queryCommitted 4
+
+## Invoke the chaincode - this does require that the chaincode have the 'initLedger'
+## method defined
+if [ "$CC_INIT_FCN" = "NA" ]; then
+ infoln "Chaincode initialization is not required"
+else
+ chaincodeInvokeInit 1 2 3 4
+fi
+
+exit 0
diff --git a/fabric/network/scripts/deployCC_2org.sh b/fabric/network/scripts/deployCC_2org.sh
@@ -0,0 +1,328 @@
+#!/usr/bin/env bash
+
+source scripts/utils.sh
+
+CHANNEL_NAME=${1:-"mychannel"}
+CC_NAME=${2}
+CC_SRC_PATH=${3}
+CC_SRC_LANGUAGE=${4}
+CC_VERSION=${5:-"1.0"}
+CC_SEQUENCE=${6:-"1"}
+CC_INIT_FCN=${7:-"NA"}
+CC_END_POLICY=${8:-"NA"}
+CC_COLL_CONFIG=${9:-"NA"}
+DELAY=${10:-"3"}
+MAX_RETRY=${11:-"5"}
+VERBOSE=${12:-"false"}
+
+println "executing with the following"
+println "- CHANNEL_NAME: ${C_GREEN}${CHANNEL_NAME}${C_RESET}"
+println "- CC_NAME: ${C_GREEN}${CC_NAME}${C_RESET}"
+println "- CC_SRC_PATH: ${C_GREEN}${CC_SRC_PATH}${C_RESET}"
+println "- CC_SRC_LANGUAGE: ${C_GREEN}${CC_SRC_LANGUAGE}${C_RESET}"
+println "- CC_VERSION: ${C_GREEN}${CC_VERSION}${C_RESET}"
+println "- CC_SEQUENCE: ${C_GREEN}${CC_SEQUENCE}${C_RESET}"
+println "- CC_END_POLICY: ${C_GREEN}${CC_END_POLICY}${C_RESET}"
+println "- CC_COLL_CONFIG: ${C_GREEN}${CC_COLL_CONFIG}${C_RESET}"
+println "- CC_INIT_FCN: ${C_GREEN}${CC_INIT_FCN}${C_RESET}"
+println "- DELAY: ${C_GREEN}${DELAY}${C_RESET}"
+println "- MAX_RETRY: ${C_GREEN}${MAX_RETRY}${C_RESET}"
+println "- VERBOSE: ${C_GREEN}${VERBOSE}${C_RESET}"
+
+FABRIC_CFG_PATH=$PWD/../config/
+
+#User has not provided a name
+if [ -z "$CC_NAME" ] || [ "$CC_NAME" = "NA" ]; then
+ fatalln "No chaincode name was provided. Valid call example: ./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-go -ccl go"
+
+# User has not provided a path
+elif [ -z "$CC_SRC_PATH" ] || [ "$CC_SRC_PATH" = "NA" ]; then
+ fatalln "No chaincode path was provided. Valid call example: ./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-go -ccl go"
+
+# User has not provided a language
+elif [ -z "$CC_SRC_LANGUAGE" ] || [ "$CC_SRC_LANGUAGE" = "NA" ]; then
+ fatalln "No chaincode language was provided. Valid call example: ./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-go -ccl go"
+
+## Make sure that the path to the chaincode exists
+elif [ ! -d "$CC_SRC_PATH" ]; then
+ fatalln "Path to chaincode does not exist. Please provide different path."
+fi
+
+CC_SRC_LANGUAGE=$(echo "$CC_SRC_LANGUAGE" | tr [:upper:] [:lower:])
+
+# do some language specific preparation to the chaincode before packaging
+if [ "$CC_SRC_LANGUAGE" = "go" ]; then
+ CC_RUNTIME_LANGUAGE=golang
+
+ infoln "Vendoring Go dependencies at $CC_SRC_PATH"
+ pushd $CC_SRC_PATH
+ GO111MODULE=on go mod vendor
+ popd
+ successln "Finished vendoring Go dependencies"
+
+elif [ "$CC_SRC_LANGUAGE" = "java" ]; then
+ CC_RUNTIME_LANGUAGE=java
+
+ infoln "Compiling Java code..."
+ pushd $CC_SRC_PATH
+ ./gradlew installDist
+ popd
+ successln "Finished compiling Java code"
+ CC_SRC_PATH=$CC_SRC_PATH/build/install/$CC_NAME
+
+elif [ "$CC_SRC_LANGUAGE" = "javascript" ]; then
+ CC_RUNTIME_LANGUAGE=node
+
+elif [ "$CC_SRC_LANGUAGE" = "typescript" ]; then
+ CC_RUNTIME_LANGUAGE=node
+
+ infoln "Compiling TypeScript code into JavaScript..."
+ pushd $CC_SRC_PATH
+ npm install
+ npm run build
+ popd
+ successln "Finished compiling TypeScript code into JavaScript"
+
+else
+ fatalln "The chaincode language ${CC_SRC_LANGUAGE} is not supported by this script. Supported chaincode languages are: go, java, javascript, and typescript"
+ exit 1
+fi
+
+INIT_REQUIRED="--init-required"
+# check if the init fcn should be called
+if [ "$CC_INIT_FCN" = "NA" ]; then
+ INIT_REQUIRED=""
+fi
+
+if [ "$CC_END_POLICY" = "NA" ]; then
+ CC_END_POLICY=""
+else
+ CC_END_POLICY="--signature-policy $CC_END_POLICY"
+fi
+
+if [ "$CC_COLL_CONFIG" = "NA" ]; then
+ CC_COLL_CONFIG=""
+else
+ CC_COLL_CONFIG="--collections-config $CC_COLL_CONFIG"
+fi
+
+# import utils
+. scripts/envVar.sh
+
+packageChaincode() {
+ set -x
+ peer lifecycle chaincode package ${CC_NAME}.tar.gz --path ${CC_SRC_PATH} --lang ${CC_RUNTIME_LANGUAGE} --label ${CC_NAME}_${CC_VERSION} >&log.txt
+ res=$?
+ { set +x; } 2>/dev/null
+ cat log.txt
+ verifyResult $res "Chaincode packaging has failed"
+ successln "Chaincode is packaged"
+}
+
+# installChaincode PEER ORG
+installChaincode() {
+ ORG=$1
+ setGlobals $ORG
+ set -x
+ peer lifecycle chaincode install ${CC_NAME}.tar.gz >&log.txt
+ res=$?
+ { set +x; } 2>/dev/null
+ cat log.txt
+ verifyResult $res "Chaincode installation on peer0.org${ORG} has failed"
+ successln "Chaincode is installed on peer0.org${ORG}"
+}
+
+# queryInstalled PEER ORG
+queryInstalled() {
+ ORG=$1
+ setGlobals $ORG
+ set -x
+ peer lifecycle chaincode queryinstalled >&log.txt
+ res=$?
+ { set +x; } 2>/dev/null
+ cat log.txt
+ PACKAGE_ID=$(sed -n "/${CC_NAME}_${CC_VERSION}/{s/^Package ID: //; s/, Label:.*$//; p;}" log.txt)
+ verifyResult $res "Query installed on peer0.org${ORG} has failed"
+ successln "Query installed successful on peer0.org${ORG} on channel"
+}
+
+# approveForMyOrg VERSION PEER ORG
+approveForMyOrg() {
+ ORG=$1
+ setGlobals $ORG
+ set -x
+ peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$ORDERER_CA" --channelID $CHANNEL_NAME --name ${CC_NAME} --version ${CC_VERSION} --package-id ${PACKAGE_ID} --sequence ${CC_SEQUENCE} ${INIT_REQUIRED} ${CC_END_POLICY} ${CC_COLL_CONFIG} >&log.txt
+ res=$?
+ { set +x; } 2>/dev/null
+ cat log.txt
+ verifyResult $res "Chaincode definition approved on peer0.org${ORG} on channel '$CHANNEL_NAME' failed"
+ successln "Chaincode definition approved on peer0.org${ORG} on channel '$CHANNEL_NAME'"
+}
+
+# checkCommitReadiness VERSION PEER ORG
+checkCommitReadiness() {
+ ORG=$1
+ shift 1
+ setGlobals $ORG
+ infoln "Checking the commit readiness of the chaincode definition on peer0.org${ORG} on channel '$CHANNEL_NAME'..."
+ local rc=1
+ local COUNTER=1
+ # continue to poll
+ # we either get a successful response, or reach MAX RETRY
+ while [ $rc -ne 0 -a $COUNTER -lt $MAX_RETRY ]; do
+ sleep $DELAY
+ infoln "Attempting to check the commit readiness of the chaincode definition on peer0.org${ORG}, Retry after $DELAY seconds."
+ set -x
+ peer lifecycle chaincode checkcommitreadiness --channelID $CHANNEL_NAME --name ${CC_NAME} --version ${CC_VERSION} --sequence ${CC_SEQUENCE} ${INIT_REQUIRED} ${CC_END_POLICY} ${CC_COLL_CONFIG} --output json >&log.txt
+ res=$?
+ { set +x; } 2>/dev/null
+ let rc=0
+ for var in "$@"; do
+ grep "$var" log.txt &>/dev/null || let rc=1
+ done
+ COUNTER=$(expr $COUNTER + 1)
+ done
+ cat log.txt
+ if test $rc -eq 0; then
+ infoln "Checking the commit readiness of the chaincode definition successful on peer0.org${ORG} on channel '$CHANNEL_NAME'"
+ else
+ fatalln "After $MAX_RETRY attempts, Check commit readiness result on peer0.org${ORG} is INVALID!"
+ fi
+}
+
+# commitChaincodeDefinition VERSION PEER ORG (PEER ORG)...
+commitChaincodeDefinition() {
+ parsePeerConnectionParameters $@
+ res=$?
+ verifyResult $res "Invoke transaction failed on channel '$CHANNEL_NAME' due to uneven number of peer and org parameters "
+
+ # while 'peer chaincode' command can get the orderer endpoint from the
+ # peer (if join was successful), let's supply it directly as we know
+ # it using the "-o" option
+ set -x
+ peer lifecycle chaincode commit -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$ORDERER_CA" --channelID $CHANNEL_NAME --name ${CC_NAME} "${PEER_CONN_PARMS[@]}" --version ${CC_VERSION} --sequence ${CC_SEQUENCE} ${INIT_REQUIRED} ${CC_END_POLICY} ${CC_COLL_CONFIG} >&log.txt
+ res=$?
+ { set +x; } 2>/dev/null
+ cat log.txt
+ verifyResult $res "Chaincode definition commit failed on peer0.org${ORG} on channel '$CHANNEL_NAME' failed"
+ successln "Chaincode definition committed on channel '$CHANNEL_NAME'"
+}
+
+# queryCommitted ORG
+queryCommitted() {
+ ORG=$1
+ setGlobals $ORG
+ EXPECTED_RESULT="Version: ${CC_VERSION}, Sequence: ${CC_SEQUENCE}, Endorsement Plugin: escc, Validation Plugin: vscc"
+ infoln "Querying chaincode definition on peer0.org${ORG} on channel '$CHANNEL_NAME'..."
+ local rc=1
+ local COUNTER=1
+ # continue to poll
+ # we either get a successful response, or reach MAX RETRY
+ while [ $rc -ne 0 -a $COUNTER -lt $MAX_RETRY ]; do
+ sleep $DELAY
+ infoln "Attempting to Query committed status on peer0.org${ORG}, Retry after $DELAY seconds."
+ set -x
+ peer lifecycle chaincode querycommitted --channelID $CHANNEL_NAME --name ${CC_NAME} >&log.txt
+ res=$?
+ { set +x; } 2>/dev/null
+ test $res -eq 0 && VALUE=$(cat log.txt | grep -o '^Version: '$CC_VERSION', Sequence: [0-9]*, Endorsement Plugin: escc, Validation Plugin: vscc')
+ test "$VALUE" = "$EXPECTED_RESULT" && let rc=0
+ COUNTER=$(expr $COUNTER + 1)
+ done
+ cat log.txt
+ if test $rc -eq 0; then
+ successln "Query chaincode definition successful on peer0.org${ORG} on channel '$CHANNEL_NAME'"
+ else
+ fatalln "After $MAX_RETRY attempts, Query chaincode definition result on peer0.org${ORG} is INVALID!"
+ fi
+}
+
+chaincodeInvokeInit() {
+ parsePeerConnectionParameters $@
+ res=$?
+ verifyResult $res "Invoke transaction failed on channel '$CHANNEL_NAME' due to uneven number of peer and org parameters "
+
+ # while 'peer chaincode' command can get the orderer endpoint from the
+ # peer (if join was successful), let's supply it directly as we know
+ # it using the "-o" option
+ set -x
+ fcn_call='{"function":"'${CC_INIT_FCN}'","Args":[]}'
+ infoln "invoke fcn call:${fcn_call}"
+ peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$ORDERER_CA" -C $CHANNEL_NAME -n ${CC_NAME} "${PEER_CONN_PARMS[@]}" --isInit -c ${fcn_call} >&log.txt
+ res=$?
+ { set +x; } 2>/dev/null
+ cat log.txt
+ verifyResult $res "Invoke execution on $PEERS failed "
+ successln "Invoke transaction successful on $PEERS on channel '$CHANNEL_NAME'"
+}
+
+chaincodeQuery() {
+ ORG=$1
+ setGlobals $ORG
+ infoln "Querying on peer0.org${ORG} on channel '$CHANNEL_NAME'..."
+ local rc=1
+ local COUNTER=1
+ # continue to poll
+ # we either get a successful response, or reach MAX RETRY
+ while [ $rc -ne 0 -a $COUNTER -lt $MAX_RETRY ]; do
+ sleep $DELAY
+ infoln "Attempting to Query peer0.org${ORG}, Retry after $DELAY seconds."
+ set -x
+ peer chaincode query -C $CHANNEL_NAME -n ${CC_NAME} -c '{"Args":["queryAllCars"]}' >&log.txt
+ res=$?
+ { set +x; } 2>/dev/null
+ let rc=$res
+ COUNTER=$(expr $COUNTER + 1)
+ done
+ cat log.txt
+ if test $rc -eq 0; then
+ successln "Query successful on peer0.org${ORG} on channel '$CHANNEL_NAME'"
+ else
+ fatalln "After $MAX_RETRY attempts, Query result on peer0.org${ORG} is INVALID!"
+ fi
+}
+
+## package the chaincode
+packageChaincode
+
+## Install chaincode on peer0.org1 and peer0.org2
+infoln "Installing chaincode on peer0.org1..."
+installChaincode 1
+infoln "Installing chaincode on peer0.org2..."
+installChaincode 2
+
+## query whether the chaincode is installed
+queryInstalled 1
+
+## approve the definition for org1
+approveForMyOrg 1
+
+## check whether the chaincode definition is ready to be committed
+## expect org1 to have approved and org2 not to
+checkCommitReadiness 1 "\"Org1MSP\": true" "\"Org2MSP\": false"
+checkCommitReadiness 2 "\"Org1MSP\": true" "\"Org2MSP\": false"
+
+## now approve also for org2
+approveForMyOrg 2
+
+## check whether the chaincode definition is ready to be committed
+## expect them both to have approved
+checkCommitReadiness 1 "\"Org1MSP\": true" "\"Org2MSP\": true"
+checkCommitReadiness 2 "\"Org1MSP\": true" "\"Org2MSP\": true"
+
+## now that we know for sure both orgs have approved, commit the definition
+commitChaincodeDefinition 1 2
+
+## query on both orgs to see that the definition committed successfully
+queryCommitted 1
+queryCommitted 2
+
+## Invoke the chaincode - this does require that the chaincode have the 'initLedger'
+## method defined
+if [ "$CC_INIT_FCN" = "NA" ]; then
+ infoln "Chaincode initialization is not required"
+else
+ chaincodeInvokeInit 1 2
+fi
+
+exit 0
diff --git a/fabric/network/scripts/envVar.sh b/fabric/network/scripts/envVar.sh
@@ -0,0 +1,115 @@
+#!/usr/bin/env bash
+#
+# Copyright IBM Corp All Rights Reserved
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+# This is a collection of bash functions used by different scripts
+
+# imports
+. scripts/utils.sh
+
+export CORE_PEER_TLS_ENABLED=true
+export ORDERER_CA=${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
+export PEER0_ORG1_CA=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
+export PEER0_ORG2_CA=${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
+export PEER0_ORG3_CA=${PWD}/organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/ca.crt
+export PEER0_ORG4_CA=${PWD}/organizations/peerOrganizations/org4.example.com/peers/peer0.org4.example.com/tls/ca.crt
+
+export ORDERER_ADMIN_TLS_SIGN_CERT=${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.crt
+export ORDERER_ADMIN_TLS_PRIVATE_KEY=${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.key
+
+# Set environment variables for the peer org
+setGlobals() {
+ local USING_ORG=""
+ if [ -z "$OVERRIDE_ORG" ]; then
+ USING_ORG=$1
+ else
+ USING_ORG="${OVERRIDE_ORG}"
+ fi
+ infoln "Using organization ${USING_ORG}"
+ if [ $USING_ORG -eq 1 ]; then
+ export CORE_PEER_LOCALMSPID="Org1MSP"
+ export CORE_PEER_TLS_ROOTCERT_FILE=$PEER0_ORG1_CA
+ export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
+ export CORE_PEER_ADDRESS=localhost:7051
+ elif [ $USING_ORG -eq 2 ]; then
+ export CORE_PEER_LOCALMSPID="Org2MSP"
+ export CORE_PEER_TLS_ROOTCERT_FILE=$PEER0_ORG2_CA
+ export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
+ export CORE_PEER_ADDRESS=localhost:9051
+
+ elif [ $USING_ORG -eq 3 ]; then
+ export CORE_PEER_LOCALMSPID="Org3MSP"
+ export CORE_PEER_TLS_ROOTCERT_FILE=$PEER0_ORG3_CA
+ export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org3.example.com/users/Admin@org3.example.com/msp
+ export CORE_PEER_ADDRESS=localhost:11051
+ elif [ $USING_ORG -eq 4 ]; then
+ export CORE_PEER_LOCALMSPID="Org4MSP"
+ export CORE_PEER_TLS_ROOTCERT_FILE=$PEER0_ORG4_CA
+ export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org4.example.com/users/Admin@org4.example.com/msp
+ export CORE_PEER_ADDRESS=localhost:13051
+ else
+ errorln "ORG Unknown"
+ fi
+
+ if [ "$VERBOSE" == "true" ]; then
+ env | grep CORE
+ fi
+}
+
+# Set environment variables for use in the CLI container
+setGlobalsCLI() {
+ setGlobals $1
+
+ local USING_ORG=""
+ if [ -z "$OVERRIDE_ORG" ]; then
+ USING_ORG=$1
+ else
+ USING_ORG="${OVERRIDE_ORG}"
+ fi
+ if [ $USING_ORG -eq 1 ]; then
+ export CORE_PEER_ADDRESS=peer0.org1.example.com:7051
+ elif [ $USING_ORG -eq 2 ]; then
+ export CORE_PEER_ADDRESS=peer0.org2.example.com:9051
+ elif [ $USING_ORG -eq 3 ]; then
+ export CORE_PEER_ADDRESS=peer0.org3.example.com:11051
+ elif [ $USING_ORG -eq 4 ]; then
+ export CORE_PEER_ADDRESS=peer0.org4.example.com:13051
+ else
+ errorln "ORG Unknown"
+ fi
+}
+
+# parsePeerConnectionParameters $@
+# Helper function that sets the peer connection parameters for a chaincode
+# operation
+parsePeerConnectionParameters() {
+ PEER_CONN_PARMS=()
+ PEERS=""
+ while [ "$#" -gt 0 ]; do
+ setGlobals $1
+ PEER="peer0.org$1"
+ ## Set peer addresses
+ if [ -z "$PEERS" ]
+ then
+ PEERS="$PEER"
+ else
+ PEERS="$PEERS $PEER"
+ fi
+ PEER_CONN_PARMS=("${PEER_CONN_PARMS[@]}" --peerAddresses $CORE_PEER_ADDRESS)
+ ## Set path to TLS certificate
+ CA=PEER0_ORG$1_CA
+ TLSINFO=(--tlsRootCertFiles "${!CA}")
+ PEER_CONN_PARMS=("${PEER_CONN_PARMS[@]}" "${TLSINFO[@]}")
+ # shift by one to get to the next organization
+ shift
+ done
+}
+
+verifyResult() {
+ if [ $1 -ne 0 ]; then
+ fatalln "$2"
+ fi
+}
diff --git a/fabric/network/scripts/org3-scripts/joinChannel.sh b/fabric/network/scripts/org3-scripts/joinChannel.sh
@@ -0,0 +1,70 @@
+#!/usr/bin/env bash
+#
+# Copyright IBM Corp. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+# This script is designed to be run in the cli container as the
+# second step of the EYFN tutorial. It joins the org3 peers to the
+# channel previously setup in the BYFN tutorial and install the
+# chaincode as version 2.0 on peer0.org3.
+#
+
+CHANNEL_NAME="$1"
+DELAY="$2"
+TIMEOUT="$3"
+VERBOSE="$4"
+: ${CHANNEL_NAME:="mychannel"}
+: ${DELAY:="3"}
+: ${TIMEOUT:="10"}
+: ${VERBOSE:="false"}
+COUNTER=1
+MAX_RETRY=5
+
+# import environment variables
+. scripts/envVar.sh
+
+# joinChannel ORG
+joinChannel() {
+ ORG=$1
+ local rc=1
+ local COUNTER=1
+ ## Sometimes Join takes time, hence retry
+ while [ $rc -ne 0 -a $COUNTER -lt $MAX_RETRY ] ; do
+ sleep $DELAY
+ set -x
+ peer channel join -b $BLOCKFILE >&log.txt
+ res=$?
+ { set +x; } 2>/dev/null
+ let rc=$res
+ COUNTER=$(expr $COUNTER + 1)
+ done
+ cat log.txt
+ verifyResult $res "After $MAX_RETRY attempts, peer0.org${ORG} has failed to join channel '$CHANNEL_NAME' "
+}
+
+setAnchorPeer() {
+ ORG=$1
+ scripts/setAnchorPeer.sh $ORG $CHANNEL_NAME
+}
+
+setGlobalsCLI 3
+BLOCKFILE="${CHANNEL_NAME}.block"
+
+echo "Fetching channel config block from orderer..."
+set -x
+peer channel fetch 0 $BLOCKFILE -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com -c $CHANNEL_NAME --tls --cafile "$ORDERER_CA" >&log.txt
+res=$?
+{ set +x; } 2>/dev/null
+cat log.txt
+verifyResult $res "Fetching config block from orderer has failed"
+
+infoln "Joining org3 peer to the channel..."
+joinChannel 3
+
+infoln "Setting anchor peer for org3..."
+setAnchorPeer 3
+
+successln "Channel '$CHANNEL_NAME' joined"
+successln "Org3 peer successfully added to network"
diff --git a/fabric/network/scripts/org3-scripts/updateChannelConfig.sh b/fabric/network/scripts/org3-scripts/updateChannelConfig.sh
@@ -0,0 +1,52 @@
+#!/usr/bin/env bash
+#
+# Copyright IBM Corp. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+# This script is designed to be run in the cli container as the
+# first step of the EYFN tutorial. It creates and submits a
+# configuration transaction to add org3 to the test network
+#
+
+CHANNEL_NAME="$1"
+DELAY="$2"
+TIMEOUT="$3"
+VERBOSE="$4"
+: ${CHANNEL_NAME:="mychannel"}
+: ${DELAY:="3"}
+: ${TIMEOUT:="10"}
+: ${VERBOSE:="false"}
+COUNTER=1
+MAX_RETRY=5
+
+
+# imports
+. scripts/envVar.sh
+. scripts/configUpdate.sh
+. scripts/utils.sh
+
+infoln "Creating config transaction to add org3 to network"
+
+# Fetch the config for the channel, writing it to config.json
+fetchChannelConfig 1 ${CHANNEL_NAME} config.json
+
+# Modify the configuration to append the new org
+set -x
+jq -s '.[0] * {"channel_group":{"groups":{"Application":{"groups": {"Org3MSP":.[1]}}}}}' config.json ./organizations/peerOrganizations/org3.example.com/org3.json > modified_config.json
+{ set +x; } 2>/dev/null
+
+# Compute a config update, based on the differences between config.json and modified_config.json, write it as a transaction to org3_update_in_envelope.pb
+createConfigUpdate ${CHANNEL_NAME} config.json modified_config.json org3_update_in_envelope.pb
+
+infoln "Signing config transaction"
+signConfigtxAsPeerOrg 1 org3_update_in_envelope.pb
+
+infoln "Submitting transaction from a different peer (peer0.org2) which also signs it"
+setGlobals 2
+set -x
+peer channel update -f org3_update_in_envelope.pb -c ${CHANNEL_NAME} -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$ORDERER_CA"
+{ set +x; } 2>/dev/null
+
+successln "Config transaction to add org3 to network submitted"
diff --git a/fabric/network/scripts/org4-scripts/joinChannel.sh b/fabric/network/scripts/org4-scripts/joinChannel.sh
@@ -0,0 +1,70 @@
+#!/usr/bin/env bash
+#
+# Copyright IBM Corp. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+# This script is designed to be run in the cli container as the
+# second step of the EYFN tutorial. It joins the org4 peers to the
+# channel previously setup in the BYFN tutorial and install the
+# chaincode as version 2.0 on peer0.org4.
+#
+
+CHANNEL_NAME="$1"
+DELAY="$2"
+TIMEOUT="$3"
+VERBOSE="$4"
+: ${CHANNEL_NAME:="mychannel"}
+: ${DELAY:="3"}
+: ${TIMEOUT:="10"}
+: ${VERBOSE:="false"}
+COUNTER=1
+MAX_RETRY=5
+
+# import environment variables
+. scripts/envVar.sh
+
+# joinChannel ORG
+joinChannel() {
+ ORG=$1
+ local rc=1
+ local COUNTER=1
+ ## Sometimes Join takes time, hence retry
+ while [ $rc -ne 0 -a $COUNTER -lt $MAX_RETRY ] ; do
+ sleep $DELAY
+ set -x
+ peer channel join -b $BLOCKFILE >&log.txt
+ res=$?
+ { set +x; } 2>/dev/null
+ let rc=$res
+ COUNTER=$(expr $COUNTER + 1)
+ done
+ cat log.txt
+ verifyResult $res "After $MAX_RETRY attempts, peer0.org${ORG} has failed to join channel '$CHANNEL_NAME' "
+}
+
+setAnchorPeer() {
+ ORG=$1
+ scripts/setAnchorPeer.sh $ORG $CHANNEL_NAME
+}
+
+setGlobalsCLI 4
+BLOCKFILE="${CHANNEL_NAME}.block"
+
+echo "Fetching channel config block from orderer..."
+set -x
+peer channel fetch 0 $BLOCKFILE -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com -c $CHANNEL_NAME --tls --cafile "$ORDERER_CA" >&log.txt
+res=$?
+{ set +x; } 2>/dev/null
+cat log.txt
+verifyResult $res "Fetching config block from orderer has failed"
+
+infoln "Joining org4 peer to the channel..."
+joinChannel 4
+
+infoln "Setting anchor peer for org4..."
+setAnchorPeer 4
+
+successln "Channel '$CHANNEL_NAME' joined"
+successln "Org4 peer successfully added to network"
diff --git a/fabric/network/scripts/org4-scripts/updateChannelConfig.sh b/fabric/network/scripts/org4-scripts/updateChannelConfig.sh
@@ -0,0 +1,53 @@
+#!/usr/bin/env bash
+#
+# Copyright IBM Corp. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+# This script is designed to be run in the cli container as the
+# first step of the EYFN tutorial. It creates and submits a
+# configuration transaction to add org4 to the test network
+#
+
+CHANNEL_NAME="$1"
+DELAY="$2"
+TIMEOUT="$3"
+VERBOSE="$4"
+: ${CHANNEL_NAME:="mychannel"}
+: ${DELAY:="3"}
+: ${TIMEOUT:="10"}
+: ${VERBOSE:="false"}
+COUNTER=1
+MAX_RETRY=5
+
+
+# imports
+. scripts/envVar.sh
+. scripts/configUpdate.sh
+. scripts/utils.sh
+
+infoln "Creating config transaction to add org4 to network"
+
+# Fetch the config for the channel, writing it to config.json
+fetchChannelConfig 1 ${CHANNEL_NAME} config.json
+
+# Modify the configuration to append the new org
+set -x
+jq -s '.[0] * {"channel_group":{"groups":{"Application":{"groups": {"Org4MSP":.[1]}}}}}' config.json ./organizations/peerOrganizations/org4.example.com/org4.json > modified_config.json
+{ set +x; } 2>/dev/null
+
+# Compute a config update, based on the differences between config.json and modified_config.json, write it as a transaction to org4_update_in_envelope.pb
+createConfigUpdate ${CHANNEL_NAME} config.json modified_config.json org4_update_in_envelope.pb
+
+infoln "Signing config transaction"
+signConfigtxAsPeerOrg 1 org4_update_in_envelope.pb
+signConfigtxAsPeerOrg 3 org4_update_in_envelope.pb
+
+infoln "Submitting transaction from a different peer (peer0.org2) which also signs it"
+setGlobals 2
+set -x
+peer channel update -f org4_update_in_envelope.pb -c ${CHANNEL_NAME} -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$ORDERER_CA"
+{ set +x; } 2>/dev/null
+
+successln "Config transaction to add org4 to network submitted"
diff --git a/fabric/network/scripts/setAnchorPeer.sh b/fabric/network/scripts/setAnchorPeer.sh
@@ -0,0 +1,61 @@
+#!/usr/bin/env bash
+#
+# Copyright IBM Corp. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+# import utils
+. scripts/envVar.sh
+. scripts/configUpdate.sh
+
+
+# NOTE: this must be run in a CLI container since it requires jq and configtxlator
+createAnchorPeerUpdate() {
+ infoln "Fetching channel config for channel $CHANNEL_NAME"
+ fetchChannelConfig $ORG $CHANNEL_NAME ${CORE_PEER_LOCALMSPID}config.json
+
+ infoln "Generating anchor peer update transaction for Org${ORG} on channel $CHANNEL_NAME"
+
+ if [ $ORG -eq 1 ]; then
+ HOST="peer0.org1.example.com"
+ PORT=7051
+ elif [ $ORG -eq 2 ]; then
+ HOST="peer0.org2.example.com"
+ PORT=9051
+ elif [ $ORG -eq 3 ]; then
+ HOST="peer0.org3.example.com"
+ PORT=11051
+ elif [ $ORG -eq 4 ]; then
+ HOST="peer0.org4.example.com"
+ PORT=13051
+ else
+ errorln "Org${ORG} unknown"
+ fi
+
+ set -x
+ # Modify the configuration to append the anchor peer
+ jq '.channel_group.groups.Application.groups.'${CORE_PEER_LOCALMSPID}'.values += {"AnchorPeers":{"mod_policy": "Admins","value":{"anchor_peers": [{"host": "'$HOST'","port": '$PORT'}]},"version": "0"}}' ${CORE_PEER_LOCALMSPID}config.json > ${CORE_PEER_LOCALMSPID}modified_config.json
+ { set +x; } 2>/dev/null
+
+ # Compute a config update, based on the differences between
+ # {orgmsp}config.json and {orgmsp}modified_config.json, write
+ # it as a transaction to {orgmsp}anchors.tx
+ createConfigUpdate ${CHANNEL_NAME} ${CORE_PEER_LOCALMSPID}config.json ${CORE_PEER_LOCALMSPID}modified_config.json ${CORE_PEER_LOCALMSPID}anchors.tx
+}
+
+updateAnchorPeer() {
+ peer channel update -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com -c $CHANNEL_NAME -f ${CORE_PEER_LOCALMSPID}anchors.tx --tls --cafile "$ORDERER_CA" >&log.txt
+ res=$?
+ cat log.txt
+ verifyResult $res "Anchor peer update failed"
+ successln "Anchor peer set for org '$CORE_PEER_LOCALMSPID' on channel '$CHANNEL_NAME'"
+}
+
+ORG=$1
+CHANNEL_NAME=$2
+setGlobalsCLI $ORG
+
+createAnchorPeerUpdate
+
+updateAnchorPeer
diff --git a/fabric/network/scripts/utils.sh b/fabric/network/scripts/utils.sh
@@ -0,0 +1,152 @@
+#!/usr/bin/env bash
+
+C_RESET='\033[0m'
+C_RED='\033[0;31m'
+C_GREEN='\033[0;32m'
+C_BLUE='\033[0;34m'
+C_YELLOW='\033[1;33m'
+
+# Print the usage message
+function printHelp() {
+ USAGE="$1"
+ if [ "$USAGE" == "up" ]; then
+ println "Usage: "
+ println " network.sh \033[0;32mup\033[0m [Flags]"
+ println
+ println " Flags:"
+ println " -ca <use CAs> - Use Certificate Authorities to generate network crypto material"
+ println " -c <channel name> - Name of channel to create (defaults to \"mychannel\")"
+ println " -s <dbtype> - Peer state database to deploy: goleveldb (default) or couchdb"
+ println " -r <max retry> - CLI times out after certain number of attempts (defaults to 5)"
+ println " -d <delay> - CLI delays for a certain number of seconds (defaults to 3)"
+ println " -verbose - Verbose mode"
+ println
+ println " -h - Print this message"
+ println
+ println " Possible Mode and flag combinations"
+ println " \033[0;32mup\033[0m -ca -r -d -s -verbose"
+ println " \033[0;32mup createChannel\033[0m -ca -c -r -d -s -verbose"
+ println
+ println " Examples:"
+ println " network.sh up createChannel -ca -c mychannel -s couchdb "
+ elif [ "$USAGE" == "createChannel" ]; then
+ println "Usage: "
+ println " network.sh \033[0;32mcreateChannel\033[0m [Flags]"
+ println
+ println " Flags:"
+ println " -c <channel name> - Name of channel to create (defaults to \"mychannel\")"
+ println " -r <max retry> - CLI times out after certain number of attempts (defaults to 5)"
+ println " -d <delay> - CLI delays for a certain number of seconds (defaults to 3)"
+ println " -verbose - Verbose mode"
+ println
+ println " -h - Print this message"
+ println
+ println " Possible Mode and flag combinations"
+ println " \033[0;32mcreateChannel\033[0m -c -r -d -verbose"
+ println
+ println " Examples:"
+ println " network.sh createChannel -c channelName"
+ elif [ "$USAGE" == "deployCC" ]; then
+ println "Usage: "
+ println " network.sh \033[0;32mdeployCC\033[0m [Flags]"
+ println
+ println " Flags:"
+ println " -c <channel name> - Name of channel to deploy chaincode to"
+ println " -ccn <name> - Chaincode name."
+ println " -ccl <language> - Programming language of chaincode to deploy: go, java, javascript, typescript"
+ println " -ccv <version> - Chaincode version. 1.0 (default), v2, version3.x, etc"
+ println " -ccs <sequence> - Chaincode definition sequence. Must be an integer, 1 (default), 2, 3, etc"
+ println " -ccp <path> - File path to the chaincode."
+ println " -ccep <policy> - (Optional) Chaincode endorsement policy using signature policy syntax. The default policy requires an endorsement from Org1 and Org2"
+ println " -cccg <collection-config> - (Optional) File path to private data collections configuration file"
+ println " -cci <fcn name> - (Optional) Name of chaincode initialization function. When a function is provided, the execution of init will be requested and the function will be invoked."
+ println
+ println " -h - Print this message"
+ println
+ println " Possible Mode and flag combinations"
+ println " \033[0;32mdeployCC\033[0m -ccn -ccl -ccv -ccs -ccp -cci -r -d -verbose"
+ println
+ println " Examples:"
+ println " network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-javascript/ ./ -ccl javascript"
+ println " network.sh deployCC -ccn mychaincode -ccp ./user/mychaincode -ccv 1 -ccl javascript"
+ else
+ println "Usage: "
+ println " network.sh <Mode> [Flags]"
+ println " Modes:"
+ println " \033[0;32mup\033[0m - Bring up Fabric orderer and peer nodes. No channel is created"
+ println " \033[0;32mup createChannel\033[0m - Bring up fabric network with one channel"
+ println " \033[0;32mcreateChannel\033[0m - Create and join a channel after the network is created"
+ println " \033[0;32mdeployCC\033[0m - Deploy a chaincode to a channel (defaults to asset-transfer-basic)"
+ println " \033[0;32mdown\033[0m - Bring down the network"
+ println
+ println " Flags:"
+ println " Used with \033[0;32mnetwork.sh up\033[0m, \033[0;32mnetwork.sh createChannel\033[0m:"
+ println " -ca <use CAs> - Use Certificate Authorities to generate network crypto material"
+ println " -c <channel name> - Name of channel to create (defaults to \"mychannel\")"
+ println " -s <dbtype> - Peer state database to deploy: goleveldb (default) or couchdb"
+ println " -r <max retry> - CLI times out after certain number of attempts (defaults to 5)"
+ println " -d <delay> - CLI delays for a certain number of seconds (defaults to 3)"
+ println " -verbose - Verbose mode"
+ println
+ println " Used with \033[0;32mnetwork.sh deployCC\033[0m"
+ println " -c <channel name> - Name of channel to deploy chaincode to"
+ println " -ccn <name> - Chaincode name."
+ println " -ccl <language> - Programming language of the chaincode to deploy: go, java, javascript, typescript"
+ println " -ccv <version> - Chaincode version. 1.0 (default), v2, version3.x, etc"
+ println " -ccs <sequence> - Chaincode definition sequence. Must be an integer, 1 (default), 2, 3, etc"
+ println " -ccp <path> - File path to the chaincode."
+ println " -ccep <policy> - (Optional) Chaincode endorsement policy using signature policy syntax. The default policy requires an endorsement from Org1 and Org2"
+ println " -cccg <collection-config> - (Optional) File path to private data collections configuration file"
+ println " -cci <fcn name> - (Optional) Name of chaincode initialization function. When a function is provided, the execution of init will be requested and the function will be invoked."
+ println
+ println " -h - Print this message"
+ println
+ println " Possible Mode and flag combinations"
+ println " \033[0;32mup\033[0m -ca -r -d -s -verbose"
+ println " \033[0;32mup createChannel\033[0m -ca -c -r -d -s -verbose"
+ println " \033[0;32mcreateChannel\033[0m -c -r -d -verbose"
+ println " \033[0;32mdeployCC\033[0m -ccn -ccl -ccv -ccs -ccp -cci -r -d -verbose"
+ println
+ println " Examples:"
+ println " network.sh up createChannel -ca -c mychannel -s couchdb"
+ println " network.sh createChannel -c channelName"
+ println " network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-javascript/ -ccl javascript"
+ println " network.sh deployCC -ccn mychaincode -ccp ./user/mychaincode -ccv 1 -ccl javascript"
+ fi
+}
+
+# println echos string
+function println() {
+ echo -e "$1"
+}
+
+# errorln echos i red color
+function errorln() {
+ println "${C_RED}${1}${C_RESET}"
+}
+
+# successln echos in green color
+function successln() {
+ println "${C_GREEN}${1}${C_RESET}"
+}
+
+# infoln echos in blue color
+function infoln() {
+ println "${C_BLUE}${1}${C_RESET}"
+}
+
+# warnln echos in yellow color
+function warnln() {
+ println "${C_YELLOW}${1}${C_RESET}"
+}
+
+# fatalln echos in red color and exits with fail status
+function fatalln() {
+ errorln "$1"
+ exit 1
+}
+
+export -f errorln
+export -f successln
+export -f infoln
+export -f warnln
diff --git a/frontend/package.json b/frontend/package.json
@@ -1,5 +1,6 @@
{
"name": "matiru-app",
+ "description": "Matiru Frontend",
"version": "1.0.0",
"main": "node_modules/expo/AppEntry.js",
"scripts": {