From ada8de36776a09cf34e7143aa600b7cd1d395967 Mon Sep 17 00:00:00 2001 From: nXtCyberNet Date: Wed, 11 Mar 2026 02:12:24 +0530 Subject: [PATCH 01/11] create the asset-transfer in go implemetation Signed-off-by: nXtCyberNet --- .../contracts/asset-transfer-go/Dockerfile | 56 ++++ .../asset-transfer-chaincode-vars.yml | 11 + .../docker/docker-entrypoint.sh | 26 ++ .../contracts/asset-transfer-go/go.mod | 28 ++ .../contracts/asset-transfer-go/go.sum | 61 ++++ .../contracts/asset-transfer-go/src/asset.go | 22 ++ .../asset-transfer-go/src/asset_transfer.go | 307 ++++++++++++++++++ .../contracts/asset-transfer-go/src/main.go | 22 ++ 8 files changed, 533 insertions(+) create mode 100644 full-stack-asset-transfer-guide/contracts/asset-transfer-go/Dockerfile create mode 100644 full-stack-asset-transfer-guide/contracts/asset-transfer-go/asset-transfer-chaincode-vars.yml create mode 100644 full-stack-asset-transfer-guide/contracts/asset-transfer-go/docker/docker-entrypoint.sh create mode 100644 full-stack-asset-transfer-guide/contracts/asset-transfer-go/go.mod create mode 100644 full-stack-asset-transfer-guide/contracts/asset-transfer-go/go.sum create mode 100644 full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset.go create mode 100644 full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset_transfer.go create mode 100644 full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/main.go diff --git a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/Dockerfile b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/Dockerfile new file mode 100644 index 0000000000..272d6ed380 --- /dev/null +++ b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/Dockerfile @@ -0,0 +1,56 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# +#Stage 1 – Builder image +FROM golang:1.23-alpine AS builder + +RUN apk add --no-cache git + +WORKDIR /build + + +COPY go.mod go.sum ./ +RUN go mod download + +COPY src/ ./src/ +RUN CGO_ENABLED=0 go build -v -o /build/chaincode ./src/... + + +# Stage 2 – Chaincode-as-a-Service (CaaS) image +FROM alpine:3.20 AS ccaas + +ARG TARGETARCH +ARG CC_SERVER_PORT=9999 + +# tini gives us proper PID-1 signal handling +ENV TINI_VERSION=v0.19.0 +ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-static-${TARGETARCH} /tini +RUN chmod +x /tini + +RUN addgroup -S chaincode && adduser -S chaincode -G chaincode +WORKDIR /home/chaincode + +COPY --from=builder /build/chaincode ./chaincode +COPY docker/docker-entrypoint.sh ./docker-entrypoint.sh +RUN chmod +x ./docker-entrypoint.sh + +ENV PORT=${CC_SERVER_PORT} +EXPOSE ${CC_SERVER_PORT} + +USER chaincode +ENTRYPOINT ["/tini", "--", "./docker-entrypoint.sh"] + + + +# Stage 3 – k8s builder image +FROM alpine:3.20 AS k8s + +RUN addgroup -S chaincode && adduser -S chaincode -G chaincode +WORKDIR /home/chaincode + +COPY --from=builder /build/chaincode ./chaincode +COPY docker/docker-entrypoint.sh ./docker-entrypoint.sh +RUN chmod +x ./docker-entrypoint.sh + +USER chaincode +CMD ["./docker-entrypoint.sh"] diff --git a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/asset-transfer-chaincode-vars.yml b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/asset-transfer-chaincode-vars.yml new file mode 100644 index 0000000000..1c83caeae9 --- /dev/null +++ b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/asset-transfer-chaincode-vars.yml @@ -0,0 +1,11 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# +--- +smart_contract_name: "asset-transfer" +smart_contract_version: "1.0.0" +smart_contract_sequence: 1 +smart_contract_package: "asset-transfer.tgz" +# smart_contract_constructor: "" +smart_contract_endorsement_policy: "" +smart_contract_collections_file: "" diff --git a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/docker/docker-entrypoint.sh b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/docker/docker-entrypoint.sh new file mode 100644 index 0000000000..6ad2eca55b --- /dev/null +++ b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/docker/docker-entrypoint.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +set -euo pipefail + +: "${CORE_PEER_TLS_ENABLED:=false}" + +if [[ ! -v CHAINCODE_SERVER_ADDRESS ]]; then + # Legacy peer-managed mode: binary acts as a regular chaincode process. + exec ./chaincode --peer.address "${CORE_PEER_ADDRESS}" + +elif [[ "${CORE_PEER_TLS_ENABLED,,}" == "true" ]]; then + # CaaS + TLS + exec ./chaincode \ + --chaincode.address "${CHAINCODE_SERVER_ADDRESS}" \ + --chaincode.id "${CHAINCODE_ID}" \ + --chaincode.tls.enabled true \ + --chaincode.tls.key.file "${CHAINCODE_TLS_KEY:-/hyperledger/privatekey.pem}" \ + --chaincode.tls.cert.file "${CHAINCODE_TLS_CERT:-/hyperledger/cert.pem}" \ + --chaincode.tls.clientCaCert.file "${CHAINCODE_TLS_CLIENT_CACERT:-/hyperledger/rootcert.pem}" + +else + # CaaS without TLS + exec ./chaincode \ + --chaincode.address "${CHAINCODE_SERVER_ADDRESS}" \ + --chaincode.id "${CHAINCODE_ID}" +fi diff --git a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/go.mod b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/go.mod new file mode 100644 index 0000000000..83fba2d131 --- /dev/null +++ b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/go.mod @@ -0,0 +1,28 @@ +module asset + +go 1.23.0 + +require ( + github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0 + github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0 +) + +require ( + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/spec v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect + google.golang.org/grpc v1.67.0 // indirect + google.golang.org/protobuf v1.36.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/go.sum b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/go.sum new file mode 100644 index 0000000000..1459ca55c9 --- /dev/null +++ b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/go.sum @@ -0,0 +1,61 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0 h1:IhkHfrl5X/fVnmB6pWeCYCdIJRi9bxj+WTnVN8DtW3c= +github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0/go.mod h1:PHHaFffjw7p7n9bmCfcm7RqDqYdivNEsJdiNIKZo5Lk= +github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0 h1:rmUoBmciB0GL/miqcbJmJbgp5QTWoJUrZo+CNxrNLF4= +github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0/go.mod h1:FeWeO/jwGjiME7ak3GufqKIcwkejtzrDG4QxbfKydWs= +github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 h1:YJrd+gMaeY0/vsN0aS0QkEKTivGoUnSRIXxGJ7KI+Pc= +github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4/go.mod h1:bau/6AJhvEcu9GKKYHlDXAxXKzYNfhP6xu2GXuxEcFk= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= +google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset.go b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset.go new file mode 100644 index 0000000000..b777b32fdf --- /dev/null +++ b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset.go @@ -0,0 +1,22 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + */ + +package main + +// OwnerIdentifier represents the owner of an asset with their organisation MSP ID and user identifier. +// Fields are lowercase to match the TypeScript serialisation format. +type OwnerIdentifier struct { + Org string `json:"org"` + User string `json:"user"` +} + +// Asset describes the details of an asset stored in the world state. +// Fields are defined in alphabetical order to produce deterministic JSON serialisation. +type Asset struct { + AppraisedValue int `json:"AppraisedValue"` + Color string `json:"Color"` + ID string `json:"ID"` + Owner string `json:"Owner"` // JSON-encoded OwnerIdentifier + Size int `json:"Size"` +} diff --git a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset_transfer.go b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset_transfer.go new file mode 100644 index 0000000000..415826939b --- /dev/null +++ b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset_transfer.go @@ -0,0 +1,307 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + */ + +package main + +import ( + "encoding/json" + "fmt" + "log" + + "github.com/hyperledger/fabric-chaincode-go/v2/pkg/cid" + "github.com/hyperledger/fabric-chaincode-go/v2/pkg/statebased" + "github.com/hyperledger/fabric-contract-api-go/v2/contractapi" +) + +// SmartContract provides functions for managing assets. +type SmartContract struct { + contractapi.Contract +} + +func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error { + exists, err := s.AssetExists(ctx, id) + if err != nil { + return err + } + if exists { + return fmt.Errorf("the asset %s already exists", id) + } + + ownerID, err := clientIdentifier(ctx, owner) + if err != nil { + return err + } + ownerJSON, err := json.Marshal(ownerID) + if err != nil { + return err + } + + asset := Asset{ + AppraisedValue: appraisedValue, + Color: color, + ID: id, + Owner: string(ownerJSON), + Size: size, + } + assetBytes, err := json.Marshal(asset) + if err != nil { + return err + } + + if err := ctx.GetStub().PutState(id, assetBytes); err != nil { + return err + } + + mspID, err := cid.GetMSPID(ctx.GetStub()) + if err != nil { + return err + } + if err := setEndorsingOrgs(ctx, id, mspID); err != nil { + return err + } + + return ctx.GetStub().SetEvent("CreateAsset", assetBytes) +} + +func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) { + assetBytes, err := readAsset(ctx, id) + if err != nil { + return nil, err + } + + var asset Asset + if err := json.Unmarshal(assetBytes, &asset); err != nil { + return nil, err + } + + return &asset, nil +} + +// UpdateAsset updates color, size, and appraised value of an existing asset. +// The asset owner cannot be changed here; use TransferAsset instead. +func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, appraisedValue int) error { + assetBytes, err := readAsset(ctx, id) + if err != nil { + return err + } + + var existing Asset + if err := json.Unmarshal(assetBytes, &existing); err != nil { + return err + } + + ok, err := hasWritePermission(ctx, &existing) + if err != nil { + return err + } + if !ok { + return fmt.Errorf("only owner can update assets") + } + + // Owner is intentionally preserved; use TransferAsset to change owner. + existing.Color = color + existing.Size = size + existing.AppraisedValue = appraisedValue + + updatedBytes, err := json.Marshal(existing) + if err != nil { + return err + } + + if err := ctx.GetStub().PutState(id, updatedBytes); err != nil { + return err + } + + mspID, err := cid.GetMSPID(ctx.GetStub()) + if err != nil { + return err + } + if err := setEndorsingOrgs(ctx, id, mspID); err != nil { + return err + } + + return ctx.GetStub().SetEvent("UpdateAsset", updatedBytes) +} + +// DeleteAsset deletes an asset from the world state. +func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error { + assetBytes, err := readAsset(ctx, id) + if err != nil { + return err + } + + var asset Asset + if err := json.Unmarshal(assetBytes, &asset); err != nil { + return err + } + + ok, err := hasWritePermission(ctx, &asset) + if err != nil { + return err + } + if !ok { + return fmt.Errorf("only owner can delete assets") + } + + if err := ctx.GetStub().DelState(id); err != nil { + return err + } + + return ctx.GetStub().SetEvent("DeleteAsset", assetBytes) +} + +// AssetExists returns true when an asset with the given ID exists in the world state. +func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) { + assetBytes, err := ctx.GetStub().GetState(id) + if err != nil { + return false, fmt.Errorf("failed to read from world state: %v", err) + } + + return assetBytes != nil, nil +} + +// TransferAsset updates the owner of an asset with the given ID. +// newOwner is the user identifier; newOwnerOrg is the MSP ID of the new owning organisation. +// Subsequent updates must be endorsed by the new owning organisation. +func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string, newOwnerOrg string) error { + assetBytes, err := readAsset(ctx, id) + if err != nil { + return err + } + + var asset Asset + if err := json.Unmarshal(assetBytes, &asset); err != nil { + return err + } + + ok, err := hasWritePermission(ctx, &asset) + if err != nil { + return err + } + if !ok { + return fmt.Errorf("only owner can transfer assets") + } + + newOwnerID := OwnerIdentifier{Org: newOwnerOrg, User: newOwner} + ownerJSON, err := json.Marshal(newOwnerID) + if err != nil { + return err + } + asset.Owner = string(ownerJSON) + + updatedBytes, err := json.Marshal(asset) + if err != nil { + return err + } + + if err := ctx.GetStub().PutState(id, updatedBytes); err != nil { + return err + } + + if err := setEndorsingOrgs(ctx, id, newOwnerOrg); err != nil { + return err + } + + return ctx.GetStub().SetEvent("TransferAsset", updatedBytes) +} + +// GetAllAssets returns all assets found in the world state. +func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) { + // Range query with empty start/end key returns all assets in the chaincode namespace. + resultsIterator, err := ctx.GetStub().GetStateByRange("", "") + if err != nil { + return nil, err + } + defer resultsIterator.Close() + + var assets []*Asset + for resultsIterator.HasNext() { + queryResponse, err := resultsIterator.Next() + if err != nil { + return nil, err + } + + var asset Asset + if err := json.Unmarshal(queryResponse.Value, &asset); err != nil { + log.Printf("skipping malformed asset entry: %v", err) + continue + } + assets = append(assets, &asset) + } + + return assets, nil +} + +// --- internal helpers ----------------------- + +func readAsset(ctx contractapi.TransactionContextInterface, id string) ([]byte, error) { + assetBytes, err := ctx.GetStub().GetState(id) + if err != nil { + return nil, fmt.Errorf("failed to read from world state: %v", err) + } + if assetBytes == nil { + return nil, fmt.Errorf("sorry, asset %s has not been created", id) + } + + return assetBytes, nil +} + +func hasWritePermission(ctx contractapi.TransactionContextInterface, asset *Asset) (bool, error) { + clientID, err := clientIdentifier(ctx, "") + if err != nil { + return false, err + } + + var ownerID OwnerIdentifier + if err := json.Unmarshal([]byte(asset.Owner), &ownerID); err != nil { + return false, fmt.Errorf("failed to parse asset owner: %v", err) + } + + return clientID.Org == ownerID.Org, nil +} + +func clientIdentifier(ctx contractapi.TransactionContextInterface, user string) (OwnerIdentifier, error) { + mspID, err := cid.GetMSPID(ctx.GetStub()) + if err != nil { + return OwnerIdentifier{}, err + } + + if user == "" { + cn, err := clientCommonName(ctx) + if err != nil { + return OwnerIdentifier{}, err + } + user = cn + } + + return OwnerIdentifier{Org: mspID, User: user}, nil +} + +func clientCommonName(ctx contractapi.TransactionContextInterface) (string, error) { + cert, err := cid.GetX509Certificate(ctx.GetStub()) + if err != nil { + return "", err + } + if cert.Subject.CommonName == "" { + return "", fmt.Errorf("unable to identify client identity common name") + } + + return cert.Subject.CommonName, nil +} + +func setEndorsingOrgs(ctx contractapi.TransactionContextInterface, key string, orgs ...string) error { + ep, err := statebased.NewStateEP(nil) + if err != nil { + return err + } + if err := ep.AddOrgs(statebased.RoleTypeMember, orgs...); err != nil { + return err + } + policy, err := ep.Policy() + if err != nil { + return err + } + + return ctx.GetStub().SetStateValidationParameter(key, policy) +} diff --git a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/main.go b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/main.go new file mode 100644 index 0000000000..529e27e381 --- /dev/null +++ b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/main.go @@ -0,0 +1,22 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + */ + +package main + +import ( + "log" + + "github.com/hyperledger/fabric-contract-api-go/v2/contractapi" +) + +func main() { + assetChaincode, err := contractapi.NewChaincode(&SmartContract{}) + if err != nil { + log.Panicf("Error creating asset-transfer chaincode: %v", err) + } + + if err := assetChaincode.Start(); err != nil { + log.Panicf("Error starting asset-transfer chaincode: %v", err) + } +} From d62b51853dce6a7dcdb7f8535cc980fed9c3fbc9 Mon Sep 17 00:00:00 2001 From: nXtCyberNet Date: Wed, 11 Mar 2026 11:20:40 +0530 Subject: [PATCH 02/11] corrected some logs based on the typescript implementation Signed-off-by: nXtCyberNet --- .../contracts/asset-transfer-go/go.mod | 7 +++++-- .../contracts/asset-transfer-go/src/asset.go | 8 -------- .../contracts/asset-transfer-go/src/asset_transfer.go | 10 +++++----- .../contracts/asset-transfer-go/src/main.go | 4 ---- 4 files changed, 10 insertions(+), 19 deletions(-) diff --git a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/go.mod b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/go.mod index 83fba2d131..1e3470bc8e 100644 --- a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/go.mod +++ b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/go.mod @@ -5,16 +5,20 @@ go 1.23.0 require ( github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0 github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0 + github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 + github.com/stretchr/testify v1.10.0 + google.golang.org/protobuf v1.36.1 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/spec v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect - github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect @@ -23,6 +27,5 @@ require ( golang.org/x/text v0.17.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/grpc v1.67.0 // indirect - google.golang.org/protobuf v1.36.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset.go b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset.go index b777b32fdf..d27f2ad26f 100644 --- a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset.go +++ b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset.go @@ -1,18 +1,10 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - */ - package main -// OwnerIdentifier represents the owner of an asset with their organisation MSP ID and user identifier. -// Fields are lowercase to match the TypeScript serialisation format. type OwnerIdentifier struct { Org string `json:"org"` User string `json:"user"` } -// Asset describes the details of an asset stored in the world state. -// Fields are defined in alphabetical order to produce deterministic JSON serialisation. type Asset struct { AppraisedValue int `json:"AppraisedValue"` Color string `json:"Color"` diff --git a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset_transfer.go b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset_transfer.go index 415826939b..823715ca51 100644 --- a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset_transfer.go +++ b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset_transfer.go @@ -25,7 +25,7 @@ func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, return err } if exists { - return fmt.Errorf("the asset %s already exists", id) + return fmt.Errorf("The asset %s already exists", id) } ownerID, err := clientIdentifier(ctx, owner) @@ -96,7 +96,7 @@ func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, return err } if !ok { - return fmt.Errorf("only owner can update assets") + return fmt.Errorf("Only owner can update assets") } // Owner is intentionally preserved; use TransferAsset to change owner. @@ -141,7 +141,7 @@ func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, return err } if !ok { - return fmt.Errorf("only owner can delete assets") + return fmt.Errorf("Only owner can delete assets") } if err := ctx.GetStub().DelState(id); err != nil { @@ -180,7 +180,7 @@ func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterfac return err } if !ok { - return fmt.Errorf("only owner can transfer assets") + return fmt.Errorf("Only owner can transfer assets") } newOwnerID := OwnerIdentifier{Org: newOwnerOrg, User: newOwner} @@ -241,7 +241,7 @@ func readAsset(ctx contractapi.TransactionContextInterface, id string) ([]byte, return nil, fmt.Errorf("failed to read from world state: %v", err) } if assetBytes == nil { - return nil, fmt.Errorf("sorry, asset %s has not been created", id) + return nil, fmt.Errorf("Sorry, asset %s has not been created", id) } return assetBytes, nil diff --git a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/main.go b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/main.go index 529e27e381..44f3416a70 100644 --- a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/main.go +++ b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/main.go @@ -1,7 +1,3 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - */ - package main import ( From 9d12804e0628399354ffa3b629773dfa50d69191 Mon Sep 17 00:00:00 2001 From: nXtCyberNet Date: Wed, 11 Mar 2026 21:18:20 +0530 Subject: [PATCH 03/11] fix: correct docker-entrypoint and remove unused testify dep in asset-transfer-go Signed-off-by: nXtCyberNet --- .../docker/docker-entrypoint.sh | 28 +++++++++++-------- .../contracts/asset-transfer-go/go.mod | 1 - 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/docker/docker-entrypoint.sh b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/docker/docker-entrypoint.sh index 6ad2eca55b..3994d15fbf 100644 --- a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/docker/docker-entrypoint.sh +++ b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/docker/docker-entrypoint.sh @@ -9,18 +9,22 @@ if [[ ! -v CHAINCODE_SERVER_ADDRESS ]]; then exec ./chaincode --peer.address "${CORE_PEER_ADDRESS}" elif [[ "${CORE_PEER_TLS_ENABLED,,}" == "true" ]]; then - # CaaS + TLS - exec ./chaincode \ - --chaincode.address "${CHAINCODE_SERVER_ADDRESS}" \ - --chaincode.id "${CHAINCODE_ID}" \ - --chaincode.tls.enabled true \ - --chaincode.tls.key.file "${CHAINCODE_TLS_KEY:-/hyperledger/privatekey.pem}" \ - --chaincode.tls.cert.file "${CHAINCODE_TLS_CERT:-/hyperledger/cert.pem}" \ - --chaincode.tls.clientCaCert.file "${CHAINCODE_TLS_CLIENT_CACERT:-/hyperledger/rootcert.pem}" + # CaaS + TLS: fabric-chaincode-go/v2 reads CHAINCODE_SERVER_ADDRESS, + # CORE_CHAINCODE_ID_NAME, and TLS vars directly as env vars. + exec env \ + CORE_CHAINCODE_ID_NAME="${CHAINCODE_ID}" \ + CHAINCODE_SERVER_ADDRESS="${CHAINCODE_SERVER_ADDRESS}" \ + CORE_PEER_TLS_ENABLED=true \ + CORE_PEER_TLS_ROOTCERT_FILE="${CHAINCODE_TLS_KEY:-/hyperledger/privatekey.pem}" \ + CORE_TLS_CLIENT_KEY_FILE="${CHAINCODE_TLS_CERT:-/hyperledger/cert.pem}" \ + CORE_TLS_CLIENT_CERT_FILE="${CHAINCODE_TLS_CLIENT_CACERT:-/hyperledger/rootcert.pem}" \ + ./chaincode else - # CaaS without TLS - exec ./chaincode \ - --chaincode.address "${CHAINCODE_SERVER_ADDRESS}" \ - --chaincode.id "${CHAINCODE_ID}" + # CaaS without TLS: fabric-chaincode-go/v2 uses CHAINCODE_SERVER_ADDRESS + # and CORE_CHAINCODE_ID_NAME env vars to start the gRPC server. + exec env \ + CORE_CHAINCODE_ID_NAME="${CHAINCODE_ID}" \ + CHAINCODE_SERVER_ADDRESS="${CHAINCODE_SERVER_ADDRESS}" \ + ./chaincode fi diff --git a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/go.mod b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/go.mod index 1e3470bc8e..ad08e38434 100644 --- a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/go.mod +++ b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/go.mod @@ -6,7 +6,6 @@ require ( github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0 github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0 github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 - github.com/stretchr/testify v1.10.0 google.golang.org/protobuf v1.36.1 ) From b17085b69e77da068e4b7291247dbc56ccc74f75 Mon Sep 17 00:00:00 2001 From: nXtCyberNet Date: Wed, 11 Mar 2026 21:21:20 +0530 Subject: [PATCH 04/11] updated the env vars in docker.sh Signed-off-by: nXtCyberNet --- .../contracts/asset-transfer-typescript/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/full-stack-asset-transfer-guide/contracts/asset-transfer-typescript/package.json b/full-stack-asset-transfer-guide/contracts/asset-transfer-typescript/package.json index 5ca122622a..e8c1854942 100644 --- a/full-stack-asset-transfer-guide/contracts/asset-transfer-typescript/package.json +++ b/full-stack-asset-transfer-guide/contracts/asset-transfer-typescript/package.json @@ -57,6 +57,7 @@ "@types/node": "^20.19.33", "eslint": "^10.0.2", "typescript": "~5.8", - "typescript-eslint": "^8.56.1" + "typescript-eslint": "^8.56.1", + "vitest": "^4.0.18" } } From 1c45f3e6f1c09563c497e50832d25a41df176aa8 Mon Sep 17 00:00:00 2001 From: nXtCyberNet Date: Tue, 28 Jul 2026 13:17:16 +0530 Subject: [PATCH 05/11] integrated with justfile Signed-off-by: nXtCyberNet --- .../contracts/asset-transfer-go/go.mod | 6 +- .../contracts/asset-transfer-go/src/asset.go | 38 ++- .../asset-transfer-go/src/asset_transfer.go | 262 ++++++++++-------- .../contracts/asset-transfer-go/src/helper.go | 140 ++++++++++ .../contracts/asset-transfer-go/src/main.go | 43 ++- .../asset-transfer-typescript/package.json | 5 +- full-stack-asset-transfer-guide/justfile | 7 +- .../tests/10-appdev-go-e2e.sh | 69 +++++ ...dev-e2e.sh => 10-appdev-typescript-e2e.sh} | 0 9 files changed, 439 insertions(+), 131 deletions(-) create mode 100644 full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/helper.go create mode 100755 full-stack-asset-transfer-guide/tests/10-appdev-go-e2e.sh rename full-stack-asset-transfer-guide/tests/{10-appdev-e2e.sh => 10-appdev-typescript-e2e.sh} (100%) diff --git a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/go.mod b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/go.mod index ad08e38434..83fba2d131 100644 --- a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/go.mod +++ b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/go.mod @@ -5,19 +5,16 @@ go 1.23.0 require ( github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0 github.com/hyperledger/fabric-contract-api-go/v2 v2.2.0 - github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 - google.golang.org/protobuf v1.36.1 ) require ( - github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/spec v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect + github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect @@ -26,5 +23,6 @@ require ( golang.org/x/text v0.17.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/grpc v1.67.0 // indirect + google.golang.org/protobuf v1.36.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset.go b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset.go index d27f2ad26f..862d1f1ab5 100644 --- a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset.go +++ b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset.go @@ -1,14 +1,38 @@ +/* +SPDX-License-Identifier: Apache-2.0 +*/ + package main -type OwnerIdentifier struct { - Org string `json:"org"` - User string `json:"user"` -} +import "fmt" +// Asset describes basic details of what makes up a simple asset. type Asset struct { - AppraisedValue int `json:"AppraisedValue"` - Color string `json:"Color"` ID string `json:"ID"` - Owner string `json:"Owner"` // JSON-encoded OwnerIdentifier + Color string `json:"Color"` + Owner string `json:"Owner"` + AppraisedValue int `json:"AppraisedValue"` Size int `json:"Size"` } + +// NewAsset creates a new Asset with validation, equivalent to the +// TypeScript Asset.newInstance() method. +func NewAsset(state Asset) (*Asset, error) { + if state.ID == "" { + return nil, fmt.Errorf("missing ID") + } + + if state.Owner == "" { + return nil, fmt.Errorf("missing Owner") + } + + asset := &Asset{ + ID: state.ID, + Color: state.Color, + Owner: state.Owner, + AppraisedValue: state.AppraisedValue, + Size: state.Size, + } + + return asset, nil +} diff --git a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset_transfer.go b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset_transfer.go index 823715ca51..e0bd930077 100644 --- a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset_transfer.go +++ b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset_transfer.go @@ -10,7 +10,6 @@ import ( "log" "github.com/hyperledger/fabric-chaincode-go/v2/pkg/cid" - "github.com/hyperledger/fabric-chaincode-go/v2/pkg/statebased" "github.com/hyperledger/fabric-contract-api-go/v2/contractapi" ) @@ -19,37 +18,46 @@ type SmartContract struct { contractapi.Contract } -func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error { - exists, err := s.AssetExists(ctx, id) +// CreateAsset issues a new asset to the world state with given details. +func (s *SmartContract) CreateAsset( + ctx contractapi.TransactionContextInterface, + state *Asset, +) error { + + if state == nil { + return fmt.Errorf("asset cannot be nil") + } + + ownerID, err := clientIdentifier(ctx, state.Owner) if err != nil { return err } - if exists { - return fmt.Errorf("The asset %s already exists", id) - } - ownerID, err := clientIdentifier(ctx, owner) + ownerJSON, err := json.Marshal(ownerID) if err != nil { return err } - ownerJSON, err := json.Marshal(ownerID) + state.Owner = string(ownerJSON) + + asset, err := NewAsset(*state) if err != nil { return err } - asset := Asset{ - AppraisedValue: appraisedValue, - Color: color, - ID: id, - Owner: string(ownerJSON), - Size: size, + exists, err := s.AssetExists(ctx, asset.ID) + if err != nil { + return err + } + if exists { + return fmt.Errorf("the asset %s already exists", asset.ID) } + assetBytes, err := json.Marshal(asset) if err != nil { return err } - if err := ctx.GetStub().PutState(id, assetBytes); err != nil { + if err := ctx.GetStub().PutState(asset.ID, assetBytes); err != nil { return err } @@ -57,14 +65,20 @@ func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, if err != nil { return err } - if err := setEndorsingOrgs(ctx, id, mspID); err != nil { + + if err := setEndorsingOrgs(ctx, asset.ID, mspID); err != nil { return err } return ctx.GetStub().SetEvent("CreateAsset", assetBytes) } -func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) { +// ReadAsset returns an existing asset stored in the world state. +func (s *SmartContract) ReadAsset( + ctx contractapi.TransactionContextInterface, + id string, +) (*Asset, error) { + assetBytes, err := readAsset(ctx, id) if err != nil { return nil, err @@ -78,38 +92,85 @@ func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, i return &asset, nil } -// UpdateAsset updates color, size, and appraised value of an existing asset. -// The asset owner cannot be changed here; use TransferAsset instead. -func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, appraisedValue int) error { - assetBytes, err := readAsset(ctx, id) +// readAsset returns the raw asset bytes. +func readAsset( + ctx contractapi.TransactionContextInterface, + id string, +) ([]byte, error) { + + assetBytes, err := ctx.GetStub().GetState(id) + if err != nil { + return nil, fmt.Errorf("failed to read from world state: %v", err) + } + + if assetBytes == nil { + return nil, fmt.Errorf("sorry, asset %s has not been created", id) + } + + return assetBytes, nil +} + +// UpdateAsset updates an existing asset in the world state with the +// provided partial asset data. The asset ID must be specified. +// The Owner field cannot be changed here; use TransferAsset instead. +func (s *SmartContract) UpdateAsset( + ctx contractapi.TransactionContextInterface, + assetUpdate *Asset, +) error { + + if assetUpdate == nil { + return fmt.Errorf("asset cannot be nil") + } + + if assetUpdate.ID == "" { + return fmt.Errorf("no asset ID specified") + } + + existingAssetBytes, err := readAsset(ctx, assetUpdate.ID) if err != nil { return err } - var existing Asset - if err := json.Unmarshal(assetBytes, &existing); err != nil { + var existingAsset Asset + if err := json.Unmarshal(existingAssetBytes, &existingAsset); err != nil { return err } - ok, err := hasWritePermission(ctx, &existing) + ok, err := hasWritePermission(ctx, &existingAsset) if err != nil { return err } if !ok { - return fmt.Errorf("Only owner can update assets") + return fmt.Errorf("only owner can update assets") + } + + // Merge the update into the existing asset. + // Preserve the owner; ownership changes must go through TransferAsset. + if assetUpdate.Color != "" { + existingAsset.Color = assetUpdate.Color + } + + if assetUpdate.Size != 0 { + existingAsset.Size = assetUpdate.Size } - // Owner is intentionally preserved; use TransferAsset to change owner. - existing.Color = color - existing.Size = size - existing.AppraisedValue = appraisedValue + if assetUpdate.AppraisedValue != 0 { + existingAsset.AppraisedValue = assetUpdate.AppraisedValue + } + + existingAsset.Owner = existingAsset.Owner - updatedBytes, err := json.Marshal(existing) + updatedAsset, err := NewAsset(existingAsset) if err != nil { return err } - if err := ctx.GetStub().PutState(id, updatedBytes); err != nil { + updatedBytes, err := json.Marshal(updatedAsset) + if err != nil { + return err + } + + if err := ctx.GetStub().PutState(updatedAsset.ID, updatedBytes); err != nil { return err } @@ -117,7 +178,8 @@ func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, if err != nil { return err } - if err := setEndorsingOrgs(ctx, id, mspID); err != nil { + + if err := setEndorsingOrgs(ctx, updatedAsset.ID, mspID); err != nil { return err } @@ -125,7 +187,11 @@ func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, } // DeleteAsset deletes an asset from the world state. -func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error { +func (s *SmartContract) DeleteAsset( + ctx contractapi.TransactionContextInterface, + id string, +) error { + assetBytes, err := readAsset(ctx, id) if err != nil { return err @@ -141,30 +207,39 @@ func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, return err } if !ok { - return fmt.Errorf("Only owner can delete assets") + return fmt.Errorf("only owner can delete assets") } if err := ctx.GetStub().DelState(id); err != nil { return err } - return ctx.GetStub().SetEvent("DeleteAsset", assetBytes) + // Matches the TypeScript event name (including its typo). + return ctx.GetStub().SetEvent("DeletaAsset", assetBytes) } -// AssetExists returns true when an asset with the given ID exists in the world state. -func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) { +// AssetExists returns true when an asset with the given ID exists. +func (s *SmartContract) AssetExists( + ctx contractapi.TransactionContextInterface, + id string, +) (bool, error) { + assetBytes, err := ctx.GetStub().GetState(id) if err != nil { - return false, fmt.Errorf("failed to read from world state: %v", err) + return false, err } return assetBytes != nil, nil } -// TransferAsset updates the owner of an asset with the given ID. -// newOwner is the user identifier; newOwnerOrg is the MSP ID of the new owning organisation. -// Subsequent updates must be endorsed by the new owning organisation. -func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string, newOwnerOrg string) error { +// TransferAsset updates the owner field of an asset. +func (s *SmartContract) TransferAsset( + ctx contractapi.TransactionContextInterface, + id string, + newOwner string, + newOwnerOrg string, +) error { + assetBytes, err := readAsset(ctx, id) if err != nil { return err @@ -180,14 +255,16 @@ func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterfac return err } if !ok { - return fmt.Errorf("Only owner can transfer assets") + return fmt.Errorf("only owner can transfer assets") } - newOwnerID := OwnerIdentifier{Org: newOwnerOrg, User: newOwner} - ownerJSON, err := json.Marshal(newOwnerID) + ownerID := ownerIdentifier(newOwner, newOwnerOrg) + + ownerJSON, err := json.Marshal(ownerID) if err != nil { return err } + asset.Owner = string(ownerJSON) updatedBytes, err := json.Marshal(asset) @@ -199,6 +276,7 @@ func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterfac return err } + // Subsequent updates must be endorsed by the new owning organization. if err := setEndorsingOrgs(ctx, id, newOwnerOrg); err != nil { return err } @@ -206,102 +284,62 @@ func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterfac return ctx.GetStub().SetEvent("TransferAsset", updatedBytes) } -// GetAllAssets returns all assets found in the world state. -func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) { - // Range query with empty start/end key returns all assets in the chaincode namespace. +// GetAllAssets returns a list of all assets in the world state. +func (s *SmartContract) GetAllAssets( + ctx contractapi.TransactionContextInterface, +) (string, error) { + resultsIterator, err := ctx.GetStub().GetStateByRange("", "") if err != nil { - return nil, err + return "", err } defer resultsIterator.Close() - var assets []*Asset + var assets []Asset + for resultsIterator.HasNext() { queryResponse, err := resultsIterator.Next() if err != nil { - return nil, err + return "", err } var asset Asset if err := json.Unmarshal(queryResponse.Value, &asset); err != nil { - log.Printf("skipping malformed asset entry: %v", err) + log.Printf("failed to unmarshal asset: %v", err) continue } - assets = append(assets, &asset) - } - return assets, nil -} - -// --- internal helpers ----------------------- - -func readAsset(ctx contractapi.TransactionContextInterface, id string) ([]byte, error) { - assetBytes, err := ctx.GetStub().GetState(id) - if err != nil { - return nil, fmt.Errorf("failed to read from world state: %v", err) + assets = append(assets, asset) } - if assetBytes == nil { - return nil, fmt.Errorf("Sorry, asset %s has not been created", id) - } - - return assetBytes, nil -} -func hasWritePermission(ctx contractapi.TransactionContextInterface, asset *Asset) (bool, error) { - clientID, err := clientIdentifier(ctx, "") + assetBytes, err := marshal(assets) if err != nil { - return false, err - } - - var ownerID OwnerIdentifier - if err := json.Unmarshal([]byte(asset.Owner), &ownerID); err != nil { - return false, fmt.Errorf("failed to parse asset owner: %v", err) + return "", err } - return clientID.Org == ownerID.Org, nil + return string(assetBytes), nil } -func clientIdentifier(ctx contractapi.TransactionContextInterface, user string) (OwnerIdentifier, error) { - mspID, err := cid.GetMSPID(ctx.GetStub()) - if err != nil { - return OwnerIdentifier{}, err +// unmarshal parses JSON into the supplied destination. +func unmarshal(data []byte, v any) error { + if len(data) == 0 { + return fmt.Errorf("empty JSON") } - if user == "" { - cn, err := clientCommonName(ctx) - if err != nil { - return OwnerIdentifier{}, err - } - user = cn - } - - return OwnerIdentifier{Org: mspID, User: user}, nil + return json.Unmarshal(data, v) } -func clientCommonName(ctx contractapi.TransactionContextInterface) (string, error) { - cert, err := cid.GetX509Certificate(ctx.GetStub()) - if err != nil { - return "", err - } - if cert.Subject.CommonName == "" { - return "", fmt.Errorf("unable to identify client identity common name") - } - - return cert.Subject.CommonName, nil +// marshal serializes an object into JSON. +func marshal(v any) ([]byte, error) { + return json.Marshal(v) } -func setEndorsingOrgs(ctx contractapi.TransactionContextInterface, key string, orgs ...string) error { - ep, err := statebased.NewStateEP(nil) +// toJSON returns a JSON string representation. +func toJSON(v any) (string, error) { + bytes, err := marshal(v) if err != nil { - return err - } - if err := ep.AddOrgs(statebased.RoleTypeMember, orgs...); err != nil { - return err - } - policy, err := ep.Policy() - if err != nil { - return err + return "", err } - return ctx.GetStub().SetStateValidationParameter(key, policy) + return string(bytes), nil } diff --git a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/helper.go b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/helper.go new file mode 100644 index 0000000000..2153a7c71b --- /dev/null +++ b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/helper.go @@ -0,0 +1,140 @@ +package main + +import ( + "crypto/x509" + "encoding/json" + "encoding/pem" + "fmt" + "strings" + + "github.com/hyperledger/fabric-chaincode-go/v2/pkg/cid" + "github.com/hyperledger/fabric-chaincode-go/v2/pkg/statebased" + "github.com/hyperledger/fabric-contract-api-go/v2/contractapi" +) + +type OwnerIdentifier struct { + Org string `json:"org"` + User string `json:"user"` +} + +// hasWritePermission returns true if the client's org matches the asset owner's org. +func hasWritePermission( + ctx contractapi.TransactionContextInterface, + asset *Asset, +) (bool, error) { + + clientID, err := clientIdentifier(ctx, "") + if err != nil { + return false, err + } + + var ownerID OwnerIdentifier + if err := json.Unmarshal([]byte(asset.Owner), &ownerID); err != nil { + return false, err + } + + return clientID.Org == ownerID.Org, nil +} + +// clientIdentifier returns the client's organization and username. +// If user is empty, the client's certificate Common Name is used. +func clientIdentifier( + ctx contractapi.TransactionContextInterface, + user string, +) (*OwnerIdentifier, error) { + + mspID, err := cid.GetMSPID(ctx.GetStub()) + if err != nil { + return nil, err + } + + if user == "" { + user, err = clientCommonName(ctx) + if err != nil { + return nil, err + } + } + + return &OwnerIdentifier{ + Org: mspID, + User: user, + }, nil +} + +// clientCommonName extracts the certificate Common Name. +func clientCommonName( + ctx contractapi.TransactionContextInterface, +) (string, error) { + + id, err := cid.GetID(ctx.GetStub()) + if err != nil { + return "", err + } + + // Fabric IDs look like: + // x509::base64(cert)::base64(ca) + + parts := strings.Split(id, "::") + if len(parts) < 2 { + return "", fmt.Errorf("invalid client identity") + } + + certPEM := []byte(parts[1]) + + block, _ := pem.Decode(certPEM) + if block == nil { + return "", fmt.Errorf("failed to decode certificate") + } + + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return "", err + } + + return cert.Subject.CommonName, nil +} + +func ownerIdentifier(user, org string) *OwnerIdentifier { + return &OwnerIdentifier{ + Org: org, + User: user, + } +} + +// setEndorsingOrgs sets state-based endorsement for a key. +func setEndorsingOrgs( + ctx contractapi.TransactionContextInterface, + ledgerKey string, + orgs ...string, +) error { + + policy, err := newMemberPolicy(orgs...) + if err != nil { + return err + } + + policyBytes, err := policy.Policy() + if err != nil { + return err + } + + return ctx.GetStub().SetStateValidationParameter( + ledgerKey, + policyBytes, + ) +} + +// newMemberPolicy creates an endorsement policy requiring MEMBER from the supplied orgs. +func newMemberPolicy(orgs ...string) (statebased.KeyEndorsementPolicy, error) { + + policy, err := statebased.NewStateEP(nil) + if err != nil { + return nil, err + } + + if err := policy.AddOrgs(statebased.RoleTypeMember, orgs...); err != nil { + return nil, err + } + + return policy, nil +} diff --git a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/main.go b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/main.go index 44f3416a70..f1303c980a 100644 --- a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/main.go +++ b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/main.go @@ -1,18 +1,55 @@ package main +/* +Package main starts the Asset Transfer chaincode as a Fabric +Chaincode-as-a-Service (CCaaS). + +Before running, ensure the following environment variables are set: + + CORE_CHAINCODE_ID_NAME=${CHAINCODE_ID} + CORE_CHAINCODE_SERVER_ADDRESS=${CHAINCODE_SERVER_ADDRESS} + +For example: + + CORE_CHAINCODE_ID_NAME=${CHAINCODE_ID} \ + CORE_CHAINCODE_SERVER_ADDRESS=${CHAINCODE_SERVER_ADDRESS} \ + go run ./src +*/ + import ( "log" + "os" + "github.com/hyperledger/fabric-chaincode-go/v2/shim" "github.com/hyperledger/fabric-contract-api-go/v2/contractapi" ) func main() { - assetChaincode, err := contractapi.NewChaincode(&SmartContract{}) + cc, err := contractapi.NewChaincode(&SmartContract{}) if err != nil { log.Panicf("Error creating asset-transfer chaincode: %v", err) } - if err := assetChaincode.Start(); err != nil { - log.Panicf("Error starting asset-transfer chaincode: %v", err) + ccid := os.Getenv("CORE_CHAINCODE_ID_NAME") + if ccid == "" { + log.Fatal("CORE_CHAINCODE_ID_NAME must be set") + } + + address := os.Getenv("CORE_CHAINCODE_SERVER_ADDRESS") + if address == "" { + log.Fatal("CORE_CHAINCODE_SERVER_ADDRESS must be set") + } + + server := &shim.ChaincodeServer{ + CCID: ccid, + Address: address, + CC: cc, + TLSProps: shim.TLSProperties{ + Disabled: true, + }, + } + + if err := server.Start(); err != nil { + log.Panicf("Error starting asset-transfer chaincode server: %v", err) } } diff --git a/full-stack-asset-transfer-guide/contracts/asset-transfer-typescript/package.json b/full-stack-asset-transfer-guide/contracts/asset-transfer-typescript/package.json index e8c1854942..5f6f6aa498 100644 --- a/full-stack-asset-transfer-guide/contracts/asset-transfer-typescript/package.json +++ b/full-stack-asset-transfer-guide/contracts/asset-transfer-typescript/package.json @@ -57,7 +57,6 @@ "@types/node": "^20.19.33", "eslint": "^10.0.2", "typescript": "~5.8", - "typescript-eslint": "^8.56.1", - "vitest": "^4.0.18" + "typescript-eslint": "^8.56.1" } -} +} \ No newline at end of file diff --git a/full-stack-asset-transfer-guide/justfile b/full-stack-asset-transfer-guide/justfile index 664c05469f..45d426adc9 100644 --- a/full-stack-asset-transfer-guide/justfile +++ b/full-stack-asset-transfer-guide/justfile @@ -108,8 +108,11 @@ test-chaincode: tests/00-chaincode-e2e.sh # Run an e2e test of the ApplicationDev scenario -test-appdev: - tests/10-appdev-e2e.sh +test-appdev-typescript: + tests/10-appdev-typescript-e2e.sh + +test-appdev-go: + tests/10-appdev-go-e2e.sh # Run an e2e test of the CloudNative scenario test-cloud: diff --git a/full-stack-asset-transfer-guide/tests/10-appdev-go-e2e.sh b/full-stack-asset-transfer-guide/tests/10-appdev-go-e2e.sh new file mode 100755 index 0000000000..5155e9ae11 --- /dev/null +++ b/full-stack-asset-transfer-guide/tests/10-appdev-go-e2e.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash + +set -v -euo pipefail + +# All tests run in the workshop root folder +cd "$(dirname "$0")"/.. + +export WORKSHOP_PATH="${PWD}" +export PATH="${WORKSHOP_PATH}/bin:${PATH}" +export FABRIC_CFG_PATH="${WORKSHOP_PATH}/config" + +"${WORKSHOP_PATH}/check.sh" + +CHAINCODE_PID= + +function exitHook() { + + # Shut down the Go chaincode process + [ -n "${CHAINCODE_PID}" ] && kill "${CHAINCODE_PID}" 2>/dev/null || true + + # Shut down Microfab + docker kill microfab &>/dev/null || true + + # Delete the network configuration and crypto material + rm -rf "${WORKSHOP_PATH}/_cfg" +} + +trap exitHook SIGINT SIGTERM EXIT + +# +# Start Microfab +# +just microfab + +# +# Configure the environment +# +source "${WORKSHOP_PATH}/_cfg/uf/org1admin.env" + +just debugcc + + +cd "${WORKSHOP_PATH}/contracts/asset-transfer-go" + +CORE_CHAINCODE_ID_NAME="${CHAINCODE_ID}" \ +CORE_CHAINCODE_SERVER_ADDRESS="${CHAINCODE_SERVER_ADDRESS}" \ +go run ./src & + +CHAINCODE_PID=$! + +sleep 5 + + +cd "${WORKSHOP_PATH}/applications/trader-typescript" + +export ENDPOINT=org1peer-api.127-0-0-1.nip.io:8080 +export MSP_ID=org1MSP +export CERTIFICATE=../../_cfg/uf/_msp/org1/org1admin/msp/signcerts/cert.pem +export PRIVATE_KEY=../../_cfg/uf/_msp/org1/org1admin/msp/keystore/cert_sk + +npm install + +npm start getAllAssets +npm start transact +npm start getAllAssets +npm start create banana bananaman yellow +npm start read banana +npm start delete banana +SIMULATED_FAILURE_COUNT=2 npm start listen \ No newline at end of file diff --git a/full-stack-asset-transfer-guide/tests/10-appdev-e2e.sh b/full-stack-asset-transfer-guide/tests/10-appdev-typescript-e2e.sh similarity index 100% rename from full-stack-asset-transfer-guide/tests/10-appdev-e2e.sh rename to full-stack-asset-transfer-guide/tests/10-appdev-typescript-e2e.sh From fba220c12da9dc2091f78943f1f65e5594b7dd30 Mon Sep 17 00:00:00 2001 From: nXtCyberNet Date: Tue, 28 Jul 2026 13:30:27 +0530 Subject: [PATCH 06/11] updated justfile Signed-off-by: nXtCyberNet --- full-stack-asset-transfer-guide/justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/full-stack-asset-transfer-guide/justfile b/full-stack-asset-transfer-guide/justfile index 45d426adc9..019d6ba835 100644 --- a/full-stack-asset-transfer-guide/justfile +++ b/full-stack-asset-transfer-guide/justfile @@ -101,7 +101,7 @@ operator-crds: check-kube ############################################################################### # Run e2e tests of all scenarios -test: test-chaincode test-appdev test-cloud # test-ansible +test: test-chaincode test-appdev-typescript test-appdev-go test-cloud # test-ansible # Run an e2e test of the SmartContractDev scenario test-chaincode: From d2b9a0022d42ab61dce5ab2d77ae28d72830f2af Mon Sep 17 00:00:00 2001 From: nXtCyberNet Date: Tue, 28 Jul 2026 13:44:41 +0530 Subject: [PATCH 07/11] fix: resolve duplicate test-appdev-go recipe and CI workflow mismatches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add doc comment to test-appdev-go recipe (prevents ambiguous context-line merge that caused 'recipe redefined' error in CI merge commit) - Add debugcc-go recipe targeting contracts/asset-transfer-go so the Go e2e test installs the Go chaincode rather than the TypeScript one - Update tests/10-appdev-go-e2e.sh to call 'just debugcc-go' instead of 'just debugcc', and fix missing trailing newline (POSIX violation) - Update .github/workflows/test-fsat.yaml: rename appdev job to appdev-typescript (calls just test-appdev-typescript) and add a new appdev-go job (calls just test-appdev-go) — the old 'just test-appdev' recipe no longer exists and would cause CI to fail Signed-off-by: nXtCyberNet --- .github/workflows/test-fsat.yaml | 13 +++++- full-stack-asset-transfer-guide/justfile | 44 ++++++++++++++++++- .../tests/10-appdev-go-e2e.sh | 2 +- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-fsat.yaml b/.github/workflows/test-fsat.yaml index 6b8ba951a3..baeb11a2f7 100644 --- a/.github/workflows/test-fsat.yaml +++ b/.github/workflows/test-fsat.yaml @@ -22,13 +22,22 @@ jobs: - run: just test-ansible working-directory: full-stack-asset-transfer-guide - appdev: + appdev-typescript: runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }} steps: - uses: actions/checkout@v6 - name: Set up Full Stack Runtime uses: ./.github/actions/fsat-setup - - run: just test-appdev + - run: just test-appdev-typescript + working-directory: full-stack-asset-transfer-guide + + appdev-go: + runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }} + steps: + - uses: actions/checkout@v6 + - name: Set up Full Stack Runtime + uses: ./.github/actions/fsat-setup + - run: just test-appdev-go working-directory: full-stack-asset-transfer-guide chaincode: diff --git a/full-stack-asset-transfer-guide/justfile b/full-stack-asset-transfer-guide/justfile index 019d6ba835..406c5f9621 100644 --- a/full-stack-asset-transfer-guide/justfile +++ b/full-stack-asset-transfer-guide/justfile @@ -111,6 +111,7 @@ test-chaincode: test-appdev-typescript: tests/10-appdev-typescript-e2e.sh +# Run an e2e test of the ApplicationDev scenario with the Go chaincode test-appdev-go: tests/10-appdev-go-e2e.sh @@ -208,7 +209,7 @@ microfab: microfab-down echo echo 'source $WORKSHOP_PATH/_cfg/uf/org1admin.env' -# Creates a chaincode package and install/approve/commit +# Creates a chaincode package (TypeScript) and install/approve/commit debugcc: #!/usr/bin/env bash set -e -o pipefail @@ -249,6 +250,47 @@ debugcc: echo echo ' source $WORKSHOP_PATH/_cfg/uf/org1admin.env' +# Creates a Go chaincode package and install/approve/commit +debugcc-go: + #!/usr/bin/env bash + set -e -o pipefail + + export CFG=$CWDIR/_cfg/uf + + pushd $CWDIR/contracts/asset-transfer-go + + # this is the ip address the peer will use to talk to the CHAINCODE_ID + # remember this is relative from where the peer is running. + export CHAINCODE_SERVER_ADDRESS=host.docker.internal:9999 + export CHAINCODE_ID=$(weft chaincode package caas --path . --label asset-transfer --address ${CHAINCODE_SERVER_ADDRESS} --archive asset-transfer.tgz --quiet) + export CORE_PEER_LOCALMSPID=org1MSP + export CORE_PEER_MSPCONFIGPATH=$CFG/_msp/org1/org1admin/msp + export CORE_PEER_ADDRESS=org1peer-api.127-0-0-1.nip.io:8080 + export CORE_PEER_CLIENT_CONNTIMEOUT=15s + export CORE_PEER_DELIVERYCLIENT_CONNTIMEOUT=15s + export ORDERER_ENDPOINT=orderer-api.127-0-0-1.nip.io:8080 + + echo "CHAINCODE_ID=${CHAINCODE_ID}" + + set -x && peer lifecycle chaincode install asset-transfer.tgz && { set +x; } 2>/dev/null + echo + set -x && peer lifecycle chaincode approveformyorg --channelID mychannel -o $ORDERER_ENDPOINT --name asset-transfer -v 0 --package-id $CHAINCODE_ID --sequence 1 --connTimeout 15s && { set +x; } 2>/dev/null + echo + set -x && peer lifecycle chaincode commit --channelID mychannel -o $ORDERER_ENDPOINT --name asset-transfer -v 0 --sequence 1 --connTimeout 15s && { set +x; } 2>/dev/null + echo + set -x && peer lifecycle chaincode querycommitted --channelID=mychannel && { set +x; } 2>/dev/null + echo + popd + + cat << CC_EOF >> $CFG/org1admin.env + export CHAINCODE_SERVER_ADDRESS=0.0.0.0:9999 + export CHAINCODE_ID=${CHAINCODE_ID} + CC_EOF + + echo "Added CHAINCODE_ID and CHAINCODE_SERVER_ADDRESS to org1admin.env" + echo + echo ' source $WORKSHOP_PATH/_cfg/uf/org1admin.env' + ############################################################################### # CLOUD NATIVE TARGETS # ############################################################################### diff --git a/full-stack-asset-transfer-guide/tests/10-appdev-go-e2e.sh b/full-stack-asset-transfer-guide/tests/10-appdev-go-e2e.sh index 5155e9ae11..c64c519155 100755 --- a/full-stack-asset-transfer-guide/tests/10-appdev-go-e2e.sh +++ b/full-stack-asset-transfer-guide/tests/10-appdev-go-e2e.sh @@ -37,7 +37,7 @@ just microfab # source "${WORKSHOP_PATH}/_cfg/uf/org1admin.env" -just debugcc +just debugcc-go cd "${WORKSHOP_PATH}/contracts/asset-transfer-go" From 964a955ddc36ea7a97f6e0ce66772ea91ffbad68 Mon Sep 17 00:00:00 2001 From: nXtCyberNet Date: Tue, 28 Jul 2026 15:14:09 +0530 Subject: [PATCH 08/11] updated justfile Signed-off-by: nXtCyberNet --- .../contracts/asset-transfer-go/src/asset_transfer.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset_transfer.go b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset_transfer.go index e0bd930077..8beb796f9a 100644 --- a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset_transfer.go +++ b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/asset_transfer.go @@ -158,8 +158,6 @@ func (s *SmartContract) UpdateAsset( existingAsset.AppraisedValue = assetUpdate.AppraisedValue } - existingAsset.Owner = existingAsset.Owner - updatedAsset, err := NewAsset(existingAsset) if err != nil { return err From 9fde7d8334203cab668f63508103fb697322afb7 Mon Sep 17 00:00:00 2001 From: nXtCyberNet Date: Wed, 29 Jul 2026 16:56:05 +0530 Subject: [PATCH 09/11] fix: remove duplicate appdev-go job that silently broke CI workflow GitHub Actions silently ignores the entire workflow file when two jobs share the same ID. The appdev-go job was defined twice: - first at line 34 (old, using checkout@v6, no Go setup) - again at line 43 (newer, using checkout@v7, referencing a non-existent applications/trader-go/go.mod path) Fix: keep a single appdev-go job that: - uses actions/checkout@v7 (consistent with all other jobs) - sets up Go via actions/setup-go@v5 reading the go.mod from the actual chaincode location (contracts/asset-transfer-go/go.mod) - runs just test-appdev-go in full-stack-asset-transfer-guide Signed-off-by: nXtCyberNet --- .github/workflows/test-fsat.yaml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test-fsat.yaml b/.github/workflows/test-fsat.yaml index b6f84c133d..6540b00dca 100644 --- a/.github/workflows/test-fsat.yaml +++ b/.github/workflows/test-fsat.yaml @@ -31,23 +31,14 @@ jobs: - run: just test-appdev-typescript working-directory: full-stack-asset-transfer-guide - appdev-go: - runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }} - steps: - - uses: actions/checkout@v6 - - name: Set up Full Stack Runtime - uses: ./.github/actions/fsat-setup - - run: just test-appdev-go - working-directory: full-stack-asset-transfer-guide - appdev-go: runs-on: ${{ github.repository == 'hyperledger/fabric-samples' && 'fabric-ubuntu-22.04' || 'ubuntu-22.04' }} steps: - uses: actions/checkout@v7 - name: Set up Go - uses: actions/setup-go@v7 + uses: actions/setup-go@v5 with: - go-version-file: full-stack-asset-transfer-guide/applications/trader-go/go.mod + go-version-file: full-stack-asset-transfer-guide/contracts/asset-transfer-go/go.mod - name: Set up Full Stack Runtime uses: ./.github/actions/fsat-setup - run: just test-appdev-go From a934f44a694872613e8b8e88c0462af6ace92bd4 Mon Sep 17 00:00:00 2001 From: nXtCyberNet Date: Wed, 29 Jul 2026 17:01:30 +0530 Subject: [PATCH 10/11] fix: remove duplicate test-appdev-go recipe and stale e2e script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The justfile had two definitions of test-appdev-go: - line 115 → tests/10-appdev-go-e2e.sh (stale, used TypeScript client) - line 119 → tests/10-appdev-e2e-go.sh (newer, used Go trader-go client) just errors on any duplicate recipe, causing ALL recipes (including test-ansible, test-chaincode etc.) to fail immediately. Changes: - Remove stale tests/10-appdev-go-e2e.sh (was using trader-typescript) - Remove the first duplicate test-appdev-go recipe from justfile - Fix tests/10-appdev-e2e-go.sh to use debugcc-go + Go chaincode server (go run ./src) instead of debugcc + TypeScript chaincode server Signed-off-by: nXtCyberNet --- full-stack-asset-transfer-guide/justfile | 4 -- .../tests/10-appdev-e2e-go.sh | 10 +-- .../tests/10-appdev-go-e2e.sh | 69 ------------------- 3 files changed, 5 insertions(+), 78 deletions(-) delete mode 100755 full-stack-asset-transfer-guide/tests/10-appdev-go-e2e.sh diff --git a/full-stack-asset-transfer-guide/justfile b/full-stack-asset-transfer-guide/justfile index 8c7f9fab6a..f99245c865 100644 --- a/full-stack-asset-transfer-guide/justfile +++ b/full-stack-asset-transfer-guide/justfile @@ -111,10 +111,6 @@ test-chaincode: test-appdev-typescript: tests/10-appdev-typescript-e2e.sh -# Run an e2e test of the ApplicationDev scenario with the Go chaincode -test-appdev-go: - tests/10-appdev-go-e2e.sh - # Run an e2e test of the ApplicationDev Go scenario test-appdev-go: tests/10-appdev-e2e-go.sh diff --git a/full-stack-asset-transfer-guide/tests/10-appdev-e2e-go.sh b/full-stack-asset-transfer-guide/tests/10-appdev-e2e-go.sh index 5ea745ad16..fdf59e8e48 100755 --- a/full-stack-asset-transfer-guide/tests/10-appdev-e2e-go.sh +++ b/full-stack-asset-transfer-guide/tests/10-appdev-e2e-go.sh @@ -36,13 +36,13 @@ trap exitHook SIGINT SIGTERM EXIT just microfab source "${WORKSHOP_PATH}/_cfg/uf/org1admin.env" -just debugcc +just debugcc-go source "${WORKSHOP_PATH}/_cfg/uf/org1admin.env" -cd "${WORKSHOP_PATH}/contracts/asset-transfer-typescript" -npm install -npm run build -node_modules/.bin/fabric-chaincode-node server --chaincode-address="${CHAINCODE_SERVER_ADDRESS}" --chaincode-id="${CHAINCODE_ID}" & +cd "${WORKSHOP_PATH}/contracts/asset-transfer-go" +CORE_CHAINCODE_ID_NAME="${CHAINCODE_ID}" \ +CORE_CHAINCODE_SERVER_ADDRESS="${CHAINCODE_SERVER_ADDRESS}" \ +go run ./src & CHAINCODE_PID=$! sleep 5 diff --git a/full-stack-asset-transfer-guide/tests/10-appdev-go-e2e.sh b/full-stack-asset-transfer-guide/tests/10-appdev-go-e2e.sh deleted file mode 100755 index c64c519155..0000000000 --- a/full-stack-asset-transfer-guide/tests/10-appdev-go-e2e.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env bash - -set -v -euo pipefail - -# All tests run in the workshop root folder -cd "$(dirname "$0")"/.. - -export WORKSHOP_PATH="${PWD}" -export PATH="${WORKSHOP_PATH}/bin:${PATH}" -export FABRIC_CFG_PATH="${WORKSHOP_PATH}/config" - -"${WORKSHOP_PATH}/check.sh" - -CHAINCODE_PID= - -function exitHook() { - - # Shut down the Go chaincode process - [ -n "${CHAINCODE_PID}" ] && kill "${CHAINCODE_PID}" 2>/dev/null || true - - # Shut down Microfab - docker kill microfab &>/dev/null || true - - # Delete the network configuration and crypto material - rm -rf "${WORKSHOP_PATH}/_cfg" -} - -trap exitHook SIGINT SIGTERM EXIT - -# -# Start Microfab -# -just microfab - -# -# Configure the environment -# -source "${WORKSHOP_PATH}/_cfg/uf/org1admin.env" - -just debugcc-go - - -cd "${WORKSHOP_PATH}/contracts/asset-transfer-go" - -CORE_CHAINCODE_ID_NAME="${CHAINCODE_ID}" \ -CORE_CHAINCODE_SERVER_ADDRESS="${CHAINCODE_SERVER_ADDRESS}" \ -go run ./src & - -CHAINCODE_PID=$! - -sleep 5 - - -cd "${WORKSHOP_PATH}/applications/trader-typescript" - -export ENDPOINT=org1peer-api.127-0-0-1.nip.io:8080 -export MSP_ID=org1MSP -export CERTIFICATE=../../_cfg/uf/_msp/org1/org1admin/msp/signcerts/cert.pem -export PRIVATE_KEY=../../_cfg/uf/_msp/org1/org1admin/msp/keystore/cert_sk - -npm install - -npm start getAllAssets -npm start transact -npm start getAllAssets -npm start create banana bananaman yellow -npm start read banana -npm start delete banana -SIMULATED_FAILURE_COUNT=2 npm start listen \ No newline at end of file From afdbf8090f4bf94a2b7f5197ad018266038341ab Mon Sep 17 00:00:00 2001 From: nXtCyberNet Date: Wed, 29 Jul 2026 17:13:59 +0530 Subject: [PATCH 11/11] modified clientcommen Signed-off-by: nXtCyberNet --- .../contracts/asset-transfer-go/src/helper.go | 28 ++++--------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/helper.go b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/helper.go index 2153a7c71b..7baeed75ef 100644 --- a/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/helper.go +++ b/full-stack-asset-transfer-guide/contracts/asset-transfer-go/src/helper.go @@ -1,11 +1,8 @@ package main import ( - "crypto/x509" "encoding/json" - "encoding/pem" "fmt" - "strings" "github.com/hyperledger/fabric-chaincode-go/v2/pkg/cid" "github.com/hyperledger/fabric-chaincode-go/v2/pkg/statebased" @@ -60,37 +57,24 @@ func clientIdentifier( User: user, }, nil } - -// clientCommonName extracts the certificate Common Name. func clientCommonName( ctx contractapi.TransactionContextInterface, ) (string, error) { - id, err := cid.GetID(ctx.GetStub()) + c, err := cid.New(ctx.GetStub()) if err != nil { return "", err } - // Fabric IDs look like: - // x509::base64(cert)::base64(ca) - - parts := strings.Split(id, "::") - if len(parts) < 2 { - return "", fmt.Errorf("invalid client identity") - } - - certPEM := []byte(parts[1]) - - block, _ := pem.Decode(certPEM) - if block == nil { - return "", fmt.Errorf("failed to decode certificate") - } - - cert, err := x509.ParseCertificate(block.Bytes) + cert, err := c.GetX509Certificate() if err != nil { return "", err } + if cert == nil { + return "", fmt.Errorf("client certificate not found") + } + return cert.Subject.CommonName, nil }