se eliminaron mas ejemplos que no necesitariamos

This commit is contained in:
luc662 2025-04-02 20:52:15 +00:00
parent 5f7ddaeb4a
commit 6c9d40ddf0
166 changed files with 0 additions and 33126 deletions

View file

@ -1,173 +0,0 @@
# Attribute based access control
The `asset-transfer-abac` sample demonstrates the use of Attribute-based access control within the context of a simple asset transfer scenario. The sample also uses authorization based on individual client identities to allow the users that interact with the network to own assets on the blockchain ledger.
Attribute-Based Access Control (ABAC) refers to the ability to restrict access to certain functionality within a smart contract based on the attributes within a users certificate. For example, you may want certain functions within a smart contract to be used only by application administrators. ABAC allows organizations to provide elevated access to certain users without requiring those users be administrators of the network. For more information, see [Attribute-based access control](https://hyperledger-fabric-ca.readthedocs.io/en/latest/users-guide.html#attribute-based-access-control) in the Fabric CA users guide.
The `asset-transfer-abac` smart contract allows you to create assets that can be updated or transferred by the asset owner. However, the ability to create or remove assets from the ledger is restricted to identities with the `abac.creator=true` attribute. The identity that creates the asset is assigned as the asset owner. Only the owner can transfer the asset to a new owner or update the asset properties. In the course of the tutorial, we will use the Fabric CA client to create identities with the attribute required to create a new asset. We will then transfer the asset to another identity and demonstrate how the `GetID()` function can be used to enforce asset ownership. We will then use the owner identity to delete the asset. Both Attribute based access control and the `GetID()` function are provided by the [Client Identity Chaincode Library](https://github.com/hyperledger/fabric-chaincode-go/blob/master/pkg/cid/README.md).
## Start the network and deploy the smart contract
We can use the Fabric test network to deploy and interact with the `asset-transfer-abac` smart contract. Run the following command to change into the test network directory and bring down any running nodes:
```
cd fabric-samples/test-network
./network.sh down
```
Run the following command to deploy the test network using Certificate Authorities:
```
./network.sh up createChannel -ca
```
You can then use the test network script to deploy the `asset-transfer-abac` smart contract to a channel on the network:
```
./network.sh deployCC -ccn abac -ccp ../asset-transfer-abac/chaincode-go/ -ccl go
```
## Register identities with attributes
We can use the one of the test network Certificate Authorities to register and enroll identities with the attribute of `abac.creator=true`. First, we need to set the following environment variables in order to use the Fabric CA client.
```
export PATH=${PWD}/../bin:${PWD}:$PATH
export FABRIC_CFG_PATH=$PWD/../config/
```
We will create the identities using the Org1 CA. Set the Fabric CA client home to the MSP of the Org1 CA admin:
```
export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org1.example.com/
```
There are two ways to generate certificates with attributes added. We will use both methods and create two identities in the process. The first method is to specify that the attribute be added to the certificate by default when the identity is registered. The following command will register an identity named creator1 with the attribute of `abac.creator=true`.
```
fabric-ca-client register --id.name creator1 --id.secret creator1pw --id.type client --id.affiliation org1 --id.attrs 'abac.creator=true:ecert' --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"
```
The `ecert` suffix adds the attribute to the certificate automatically when the identity is enrolled. As a result, the following enroll command will contain the attribute that was provided in the registration command.
```
fabric-ca-client enroll -u https://creator1:creator1pw@localhost:7054 --caname ca-org1 -M "${PWD}/organizations/peerOrganizations/org1.example.com/users/creator1@org1.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"
```
Now that we have enrolled the identity, run the command below to copy the Node OU configuration file into the creator1 MSP folder.
```
cp "${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org1.example.com/users/creator1@org1.example.com/msp/config.yaml"
```
The second method is to request that the attribute be added upon enrollment. The following command will register an identity named creator2 with the same `abac.creator` attribute.
```
fabric-ca-client register --id.name creator2 --id.secret creator2pw --id.type client --id.affiliation org1 --id.attrs 'abac.creator=true:' --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"
```
The following enroll command will add the attribute to the certificate:
```
fabric-ca-client enroll -u https://creator2:creator2pw@localhost:7054 --caname ca-org1 --enrollment.attrs "abac.creator" -M "${PWD}/organizations/peerOrganizations/org1.example.com/users/creator2@org1.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"
```
Run the command below to copy the Node OU configuration file into the creator2 MSP folder.
```
cp "${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org1.example.com/users/creator2@org1.example.com/msp/config.yaml"
```
## Create an asset
You can use either identity with the `abac.creator=true` attribute to create an asset using the `asset-transfer-abac` smart contract. We will set the following environment variables to use the first identity that was generated, creator1:
```
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID=Org1MSP
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/creator1@org1.example.com/msp
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_ADDRESS=localhost:7051
export TARGET_TLS_OPTIONS=(-o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt")
```
Run the following command to create Asset1:
```
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n abac -c '{"function":"CreateAsset","Args":["Asset1","blue","20","100"]}'
```
You can use the command below to query the asset on the ledger:
```
peer chaincode query -C mychannel -n abac -c '{"function":"ReadAsset","Args":["Asset1"]}'
```
The result will list the creator1 identity as the asset owner. The `GetID()` API reads the name and issuer from the certificate of the identity that submitted the transaction and assigns that identity as the asset owner:
```
{"ID":"Asset1","color":"blue","size":20,"owner":"x509::CN=creator1,OU=client+OU=org1,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US","appraisedValue":100}
```
## Transfer the asset
As the owner of Asset1, the creator1 identity has the ability to transfer the asset to another owner. In order to transfer the asset, the owner needs to provide the name and issuer of the new owner to the `TransferAsset` function. The `asset-transfer-abac` smart contract has a `GetSubmittingClientIdentity` function that allows users to retrieve their certificate information and provide it to the asset owner out of band (we omit this step). Issue the command below to transfer Asset1 to the user1 identity from Org1 that was created when the test network was deployed:
```
export RECIPIENT="x509::CN=user1,OU=client,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US"
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n abac -c '{"function":"TransferAsset","Args":["Asset1","'"$RECIPIENT"'"]}'
```
Query the ledger to verify that the asset has a new owner:
```
peer chaincode query -C mychannel -n abac -c '{"function":"ReadAsset","Args":["Asset1"]}'
```
We can see that Asset1 with is now owned by User1:
```
{"ID":"Asset1","color":"blue","size":20,"owner":"x509::CN=user1,OU=client,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US","appraisedValue":100}
```
## Update the asset
Now that the asset has been transferred, the new owner can update the asset properties. The smart contract uses the `GetID()` API to ensure that the update is being submitted by the asset owner. To demonstrate the difference between identity and attribute based access control, lets try to update the asset using the creator1 identity first:
```
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n abac -c '{"function":"UpdateAsset","Args":["Asset1","green","20","100"]}'
```
Even though creator1 can create new assets, the smart contract detects that the transaction was not submitted by the identity that owns the asset, user1. The command returns the following error:
```
Error: endorsement failure during invoke. response: status:500 message:"submitting client not authorized to update asset, does not own asset"
```
Run the following command to operate as the asset owner by setting the MSP path to User1:
```
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp
```
We can now update the asset. Run the following command to change the asset color from blue to green. All other aspects of the asset will remain unchanged.
```
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n abac -c '{"function":"UpdateAsset","Args":["Asset1","green","20","100"]}'
```
Run the query command again to verify that the asset has changed color:
```
peer chaincode query -C mychannel -n abac -c '{"function":"ReadAsset","Args":["Asset1"]}'
```
The result will display that Asset1 is now green:
```
{"ID":"Asset1","color":"green","size":20,"owner":"x509::CN=user1,OU=client,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US","appraisedValue":100}
```
## Delete the asset
The owner also has the ability to delete the asset. Run the following command to remove Asset1 from the ledger:
```
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n abac -c '{"function":"DeleteAsset","Args":["Asset1"]}'
```
If you query the ledger once more, you will see that Asset1 no longer exists:
```
peer chaincode query -C mychannel -n abac -c '{"function":"ReadAsset","Args":["Asset1"]}'
```
While we are operating as User1, we can demonstrate attribute based access control by trying to create an asset using an identity without the `abac.creator=true` attribute. Run the following command to try to create Asset1 as User1:
```
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n abac -c '{"function":"CreateAsset","Args":["Asset2","red","20","100"]}'
```
The smart contract will return the following error:
```
Error: endorsement failure during invoke. response: status:500 message:"submitting client not authorized to create asset, does not have abac.creator role"
```
## Clean up
When you are finished, you can run the following command to bring down the test network:
```
./network.sh down
```

View file

@ -1,32 +0,0 @@
module github.com/hyperledger/fabric-samples/asset-transfer-abac/chaincode-go
go 1.21
require github.com/hyperledger/fabric-contract-api-go/v2 v2.0.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/gobuffalo/envy v1.10.2 // indirect
github.com/gobuffalo/packd v1.0.2 // indirect
github.com/gobuffalo/packr v1.30.1 // indirect
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0-20240618210511-f7903324a8af // indirect
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/rogpeppe/go-internal v1.12.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
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
google.golang.org/grpc v1.64.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View file

@ -1,132 +0,0 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
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/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/envy v1.10.2 h1:EIi03p9c3yeuRCFPOKcSfajzkLb3hrRjEpHGI8I2Wo4=
github.com/gobuffalo/envy v1.10.2/go.mod h1:qGAGwdvDsaEtPhfBzb3o0SfDea8ByGn9j8bKmVft9z8=
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
github.com/gobuffalo/packd v1.0.2 h1:Yg523YqnOxGIWCp69W12yYBKsoChwI7mtu6ceM9Bwfw=
github.com/gobuffalo/packd v1.0.2/go.mod h1:sUc61tDqGMXON80zpKGp92lDb86Km28jfvX7IAyxFT8=
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
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/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0-20240618210511-f7903324a8af h1:WT4NjX7Uk03GSeH++jF3a0wp4FhybTM86zDPCETvmSk=
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0-20240618210511-f7903324a8af/go.mod h1:f/ER25FaBepxJugwpLhbD2hLAoZaZEVqkBjOcHjw72Y=
github.com/hyperledger/fabric-contract-api-go/v2 v2.0.0 h1:IDiCGVOBlRd6zpL0Y+f6V7IpBqa4/Z5JAK9SF7a5ea8=
github.com/hyperledger/fabric-contract-api-go/v2 v2.0.0/go.mod h1:pdqhe7ALf4lmXgQdprCyNWYdnCPxgj02Vhf8JF5w8po=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 h1:Xpd6fzG/KjAOHJsq7EQXY2l+qi/y8muxBaY7R6QWABk=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3/go.mod h1:2pq0ui6ZWA0cC8J+eCErgnMDCS1kPOEYVY+06ZAK0qE=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
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/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
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/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
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=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -1,214 +0,0 @@
package abac
import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
)
// SmartContract provides functions for managing an Asset
type SmartContract struct {
contractapi.Contract
}
// Asset describes basic details of what makes up a simple asset
type Asset struct {
ID string `json:"ID"`
Color string `json:"color"`
Size int `json:"size"`
Owner string `json:"owner"`
AppraisedValue int `json:"appraisedValue"`
}
// CreateAsset issues a new asset to the world state with given details.
func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, appraisedValue int) error {
// Demonstrate the use of Attribute-Based Access Control (ABAC) by checking
// to see if the caller has the "abac.creator" attribute with a value of true;
// if not, return an error.
err := ctx.GetClientIdentity().AssertAttributeValue("abac.creator", "true")
if err != nil {
return fmt.Errorf("submitting client not authorized to create asset, does not have abac.creator role")
}
exists, err := s.AssetExists(ctx, id)
if err != nil {
return err
}
if exists {
return fmt.Errorf("the asset %s already exists", id)
}
// Get ID of submitting client identity
clientID, err := s.GetSubmittingClientIdentity(ctx)
if err != nil {
return err
}
asset := Asset{
ID: id,
Color: color,
Size: size,
Owner: clientID,
AppraisedValue: appraisedValue,
}
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
return ctx.GetStub().PutState(id, assetJSON)
}
// UpdateAsset updates an existing asset in the world state with provided parameters.
func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, newColor string, newSize int, newValue int) error {
asset, err := s.ReadAsset(ctx, id)
if err != nil {
return err
}
clientID, err := s.GetSubmittingClientIdentity(ctx)
if err != nil {
return err
}
if clientID != asset.Owner {
return fmt.Errorf("submitting client not authorized to update asset, does not own asset")
}
asset.Color = newColor
asset.Size = newSize
asset.AppraisedValue = newValue
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
return ctx.GetStub().PutState(id, assetJSON)
}
// DeleteAsset deletes a given asset from the world state.
func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error {
asset, err := s.ReadAsset(ctx, id)
if err != nil {
return err
}
clientID, err := s.GetSubmittingClientIdentity(ctx)
if err != nil {
return err
}
if clientID != asset.Owner {
return fmt.Errorf("submitting client not authorized to update asset, does not own asset")
}
return ctx.GetStub().DelState(id)
}
// TransferAsset updates the owner field of asset with given id in world state.
func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string) error {
asset, err := s.ReadAsset(ctx, id)
if err != nil {
return err
}
clientID, err := s.GetSubmittingClientIdentity(ctx)
if err != nil {
return err
}
if clientID != asset.Owner {
return fmt.Errorf("submitting client not authorized to update asset, does not own asset")
}
asset.Owner = newOwner
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
return ctx.GetStub().PutState(id, assetJSON)
}
// ReadAsset returns the asset stored in the world state with given id.
func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {
assetJSON, err := ctx.GetStub().GetState(id)
if err != nil {
return nil, fmt.Errorf("failed to read from world state: %v", err)
}
if assetJSON == nil {
return nil, fmt.Errorf("the asset %s does not exist", id)
}
var asset Asset
err = json.Unmarshal(assetJSON, &asset)
if err != nil {
return nil, err
}
return &asset, nil
}
// GetAllAssets returns all assets found in world state
func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) {
// range query with empty string for startKey and endKey does an
// open-ended query of 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
err = json.Unmarshal(queryResponse.Value, &asset)
if err != nil {
return nil, err
}
assets = append(assets, &asset)
}
return assets, nil
}
// AssetExists returns true when asset with given ID exists in world state
func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
assetJSON, err := ctx.GetStub().GetState(id)
if err != nil {
return false, fmt.Errorf("failed to read from world state: %v", err)
}
return assetJSON != nil, nil
}
// GetSubmittingClientIdentity returns the name and issuer of the identity that
// invokes the smart contract. This function base64 decodes the identity string
// before returning the value to the client or smart contract.
func (s *SmartContract) GetSubmittingClientIdentity(ctx contractapi.TransactionContextInterface) (string, error) {
b64ID, err := ctx.GetClientIdentity().GetID()
if err != nil {
return "", fmt.Errorf("Failed to read clientID: %v", err)
}
decodeID, err := base64.StdEncoding.DecodeString(b64ID)
if err != nil {
return "", fmt.Errorf("failed to base64 decode clientID: %v", err)
}
return string(decodeID), nil
}

View file

@ -1,23 +0,0 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"log"
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
abac "github.com/hyperledger/fabric-samples/asset-transfer-abac/chaincode-go/smart-contract"
)
func main() {
abacSmartContract, err := contractapi.NewChaincode(&abac.SmartContract{})
if err != nil {
log.Panicf("Error creating abac chaincode: %v", err)
}
if err := abacSmartContract.Start(); err != nil {
log.Panicf("Error starting abac chaincode: %v", err)
}
}

View file

@ -1,84 +0,0 @@
# Asset transfer events sample
The asset transfer events sample demonstrates:
- Emitting chaincode events from smart contract transaction functions.
- Receiving chaincode events in a client application.
- Replaying previous chaincode events in a client application.
Events are published when a block is committed to the ledger.
For more information about event services on per-channel basis, visit the
[Channel-based event service](https://hyperledger-fabric.readthedocs.io/en/latest/peer_event_services.html)
page in the Fabric documentation.
## About the sample
This sample includes smart contract and application code in multiple languages. In a use-case similar to basic asset transfer (see [asset-transfer-basic](../asset-transfer-basic) folder) this sample shows sending and receiving of events during create / update / delete of an asset, and during transfer of an asset to a new owner.
### Application
Follow the execution flow in the client application code, and corresponding output on running the application. Pay attention to the sequence of:
- Transaction invocations (console output like "**--> Submit transaction**").
- Events received by the application (console output like "**<-- Chaincode event received**").
Notice that events will be received by the listener after the application code submits the transaction and it is committed to the ledger, but during other application activity unrelated to the event.
### Smart Contract
The smart contract (in folder `chaincode-xyz`) implements the following functions to support the application:
- CreateAsset
- ReadAsset
- UpdateAsset
- DeleteAsset
- TransferAsset
Note that the asset transfer implemented by the smart contract is a simplified scenario, without ownership validation, meant only to demonstrate the use of sending and receiving events.
## Running the sample
Like other samples, the Fabric test network is used to deploy and run this sample. Follow these steps in order:
1. Create the test network and a channel (from the `test-network` folder).
```
./network.sh up createChannel -c mychannel -ca
```
1. Deploy one of the smart contract implementations (from the `test-network` folder).
```
# To deploy the Go chaincode implementation
./network.sh deployCC -ccn events -ccp ../asset-transfer-events/chaincode-go/ -ccl go -ccep "OR('Org1MSP.peer','Org2MSP.peer')"
# To deploy the JavaScript chaincode implementation
./network.sh deployCC -ccn events -ccp ../asset-transfer-events/chaincode-javascript/ -ccl javascript -ccep "OR('Org1MSP.peer','Org2MSP.peer')"
# To deploy the Java chaincode implementation
./network.sh deployCC -ccn events -ccp ../asset-transfer-events/chaincode-java/ -ccl java -ccep "OR('Org1MSP.peer','Org2MSP.peer')"
```
1. Run the application (from the `asset-transfer-events` folder).
```
# To run the Go sample application
cd application-gateway-go
go run .
# To run the TypeScript sample application
cd application-gateway-typescript
npm install
npm start
# To run the Java sample application
cd application-gateway-java
./gradlew run
```
## Clean up
When you are finished, you can bring down the test network (from the `test-network` folder). The command will remove all the nodes of the test network, and delete any ledger data that you created.
```
./network.sh down
```

View file

@ -1,170 +0,0 @@
/*
Copyright 2022 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/hyperledger/fabric-gateway/pkg/client"
)
const (
channelName = "mychannel"
chaincodeName = "events"
)
var now = time.Now()
var assetID = fmt.Sprintf("asset%d", now.Unix()*1e3+int64(now.Nanosecond())/1e6)
func main() {
clientConnection := newGrpcConnection()
defer clientConnection.Close()
id := newIdentity()
sign := newSign()
gateway, err := client.Connect(
id,
client.WithSign(sign),
client.WithClientConnection(clientConnection),
client.WithEvaluateTimeout(5*time.Second),
client.WithEndorseTimeout(15*time.Second),
client.WithSubmitTimeout(5*time.Second),
client.WithCommitStatusTimeout(1*time.Minute),
)
if err != nil {
panic(err)
}
defer gateway.Close()
network := gateway.GetNetwork(channelName)
contract := network.GetContract(chaincodeName)
// Context used for event listening
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Listen for events emitted by subsequent transactions
startChaincodeEventListening(ctx, network)
firstBlockNumber := createAsset(contract)
updateAsset(contract)
transferAsset(contract)
deleteAsset(contract)
// Replay events from the block containing the first transaction
replayChaincodeEvents(ctx, network, firstBlockNumber)
}
func startChaincodeEventListening(ctx context.Context, network *client.Network) {
fmt.Println("\n*** Start chaincode event listening")
events, err := network.ChaincodeEvents(ctx, chaincodeName)
if err != nil {
panic(fmt.Errorf("failed to start chaincode event listening: %w", err))
}
go func() {
for event := range events {
asset := formatJSON(event.Payload)
fmt.Printf("\n<-- Chaincode event received: %s - %s\n", event.EventName, asset)
}
}()
}
func formatJSON(data []byte) string {
var result bytes.Buffer
if err := json.Indent(&result, data, "", " "); err != nil {
panic(fmt.Errorf("failed to parse JSON: %w", err))
}
return result.String()
}
func createAsset(contract *client.Contract) uint64 {
fmt.Printf("\n--> Submit transaction: CreateAsset, %s owned by Sam with appraised value 100\n", assetID)
_, commit, err := contract.SubmitAsync("CreateAsset", client.WithArguments(assetID, "blue", "10", "Sam", "100"))
if err != nil {
panic(fmt.Errorf("failed to submit transaction: %w", err))
}
status, err := commit.Status()
if err != nil {
panic(fmt.Errorf("failed to get transaction commit status: %w", err))
}
if !status.Successful {
panic(fmt.Errorf("failed to commit transaction with status code %v", status.Code))
}
fmt.Println("\n*** CreateAsset committed successfully")
return status.BlockNumber
}
func updateAsset(contract *client.Contract) {
fmt.Printf("\n--> Submit transaction: UpdateAsset, %s update appraised value to 200\n", assetID)
_, err := contract.SubmitTransaction("UpdateAsset", assetID, "blue", "10", "Sam", "200")
if err != nil {
panic(fmt.Errorf("failed to submit transaction: %w", err))
}
fmt.Println("\n*** UpdateAsset committed successfully")
}
func transferAsset(contract *client.Contract) {
fmt.Printf("\n--> Submit transaction: TransferAsset, %s to Mary\n", assetID)
_, err := contract.SubmitTransaction("TransferAsset", assetID, "Mary")
if err != nil {
panic(fmt.Errorf("failed to submit transaction: %w", err))
}
fmt.Println("\n*** TransferAsset committed successfully")
}
func deleteAsset(contract *client.Contract) {
fmt.Printf("\n--> Submit transaction: DeleteAsset, %s\n", assetID)
_, err := contract.SubmitTransaction("DeleteAsset", assetID)
if err != nil {
panic(fmt.Errorf("failed to submit transaction: %w", err))
}
fmt.Println("\n*** DeleteAsset committed successfully")
}
func replayChaincodeEvents(ctx context.Context, network *client.Network, startBlock uint64) {
fmt.Println("\n*** Start chaincode event replay")
events, err := network.ChaincodeEvents(ctx, chaincodeName, client.WithStartBlock(startBlock))
if err != nil {
panic(fmt.Errorf("failed to start chaincode event listening: %w", err))
}
for {
select {
case <-time.After(10 * time.Second):
panic(errors.New("timeout waiting for event replay"))
case event := <-events:
asset := formatJSON(event.Payload)
fmt.Printf("\n<-- Chaincode event replayed: %s - %s\n", event.EventName, asset)
if event.EventName == "DeleteAsset" {
// Reached the last submitted transaction so return to stop listening for events
return
}
}
}
}

View file

@ -1,114 +0,0 @@
/*
Copyright 2022 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"crypto/x509"
"fmt"
"os"
"path"
"github.com/hyperledger/fabric-gateway/pkg/identity"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
const (
mspID = "Org1MSP"
cryptoPath = "../../test-network/organizations/peerOrganizations/org1.example.com"
certPath = cryptoPath + "/users/User1@org1.example.com/msp/signcerts"
keyPath = cryptoPath + "/users/User1@org1.example.com/msp/keystore"
tlsCertPath = cryptoPath + "/peers/peer0.org1.example.com/tls/ca.crt"
peerEndpoint = "dns:///localhost:7051"
gatewayPeer = "peer0.org1.example.com"
)
// newGrpcConnection creates a gRPC connection to the Gateway server.
func newGrpcConnection() *grpc.ClientConn {
certificatePEM, err := os.ReadFile(tlsCertPath)
if err != nil {
panic(fmt.Errorf("failed to read TLS certifcate file: %w", err))
}
certificate, err := identity.CertificateFromPEM(certificatePEM)
if err != nil {
panic(err)
}
certPool := x509.NewCertPool()
certPool.AddCert(certificate)
transportCredentials := credentials.NewClientTLSFromCert(certPool, gatewayPeer)
connection, err := grpc.NewClient(peerEndpoint, grpc.WithTransportCredentials(transportCredentials))
if err != nil {
panic(fmt.Errorf("failed to create gRPC connection: %w", err))
}
return connection
}
// newIdentity creates a client identity for this Gateway connection using an X.509 certificate.
func newIdentity() *identity.X509Identity {
certificatePEM, err := readFirstFile(certPath)
if err != nil {
panic(fmt.Errorf("failed to read certificate file: %w", err))
}
certificate, err := identity.CertificateFromPEM(certificatePEM)
if err != nil {
panic(err)
}
id, err := identity.NewX509Identity(mspID, certificate)
if err != nil {
panic(err)
}
return id
}
func loadCertificate(filename string) (*x509.Certificate, error) {
certificatePEM, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("failed to read certificate file: %w", err)
}
return identity.CertificateFromPEM(certificatePEM)
}
// newSign creates a function that generates a digital signature from a message digest using a private key.
func newSign() identity.Sign {
privateKeyPEM, err := readFirstFile(keyPath)
if err != nil {
panic(fmt.Errorf("failed to read private key file: %w", err))
}
privateKey, err := identity.PrivateKeyFromPEM(privateKeyPEM)
if err != nil {
panic(err)
}
sign, err := identity.NewPrivateKeySign(privateKey)
if err != nil {
panic(err)
}
return sign
}
func readFirstFile(dirPath string) ([]byte, error) {
dir, err := os.Open(dirPath)
if err != nil {
return nil, err
}
fileNames, err := dir.Readdirnames(1)
if err != nil {
return nil, err
}
return os.ReadFile(path.Join(dirPath, fileNames[0]))
}

View file

@ -1,19 +0,0 @@
module assetTransfer
go 1.21
require (
github.com/hyperledger/fabric-gateway v1.5.0
google.golang.org/grpc v1.63.2
)
require (
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect
google.golang.org/protobuf v1.33.0 // indirect
)

View file

@ -1,32 +0,0 @@
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/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-gateway v1.5.0 h1:JChlqtJNm2479Q8YWJ6k8wwzOiu2IRrV3K8ErsQmdTU=
github.com/hyperledger/fabric-gateway v1.5.0/go.mod h1:v13OkXAp7pKi4kh6P6epn27SyivRbljr8Gkfy8JlbtM=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 h1:Xpd6fzG/KjAOHJsq7EQXY2l+qi/y8muxBaY7R6QWABk=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3/go.mod h1:2pq0ui6ZWA0cC8J+eCErgnMDCS1kPOEYVY+06ZAK0qE=
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
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/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -1,26 +0,0 @@
plugins {
// Apply the application plugin to add support for building a CLI application.
id 'application'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.hyperledger.fabric:fabric-gateway:1.4.0'
compileOnly 'io.grpc:grpc-api:1.59.0'
runtimeOnly 'io.grpc:grpc-netty-shaded:1.59.0'
implementation 'com.google.code.gson:gson:2.10.1'
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
}
}
application {
// Define the main class for the application.
mainClass = 'App'
}

View file

@ -1,7 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View file

@ -1,249 +0,0 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View file

@ -1,92 +0,0 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View file

@ -1,10 +0,0 @@
/*
* This file was generated by the Gradle 'init' task.
*
* The settings file is used to specify which projects to include in your build.
*
* Detailed information about configuring a multi-project build in Gradle can be found
* in the user manual at https://docs.gradle.org/6.5/userguide/multi_project_builds.html
*/
rootProject.name = 'asset-transfer-events'

View file

@ -1,173 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParser;
import io.grpc.Status;
import org.hyperledger.fabric.client.ChaincodeEvent;
import org.hyperledger.fabric.client.CloseableIterator;
import org.hyperledger.fabric.client.CommitException;
import org.hyperledger.fabric.client.CommitStatusException;
import org.hyperledger.fabric.client.Contract;
import org.hyperledger.fabric.client.EndorseException;
import org.hyperledger.fabric.client.Gateway;
import org.hyperledger.fabric.client.GatewayRuntimeException;
import org.hyperledger.fabric.client.Network;
import org.hyperledger.fabric.client.SubmitException;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public final class App implements AutoCloseable {
private static final String channelName = "mychannel";
private static final String chaincodeName = "events";
private final Network network;
private final Contract contract;
private final String assetId = "asset" + Instant.now().toEpochMilli();
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
private final ExecutorService executor = Executors.newCachedThreadPool();
public static void main(final String[] args) throws Exception {
var grpcChannel = Connections.newGrpcConnection();
var builder = Gateway.newInstance()
.identity(Connections.newIdentity())
.signer(Connections.newSigner())
.connection(grpcChannel)
.evaluateOptions(options -> options.withDeadlineAfter(5, TimeUnit.SECONDS))
.endorseOptions(options -> options.withDeadlineAfter(15, TimeUnit.SECONDS))
.submitOptions(options -> options.withDeadlineAfter(5, TimeUnit.SECONDS))
.commitStatusOptions(options -> options.withDeadlineAfter(1, TimeUnit.MINUTES));
try (var gateway = builder.connect(); var app = new App(gateway)) {
app.run();
} finally {
grpcChannel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
}
}
public App(final Gateway gateway) {
network = gateway.getNetwork(channelName);
contract = network.getContract(chaincodeName);
}
public void run() throws EndorseException, SubmitException, CommitStatusException, CommitException {
// Listen for events emitted by subsequent transactions, stopping when the try-with-resources block exits
try (var eventSession = startChaincodeEventListening()) {
var firstBlockNumber = createAsset();
updateAsset();
transferAsset();
deleteAsset();
// Replay events from the block containing the first transaction
replayChaincodeEvents(firstBlockNumber);
}
}
private CloseableIterator<ChaincodeEvent> startChaincodeEventListening() {
System.out.println("\n*** Start chaincode event listening");
var eventIter = network.getChaincodeEvents(chaincodeName);
executor.execute(() -> readEvents(eventIter));
return eventIter;
}
private void readEvents(final CloseableIterator<ChaincodeEvent> eventIter) {
try {
eventIter.forEachRemaining(event -> {
var payload = prettyJson(event.getPayload());
System.out.println("\n<-- Chaincode event received: " + event.getEventName() + " - " + payload);
});
} catch (GatewayRuntimeException e) {
if (e.getStatus().getCode() != Status.Code.CANCELLED) {
throw e;
}
}
}
private String prettyJson(final byte[] json) {
return prettyJson(new String(json, StandardCharsets.UTF_8));
}
private String prettyJson(final String json) {
var parsedJson = JsonParser.parseString(json);
return gson.toJson(parsedJson);
}
private long createAsset() throws EndorseException, SubmitException, CommitStatusException {
System.out.println("\n--> Submit transaction: CreateAsset, " + assetId + " owned by Sam with appraised value 100");
var commit = contract.newProposal("CreateAsset")
.addArguments(assetId, "blue", "10", "Sam", "100")
.build()
.endorse()
.submitAsync();
var status = commit.getStatus();
if (!status.isSuccessful()) {
throw new RuntimeException("failed to commit transaction with status code " + status.getCode());
}
System.out.println("\n*** CreateAsset committed successfully");
return status.getBlockNumber();
}
private void updateAsset() throws EndorseException, SubmitException, CommitStatusException, CommitException {
System.out.println("\n--> Submit transaction: UpdateAsset, " + assetId + " update appraised value to 200");
contract.submitTransaction("UpdateAsset", assetId, "blue", "10", "Sam", "200");
System.out.println("\n*** UpdateAsset committed successfully");
}
private void transferAsset() throws EndorseException, SubmitException, CommitStatusException, CommitException {
System.out.println("\n--> Submit transaction: TransferAsset, " + assetId + " to Mary");
contract.submitTransaction("TransferAsset", assetId, "Mary");
System.out.println("\n*** TransferAsset committed successfully");
}
private void deleteAsset() throws EndorseException, SubmitException, CommitStatusException, CommitException {
System.out.println("\n--> Submit transaction: DeleteAsset, " + assetId);
contract.submitTransaction("DeleteAsset", assetId);
System.out.println("\n*** DeleteAsset committed successfully");
}
private void replayChaincodeEvents(final long startBlock) {
System.out.println("\n*** Start chaincode event replay");
var request = network.newChaincodeEventsRequest(chaincodeName)
.startBlock(startBlock)
.build();
try (var eventIter = request.getEvents()) {
while (eventIter.hasNext()) {
var event = eventIter.next();
var payload = prettyJson(event.getPayload());
System.out.println("\n<-- Chaincode event replayed: " + event.getEventName() + " - " + payload);
if (event.getEventName().equals("DeleteAsset")) {
// Reached the last submitted transaction so break to close the iterator and stop listening for events
break;
}
}
}
}
@Override
public void close() throws Exception {
executor.shutdownNow();
}
}

View file

@ -1,70 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import io.grpc.Grpc;
import io.grpc.ManagedChannel;
import io.grpc.TlsChannelCredentials;
import org.hyperledger.fabric.client.identity.Identities;
import org.hyperledger.fabric.client.identity.Identity;
import org.hyperledger.fabric.client.identity.Signer;
import org.hyperledger.fabric.client.identity.Signers;
import org.hyperledger.fabric.client.identity.X509Identity;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.cert.CertificateException;
public final class Connections {
// Path to crypto materials.
private static final Path cryptoPath = Paths.get("..", "..", "test-network", "organizations", "peerOrganizations", "org1.example.com");
// Path to user certificate.
private static final Path certDirPath = cryptoPath.resolve(Paths.get("users", "User1@org1.example.com", "msp", "signcerts"));
// Path to user private key directory.
private static final Path keyDirPath = cryptoPath.resolve(Paths.get("users", "User1@org1.example.com", "msp", "keystore"));
// Path to peer tls certificate.
private static final Path tlsCertPath = cryptoPath.resolve(Paths.get("peers", "peer0.org1.example.com", "tls", "ca.crt"));
// Gateway peer end point.
private static final String peerEndpoint = "localhost:7051";
private static final String overrideAuth = "peer0.org1.example.com";
private static final String mspID = "Org1MSP";
private Connections() {
// Private constructor to prevent instantiation
}
public static ManagedChannel newGrpcConnection() throws IOException {
var credentials = TlsChannelCredentials.newBuilder()
.trustManager(tlsCertPath.toFile())
.build();
return Grpc.newChannelBuilder(peerEndpoint, credentials)
.overrideAuthority(overrideAuth)
.build();
}
public static Identity newIdentity() throws IOException, CertificateException {
try (var certReader = Files.newBufferedReader(getFirstFilePath(certDirPath))) {
var certificate = Identities.readX509Certificate(certReader);
return new X509Identity(mspID, certificate);
}
}
public static Signer newSigner() throws IOException, InvalidKeyException {
try (var keyReader = Files.newBufferedReader(getFirstFilePath(keyDirPath))) {
var privateKey = Identities.readPrivateKey(keyReader);
return Signers.newPrivateKeySigner(privateKey);
}
}
private static Path getFirstFilePath(Path dirPath) throws IOException {
try (var keyFiles = Files.list(dirPath)) {
return keyFiles.findFirst().orElseThrow();
}
}}

View file

@ -1,14 +0,0 @@
#
# SPDX-License-Identifier: Apache-2.0
#
# Coverage directory used by tools like istanbul
coverage
# Dependency directories
node_modules/
jspm_packages/
# Compiled TypeScript files
dist

View file

@ -1 +0,0 @@
engine-strict=true

View file

@ -1,13 +0,0 @@
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(js.configs.recommended, ...tseslint.configs.strictTypeChecked, {
languageOptions: {
ecmaVersion: 2023,
sourceType: 'module',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: import.meta.dirname,
},
},
});

