matiru

Blockchain-Based Supply Chain Transparency for Agricultural Produce
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:
MREADME.md | 53+++++++++++++++++++++++++++++++++++++----------------
Mbackend/.gitignore | 2+-
Mbackend/initUsers.js | 90++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Mbackend/package.json | 3++-
Mbackend/server.js | 145++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Dchaincode/collections_config.json | 11-----------
Dchaincode/index.js | 540-------------------------------------------------------------------------------
Dchaincode/package.json | 13-------------
Afabric/.gitignore | 1+
Afabric/chaincode/index.js | 569+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rchaincode/package-lock.json -> fabric/chaincode/package-lock.json | 0
Afabric/chaincode/package.json | 13+++++++++++++
Afabric/network/.gitignore | 15+++++++++++++++
Afabric/network/addOrg3/addOrg3.sh | 263+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/addOrg3/ccp-generate.sh | 36++++++++++++++++++++++++++++++++++++
Afabric/network/addOrg3/ccp-template.json | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/addOrg3/ccp-template.yaml | 34++++++++++++++++++++++++++++++++++
Afabric/network/addOrg3/configtx.yaml | 38++++++++++++++++++++++++++++++++++++++
Afabric/network/addOrg3/docker/docker-compose-ca-org3.yaml | 27+++++++++++++++++++++++++++
Afabric/network/addOrg3/docker/docker-compose-couch-org3.yaml | 42++++++++++++++++++++++++++++++++++++++++++
Afabric/network/addOrg3/docker/docker-compose-org3.yaml | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/addOrg3/fabric-ca/org3/fabric-ca-server-config.yaml | 406+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/addOrg3/fabric-ca/registerEnroll.sh | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/addOrg3/org3-crypto.yaml | 21+++++++++++++++++++++
Afabric/network/addOrg4/addOrg4.sh | 263+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/addOrg4/ccp-generate.sh | 36++++++++++++++++++++++++++++++++++++
Afabric/network/addOrg4/ccp-template.json | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/addOrg4/ccp-template.yaml | 34++++++++++++++++++++++++++++++++++
Afabric/network/addOrg4/configtx.yaml | 38++++++++++++++++++++++++++++++++++++++
Afabric/network/addOrg4/docker/docker-compose-ca-org4.yaml | 27+++++++++++++++++++++++++++
Afabric/network/addOrg4/docker/docker-compose-couch-org4.yaml | 42++++++++++++++++++++++++++++++++++++++++++
Afabric/network/addOrg4/docker/docker-compose-org4.yaml | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/addOrg4/fabric-ca/org4/fabric-ca-server-config.yaml | 406+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/addOrg4/fabric-ca/registerEnroll.sh | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/addOrg4/org4-crypto.yaml | 21+++++++++++++++++++++
Afabric/network/configtx/configtx.yaml | 319+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/docker/docker-compose-ca.yaml | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/docker/docker-compose-couch.yaml | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/docker/docker-compose-test-net.yaml | 156+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/network.sh | 478+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/organizations/ccp-generate.sh | 45+++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/organizations/ccp-template.json | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/organizations/ccp-template.yaml | 35+++++++++++++++++++++++++++++++++++
Afabric/network/organizations/cryptogen/crypto-config-orderer.yaml | 22++++++++++++++++++++++
Afabric/network/organizations/cryptogen/crypto-config-org1.yaml | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/organizations/cryptogen/crypto-config-org2.yaml | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/organizations/fabric-ca/registerEnroll.sh | 226+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/scripts/configUpdate.sh | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/scripts/createChannel.sh | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/scripts/deployCC.sh | 355+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/scripts/deployCC_2org.sh | 328+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/scripts/envVar.sh | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/scripts/org3-scripts/joinChannel.sh | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/scripts/org3-scripts/updateChannelConfig.sh | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/scripts/org4-scripts/joinChannel.sh | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/scripts/org4-scripts/updateChannelConfig.sh | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/scripts/setAnchorPeer.sh | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afabric/network/scripts/utils.sh | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mfrontend/package.json | 1+
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": {