View file

@ -1,33 +0,0 @@
{
"name": "asset-transfer-events",
"version": "1.0.0",
"description": "Asset Transfer Events Application implemented in typeScript using fabric-gateway",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"engines": {
"node": ">=18"
},
"scripts": {
"build": "tsc",
"build:watch": "tsc -w",
"lint": "eslint src",
"prepare": "npm run build",
"pretest": "npm run lint",
"start": "node dist/app.js"
},
"engineStrict": true,
"author": "Hyperledger",
"license": "Apache-2.0",
"dependencies": {
"@grpc/grpc-js": "^1.10",
"@hyperledger/fabric-gateway": "^1.5"
},
"devDependencies": {
"@eslint/js": "^9.3.0",
"@tsconfig/node18": "^18.2.2",
"@types/node": "^18.18.6",
"eslint": "^8.57.0",
"typescript": "~5.4",
"typescript-eslint": "^7.13.0"
}
}

View file

@ -1,157 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import * as grpc from '@grpc/grpc-js';
import { ChaincodeEvent, CloseableAsyncIterable, connect, Contract, GatewayError, Network } from '@hyperledger/fabric-gateway';
import { TextDecoder } from 'util';
import { newGrpcConnection, newIdentity, newSigner } from './connect';
const channelName = 'mychannel';
const chaincodeName = 'events';
const utf8Decoder = new TextDecoder();
const now = Date.now();
const assetId = `asset${String(now)}`;
async function main(): Promise<void> {
const client = await newGrpcConnection();
const gateway = connect({
client,
identity: await newIdentity(),
signer: await newSigner(),
evaluateOptions: () => {
return { deadline: Date.now() + 5000 }; // 5 seconds
},
endorseOptions: () => {
return { deadline: Date.now() + 15000 }; // 15 seconds
},
submitOptions: () => {
return { deadline: Date.now() + 5000 }; // 5 seconds
},
commitStatusOptions: () => {
return { deadline: Date.now() + 60000 }; // 1 minute
},
});
let events: CloseableAsyncIterable<ChaincodeEvent> | undefined;
try {
const network = gateway.getNetwork(channelName);
const contract = network.getContract(chaincodeName);
// Listen for events emitted by subsequent transactions
events = await startEventListening(network);
const firstBlockNumber = await createAsset(contract);
await updateAsset(contract);
await transferAsset(contract);
await deleteAssetByID(contract);
// Replay events from the block containing the first transaction
await replayChaincodeEvents(network,firstBlockNumber);
} finally {
events?.close();
gateway.close();
client.close();
}
}
main().catch((error: unknown) => {
console.error('******** FAILED to run the application:', error);
process.exitCode = 1;
});
async function startEventListening(network: Network): Promise<CloseableAsyncIterable<ChaincodeEvent>> {
console.log('\n*** Start chaincode event listening');
const events = await network.getChaincodeEvents(chaincodeName);
void readEvents(events); // Don't await - run asynchronously
return events;
}
async function readEvents(events: CloseableAsyncIterable<ChaincodeEvent>): Promise<void> {
try {
for await (const event of events) {
const payload = parseJson(event.payload);
console.log(`\n<-- Chaincode event received: ${event.eventName} -`, payload);
}
} catch (error: unknown) {
// Ignore the read error when events.close() is called explicitly
if (!(error instanceof GatewayError) || error.code !== grpc.status.CANCELLED.valueOf()) {
throw error;
}
}
}
function parseJson(jsonBytes: Uint8Array): unknown {
const json = utf8Decoder.decode(jsonBytes);
return JSON.parse(json);
}
async function createAsset(contract: Contract): Promise<bigint> {
console.log(`\n--> Submit Transaction: CreateAsset, ${assetId} owned by Sam with appraised value 100`);
const result = await contract.submitAsync('CreateAsset', {
arguments: [ assetId, 'blue', '10', 'Sam', '100' ],
});
const status = await result.getStatus();
if (!status.successful) {
throw new Error(`failed to commit transaction ${status.transactionId} with status code ${String(status.code)}`);
}
console.log('\n*** CreateAsset committed successfully');
return status.blockNumber;
}
async function updateAsset(contract: Contract): Promise<void> {
console.log(`\n--> Submit transaction: UpdateAsset, ${assetId} update appraised value to 200`);
await contract.submitTransaction('UpdateAsset', assetId, 'blue', '10', 'Sam', '200');
console.log('\n*** UpdateAsset committed successfully');
}
async function transferAsset(contract: Contract): Promise<void> {
console.log(`\n--> Submit transaction: TransferAsset, ${assetId} to Mary`);
await contract.submitTransaction('TransferAsset', assetId, 'Mary');
console.log('\n*** TransferAsset committed successfully');
}
async function deleteAssetByID(contract: Contract): Promise<void>{
console.log(`\n--> Submit transaction: DeleteAsset, ${assetId}`);
await contract.submitTransaction('DeleteAsset', assetId);
console.log('\n*** DeleteAsset committed successfully');
}
async function replayChaincodeEvents(network: Network, startBlock: bigint): Promise<void> {
console.log('\n*** Start chaincode event replay');
const events = await network.getChaincodeEvents(chaincodeName, {
startBlock,
});
try {
for await (const event of events) {
const payload = parseJson(event.payload);
console.log(`\n<-- Chaincode event replayed: ${event.eventName} -`, payload);
if (event.eventName === 'DeleteAsset') {
// Reached the last submitted transaction so break to stop listening for events
break;
}
}
} finally {
events.close();
}
}

View file

@ -1,58 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import * as grpc from '@grpc/grpc-js';
import { Identity, Signer, signers } from '@hyperledger/fabric-gateway';
import * as crypto from 'crypto';
import { promises as fs } from 'fs';
import * as path from 'path';
const mspId = 'Org1MSP';
// Path to crypto materials.
const cryptoPath = path.resolve(__dirname, '..', '..', '..', 'test-network', 'organizations', 'peerOrganizations', 'org1.example.com');
// Path to user private key directory.
const keyDirectoryPath = path.resolve(cryptoPath, 'users', 'User1@org1.example.com', 'msp', 'keystore');
// Path to user certificate.
const certDirectoryPath = path.resolve(cryptoPath, 'users', 'User1@org1.example.com', 'msp', 'signcerts');
// Path to peer tls certificate.
const tlsCertPath = path.resolve(cryptoPath, 'peers', 'peer0.org1.example.com', 'tls', 'ca.crt');
// Gateway peer endpoint.
const peerEndpoint = 'localhost:7051';
export async function newGrpcConnection(): Promise<grpc.Client> {
const tlsRootCert = await fs.readFile(tlsCertPath);
const tlsCredentials = grpc.credentials.createSsl(tlsRootCert);
return new grpc.Client(peerEndpoint, tlsCredentials, {
'grpc.ssl_target_name_override': 'peer0.org1.example.com',
});
}
export async function newIdentity(): Promise<Identity> {
const certPath = await getFirstDirFileName(certDirectoryPath);
const credentials = await fs.readFile(certPath);
return { mspId, credentials };
}
export async function newSigner(): Promise<Signer> {
const keyPath = await getFirstDirFileName(keyDirectoryPath);
const privateKeyPem = await fs.readFile(keyPath);
const privateKey = crypto.createPrivateKey(privateKeyPem);
return signers.newPrivateKeySigner(privateKey);
}
async function getFirstDirFileName(dirPath: string): Promise<string> {
const files = await fs.readdir(dirPath);
const file = files[0];
if (!file) {
throw new Error(`No files in directory: ${dirPath}`);
}
return path.join(dirPath, file);
}

View file

@ -1,15 +0,0 @@
{
"extends": "@tsconfig/node18/tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"noUnusedLocals": true,
"noImplicitReturns": true,
"noUncheckedIndexedAccess": true,
"forceConsistentCasingInFileNames": true
},
"include": ["./src/**/*"],
"exclude": ["./src/**/*.spec.ts"]
}

View file

@ -1,23 +0,0 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"log"
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
"github.com/hyperledger/fabric-samples/asset-transfer-events/chaincode-go/chaincode"
)
func main() {
assetChaincode, err := contractapi.NewChaincode(&chaincode.SmartContract{})
if err != nil {
log.Panicf("Error creating asset-transfer-events chaincode: %v", err)
}
if err := assetChaincode.Start(); err != nil {
log.Panicf("Error starting asset-transfer-events chaincode: %v", err)
}
}

View file

@ -1,134 +0,0 @@
package chaincode
import (
"encoding/json"
"fmt"
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
)
// SmartContract provides functions for managing an Asset
type SmartContract struct {
contractapi.Contract
}
// Asset describes basic details of what makes up a simple asset
// Insert struct field in alphabetic order => to achieve determinism across languages
// golang keeps the order when marshal to json but doesn't order automatically
type Asset struct {
AppraisedValue int `json:"AppraisedValue"`
Color string `json:"Color"`
ID string `json:"ID"`
Owner string `json:"Owner"`
Size int `json:"Size"`
}
// CreateAsset issues a new asset to the world state with given details.
func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
existing, err := s.readState(ctx, id)
if err == nil && existing != nil {
return fmt.Errorf("the asset %s already exists", id)
}
asset := Asset{
ID: id,
Color: color,
Size: size,
Owner: owner,
AppraisedValue: appraisedValue,
}
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
ctx.GetStub().SetEvent("CreateAsset", assetJSON)
return ctx.GetStub().PutState(id, assetJSON)
}
func (s *SmartContract) readState(ctx contractapi.TransactionContextInterface, id string) ([]byte, error) {
assetJSON, err := ctx.GetStub().GetState(id)
if err != nil {
return nil, fmt.Errorf("failed to read from world state: %w", err)
}
if assetJSON == nil {
return nil, fmt.Errorf("the asset %s does not exist", id)
}
return assetJSON, nil
}
// ReadAsset returns the asset stored in the world state with given id.
func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {
assetJSON, err := s.readState(ctx, id)
if err != nil {
return nil, err
}
var asset Asset
err = json.Unmarshal(assetJSON, &asset)
if err != nil {
return nil, err
}
return &asset, nil
}
// UpdateAsset updates an existing asset in the world state with provided parameters.
func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
_, err := s.readState(ctx, id)
if err != nil {
return err
}
// overwriting original asset with new asset
asset := Asset{
ID: id,
Color: color,
Size: size,
Owner: owner,
AppraisedValue: appraisedValue,
}
assetJSON, err := json.Marshal(asset)
if err != nil {
return err
}
ctx.GetStub().SetEvent("UpdateAsset", assetJSON)
return ctx.GetStub().PutState(id, assetJSON)
}
// DeleteAsset deletes an given asset from the world state.
func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error {
assetJSON, err := s.readState(ctx, id)
if err != nil {
return err
}
ctx.GetStub().SetEvent("DeleteAsset", assetJSON)
return ctx.GetStub().DelState(id)
}
// TransferAsset updates the owner field of asset with given id in world state, and returns the old owner.
func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string) (string, error) {
asset, err := s.ReadAsset(ctx, id)
if err != nil {
return "", err
}
oldOwner := asset.Owner
asset.Owner = newOwner
assetJSON, err := json.Marshal(asset)
if err != nil {
return "", err
}
ctx.GetStub().SetEvent("TransferAsset", assetJSON)
err = ctx.GetStub().PutState(id, assetJSON)
if err != nil {
return "", err
}
return oldOwner, nil
}

View file

@ -1,32 +0,0 @@
module github.com/hyperledger/fabric-samples/asset-transfer-events/chaincode-go
go 1.21
require github.com/hyperledger/fabric-contract-api-go/v2 v2.0.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/gobuffalo/envy v1.10.2 // indirect
github.com/gobuffalo/packd v1.0.2 // indirect
github.com/gobuffalo/packr v1.30.1 // indirect
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0-20240618210511-f7903324a8af // indirect
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/rogpeppe/go-internal v1.12.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
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect
google.golang.org/grpc v1.64.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View file

@ -1,132 +0,0 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
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/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/envy v1.10.2 h1:EIi03p9c3yeuRCFPOKcSfajzkLb3hrRjEpHGI8I2Wo4=
github.com/gobuffalo/envy v1.10.2/go.mod h1:qGAGwdvDsaEtPhfBzb3o0SfDea8ByGn9j8bKmVft9z8=
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
github.com/gobuffalo/packd v1.0.2 h1:Yg523YqnOxGIWCp69W12yYBKsoChwI7mtu6ceM9Bwfw=
github.com/gobuffalo/packd v1.0.2/go.mod h1:sUc61tDqGMXON80zpKGp92lDb86Km28jfvX7IAyxFT8=
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
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/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0-20240618210511-f7903324a8af h1:WT4NjX7Uk03GSeH++jF3a0wp4FhybTM86zDPCETvmSk=
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0-20240618210511-f7903324a8af/go.mod h1:f/ER25FaBepxJugwpLhbD2hLAoZaZEVqkBjOcHjw72Y=
github.com/hyperledger/fabric-contract-api-go/v2 v2.0.0 h1:IDiCGVOBlRd6zpL0Y+f6V7IpBqa4/Z5JAK9SF7a5ea8=
github.com/hyperledger/fabric-contract-api-go/v2 v2.0.0/go.mod h1:pdqhe7ALf4lmXgQdprCyNWYdnCPxgj02Vhf8JF5w8po=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 h1:Xpd6fzG/KjAOHJsq7EQXY2l+qi/y8muxBaY7R6QWABk=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3/go.mod h1:2pq0ui6ZWA0cC8J+eCErgnMDCS1kPOEYVY+06ZAK0qE=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
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/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
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/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
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=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -1,69 +0,0 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
plugins {
id 'com.github.johnrengelman.shadow' version '8.1.1'
id 'application'
id 'checkstyle'
id 'jacoco'
}
group 'org.hyperledger.fabric.samples'
version '1.0-SNAPSHOT'
dependencies {
implementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.5.+'
implementation 'org.json:json:+'
testImplementation 'org.hyperledger.fabric-chaincode-java:fabric-chaincode-shim:2.5.+'
}
repositories {
mavenCentral()
maven {
url 'https://jitpack.io'
}
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
}
}
application {
mainClassName = 'org.hyperledger.fabric.contract.ContractRouter'
}
checkstyle {
toolVersion '8.21'
configFile file("config/checkstyle/checkstyle.xml")
}
checkstyleMain {
source ='src/main/java'
}
checkstyleTest {
source ='src/test/java'
}
jacocoTestReport {
dependsOn test
}
mainClassName = 'org.hyperledger.fabric.contract.ContractRouter'
shadowJar {
archiveBaseName = 'chaincode'
archiveVersion = ''
archiveClassifier = ''
mergeServiceFiles()
manifest {
attributes 'Main-Class': 'org.hyperledger.fabric.contract.ContractRouter'
}
}
installDist.dependsOn check

View file

@ -1,171 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<!--
Checkstyle configuration that matches the Eclipse formatter
Checkstyle is very configurable. Be sure to read the documentation at
http://checkstyle.sourceforge.net (or in your downloaded distribution).
Most Checks are configurable, be sure to consult the documentation.
To completely disable a check, just comment it out or delete it from the file.
Finally, it is worth reading the documentation.
-->
<module name="Checker">
<!--
If you set the basedir property below, then all reported file
names will be relative to the specified directory. See
https://checkstyle.org/5.x/config.html#Checker
<property name="basedir" value="${basedir}"/>
-->
<property name="fileExtensions" value="java, properties, xml"/>
<module name="SuppressionFilter">
<property name="file" value="${config_loc}/suppressions.xml"/>
<property name="optional" value="false"/>
</module>
<!-- Excludes all 'module-info.java' files -->
<!-- See https://checkstyle.org/config_filefilters.html -->
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="module\-info\.java$"/>
</module>
<!-- Checks that a package-info.java file exists for each package. -->
<!-- See http://checkstyle.sourceforge.net/config_javadoc.html#JavadocPackage -->
<!-- <module name="JavadocPackage"/> -->
<!-- Checks whether files end with a new line. -->
<!-- See http://checkstyle.sourceforge.net/config_misc.html#NewlineAtEndOfFile -->
<module name="NewlineAtEndOfFile"/>
<!-- Checks that property files contain the same keys. -->
<!-- See http://checkstyle.sourceforge.net/config_misc.html#Translation -->
<module name="Translation"/>
<!-- Checks for Size Violations. -->
<!-- See http://checkstyle.sourceforge.net/config_sizes.html -->
<module name="FileLength"/>
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sourceforge.net/config_whitespace.html -->
<module name="FileTabCharacter"/>
<!-- Miscellaneous other checks. -->
<!-- See http://checkstyle.sourceforge.net/config_misc.html -->
<module name="RegexpSingleline">
<property name="format" value="\s+$"/>
<property name="minimum" value="0"/>
<property name="maximum" value="0"/>
<property name="message" value="Line has trailing spaces."/>
</module>
<!-- Checks for Headers -->
<!-- See http://checkstyle.sourceforge.net/config_header.html -->
<!-- <module name="Header"> -->
<!-- <property name="headerFile" value="${checkstyle.header.file}"/> -->
<!-- <property name="fileExtensions" value="java"/> -->
<!-- </module> -->
<module name="TreeWalker">
<!-- Checks for Javadoc comments. -->
<!-- See http://checkstyle.sourceforge.net/config_javadoc.html -->
<!-- <module name="JavadocMethod"/> -->
<!-- <module name="JavadocType"/> -->
<!-- <module name="JavadocVariable"/> -->
<!-- <module name="JavadocStyle"/> -->
<!-- <module name="MissingJavadocMethod"/> -->
<!-- Checks for Naming Conventions. -->
<!-- See http://checkstyle.sourceforge.net/config_naming.html -->
<module name="ConstantName"/>
<module name="LocalFinalVariableName"/>
<module name="LocalVariableName"/>
<module name="PackageName"/>
<module name="StaticVariableName"/>
<module name="TypeName"/>
<!-- Checks for imports -->
<!-- See http://checkstyle.sourceforge.net/config_import.html -->
<module name="AvoidStarImport"/>
<module name="IllegalImport"/> <!-- defaults to sun.* packages -->
<module name="RedundantImport"/>
<module name="UnusedImports">
<property name="processJavadoc" value="false"/>
</module>
<!-- Checks for Size Violations. -->
<!-- See http://checkstyle.sourceforge.net/config_sizes.html -->
<module name="MethodLength"/>
<module name="ParameterNumber"/>
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sourceforge.net/config_whitespace.html -->
<module name="EmptyForIteratorPad"/>
<module name="GenericWhitespace"/>
<module name="MethodParamPad"/>
<module name="NoWhitespaceAfter"/>
<module name="NoWhitespaceBefore"/>
<module name="OperatorWrap"/>
<module name="ParenPad"/>
<module name="TypecastParenPad"/>
<module name="WhitespaceAfter"/>
<module name="WhitespaceAround"/>
<!-- Modifier Checks -->
<!-- See http://checkstyle.sourceforge.net/config_modifiers.html -->
<module name="ModifierOrder"/>
<module name="RedundantModifier"/>
<!-- Checks for blocks. You know, those {}'s -->
<!-- See http://checkstyle.sourceforge.net/config_blocks.html -->
<module name="AvoidNestedBlocks"/>
<module name="EmptyBlock"/>
<module name="LeftCurly"/>
<module name="NeedBraces"/>
<module name="RightCurly"/>
<!-- Checks for common coding problems -->
<!-- See http://checkstyle.sourceforge.net/config_coding.html -->
<module name="EmptyStatement"/>
<module name="EqualsHashCode"/>
<module name="HiddenField">
<property name="ignoreConstructorParameter" value="true"/>
</module>
<module name="IllegalInstantiation"/>
<module name="InnerAssignment"/>
<module name="MissingSwitchDefault"/>
<module name="MultipleVariableDeclarations"/>
<module name="SimplifyBooleanExpression"/>
<module name="SimplifyBooleanReturn"/>
<!-- Checks for class design -->
<!-- See http://checkstyle.sourceforge.net/config_design.html -->
<module name="DesignForExtension"/>
<module name="FinalClass"/>
<module name="HideUtilityClassConstructor"/>
<module name="InterfaceIsType"/>
<module name="VisibilityModifier">
<property name="allowPublicFinalFields" value="true"/>
</module>
<!-- Miscellaneous other checks. -->
<!-- See http://checkstyle.sourceforge.net/config_misc.html -->
<module name="ArrayTypeStyle"/>
<module name="FinalParameters"/>
<module name="TodoComment"/>
<module name="UpperEll"/>
</module>
</module>

View file

@ -1,8 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE suppressions PUBLIC
"-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN"
"https://checkstyle.org/dtds/suppressions_1_2.dtd">
<suppressions>
</suppressions>

View file

@ -1,7 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View file

@ -1,249 +0,0 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View file

@ -1,92 +0,0 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View file

@ -1,5 +0,0 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
rootProject.name = 'events'

View file

@ -1,158 +0,0 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.fabric.samples.events;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import static java.nio.charset.StandardCharsets.UTF_8;
import org.hyperledger.fabric.contract.annotation.DataType;
import org.hyperledger.fabric.contract.annotation.Property;
import org.json.JSONObject;
@DataType()
public final class Asset {
@Property()
private final String assetID;
@Property()
private String color;
@Property()
private int size;
@Property()
private String owner;
@Property()
private int appraisedValue;
public Asset(final String assetID, final String color,
final int size, final String owner, final int value) {
this.assetID = assetID;
this.color = color;
this.size = size;
this.owner = owner;
this.appraisedValue = value;
}
public String getAssetID() {
return assetID;
}
public String getColor() {
return color;
}
public int getSize() {
return size;
}
public String getOwner() {
return owner;
}
public int getAppraisedValue() {
return appraisedValue;
}
public void setOwner(final String newowner) {
this.owner = newowner;
}
public void setAppraisedValue(final int value) {
this.appraisedValue = value;
}
public void setColor(final String c) {
this.color = c;
}
public void setSize(final int s) {
this.size = s;
}
// Serialize asset without private properties
public byte[] serialize() {
return serialize(null).getBytes(UTF_8);
}
public String serialize(final String privateProps) {
Map<String, Object> tMap = new HashMap();
tMap.put("ID", assetID);
tMap.put("Color", color);
tMap.put("Owner", owner);
tMap.put("Size", Integer.toString(size));
tMap.put("AppraisedValue", Integer.toString(appraisedValue));
if (privateProps != null && privateProps.length() > 0) {
tMap.put("asset_properties", new JSONObject(privateProps));
}
return new JSONObject(tMap).toString();
}
public static Asset deserialize(final byte[] assetJSON) {
return deserialize(new String(assetJSON, UTF_8));
}
public static Asset deserialize(final String assetJSON) {
JSONObject json = new JSONObject(assetJSON);
Map<String, Object> tMap = json.toMap();
final String id = (String) tMap.get("ID");
final String color = (String) tMap.get("Color");
final String owner = (String) tMap.get("Owner");
int size = 0;
int appraisedValue = 0;
if (tMap.containsKey("Size")) {
size = Integer.parseInt((String) tMap.get("Size"));
}
if (tMap.containsKey("AppraisedValue")) {
appraisedValue = Integer.parseInt((String) tMap.get("AppraisedValue"));
}
return new Asset(id, color, size, owner, appraisedValue);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if ((obj == null) || (getClass() != obj.getClass())) {
return false;
}
Asset other = (Asset) obj;
return Objects.deepEquals(
new String[]{getAssetID(), getColor(), getOwner()},
new String[]{other.getAssetID(), other.getColor(), other.getOwner()})
&&
Objects.deepEquals(
new int[]{getSize(), getAppraisedValue()},
new int[]{other.getSize(), other.getAppraisedValue()});
}
@Override
public int hashCode() {
return Objects.hash(getAssetID(), getColor(), getSize(), getOwner(), getAppraisedValue());
}
@Override
public String toString() {
return this.getClass().getSimpleName() + "@" + Integer.toHexString(hashCode())
+ " [assetID=" + assetID + ", appraisedValue=" + appraisedValue + ", color="
+ color + ", size=" + size + ", owner=" + owner + "]";
}
}

View file

@ -1,299 +0,0 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.fabric.samples.events;
import static java.nio.charset.StandardCharsets.UTF_8;
import org.hyperledger.fabric.contract.Context;
import org.hyperledger.fabric.contract.ContractInterface;
import org.hyperledger.fabric.contract.annotation.Contact;
import org.hyperledger.fabric.contract.annotation.Contract;
import org.hyperledger.fabric.contract.annotation.Default;
import org.hyperledger.fabric.contract.annotation.Info;
import org.hyperledger.fabric.contract.annotation.License;
import org.hyperledger.fabric.contract.annotation.Transaction;
import org.hyperledger.fabric.shim.ChaincodeException;
import org.hyperledger.fabric.shim.ChaincodeStub;
import java.util.Map;
/**
* Main Chaincode class.
*
* @see org.hyperledger.fabric.shim.Chaincode
* <p>
* Each chaincode transaction function must take, Context as first parameter.
* Unless specified otherwise via annotation (@Contract or @Transaction), the contract name
* is the class name (without package)
* and the transaction name is the method name.
*/
@Contract(
name = "asset-transfer-events-java",
info = @Info(
title = "Asset Transfer Events",
description = "The hyperlegendary asset transfer events sample",
version = "0.0.1-SNAPSHOT",
license = @License(
name = "Apache 2.0 License",
url = "http://www.apache.org/licenses/LICENSE-2.0.html"),
contact = @Contact(
email = "a.transfer@example.com",
name = "Fabric Development Team",
url = "https://hyperledger.example.com")))
@Default
public final class AssetTransfer implements ContractInterface {
static final String IMPLICIT_COLLECTION_NAME_PREFIX = "_implicit_org_";
static final String PRIVATE_PROPS_KEY = "asset_properties";
/**
* Retrieves the asset details with the specified ID
*
* @param ctx the transaction context
* @param assetID the ID of the asset
* @return the asset found on the ledger. Returns error if asset is not found
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public String ReadAsset(final Context ctx, final String assetID) {
System.out.printf("ReadAsset: ID %s\n", assetID);
Asset asset = getState(ctx, assetID);
String privData = readPrivateData(ctx, assetID);
return asset.serialize(privData);
}
/**
* Creates a new asset on the ledger. Saves the passed private data (asset properties) from transient map input.
*
* @param ctx the transaction context
* Transient map with asset_properties key with asset json as value
* @param assetID
* @param color
* @param size
* @param owner
* @param appraisedValue
* @return the created asset
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public Asset CreateAsset(final Context ctx, final String assetID, final String color, final int size, final String owner, final int appraisedValue) {
ChaincodeStub stub = ctx.getStub();
// input validations
String errorMessage = null;
if (assetID == null || assetID.equals("")) {
errorMessage = String.format("Empty input: assetID");
}
if (owner == null || owner.equals("")) {
errorMessage = String.format("Empty input: owner");
}
if (errorMessage != null) {
System.err.println(errorMessage);
throw new ChaincodeException(errorMessage, AssetTransferErrors.INCOMPLETE_INPUT.toString());
}
// Check if asset already exists
byte[] assetJSON = ctx.getStub().getState(assetID);
if (assetJSON != null && assetJSON.length > 0) {
errorMessage = String.format("Asset %s already exists", assetID);
System.err.println(errorMessage);
throw new ChaincodeException(errorMessage, AssetTransferErrors.ASSET_ALREADY_EXISTS.toString());
}
Asset asset = new Asset(assetID, color, size, owner, appraisedValue);
savePrivateData(ctx, assetID);
assetJSON = asset.serialize();
System.out.printf("CreateAsset Put: ID %s Data %s\n", assetID, new String(assetJSON));
stub.putState(assetID, assetJSON);
// add Event data to the transaction data. Event will be published after the block containing
// this transaction is committed
stub.setEvent("CreateAsset", assetJSON);
return asset;
}
/**
* TransferAsset transfers the asset to the new owner
* Save any private data, if provided in transient map
*
* @param ctx the transaction context
* @param assetID asset to delete
* @param newOwner new owner for the asset
* @return none
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public void TransferAsset(final Context ctx, final String assetID, final String newOwner) {
ChaincodeStub stub = ctx.getStub();
String errorMessage = null;
if (assetID == null || assetID.equals("")) {
errorMessage = "Empty input: assetID";
}
if (newOwner == null || newOwner.equals("")) {
errorMessage = "Empty input: newOwner";
}
if (errorMessage != null) {
System.err.println(errorMessage);
throw new ChaincodeException(errorMessage, AssetTransferErrors.INCOMPLETE_INPUT.toString());
}
System.out.printf("TransferAsset: verify asset %s exists\n", assetID);
Asset thisAsset = getState(ctx, assetID);
// Transfer asset to new owner
thisAsset.setOwner(newOwner);
System.out.printf(" Transfer Asset: ID %s to owner %s\n", assetID, newOwner);
savePrivateData(ctx, assetID); // save private data if any
byte[] assetJSON = thisAsset.serialize();
stub.putState(assetID, assetJSON);
stub.setEvent("TransferAsset", assetJSON); //publish Event
}
/**
* Update existing asset on the ledger with provided parameters.
* Saves the passed private data (asset properties) from transient map input.
*
* @param ctx the transaction context
* Transient map with asset_properties key with asset json as value
* @param assetID
* @param color
* @param size
* @param owner
* @param appraisedValue
* @return the created asset
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public Asset UpdateAsset(final Context ctx, final String assetID, final String color, final int size, final String owner, final int appraisedValue) {
ChaincodeStub stub = ctx.getStub();
// input validations
String errorMessage = null;
if (assetID == null || assetID.equals("")) {
errorMessage = String.format("Empty input: assetID");
}
if (errorMessage != null) {
System.err.println(errorMessage);
throw new ChaincodeException(errorMessage, AssetTransferErrors.INCOMPLETE_INPUT.toString());
}
// reads from the Statedb. Check if asset already exists
Asset asset = getState(ctx, assetID);
if (owner != null) {
asset.setOwner(owner);
}
if (color != null) {
asset.setColor(color);
}
if (size > 0) {
asset.setSize(size);
}
if (appraisedValue > 0) {
asset.setAppraisedValue(appraisedValue);
}
savePrivateData(ctx, assetID);
byte[] assetJSON = asset.serialize();
System.out.printf("UpdateAsset Put: ID %s Data %s\n", assetID, new String(assetJSON));
stub.putState(assetID, assetJSON);
stub.setEvent("UpdateAsset", assetJSON); //publish Event
return asset;
}
/**
* Deletes a asset & related details from the ledger.
*
* @param ctx the transaction context
* @param assetID asset to delete
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public void DeleteAsset(final Context ctx, final String assetID) {
ChaincodeStub stub = ctx.getStub();
System.out.printf("DeleteAsset: verify asset %s exists\n", assetID);
Asset asset = getState(ctx, assetID);
System.out.printf(" DeleteAsset: ID %s\n", assetID);
// delete private details of asset
removePrivateData(ctx, assetID);
stub.delState(assetID); // delete the key from Statedb
stub.setEvent("DeleteAsset", asset.serialize()); // publish Event
}
private Asset getState(final Context ctx, final String assetID) {
byte[] assetJSON = ctx.getStub().getState(assetID);
if (assetJSON == null || assetJSON.length == 0) {
String errorMessage = String.format("Asset %s does not exist", assetID);
System.err.println(errorMessage);
throw new ChaincodeException(errorMessage, AssetTransferErrors.ASSET_NOT_FOUND.toString());
}
try {
Asset asset = Asset.deserialize(assetJSON);
return asset;
} catch (Exception e) {
throw new ChaincodeException("Deserialize error: " + e.getMessage(), AssetTransferErrors.DATA_ERROR.toString());
}
}
private String readPrivateData(final Context ctx, final String assetKey) {
String peerMSPID = ctx.getStub().getMspId();
String clientMSPID = ctx.getClientIdentity().getMSPID();
String implicitCollectionName = getCollectionName(ctx);
String privData = null;
// only if ClientOrgMatchesPeerOrg
if (peerMSPID.equals(clientMSPID)) {
System.out.printf(" ReadPrivateData from collection %s, ID %s\n", implicitCollectionName, assetKey);
byte[] propJSON = ctx.getStub().getPrivateData(implicitCollectionName, assetKey);
if (propJSON != null && propJSON.length > 0) {
privData = new String(propJSON, UTF_8);
}
}
return privData;
}
private void savePrivateData(final Context ctx, final String assetKey) {
String peerMSPID = ctx.getStub().getMspId();
String clientMSPID = ctx.getClientIdentity().getMSPID();
String implicitCollectionName = getCollectionName(ctx);
if (peerMSPID.equals(clientMSPID)) {
Map<String, byte[]> transientMap = ctx.getStub().getTransient();
if (transientMap != null && transientMap.containsKey(PRIVATE_PROPS_KEY)) {
byte[] transientAssetJSON = transientMap.get(PRIVATE_PROPS_KEY);
System.out.printf("Asset's PrivateData Put in collection %s, ID %s\n", implicitCollectionName, assetKey);
ctx.getStub().putPrivateData(implicitCollectionName, assetKey, transientAssetJSON);
}
}
}
private void removePrivateData(final Context ctx, final String assetKey) {
String peerMSPID = ctx.getStub().getMspId();
String clientMSPID = ctx.getClientIdentity().getMSPID();
String implicitCollectionName = getCollectionName(ctx);
if (peerMSPID.equals(clientMSPID)) {
System.out.printf("PrivateData Delete from collection %s, ID %s\n", implicitCollectionName, assetKey);
ctx.getStub().delPrivateData(implicitCollectionName, assetKey);
}
}
// Return the implicit collection name, to use for private property persistance
private String getCollectionName(final Context ctx) {
// Get the MSP ID of submitting client identity
String clientMSPID = ctx.getClientIdentity().getMSPID();
String collectionName = IMPLICIT_COLLECTION_NAME_PREFIX + clientMSPID;
return collectionName;
}
private enum AssetTransferErrors {
INCOMPLETE_INPUT,
INVALID_ACCESS,
ASSET_NOT_FOUND,
ASSET_ALREADY_EXISTS,
DATA_ERROR
}
}

View file

@ -1,5 +0,0 @@
#
# SPDX-License-Identifier: Apache-2.0
#
coverage

View file

@ -1,39 +0,0 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
module.exports = {
env: {
node: true,
mocha: true,
es6: true
},
parserOptions: {
ecmaVersion: 8,
sourceType: 'script'
},
extends: 'eslint:recommended',
rules: {
indent: ['error', 'tab'],
'linebreak-style': ['error', 'unix'],
quotes: ['error', 'single'],
semi: ['error', 'always'],
'no-unused-vars': ['error', { args: 'none' }],
'no-console': 'off',
curly: 'error',
eqeqeq: 'error',
'no-throw-literal': 'error',
strict: 'error',
'no-var': 'error',
'dot-notation': 'error',
'no-trailing-spaces': 'error',
'no-use-before-define': 'error',
'no-useless-call': 'error',
'no-with': 'error',
'operator-linebreak': 'error',
yoda: 'error',
'quote-props': ['error', 'as-needed'],
'no-constant-condition': ['error', { checkLoops: false }]
}
};

View file

@ -1,15 +0,0 @@
#
# SPDX-License-Identifier: Apache-2.0
#
# Coverage directory used by tools like istanbul
coverage
# Report cache used by istanbul
.nyc_output
# Dependency directories
node_modules/
jspm_packages/
package-lock.json

View file

@ -1,12 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const assetTransferEvents = require('./lib/assetTransferEvents');
module.exports.AssetTransferEvents = assetTransferEvents;
module.exports.contracts = [assetTransferEvents];

View file

@ -1,128 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Contract } = require('fabric-contract-api');
async function savePrivateData(ctx, assetKey) {
const clientOrg = ctx.clientIdentity.getMSPID();
const peerOrg = ctx.stub.getMspID();
const collection = '_implicit_org_' + peerOrg;
if (clientOrg === peerOrg) {
const transientMap = ctx.stub.getTransient();
if (transientMap) {
const properties = transientMap.get('asset_properties');
if (properties) {
await ctx.stub.putPrivateData(collection, assetKey, properties);
}
}
}
}
async function removePrivateData(ctx, assetKey) {
const clientOrg = ctx.clientIdentity.getMSPID();
const peerOrg = ctx.stub.getMspID();
const collection = '_implicit_org_' + peerOrg;
if (clientOrg === peerOrg) {
const propertiesBuffer = await ctx.stub.getPrivateData(collection, assetKey);
if (propertiesBuffer && propertiesBuffer.length > 0) {
await ctx.stub.deletePrivateData(collection, assetKey);
}
}
}
async function addPrivateData(ctx, assetKey, asset) {
const clientOrg = ctx.clientIdentity.getMSPID();
const peerOrg = ctx.stub.getMspID();
const collection = '_implicit_org_' + peerOrg;
if (clientOrg === peerOrg) {
const propertiesBuffer = await ctx.stub.getPrivateData(collection, assetKey);
if (propertiesBuffer && propertiesBuffer.length > 0) {
const properties = JSON.parse(propertiesBuffer.toString());
asset.asset_properties = properties;
}
}
}
async function readState(ctx, id) {
const assetBuffer = await ctx.stub.getState(id); // get the asset from chaincode state
if (!assetBuffer || assetBuffer.length === 0) {
throw new Error(`The asset ${id} does not exist`);
}
const assetString = assetBuffer.toString();
const asset = JSON.parse(assetString);
return asset;
}
class AssetTransferEvents extends Contract {
// CreateAsset issues a new asset to the world state with given details.
async CreateAsset(ctx, id, color, size, owner, appraisedValue) {
const asset = {
ID: id,
Color: color,
Size: size,
Owner: owner,
AppraisedValue: appraisedValue,
};
await savePrivateData(ctx, id);
const assetBuffer = Buffer.from(JSON.stringify(asset));
ctx.stub.setEvent('CreateAsset', assetBuffer);
return ctx.stub.putState(id, assetBuffer);
}
// TransferAsset updates the owner field of an asset with the given id in
// the world state.
async TransferAsset(ctx, id, newOwner) {
const asset = await readState(ctx, id);
asset.Owner = newOwner;
const assetBuffer = Buffer.from(JSON.stringify(asset));
await savePrivateData(ctx, id);
ctx.stub.setEvent('TransferAsset', assetBuffer);
return ctx.stub.putState(id, assetBuffer);
}
// ReadAsset returns the asset stored in the world state with given id.
async ReadAsset(ctx, id) {
const asset = await readState(ctx, id);
await addPrivateData(ctx, asset.ID, asset);
return JSON.stringify(asset);
}
// UpdateAsset updates an existing asset in the world state with provided parameters.
async UpdateAsset(ctx, id, color, size, owner, appraisedValue) {
const asset = await readState(ctx, id);
asset.Color = color;
asset.Size = size;
asset.Owner = owner;
asset.AppraisedValue = appraisedValue;
const assetBuffer = Buffer.from(JSON.stringify(asset));
await savePrivateData(ctx, id);
ctx.stub.setEvent('UpdateAsset', assetBuffer);
return ctx.stub.putState(id, assetBuffer);
}
// DeleteAsset deletes an given asset from the world state.
async DeleteAsset(ctx, id) {
const asset = await readState(ctx, id);
const assetBuffer = Buffer.from(JSON.stringify(asset));
await removePrivateData(ctx, id);
ctx.stub.setEvent('DeleteAsset', assetBuffer);
return ctx.stub.deleteState(id);
}
}
module.exports = AssetTransferEvents;

File diff suppressed because it is too large Load diff

View file

@ -1,48 +0,0 @@
{
"name": "asset-transfer-events",
"version": "1.0.0",
"description": "Asset-Transfer-Events contract implemented in JavaScript",
"main": "index.js",
"engines": {
"node": ">=18"
},
"scripts": {
"lint": "eslint .",
"pretest": "npm run lint",
"test": "nyc mocha --recursive",
"start": "fabric-chaincode-node start"
},
"engineStrict": true,
"author": "Hyperledger",
"license": "Apache-2.0",
"dependencies": {
"fabric-contract-api": "~2.5",
"fabric-shim": "~2.5"
},
"devDependencies": {
"chai": "^4.4.1",
"eslint": "^8.57.0",
"mocha": "^10.4.0",
"nyc": "^15.1.0",
"sinon": "^18.0.0",
"sinon-chai": "^3.7.0"
},
"nyc": {
"exclude": [
"coverage/**",
"test/**",
"index.js",
".eslintrc.js"
],
"reporter": [
"text-summary",
"html"
],
"all": true,
"check-coverage": true,
"statements": 100,
"branches": 100,
"functions": 100,
"lines": 100
}
}

View file

@ -1,229 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const sinon = require('sinon');
const chai = require('chai');
const sinonChai = require('sinon-chai');
const expect = chai.expect;
const { Context } = require('fabric-contract-api');
const { ChaincodeStub, ClientIdentity } = require('fabric-shim');
const AssetTransfer = require('../lib/assetTransferEvents.js');
let assert = sinon.assert;
chai.use(sinonChai);
describe('Asset Transfer Events Tests', () => {
let transactionContext, chaincodeStub, clientIdentity, asset;
let transientMap, asset_properties;
beforeEach(() => {
transactionContext = new Context();
chaincodeStub = sinon.createStubInstance(ChaincodeStub);
chaincodeStub.getMspID.returns('org1');
transactionContext.setChaincodeStub(chaincodeStub);
clientIdentity = sinon.createStubInstance(ClientIdentity);
clientIdentity.getMSPID.returns('org1');
transactionContext.clientIdentity = clientIdentity;
chaincodeStub.putState.callsFake((key, value) => {
if (!chaincodeStub.states) {
chaincodeStub.states = {};
}
chaincodeStub.states[key] = value;
});
chaincodeStub.getState.callsFake(async (key) => {
let ret;
if (chaincodeStub.states) {
ret = chaincodeStub.states[key];
}
return Promise.resolve(ret);
});
chaincodeStub.deleteState.callsFake(async (key) => {
if (chaincodeStub.states) {
delete chaincodeStub.states[key];
}
return Promise.resolve(key);
});
chaincodeStub.getStateByRange.callsFake(async () => {
function* internalGetStateByRange() {
if (chaincodeStub.states) {
// Shallow copy
const copied = Object.assign({}, chaincodeStub.states);
for (let key in copied) {
yield {value: copied[key]};
}
}
}
return Promise.resolve(internalGetStateByRange());
});
asset = {
ID: 'asset1',
Color: 'blue',
Size: 5,
Owner: 'Tomoko',
AppraisedValue: 300,
};
const randomNumber = Math.floor(Math.random() * 100) + 1;
asset_properties = {
object_type: 'asset_properties',
asset_id: 'asset1',
Price: '90',
salt: Buffer.from(randomNumber.toString()).toString('hex')
};
transientMap = new Map();
transientMap.set('asset_properties', Buffer.from(JSON.stringify(asset_properties)));
});
describe('Test CreateAsset', () => {
it('should return error on CreateAsset', async () => {
chaincodeStub.putState.rejects('failed inserting key');
let assetTransfer = new AssetTransfer();
try {
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
assert.fail('CreateAsset should have failed');
} catch(err) {
expect(err.name).to.equal('failed inserting key');
}
});
it('should return success on CreateAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
let ret = JSON.parse((await chaincodeStub.getState(asset.ID)).toString());
expect(ret).to.eql(asset);
});
it('should return success on CreateAsset with transient data', async () => {
let assetTransfer = new AssetTransfer();
chaincodeStub.getTransient.returns(transientMap);
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
let ret = JSON.parse((await chaincodeStub.getState(asset.ID)).toString());
expect(ret).to.eql(asset);
});
});
describe('Test ReadAsset', () => {
it('should return error on ReadAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
try {
await assetTransfer.ReadAsset(transactionContext, 'asset2');
assert.fail('ReadAsset should have failed');
} catch (err) {
expect(err.message).to.equal('The asset asset2 does not exist');
}
});
it('should return success on ReadAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
const assetString = await assetTransfer.ReadAsset(transactionContext, 'asset1');
const readAsset = JSON.parse(assetString);
expect(readAsset).to.eql(asset);
});
it('should return success on ReadAsset with private data', async () => {
asset.asset_properties = asset_properties;
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
chaincodeStub.getPrivateData.returns(Buffer.from(JSON.stringify(asset_properties)));
const assetString = await assetTransfer.ReadAsset(transactionContext, 'asset1');
const readAsset = JSON.parse(assetString);
expect(readAsset).to.eql(asset);
});
});
describe('Test UpdateAsset', () => {
it('should return error on UpdateAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
try {
await assetTransfer.UpdateAsset(transactionContext, 'asset2', 'orange', 10, 'Me', 500);
assert.fail('UpdateAsset should have failed');
} catch (err) {
expect(err.message).to.equal('The asset asset2 does not exist');
}
});
it('should return success on UpdateAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
await assetTransfer.UpdateAsset(transactionContext, 'asset1', 'orange', 10, 'Me', 500);
let ret = JSON.parse(await chaincodeStub.getState(asset.ID));
let expected = {
ID: 'asset1',
Color: 'orange',
Size: 10,
Owner: 'Me',
AppraisedValue: 500
};
expect(ret).to.eql(expected);
});
});
describe('Test DeleteAsset', () => {
it('should return error on DeleteAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
try {
await assetTransfer.DeleteAsset(transactionContext, 'asset2');
assert.fail('DeleteAsset should have failed');
} catch (err) {
expect(err.message).to.equal('The asset asset2 does not exist');
}
});
it('should return success on DeleteAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
await assetTransfer.DeleteAsset(transactionContext, asset.ID);
let ret = await chaincodeStub.getState(asset.ID);
expect(ret).to.equal(undefined);
});
});
describe('Test TransferAsset', () => {
it('should return error on TransferAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
try {
await assetTransfer.TransferAsset(transactionContext, 'asset2', 'Me');
assert.fail('DeleteAsset should have failed');
} catch (err) {
expect(err.message).to.equal('The asset asset2 does not exist');
}
});
it('should return success on TransferAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
await assetTransfer.TransferAsset(transactionContext, asset.ID, 'Me');
let ret = JSON.parse((await chaincodeStub.getState(asset.ID)).toString());
expect(ret).to.eql(Object.assign({}, asset, {Owner: 'Me'}));
});
});
});

View file

@ -1,615 +0,0 @@
## Dutch auction
This example allows you to run a [Dutch auction](https://en.wikipedia.org/wiki/Dutch_auction) that sells multiple items of the same good. All items are sold at the price that clears the auction. You also have the option of adding an auditor organization to the auction. If the organizations running the auction cannot agree, or encounter a technical error that prevents them from updating the auction, one of the auction participants can appeal to an auditor organization. The dutch auction smart contract provides an example of how create a complex signature policy by creating a protobuf and then using the policy for state based endorsement.
This tutorial uses the example smart contract to run an auction in which a single seller wants to sell 100 tickets to multiple bidders. If you chose to add an auditor to the auction, you can appeal to the auditor to end the auction by overriding the standard auction endorsement policy.
## Deploy the chaincode
Change into the test network directory.
```
cd fabric-samples/test-network
```
If the test network is already running, run the following command to bring the network down and start from a clean initial state.
```
./network.sh down
```
You can then run the following command to deploy a new network.
```
./network.sh up createChannel -ca
```
Run the following command to deploy the dutch auction smart contract.
```
./network.sh deployCC -ccn auction -ccp ../auction-dutch/chaincode-go/ -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -ccl go
```
Note that we deploy the smart contract with an endorsement policy of `"OR('Org1MSP.peer','Org2MSP.peer')" ` instead of using the default endorsement policy of the majority of orgs on the channel. Either Org1 or Org2 can create an auction without the endorsement of the other organization.
## Add an auditor (optional)
The smart contract allows you to add an auditor organization to the auction. The auditor can add bids, close the auction, or end the auction if participants cannot cooperate. In this tutorial, we will add the Org3 organization to the test network channel and install an auditor specific version of the dutch auction smart contract. This allows you to use Org3 as the auditor organization.
From the `test-network` directory, issue the following commands to add Org3 to the channel:
```
cd addOrg3
./addOrg3.sh up
```
Navigate back to the test network directory:
```
cd ..
```
Set the following environment to interact with the test network as Org3.
```
export PATH=${PWD}/../bin:$PATH
export FABRIC_CFG_PATH=${PWD}/../config/
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID=Org3MSP
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org3.example.com/users/Admin@org3.example.com/msp
export CORE_PEER_ADDRESS=localhost:11051
```
To deploy the smart contract on the Org3 peer, we need to use the peer lifecycle chaincode commands to install the chaincode package and approve the chaincode definition as Org3. Run the following command to package the auditor version of the dutch auction smart contract:
```
peer lifecycle chaincode package auction.tar.gz --path ../auction-dutch/chaincode-go-auditor/ --lang golang --label auction_1
```
Install the chaincode package on the Org3 peer:
```
peer lifecycle chaincode install auction.tar.gz
```
The next step is to approve the chaincode as the Org3 admin. This requires getting the package ID of the chaincode that we just installed.
```
peer lifecycle chaincode queryinstalled
```
The command should return a response similar to the following:
```
Installed chaincodes on peer:
Package ID: auction_1:8f0d6b6b5a616a1c2b6a9268418f2ee65718acc3c07ea12e123b189b3fb4fb14, Label: auction_1
```
Save the package ID returned by the command above as an environment variable. The package ID will not be the same for all users, so you need to complete this step using the package ID returned from your console.
```
export CC_PACKAGE_ID=auction_1:8f0d6b6b5a616a1c2b6a9268418f2ee65718acc3c07ea12e123b189b3fb4fb14
```
You can now approve the auction chaincode for Org3:
```
peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" --channelID mychannel --name auction --version 1.0 --package-id $CC_PACKAGE_ID --sequence 1 --signature-policy "OR('Org1MSP.peer','Org2MSP.peer')"
```
The command will start the dutch auction chaincode on the Org3 peer. Note that we did not update the endorsement policy before we added the auditor organization. Only Org1 and Org2 will be able create an auction. The auditor is added the endorsement policy after the auction is created. Because the auditor does not need to create an auction or create new bids, the auditor can run a different version of the smart contract than the auction participants. The auditor version of the smart contract also adds logic to check that the request is submitted by one of the auction participants before the auditor can intervene.
## Install the application dependencies
We will run the dutch auction using a series of Node.js applications. Change into the `application-javascript` directory:
```
cd fabric-samples/auction-dutch/application-javascript
```
From this directory, run the following command to download the application dependencies if you have not done so already:
```
npm install
```
## Register and enroll the application identities
To interact with the network, you will need to enroll the Certificate Authority administrators of Org1 and Org2. You can use the `enrollAdmin.js` program for this task. Run the following command to enroll the Org1 admin:
```
node enrollAdmin.js org1
```
You should see the logs of the admin wallet being created on your local file system. Now run the command to enroll the CA admin of Org2:
```
node enrollAdmin.js org2
```
We can use the CA admins of both organizations to register and enroll the identities of the seller that will create the auction and the bidders who will try to purchase the tickets. Run the following command to register and enroll the seller identity that will create the auction. The seller will belong to Org1.
```
node registerEnrollUser.js org1 seller
```
You should see the logs of the seller wallet being created as well. Run the following commands to register and enroll two bidders from Org1 and another three bidders from Org2:
```
node registerEnrollUser.js org1 bidder1
node registerEnrollUser.js org1 bidder2
node registerEnrollUser.js org2 bidder3
node registerEnrollUser.js org2 bidder4
node registerEnrollUser.js org2 bidder5
```
## Create the auction
The seller from Org1 would like to create an auction to sell 100 tickets. Run the following command to use the seller wallet to run the `createAuction.js` application. The seller needs to provide an auction ID, the item to be sold, and the quantity to be sold to create the auction. The seller uses `withAuditor` to indicate that Org3 will be added as the auditor organization. If you do not want to add an auditor, you can provide a value of `noAuditor`. You will see the application query the auction after it is created.
```
node createAuction.js org1 seller auction1 tickets 100 withAuditor
```
Adding an auditor to the auction creates an endorsement policy with the auditor included. Without the auditor, each organization with sellers or bidders participating in the auction is added to the auction endorsement policy. For example, if the auction had two organizations participating in the auction, the auction endorsement policy would be `AND(Org1, Org2)`. However, if the selling organization decides to add an auditor, the auditor organization would be added to the endorsement policy. If the participating organizations disagree, or if a participant has a technical problem, the auditor can join any one of the participating organizations and agree to update the auction. Extending the example above, if the auction with two organizations added an auditor, the auction endorsement policy would be `OR(AND(Org1, Org2), AND(auditor, OR(Org1, Org2)))`.
## Bid on the auction
We can now use the bidder wallets to submit bids to the auction:
### Bid as bidder1
Bidder1 will create a bid to purchase 50 tickets for 80 dollars.
```
node bid.js org1 bidder1 auction1 50 80
```
The application will query the bid after it is created:
```
*** Result: Bid: {
"objectType": "bid",
"quantity": 50,
"price": 80,
"org": "Org1MSP",
"buyer": "x509::CN=bidder1,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US"
}
```
The `bid.js` application also prints the bidID:
```
*** Result ***SAVE THIS VALUE*** BidID: 6630e1bb06e827a2b77023f63677fae8a0ad43126730e450d3252fa58eeb85b1
```
The BidID acts as the unique identifier for the bid. This ID allows you to query the bid using the `queryBid.js` program and add the bid to the auction. Save the bidID returned by the application as an environment variable in your terminal:
```
export BIDDER1_BID_ID=6630e1bb06e827a2b77023f63677fae8a0ad43126730e450d3252fa58eeb85b1
```
This value will be different for each transaction, so you will need to use the value returned in your terminal.
Now that the bid has been created, you can submit the bid to the auction. Run the following command to submit the bid that was just created:
```
node submitBid.js org1 bidder1 auction1 $BIDDER1_BID_ID
```
The hash of bid is added to the list of private bids in that have been submitted to `auction1`. Storing the hash on the public auction ledger allows users to prove the accuracy of the bids they reveal once bidding is closed. The application queries the auction to verify that the bid was added:
```
*** Result: Auction: {
"objectType": "auction",
"item": "tickets",
"seller": "x509::CN=seller,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US",
"quantity": 100,
"organizations": [
"Org1MSP"
],
"privateBids": {
"\u0000bid\u0000auction1\u00006630e1bb06e827a2b77023f63677fae8a0ad43126730e450d3252fa58eeb85b1\u0000": {
"org": "Org1MSP",
"hash": "2f7a62152627d69d73e31b62cd4731d32ecc277de0eef4d30b1235891298abf7"
}
},
"revealedBids": {},
"winners": [],
"price": 0,
"status": "open",
"auditor": true
}
```
### Bid as bidder2
Let's submit another bid. Bidder2 would like to purchase 40 tickets for 50 dollars.
```
node bid.js org1 bidder2 auction1 40 50
```
Save the Bid ID returned by the application:
```
export BIDDER2_BID_ID=5796569dae2e95242eadc5cf1cf8aa24f5ae072d801e7decb2547530de5a65e8
```
Submit bidder2's bid to the auction:
```
node submitBid.js org1 bidder2 auction1 $BIDDER2_BID_ID
```
### Bid as bidder3 from Org2
Bidder3 will bid for 30 tickets at 70 dollars:
```
node bid.js org2 bidder3 auction1 30 70
```
Save the Bid ID returned by the application:
```
export BIDDER3_BID_ID=d52ea4d9b4bc428d395db2d68323bc12cc9b5c1f8617900f459ccd41c38d3c0a
```
Add bidder3's bid to the auction:
```
node submitBid.js org2 bidder3 auction1 $BIDDER3_BID_ID
```
Because bidder3 belongs to Org2, submitting the bid will add Org2 to the list of participating organizations. You can see the Org2 MSP ID has been added to the list of `"organizations"` in the updated auction returned by the application:
```
*** Result: Auction: {
"objectType": "auction",
"item": "tickets",
"seller": "x509::CN=seller,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US",
"quantity": 100,
"organizations": [
"Org1MSP",
"Org2MSP"
],
"privateBids": {
"\u0000bid\u0000auction1\u00005796569dae2e95242eadc5cf1cf8aa24f5ae072d801e7decb2547530de5a65e8\u0000": {
"org": "Org1MSP",
"hash": "598749480aa3af816a829455e1fdac25a44f31c2ae81f911f85d004f44dbbe6c"
},
"\u0000bid\u0000auction1\u00006630e1bb06e827a2b77023f63677fae8a0ad43126730e450d3252fa58eeb85b1\u0000": {
"org": "Org1MSP",
"hash": "2f7a62152627d69d73e31b62cd4731d32ecc277de0eef4d30b1235891298abf7"
},
"\u0000bid\u0000auction1\u0000d52ea4d9b4bc428d395db2d68323bc12cc9b5c1f8617900f459ccd41c38d3c0a\u0000": {
"org": "Org2MSP",
"hash": "bf1e9fb80ea3e29780fe13b4781b6dad28fa83b4b5db68bd7e90252875d152fb"
}
},
"revealedBids": {},
"winners": [],
"price": 0,
"status": "open",
"auditor": true
}
```
Now that a bid from Org2 has been added to the auction, any updates to the auction need to be endorsed by the Org2 peer. The applications will use the `"organizations"` field to specify which organizations need to endorse submitting a new bid, revealing a bid, or updating the auction status.
### Bid as bidder4
Bidder4 from Org2 would like to purchase 15 tickets for 60 dollars:
```
node bid.js org2 bidder4 auction1 15 60
```
Save the Bid ID returned by the application:
```
export BIDDER4_BID_ID=c6464f984bb01e639a46e58b94c496e8bbd829b5e4fa7ffcc150d9a565d45684
```
Add bidder4's bid to the auction:
```
node submitBid.js org2 bidder4 auction1 $BIDDER4_BID_ID
```
### Bid as bidder5
Bidder5 from Org2 will bid for 20 tickets at 60 dollars:
```
node bid.js org2 bidder5 auction1 20 60
```
Save the Bid ID returned by the application:
```
export BIDDER5_BID_ID=f4024ab09b4dacf0a636927414850dde2a2a5e8ec4601e2a0071f5c233248207
```
Add bidder5's bid to the auction:
```
node submitBid.js org2 bidder5 auction1 $BIDDER5_BID_ID
```
## Close the auction
Now that all five bidders have joined the auction, the seller would like to close the auction and allow buyers to reveal their bids. The seller identity that created the auction needs to submit the transaction:
```
node closeAuction.js org1 seller auction1
```
The application will query the auction to allow you to verify that the auction status has changed to closed.
## Reveal bids
After the auction is closed, bidders can try to win the auction by revealing their bids. The transaction to reveal a bid needs to pass four checks:
1. The auction is closed.
2. The transaction was submitted by the identity that created the bid.
3. The hash of the revealed bid matches the hash of the bid on the channel ledger. This confirms that the bid is the same as the bid that is stored in the private data collection.
4. The hash of the revealed bid matches the hash that was submitted to the auction. This confirms that the bid was not altered after the auction was closed.
Use the `revealBid.js` application to reveal the bid of Bidder1:
```
node revealBid.js org1 bidder1 auction1 $BIDDER1_BID_ID
```
The full bid details, including the quantity and price, are now visible:
```
*** Result: Auction: {
"objectType": "auction",
"item": "tickets",
"seller": "x509::CN=seller,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US",
"quantity": 100,
"organizations": [
"Org1MSP",
"Org2MSP"
],
"privateBids": {
"\u0000bid\u0000auction1\u00005796569dae2e95242eadc5cf1cf8aa24f5ae072d801e7decb2547530de5a65e8\u0000": {
"org": "Org1MSP",
"hash": "598749480aa3af816a829455e1fdac25a44f31c2ae81f911f85d004f44dbbe6c"
},
"\u0000bid\u0000auction1\u00006630e1bb06e827a2b77023f63677fae8a0ad43126730e450d3252fa58eeb85b1\u0000": {
"org": "Org1MSP",
"hash": "2f7a62152627d69d73e31b62cd4731d32ecc277de0eef4d30b1235891298abf7"
},
"\u0000bid\u0000auction1\u0000c6464f984bb01e639a46e58b94c496e8bbd829b5e4fa7ffcc150d9a565d45684\u0000": {
"org": "Org2MSP",
"hash": "eefcadf8e9e5cb8322a6e642ab6d5512d62e6d68f37a72b00f5b0d9e580eddb9"
},
"\u0000bid\u0000auction1\u0000d52ea4d9b4bc428d395db2d68323bc12cc9b5c1f8617900f459ccd41c38d3c0a\u0000": {
"org": "Org2MSP",
"hash": "bf1e9fb80ea3e29780fe13b4781b6dad28fa83b4b5db68bd7e90252875d152fb"
},
"\u0000bid\u0000auction1\u0000f4024ab09b4dacf0a636927414850dde2a2a5e8ec4601e2a0071f5c233248207\u0000": {
"org": "Org2MSP",
"hash": "de82232141bac06ea3818146fb650dc9930d45b9ceab506ac66942b119eec094"
}
},
"revealedBids": {
"\u0000bid\u0000auction1\u00006630e1bb06e827a2b77023f63677fae8a0ad43126730e450d3252fa58eeb85b1\u0000": {
"objectType": "bid",
"quantity": 50,
"price": 80,
"org": "Org1MSP",
"buyer": "x509::CN=bidder1,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US"
}
},
"winners": [],
"price": 0,
"status": "closed",
"auditor": true
}
```
We will add three more bidders, the second bidder from Org1 and two bidders from Org2. Run the following commands to reveal the bidders:
```
node revealBid.js org1 bidder2 auction1 $BIDDER2_BID_ID
node revealBid.js org2 bidder4 auction1 $BIDDER4_BID_ID
node revealBid.js org2 bidder5 auction1 $BIDDER5_BID_ID
```
Let's try to end the auction using the seller identity and see what happens.
```
node endAuction.js org1 seller auction1
```
The output should look something like the following:
```
--> Submit the transaction to end the auction
2021-01-28T16:47:27.501Z - error: [DiscoveryHandler]: compareProposalResponseResults[undefined] - read/writes result sets do not match index=1
2021-01-28T16:47:27.503Z - error: [Transaction]: Error: No valid responses from any peers. Errors:
peer=undefined, status=grpc, message=Peer endorsements do not match
******** FAILED to submit bid: Error: No valid responses from any peers. Errors:
peer=undefined, status=grpc, message=Peer endorsements do not match
```
Instead of ending the auction, the transaction results in an endorsement policy failure. The end of the auction needs to be endorsed by Org2. Before endorsing the transaction, the Org2 peer queries its private data collection for any winning bids that have not yet been revealed. Because the price that would clear the auction with the currently revealed bids is lower than the bid of Bidder3, the Org2 peer refuses to endorse the transaction that would end the auction.
In order to end the auction, Org1 would either need to wait for Org2 to reveal the final bid or appeal to the auditor. Depending on if you created the organization with an auditor, you can end the auction with either set of steps.
## End the auction using an auditor
If Org2 is unable to endorse the transaction to end the auction, Org1 can ask the auditor to intervene. The following program gets an endorsement from the Org3 auditor and Org1 to end the auction. As a result, the transaction would meet the auditor component of the state based endorsement policy.
```
node endAuctionwithAuditor org1 seller auction1
```
Even though Org2 has not agreed to the end of the auction, the endorsement Org1 is sufficient to end the auction if the auditor agrees. As part of ending the auction, both Org1 and the auditor need to calculate the same price and the same set of winners. Each winning bidder is listed next to the quantity that was allocated to them.
```
*** Result: Auction: {
"objectType": "auction",
"item": "tickets",
"seller": "x509::CN=seller,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US",
"quantity": 100,
"organizations": [
"Org1MSP",
"Org2MSP"
],
"privateBids": {
"\u0000bid\u0000auction1\u00005796569dae2e95242eadc5cf1cf8aa24f5ae072d801e7decb2547530de5a65e8\u0000": {
"org": "Org1MSP",
"hash": "598749480aa3af816a829455e1fdac25a44f31c2ae81f911f85d004f44dbbe6c"
},
"\u0000bid\u0000auction1\u00006630e1bb06e827a2b77023f63677fae8a0ad43126730e450d3252fa58eeb85b1\u0000": {
"org": "Org1MSP",
"hash": "2f7a62152627d69d73e31b62cd4731d32ecc277de0eef4d30b1235891298abf7"
},
"\u0000bid\u0000auction1\u0000c6464f984bb01e639a46e58b94c496e8bbd829b5e4fa7ffcc150d9a565d45684\u0000": {
"org": "Org2MSP",
"hash": "eefcadf8e9e5cb8322a6e642ab6d5512d62e6d68f37a72b00f5b0d9e580eddb9"
},
"\u0000bid\u0000auction1\u0000d52ea4d9b4bc428d395db2d68323bc12cc9b5c1f8617900f459ccd41c38d3c0a\u0000": {
"org": "Org2MSP",
"hash": "bf1e9fb80ea3e29780fe13b4781b6dad28fa83b4b5db68bd7e90252875d152fb"
},
"\u0000bid\u0000auction1\u0000f4024ab09b4dacf0a636927414850dde2a2a5e8ec4601e2a0071f5c233248207\u0000": {
"org": "Org2MSP",
"hash": "de82232141bac06ea3818146fb650dc9930d45b9ceab506ac66942b119eec094"
}
},
"revealedBids": {
"\u0000bid\u0000auction1\u00005796569dae2e95242eadc5cf1cf8aa24f5ae072d801e7decb2547530de5a65e8\u0000": {
"objectType": "bid",
"quantity": 40,
"price": 50,
"org": "Org1MSP",
"buyer": "x509::CN=bidder2,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US"
},
"\u0000bid\u0000auction1\u00006630e1bb06e827a2b77023f63677fae8a0ad43126730e450d3252fa58eeb85b1\u0000": {
"objectType": "bid",
"quantity": 50,
"price": 80,
"org": "Org1MSP",
"buyer": "x509::CN=bidder1,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US"
},
"\u0000bid\u0000auction1\u0000c6464f984bb01e639a46e58b94c496e8bbd829b5e4fa7ffcc150d9a565d45684\u0000": {
"objectType": "bid",
"quantity": 15,
"price": 60,
"org": "Org2MSP",
"buyer": "x509::CN=bidder4,OU=client+OU=org2+OU=department1::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK"
},
"\u0000bid\u0000auction1\u0000f4024ab09b4dacf0a636927414850dde2a2a5e8ec4601e2a0071f5c233248207\u0000": {
"objectType": "bid",
"quantity": 20,
"price": 60,
"org": "Org2MSP",
"buyer": "x509::CN=bidder5,OU=client+OU=org2+OU=department1::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK"
}
},
"winners": [
{
"buyer": "x509::CN=bidder1,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US",
"quantity": 50
},
{
"buyer": "x509::CN=bidder4,OU=client+OU=org2+OU=department1::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK",
"quantity": 15
},
{
"buyer": "x509::CN=bidder5,OU=client+OU=org2+OU=department1::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK",
"quantity": 20
},
{
"buyer": "x509::CN=bidder2,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US",
"quantity": 15
}
],
"price": 50,
"status": "ended",
"auditor": true
}
```
The auction allocates tickets to the highest bids first. Because all 100 tickets are sold after allocating tickets to the bid that was submitted at 50, 50 is the `"price"` that clears the auction.
## End the auction without an auditor
If we did not add an auditor to the auction, we need to add the remaining bid so that Org2 will endorse ending the auction.
```
node revealBid.js org2 bidder3 auction1 $BIDDER3_BID_ID
```
Now that all the winning bids have been revealed, we can submit the transaction to end the auction once more.
```
node endAuction org1 seller auction1
```
The transaction was successfully endorsed by both Org1 and Org2, who both calculated the same price and winners of the auction. Each winning bidder is listed next to the quantity that was allocated to them.
```
*** Result: Auction: {
"objectType": "auction",
"item": "tickets",
"seller": "x509::CN=seller,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US",
"quantity": 100,
"organizations": [
"Org1MSP",
"Org2MSP"
],
"privateBids": {
"\u0000bid\u0000auction1\u0000482b2a68fbbfae329b0b4bc9d70b90f3a55fdcbae5f5274dec34d438efb6847e\u0000": {
"org": "Org1MSP",
"hash": "2f7a62152627d69d73e31b62cd4731d32ecc277de0eef4d30b1235891298abf7"
},
"\u0000bid\u0000auction1\u000048d93017ac65cff0dd23406cc29918724fd84c8e7014eee30fd492fef760e6a4\u0000": {
"org": "Org2MSP",
"hash": "bf1e9fb80ea3e29780fe13b4781b6dad28fa83b4b5db68bd7e90252875d152fb"
},
"\u0000bid\u0000auction1\u00005ba4c856224cdc8209b0e42f30a757331e3fb8a8b660b64a55e1bcf688b745ad\u0000": {
"org": "Org1MSP",
"hash": "598749480aa3af816a829455e1fdac25a44f31c2ae81f911f85d004f44dbbe6c"
},
"\u0000bid\u0000auction1\u000063c8a192dae1332ae42af890f8a966fea2ae8365ca9746447e014a7c0494d64e\u0000": {
"org": "Org2MSP",
"hash": "de82232141bac06ea3818146fb650dc9930d45b9ceab506ac66942b119eec094"
},
"\u0000bid\u0000auction1\u000066ff6d8bbe81e98654fc417915808031d49e93cd8d7475f15317d801317254fa\u0000": {
"org": "Org2MSP",
"hash": "eefcadf8e9e5cb8322a6e642ab6d5512d62e6d68f37a72b00f5b0d9e580eddb9"
}
},
"revealedBids": {
"\u0000bid\u0000auction1\u0000482b2a68fbbfae329b0b4bc9d70b90f3a55fdcbae5f5274dec34d438efb6847e\u0000": {
"objectType": "bid",
"quantity": 50,
"price": 80,
"org": "Org1MSP",
"buyer": "x509::CN=bidder1,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US"
},
"\u0000bid\u0000auction1\u000048d93017ac65cff0dd23406cc29918724fd84c8e7014eee30fd492fef760e6a4\u0000": {
"objectType": "bid",
"quantity": 30,
"price": 70,
"org": "Org2MSP",
"buyer": "x509::CN=bidder3,OU=client+OU=org2+OU=department1::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK"
},
"\u0000bid\u0000auction1\u00005ba4c856224cdc8209b0e42f30a757331e3fb8a8b660b64a55e1bcf688b745ad\u0000": {
"objectType": "bid",
"quantity": 40,
"price": 50,
"org": "Org1MSP",
"buyer": "x509::CN=bidder2,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US"
},
"\u0000bid\u0000auction1\u000063c8a192dae1332ae42af890f8a966fea2ae8365ca9746447e014a7c0494d64e\u0000": {
"objectType": "bid",
"quantity": 20,
"price": 60,
"org": "Org2MSP",
"buyer": "x509::CN=bidder5,OU=client+OU=org2+OU=department1::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK"
},
"\u0000bid\u0000auction1\u000066ff6d8bbe81e98654fc417915808031d49e93cd8d7475f15317d801317254fa\u0000": {
"objectType": "bid",
"quantity": 15,
"price": 60,
"org": "Org2MSP",
"buyer": "x509::CN=bidder4,OU=client+OU=org2+OU=department1::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK"
}
},
"winners": [
{
"buyer": "x509::CN=bidder1,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US",
"quantity": 50
},
{
"buyer": "x509::CN=bidder3,OU=client+OU=org2+OU=department1::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK",
"quantity": 30
},
{
"buyer": "x509::CN=bidder4,OU=client+OU=org2+OU=department1::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK",
"quantity": 15
},
{
"buyer": "x509::CN=bidder5,OU=client+OU=org2+OU=department1::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK",
"quantity": 5
}
],
"price": 60,
"status": "ended",
"auditor": false
}
```
The auction allocates tickets to the highest bids first. Because all 100 tickets are sold after allocating tickets to the bids that were submitted at 60, 60 is the `"price"` that clears the auction. The first 80 tickets are allocated to Bidder1 and Bidder3. The remaining 20 tickers are allocated to Bidder4 and Bidder5. When bids are tied, the auction smart contract fills the smaller bids first. As a result, Bidder4 is awarded their full bid of 15 tickets, while Bidder5 is allocated the remaining 5 tickets.
## Clean up
When your are done using the auction smart contract, you can bring down the network and clean up the environment. In the `auction-dutch/application-javascript` directory, run the following command to remove the wallets used to run the applications:
```
rm -rf wallet
```
You can then navigate to the test network directory and bring down the network:
````
cd ../../test-network/
./network.sh down
````

View file

@ -1,5 +0,0 @@
#
# SPDX-License-Identifier: Apache-2.0
#
coverage

View file

@ -1,36 +0,0 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
module.exports = {
env: {
node: true,
mocha: true
},
parserOptions: {
ecmaVersion: 8,
sourceType: 'script'
},
extends: 'eslint:recommended',
rules: {
indent: ['error', 'tab'],
'linebreak-style': ['error', 'unix'],
quotes: ['error', 'single'],
semi: ['error', 'always'],
'no-unused-vars': ['error', { args: 'none' }],
'no-console': 'off',
curly: 'error',
eqeqeq: 'error',
'no-throw-literal': 'error',
strict: 'error',
'no-var': 'error',
'dot-notation': 'error',
'no-trailing-spaces': 'error',
'no-use-before-define': 'error',
'no-useless-call': 'error',
'no-with': 'error',
'operator-linebreak': 'error',
yoda: 'error',
'quote-props': ['error', 'as-needed']
}
};

View file

@ -1,98 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const path = require('path');
const { buildCCPOrg1, buildCCPOrg2, buildWallet, prettyJSONString } = require('../../test-application/javascript/AppUtil.js');
const myChannel = 'mychannel';
const myChaincodeName = 'auction';
async function bid (ccp, wallet, user, orgMSP, auctionID, quantity, price) {
try {
const gateway = new Gateway();
// connect using Discovery enabled
await gateway.connect(ccp,
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
const network = await gateway.getNetwork(myChannel);
const contract = network.getContract(myChaincodeName);
console.log('\n--> Evaluate Transaction: get your client ID');
const buyer = await contract.evaluateTransaction('GetSubmittingClientIdentity');
console.log('*** Result: Buyer ID is ' + buyer.toString());
const bidData = { objectType: 'bid', quantity: parseInt(quantity), price: parseInt(price), org: orgMSP, buyer: buyer.toString() };
const statefulTxn = contract.createTransaction('Bid');
statefulTxn.setEndorsingOrganizations(orgMSP);
const tmapData = Buffer.from(JSON.stringify(bidData));
statefulTxn.setTransient({
bid: tmapData
});
const bidID = statefulTxn.getTransactionId();
console.log('\n--> Submit Transaction: Create the bid that is stored in your private data collection of your organization');
await statefulTxn.submit(auctionID);
console.log('*** Result: committed');
console.log('*** Result ***SAVE THIS VALUE*** BidID: ' + bidID.toString());
console.log('\n--> Evaluate Transaction: read the bid that was just created');
const result = await contract.evaluateTransaction('QueryBid', auctionID, bidID);
console.log('*** Result: Bid: ' + prettyJSONString(result.toString()));
gateway.disconnect();
} catch (error) {
console.error(`******** FAILED to submit bid: ${error}`);
if (error.stack) {
console.error(error.stack);
}
process.exit(1);
}
}
async function main () {
try {
if (process.argv[2] === undefined || process.argv[3] === undefined ||
process.argv[4] === undefined || process.argv[5] === undefined ||
process.argv[6] === undefined) {
console.log('Usage: node bid.js org userID auctionID quantity price');
process.exit(1);
}
const org = process.argv[2];
const user = process.argv[3];
const auctionID = process.argv[4];
const quantity = process.argv[5];
const price = process.argv[6];
if (org === 'Org1' || org === 'org1') {
const orgMSP = 'Org1MSP';
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await bid(ccp, wallet, user, orgMSP, auctionID, quantity, price);
} else if (org === 'Org2' || org === 'org2') {
const orgMSP = 'Org2MSP';
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await bid(ccp, wallet, user, orgMSP, auctionID, quantity, price);
} else {
console.log('Usage: node bid.js org userID auctionID quantity price');
console.log('Org must be Org1 or Org2');
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
process.exit(1);
}
}
main();

View file

@ -1,90 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const path = require('path');
const { buildCCPOrg1, buildCCPOrg2, buildWallet, prettyJSONString } = require('../../test-application/javascript/AppUtil.js');
const myChannel = 'mychannel';
const myChaincodeName = 'auction';
async function closeAuction (ccp, wallet, user, auctionID) {
try {
const gateway = new Gateway();
// connect using Discovery enabled
await gateway.connect(ccp,
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
const network = await gateway.getNetwork(myChannel);
const contract = network.getContract(myChaincodeName);
// Query the auction to get the list of endorsing orgs.
// console.log('\n--> Evaluate Transaction: query the auction you want to close');
const auctionString = await contract.evaluateTransaction('QueryAuction', auctionID);
// console.log('*** Result: Bid: ' + prettyJSONString(auctionString.toString()));
const auctionJSON = JSON.parse(auctionString);
const statefulTxn = contract.createTransaction('CloseAuction');
if (auctionJSON.organizations.length === 2) {
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0], auctionJSON.organizations[1]);
} else {
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0]);
}
console.log('\n--> Submit Transaction: close auction');
await statefulTxn.submit(auctionID);
console.log('*** Result: committed');
console.log('\n--> Evaluate Transaction: query the updated auction');
const result = await contract.evaluateTransaction('QueryAuction', auctionID);
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
gateway.disconnect();
} catch (error) {
console.error(`******** FAILED to submit bid: ${error}`);
process.exit(1);
}
}
async function main () {
try {
if (process.argv[2] === undefined || process.argv[3] === undefined || process.argv[4] === undefined) {
console.log('Usage: node closeAuction.js org userID auctionID');
process.exit(1);
}
const org = process.argv[2];
const user = process.argv[3];
const auctionID = process.argv[4];
if (org === 'Org1' || org === 'org1') {
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await closeAuction(ccp, wallet, user, auctionID);
} else if (org === 'Org2' || org === 'org2') {
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await closeAuction(ccp, wallet, user, auctionID);
} else {
console.log('Usage: node closeAuction.js org userID auctionID');
console.log('Org must be Org1 or Org2');
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
if (error.stack) {
console.error(error.stack);
}
process.exit(1);
}
}
main();

View file

@ -1,78 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const path = require('path');
const { buildCCPOrg1, buildCCPOrg2, buildWallet, prettyJSONString } = require('../../test-application/javascript/AppUtil.js');
const myChannel = 'mychannel';
const myChaincodeName = 'auction';
async function createAuction (ccp, wallet, user, auctionID, item, quantity, auditor) {
try {
const gateway = new Gateway();
// connect using Discovery enabled
await gateway.connect(ccp,
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
const network = await gateway.getNetwork(myChannel);
const contract = network.getContract(myChaincodeName);
const statefulTxn = contract.createTransaction('CreateAuction');
console.log('\n--> Submit Transaction: Propose a new auction');
await statefulTxn.submit(auctionID, item, parseInt(quantity), auditor);
console.log('*** Result: committed');
console.log('\n--> Evaluate Transaction: query the auction that was just created');
const result = await contract.evaluateTransaction('QueryAuction', auctionID);
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
gateway.disconnect();
} catch (error) {
console.error(`******** FAILED to submit bid: ${error}`);
}
}
async function main () {
try {
if (process.argv[2] === undefined || process.argv[3] === undefined ||
process.argv[4] === undefined || process.argv[5] === undefined ||
process.argv[6] === undefined) {
console.log('Usage: node createAuction.js org userID auctionID item quantity');
process.exit(1);
}
const org = process.argv[2];
const user = process.argv[3];
const auctionID = process.argv[4];
const item = process.argv[5];
const quantity = process.argv[6];
const auditor = process.argv[7];
if (org === 'Org1' || org === 'org1') {
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await createAuction(ccp, wallet, user, auctionID, item, quantity, auditor);
} else if (org === 'Org2' || org === 'org2') {
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await createAuction(ccp, wallet, user, auctionID, item, quantity, auditor);
} else {
console.log('Usage: node createAuction.js org userID auctionID item quantity');
console.log('Org must be Org1 or Org2');
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
}
}
main();

View file

@ -1,91 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const path = require('path');
const { buildCCPOrg1, buildCCPOrg2, buildWallet, prettyJSONString } = require('../../test-application/javascript/AppUtil.js');
const myChannel = 'mychannel';
const myChaincodeName = 'auction';
async function endAuction (ccp, wallet, user, auctionID) {
try {
const gateway = new Gateway();
// connect using Discovery enabled
await gateway.connect(ccp,
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
const network = await gateway.getNetwork(myChannel);
const contract = network.getContract(myChaincodeName);
// Query the auction to get the list of endorsing orgs.
// console.log('\n--> Evaluate Transaction: query the auction you want to end');
const auctionString = await contract.evaluateTransaction('QueryAuction', auctionID);
// console.log('*** Result: Bid: ' + prettyJSONString(auctionString.toString()));
const auctionJSON = JSON.parse(auctionString);
const statefulTxn = contract.createTransaction('EndAuction');
if (auctionJSON.organizations.length === 2) {
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0], auctionJSON.organizations[1]);
} else {
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0]);
}
console.log('\n--> Submit the transaction to end the auction');
await statefulTxn.submit(auctionID);
console.log('*** Result: committed');
console.log('\n--> Evaluate Transaction: query the updated auction');
const result = await contract.evaluateTransaction('QueryAuction', auctionID);
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
gateway.disconnect();
} catch (error) {
console.error(`******** FAILED to submit bid: ${error}`);
process.exit(1);
}
}
async function main () {
try {
if (process.argv[2] === undefined || process.argv[3] === undefined ||
process.argv[4] === undefined) {
console.log('Usage: node endAuction.js org userID auctionID');
process.exit(1);
}
const org = process.argv[2];
const user = process.argv[3];
const auctionID = process.argv[4];
if (org === 'Org1' || org === 'org1') {
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await endAuction(ccp, wallet, user, auctionID);
} else if (org === 'Org2' || org === 'org2') {
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await endAuction(ccp, wallet, user, auctionID);
} else {
console.log('Usage: node endAuction.js org userID auctionID');
console.log('Org must be Org1 or Org2');
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
if (error.stack) {
console.error(error.stack);
}
process.exit(1);
}
}
main();

View file

@ -1,83 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const path = require('path');
const { buildCCPOrg1, buildCCPOrg2, buildWallet, prettyJSONString } = require('../../test-application/javascript/AppUtil.js');
const myChannel = 'mychannel';
const myChaincodeName = 'auction';
async function endAuction (ccp, wallet, org, user, auctionID) {
try {
const gateway = new Gateway();
// connect using Discovery enabled
await gateway.connect(ccp,
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
const network = await gateway.getNetwork(myChannel);
const contract = network.getContract(myChaincodeName);
const statefulTxn = contract.createTransaction('EndAuction');
statefulTxn.setEndorsingOrganizations(org, 'Org3MSP');
console.log('\n--> Submit the transaction to end the auction');
await statefulTxn.submit(auctionID);
console.log('*** Result: committed');
console.log('\n--> Evaluate Transaction: query the updated auction');
const result = await contract.evaluateTransaction('QueryAuction', auctionID);
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
gateway.disconnect();
} catch (error) {
console.error(`******** FAILED to submit bid: ${error}`);
process.exit(1);
}
}
async function main () {
try {
if (process.argv[2] === undefined || process.argv[3] === undefined ||
process.argv[4] === undefined) {
console.log('Usage: node endAuction.js org userID auctionID');
process.exit(1);
}
const org = process.argv[2];
const user = process.argv[3];
const auctionID = process.argv[4];
if (org === 'Org1' || org === 'org1') {
const orgMSP = 'Org1MSP';
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await endAuction(ccp, wallet, orgMSP, user, auctionID);
} else if (org === 'Org2' || org === 'org2') {
const orgMSP = 'Org2MSP';
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await endAuction(ccp, wallet, orgMSP, user, auctionID);
} else {
console.log('Usage: node endAuction.js org userID auctionID');
console.log('Org must be Org1 or Org2');
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
if (error.stack) {
console.error(error.stack);
}
process.exit(1);
}
}
main();

View file

@ -1,62 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Wallets } = require('fabric-network');
const FabricCAServices = require('fabric-ca-client');
const path = require('path');
const { buildCAClient, enrollAdmin } = require('../../test-application/javascript/CAUtil.js');
const { buildCCPOrg1, buildCCPOrg2, buildWallet } = require('../../test-application/javascript/AppUtil.js');
const mspOrg1 = 'Org1MSP';
const mspOrg2 = 'Org2MSP';
async function connectToOrg1CA () {
console.log('\n--> Enrolling the Org1 CA admin');
const ccpOrg1 = buildCCPOrg1();
const caOrg1Client = buildCAClient(FabricCAServices, ccpOrg1, 'ca.org1.example.com');
const walletPathOrg1 = path.join(__dirname, 'wallet/org1');
const walletOrg1 = await buildWallet(Wallets, walletPathOrg1);
await enrollAdmin(caOrg1Client, walletOrg1, mspOrg1);
}
async function connectToOrg2CA () {
console.log('\n--> Enrolling the Org2 CA admin');
const ccpOrg2 = buildCCPOrg2();
const caOrg2Client = buildCAClient(FabricCAServices, ccpOrg2, 'ca.org2.example.com');
const walletPathOrg2 = path.join(__dirname, 'wallet/org2');
const walletOrg2 = await buildWallet(Wallets, walletPathOrg2);
await enrollAdmin(caOrg2Client, walletOrg2, mspOrg2);
}
async function main () {
if (process.argv[2] === undefined) {
console.log('Usage: node enrollAdmin.js Org');
process.exit(1);
}
const org = process.argv[2];
try {
if (org === 'Org1' || org === 'org1') {
await connectToOrg1CA();
} else if (org === 'Org2' || org === 'org2') {
await connectToOrg2CA();
} else {
console.log('Usage: node registerUser.js org userID');
console.log('Org must be Org1 or Org2');
}
} catch (error) {
console.error(`Error in enrolling admin: ${error}`);
process.exit(1);
}
}
main();

View file

@ -1,26 +0,0 @@
{
"name": "auction",
"version": "1.0.0",
"description": "auction application implemented in JavaScript",
"engines": {
"node": ">=12",
"npm": ">=5"
},
"engineStrict": true,
"author": "Hyperledger",
"license": "Apache-2.0",
"scripts": {
"lint": "eslint *.js"
},
"dependencies": {
"fabric-ca-client": "^2.2.19",
"fabric-network": "^2.2.19"
},
"devDependencies": {
"eslint": "^7.20.0",
"eslint-config-standard": "^16.0.2",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.3.1"
}
}

View file

@ -1,68 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const path = require('path');
const { buildCCPOrg1, buildCCPOrg2, buildWallet, prettyJSONString } = require('../../test-application/javascript/AppUtil.js');
const myChannel = 'mychannel';
const myChaincodeName = 'auction';
async function queryAuction (ccp, wallet, user, auctionID) {
try {
const gateway = new Gateway();
// connect using Discovery enabled
await gateway.connect(ccp,
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
const network = await gateway.getNetwork(myChannel);
const contract = network.getContract(myChaincodeName);
console.log('\n--> Evaluate Transaction: query the auction');
const result = await contract.evaluateTransaction('QueryAuction', auctionID);
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
gateway.disconnect();
} catch (error) {
console.error(`******** FAILED to submit bid: ${error}`);
}
}
async function main () {
try {
if (process.argv[2] === undefined || process.argv[3] === undefined ||
process.argv[4] === undefined) {
console.log('Usage: node queryAuction.js org userID auctionID');
process.exit(1);
}
const org = process.argv[2];
const user = process.argv[3];
const auctionID = process.argv[4];
if (org === 'Org1' || org === 'org1') {
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await queryAuction(ccp, wallet, user, auctionID);
} else if (org === 'Org2' || org === 'org2') {
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await queryAuction(ccp, wallet, user, auctionID);
} else {
console.log('Usage: node queryAuction.js org userID auctionID');
console.log('Org must be Org1 or Org2');
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
}
}
main();

View file

@ -1,69 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const path = require('path');
const { buildCCPOrg1, buildCCPOrg2, buildWallet, prettyJSONString } = require('../../../test-application/javascript/AppUtil.js');
const myChannel = 'mychannel';
const myChaincodeName = 'auction';
async function queryBid (ccp, wallet, user, auctionID, bidID) {
try {
const gateway = new Gateway();
// connect using Discovery enabled
await gateway.connect(ccp,
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
const network = await gateway.getNetwork(myChannel);
const contract = network.getContract(myChaincodeName);
console.log('\n--> Evaluate Transaction: read bid from private data store');
const result = await contract.evaluateTransaction('QueryBid', auctionID, bidID);
console.log('*** Result: Bid: ' + prettyJSONString(result.toString()));
gateway.disconnect();
} catch (error) {
console.error(`******** FAILED to submit bid: ${error}`);
}
}
async function main () {
try {
if (process.argv[2] === undefined || process.argv[3] === undefined ||
process.argv[4] === undefined || process.argv[5] === undefined) {
console.log('Usage: node bid.js org userID auctionID bidID');
process.exit(1);
}
const org = process.argv[2];
const user = process.argv[3];
const auctionID = process.argv[4];
const bidID = process.argv[5];
if (org === 'Org1' || org === 'org1') {
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await queryBid(ccp, wallet, user, auctionID, bidID);
} else if (org === 'Org2' || org === 'org2') {
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await queryBid(ccp, wallet, user, auctionID, bidID);
} else {
console.log('Usage: node bid.js org userID auctionID bidID');
console.log('Org must be Org1 or Org2');
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
}
}
main();

View file

@ -1,63 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Wallets } = require('fabric-network');
const FabricCAServices = require('fabric-ca-client');
const path = require('path');
const { buildCAClient, registerAndEnrollUser } = require('../../test-application/javascript/CAUtil.js');
const { buildCCPOrg1, buildCCPOrg2, buildWallet } = require('../../test-application/javascript/AppUtil.js');
const mspOrg1 = 'Org1MSP';
const mspOrg2 = 'Org2MSP';
async function connectToOrg1CA (UserID) {
console.log('\n--> Register and enrolling new user');
const ccpOrg1 = buildCCPOrg1();
const caOrg1Client = buildCAClient(FabricCAServices, ccpOrg1, 'ca.org1.example.com');
const walletPathOrg1 = path.join(__dirname, 'wallet/org1');
const walletOrg1 = await buildWallet(Wallets, walletPathOrg1);
await registerAndEnrollUser(caOrg1Client, walletOrg1, mspOrg1, UserID, 'org1.department1');
}
async function connectToOrg2CA (UserID) {
console.log('\n--> Register and enrolling new user');
const ccpOrg2 = buildCCPOrg2();
const caOrg2Client = buildCAClient(FabricCAServices, ccpOrg2, 'ca.org2.example.com');
const walletPathOrg2 = path.join(__dirname, 'wallet/org2');
const walletOrg2 = await buildWallet(Wallets, walletPathOrg2);
await registerAndEnrollUser(caOrg2Client, walletOrg2, mspOrg2, UserID, 'org2.department1');
}
async function main () {
if (process.argv[2] === undefined && process.argv[3] === undefined) {
console.log('Usage: node registerEnrollUser.js org userID');
process.exit(1);
}
const org = process.argv[2];
const userId = process.argv[3];
try {
if (org === 'Org1' || org === 'org1') {
await connectToOrg1CA(userId);
} else if (org === 'Org2' || org === 'org2') {
await connectToOrg2CA(userId);
} else {
console.log('Usage: node registerEnrollUser.js org userID');
console.log('Org must be Org1 or Org2');
}
} catch (error) {
console.error(`Error in enrolling admin: ${error}`);
process.exit(1);
}
}
main();

View file

@ -1,100 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const path = require('path');
const { buildCCPOrg1, buildCCPOrg2, buildWallet, prettyJSONString } = require('../../test-application/javascript/AppUtil.js');
const myChannel = 'mychannel';
const myChaincodeName = 'auction';
async function addBid (ccp, wallet, user, auctionID, bidID) {
try {
const gateway = new Gateway();
// connect using Discovery enabled
await gateway.connect(ccp,
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
const network = await gateway.getNetwork(myChannel);
const contract = network.getContract(myChaincodeName);
console.log('\n--> Evaluate Transaction: read your bid');
const bidString = await contract.evaluateTransaction('QueryBid', auctionID, bidID);
const bidJSON = JSON.parse(bidString);
// console.log('\n--> Evaluate Transaction: query the auction you want to join');
const auctionString = await contract.evaluateTransaction('QueryAuction', auctionID);
// console.log('*** Result: Bid: ' + prettyJSONString(auctionString.toString()));
const auctionJSON = JSON.parse(auctionString);
const bidData = { objectType: 'bid', quantity: parseInt(bidJSON.quantity), price: parseInt(bidJSON.price), org: bidJSON.org, buyer: bidJSON.buyer };
console.log('*** Result: Bid: ' + JSON.stringify(bidData, null, 2));
const statefulTxn = contract.createTransaction('RevealBid');
const tmapData = Buffer.from(JSON.stringify(bidData));
statefulTxn.setTransient({
bid: tmapData
});
if (auctionJSON.organizations.length === 2) {
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0], auctionJSON.organizations[1]);
} else {
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0]);
}
await statefulTxn.submit(auctionID, bidID);
console.log('\n--> Evaluate Transaction: query the auction to see that our bid was added');
const result = await contract.evaluateTransaction('QueryAuction', auctionID);
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
gateway.disconnect();
} catch (error) {
console.error(`******** FAILED to submit bid: ${error}`);
process.exit(1);
}
}
async function main () {
try {
if (process.argv[2] === undefined || process.argv[3] === undefined ||
process.argv[4] === undefined || process.argv[5] === undefined) {
console.log('Usage: node revealBid.js org userID auctionID bidID');
process.exit(1);
}
const org = process.argv[2];
const user = process.argv[3];
const auctionID = process.argv[4];
const bidID = process.argv[5];
if (org === 'Org1' || org === 'org1') {
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await addBid(ccp, wallet, user, auctionID, bidID);
} else if (org === 'Org2' || org === 'org2') {
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await addBid(ccp, wallet, user, auctionID, bidID);
} else {
console.log('Usage: node revealBid.js org userID auctionID bidID');
console.log('Org must be Org1 or Org2');
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
if (error.stack) {
console.error(error.stack);
}
process.exit(1);
}
}
main();

View file

@ -1,89 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const path = require('path');
const { buildCCPOrg1, buildCCPOrg2, buildWallet, prettyJSONString } = require('../../test-application/javascript/AppUtil.js');
const myChannel = 'mychannel';
const myChaincodeName = 'auction';
async function submitBid (ccp, wallet, user, auctionID, bidID) {
try {
const gateway = new Gateway();
// connect using Discovery enabled
await gateway.connect(ccp,
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
const network = await gateway.getNetwork(myChannel);
const contract = network.getContract(myChaincodeName);
console.log('\n--> Evaluate Transaction: query the auction you want to join');
const auctionString = await contract.evaluateTransaction('QueryAuction', auctionID);
const auctionJSON = JSON.parse(auctionString);
const statefulTxn = contract.createTransaction('SubmitBid');
if (auctionJSON.organizations.length === 2) {
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0], auctionJSON.organizations[1]);
} else {
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0]);
}
console.log('\n--> Submit Transaction: add bid to the auction');
await statefulTxn.submit(auctionID, bidID);
console.log('\n--> Evaluate Transaction: query the auction to see that our bid was added');
const result = await contract.evaluateTransaction('QueryAuction', auctionID);
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
gateway.disconnect();
} catch (error) {
console.error(`******** FAILED to submit bid: ${error}`);
process.exit(1);
}
}
async function main () {
try {
if (process.argv[2] === undefined || process.argv[3] === undefined ||
process.argv[4] === undefined || process.argv[5] === undefined) {
console.log('Usage: node submitBid.js org userID auctionID bidID');
process.exit(1);
}
const org = process.argv[2];
const user = process.argv[3];
const auctionID = process.argv[4];
const bidID = process.argv[5];
if (org === 'Org1' || org === 'org1') {
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await submitBid(ccp, wallet, user, auctionID, bidID);
} else if (org === 'Org2' || org === 'org2') {
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await submitBid(ccp, wallet, user, auctionID, bidID);
} else {
console.log('Usage: node submitBid.js org userID auctionID bidID');
console.log('Org must be Org1 or Org2');
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
if (error.stack) {
console.error(error.stack);
}
process.exit(1);
}
}
main();

View file

@ -1,34 +0,0 @@
module github.com/hyperledger/fabric-samples/auction/dutch-auction/chaincode-go-auditor
go 1.21
require (
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0-20240618210511-f7903324a8af
github.com/hyperledger/fabric-contract-api-go/v2 v2.0.0
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3
google.golang.org/protobuf v1.34.2
)
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/gobuffalo/envy v1.10.2 // indirect
github.com/gobuffalo/packd v1.0.2 // indirect
github.com/gobuffalo/packr v1.30.1 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/rogpeppe/go-internal v1.12.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
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
google.golang.org/grpc v1.64.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View file

@ -1,132 +0,0 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
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/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/envy v1.10.2 h1:EIi03p9c3yeuRCFPOKcSfajzkLb3hrRjEpHGI8I2Wo4=
github.com/gobuffalo/envy v1.10.2/go.mod h1:qGAGwdvDsaEtPhfBzb3o0SfDea8ByGn9j8bKmVft9z8=
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
github.com/gobuffalo/packd v1.0.2 h1:Yg523YqnOxGIWCp69W12yYBKsoChwI7mtu6ceM9Bwfw=
github.com/gobuffalo/packd v1.0.2/go.mod h1:sUc61tDqGMXON80zpKGp92lDb86Km28jfvX7IAyxFT8=
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
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/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0-20240618210511-f7903324a8af h1:WT4NjX7Uk03GSeH++jF3a0wp4FhybTM86zDPCETvmSk=
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0-20240618210511-f7903324a8af/go.mod h1:f/ER25FaBepxJugwpLhbD2hLAoZaZEVqkBjOcHjw72Y=
github.com/hyperledger/fabric-contract-api-go/v2 v2.0.0 h1:IDiCGVOBlRd6zpL0Y+f6V7IpBqa4/Z5JAK9SF7a5ea8=
github.com/hyperledger/fabric-contract-api-go/v2 v2.0.0/go.mod h1:pdqhe7ALf4lmXgQdprCyNWYdnCPxgj02Vhf8JF5w8po=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 h1:Xpd6fzG/KjAOHJsq7EQXY2l+qi/y8muxBaY7R6QWABk=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3/go.mod h1:2pq0ui6ZWA0cC8J+eCErgnMDCS1kPOEYVY+06ZAK0qE=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
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/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
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/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
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=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -1,440 +0,0 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
package auction
import (
"bytes"
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
"sort"
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
)
type SmartContract struct {
contractapi.Contract
}
// Auction data
type Auction struct {
Type string `json:"objectType"`
ItemSold string `json:"item"`
Seller string `json:"seller"`
Quantity int `json:"quantity"`
Orgs []string `json:"organizations"`
PrivateBids map[string]BidHash `json:"privateBids"`
RevealedBids map[string]FullBid `json:"revealedBids"`
Winners []Winners `json:"winners"`
Price int `json:"price"`
Status string `json:"status"`
Auditor bool `json:"auditor"`
}
// FullBid is the structure of a revealed bid
type FullBid struct {
Type string `json:"objectType"`
Quantity int `json:"quantity"`
Price int `json:"price"`
Org string `json:"org"`
Buyer string `json:"buyer"`
}
// BidHash is the structure of a private bid
type BidHash struct {
Org string `json:"org"`
Hash string `json:"hash"`
}
// Winners stores the winners of the auction
type Winners struct {
Buyer string `json:"buyer"`
Quantity int `json:"quantity"`
}
const bidKeyType = "bid"
// SubmitBid is used by the bidder to add the hash of that bid stored in private data to the
// auction. Note that this function alters the auction in private state, and needs
// to meet the auction endorsement policy. Transaction ID is used identify the bid
func (s *SmartContract) SubmitBid(ctx contractapi.TransactionContextInterface, auctionID string, txID string) error {
// get the MSP ID of the bidder's org
clientOrgID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return fmt.Errorf("failed to get client MSP ID: %v", err)
}
// get the auction from public state
auction, err := s.QueryAuction(ctx, auctionID)
if err != nil {
return fmt.Errorf("failed to get auction from public state %v", err)
}
// the auction needs to be open for users to add their bid
status := auction.Status
if status != "open" {
return fmt.Errorf("cannot join closed or ended auction")
}
// get the inplicit collection name of bidder's org
collection, err := getCollectionName(ctx)
if err != nil {
return fmt.Errorf("failed to get implicit collection name: %v", err)
}
// use the transaction ID passed as a parameter to create composite bid key
bidKey, err := ctx.GetStub().CreateCompositeKey(bidKeyType, []string{auctionID, txID})
if err != nil {
return fmt.Errorf("failed to create composite key: %v", err)
}
// get the hash of the bid if found in private collection
bidHash, err := ctx.GetStub().GetPrivateDataHash(collection, bidKey)
if err != nil {
return fmt.Errorf("failed to read bid bash from collection: %v", err)
}
if bidHash == nil {
return fmt.Errorf("bid hash does not exist: %s", bidKey)
}
// store the hash along with the bidder's organization
newHash := BidHash{
Org: clientOrgID,
Hash: fmt.Sprintf("%x", bidHash),
}
auction.PrivateBids[bidKey] = newHash
// Add the bidding organization to the list of participating organization's if it is not already
orgs := auction.Orgs
if !(contains(orgs, clientOrgID)) {
newOrgs := append(orgs, clientOrgID)
auction.Orgs = newOrgs
err = setAssetStateBasedEndorsement(ctx, auctionID, newOrgs, auction.Auditor)
if err != nil {
return fmt.Errorf("failed setting state based endorsement for new organization: %v", err)
}
}
newAuctionJSON, _ := json.Marshal(auction)
err = ctx.GetStub().PutState(auctionID, newAuctionJSON)
if err != nil {
return fmt.Errorf("failed to update auction: %v", err)
}
return nil
}
// RevealBid is used by a bidder to reveal their bid after the auction is closed
func (s *SmartContract) RevealBid(ctx contractapi.TransactionContextInterface, auctionID string, txID string) error {
// get the MSP ID of the bidder's org
clientOrgID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return fmt.Errorf("failed to get client MSP ID: %v", err)
}
// get bid from transient map
transientMap, err := ctx.GetStub().GetTransient()
if err != nil {
return fmt.Errorf("error getting transient: %v", err)
}
transientBidJSON, ok := transientMap["bid"]
if !ok {
return fmt.Errorf("bid key not found in the transient map")
}
// get implicit collection name of organization ID
collection, err := getCollectionName(ctx)
if err != nil {
return fmt.Errorf("failed to get implicit collection name: %v", err)
}
// use transaction ID to create composit bid key
bidKey, err := ctx.GetStub().CreateCompositeKey(bidKeyType, []string{auctionID, txID})
if err != nil {
return fmt.Errorf("failed to create composite key: %v", err)
}
// get bid hash of bid if private bid on the public ledger
bidHash, err := ctx.GetStub().GetPrivateDataHash(collection, bidKey)
if err != nil {
return fmt.Errorf("failed to read bid bash from collection: %v", err)
}
if bidHash == nil {
return fmt.Errorf("bid hash does not exist: %s", bidKey)
}
// get auction from public state
auction, err := s.QueryAuction(ctx, auctionID)
if err != nil {
return fmt.Errorf("failed to get auction from public state %v", err)
}
// check that the bidders org is a participant in the auction
orgs := auction.Orgs
if !(contains(orgs, clientOrgID)) {
return fmt.Errorf("particiant %s is not a member of the auction", clientOrgID)
}
// Complete a series of three checks before we add the bid to the auction
// check 1: check that the auction is closed. We cannot reveal an
// bid to an open auction
status := auction.Status
if status != "closed" {
return errors.New("cannot reveal bid for open or ended auction")
}
// check 2: check that hash of revealed bid matches hash of private bid
// on the public ledger. This checks that the bidder is telling the truth
// about the value of their bid
hash := sha256.New()
hash.Write(transientBidJSON)
calculatedBidJSONHash := hash.Sum(nil)
// verify that the hash of the passed immutable properties matches the on-chain hash
if !bytes.Equal(calculatedBidJSONHash, bidHash) {
return fmt.Errorf("hash %x for bid JSON %s does not match hash in auction: %x",
calculatedBidJSONHash,
transientBidJSON,
bidHash,
)
}
// check 3; check hash of relealed bid matches hash of private bid that was
// added earlier. This ensures that the bid has not changed since it
// was added to the auction
privateBidHashString := auction.PrivateBids[bidKey].Hash
onChainBidHashString := fmt.Sprintf("%x", bidHash)
if privateBidHashString != onChainBidHashString {
return fmt.Errorf("hash %s for bid JSON %s does not match hash in auction: %s, bidder must have changed bid",
privateBidHashString,
transientBidJSON,
onChainBidHashString,
)
}
// we can add the bid to the auction if all checks have passed
type transientBidInput struct {
Quantity int `json:"quantity"`
Price int `json:"price"`
Org string `json:"org"`
Buyer string `json:"buyer"`
}
// unmarshal bid imput
var bidInput transientBidInput
err = json.Unmarshal(transientBidJSON, &bidInput)
if err != nil {
return fmt.Errorf("failed to unmarshal JSON: %v", err)
}
// get ID of submitting client
clientID, err := s.GetSubmittingClientIdentity(ctx)
if err != nil {
return fmt.Errorf("failed to get client identity %v", err)
}
// marshal transient parameters and ID and MSPID into bid object
newBid := FullBid{
Type: bidKeyType,
Quantity: bidInput.Quantity,
Price: bidInput.Price,
Org: bidInput.Org,
Buyer: bidInput.Buyer,
}
// check 4: make sure that the transaction is being submitted is the bidder
if bidInput.Buyer != clientID {
return fmt.Errorf("permission denied, client id %v is not the owner of the bid", clientID)
}
auction.RevealedBids[bidKey] = newBid
auctionJSON, _ := json.Marshal(auction)
// put auction with bid added back into state
err = ctx.GetStub().PutState(auctionID, auctionJSON)
if err != nil {
return fmt.Errorf("failed to update auction: %v", err)
}
return nil
}
// CloseAuction can be used by the seller to close the auction. This prevents
// bids from being added to the auction, and allows users to reveal their bid
func (s *SmartContract) CloseAuction(ctx contractapi.TransactionContextInterface, auctionID string) error {
// get the MSP ID of the bidder's org
clientOrgID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return fmt.Errorf("failed to get client MSP ID: %v", err)
}
// get auction from public state
auction, err := s.QueryAuction(ctx, auctionID)
if err != nil {
return fmt.Errorf("failed to get auction from public state %v", err)
}
// check that the bidders org is a participant in the auction
orgs := auction.Orgs
if !(contains(orgs, clientOrgID)) {
return fmt.Errorf("particiant %s is not a member of the auction", clientOrgID)
}
// the auction can only be closed by the seller
// get ID of submitting client
clientID, err := s.GetSubmittingClientIdentity(ctx)
if err != nil {
return fmt.Errorf("failed to get client identity %v", err)
}
seller := auction.Seller
if seller != clientID {
return fmt.Errorf("auction can only be closed by seller: %v", err)
}
status := auction.Status
if status != "open" {
return errors.New("cannot close auction that is not open")
}
auction.Status = string("closed")
closedAuctionJSON, _ := json.Marshal(auction)
err = ctx.GetStub().PutState(auctionID, closedAuctionJSON)
if err != nil {
return fmt.Errorf("failed to close auction: %v", err)
}
return nil
}
// EndAuction both changes the auction status to closed and calculates the winners
// of the auction
func (s *SmartContract) EndAuction(ctx contractapi.TransactionContextInterface, auctionID string) error {
// get the MSP ID of the bidder's org
clientOrgID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return fmt.Errorf("failed to get client MSP ID: %v", err)
}
// get auction from public state
auction, err := s.QueryAuction(ctx, auctionID)
if err != nil {
return fmt.Errorf("failed to get auction from public state %v", err)
}
// check that the bidders org is a participant in the auction
orgs := auction.Orgs
if !(contains(orgs, clientOrgID)) {
return fmt.Errorf("particiant %s is not a member of the auction", clientOrgID)
}
// Check that the auction is being ended by the seller
// get ID of submitting client
clientID, err := s.GetSubmittingClientIdentity(ctx)
if err != nil {
return fmt.Errorf("failed to get client identity %v", err)
}
seller := auction.Seller
if seller != clientID {
return fmt.Errorf("auction can only be ended by seller: %v", err)
}
status := auction.Status
if status != "closed" {
return errors.New("can only end a closed auction")
}
// get the list of revealed bids
revealedBidMap := auction.RevealedBids
if len(auction.RevealedBids) == 0 {
return fmt.Errorf("no bids have been revealed, cannot end auction: %v", err)
}
// sort the map of revealed bids to make it easier to calculate winners
// if bids are tied, fill smaller bids first
var bidders []FullBid
for _, bid := range revealedBidMap {
bidders = append(bidders, bid)
}
sort.Slice(bidders, func(p, q int) bool {
if bidders[p].Price > bidders[q].Price {
return true
}
if bidders[p].Price < bidders[q].Price {
return false
}
return bidders[p].Quantity < bidders[q].Quantity
})
i := 0
remainingQuantity := auction.Quantity
// calculate the winners
for remainingQuantity > 0 {
// create the next winning bid
winner := Winners{
Buyer: bidders[i].Buyer,
Quantity: bidders[i].Quantity,
}
// add them to the list of winners and change the winning price
auction.Winners = append(auction.Winners, winner)
auction.Price = bidders[i].Price
// Calculate the quantity that goes to the winner
// if there is sufficient quantity to give them the full bid
if remainingQuantity > bidders[i].Quantity {
remainingQuantity = remainingQuantity - bidders[i].Quantity
// if there is not, give the remainder
} else {
auction.Winners[i].Quantity = remainingQuantity
remainingQuantity = 0
}
i++
if i == len(bidders) {
remainingQuantity = 0
}
}
// check if there is a winning bid that has yet to be revealed
err = checkForHigherBid(ctx, auction.Price, auction.RevealedBids, auction.PrivateBids)
if err != nil {
return fmt.Errorf("cannot end auction: %v", err)
}
auction.Status = "ended"
endedAuctionJSON, _ := json.Marshal(auction)
err = ctx.GetStub().PutState(auctionID, endedAuctionJSON)
if err != nil {
return fmt.Errorf("failed to end auction: %v", err)
}
return nil
}

View file

@ -1,91 +0,0 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
package auction
import (
"encoding/json"
"fmt"
"github.com/hyperledger/fabric-chaincode-go/v2/shim"
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
)
// QueryAuction allows all members of the channel to read a public auction
func (s *SmartContract) QueryAuction(ctx contractapi.TransactionContextInterface, auctionID string) (*Auction, error) {
auctionJSON, err := ctx.GetStub().GetState(auctionID)
if err != nil {
return nil, fmt.Errorf("failed to get auction object %v: %v", auctionID, err)
}
if auctionJSON == nil {
return nil, fmt.Errorf("auction does not exist")
}
var auction *Auction
err = json.Unmarshal(auctionJSON, &auction)
if err != nil {
return nil, err
}
return auction, nil
}
// checkForHigherBid is an internal function that is used to determine if a winning bid has yet to be revealed
func checkForHigherBid(ctx contractapi.TransactionContextInterface, auctionPrice int, revealedBidders map[string]FullBid, bidders map[string]BidHash) error {
// Get MSP ID of peer org
peerMSPID, err := shim.GetMSPID()
if err != nil {
return fmt.Errorf("failed getting the peer's MSPID: %v", err)
}
var error error
error = nil
for bidKey, privateBid := range bidders {
if _, bidInAuction := revealedBidders[bidKey]; bidInAuction {
// bid is already revealed, no action to take
} else {
collection := "_implicit_org_" + privateBid.Org
if privateBid.Org == peerMSPID {
bidJSON, err := ctx.GetStub().GetPrivateData(collection, bidKey)
if err != nil {
return fmt.Errorf("failed to get bid %v: %v", bidKey, err)
}
if bidJSON == nil {
return fmt.Errorf("bid %v does not exist", bidKey)
}
var bid *FullBid
err = json.Unmarshal(bidJSON, &bid)
if err != nil {
return err
}
if bid.Price > auctionPrice {
error = fmt.Errorf("Cannot close auction, bidder has a higher price: %v", err)
}
} else {
hash, err := ctx.GetStub().GetPrivateDataHash(collection, bidKey)
if err != nil {
return fmt.Errorf("failed to read bid hash from collection: %v", err)
}
if hash == nil {
return fmt.Errorf("bid hash does not exist: %s", bidKey)
}
}
}
}
return error
}

View file

@ -1,203 +0,0 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
package auction
import (
"encoding/base64"
"fmt"
"github.com/hyperledger/fabric-chaincode-go/v2/shim"
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
"github.com/hyperledger/fabric-protos-go-apiv2/common"
"github.com/hyperledger/fabric-protos-go-apiv2/msp"
"google.golang.org/protobuf/proto"
)
func (s *SmartContract) GetSubmittingClientIdentity(ctx contractapi.TransactionContextInterface) (string, error) {
b64ID, err := ctx.GetClientIdentity().GetID()
if err != nil {
return "", fmt.Errorf("Failed to read clientID: %v", err)
}
decodeID, err := base64.StdEncoding.DecodeString(b64ID)
if err != nil {
return "", fmt.Errorf("failed to base64 decode clientID: %v", err)
}
return string(decodeID), nil
}
// getCollectionName is an internal helper function to get collection of submitting client identity.
func getCollectionName(ctx contractapi.TransactionContextInterface) (string, error) {
// Get the MSP ID of submitting client identity
clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return "", fmt.Errorf("failed to get verified MSPID: %v", err)
}
// Create the collection name
orgCollection := "_implicit_org_" + clientMSPID
return orgCollection, nil
}
// verifyClientOrgMatchesPeerOrg is an internal function used to verify that client org id matches peer org id.
func verifyClientOrgMatchesPeerOrg(ctx contractapi.TransactionContextInterface) error {
clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return fmt.Errorf("failed getting the client's MSPID: %v", err)
}
peerMSPID, err := shim.GetMSPID()
if err != nil {
return fmt.Errorf("failed getting the peer's MSPID: %v", err)
}
if clientMSPID != peerMSPID {
return fmt.Errorf("client from org %v is not authorized to read or write private data from an org %v peer", clientMSPID, peerMSPID)
}
return nil
}
func contains(sli []string, str string) bool {
for _, a := range sli {
if a == str {
return true
}
}
return false
}
func setAssetStateBasedEndorsement(ctx contractapi.TransactionContextInterface, assetId string, mspids []string, auditor bool) error {
principals := make([]*msp.MSPPrincipal, len(mspids))
participantSigsPolicy := make([]*common.SignaturePolicy, len(mspids))
for i, id := range mspids {
principal, err := proto.Marshal(
&msp.MSPRole{
Role: msp.MSPRole_PEER,
MspIdentifier: id,
},
)
if err != nil {
return err
}
principals[i] = &msp.MSPPrincipal{
PrincipalClassification: msp.MSPPrincipal_ROLE,
Principal: principal,
}
participantSigsPolicy[i] = &common.SignaturePolicy{
Type: &common.SignaturePolicy_SignedBy{
SignedBy: int32(i),
},
}
}
if auditor == false {
// create the defalt policy for an auction without an auditor
policy := &common.SignaturePolicyEnvelope{
Version: 0,
Rule: &common.SignaturePolicy{
Type: &common.SignaturePolicy_NOutOf_{
NOutOf: &common.SignaturePolicy_NOutOf{
N: int32(len(mspids)),
Rules: participantSigsPolicy,
},
},
},
Identities: principals,
}
spBytes, err := proto.Marshal(policy)
if err != nil {
return err
}
err = ctx.GetStub().SetStateValidationParameter(assetId, spBytes)
if err != nil {
return fmt.Errorf("failed to set validation parameter on auction: %v", err)
}
} else {
// create the defalt policy for an auction with an auditor
// create the auditor identity and signature policy
auditorMSP, err := proto.Marshal(
&msp.MSPRole{
Role: msp.MSPRole_PEER,
MspIdentifier: "Org3MSP",
},
)
if err != nil {
return err
}
principals = append(principals, &msp.MSPPrincipal{
PrincipalClassification: msp.MSPPrincipal_ROLE,
Principal: auditorMSP,
},
)
// Create the policies in case the auditor is needed. In this case, an
// auditor and 1 participant can update the auction.
auditorPolicies := make([]*common.SignaturePolicy, 2)
auditorPolicies[0] = &common.SignaturePolicy{
Type: &common.SignaturePolicy_SignedBy{
SignedBy: int32(len(principals) - 1),
},
}
auditorPolicies[1] = &common.SignaturePolicy{
Type: &common.SignaturePolicy_NOutOf_{
NOutOf: &common.SignaturePolicy_NOutOf{
N: 1,
Rules: participantSigsPolicy,
},
},
}
// The auditor policy below is equivilent to AND(auditor, OR(participants))
policies := make([]*common.SignaturePolicy, 2)
policies[0] = &common.SignaturePolicy{
Type: &common.SignaturePolicy_NOutOf_{
NOutOf: &common.SignaturePolicy_NOutOf{
N: 2,
Rules: auditorPolicies,
},
},
}
// Participants can also update the auction without an auditor
policies[1] = &common.SignaturePolicy{
Type: &common.SignaturePolicy_NOutOf_{
NOutOf: &common.SignaturePolicy_NOutOf{
N: int32(len(mspids)),
Rules: participantSigsPolicy,
},
},
}
// Either the auditor policy or the participant policy can update
// the auction
policy := &common.SignaturePolicyEnvelope{
Version: 0,
Rule: &common.SignaturePolicy{
Type: &common.SignaturePolicy_NOutOf_{
NOutOf: &common.SignaturePolicy_NOutOf{
N: 1,
Rules: policies,
},
},
},
Identities: principals,
}
spBytes, err := proto.Marshal(policy)
if err != nil {
return err
}
err = ctx.GetStub().SetStateValidationParameter(assetId, spBytes)
if err != nil {
return fmt.Errorf("failed to set validation parameter on auction: %v", err)
}
}
return nil
}

View file

@ -1,23 +0,0 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"log"
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
auction "github.com/hyperledger/fabric-samples/auction/dutch-auction/chaincode-go-auditor/smart-contract"
)
func main() {
auctionSmartContract, err := contractapi.NewChaincode(&auction.SmartContract{})
if err != nil {
log.Panicf("Error creating auction chaincode: %v", err)
}
if err := auctionSmartContract.Start(); err != nil {
log.Panicf("Error starting auction chaincode: %v", err)
}
}

View file

@ -1,34 +0,0 @@
module github.com/hyperledger/fabric-samples/auction/dutch-auction/chaincode-go
go 1.21
require (
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0-20240618210511-f7903324a8af
github.com/hyperledger/fabric-contract-api-go/v2 v2.0.0
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3
google.golang.org/protobuf v1.34.2
)
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/gobuffalo/envy v1.10.2 // indirect
github.com/gobuffalo/packd v1.0.2 // indirect
github.com/gobuffalo/packr v1.30.1 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/rogpeppe/go-internal v1.12.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
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
google.golang.org/grpc v1.64.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View file

@ -1,132 +0,0 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
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/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/envy v1.10.2 h1:EIi03p9c3yeuRCFPOKcSfajzkLb3hrRjEpHGI8I2Wo4=
github.com/gobuffalo/envy v1.10.2/go.mod h1:qGAGwdvDsaEtPhfBzb3o0SfDea8ByGn9j8bKmVft9z8=
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
github.com/gobuffalo/packd v1.0.2 h1:Yg523YqnOxGIWCp69W12yYBKsoChwI7mtu6ceM9Bwfw=
github.com/gobuffalo/packd v1.0.2/go.mod h1:sUc61tDqGMXON80zpKGp92lDb86Km28jfvX7IAyxFT8=
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
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/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0-20240618210511-f7903324a8af h1:WT4NjX7Uk03GSeH++jF3a0wp4FhybTM86zDPCETvmSk=
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0-20240618210511-f7903324a8af/go.mod h1:f/ER25FaBepxJugwpLhbD2hLAoZaZEVqkBjOcHjw72Y=
github.com/hyperledger/fabric-contract-api-go/v2 v2.0.0 h1:IDiCGVOBlRd6zpL0Y+f6V7IpBqa4/Z5JAK9SF7a5ea8=
github.com/hyperledger/fabric-contract-api-go/v2 v2.0.0/go.mod h1:pdqhe7ALf4lmXgQdprCyNWYdnCPxgj02Vhf8JF5w8po=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 h1:Xpd6fzG/KjAOHJsq7EQXY2l+qi/y8muxBaY7R6QWABk=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3/go.mod h1:2pq0ui6ZWA0cC8J+eCErgnMDCS1kPOEYVY+06ZAK0qE=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
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/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
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/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
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=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -1,512 +0,0 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
package auction
import (
"bytes"
"crypto/sha256"
"encoding/json"
"fmt"
"sort"
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
)
type SmartContract struct {
contractapi.Contract
}
// Auction data
type Auction struct {
Type string `json:"objectType"`
ItemSold string `json:"item"`
Seller string `json:"seller"`
Quantity int `json:"quantity"`
Orgs []string `json:"organizations"`
PrivateBids map[string]BidHash `json:"privateBids"`
RevealedBids map[string]FullBid `json:"revealedBids"`
Winners []Winners `json:"winners"`
Price int `json:"price"`
Status string `json:"status"`
Auditor bool `json:"auditor"`
}
// FullBid is the structure of a revealed bid
type FullBid struct {
Type string `json:"objectType"`
Quantity int `json:"quantity"`
Price int `json:"price"`
Org string `json:"org"`
Buyer string `json:"buyer"`
}
// BidHash is the structure of a private bid
type BidHash struct {
Org string `json:"org"`
Hash string `json:"hash"`
}
// Winners stores the winners of the auction
type Winners struct {
Buyer string `json:"buyer"`
Quantity int `json:"quantity"`
}
const bidKeyType = "bid"
// CreateAuction creates on auction on the public channel. The identity that
// submits the transacion becomes the seller of the auction
func (s *SmartContract) CreateAuction(ctx contractapi.TransactionContextInterface, auctionID string, itemsold string, quantity int, withAuditor string) error {
// get ID of submitting client
clientID, err := s.GetSubmittingClientIdentity(ctx)
if err != nil {
return fmt.Errorf("failed to get client identity %v", err)
}
// get org of submitting client
clientOrgID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return fmt.Errorf("failed to get client identity %v", err)
}
auditor := false
if withAuditor == "withAuditor" {
auditor = true
}
// Create auction
bidders := make(map[string]BidHash)
revealedBids := make(map[string]FullBid)
auction := Auction{
Type: "auction",
ItemSold: itemsold,
Quantity: quantity,
Price: 0,
Seller: clientID,
Orgs: []string{clientOrgID},
PrivateBids: bidders,
RevealedBids: revealedBids,
Winners: []Winners{},
Status: "open",
Auditor: auditor,
}
auctionJSON, err := json.Marshal(auction)
if err != nil {
return err
}
// put auction into state
err = ctx.GetStub().PutState(auctionID, auctionJSON)
if err != nil {
return fmt.Errorf("failed to put auction in public data: %v", err)
}
// set the seller of the auction as an endorser
err = setAssetStateBasedEndorsement(ctx, auctionID, []string{clientOrgID}, auditor)
if err != nil {
return fmt.Errorf("failed setting state based endorsement for new organization: %v", err)
}
return nil
}
// Bid is used to add a users bid to the auction. The bid is stored in the private
// data collection on the peer of the bidder's organization. The function returns
// the transaction ID so that users can identify and query their bid
func (s *SmartContract) Bid(ctx contractapi.TransactionContextInterface, auctionID string) (string, error) {
// get bid from transient map
transientMap, err := ctx.GetStub().GetTransient()
if err != nil {
return "", fmt.Errorf("error getting transient: %v", err)
}
bidJSON, ok := transientMap["bid"]
if !ok {
return "", fmt.Errorf("bid key not found in the transient map")
}
// get the implicit collection name using the bidder's organization ID
collection, err := getCollectionName(ctx)
if err != nil {
return "", fmt.Errorf("failed to get implicit collection name: %v", err)
}
// the bidder has to target their peer to store the bid
err = verifyClientOrgMatchesPeerOrg(ctx)
if err != nil {
return "", fmt.Errorf("cannot store bid on this peer, not a member of this org: %v", err)
}
// the transaction ID is used as a unique index for the bid
txID := ctx.GetStub().GetTxID()
// create a composite key using the transaction ID
bidKey, err := ctx.GetStub().CreateCompositeKey(bidKeyType, []string{auctionID, txID})
if err != nil {
return "", fmt.Errorf("failed to create composite key: %v", err)
}
// put the bid into the organization's implicit data collection
err = ctx.GetStub().PutPrivateData(collection, bidKey, bidJSON)
if err != nil {
return "", fmt.Errorf("failed to input price into collection: %v", err)
}
// return the trannsaction ID so that the uset can identify their bid
return txID, nil
}
// SubmitBid is used by the bidder to add the hash of that bid stored in private data to the
// auction. Note that this function alters the auction in private state, and needs
// to meet the auction endorsement policy. Transaction ID is used identify the bid
func (s *SmartContract) SubmitBid(ctx contractapi.TransactionContextInterface, auctionID string, txID string) error {
// get the MSP ID of the bidder's org
clientOrgID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return fmt.Errorf("failed to get client MSP ID: %v", err)
}
// get the auction from public state
auction, err := s.QueryAuction(ctx, auctionID)
if err != nil {
return fmt.Errorf("failed to get auction from public state %v", err)
}
// the auction needs to be open for users to add their bid
status := auction.Status
if status != "open" {
return fmt.Errorf("cannot join closed or ended auction")
}
// get the inplicit collection name of bidder's org
collection, err := getCollectionName(ctx)
if err != nil {
return fmt.Errorf("failed to get implicit collection name: %v", err)
}
// use the transaction ID passed as a parameter to create composite bid key
bidKey, err := ctx.GetStub().CreateCompositeKey(bidKeyType, []string{auctionID, txID})
if err != nil {
return fmt.Errorf("failed to create composite key: %v", err)
}
// get the hash of the bid if found in private collection
bidHash, err := ctx.GetStub().GetPrivateDataHash(collection, bidKey)
if err != nil {
return fmt.Errorf("failed to read bid bash from collection: %v", err)
}
if bidHash == nil {
return fmt.Errorf("bid hash does not exist: %s", bidKey)
}
// store the hash along with the bidder's organization
newHash := BidHash{
Org: clientOrgID,
Hash: fmt.Sprintf("%x", bidHash),
}
auction.PrivateBids[bidKey] = newHash
// Add the bidding organization to the list of participating organization's if it is not already
orgs := auction.Orgs
if !(contains(orgs, clientOrgID)) {
newOrgs := append(orgs, clientOrgID)
auction.Orgs = newOrgs
err = setAssetStateBasedEndorsement(ctx, auctionID, newOrgs, auction.Auditor)
if err != nil {
return fmt.Errorf("failed setting state based endorsement for new organization: %v", err)
}
}
newAuctionJSON, _ := json.Marshal(auction)
err = ctx.GetStub().PutState(auctionID, newAuctionJSON)
if err != nil {
return fmt.Errorf("failed to update auction: %v", err)
}
return nil
}
// RevealBid is used by a bidder to reveal their bid after the auction is closed
func (s *SmartContract) RevealBid(ctx contractapi.TransactionContextInterface, auctionID string, txID string) error {
// get bid from transient map
transientMap, err := ctx.GetStub().GetTransient()
if err != nil {
return fmt.Errorf("error getting transient: %v", err)
}
transientBidJSON, ok := transientMap["bid"]
if !ok {
return fmt.Errorf("bid key not found in the transient map")
}
// get implicit collection name of organization ID
collection, err := getCollectionName(ctx)
if err != nil {
return fmt.Errorf("failed to get implicit collection name: %v", err)
}
// use transaction ID to create composit bid key
bidKey, err := ctx.GetStub().CreateCompositeKey(bidKeyType, []string{auctionID, txID})
if err != nil {
return fmt.Errorf("failed to create composite key: %v", err)
}
// get bid hash of bid if private bid on the public ledger
bidHash, err := ctx.GetStub().GetPrivateDataHash(collection, bidKey)
if err != nil {
return fmt.Errorf("failed to read bid bash from collection: %v", err)
}
if bidHash == nil {
return fmt.Errorf("bid hash does not exist: %s", bidKey)
}
// get auction from public state
auction, err := s.QueryAuction(ctx, auctionID)
if err != nil {
return fmt.Errorf("failed to get auction from public state %v", err)
}
// Complete a series of three checks before we add the bid to the auction
// check 1: check that the auction is closed. We cannot reveal an
// bid to an open auction
status := auction.Status
if status != "closed" {
return fmt.Errorf("cannot reveal bid for open or ended auction")
}
// check 2: check that hash of revealed bid matches hash of private bid
// on the public ledger. This checks that the bidder is telling the truth
// about the value of their bid
hash := sha256.New()
hash.Write(transientBidJSON)
calculatedBidJSONHash := hash.Sum(nil)
// verify that the hash of the passed immutable properties matches the on-chain hash
if !bytes.Equal(calculatedBidJSONHash, bidHash) {
return fmt.Errorf("hash %x for bid JSON %s does not match hash in auction: %x",
calculatedBidJSONHash,
transientBidJSON,
bidHash,
)
}
// check 3; check hash of relealed bid matches hash of private bid that was
// added earlier. This ensures that the bid has not changed since it
// was added to the auction
privateBidHashString := auction.PrivateBids[bidKey].Hash
onChainBidHashString := fmt.Sprintf("%x", bidHash)
if privateBidHashString != onChainBidHashString {
return fmt.Errorf("hash %s for bid JSON %s does not match hash in auction: %s, bidder must have changed bid",
privateBidHashString,
transientBidJSON,
onChainBidHashString,
)
}
// we can add the bid to the auction if all checks have passed
type transientBidInput struct {
Quantity int `json:"quantity"`
Price int `json:"price"`
Org string `json:"org"`
Buyer string `json:"buyer"`
}
// unmarshal bid imput
var bidInput transientBidInput
err = json.Unmarshal(transientBidJSON, &bidInput)
if err != nil {
return fmt.Errorf("failed to unmarshal JSON: %v", err)
}
// get ID of submitting client
clientID, err := s.GetSubmittingClientIdentity(ctx)
if err != nil {
return fmt.Errorf("failed to get client identity %v", err)
}
// marshal transient parameters and ID and MSPID into bid object
newBid := FullBid{
Type: bidKeyType,
Quantity: bidInput.Quantity,
Price: bidInput.Price,
Org: bidInput.Org,
Buyer: bidInput.Buyer,
}
// check 4: make sure that the transaction is being submitted is the bidder
if bidInput.Buyer != clientID {
return fmt.Errorf("permission denied, client id %v is not the owner of the bid", clientID)
}
revealedBids := auction.RevealedBids
revealedBids[bidKey] = newBid
auction.RevealedBids = revealedBids
auctionJSON, _ := json.Marshal(auction)
// put auction with bid added back into state
err = ctx.GetStub().PutState(auctionID, auctionJSON)
if err != nil {
return fmt.Errorf("failed to update auction: %v", err)
}
return nil
}
// CloseAuction can be used by the seller to close the auction. This prevents
// bids from being added to the auction, and allows users to reveal their bid
func (s *SmartContract) CloseAuction(ctx contractapi.TransactionContextInterface, auctionID string) error {
// get auction from public state
auction, err := s.QueryAuction(ctx, auctionID)
if err != nil {
return fmt.Errorf("failed to get auction from public state %v", err)
}
// the auction can only be closed by the seller
// get ID of submitting client
clientID, err := s.GetSubmittingClientIdentity(ctx)
if err != nil {
return fmt.Errorf("failed to get client identity %v", err)
}
seller := auction.Seller
if seller != clientID {
return fmt.Errorf("auction can only be closed by seller: %v", err)
}
status := auction.Status
if status != "open" {
return fmt.Errorf("cannot close auction that is not open")
}
auction.Status = string("closed")
closedAuctionJSON, _ := json.Marshal(auction)
err = ctx.GetStub().PutState(auctionID, closedAuctionJSON)
if err != nil {
return fmt.Errorf("failed to close auction: %v", err)
}
return nil
}
// EndAuction both changes the auction status to closed and calculates the winners
// of the auction
func (s *SmartContract) EndAuction(ctx contractapi.TransactionContextInterface, auctionID string) error {
// get auction from public state
auction, err := s.QueryAuction(ctx, auctionID)
if err != nil {
return fmt.Errorf("failed to get auction from public state %v", err)
}
// Check that the auction is being ended by the seller
// get ID of submitting client
clientID, err := s.GetSubmittingClientIdentity(ctx)
if err != nil {
return fmt.Errorf("failed to get client identity %v", err)
}
seller := auction.Seller
if seller != clientID {
return fmt.Errorf("auction can only be ended by seller: %v", err)
}
status := auction.Status
if status != "closed" {
return fmt.Errorf("can only end a closed auction")
}
// get the list of revealed bids
revealedBidMap := auction.RevealedBids
if len(auction.RevealedBids) == 0 {
return fmt.Errorf("no bids have been revealed, cannot end auction: %v", err)
}
// sort the map of revealed bids to make it easier to calculate winners
// if bids are tied, fill smaller bids first
var bidders []FullBid
for _, bid := range revealedBidMap {
bidders = append(bidders, bid)
}
sort.Slice(bidders, func(p, q int) bool {
if bidders[p].Price > bidders[q].Price {
return true
}
if bidders[p].Price < bidders[q].Price {
return false
}
return bidders[p].Quantity < bidders[q].Quantity
})
i := 0
remainingQuantity := auction.Quantity
// calculate the winners
for remainingQuantity > 0 {
// create the next winning bid
winner := Winners{
Buyer: bidders[i].Buyer,
Quantity: bidders[i].Quantity,
}
// add them to the list of winners and change the winning price
auction.Winners = append(auction.Winners, winner)
auction.Price = bidders[i].Price
// Calculate the quantity that goes to the winner
// if there is sufficient quantity to give them the full bid
if remainingQuantity > bidders[i].Quantity {
remainingQuantity = remainingQuantity - bidders[i].Quantity
// if there is not, give the remainder
} else {
auction.Winners[i].Quantity = remainingQuantity
remainingQuantity = 0
}
i++
if i == len(bidders) {
remainingQuantity = 0
}
}
// check if there is a winning bid that has yet to be revealed
err = checkForHigherBid(ctx, auction.Price, auction.RevealedBids, auction.PrivateBids)
if err != nil {
return fmt.Errorf("cannot end auction: %v", err)
}
auction.Status = "ended"
endedAuctionJSON, _ := json.Marshal(auction)
err = ctx.GetStub().PutState(auctionID, endedAuctionJSON)
if err != nil {
return fmt.Errorf("failed to end auction: %v", err)
}
return nil
}

View file

@ -1,136 +0,0 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
package auction
import (
"encoding/json"
"fmt"
"github.com/hyperledger/fabric-chaincode-go/v2/shim"
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
)
// QueryAuction allows all members of the channel to read a public auction
func (s *SmartContract) QueryAuction(ctx contractapi.TransactionContextInterface, auctionID string) (*Auction, error) {
auctionJSON, err := ctx.GetStub().GetState(auctionID)
if err != nil {
return nil, fmt.Errorf("failed to get auction object %v: %v", auctionID, err)
}
if auctionJSON == nil {
return nil, fmt.Errorf("auction does not exist")
}
var auction *Auction
err = json.Unmarshal(auctionJSON, &auction)
if err != nil {
return nil, err
}
return auction, nil
}
// QueryBid allows the submitter of the bid to read their bid from public state
func (s *SmartContract) QueryBid(ctx contractapi.TransactionContextInterface, auctionID string, txID string) (*FullBid, error) {
err := verifyClientOrgMatchesPeerOrg(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get implicit collection name: %v", err)
}
clientID, err := s.GetSubmittingClientIdentity(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get client identity %v", err)
}
collection, err := getCollectionName(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get implicit collection name: %v", err)
}
bidKey, err := ctx.GetStub().CreateCompositeKey(bidKeyType, []string{auctionID, txID})
if err != nil {
return nil, fmt.Errorf("failed to create composite key: %v", err)
}
bidJSON, err := ctx.GetStub().GetPrivateData(collection, bidKey)
if err != nil {
return nil, fmt.Errorf("failed to get bid %v: %v", bidKey, err)
}
if bidJSON == nil {
return nil, fmt.Errorf("bid %v does not exist", bidKey)
}
var bid *FullBid
err = json.Unmarshal(bidJSON, &bid)
if err != nil {
return nil, err
}
// check that the client querying the bid is the bid owner
if bid.Buyer != clientID {
return nil, fmt.Errorf("permission denied, client id %v is not the owner of the bid", clientID)
}
return bid, nil
}
// checkForHigherBid is an internal function that is used to determine if a winning bid has yet to be revealed
func checkForHigherBid(ctx contractapi.TransactionContextInterface, auctionPrice int, revealedBidders map[string]FullBid, bidders map[string]BidHash) error {
// Get MSP ID of peer org
peerMSPID, err := shim.GetMSPID()
if err != nil {
return fmt.Errorf("failed getting the peer's MSPID: %v", err)
}
var error error
error = nil
for bidKey, privateBid := range bidders {
if _, bidInAuction := revealedBidders[bidKey]; bidInAuction {
// bid is already revealed, no action to take
} else {
collection := "_implicit_org_" + privateBid.Org
if privateBid.Org == peerMSPID {
bidJSON, err := ctx.GetStub().GetPrivateData(collection, bidKey)
if err != nil {
return fmt.Errorf("failed to get bid %v: %v", bidKey, err)
}
if bidJSON == nil {
return fmt.Errorf("bid %v does not exist", bidKey)
}
var bid *FullBid
err = json.Unmarshal(bidJSON, &bid)
if err != nil {
return err
}
if bid.Price > auctionPrice {
error = fmt.Errorf("cannot close auction, bidder has a higher price: %v", err)
}
} else {
hash, err := ctx.GetStub().GetPrivateDataHash(collection, bidKey)
if err != nil {
return fmt.Errorf("failed to read bid hash from collection: %v", err)
}
if hash == nil {
return fmt.Errorf("bid hash does not exist: %s", bidKey)
}
}
}
}
return error
}

View file

@ -1,205 +0,0 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
package auction
import (
"encoding/base64"
"fmt"
"github.com/hyperledger/fabric-chaincode-go/v2/shim"
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
"github.com/hyperledger/fabric-protos-go-apiv2/common"
"github.com/hyperledger/fabric-protos-go-apiv2/msp"
"google.golang.org/protobuf/proto"
)
func (s *SmartContract) GetSubmittingClientIdentity(ctx contractapi.TransactionContextInterface) (string, error) {
b64ID, err := ctx.GetClientIdentity().GetID()
if err != nil {
return "", fmt.Errorf("failed to read clientID: %v", err)
}
decodeID, err := base64.StdEncoding.DecodeString(b64ID)
if err != nil {
return "", fmt.Errorf("failed to base64 decode clientID: %v", err)
}
return string(decodeID), nil
}
// getCollectionName is an internal helper function to get collection of submitting client identity.
func getCollectionName(ctx contractapi.TransactionContextInterface) (string, error) {
// Get the MSP ID of submitting client identity
clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return "", fmt.Errorf("failed to get verified MSPID: %v", err)
}
// Create the collection name
orgCollection := "_implicit_org_" + clientMSPID
return orgCollection, nil
}
// verifyClientOrgMatchesPeerOrg is an internal function used to verify that client org id matches peer org id.
func verifyClientOrgMatchesPeerOrg(ctx contractapi.TransactionContextInterface) error {
clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return fmt.Errorf("failed getting the client's MSPID: %v", err)
}
peerMSPID, err := shim.GetMSPID()
if err != nil {
return fmt.Errorf("failed getting the peer's MSPID: %v", err)
}
if clientMSPID != peerMSPID {
return fmt.Errorf("client from org %v is not authorized to read or write private data from an org %v peer", clientMSPID, peerMSPID)
}
return nil
}
func contains(sli []string, str string) bool {
for _, a := range sli {
if a == str {
return true
}
}
return false
}
func setAssetStateBasedEndorsement(ctx contractapi.TransactionContextInterface, assetId string, mspids []string, auditor bool) error {
principals := make([]*msp.MSPPrincipal, len(mspids))
participantSigsPolicy := make([]*common.SignaturePolicy, len(mspids))
for i, id := range mspids {
principal, err := proto.Marshal(
&msp.MSPRole{
Role: msp.MSPRole_PEER,
MspIdentifier: id,
},
)
if err != nil {
return err
}
principals[i] = &msp.MSPPrincipal{
PrincipalClassification: msp.MSPPrincipal_ROLE,
Principal: principal,
}
participantSigsPolicy[i] = &common.SignaturePolicy{
Type: &common.SignaturePolicy_SignedBy{
SignedBy: int32(i),
},
}
}
if !auditor {
// create the defalt policy for an auction without an auditor
policy := &common.SignaturePolicyEnvelope{
Version: 0,
Rule: &common.SignaturePolicy{
Type: &common.SignaturePolicy_NOutOf_{
NOutOf: &common.SignaturePolicy_NOutOf{
N: int32(len(mspids)),
Rules: participantSigsPolicy,
},
},
},
Identities: principals,
}
spBytes, err := proto.Marshal(policy)
if err != nil {
return err
}
err = ctx.GetStub().SetStateValidationParameter(assetId, spBytes)
if err != nil {
return fmt.Errorf("failed to set validation parameter on auction: %v", err)
}
} else {
// create the defalt policy for an auction with an auditor
// create the auditor identity and signature policy
auditorMSP, err := proto.Marshal(
&msp.MSPRole{
Role: msp.MSPRole_PEER,
MspIdentifier: "Org3MSP",
},
)
if err != nil {
return err
}
principals = append(principals, &msp.MSPPrincipal{
PrincipalClassification: msp.MSPPrincipal_ROLE,
Principal: auditorMSP,
},
)
// Create the policies in case the auditor is needed. In this case, an
// auditor and 1 participant can update the auction.
auditorPolicies := make([]*common.SignaturePolicy, 2)
auditorPolicies[0] = &common.SignaturePolicy{
Type: &common.SignaturePolicy_SignedBy{
SignedBy: int32(len(principals) - 1),
},
}
auditorPolicies[1] = &common.SignaturePolicy{
Type: &common.SignaturePolicy_NOutOf_{
NOutOf: &common.SignaturePolicy_NOutOf{
N: 1,
Rules: participantSigsPolicy,
},
},
}
// For two organizations, the auditor policy below is equivilent to
// AND(auditor, OR(Org1, Org2))
policies := make([]*common.SignaturePolicy, 2)
policies[0] = &common.SignaturePolicy{
Type: &common.SignaturePolicy_NOutOf_{
NOutOf: &common.SignaturePolicy_NOutOf{
N: 2,
Rules: auditorPolicies,
},
},
}
// Participants can also update the auction without an auditor
policies[1] = &common.SignaturePolicy{
Type: &common.SignaturePolicy_NOutOf_{
NOutOf: &common.SignaturePolicy_NOutOf{
N: int32(len(mspids)),
Rules: participantSigsPolicy,
},
},
}
// Either the auditor policy or the participant policy can update
// the auction. For example, for two organizations, the full policy would be
// equivilent to OR(AND(Org1, Org2),AND(auditor, OR(Org1, Org2)))
policy := &common.SignaturePolicyEnvelope{
Version: 0,
Rule: &common.SignaturePolicy{
Type: &common.SignaturePolicy_NOutOf_{
NOutOf: &common.SignaturePolicy_NOutOf{
N: 1,
Rules: policies,
},
},
},
Identities: principals,
}
spBytes, err := proto.Marshal(policy)
if err != nil {
return err
}
err = ctx.GetStub().SetStateValidationParameter(assetId, spBytes)
if err != nil {
return fmt.Errorf("failed to set validation parameter on auction: %v", err)
}
}
return nil
}

View file

@ -1,23 +0,0 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"log"
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
auction "github.com/hyperledger/fabric-samples/auction/dutch-auction/chaincode-go/smart-contract"
)
func main() {
auctionSmartContract, err := contractapi.NewChaincode(&auction.SmartContract{})
if err != nil {
log.Panicf("Error creating auction chaincode: %v", err)
}
if err := auctionSmartContract.Start(); err != nil {
log.Panicf("Error starting auction chaincode: %v", err)
}
}

View file

@ -1,410 +0,0 @@
## Simple blind auction sample
The simple blind auction sample uses Hyperledger Fabric to run an auction where bids are kept private until the auction period is over. Instead of displaying the full bid on the public ledger, buyers can only see hashes of other bids while bidding is underway. This prevents buyers from changing their bids in response to bids submitted by others. After the bidding period ends, participants reveal their bid to try to win the auction. The organizations participating in the auction verify that a revealed bid matches the hash on the public ledger. Whichever has the highest bid wins.
A user that wants to sell one item can use the smart contract to create an auction. The auction is stored on the channel ledger and can be read by all channel members. The auctions created by the smart contract are run in three steps:
1. Each auction is created with the status **open**. While the auction is open, buyers can add new bids to the auction. The full bids of each buyer are stored in the implicit private data collections of their organization. After the bid is created, the bidder can submit the hash of the bid to the auction. A bid is added to the auction in two steps because the transaction that creates the bid only needs to be endorsed by a peer of the bidders organization, while a transaction that updates the auction may need to be endorsed by multiple organizations. When the bid is added to the auction, the bidder's organization is added to the list of organizations that need to endorse any updates to the auction.
2. The auction is **closed** to prevent additional bids from being added to the auction. After the auction is closed, bidders that submitted bids to the auction can reveal their full bid. Only revealed bids can win the auction.
3. The auction is **ended** to calculate the winner from the set of revealed bids. All organizations participating in the auction calculate the price that clears the auction and the winning bid. The seller can end the auction only if all bidding organizations endorse the same winner and price.
Before endorsing the transaction that ends the auction, each organization queries the implicit private data collection on their peers to check if any organization member has a winning bid that has not yet been revealed. If a winning bid is found, the organization will withhold their endorsement and prevent the auction from being closed. This prevents the seller from ending the auction prematurely, or colluding with buyers to end the auction at an artificially low price.
The sample uses several Fabric features to make the auction private and secure. Bids are stored in private data collections to prevent bids from being distributed to other peers in the channel. When bidding is closed, the auction smart contract uses the `GetPrivateDataHash()` API to verify that the bid stored in private data is the same bid that is being revealed. State based endorsement is used to add the organization of each bidder to the auction endorsement policy. The smart contract uses the `GetClientIdentity.GetID()` API to ensure that only the potential buyer can read their bid from private state and only the seller can close or end the auction.
This tutorial uses the auction smart contract in a scenario where one seller wants to auction a painting. Four potential buyers from two different organizations will submit bids to the auction and try to win the auction.
## Deploy the chaincode
We will run the auction smart contract using the Fabric test network. Open a command terminal and navigate to the test network directory:
```
cd fabric-samples/test-network
```
You can then run the following command to deploy the test network.
```
./network.sh up createChannel -ca
```
Note that we use the `-ca` flag to deploy the network using certificate authorities. We will use the CA to register and enroll our sellers and buyers.
Run the following command to deploy the auction smart contract. We will override the default endorsement policy to allow any channel member to create an auction without requiring an endorsement from another organization.
```
./network.sh deployCC -ccn auction -ccp ../auction-simple/chaincode-go/ -ccl go -ccep "OR('Org1MSP.peer','Org2MSP.peer')"
```
## Install the application dependencies
We will interact with the auction smart contract through a set of Node.js applications. Change into the `application-javascript` directory:
```
cd fabric-samples/auction-simple/application-javascript
```
From this directory, run the following command to download the application dependencies:
```
npm install
```
## Register and enroll the application identities
To interact with the network, you will need to enroll the Certificate Authority administrators of Org1 and Org2. You can use the `enrollAdmin.js` program for this task. Run the following command to enroll the Org1 admin:
```
node enrollAdmin.js org1
```
You should see the logs of the admin wallet being created on your local file system. Now run the command to enroll the CA admin of Org2:
```
node enrollAdmin.js org2
```
We can use the CA admins of both organizations to register and enroll the identities of the seller that will create the auction and the bidders who will try to purchase the painting.
Run the following command to register and enroll the seller identity that will create the auction. The seller will belong to Org1.
```
node registerEnrollUser.js org1 seller
```
You should see the logs of the seller wallet being created as well. Run the following commands to register and enroll 2 bidders from Org1 and another 2 bidders from Org2:
```
node registerEnrollUser.js org1 bidder1
node registerEnrollUser.js org1 bidder2
node registerEnrollUser.js org2 bidder3
node registerEnrollUser.js org2 bidder4
```
## Create the auction
The seller from Org1 would like to create an auction to sell a vintage Matchbox painting. Run the following command to use the seller wallet to run the `createAuction.js` application. The program will submit a transaction to the network that creates the auction on the channel ledger. The organization and identity name are passed to the application to use the wallet that was created by the `registerEnrollUser.js` application. The seller needs to provide an ID for the auction and the item to be sold to create the auction:
```
node createAuction.js org1 seller PaintingAuction painting
```
After the transaction is complete, the `createAuction.js` application will query the auction stored in the public channel ledger:
```
*** Result: Auction: {
"objectType": "auction",
"item": "painting",
"seller": "x509::CN=seller,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US",
"organizations": [
"Org1MSP"
],
"privateBids": {},
"revealedBids": {},
"winner": "",
"price": 0,
"status": "open"
}
```
The smart contract uses the `GetClientIdentity().GetID()` API to read the identity that creates the auction and defines that identity as the auction `"seller"`. The seller is identified by the name and issuer of the seller's certificate.
## Bid on the auction
We can now use the bidder wallets to submit bids to the auction:
### Bid as bidder1
Bidder1 will create a bid to purchase the painting for 800 dollars.
```
node bid.js org1 bidder1 PaintingAuction 800
```
The application will query the bid after it is created:
```
*** Result: Bid: {
"objectType": "bid",
"price": 800,
"org": "Org1MSP",
"bidder": "x509::CN=bidder1,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US"
}
```
The bid is stored in the Org1 implicit data collection. The `"bidder"` parameter is the information from the certificate of the user that created the bid. Only this identity will be able can query the bid from private state or reveal the bid during the auction.
The `bid.js` application also prints the bidID:
```
*** Result ***SAVE THIS VALUE*** BidID: 67d85ef08e32de20994c816362d0952fe5c2ae3f2d1083600c3ac61f65a89f60
```
The BidID acts as the unique identifier for the bid. This ID allows you to query the bid using the `queryBid.js` program and add the bid to the auction. Save the bidID returned by the application as an environment variable in your terminal:
```
export BIDDER1_BID_ID=67d85ef08e32de20994c816362d0952fe5c2ae3f2d1083600c3ac61f65a89f60
```
This value will be different for each transaction, so you will need to use the value returned in your terminal.
Now that the bid has been created, you can submit the bid to the auction. Run the following command to submit the bid that was just created:
```
node submitBid.js org1 bidder1 PaintingAuction $BIDDER1_BID_ID
```
The hash of bid will be added to the list private bids in that have been submitted to `PaintingAuction`. Storing the hash in the public auction allows users to accurately reveal the bid after bidding is closed. The application will query the auction to verify that the bid was added:
```
*** Result: Auction: {
"objectType": "auction",
"item": "painting",
"seller": "x509::CN=seller,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US",
"organizations": [
"Org1MSP"
],
"privateBids": {
"\u0000bid\u0000PaintingAuction\u00005c049b0b4552d34c88e0f8fb5abca31fa04472b7e1336a16650ac8cfb0b16472\u0000": {
"org": "Org1MSP",
"hash": "0b8bbdb96b1d252e71ac1ed71df3580f7a0e31a743a4a09bbf5196dffef426b2"
}
},
"revealedBids": {},
"winner": "",
"price": 0,
"status": "open"
}
```
### Bid as bidder2
Let's submit another bid. Bidder2 would like to purchase the painting for 500 dollars.
```
node bid.js org1 bidder2 PaintingAuction 500
```
Save the Bid ID returned by the application:
```
export BIDDER2_BID_ID=0fa8b3b15923966f205a1f5ebd163d2707d069ffa055105114fc654d225f511d
```
Submit bidder2's bid to the auction:
```
node submitBid.js org1 bidder2 PaintingAuction $BIDDER2_BID_ID
```
### Bid as bidder3 from Org2
Bidder3 will bid 700 dollars for the painting:
```
node bid.js org2 bidder3 PaintingAuction 700
```
Save the Bid ID returned by the application:
```
export BIDDER3_BID_ID=cda8bb2849fc0553efb036c56ea86d82791a695b5641941dac797dc6e2d75768
```
Add bidder3's bid to the auction:
```
node submitBid.js org2 bidder3 PaintingAuction $BIDDER3_BID_ID
```
Because bidder3 belongs to Org2, submitting the bid will add Org2 to the list of participating organizations. You can see the Org2 MSP ID has been added to the list of `"organizations"` in the updated auction returned by the application:
```
*** Result: Auction: {
"objectType": "auction",
"item": "painting",
"seller": "x509::CN=seller,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US",
"organizations": [
"Org1MSP",
"Org2MSP"
],
"privateBids": {
"\u0000bid\u0000PaintingAuction\u00001b9dc0006fef10413df5cca927cabdf73ab854fe92b7a7b2eebfa00961fdac67\u0000": {
"org": "Org1MSP",
"hash": "15cd9a3e12825017f3e758499ac6138ebbe1adec4c49cc6ea6a0973fc6514666"
},
"\u0000bid\u0000PaintingAuction\u00005c049b0b4552d34c88e0f8fb5abca31fa04472b7e1336a16650ac8cfb0b16472\u0000": {
"org": "Org1MSP",
"hash": "0b8bbdb96b1d252e71ac1ed71df3580f7a0e31a743a4a09bbf5196dffef426b2"
},
"\u0000bid\u0000PaintingAuction\u00005ee4fa53b54ea0821e57a6884a1ada5eb04f136ee222e92d7399bcdf47556ea1\u0000": {
"org": "Org2MSP",
"hash": "14d47d17acceceb483e87c14a4349844874fce549d71c6a23457d953ed8ffbd3"
}
},
"revealedBids": {},
"winner": "",
"price": 0,
"status": "open"
}
```
Now that a bid from Org2 has been added to the auction, any updates to the auction need to be endorsed by the Org2 peer. The applications will use `"organizations"` field to specify which organizations need to endorse submitting a new bid, revealing a bid, or updating the auction status.
### Bid as bidder4
Bidder4 from Org2 would like to purchase the painting for 900 dollars:
```
node bid.js org2 bidder4 PaintingAuction 900
```
Save the Bid ID returned by the application:
```
export BIDDER4_BID_ID=83861eb17715ff537a1e73cd2d08509dc7199572806a5368706516759af1a257
```
Add bidder4's bid to the auction:
```
node submitBid.js org2 bidder4 PaintingAuction $BIDDER4_BID_ID
```
## Close the auction
Now that all four bidders have joined the auction, the seller would like to close the auction and allow buyers to reveal their bids. The seller identity that created the auction needs to submit the transaction:
```
node closeAuction.js org1 seller PaintingAuction
```
The application will query the auction to allow you to verify that the auction status has changed to closed. As a test, you can try to create and submit a new bid to verify that no new bids can be added to the auction.
## Reveal bids
After the auction is closed, bidders can try to win the auction by revealing their bids. The transaction to reveal a bid needs to pass four checks:
1. The auction is closed.
2. The transaction was submitted by the identity that created the bid.
3. The hash of the revealed bid matches the hash of the bid on the channel ledger. This confirms that the bid is the same as the bid that is stored in the private data collection.
4. The hash of the revealed bid matches the hash that was submitted to the auction. This confirms that the bid was not altered after the auction was closed.
Use the `revealBid.js` application to reveal the bid of Bidder1:
```
node revealBid.js org1 bidder1 PaintingAuction $BIDDER1_BID_ID
```
The full bid details, including the price, are now visible:
```
*** Result: Auction: {
"objectType": "auction",
"item": "painting",
"seller": "x509::CN=seller,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US",
"organizations": [
"Org1MSP",
"Org2MSP"
],
"privateBids": {
"\u0000bid\u0000PaintingAuction\u000019a7a0dd2c5456a3f79c2f9ccb09dddd0f1c9ece514dfea7cbea06e7cbc79855\u0000": {
"org": "Org2MSP",
"hash": "08db66c6cc226577a3153dadeb0b77d3834162fcf5f008b344058a1bc5c1b3a4"
},
"\u0000bid\u0000PaintingAuction\u00001b9dc0006fef10413df5cca927cabdf73ab854fe92b7a7b2eebfa00961fdac67\u0000": {
"org": "Org1MSP",
"hash": "15cd9a3e12825017f3e758499ac6138ebbe1adec4c49cc6ea6a0973fc6514666"
},
"\u0000bid\u0000PaintingAuction\u00005c049b0b4552d34c88e0f8fb5abca31fa04472b7e1336a16650ac8cfb0b16472\u0000": {
"org": "Org1MSP",
"hash": "0b8bbdb96b1d252e71ac1ed71df3580f7a0e31a743a4a09bbf5196dffef426b2"
},
"\u0000bid\u0000PaintingAuction\u00005ee4fa53b54ea0821e57a6884a1ada5eb04f136ee222e92d7399bcdf47556ea1\u0000": {
"org": "Org2MSP",
"hash": "14d47d17acceceb483e87c14a4349844874fce549d71c6a23457d953ed8ffbd3"
}
},
"revealedBids": {
"\u0000bid\u0000PaintingAuction\u00005c049b0b4552d34c88e0f8fb5abca31fa04472b7e1336a16650ac8cfb0b16472\u0000": {
"objectType": "bid",
"price": 800,
"org": "Org1MSP",
"bidder": "x509::CN=bidder1,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US"
}
},
"winner": "",
"price": 0,
"status": "closed"
}
```
Bidder3 from Org2 will also reveal their bid:
```
node revealBid.js org2 bidder3 PaintingAuction $BIDDER3_BID_ID
```
If the auction ended now, Bidder1 would win. Let's try to end the auction using the seller identity and see what happens.
```
node endAuction.js org1 seller PaintingAuction
```
The output should look something like the following:
```
--> Submit the transaction to end the auction
2021-01-28T16:47:27.501Z - error: [DiscoveryHandler]: compareProposalResponseResults[undefined] - read/writes result sets do not match index=1
2021-01-28T16:47:27.503Z - error: [Transaction]: Error: No valid responses from any peers. Errors:
peer=undefined, status=grpc, message=Peer endorsements do not match
******** FAILED to submit bid: Error: No valid responses from any peers. Errors:
peer=undefined, status=grpc, message=Peer endorsements do not match
```
Instead of ending the auction, the transaction results in an endorsement policy failure. The end of the auction needs to be endorsed by Org2. Before endorsing the transaction, the Org2 peer queries its private data collection for any winning bids that have not yet been revealed. Because Bidder4 created a bid that is above the winning price, the Org2 peer refuses to endorse the transaction that would end the auction.
Before we can end the auction, we need to reveal the bid from bidder4.
```
node revealBid.js org2 bidder4 PaintingAuction $BIDDER4_BID_ID
```
Bidder2 from Org1 would not win the auction in either case. As a result, Bidder2 decides not to reveal their bid.
## End the auction
Now that the winning bids have been revealed, we can end the auction:
```
node endAuction org1 seller PaintingAuction
```
The transaction was successfully endorsed by both Org1 and Org2, who both calculated the same price and winner. The winning bidder is listed along with the price:
```
*** Result: Auction: {
"objectType": "auction",
"item": "painting",
"seller": "x509::CN=seller,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US",
"organizations": [
"Org1MSP",
"Org2MSP"
],
"privateBids": {
"\u0000bid\u0000PaintingAuction\u000019a7a0dd2c5456a3f79c2f9ccb09dddd0f1c9ece514dfea7cbea06e7cbc79855\u0000": {
"org": "Org2MSP",
"hash": "08db66c6cc226577a3153dadeb0b77d3834162fcf5f008b344058a1bc5c1b3a4"
},
"\u0000bid\u0000PaintingAuction\u00001b9dc0006fef10413df5cca927cabdf73ab854fe92b7a7b2eebfa00961fdac67\u0000": {
"org": "Org1MSP",
"hash": "15cd9a3e12825017f3e758499ac6138ebbe1adec4c49cc6ea6a0973fc6514666"
},
"\u0000bid\u0000PaintingAuction\u00005c049b0b4552d34c88e0f8fb5abca31fa04472b7e1336a16650ac8cfb0b16472\u0000": {
"org": "Org1MSP",
"hash": "0b8bbdb96b1d252e71ac1ed71df3580f7a0e31a743a4a09bbf5196dffef426b2"
},
"\u0000bid\u0000PaintingAuction\u00005ee4fa53b54ea0821e57a6884a1ada5eb04f136ee222e92d7399bcdf47556ea1\u0000": {
"org": "Org2MSP",
"hash": "14d47d17acceceb483e87c14a4349844874fce549d71c6a23457d953ed8ffbd3"
}
},
"revealedBids": {
"\u0000bid\u0000PaintingAuction\u000019a7a0dd2c5456a3f79c2f9ccb09dddd0f1c9ece514dfea7cbea06e7cbc79855\u0000": {
"objectType": "bid",
"price": 900,
"org": "Org2MSP",
"bidder": "x509::CN=bidder4,OU=client+OU=org2+OU=department1::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK"
},
"\u0000bid\u0000PaintingAuction\u00005c049b0b4552d34c88e0f8fb5abca31fa04472b7e1336a16650ac8cfb0b16472\u0000": {
"objectType": "bid",
"price": 800,
"org": "Org1MSP",
"bidder": "x509::CN=bidder1,OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US"
},
"\u0000bid\u0000PaintingAuction\u00005ee4fa53b54ea0821e57a6884a1ada5eb04f136ee222e92d7399bcdf47556ea1\u0000": {
"objectType": "bid",
"price": 700,
"org": "Org2MSP",
"bidder": "x509::CN=bidder3,OU=client+OU=org2+OU=department1::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK"
}
},
"winner": "x509::CN=bidder4,OU=client+OU=org2+OU=department1::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK",
"price": 900,
"status": "ended"
}
```
## Clean up
When your are done using the auction smart contract, you can bring down the network and clean up the environment. In the `auction-simple/application-javascript` directory, run the following command to remove the wallets used to run the applications:
```
rm -rf wallet
```
You can then navigate to the test network directory and bring down the network:
````
cd ../../test-network/
./network.sh down
````

View file

@ -1,5 +0,0 @@
#
# SPDX-License-Identifier: Apache-2.0
#
coverage

View file

@ -1,36 +0,0 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
module.exports = {
env: {
node: true,
mocha: true
},
parserOptions: {
ecmaVersion: 8,
sourceType: 'script'
},
extends: 'eslint:recommended',
rules: {
indent: ['error', 'tab'],
'linebreak-style': ['error', 'unix'],
quotes: ['error', 'single'],
semi: ['error', 'always'],
'no-unused-vars': ['error', { args: 'none' }],
'no-console': 'off',
curly: 'error',
eqeqeq: 'error',
'no-throw-literal': 'error',
strict: 'error',
'no-var': 'error',
'dot-notation': 'error',
'no-trailing-spaces': 'error',
'no-use-before-define': 'error',
'no-useless-call': 'error',
'no-with': 'error',
'operator-linebreak': 'error',
yoda: 'error',
'quote-props': ['error', 'as-needed']
}
};

View file

@ -1,101 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const path = require('path');
const { buildCCPOrg1, buildCCPOrg2, buildWallet, prettyJSONString} = require('../../test-application/javascript/AppUtil.js');
const myChannel = 'mychannel';
const myChaincodeName = 'auction';
async function bid(ccp,wallet,user,orgMSP,auctionID,price) {
try {
const gateway = new Gateway();
// Connect using Discovery enabled
await gateway.connect(ccp,
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
const network = await gateway.getNetwork(myChannel);
const contract = network.getContract(myChaincodeName);
console.log('\n--> Evaluate Transaction: get your client ID');
let bidder = await contract.evaluateTransaction('GetSubmittingClientIdentity');
console.log('*** Result: Bidder ID is ' + bidder.toString());
let bidData = { objectType: 'bid', price: parseInt(price), org: orgMSP, bidder: bidder.toString()};
let statefulTxn = contract.createTransaction('Bid');
statefulTxn.setEndorsingOrganizations(orgMSP);
let tmapData = Buffer.from(JSON.stringify(bidData));
statefulTxn.setTransient({
bid: tmapData
});
let bidID = statefulTxn.getTransactionId();
console.log('\n--> Submit Transaction: Create the bid that is stored in your organization\'s private data collection');
await statefulTxn.submit(auctionID);
console.log('*** Result: committed');
console.log('*** Result ***SAVE THIS VALUE*** BidID: ' + bidID.toString());
console.log('\n--> Evaluate Transaction: read the bid that was just created');
let result = await contract.evaluateTransaction('QueryBid',auctionID,bidID);
console.log('*** Result: Bid: ' + prettyJSONString(result.toString()));
gateway.disconnect();
} catch (error) {
console.error(`******** FAILED to submit bid: ${error}`);
if (error.stack) {
console.error(error.stack);
}
process.exit(1);
}
}
async function main() {
try {
if (process.argv[2] === undefined || process.argv[3] === undefined ||
process.argv[4] === undefined || process.argv[5] === undefined) {
console.log('Usage: node bid.js org userID auctionID price');
process.exit(1);
}
const org = process.argv[2];
const user = process.argv[3];
const auctionID = process.argv[4];
const price = process.argv[5];
if (org === 'Org1' || org === 'org1') {
const orgMSP = 'Org1MSP';
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await bid(ccp,wallet,user,orgMSP,auctionID,price);
}
else if (org === 'Org2' || org === 'org2') {
const orgMSP = 'Org2MSP';
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await bid(ccp,wallet,user,orgMSP,auctionID,price);
} else {
console.log('Usage: node bid.js org userID auctionID price');
console.log('Org must be Org1 or Org2');
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
process.exit(1);
}
}
main();

View file

@ -1,93 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const path = require('path');
const { buildCCPOrg1, buildCCPOrg2, buildWallet, prettyJSONString} = require('../../test-application/javascript/AppUtil.js');
const myChannel = 'mychannel';
const myChaincodeName = 'auction';
async function closeAuction(ccp,wallet,user,auctionID) {
try {
const gateway = new Gateway();
// Connect using Discovery enabled
await gateway.connect(ccp,
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
const network = await gateway.getNetwork(myChannel);
const contract = network.getContract(myChaincodeName);
// Query the auction to get the list of endorsing orgs.
let auctionString = await contract.evaluateTransaction('QueryAuction',auctionID);
let auctionJSON = JSON.parse(auctionString);
let statefulTxn = contract.createTransaction('CloseAuction');
if (auctionJSON.organizations.length === 2) {
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0],auctionJSON.organizations[1]);
} else {
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0]);
}
console.log('\n--> Submit Transaction: close auction');
await statefulTxn.submit(auctionID);
console.log('*** Result: committed');
console.log('\n--> Evaluate Transaction: query the updated auction');
let result = await contract.evaluateTransaction('QueryAuction',auctionID);
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
gateway.disconnect();
} catch (error) {
console.error(`******** FAILED to submit bid: ${error}`);
process.exit(1);
}
}
async function main() {
try {
if (process.argv[2] === undefined || process.argv[3] === undefined ||
process.argv[4] === undefined) {
console.log('Usage: node closeAuction.js org userID auctionID');
process.exit(1);
}
const org = process.argv[2];
const user = process.argv[3];
const auctionID = process.argv[4];
if (org === 'Org1' || org === 'org1') {
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await closeAuction(ccp,wallet,user,auctionID);
}
else if (org === 'Org2' || org === 'org2') {
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await closeAuction(ccp,wallet,user,auctionID);
} else {
console.log('Usage: node closeAuction.js org userID auctionID ');
console.log('Org must be Org1 or Org2');
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
if (error.stack) {
console.error(error.stack);
}
process.exit(1);
}
}
main();

View file

@ -1,79 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const path = require('path');
const { buildCCPOrg1, buildCCPOrg2, buildWallet, prettyJSONString} = require('../../test-application/javascript/AppUtil.js');
const myChannel = 'mychannel';
const myChaincodeName = 'auction';
async function createAuction(ccp,wallet,user,auctionID,item) {
try {
const gateway = new Gateway();
// Connect using Discovery enabled
await gateway.connect(ccp,
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
const network = await gateway.getNetwork(myChannel);
const contract = network.getContract(myChaincodeName);
let statefulTxn = contract.createTransaction('CreateAuction');
console.log('\n--> Submit Transaction: Propose a new auction');
await statefulTxn.submit(auctionID,item);
console.log('*** Result: committed');
console.log('\n--> Evaluate Transaction: query the auction that was just created');
let result = await contract.evaluateTransaction('QueryAuction',auctionID);
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
gateway.disconnect();
} catch (error) {
console.error(`******** FAILED to submit bid: ${error}`);
}
}
async function main() {
try {
if (process.argv[2] === undefined || process.argv[3] === undefined ||
process.argv[4] === undefined || process.argv[5] === undefined) {
console.log('Usage: node createAuction.js org userID auctionID item');
process.exit(1);
}
const org = process.argv[2];
const user = process.argv[3];
const auctionID = process.argv[4];
const item = process.argv[5];
if (org === 'Org1' || org === 'org1') {
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await createAuction(ccp,wallet,user,auctionID,item);
}
else if (org === 'Org2' || org === 'org2') {
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await createAuction(ccp,wallet,user,auctionID,item);
} else {
console.log('Usage: node createAuction.js org userID auctionID item');
console.log('Org must be Org1 or Org2');
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
}
}
main();

View file

@ -1,93 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const path = require('path');
const { buildCCPOrg1, buildCCPOrg2, buildWallet, prettyJSONString} = require('../../test-application/javascript/AppUtil.js');
const myChannel = 'mychannel';
const myChaincodeName = 'auction';
async function endAuction(ccp,wallet,user,auctionID) {
try {
const gateway = new Gateway();
// Connect using Discovery enabled
await gateway.connect(ccp,
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
const network = await gateway.getNetwork(myChannel);
const contract = network.getContract(myChaincodeName);
// Query the auction to get the list of endorsing orgs.
let auctionString = await contract.evaluateTransaction('QueryAuction',auctionID);
let auctionJSON = JSON.parse(auctionString);
let statefulTxn = contract.createTransaction('EndAuction');
if (auctionJSON.organizations.length === 2) {
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0],auctionJSON.organizations[1]);
} else {
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0]);
}
console.log('\n--> Submit the transaction to end the auction');
await statefulTxn.submit(auctionID);
console.log('*** Result: committed');
console.log('\n--> Evaluate Transaction: query the updated auction');
let result = await contract.evaluateTransaction('QueryAuction',auctionID);
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
gateway.disconnect();
} catch (error) {
console.error(`******** FAILED to submit bid: ${error}`);
process.exit(1);
}
}
async function main() {
try {
if (process.argv[2] === undefined || process.argv[3] === undefined ||
process.argv[4] === undefined) {
console.log('Usage: node endAuction.js org userID auctionID');
process.exit(1);
}
const org = process.argv[2];
const user = process.argv[3];
const auctionID = process.argv[4];
if (org === 'Org1' || org === 'org1') {
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await endAuction(ccp,wallet,user,auctionID);
}
else if (org === 'Org2' || org === 'org2') {
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await endAuction(ccp,wallet,user,auctionID);
} else {
console.log('Usage: node endAuction.js org userID auctionID');
console.log('Org must be Org1 or Org2');
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
if (error.stack) {
console.error(error.stack);
}
process.exit(1);
}
}
main();

View file

@ -1,67 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Wallets } = require('fabric-network');
const FabricCAServices = require('fabric-ca-client');
const path = require('path');
const { buildCAClient, enrollAdmin } = require('../../test-application/javascript/CAUtil.js');
const { buildCCPOrg1, buildCCPOrg2, buildWallet } = require('../../test-application/javascript/AppUtil.js');
const mspOrg1 = 'Org1MSP';
const mspOrg2 = 'Org2MSP';
async function connectToOrg1CA() {
console.log('\n--> Enrolling the Org1 CA admin');
const ccpOrg1 = buildCCPOrg1();
const caOrg1Client = buildCAClient(FabricCAServices, ccpOrg1, 'ca.org1.example.com');
const walletPathOrg1 = path.join(__dirname, 'wallet/org1');
const walletOrg1 = await buildWallet(Wallets, walletPathOrg1);
await enrollAdmin(caOrg1Client, walletOrg1, mspOrg1);
}
async function connectToOrg2CA() {
console.log('\n--> Enrolling the Org2 CA admin');
const ccpOrg2 = buildCCPOrg2();
const caOrg2Client = buildCAClient(FabricCAServices, ccpOrg2, 'ca.org2.example.com');
const walletPathOrg2 = path.join(__dirname, 'wallet/org2');
const walletOrg2 = await buildWallet(Wallets, walletPathOrg2);
await enrollAdmin(caOrg2Client, walletOrg2, mspOrg2);
}
async function main() {
if (process.argv[2] === undefined) {
console.log('Usage: node enrollAdmin.js Org');
process.exit(1);
}
const org = process.argv[2];
try {
if (org === 'Org1' || org === 'org1') {
await connectToOrg1CA();
}
else if (org === 'Org2' || org === 'org2') {
await connectToOrg2CA();
} else {
console.log('Usage: node registerUser.js org userID');
console.log('Org must be Org1 or Org2');
}
} catch (error) {
console.error(`Error in enrolling admin: ${error}`);
process.exit(1);
}
}
main();

View file

@ -1,22 +0,0 @@
{
"name": "auction",
"version": "1.0.0",
"description": "auction application implemented in JavaScript",
"engines": {
"node": ">=12",
"npm": ">=5"
},
"engineStrict": true,
"author": "Hyperledger",
"license": "Apache-2.0",
"scripts": {
"lint": "eslint *.js"
},
"dependencies": {
"fabric-ca-client": "^2.2.19",
"fabric-network": "^2.2.19"
},
"devDependencies": {
"eslint": "^7.32.0"
}
}

View file

@ -1,72 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const path = require('path');
const { buildCCPOrg1, buildCCPOrg2, buildWallet, prettyJSONString} = require('../../test-application/javascript/AppUtil.js');
const myChannel = 'mychannel';
const myChaincodeName = 'auction';
async function queryAuction(ccp,wallet,user,auctionID) {
try {
const gateway = new Gateway();
// Connect using Discovery enabled
await gateway.connect(ccp,
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
const network = await gateway.getNetwork(myChannel);
const contract = network.getContract(myChaincodeName);
console.log('\n--> Evaluate Transaction: query the auction');
let result = await contract.evaluateTransaction('QueryAuction',auctionID);
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
gateway.disconnect();
} catch (error) {
console.error(`******** FAILED to submit bid: ${error}`);
}
}
async function main() {
try {
if (process.argv[2] === undefined || process.argv[3] === undefined ||
process.argv[4] === undefined) {
console.log('Usage: node queryAuction.js org userID auctionID');
process.exit(1);
}
const org = process.argv[2];
const user = process.argv[3];
const auctionID = process.argv[4];
if (org === 'Org1' || org === 'org1') {
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await queryAuction(ccp,wallet,user,auctionID);
}
else if (org === 'Org2' || org === 'org2') {
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await queryAuction(ccp,wallet,user,auctionID);
} else {
console.log('Usage: node queryAuction.js org userID auctionID');
console.log('Org must be Org1 or Org2');
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
}
}
main();

View file

@ -1,73 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const path = require('path');
const { buildCCPOrg1, buildCCPOrg2, buildWallet, prettyJSONString} = require('../../test-application/javascript/AppUtil.js');
const myChannel = 'mychannel';
const myChaincodeName = 'auction';
async function queryBid(ccp,wallet,user,auctionID,bidID) {
try {
const gateway = new Gateway();
// Connect using Discovery enabled
await gateway.connect(ccp,
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
const network = await gateway.getNetwork(myChannel);
const contract = network.getContract(myChaincodeName);
console.log('\n--> Evaluate Transaction: read bid from private data store');
let result = await contract.evaluateTransaction('QueryBid',auctionID,bidID);
console.log('*** Result: Bid: ' + prettyJSONString(result.toString()));
gateway.disconnect();
} catch (error) {
console.error(`******** FAILED to submit bid: ${error}`);
}
}
async function main() {
try {
if (process.argv[2] === undefined || process.argv[3] === undefined ||
process.argv[4] === undefined || process.argv[5] === undefined) {
console.log('Usage: node bid.js org userID auctionID bidID');
process.exit(1);
}
const org = process.argv[2];
const user = process.argv[3];
const auctionID = process.argv[4];
const bidID = process.argv[5];
if (org === 'Org1' || org === 'org1') {
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await queryBid(ccp,wallet,user,auctionID,bidID);
}
else if (org === 'Org2' || org === 'org2') {
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await queryBid(ccp,wallet,user,auctionID,bidID);
} else {
console.log('Usage: node bid.js org userID auctionID bidID');
console.log('Org must be Org1 or Org2');
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
}
}
main();

View file

@ -1,68 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Wallets } = require('fabric-network');
const FabricCAServices = require('fabric-ca-client');
const path = require('path');
const { buildCAClient, registerAndEnrollUser } = require('../../test-application/javascript/CAUtil.js');
const { buildCCPOrg1, buildCCPOrg2, buildWallet } = require('../../test-application/javascript/AppUtil.js');
const mspOrg1 = 'Org1MSP';
const mspOrg2 = 'Org2MSP';
async function connectToOrg1CA(UserID) {
console.log('\n--> Register and enrolling new user');
const ccpOrg1 = buildCCPOrg1();
const caOrg1Client = buildCAClient(FabricCAServices, ccpOrg1, 'ca.org1.example.com');
const walletPathOrg1 = path.join(__dirname, 'wallet/org1');
const walletOrg1 = await buildWallet(Wallets, walletPathOrg1);
await registerAndEnrollUser(caOrg1Client, walletOrg1, mspOrg1, UserID, 'org1.department1');
}
async function connectToOrg2CA(UserID) {
console.log('\n--> Register and enrolling new user');
const ccpOrg2 = buildCCPOrg2();
const caOrg2Client = buildCAClient(FabricCAServices, ccpOrg2, 'ca.org2.example.com');
const walletPathOrg2 = path.join(__dirname, 'wallet/org2');
const walletOrg2 = await buildWallet(Wallets, walletPathOrg2);
await registerAndEnrollUser(caOrg2Client, walletOrg2, mspOrg2, UserID, 'org2.department1');
}
async function main() {
if (process.argv[2] === undefined && process.argv[3] === undefined) {
console.log('Usage: node registerEnrollUser.js org userID');
process.exit(1);
}
const org = process.argv[2];
const userId = process.argv[3];
try {
if (org === 'Org1' || org === 'org1') {
await connectToOrg1CA(userId);
}
else if (org === 'Org2' || org === 'org2') {
await connectToOrg2CA(userId);
} else {
console.log('Usage: node registerEnrollUser.js org userID');
console.log('Org must be Org1 or Org2');
}
} catch (error) {
console.error(`Error in enrolling admin: ${error}`);
process.exit(1);
}
}
main();

View file

@ -1,103 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const path = require('path');
const { buildCCPOrg1, buildCCPOrg2, buildWallet, prettyJSONString} = require('../../test-application/javascript/AppUtil.js');
const myChannel = 'mychannel';
const myChaincodeName = 'auction';
async function addBid(ccp,wallet,user,auctionID,bidID) {
try {
const gateway = new Gateway();
await gateway.connect(ccp,
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
const network = await gateway.getNetwork(myChannel);
const contract = network.getContract(myChaincodeName);
console.log('\n--> Evaluate Transaction: read your bid');
let bidString = await contract.evaluateTransaction('QueryBid',auctionID,bidID);
let bidJSON = JSON.parse(bidString);
// console.log('\n--> Evaluate Transaction: query the auction you want to join');
let auctionString = await contract.evaluateTransaction('QueryAuction',auctionID);
// console.log('*** Result: Bid: ' + prettyJSONString(auctionString.toString()));
let auctionJSON = JSON.parse(auctionString);
let bidData = { objectType: 'bid', price: parseInt(bidJSON.price), org: bidJSON.org, bidder: bidJSON.bidder};
console.log('*** Result: Bid: ' + JSON.stringify(bidData,null,2));
let statefulTxn = contract.createTransaction('RevealBid');
let tmapData = Buffer.from(JSON.stringify(bidData));
statefulTxn.setTransient({
bid: tmapData
});
if (auctionJSON.organizations.length === 2) {
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0],auctionJSON.organizations[1]);
} else {
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0]);
}
await statefulTxn.submit(auctionID,bidID);
console.log('\n--> Evaluate Transaction: query the auction to see that our bid was added');
let result = await contract.evaluateTransaction('QueryAuction',auctionID);
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
gateway.disconnect();
} catch (error) {
console.error(`******** FAILED to submit bid: ${error}`);
process.exit(1);
}
}
async function main() {
try {
if (process.argv[2] === undefined || process.argv[3] === undefined ||
process.argv[4] === undefined || process.argv[5] === undefined) {
console.log('Usage: node revealBid.js org userID auctionID bidID');
process.exit(1);
}
const org = process.argv[2];
const user = process.argv[3];
const auctionID = process.argv[4];
const bidID = process.argv[5];
if (org === 'Org1' || org === 'org1') {
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await addBid(ccp,wallet,user,auctionID,bidID);
}
else if (org === 'Org2' || org === 'org2') {
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await addBid(ccp,wallet,user,auctionID,bidID);
}
else {
console.log('Usage: node revealBid.js org userID auctionID bidID');
console.log('Org must be Org1 or Org2');
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
if (error.stack) {
console.error(error.stack);
}
process.exit(1);
}
}
main();

View file

@ -1,104 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const path = require('path');
const { buildCCPOrg1, buildCCPOrg2, buildWallet } = require('../../test-application/javascript/AppUtil.js');
const myChannel = 'mychannel';
const myChaincodeName = 'auction';
function prettyJSONString(inputString) {
if (inputString) {
return JSON.stringify(JSON.parse(inputString), null, 2);
}
else {
return inputString;
}
}
async function submitBid(ccp,wallet,user,auctionID,bidID) {
try {
const gateway = new Gateway();
// Connect using Discovery enabled
await gateway.connect(ccp,
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
const network = await gateway.getNetwork(myChannel);
const contract = network.getContract(myChaincodeName);
console.log('\n--> Evaluate Transaction: query the auction you want to join');
let auctionString = await contract.evaluateTransaction('QueryAuction',auctionID);
let auctionJSON = JSON.parse(auctionString);
let statefulTxn = contract.createTransaction('SubmitBid');
if (auctionJSON.organizations.length === 2) {
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0],auctionJSON.organizations[1]);
} else {
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0]);
}
console.log('\n--> Submit Transaction: add bid to the auction');
await statefulTxn.submit(auctionID,bidID);
console.log('\n--> Evaluate Transaction: query the auction to see that our bid was added');
let result = await contract.evaluateTransaction('QueryAuction',auctionID);
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
gateway.disconnect();
} catch (error) {
console.error(`******** FAILED to submit bid: ${error}`);
process.exit(1);
}
}
async function main() {
try {
if (process.argv[2] === undefined || process.argv[3] === undefined ||
process.argv[4] === undefined || process.argv[5] === undefined) {
console.log('Usage: node submitBid.js org userID auctionID bidID');
process.exit(1);
}
const org = process.argv[2];
const user = process.argv[3];
const auctionID = process.argv[4];
const bidID = process.argv[5];
if (org === 'Org1' || org === 'org1') {
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await submitBid(ccp,wallet,user,auctionID,bidID);
}
else if (org === 'Org2' || org === 'org2') {
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await submitBid(ccp,wallet,user,auctionID,bidID);
}
else {
console.log('Usage: node submitBid.js org userID auctionID bidID');
console.log('Org must be Org1 or Org2');
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
if (error.stack) {
console.error(error.stack);
}
process.exit(1);
}
}
main();

View file

@ -1,34 +0,0 @@
module github.com/hyperledger/fabric-samples/auction/chaincode-go
go 1.21
require (
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0-20240618210511-f7903324a8af
github.com/hyperledger/fabric-contract-api-go/v2 v2.0.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/gobuffalo/envy v1.10.2 // indirect
github.com/gobuffalo/packd v1.0.2 // indirect
github.com/gobuffalo/packr v1.30.1 // indirect
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/rogpeppe/go-internal v1.12.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
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
google.golang.org/grpc v1.64.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View file

@ -1,132 +0,0 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
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/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/envy v1.10.2 h1:EIi03p9c3yeuRCFPOKcSfajzkLb3hrRjEpHGI8I2Wo4=
github.com/gobuffalo/envy v1.10.2/go.mod h1:qGAGwdvDsaEtPhfBzb3o0SfDea8ByGn9j8bKmVft9z8=
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
github.com/gobuffalo/packd v1.0.2 h1:Yg523YqnOxGIWCp69W12yYBKsoChwI7mtu6ceM9Bwfw=
github.com/gobuffalo/packd v1.0.2/go.mod h1:sUc61tDqGMXON80zpKGp92lDb86Km28jfvX7IAyxFT8=
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
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/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0-20240618210511-f7903324a8af h1:WT4NjX7Uk03GSeH++jF3a0wp4FhybTM86zDPCETvmSk=
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0-20240618210511-f7903324a8af/go.mod h1:f/ER25FaBepxJugwpLhbD2hLAoZaZEVqkBjOcHjw72Y=
github.com/hyperledger/fabric-contract-api-go/v2 v2.0.0 h1:IDiCGVOBlRd6zpL0Y+f6V7IpBqa4/Z5JAK9SF7a5ea8=
github.com/hyperledger/fabric-contract-api-go/v2 v2.0.0/go.mod h1:pdqhe7ALf4lmXgQdprCyNWYdnCPxgj02Vhf8JF5w8po=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 h1:Xpd6fzG/KjAOHJsq7EQXY2l+qi/y8muxBaY7R6QWABk=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3/go.mod h1:2pq0ui6ZWA0cC8J+eCErgnMDCS1kPOEYVY+06ZAK0qE=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
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/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
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/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
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=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -1,448 +0,0 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
package auction
import (
"bytes"
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
)
type SmartContract struct {
contractapi.Contract
}
// Auction data
type Auction struct {
Type string `json:"objectType"`
ItemSold string `json:"item"`
Seller string `json:"seller"`
Orgs []string `json:"organizations"`
PrivateBids map[string]BidHash `json:"privateBids"`
RevealedBids map[string]FullBid `json:"revealedBids"`
Winner string `json:"winner"`
Price int `json:"price"`
Status string `json:"status"`
}
// FullBid is the structure of a revealed bid
type FullBid struct {
Type string `json:"objectType"`
Price int `json:"price"`
Org string `json:"org"`
Bidder string `json:"bidder"`
}
// BidHash is the structure of a private bid
type BidHash struct {
Org string `json:"org"`
Hash string `json:"hash"`
}
const bidKeyType = "bid"
// CreateAuction creates on auction on the public channel. The identity that
// submits the transacion becomes the seller of the auction
func (s *SmartContract) CreateAuction(ctx contractapi.TransactionContextInterface, auctionID string, itemsold string) error {
// get ID of submitting client
clientID, err := s.GetSubmittingClientIdentity(ctx)
if err != nil {
return fmt.Errorf("failed to get client identity %v", err)
}
// get org of submitting client
clientOrgID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return fmt.Errorf("failed to get client identity %v", err)
}
// Create auction
bidders := make(map[string]BidHash)
revealedBids := make(map[string]FullBid)
auction := Auction{
Type: "auction",
ItemSold: itemsold,
Price: 0,
Seller: clientID,
Orgs: []string{clientOrgID},
PrivateBids: bidders,
RevealedBids: revealedBids,
Winner: "",
Status: "open",
}
auctionJSON, err := json.Marshal(auction)
if err != nil {
return err
}
// put auction into state
err = ctx.GetStub().PutState(auctionID, auctionJSON)
if err != nil {
return fmt.Errorf("failed to put auction in public data: %v", err)
}
// set the seller of the auction as an endorser
err = setAssetStateBasedEndorsement(ctx, auctionID, clientOrgID)
if err != nil {
return fmt.Errorf("failed setting state based endorsement for new organization: %v", err)
}
return nil
}
// Bid is used to add a user's bid to the auction. The bid is stored in the private
// data collection on the peer of the bidder's organization. The function returns
// the transaction ID so that users can identify and query their bid
func (s *SmartContract) Bid(ctx contractapi.TransactionContextInterface, auctionID string) (string, error) {
// get bid from transient map
transientMap, err := ctx.GetStub().GetTransient()
if err != nil {
return "", fmt.Errorf("error getting transient: %v", err)
}
BidJSON, ok := transientMap["bid"]
if !ok {
return "", errors.New("bid key not found in the transient map")
}
// get the implicit collection name using the bidder's organization ID
collection, err := getCollectionName(ctx)
if err != nil {
return "", fmt.Errorf("failed to get implicit collection name: %v", err)
}
// the bidder has to target their peer to store the bid
err = verifyClientOrgMatchesPeerOrg(ctx)
if err != nil {
return "", fmt.Errorf("cannot store bid on this peer, not a member of this org: Error %v", err)
}
// the transaction ID is used as a unique index for the bid
txID := ctx.GetStub().GetTxID()
// create a composite key using the transaction ID
bidKey, err := ctx.GetStub().CreateCompositeKey(bidKeyType, []string{auctionID, txID})
if err != nil {
return "", fmt.Errorf("failed to create composite key: %v", err)
}
// put the bid into the organization's implicit data collection
err = ctx.GetStub().PutPrivateData(collection, bidKey, BidJSON)
if err != nil {
return "", fmt.Errorf("failed to input price into collection: %v", err)
}
// return the trannsaction ID so that the uset can identify their bid
return txID, nil
}
// SubmitBid is used by the bidder to add the hash of that bid stored in private data to the
// auction. Note that this function alters the auction in private state, and needs
// to meet the auction endorsement policy. Transaction ID is used identify the bid
func (s *SmartContract) SubmitBid(ctx contractapi.TransactionContextInterface, auctionID string, txID string) error {
// get the MSP ID of the bidder's org
clientOrgID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return fmt.Errorf("failed to get client MSP ID: %v", err)
}
// get the auction from public state
auction, err := s.QueryAuction(ctx, auctionID)
if err != nil {
return fmt.Errorf("failed to get auction from public state %v", err)
}
// the auction needs to be open for users to add their bid
Status := auction.Status
if Status != "open" {
return errors.New("cannot join closed or ended auction")
}
// get the inplicit collection name of bidder's org
collection, err := getCollectionName(ctx)
if err != nil {
return fmt.Errorf("failed to get implicit collection name: %v", err)
}
// use the transaction ID passed as a parameter to create composite bid key
bidKey, err := ctx.GetStub().CreateCompositeKey(bidKeyType, []string{auctionID, txID})
if err != nil {
return fmt.Errorf("failed to create composite key: %v", err)
}
// get the hash of the bid stored in private data collection
bidHash, err := ctx.GetStub().GetPrivateDataHash(collection, bidKey)
if err != nil {
return fmt.Errorf("failed to read bid bash from collection: %v", err)
}
if bidHash == nil {
return fmt.Errorf("bid hash does not exist: %s", bidKey)
}
// store the hash along with the bidder's organization
NewHash := BidHash{
Org: clientOrgID,
Hash: fmt.Sprintf("%x", bidHash),
}
auction.PrivateBids[bidKey] = NewHash
// Add the bidding organization to the list of participating organizations if it is not already
Orgs := auction.Orgs
if !(contains(Orgs, clientOrgID)) {
newOrgs := append(Orgs, clientOrgID)
auction.Orgs = newOrgs
err = addAssetStateBasedEndorsement(ctx, auctionID, clientOrgID)
if err != nil {
return fmt.Errorf("failed setting state based endorsement for new organization: %v", err)
}
}
newAuctionJSON, _ := json.Marshal(auction)
err = ctx.GetStub().PutState(auctionID, newAuctionJSON)
if err != nil {
return fmt.Errorf("failed to update auction: %v", err)
}
return nil
}
// RevealBid is used by a bidder to reveal their bid after the auction is closed
func (s *SmartContract) RevealBid(ctx contractapi.TransactionContextInterface, auctionID string, txID string) error {
// get bid from transient map
transientMap, err := ctx.GetStub().GetTransient()
if err != nil {
return fmt.Errorf("error getting transient: %v", err)
}
transientBidJSON, ok := transientMap["bid"]
if !ok {
return errors.New("bid key not found in the transient map")
}
// get implicit collection name of organization ID
collection, err := getCollectionName(ctx)
if err != nil {
return fmt.Errorf("failed to get implicit collection name: %v", err)
}
// use transaction ID to create composit bid key
bidKey, err := ctx.GetStub().CreateCompositeKey(bidKeyType, []string{auctionID, txID})
if err != nil {
return fmt.Errorf("failed to create composite key: %v", err)
}
// get bid hash of bid if private bid on the public ledger
bidHash, err := ctx.GetStub().GetPrivateDataHash(collection, bidKey)
if err != nil {
return fmt.Errorf("failed to read bid bash from collection: %v", err)
}
if bidHash == nil {
return fmt.Errorf("bid hash does not exist: %s", bidKey)
}
// get auction from public state
auction, err := s.QueryAuction(ctx, auctionID)
if err != nil {
return fmt.Errorf("failed to get auction from public state %v", err)
}
// Complete a series of three checks before we add the bid to the auction
// check 1: check that the auction is closed. We cannot reveal a
// bid to an open auction
Status := auction.Status
if Status != "closed" {
return errors.New("cannot reveal bid for open or ended auction")
}
// check 2: check that hash of revealed bid matches hash of private bid
// on the public ledger. This checks that the bidder is telling the truth
// about the value of their bid
hash := sha256.New()
hash.Write(transientBidJSON)
calculatedBidJSONHash := hash.Sum(nil)
// verify that the hash of the passed immutable properties matches the on-chain hash
if !bytes.Equal(calculatedBidJSONHash, bidHash) {
return fmt.Errorf("hash %x for bid JSON %s does not match hash in auction: %x",
calculatedBidJSONHash,
transientBidJSON,
bidHash,
)
}
// check 3; check hash of relealed bid matches hash of private bid that was
// added earlier. This ensures that the bid has not changed since it
// was added to the auction
privateBidHashString := auction.PrivateBids[bidKey].Hash
onChainBidHashString := fmt.Sprintf("%x", bidHash)
if privateBidHashString != onChainBidHashString {
return fmt.Errorf("hash %s for bid JSON %s does not match hash in auction: %s, bidder must have changed bid",
privateBidHashString,
transientBidJSON,
onChainBidHashString,
)
}
// we can add the bid to the auction if all checks have passed
type transientBidInput struct {
Price int `json:"price"`
Org string `json:"org"`
Bidder string `json:"bidder"`
}
// unmarshal bid input
var bidInput transientBidInput
err = json.Unmarshal(transientBidJSON, &bidInput)
if err != nil {
return fmt.Errorf("failed to unmarshal JSON: %v", err)
}
// Get ID of submitting client identity
clientID, err := s.GetSubmittingClientIdentity(ctx)
if err != nil {
return fmt.Errorf("failed to get client identity %v", err)
}
// marshal transient parameters and ID and MSPID into bid object
NewBid := FullBid{
Type: bidKeyType,
Price: bidInput.Price,
Org: bidInput.Org,
Bidder: bidInput.Bidder,
}
// check 4: make sure that the transaction is being submitted is the bidder
if bidInput.Bidder != clientID {
return fmt.Errorf("permission denied, client id %v is not the owner of the bid", clientID)
}
auction.RevealedBids[bidKey] = NewBid
newAuctionJSON, _ := json.Marshal(auction)
// put auction with bid added back into state
err = ctx.GetStub().PutState(auctionID, newAuctionJSON)
if err != nil {
return fmt.Errorf("failed to update auction: %v", err)
}
return nil
}
// CloseAuction can be used by the seller to close the auction. This prevents
// bids from being added to the auction, and allows users to reveal their bid
func (s *SmartContract) CloseAuction(ctx contractapi.TransactionContextInterface, auctionID string) error {
// get auction from public state
auction, err := s.QueryAuction(ctx, auctionID)
if err != nil {
return fmt.Errorf("failed to get auction from public state %v", err)
}
// the auction can only be closed by the seller
// get ID of submitting client
clientID, err := s.GetSubmittingClientIdentity(ctx)
if err != nil {
return fmt.Errorf("failed to get client identity %v", err)
}
Seller := auction.Seller
if Seller != clientID {
return fmt.Errorf("auction can only be closed by seller: %v", err)
}
Status := auction.Status
if Status != "open" {
return errors.New("cannot close auction that is not open")
}
auction.Status = string("closed")
closedAuctionJSON, _ := json.Marshal(auction)
err = ctx.GetStub().PutState(auctionID, closedAuctionJSON)
if err != nil {
return fmt.Errorf("failed to close auction: %v", err)
}
return nil
}
// EndAuction both changes the auction status to closed and calculates the winners
// of the auction
func (s *SmartContract) EndAuction(ctx contractapi.TransactionContextInterface, auctionID string) error {
// get auction from public state
auction, err := s.QueryAuction(ctx, auctionID)
if err != nil {
return fmt.Errorf("failed to get auction from public state %v", err)
}
// Check that the auction is being ended by the seller
// get ID of submitting client
clientID, err := s.GetSubmittingClientIdentity(ctx)
if err != nil {
return fmt.Errorf("failed to get client identity %v", err)
}
Seller := auction.Seller
if Seller != clientID {
return fmt.Errorf("auction can only be ended by seller: %v", err)
}
Status := auction.Status
if Status != "closed" {
return errors.New("can only end a closed auction")
}
// get the list of revealed bids
revealedBidMap := auction.RevealedBids
if len(auction.RevealedBids) == 0 {
return fmt.Errorf("no bids have been revealed, cannot end auction: %v", err)
}
// determine the highest bid
for _, bid := range revealedBidMap {
if bid.Price > auction.Price {
auction.Winner = bid.Bidder
auction.Price = bid.Price
}
}
// check if there is a winning bid that has yet to be revealed
err = checkForHigherBid(ctx, auction.Price, auction.RevealedBids, auction.PrivateBids)
if err != nil {
return fmt.Errorf("cannot end auction: %v", err)
}
auction.Status = "ended"
endedAuctionJSON, _ := json.Marshal(auction)
err = ctx.GetStub().PutState(auctionID, endedAuctionJSON)
if err != nil {
return fmt.Errorf("failed to end auction: %v", err)
}
return nil
}

View file

@ -1,137 +0,0 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
package auction
import (
"encoding/json"
"errors"
"fmt"
"github.com/hyperledger/fabric-chaincode-go/v2/shim"
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
)
// QueryAuction allows all members of the channel to read a public auction
func (s *SmartContract) QueryAuction(ctx contractapi.TransactionContextInterface, auctionID string) (*Auction, error) {
auctionJSON, err := ctx.GetStub().GetState(auctionID)
if err != nil {
return nil, fmt.Errorf("failed to get auction object %v: %v", auctionID, err)
}
if auctionJSON == nil {
return nil, errors.New("auction does not exist")
}
var auction *Auction
err = json.Unmarshal(auctionJSON, &auction)
if err != nil {
return nil, err
}
return auction, nil
}
// QueryBid allows the submitter of the bid to read their bid from public state
func (s *SmartContract) QueryBid(ctx contractapi.TransactionContextInterface, auctionID string, txID string) (*FullBid, error) {
err := verifyClientOrgMatchesPeerOrg(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get implicit collection name: %v", err)
}
clientID, err := s.GetSubmittingClientIdentity(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get client identity %v", err)
}
collection, err := getCollectionName(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get implicit collection name: %v", err)
}
bidKey, err := ctx.GetStub().CreateCompositeKey(bidKeyType, []string{auctionID, txID})
if err != nil {
return nil, fmt.Errorf("failed to create composite key: %v", err)
}
bidJSON, err := ctx.GetStub().GetPrivateData(collection, bidKey)
if err != nil {
return nil, fmt.Errorf("failed to get bid %v: %v", bidKey, err)
}
if bidJSON == nil {
return nil, fmt.Errorf("bid %v does not exist", bidKey)
}
var bid *FullBid
err = json.Unmarshal(bidJSON, &bid)
if err != nil {
return nil, err
}
// check that the client querying the bid is the bid owner
if bid.Bidder != clientID {
return nil, fmt.Errorf("permission denied, client id %v is not the owner of the bid", clientID)
}
return bid, nil
}
// checkForHigherBid is an internal function that is used to determine if a winning bid has yet to be revealed
func checkForHigherBid(ctx contractapi.TransactionContextInterface, auctionPrice int, revealedBidders map[string]FullBid, bidders map[string]BidHash) error {
// Get MSP ID of peer org
peerMSPID, err := shim.GetMSPID()
if err != nil {
return fmt.Errorf("failed getting the peer's MSPID: %v", err)
}
var error error
error = nil
for bidKey, privateBid := range bidders {
if _, bidInAuction := revealedBidders[bidKey]; bidInAuction {
// bid is already revealed, no action to take
} else {
collection := "_implicit_org_" + privateBid.Org
if privateBid.Org == peerMSPID {
bidJSON, err := ctx.GetStub().GetPrivateData(collection, bidKey)
if err != nil {
return fmt.Errorf("failed to get bid %v: %v", bidKey, err)
}
if bidJSON == nil {
return fmt.Errorf("bid %v does not exist", bidKey)
}
var bid *FullBid
err = json.Unmarshal(bidJSON, &bid)
if err != nil {
return err
}
if bid.Price > auctionPrice {
error = fmt.Errorf("cannot close auction, bidder has a higher price: %v", err)
}
} else {
Hash, err := ctx.GetStub().GetPrivateDataHash(collection, bidKey)
if err != nil {
return fmt.Errorf("failed to read bid hash from collection: %v", err)
}
if Hash == nil {
return fmt.Errorf("bid hash does not exist: %s", bidKey)
}
}
}
}
return error
}

View file

@ -1,121 +0,0 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
package auction
import (
"encoding/base64"
"fmt"
"github.com/hyperledger/fabric-chaincode-go/v2/pkg/statebased"
"github.com/hyperledger/fabric-chaincode-go/v2/shim"
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
)
func (s *SmartContract) GetSubmittingClientIdentity(ctx contractapi.TransactionContextInterface) (string, error) {
b64ID, err := ctx.GetClientIdentity().GetID()
if err != nil {
return "", fmt.Errorf("failed to read clientID: %v", err)
}
decodeID, err := base64.StdEncoding.DecodeString(b64ID)
if err != nil {
return "", fmt.Errorf("failed to base64 decode clientID: %v", err)
}
return string(decodeID), nil
}
// setAssetStateBasedEndorsement sets the endorsement policy of a new auction
func setAssetStateBasedEndorsement(ctx contractapi.TransactionContextInterface, auctionID string, orgToEndorse string) error {
endorsementPolicy, err := statebased.NewStateEP(nil)
if err != nil {
return err
}
err = endorsementPolicy.AddOrgs(statebased.RoleTypePeer, orgToEndorse)
if err != nil {
return fmt.Errorf("failed to add org to endorsement policy: %v", err)
}
policy, err := endorsementPolicy.Policy()
if err != nil {
return fmt.Errorf("failed to create endorsement policy bytes from org: %v", err)
}
err = ctx.GetStub().SetStateValidationParameter(auctionID, policy)
if err != nil {
return fmt.Errorf("failed to set validation parameter on auction: %v", err)
}
return nil
}
// addAssetStateBasedEndorsement adds a new organization as an endorser of the auction
func addAssetStateBasedEndorsement(ctx contractapi.TransactionContextInterface, auctionID string, orgToEndorse string) error {
endorsementPolicy, err := ctx.GetStub().GetStateValidationParameter(auctionID)
if err != nil {
return err
}
newEndorsementPolicy, err := statebased.NewStateEP(endorsementPolicy)
if err != nil {
return err
}
err = newEndorsementPolicy.AddOrgs(statebased.RoleTypePeer, orgToEndorse)
if err != nil {
return fmt.Errorf("failed to add org to endorsement policy: %v", err)
}
policy, err := newEndorsementPolicy.Policy()
if err != nil {
return fmt.Errorf("failed to create endorsement policy bytes from org: %v", err)
}
err = ctx.GetStub().SetStateValidationParameter(auctionID, policy)
if err != nil {
return fmt.Errorf("failed to set validation parameter on auction: %v", err)
}
return nil
}
// getCollectionName is an internal helper function to get collection of submitting client identity.
func getCollectionName(ctx contractapi.TransactionContextInterface) (string, error) {
// Get the MSP ID of submitting client identity
clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return "", fmt.Errorf("failed to get verified MSPID: %v", err)
}
// Create the collection name
orgCollection := "_implicit_org_" + clientMSPID
return orgCollection, nil
}
// verifyClientOrgMatchesPeerOrg is an internal function used to verify that client org id matches peer org id.
func verifyClientOrgMatchesPeerOrg(ctx contractapi.TransactionContextInterface) error {
clientMSPID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return fmt.Errorf("failed getting the client's MSPID: %v", err)
}
peerMSPID, err := shim.GetMSPID()
if err != nil {
return fmt.Errorf("failed getting the peer's MSPID: %v", err)
}
if clientMSPID != peerMSPID {
return fmt.Errorf("client from org %v is not authorized to read or write private data from an org %v peer", clientMSPID, peerMSPID)
}
return nil
}
func contains(sli []string, str string) bool {
for _, a := range sli {
if a == str {
return true
}
}
return false
}

View file

@ -1,23 +0,0 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"log"
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
"github.com/hyperledger/fabric-samples/auction/chaincode-go/smart-contract"
)
func main() {
auctionSmartContract, err := contractapi.NewChaincode(&auction.SmartContract{})
if err != nil {
log.Panicf("Error creating auction chaincode: %v", err)
}
if err := auctionSmartContract.Start(); err != nil {
log.Panicf("Error starting auction chaincode: %v", err)
}
}

View file

@ -1,276 +0,0 @@
# ERC-1155 Chaincode
This is a sample ERC-1155 chaincode written in Go.
ERC-20 is the standard for fungible tokens. ERC-721 is the standard for non-fungible tokens. ERC-1155 is the standard for multiple tokens (both fungible and non-fungible). [More information about the ERC-1155 standard can be found here.](https://eips.ethereum.org/EIPS/eip-1155)
## Architecture
This implementation aims for high throughput by minimizing key collisions. The balance of accounts is distributed over multiple keys. The token transfers can be batched using the batched versions of functions (e.g. BatchTransferFrom, BalanceOfBatch). Since ERC-1155 is account-based, the interface of the chaincode is account-based. However, since the balances are distributed over multiple keys, the chaincode has a model similar to a UTXO-based chaincode internally.
In this chaincode, one organization has a minter/burner role just like in the [ERC-20 example in this repository](https://github.com/hyperledger/fabric-samples/tree/main/token-erc-20).
## Functions Implemented
The following required functions of ERC-1155 are implemented:
- safeTransferFrom
- safeBatchTransferFrom
- balanceOf
- balanceOfBatch
- setApprovalForAll
- isApprovedForAll
Note: The "safe" prefix is omitted from "TransferFrom" in the implementation because the prefix is related to some issue about backwards compatibility with older smart contracts in Ethereum.
Note: TransferFrom is used to send a single token type between two users. BatchTransferFrom is used to send multiple tokens between two users. So, BatchTransferFrom is a more general form of TransferFrom. Ethereum defined two functions instead of one because this reduces gas costs. Since there is no gas cost in Fabric, implementing these two functions is unnecessary. Nevertheless, both of them are implemented to to conform the ERC-1155 standard.
## Additional Functions Implemented
The following additional functions are also implemented. The following paragraphs give the reasoning behind adding these functions:
- Optional Metadata URI extension:
Defined in ERC-1155 but not required. Allows one to set a URI for tokens and get the URI.
- SetURI
- URI
- Mint/Burn extension:
Although Mint / Burn are not required, they are necessary to change the supply of tokens, create new fungible or non-fungible tokens. In a real implementation, they will be implemented unless the supply of the tokens is fixed beforehand. MintBatch / BurnBatch is only implemented to complement the TransferFrom/BatchTransferFrom. Actually, using only MintBatch and BurnBatch would be enough.
- Mint
- MintBatch
- Burn
- BurnBatch
- Extra/utility functions
- BatchTransferFromMultiRecipient: This is not defined in the standard. We created this function to solve an issue we encountered. It is only required if a person wants to send tokens to multiple persons in a blockchain block. If a person doesn't use this function and create two transactions in a single block, there will be key conflicts because the chaincode will try to decrement the balance of the sender twice in a block and this causes a key conflict in Fabric [just like explained in here](https://github.com/hyperledger/fabric-samples/tree/main/high-throughput). This problem does not exist in Ethereum because, in Ethereum, the transactions are ordered before they are executed.
- BroadcastTokenExistence: Explained in ERC-1155 but it is not required. It is only used if a token minter wants to announce the existence of a token without minting it.
- ClientAccountID: This function is special for Fabric because we do not have wallet addresses in Fabric and users need to know their account ID to transfer tokens.
- ClientAccountBalance: A shorthand for BalanceOf function.
## Example Usage
### Launch test network
Open a command terminal and navigate to the test network directory.
```bash
cd fabric-samples/test-network
```
Clean up the existing network if you have any.
```bash
./network.sh down
```
Start test network
```bash
./network.sh up createChannel -ca
```
### Deploy chaincode
Deploy ERC-1155 chaincode.
```bash
./network.sh deployCC -ccn erc1155 -ccp ../token-erc-1155/chaincode-go/ -ccl go
```
### Register identities
In this example, there are two organizations (org). We will register new identities using the Org1 and Org2 Certificate Authorities (CA's), and then use the CA's to generate each identity's certificate and private key.
We need to set the following environment variables to use the Fabric CA client (and subsequent commands). The first command sets Fabric config path. The second command adds Fabric CLI utilities to path.
```bash
export FABRIC_CFG_PATH=${PWD}/../config/
export PATH=${PWD}/../bin:$PATH
```
The terminal we have been using will represent Org1. We will use the Org1 CA to create a new identity. Set the Fabric CA client home to the MSP of the Org1 CA admin (this identity was generated by the test network script).
```bash
export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org1.example.com/
```
Register `Person1` identity to Org1.
```bash
fabric-ca-client register --caname ca-org1 --id.name person1 --id.secret person1pw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"
```
Generate the identity certificates and MSP folder.
```bash
fabric-ca-client enroll -u https://person1:person1pw@localhost:7054 --caname ca-org1 -M "${PWD}/organizations/peerOrganizations/org1.example.com/users/person1@org1.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"
```
Copy the Node OU configuration file into the identity MSP folder.
```bash
cp "${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org1.example.com/users/person1@org1.example.com/msp/config.yaml"
```
Open a new terminal to represent Org2 and navigate to fabric-samples/test-network. We'll use the Org2 CA to create the Org2 identities. Set the Fabric CA client home to the MSP of the Org2 CA admin.
```bash
cd fabric-samples/test-network
export FABRIC_CFG_PATH=${PWD}/../config/
export PATH=${PWD}/../bin:$PATH
export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org2.example.com/
```
Register `Person2`, `Person3`, `Person4`, `Person5` to Org2.
```bash
fabric-ca-client register --caname ca-org2 --id.name person2 --id.secret person2pw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
fabric-ca-client enroll -u https://person2:person2pw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/users/person2@org2.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
cp "${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org2.example.com/users/person2@org2.example.com/msp/config.yaml"
fabric-ca-client register --caname ca-org2 --id.name person3 --id.secret person3pw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
fabric-ca-client enroll -u https://person3:person3pw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/users/person3@org2.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
cp "${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org2.example.com/users/person3@org2.example.com/msp/config.yaml"
fabric-ca-client register --caname ca-org2 --id.name person4 --id.secret person4pw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
fabric-ca-client enroll -u https://person4:person4pw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/users/person4@org2.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
cp "${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org2.example.com/users/person4@org2.example.com/msp/config.yaml"
fabric-ca-client register --caname ca-org2 --id.name person5 --id.secret person5pw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
fabric-ca-client enroll -u https://person5:person5pw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/users/person5@org2.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
cp "${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org2.example.com/users/person5@org2.example.com/msp/config.yaml"
```
## Initialize the contract
Once we created the identity of the minter we can now initialize the contract.
Note that we need to call the initialize function before being able to use any functions of the contract. Initialize() can be called only once.
Shift back to the Org1 terminal, we'll set the following environment variables to operate the `peer` CLI as the minter identity from Org1.
```bash
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID=Org1MSP
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/person1@org1.example.com/msp
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_ADDRESS=localhost:7051
export TARGET_TLS_OPTIONS=(-o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt")```
We can then invoke the smart contract to initialize it:
```bash
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n erc1155 -c '{"function":"Initialize","Args":["some name", "some symbol"]}'
```
### Get account ID
Now, get client account ID for Person1.
```bash
peer chaincode query -C mychannel -n erc1155 -c '{"function":"ClientAccountID","Args":[]}'
```
```
eDUwOTo6Q049cGVyc29uMSxPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT
```
Client Account ID is a base64-encoded concatenation of the issuer and subject from the client identity's enrolment certificate.
You can decode it with the following command:
```bash
echo "eDUwOTo6Q049cGVyc29uMSxPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT" | base64 --decode
```
```
x509::CN=person1,OU=client,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US
```
Set the client account IDs as environment variables on both of the terminals to make the subsequent commands more readable.
```bash
export P1="eDUwOTo6Q049cGVyc29uMSxPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT"
export P2="eDUwOTo6Q049cGVyc29uMixPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcyLmV4YW1wbGUuY29tLE89b3JnMi5leGFtcGxlLmNvbSxMPUh1cnNsZXksU1Q9SGFtcHNoaXJlLEM9VUs="
export P3="eDUwOTo6Q049cGVyc29uMyxPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcyLmV4YW1wbGUuY29tLE89b3JnMi5leGFtcGxlLmNvbSxMPUh1cnNsZXksU1Q9SGFtcHNoaXJlLEM9VUs="
export P4="eDUwOTo6Q049cGVyc29uNCxPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcyLmV4YW1wbGUuY29tLE89b3JnMi5leGFtcGxlLmNvbSxMPUh1cnNsZXksU1Q9SGFtcHNoaXJlLEM9VUs="
export P5="eDUwOTo6Q049cGVyc29uNSxPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcyLmV4YW1wbGUuY29tLE89b3JnMi5leGFtcGxlLmNvbSxMPUh1cnNsZXksU1Q9SGFtcHNoaXJlLEM9VUs="
```
### Mint tokens
Mint tokens by calling the MintBatch function in order to create 100 token1s, 200 token2s, 300 token3s, 150 token4s, 100 token5s, 100 token6s as Person P1 from organization 1.
```bash
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n erc1155 -c "{\"function\":\"MintBatch\",\"Args\":[\"$P1\",\"[1,2,3,4,5,6]\",\"[100,200,300,150,100,100]\"]}" --waitForEvent
```
Query the tokens of Person1.
```bash
peer chaincode query -C mychannel -n erc1155 -c "{\"function\":\"BalanceOfBatch\",\"Args\":[\"[\\\"$P1\\\",\\\"$P1\\\",\\\"$P1\\\",\\\"$P1\\\",\\\"$P1\\\",\\\"$P1\\\"]\",\"[1,2,3,4,5,6]\"]}"
```
```
[100,200,300,150,100,100]
```
Side note: There may seem too many slashes in the previous command. It double escapes the quotes. One escape is to be able to use quotes in the `-c` argument of the command. The second escape is necessary to pass the account IDs as an array. Quote is needed since the elements of the array are strings.
### Transfer tokens
#### TransferFrom
Send Person P2 six token3s by calling TransferFrom as Person P1.
```bash
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n erc1155 -c "{\"function\":\"TransferFrom\",\"Args\":[\"$P1\",\"$P2\",\"3\",\"6\"]}" --waitForEvent
```
Get the new Balance of Person1 for token3.
```bash
peer chaincode query -C mychannel -n erc1155 -c "{\"function\":\"BalanceOf\",\"Args\":[\"$P1\",\"3\"]}"
```
```
294
```
Switch to the Org2 terminal and set the following environment variables.
```bash
export FABRIC_CFG_PATH=$PWD/../config/
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID=Org2MSP
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/person2@org2.example.com/msp
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_ADDRESS=localhost:9051
export TARGET_TLS_OPTIONS=(-o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt")
```
Get the new balance of Person2 for token3.
```bash
peer chaincode query -C mychannel -n erc1155 -c "{\"function\":\"BalanceOf\",\"Args\":[\"$P2\",\"3\"]}"
```
```
6
```
#### BatchTransferFrom
Switch to the Org1 terminal.
Send Person P2 six token3s, three token4s, and one token2s by calling BatchTransferFrom as Person P1.
```bash
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n erc1155 -c "{\"function\":\"BatchTransferFrom\",\"Args\":[\"$P1\",\"$P2\",\"[3,4,2]\",\"[6,3,1]\"]}" --waitForEvent
```
#### BatchTransferFromMultiReceipent
Call BatchTransferFromMultiReceipent as Person1 in order to send:
- six token5s to person P3,
- six token3s to person P4,
- three token4s to person P2,
- two token2s to person P5,
- and three token6s to person P2.
```bash
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n erc1155 -c "{\"function\":\"BatchTransferFromMultiRecipient\",\"Args\":[\"$P1\",\"[\\\"$P3\\\",\\\"$P4\\\",\\\"$P2\\\",\\\"$P5\\\",\\\"$P2\\\"]\",\"[5,3,4,2,6]\",\"[6,6,3,2,3]\"]}" --waitForEvent
```
### Clean up
When you are finished, you can bring down the test network. This command will bring down the CAs, peers, and ordering node of the network that you created.
```bash
./network.sh down
```
## Acknowledgement
This work has been carried out at Boğaziçi University and has received funding from the European Unions Horizon 2020 Research and Innovation programme under Grant Agreement No. 856632.

File diff suppressed because it is too large Load diff

View file

@ -1,27 +0,0 @@
/*
2021 Baran Kılıç <baran.kilic@boun.edu.tr>
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"erc1155/chaincode"
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
)
func main() {
smartContract := new(chaincode.SmartContract)
cc, err := contractapi.NewChaincode(smartContract)
if err != nil {
panic(err.Error())
}
if err := cc.Start(); err != nil {
panic(err.Error())
}
}

View file

@ -1,32 +0,0 @@
module erc1155
go 1.21
require github.com/hyperledger/fabric-contract-api-go/v2 v2.0.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/gobuffalo/envy v1.10.2 // indirect
github.com/gobuffalo/packd v1.0.2 // indirect
github.com/gobuffalo/packr v1.30.1 // indirect
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0-20240618210511-f7903324a8af // indirect
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/rogpeppe/go-internal v1.12.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
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
google.golang.org/grpc v1.64.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View file

@ -1,132 +0,0 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
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/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/envy v1.10.2 h1:EIi03p9c3yeuRCFPOKcSfajzkLb3hrRjEpHGI8I2Wo4=
github.com/gobuffalo/envy v1.10.2/go.mod h1:qGAGwdvDsaEtPhfBzb3o0SfDea8ByGn9j8bKmVft9z8=
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
github.com/gobuffalo/packd v1.0.2 h1:Yg523YqnOxGIWCp69W12yYBKsoChwI7mtu6ceM9Bwfw=
github.com/gobuffalo/packd v1.0.2/go.mod h1:sUc61tDqGMXON80zpKGp92lDb86Km28jfvX7IAyxFT8=
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
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/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0-20240618210511-f7903324a8af h1:WT4NjX7Uk03GSeH++jF3a0wp4FhybTM86zDPCETvmSk=
github.com/hyperledger/fabric-chaincode-go/v2 v2.0.0-20240618210511-f7903324a8af/go.mod h1:f/ER25FaBepxJugwpLhbD2hLAoZaZEVqkBjOcHjw72Y=
github.com/hyperledger/fabric-contract-api-go/v2 v2.0.0 h1:IDiCGVOBlRd6zpL0Y+f6V7IpBqa4/Z5JAK9SF7a5ea8=
github.com/hyperledger/fabric-contract-api-go/v2 v2.0.0/go.mod h1:pdqhe7ALf4lmXgQdprCyNWYdnCPxgj02Vhf8JF5w8po=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 h1:Xpd6fzG/KjAOHJsq7EQXY2l+qi/y8muxBaY7R6QWABk=
github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3/go.mod h1:2pq0ui6ZWA0cC8J+eCErgnMDCS1kPOEYVY+06ZAK0qE=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
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/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
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/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
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=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -1,466 +0,0 @@
# ERC-20 token scenario
The ERC-20 token smart contract demonstrates how to create and transfer fungible tokens using an account-based model. In an ERC-20 account-based model, there is an account for each participant that holds a balance of tokens.
A mint transaction creates tokens in an account, while a transfer transaction debits the caller's account and credits another account.
In this sample it is assumed that only one organization (played by Org1) is in a central banker role and can mint new tokens into their account, while any organization can transfer tokens from their account to a recipient's account.
Accounts could be defined at the organization level or client identity level. In this sample accounts are defined at the client identity level, where every authorized client with an enrollment certificate from their organization implicitly has an account ID that matches their client ID.
The client ID is simply a base64-encoded concatenation of the issuer and subject from the client identity's enrollment certificate. The client ID can therefore be considered the account ID that is used as the payment address of a recipient.
In this tutorial, you will mint and transfer tokens as follows:
- A member of Org1 uses the `Mint` function to create new tokens into their account. The `Mint` smart contract function reads the certificate information of the client identity that submitted the transaction using the `GetClientIdentity.GetID()` API and credits the account associated with the client ID with the requested number of tokens.
- The same minter client will then use the `Transfer` function to transfer the requested number of tokens to the recipient's account. It is assumed that the recipient has provided their account ID to the transfer caller out of band. The recipient can then transfer tokens to other registered users in the same fashion.
## Bring up the test network
You can run the ERC-20 token transfer scenario using the Fabric test network. Open a command terminal and navigate to the test network directory in your local clone of the `fabric-samples`. We will operate from the `test-network` directory for the remainder of the tutorial.
```
cd fabric-samples/test-network
```
Run the following command to start the test network:
```
./network.sh up createChannel -ca
```
The test network is deployed with two peer organizations. The `createChannel` flag deploys the network with a single channel named `mychannel` with Org1 and Org2 as channel members.
The -ca flag is used to deploy the network using certificate authorities. This allows you to use each organization's CA to register and enroll new users for this tutorial.
## Deploy the smart contract to the channel
You can use the test network script to deploy the ERC-20 token contract to the channel that was just created. Deploy the smart contract to `mychannel` using the following command:
**For a Go Contract:**
```
./network.sh deployCC -ccn token_erc20 -ccp ../token-erc-20/chaincode-go/ -ccl go
```
**For a Java Contract:**
```
./network.sh deployCC -ccn token_erc20 -ccp ../token-erc-20/chaincode-java/ -ccl java
```
**For a JavaScript Contract:**
```
./network.sh deployCC -ccn token_erc20 -ccp ../token-erc-20/chaincode-javascript/ -ccl javascript
```
The above commands deploys the chaincode with short name `token_erc20`. The smart contract will use the default endorsement policy of majority of channel members.
Since the channel has two members, this implies that we'll need to get peer endorsements from 2 out of the 2 channel members.
Now you are ready to call the deployed smart contract via peer CLI calls. But let's first create the client identities for our scenario.
## Register identities
The smart contract supports accounts owned by individual client identities from organizations that are members of the channel. In our scenario, the minter of the tokens will be a member of Org1, while the recipient will belong to Org2. To highlight the connection between the `GetClientIdentity().GetID()` API and the information within a user's certificate, we will register two new identities using the Org1 and Org2 Certificate Authorities (CA's), and then use the CA's to generate each identity's certificate and private key.
First, we need to set the following environment variables to use the Fabric CA client (and subsequent commands).
```
export PATH=${PWD}/../bin:${PWD}:$PATH
export FABRIC_CFG_PATH=$PWD/../config/
```
The terminal we have been using will represent Org1. We will use the Org1 CA to create the minter identity. Set the Fabric CA client home to the MSP of the Org1 CA admin (this identity was generated by the test network script):
```
export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org1.example.com/
```
You can register a new minter client identity using the `fabric-ca-client` tool:
```
fabric-ca-client register --caname ca-org1 --id.name minter --id.secret minterpw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"
```
You can now generate the identity certificates and MSP folder by providing the minter's enroll name and secret to the enroll command:
```
fabric-ca-client enroll -u https://minter:minterpw@localhost:7054 --caname ca-org1 -M "${PWD}/organizations/peerOrganizations/org1.example.com/users/minter@org1.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"
```
Run the command below to copy the Node OU configuration file into the minter identity MSP folder.
```
cp "${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org1.example.com/users/minter@org1.example.com/msp/config.yaml"
```
Open a new terminal to represent Org2 and navigate to fabric-samples/test-network. We'll use the Org2 CA to create the Org2 recipient identity. Set the Fabric CA client home to the MSP of the Org2 CA admin:
```
cd fabric-samples/test-network
export PATH=${PWD}/../bin:${PWD}:$PATH
export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org2.example.com/
```
You can register a recipient client identity using the `fabric-ca-client` tool:
```
fabric-ca-client register --caname ca-org2 --id.name recipient --id.secret recipientpw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
```
We can now enroll to generate the recipient's identity certificates and MSP folder:
```
fabric-ca-client enroll -u https://recipient:recipientpw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/users/recipient@org2.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
```
Run the command below to copy the Node OU configuration file into the recipient identity MSP folder.
```
cp "${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org2.example.com/users/recipient@org2.example.com/msp/config.yaml"
```
## Initialize the contract
Once we created the identity of the minter we can now initialize the contract.
Note that we need to call the initialize function before being able to use any functions of the contract. Initialize() can be called only once.
Shift back to the Org1 terminal, we'll set the following environment variables to operate the `peer` CLI as the minter identity from Org1.
```
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID=Org1MSP
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/minter@org1.example.com/msp
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_ADDRESS=localhost:7051
export TARGET_TLS_OPTIONS=(-o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt")
```
The last environment variable above will be utilized within the CLI invoke commands to set the target peers for endorsement, and the target ordering service endpoint and TLS options.
We can then invoke the smart contract to initilize it
```
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n token_erc20 -c '{"function":"Initialize","Args":["some name", "some symbol", "2"]}'
```
## Mint some tokens
Now that we have initialized the contract and created the identity of the minter, we can invoke the smart contract to mint some tokens.
We can then invoke the smart contract to mint 5000 tokens:
```
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n token_erc20 -c '{"function":"Mint","Args":["5000"]}'
```
The mint function validated that the client is a member of the minter organization, and then credited the minter client's account with 5000 tokens. We can check the minter client's account balance by calling the `ClientAccountBalance` function.
```
peer chaincode query -C mychannel -n token_erc20 -c '{"function":"ClientAccountBalance","Args":[]}'
```
The function queries the balance of the account associated with the minter client ID and returns:
```
5000
```
## Transfer tokens
The minter intends to transfer 100 tokens to the Org2 recipient, but first the Org2 recipient needs to provide their own account ID as the payment address.
A client can derive their account ID from their own public certificate, but to be sure the account ID is accurate, the contract has a `ClientAccountID` utility function that simply looks at the callers certificate and returns the calling client's ID, which will be used as the account ID.
Let's prepare the Org2 terminal by setting the environment variables for the Org2 recipient user.
```
export FABRIC_CFG_PATH=$PWD/../config/
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID=Org2MSP
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/recipient@org2.example.com/msp
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_ADDRESS=localhost:9051
```
Using the Org2 terminal, the Org2 recipient user can retrieve their own account ID:
```
peer chaincode query -C mychannel -n token_erc20 -c '{"function":"ClientAccountID","Args":[]}'
```
**For a Go Contract:**
The function returns of recipient's account ID:
```
eDUwOTo6Q049cmVjaXBpZW50LE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzIuZXhhbXBsZS5jb20sTz1vcmcyLmV4YW1wbGUuY29tLEw9SHVyc2xleSxTVD1IYW1wc2hpcmUsQz1VSw==
```
Let's base64 decode the account ID to make sure it represents the Org2 recipient user:
```
echo eDUwOTo6Q049cmVjaXBpZW50LE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzIuZXhhbXBsZS5jb20sTz1vcmcyLmV4YW1wbGUuY29tLEw9SHVyc2xleSxTVD1IYW1wc2hpcmUsQz1VSw== | base64 --decode
```
The result shows that the subject and issuer is indeed the recipient user from Org2:
```
x509::CN=recipient,OU=client,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK
```
**For a Java Contract**
The function returns of recipient's client ID.
```
x509::CN=recipient, OU=client, O=Hyperledger, ST=North Carolina, C=US::CN=ca.org2.example.com, O=org2.example.com, L=Hursley, ST=Hampshire, C=UK
```
**For a JavaScript Contract:**
The function returns of recipient's client ID.
The result shows that the subject and issuer is indeed the recipient user from Org2:
```
x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=recipient::/C=UK/ST=Hampshire/L=Hursley/O=org2.example.com/CN=ca.org2.example.com
```
After the Org2 recipient provides their account ID to the minter, the minter can initiate a transfer from their account to the recipient's account.
Back in the Org1 terminal, request the transfer of 100 tokens to the recipient account:
**For a Go Contract:**
```
export RECIPIENT="eDUwOTo6Q049cmVjaXBpZW50LE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzIuZXhhbXBsZS5jb20sTz1vcmcyLmV4YW1wbGUuY29tLEw9SHVyc2xleSxTVD1IYW1wc2hpcmUsQz1VSw=="
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n token_erc20 -c '{"function":"Transfer","Args":[ "'"$RECIPIENT"'","100"]}'
```
***For a Java Contract**
```
export RECIPIENT="x509::CN=recipient, OU=client, O=Hyperledger, ST=North Carolina, C=US::CN=ca.org2.example.com, O=org2.example.com, L=Hursley, ST=Hampshire, C=UK"
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n token_erc20 -c '{"function":"Transfer","Args":[ "'"$RECIPIENT"'","100"]}'
```
**For a JavaScript Contract:**
```
export RECIPIENT="x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=recipient::/C=UK/ST=Hampshire/L=Hursley/O=org2.example.com/CN=ca.org2.example.com"
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n token_erc20 -c '{"function":"Transfer","Args":[ "'"$RECIPIENT"'","100"]}'
```
The `Transfer` function validates that the account associated with the calling client ID has sufficient funds for the transfer.
It will then debit the caller's account and credit the recipient's account. Note that the sample contract will automatically create an account with zero balance for the recipient, if one does not yet exist.
While still in the Org1 terminal, let's request the minter's account balance again:
```
peer chaincode query -C mychannel -n token_erc20 -c '{"function":"ClientAccountBalance","Args":[]}'
```
The function queries the balance of the account associated with the minter client ID and returns:
```
4900
```
And then using the Org2 terminal, let's request the recipient's balance:
```
peer chaincode query -C mychannel -n token_erc20 -c '{"function":"ClientAccountBalance","Args":[]}'
```
The function queries the balance of the account associated with the recipient client ID and returns:
```
100
```
Congratulations, you've transferred 100 tokens! The Org2 recipient can now transfer tokens to other registered users in the same manner.
## 3rd party transfers (TransferFrom)
This sample has another ERC-20 transfer method called `TransferFrom`, which allows an approved 3rd party spender to transfer fungible tokens on behalf of the account owner. This scenario demonstrates how to approve the spender and transfer fungible tokens.
In this scenario, you will approve the spender and transfer tokens as follows:
- A minter has already created tokens according to the scenario above.
- The same minter client uses the `Approve` function to set the allowance of tokens a spender client can transfer on behalf of the minter. It is assumed that the spender has provided their client ID to the `Approve` caller out of band.
- The spender client will then use the `TransferFrom` function to transfer the requested number of tokens to the recipient's account on behalf of the minter. It is assumed that the recipient has provided their client ID to the `TransferFrom` caller out of band.
## Register identity for 3rd party spender
You have already brought up the network and deployed the smart contract to the channel. We will use the same network and smart contract.
We will use the Org1 CA to create the spender identity.
Back in the Org1 terminal, you can register a new spender client identity using the `fabric-ca-client` tool:
```
fabric-ca-client register --caname ca-org1 --id.name spender --id.secret spenderpw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"
```
You can now generate the identity certificates and MSP folder by providing the spender's enroll name and secret to the enroll command:
```
fabric-ca-client enroll -u https://spender:spenderpw@localhost:7054 --caname ca-org1 -M "${PWD}/organizations/peerOrganizations/org1.example.com/users/spender@org1.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"
```
Run the command below to copy the Node OU configuration file into the spender identity MSP folder.
```
cp "${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org1.example.com/users/spender@org1.example.com/msp/config.yaml"
```
## Approve a spender
The minter intends to approve 500 tokens to be transferred by the spender, but first the spender needs to provide their own client ID as the payment address.
Open a 3rd terminal to represent the spender in Org1 and navigate to fabric-samples/test-network. Set the the environment variables for the Org1 spender user.
```
export PATH=${PWD}/../bin:${PWD}:$PATH
export FABRIC_CFG_PATH=$PWD/../config/
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID=Org1MSP
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/spender@org1.example.com/msp
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_ADDRESS=localhost:7051
export TARGET_TLS_OPTIONS=(-o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt")
```
Now the Org1 spender can retrieve their own client ID:
```
peer chaincode query -C mychannel -n token_erc20 -c '{"function":"ClientAccountID","Args":[]}'
```
**For a Go Contract:**
The function returns of spender's account ID:
```
eDUwOTo6Q049c3BlbmRlcixPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT
```
**For a Java Contract**
The function returns of spenders's client ID.
```
x509::CN=spender, OU=client, O=Hyperledger, ST=North Carolina, C=US::CN=ca.org1.example.com, O=org1.example.com, L=Durham, ST=North Carolina, C=US
```
**For a JavaScript Contract:**
The function returns of spender's client ID.
The result shows that the subject and issuer is indeed the recipient user from Org2:
```
x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=spender::/C=US/ST=North Carolina/L=Durham/O=org1.example.com/CN=ca.org1.example.com
```
After the Org1 spender provides their client ID to the minter, the minter can approve a spender.
Back in the Org1 minter terminal, request the approval of 500 tokens to be withdrawn by the spender.
**For a Go Contract:**
```
export SPENDER="eDUwOTo6Q049c3BlbmRlcixPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT"
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n token_erc20 -c '{"function":"Approve","Args":[ "'"$SPENDER"'","500"]}'
```
**For a Java Contract:**
```
export SPENDER="x509::CN=spender, OU=client, O=Hyperledger, ST=North Carolina, C=US::CN=ca.org1.example.com, O=org1.example.com, L=Durham, ST=North Carolina, C=US"
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n token_erc20 -c '{"function":"Approve","Args":["'"$SPENDER"'", "500"]}'
```
**For a JavaScript Contract:**
```
export SPENDER="x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=spender::/C=US/ST=North Carolina/L=Durham/O=org1.example.com/CN=ca.org1.example.com"
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n token_erc20 -c '{"function":"Approve","Args":["'"$SPENDER"'", "500"]}'
```
The approve function specified that the spender client can transfer 500 tokens on behalf of the minter. We can check the spender client's allowance from the minter by calling the `allowance` function.
Let's request the spender's allowance from the Org1 minter terminal.
**For a Go Contract:**
```
export MINTER="eDUwOTo6Q049bWludGVyLE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzEuZXhhbXBsZS5jb20sTz1vcmcxLmV4YW1wbGUuY29tLEw9RHVyaGFtLFNUPU5vcnRoIENhcm9saW5hLEM9VVM="
peer chaincode query -C mychannel -n token_erc20 -c '{"function":"Allowance","Args":["'"$MINTER"'", "'"$SPENDER"'"]}'
```
**For a Java Contract:**
```
export MINTER="x509::CN=minter, OU=client, O=Hyperledger, ST=North Carolina, C=US::CN=ca.org1.example.com, O=org1.example.com, L=Durham, ST=North Carolina, C=US"
peer chaincode query -C mychannel -n token_erc20 -c '{"function":"Allowance","Args":["'"$MINTER"'", "'"$SPENDER"'"]}'
```
**For a JavaScript Contract:**
```
export MINTER="x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=minter::/C=US/ST=North Carolina/L=Durham/O=org1.example.com/CN=ca.org1.example.com"
peer chaincode query -C mychannel -n token_erc20 -c '{"function":"Allowance","Args":["'"$MINTER"'", "'"$SPENDER"'"]}'
```
The function queries the allowance associated with the spender client ID and returns:
```
500
```
## TransferFrom tokens
The spender intends to transfer 100 tokens to the Org2 recipient on behalf of the minter. The spender has already got the minter client Id and the recipient client ID.
Back in the 3rd terminal, request the transfer of 100 tokens to the recipient account.
**For a Go Contract:**
```
export MINTER="eDUwOTo6Q049bWludGVyLE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzEuZXhhbXBsZS5jb20sTz1vcmcxLmV4YW1wbGUuY29tLEw9RHVyaGFtLFNUPU5vcnRoIENhcm9saW5hLEM9VVM="
export RECIPIENT="eDUwOTo6Q049cmVjaXBpZW50LE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzIuZXhhbXBsZS5jb20sTz1vcmcyLmV4YW1wbGUuY29tLEw9SHVyc2xleSxTVD1IYW1wc2hpcmUsQz1VSw=="
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n token_erc20 -c '{"function":"TransferFrom","Args":[ "'"$MINTER"'", "'"$RECIPIENT"'", "100"]}'
```
**For a Java Contract:**
```
export MINTER="x509::CN=minter, OU=client, O=Hyperledger, ST=North Carolina, C=US::CN=ca.org1.example.com, O=org1.example.com, L=Durham, ST=North Carolina, C=US"
export RECIPIENT="x509::CN=recipient, OU=client, O=Hyperledger, ST=North Carolina, C=US::CN=ca.org2.example.com, O=org2.example.com, L=Hursley, ST=Hampshire, C=UK"
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n token_erc20 -c '{"function":"TransferFrom","Args":[ "'"$MINTER"'", "'"$RECIPIENT"'", "100"]}'
```
**For a JavaScript Contract:**
```
export MINTER="x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=minter::/C=US/ST=North Carolina/L=Durham/O=org1.example.com/CN=ca.org1.example.com"
export RECIPIENT="x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=recipient::/C=UK/ST=Hampshire/L=Hursley/O=org2.example.com/CN=ca.org2.example.com"
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n token_erc20 -c '{"function":"TransferFrom","Args":[ "'"$MINTER"'", "'"$RECIPIENT"'", "100"]}'
```
The `TransferFrom` function has three args: sender, recipient, amount. The function validates that the account associated with the sender has sufficient funds for the transfer. The function also validates if the allowance associated with the calling client ID exceeds funds to be transferred.
It will then debit the sender's account and credit the recipient's account. It will also decrease the spender's allowance approved by the minter. Note that the sample contract will automatically create an account with zero balance for the recipient, if one does not yet exist.
While still in the 3rd terminal for the spender, let's request the minter's account balance again:
```
peer chaincode query -C mychannel -n token_erc20 -c '{"function":"BalanceOf","Args":["'"$MINTER"'"]}'
```
The function queries the balance of the account associated with the minter client ID and returns:
```
4800
```
While still in the 3rd terminal for the spender, let's request the spender's allowance from the minter again.
**For a Go Contract:**
```
export SPENDER="eDUwOTo6Q049c3BlbmRlcixPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT"
peer chaincode query -C mychannel -n token_erc20 -c '{"function":"Allowance","Args":["'"$MINTER"'", "'"$SPENDER"'"]}'
```
**For a Java Contract:**
```
export SPENDER="x509::CN=spender, OU=client, O=Hyperledger, ST=North Carolina, C=US::CN=ca.org1.example.com, O=org1.example.com, L=Durham, ST=North Carolina, C=US"
peer chaincode query -C mychannel -n token_erc20 -c '{"function":"Allowance","Args":["'"$MINTER"'", "'"$SPENDER"'"]}'
```
**For a JavaScript Contract:**
```
export SPENDER="x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=spender::/C=US/ST=North Carolina/L=Durham/O=org1.example.com/CN=ca.org1.example.com"
peer chaincode query -C mychannel -n token_erc20 -c '{"function":"Allowance","Args":["'"$MINTER"'", "'"$SPENDER"'"]}'
```
The function queries the allowance associated with the spender client ID and returns:
```
400
```
And then using the Org2 terminal, let's request the recipient's balance:
```
peer chaincode query -C mychannel -n token_erc20 -c '{"function":"ClientAccountBalance","Args":[]}'
```
The function queries the balance of the account associated with the recipient client ID and returns:
```
200
```
Congratulations, you've transferred 100 tokens! The Org2 recipient can now transfer tokens to other registered users in the same manner.
## Clean up
When you are finished, you can bring down the test network. The command will remove all the nodes of the test network, and delete any ledger data that you created:
```
./network.sh down
```
## Contract extension ideas
You can extend the basic ERC-20 account-based token sample to meet other requirements. For example:
* Rather than using the default 'majority' endorsement policy, you could set the endorsement policy to a subset of organizations that represent trust anchors for the contract execution.
* You could also require that accounts get setup before use, and apply state-based endorsement for each account key that has been created. For example on an Org1 account, set state-based endorsement policy to be Org1 and the central banker (or some other trust anchor). And on an Org2 account, set state-based endorsement policy to be Org2 and the central banker (or some other trust anchor). Then to transfer tokens from an Org1 account to an Org2 account, you would require endorsements from Org1, Org2, and the central banker (or some other trust anchor).
* You could utilize anonymous addresses for accounts based on private-public key pairs, instead of accounts keyed by the client ID. In order to spend the tokens, the client would have to sign the transfer input as proof that they own the address private key, which the contract would then validate, similar to the Ethereum model in the permissionless blockchain space. However, in a permissioned blockchain such as Fabric, only registered clients are authorized to participate. Furthermore, if you don't want to leak the registered client identity associated with each account, the clients could be registered using an Identity Mixer MSP, so that the client itself is also anonymous in each of the token transactions.

Some files were not shown because too many files have changed in this diff Show more