mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-17 07:25:10 +00:00
Create auction sample (#355)
Signed-off-by: NIKHIL E GUPTA <negupta@us.ibm.com> Co-authored-by: NIKHIL E GUPTA <negupta@us.ibm.com>
This commit is contained in:
parent
981893ab4d
commit
bf1d9fc667
19 changed files with 2814 additions and 0 deletions
423
auction/README.md
Normal file
423
auction/README.md
Normal file
|
|
@ -0,0 +1,423 @@
|
|||
## Auction sample
|
||||
|
||||
The auction sample uses Hyperledger Fabric to run an auction where bids kept are private from other bidders. Instead of displaying the full bid on the public ledger, buyers can only see hashes of other bids while bidding is underway. This prevents potential buyers from changing their bids in response to bids that have already been submitted. After the bidding period ends, participants can 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.
|
||||
|
||||
A user that wants to sell one or more items 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, potential buyers can add new bids to the auction. The full bids of each buyer are stored in the implicit private data collection 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 be endorsed by a peer of the bidder's 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, the 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 winners from the set of revealed bids. All organizations participating in the auction calculate the price that clears the auction and the winning set of bids. The seller can end the auction only if all bidding organizations endorse the same winners 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 as the one 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 sell a valuable painting. Four potential buyers from two different organizations will submit bids to purchase the painting. You can also use the auction smart contract implement a [Dutch auction](https://en.wikipedia.org/wiki/Dutch_auction) for multiple items of the same type. To run an auction that sells multiple items, see [Running a Dutch auction](dutch-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/chaincode-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/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 for the 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, the item to be sold, and the quantity to be sold to create the auction:
|
||||
```
|
||||
node createAuction.js org1 seller auction1 painting 1
|
||||
```
|
||||
|
||||
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": "eDUwOTo6Q049c2VsbGVyLE9VPWNsaWVudCtPVT1vcmcxK09VPWRlcGFydG1lbnQxOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT",
|
||||
"quantity": 1,
|
||||
"organizations": [
|
||||
"Org1MSP"
|
||||
],
|
||||
"privateBids": {},
|
||||
"revealedBids": {},
|
||||
"winners": [],
|
||||
"price": 0,
|
||||
"status": "open"
|
||||
}
|
||||
```
|
||||
The smart contract uses the `GetClientIdentity().GetID()` API to read identity that creates the auction and defines that identity as the auction `"seller"`. You can see the seller information by decoding the `"seller"` string out of base64 format:
|
||||
|
||||
```
|
||||
echo eDUwOTo6Q049c2VsbGVyLE9VPWNsaWVudCtPVT1vcmcxK09VPWRlcGFydG1lbnQxOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT | base64 --decode
|
||||
```
|
||||
|
||||
The result is the name and issuer of the seller's certificate:
|
||||
```
|
||||
x509::CN=org1admin,OU=admin,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=USn
|
||||
```
|
||||
|
||||
## 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 auction1 1 800
|
||||
```
|
||||
|
||||
The application will query the bid after it is created:
|
||||
```
|
||||
*** Result: Bid: {
|
||||
"objectType": "bid",
|
||||
"quantity": 1,
|
||||
"price": 800,
|
||||
"org": "Org1MSP",
|
||||
"buyer": "eDUwOTo6Q049YmlkZGVyMSxPVT1jbGllbnQrT1U9b3JnMStPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMS5leGFtcGxlLmNvbSxPPW9yZzEuZXhhbXBsZS5jb20sTD1EdXJoYW0sU1Q9Tm9ydGggQ2Fyb2xpbmEsQz1VUw=="
|
||||
}
|
||||
```
|
||||
|
||||
The bid is stored in the Org1 implicit data collection. The `"buyer"` parameter is the information from the certificate of the user that created the bid. Only this identity will be able to 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: 68d3de99032700d92b2d753c79d5aff2ca79378b169efb6a70ca3e0ea43acb1b
|
||||
```
|
||||
|
||||
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=68d3de99032700d92b2d753c79d5aff2ca79378b169efb6a70ca3e0ea43acb1b
|
||||
```
|
||||
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": "painting",
|
||||
"seller": "eDUwOTo6Q049c2VsbGVyLE9VPWNsaWVudCtPVT1vcmcxK09VPWRlcGFydG1lbnQxOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT",
|
||||
"quantity": 1,
|
||||
"organizations": [
|
||||
"Org1MSP"
|
||||
],
|
||||
"privateBids": {
|
||||
"\u0000bid\u0000auction1\u000003382872f8f9dc94f211385d3d127f155e22de1bee8e112dcb90beb3e78f2722\u0000": {
|
||||
"org": "Org1MSP",
|
||||
"hash": "9345d28bb40b7026cc9e14743147356ae1cac99ced8190fe72c9be69fbfc4b71"
|
||||
}
|
||||
},
|
||||
"revealedBids": {},
|
||||
"winners": [],
|
||||
"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 auction1 1 500
|
||||
```
|
||||
|
||||
Save the Bid ID returned by the application:
|
||||
```
|
||||
export BIDDER2_BID_ID=77dc57876a1bc5cb798863f07eaab6f041608e55c60900b2cbcf0acc7723e8ec
|
||||
```
|
||||
|
||||
Submit bidder2's bid to the auction:
|
||||
```
|
||||
node submitBid.js org1 bidder2 auction1 $BIDDER2_BID_ID
|
||||
```
|
||||
|
||||
### Bid as bidder3 from Org2
|
||||
|
||||
Bidder3 will bid 700 dollars for the painting:
|
||||
```
|
||||
node bid.js org2 bidder3 auction1 1 700
|
||||
```
|
||||
|
||||
Save the Bid ID returned by the application:
|
||||
```
|
||||
export BIDDER3_BID_ID=7e2d0c33d0ff1030d855e5fb76f2a4eb30589549b4cb6e581da17d3705cbf77e
|
||||
```
|
||||
|
||||
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": "painting",
|
||||
"seller": "eDUwOTo6Q049c2VsbGVyLE9VPWNsaWVudCtPVT1vcmcxK09VPWRlcGFydG1lbnQxOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT",
|
||||
"quantity": 1,
|
||||
"organizations": [
|
||||
"Org1MSP",
|
||||
"Org2MSP"
|
||||
],
|
||||
"privateBids": {
|
||||
"\u0000bid\u0000auction1\u000003382872f8f9dc94f211385d3d127f155e22de1bee8e112dcb90beb3e78f2722\u0000": {
|
||||
"org": "Org1MSP",
|
||||
"hash": "9345d28bb40b7026cc9e14743147356ae1cac99ced8190fe72c9be69fbfc4b71"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u0000347e08fbbf766a0f3678c3c6e05b0613026fa4d4619ad80869773938ed893f8d\u0000": {
|
||||
"org": "Org2MSP",
|
||||
"hash": "ecaf72ecd6daf27889434491b0e5580c94bd682d8b0283c142b085d51fcf1f69"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u0000e624a5f04fb8878e0d20bf10649f06113771eba6ea4c58f46d17264d1b492a46\u0000": {
|
||||
"org": "Org1MSP",
|
||||
"hash": "8434bfe683c4436484176e6c9b21a5d684c0710fce9fd2506af4e05bb0f8a3ea"
|
||||
}
|
||||
},
|
||||
"revealedBids": {},
|
||||
"winners": [],
|
||||
"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 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 the painting for 900 dollars:
|
||||
```
|
||||
node bid.js org2 bidder4 auction1 1 900
|
||||
```
|
||||
|
||||
Save the Bid ID returned by the application:
|
||||
```
|
||||
export BIDDER4_BID_ID=083478f1af2ba391a5b8d7c590cfb790aedf11578d64ebc4e5efe793555ff212
|
||||
```
|
||||
|
||||
Add bidder4's bid to the auction:
|
||||
```
|
||||
node submitBid.js org2 bidder4 auction1 $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 auction1
|
||||
```
|
||||
|
||||
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 auction1 $BIDDER1_BID_ID
|
||||
```
|
||||
|
||||
The full bid details, including the quantity and price, are now visible:
|
||||
```
|
||||
*** Result: Auction: {
|
||||
"objectType": "auction",
|
||||
"item": "painting",
|
||||
"seller": "eDUwOTo6Q049c2VsbGVyLE9VPWNsaWVudCtPVT1vcmcxK09VPWRlcGFydG1lbnQxOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT",
|
||||
"quantity": 100,
|
||||
"organizations": [
|
||||
"Org1MSP",
|
||||
"Org2MSP"
|
||||
],
|
||||
"privateBids": {
|
||||
"\u0000bid\u0000auction1\u00002fcaee344b361a95701dc03ca4417c6c158845f9e4be3b2555b17c2b691c5ec6\u0000": {
|
||||
"org": "Org2MSP",
|
||||
"hash": "d6f661d8b664244ce55065edc2fd95a982221888bb19afdad931b185e187ed4f"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u000083d529d37ea3518c67135fa8b9bf0bff33053bed0e2f38cb6a7ff75882efcf95\u0000": {
|
||||
"org": "Org2MSP",
|
||||
"hash": "4446c7eb0e2d64165a916ee996348a18716f4c97e632d58d5a8c20eeec5a9238"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u00009e445520af04c8a3c05b484408f0406c9b09503903a9ed9b093f5d894f83dfea\u0000": {
|
||||
"org": "Org1MSP",
|
||||
"hash": "584dbad2269a44afb42bdbc7a7a4c08a7cd50deece1eb3fc38d4e49b9342f270"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u0000f3304fd81664f304706ff39b5a978af94bc3e055e5bf32aed1c25b35df24c136\u0000": {
|
||||
"org": "Org1MSP",
|
||||
"hash": "bbcd0c7c376e6681a76d8c5482c97f8bdcda55c90c5478100c3aef17815c4fd3"
|
||||
}
|
||||
},
|
||||
"revealedBids": {
|
||||
"\u0000bid\u0000auction1\u00009e445520af04c8a3c05b484408f0406c9b09503903a9ed9b093f5d894f83dfea\u0000": {
|
||||
"objectType": "bid",
|
||||
"quantity": 1,
|
||||
"price": 800,
|
||||
"org": "Org1MSP",
|
||||
"buyer": "eDUwOTo6Q049YmlkZGVyMSxPVT1jbGllbnQrT1U9b3JnMStPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMS5leGFtcGxlLmNvbSxPPW9yZzEuZXhhbXBsZS5jb20sTD1EdXJoYW0sU1Q9Tm9ydGggQ2Fyb2xpbmEsQz1VUw=="
|
||||
}
|
||||
},
|
||||
"winners": [],
|
||||
"price": 0,
|
||||
"status": "closed"
|
||||
}
|
||||
```
|
||||
|
||||
Bidder3 from Org2 will also reveal their bid:
|
||||
```
|
||||
node revealBid.js org2 bidder3 auction1 $BIDDER3_BID_ID
|
||||
```
|
||||
|
||||
If the action ended now, Bidder1 would win the auction. Let's try to end the auction using the seller identity and see what happens.
|
||||
|
||||
```
|
||||
node endAuction.js org1 seller auction1
|
||||
```
|
||||
|
||||
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 the Org2 implicit data collection to look for a winning bid that has not yet been revealed. Because Bidder4 created a bid that is above the winning price, the Org2 peer refuses to endorse the transaction that ends the auction.
|
||||
|
||||
Before we can end the auction, we need to reveal the bid from bidder4.
|
||||
```
|
||||
node revealBid.js org2 bidder4 auction1 $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 auction1
|
||||
```
|
||||
|
||||
The transaction was successfully endorsed by both Org1 and Org2, who both calculated the same price and winner of the auction.
|
||||
```
|
||||
*** Result: Auction: {
|
||||
"objectType": "auction",
|
||||
"item": "painting",
|
||||
"seller": "eDUwOTo6Q049c2VsbGVyLE9VPWNsaWVudCtPVT1vcmcxK09VPWRlcGFydG1lbnQxOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT",
|
||||
"quantity": 1,
|
||||
"organizations": [
|
||||
"Org1MSP",
|
||||
"Org2MSP"
|
||||
],
|
||||
"privateBids": {
|
||||
"\u0000bid\u0000auction1\u000003382872f8f9dc94f211385d3d127f155e22de1bee8e112dcb90beb3e78f2722\u0000": {
|
||||
"org": "Org1MSP",
|
||||
"hash": "9345d28bb40b7026cc9e14743147356ae1cac99ced8190fe72c9be69fbfc4b71"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u0000347e08fbbf766a0f3678c3c6e05b0613026fa4d4619ad80869773938ed893f8d\u0000": {
|
||||
"org": "Org2MSP",
|
||||
"hash": "ecaf72ecd6daf27889434491b0e5580c94bd682d8b0283c142b085d51fcf1f69"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u0000e624a5f04fb8878e0d20bf10649f06113771eba6ea4c58f46d17264d1b492a46\u0000": {
|
||||
"org": "Org1MSP",
|
||||
"hash": "8434bfe683c4436484176e6c9b21a5d684c0710fce9fd2506af4e05bb0f8a3ea"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u0000f67eed2c00812d02482d40a652d353c505f3161cd6837077443ee2f425413f5c\u0000": {
|
||||
"org": "Org2MSP",
|
||||
"hash": "0c49d016c894b5623bc0be211409ebb0561c272c65f9faf2959002b0168b238c"
|
||||
}
|
||||
},
|
||||
"revealedBids": {
|
||||
"\u0000bid\u0000auction1\u000003382872f8f9dc94f211385d3d127f155e22de1bee8e112dcb90beb3e78f2722\u0000": {
|
||||
"objectType": "bid",
|
||||
"quantity": 1,
|
||||
"price": 800,
|
||||
"org": "Org1MSP",
|
||||
"buyer": "eDUwOTo6Q049YmlkZGVyMSxPVT1jbGllbnQrT1U9b3JnMStPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMS5leGFtcGxlLmNvbSxPPW9yZzEuZXhhbXBsZS5jb20sTD1EdXJoYW0sU1Q9Tm9ydGggQ2Fyb2xpbmEsQz1VUw=="
|
||||
},
|
||||
"\u0000bid\u0000auction1\u0000347e08fbbf766a0f3678c3c6e05b0613026fa4d4619ad80869773938ed893f8d\u0000": {
|
||||
"objectType": "bid",
|
||||
"quantity": 1,
|
||||
"price": 700,
|
||||
"org": "Org2MSP",
|
||||
"buyer": "eDUwOTo6Q049YmlkZGVyMyxPVT1jbGllbnQrT1U9b3JnMitPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMi5leGFtcGxlLmNvbSxPPW9yZzIuZXhhbXBsZS5jb20sTD1IdXJzbGV5LFNUPUhhbXBzaGlyZSxDPVVL"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u0000f67eed2c00812d02482d40a652d353c505f3161cd6837077443ee2f425413f5c\u0000": {
|
||||
"objectType": "bid",
|
||||
"quantity": 1,
|
||||
"price": 900,
|
||||
"org": "Org2MSP",
|
||||
"buyer": "eDUwOTo6Q049YmlkZGVyNCxPVT1jbGllbnQrT1U9b3JnMitPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMi5leGFtcGxlLmNvbSxPPW9yZzIuZXhhbXBsZS5jb20sTD1IdXJzbGV5LFNUPUhhbXBzaGlyZSxDPVVL"
|
||||
}
|
||||
},
|
||||
"winners": [
|
||||
{
|
||||
"buyer": "eDUwOTo6Q049YmlkZGVyNCxPVT1jbGllbnQrT1U9b3JnMitPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMi5leGFtcGxlLmNvbSxPPW9yZzIuZXhhbXBsZS5jb20sTD1IdXJzbGV5LFNUPUhhbXBzaGlyZSxDPVVL",
|
||||
"quantity": 1
|
||||
}
|
||||
],
|
||||
"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/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
|
||||
````
|
||||
114
auction/application-javascript/bid.js
Normal file
114
auction/application-javascript/bid.js
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* 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 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');
|
||||
let buyer = await contract.evaluateTransaction('GetID');
|
||||
console.log('*** Result: Buyer ID is ' + buyer.toString());
|
||||
|
||||
let bidData = { objectType: 'bid', quantity: parseInt(quantity) , price: parseInt(price), org: orgMSP, buyer: buyer.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 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');
|
||||
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
|
||||
|| 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();
|
||||
109
auction/application-javascript/closeAuction.js
Normal file
109
auction/application-javascript/closeAuction.js
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* 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 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');
|
||||
let auctionString = await contract.evaluateTransaction('QueryAuction',auctionID);
|
||||
//console.log('*** Result: Bid: ' + prettyJSONString(auctionString.toString()));
|
||||
var 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 orgMSP = 'Org1MSP';
|
||||
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 orgMSP = 'Org2MSP';
|
||||
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();
|
||||
95
auction/application-javascript/createAuction.js
Normal file
95
auction/application-javascript/createAuction.js
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* 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 createAuction(ccp,wallet,user,auctionID,item,quantity) {
|
||||
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,parseInt(quantity));
|
||||
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
|
||||
|| 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];
|
||||
|
||||
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 createAuction(ccp,wallet,user,auctionID,item,quantity);
|
||||
}
|
||||
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 createAuction(ccp,wallet,user,auctionID,item,quantity);
|
||||
} 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();
|
||||
109
auction/application-javascript/endAuction.js
Normal file
109
auction/application-javascript/endAuction.js
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* 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 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');
|
||||
let auctionString = await contract.evaluateTransaction('QueryAuction',auctionID);
|
||||
//console.log('*** Result: Bid: ' + prettyJSONString(auctionString.toString()));
|
||||
var 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 orgMSP = 'Org1MSP';
|
||||
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 orgMSP = 'Org2MSP';
|
||||
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();
|
||||
76
auction/application-javascript/enrollAdmin.js
Normal file
76
auction/application-javascript/enrollAdmin.js
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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';
|
||||
|
||||
function prettyJSONString(inputString) {
|
||||
if (inputString) {
|
||||
return JSON.stringify(JSON.parse(inputString), null, 2);
|
||||
}
|
||||
else {
|
||||
return inputString;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
16
auction/application-javascript/package.json
Normal file
16
auction/application-javascript/package.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"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",
|
||||
"dependencies": {
|
||||
"fabric-ca-client": "^2.2.0",
|
||||
"fabric-network": "^2.2.0"
|
||||
}
|
||||
}
|
||||
86
auction/application-javascript/queryAuction.js
Normal file
86
auction/application-javascript/queryAuction.js
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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 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 orgMSP = 'Org1MSP';
|
||||
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 orgMSP = 'Org2MSP';
|
||||
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();
|
||||
87
auction/application-javascript/queryBid.js
Normal file
87
auction/application-javascript/queryBid.js
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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 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 orgMSP = 'Org1MSP';
|
||||
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 orgMSP = 'Org2MSP';
|
||||
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();
|
||||
77
auction/application-javascript/registerEnrollUser.js
Normal file
77
auction/application-javascript/registerEnrollUser.js
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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';
|
||||
|
||||
function prettyJSONString(inputString) {
|
||||
if (inputString) {
|
||||
return JSON.stringify(JSON.parse(inputString), null, 2);
|
||||
}
|
||||
else {
|
||||
return inputString;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
119
auction/application-javascript/revealBid.js
Normal file
119
auction/application-javascript/revealBid.js
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* 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 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');
|
||||
let bidString = await contract.evaluateTransaction('QueryBid',auctionID,bidID);
|
||||
var 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()));
|
||||
var auctionJSON = JSON.parse(auctionString);
|
||||
|
||||
let 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));
|
||||
|
||||
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 orgMSP = 'Org1MSP';
|
||||
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 orgMSP = 'Org2MSP';
|
||||
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();
|
||||
108
auction/application-javascript/submitBid.js
Normal file
108
auction/application-javascript/submitBid.js
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* 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);
|
||||
var 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 orgMSP = 'Org1MSP';
|
||||
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 orgMSP = 'Org2MSP';
|
||||
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();
|
||||
8
auction/chaincode-go/go.mod
Normal file
8
auction/chaincode-go/go.mod
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
module github.com/hyperledger/fabric-samples/auction/chaincode-go
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/hyperledger/fabric-chaincode-go v0.0.0-20200728190242-9b3ae92d8664
|
||||
github.com/hyperledger/fabric-contract-api-go v1.1.0
|
||||
)
|
||||
139
auction/chaincode-go/go.sum
Normal file
139
auction/chaincode-go/go.sum
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DATA-DOG/go-txdb v0.1.3/go.mod h1:DhAhxMXZpUJVGnT+p9IbzJoRKvlArO2pkHjnGX7o0n0=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
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/cucumber/godog v0.8.0/go.mod h1:Cp3tEV1LRAyH/RuCThcxHS/+9ORZ+FMzPva2AZ5Ki+A=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
|
||||
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w=
|
||||
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
|
||||
github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
|
||||
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU=
|
||||
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
|
||||
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
|
||||
github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4=
|
||||
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
|
||||
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/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hyperledger/fabric-chaincode-go v0.0.0-20200424173110-d7076418f212/go.mod h1:N7H3sA7Tx4k/YzFq7U0EPdqJtqvM4Kild0JoCc7C0Dc=
|
||||
github.com/hyperledger/fabric-chaincode-go v0.0.0-20200728190242-9b3ae92d8664 h1:Pu/9SNpo71SJj5DGehCXOKD9QGQ3MsuWjpsLM9Mkdwg=
|
||||
github.com/hyperledger/fabric-chaincode-go v0.0.0-20200728190242-9b3ae92d8664/go.mod h1:N7H3sA7Tx4k/YzFq7U0EPdqJtqvM4Kild0JoCc7C0Dc=
|
||||
github.com/hyperledger/fabric-contract-api-go v1.1.0 h1:K9uucl/6eX3NF0/b+CGIiO1IPm1VYQxBkpnVGJur2S4=
|
||||
github.com/hyperledger/fabric-contract-api-go v1.1.0/go.mod h1:nHWt0B45fK53owcFpLtAe8DH0Q5P068mnzkNXMPSL7E=
|
||||
github.com/hyperledger/fabric-protos-go v0.0.0-20190919234611-2a87503ac7c9/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0=
|
||||
github.com/hyperledger/fabric-protos-go v0.0.0-20200424173316-dd554ba3746e h1:9PS5iezHk/j7XriSlNuSQILyCOfcZ9wZ3/PiucmSE8E=
|
||||
github.com/hyperledger/fabric-protos-go v0.0.0-20200424173316-dd554ba3746e/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
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.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
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/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 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
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.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/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-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
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.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
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.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542 h1:6ZQFf1D2YYDDI7eSwW8adlkkavTB9sw5I24FVtEvNUQ=
|
||||
golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b h1:lohp5blsw53GBXtLyLNaTXPXS9pJ1tiTw61ZHUoE9Qw=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
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.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
539
auction/chaincode-go/smart-contract/auction.go
Normal file
539
auction/chaincode-go/smart-contract/auction.go
Normal file
|
|
@ -0,0 +1,539 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package auction
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/hyperledger/fabric-contract-api-go/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"`
|
||||
}
|
||||
|
||||
// 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) error {
|
||||
|
||||
// get ID of submitting client
|
||||
clientID, err := ctx.GetClientIdentity().GetID()
|
||||
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,
|
||||
Quantity: quantity,
|
||||
Price: 0,
|
||||
Seller: clientID,
|
||||
Orgs: []string{clientOrgID},
|
||||
PrivateBids: bidders,
|
||||
RevealedBids: revealedBids,
|
||||
Winners: []Winners{},
|
||||
Status: "open",
|
||||
}
|
||||
|
||||
auctionBytes, err := json.Marshal(auction)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// put auction into state
|
||||
err = ctx.GetStub().PutState(auctionID, auctionBytes)
|
||||
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 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: 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 state
|
||||
auctionBytes, err := ctx.GetStub().GetState(auctionID)
|
||||
var auctionJSON Auction
|
||||
|
||||
if auctionBytes == nil {
|
||||
return fmt.Errorf("Auction not found: %v", auctionID)
|
||||
}
|
||||
err = json.Unmarshal(auctionBytes, &auctionJSON)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create auction object JSON: %v", err)
|
||||
}
|
||||
|
||||
// the auction needs to be open for users to add their bid
|
||||
Status := auctionJSON.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),
|
||||
}
|
||||
|
||||
bidders := make(map[string]BidHash)
|
||||
bidders = auctionJSON.PrivateBids
|
||||
bidders[bidKey] = NewHash
|
||||
auctionJSON.PrivateBids = bidders
|
||||
|
||||
// Add the bidding organization to the list of participating organization's if it is not already
|
||||
Orgs := auctionJSON.Orgs
|
||||
if !(contains(Orgs, clientOrgID)) {
|
||||
newOrgs := append(Orgs, clientOrgID)
|
||||
auctionJSON.Orgs = newOrgs
|
||||
|
||||
err = addAssetStateBasedEndorsement(ctx, auctionID, clientOrgID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed setting state based endorsement for new organization: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
newAuctionBytes, _ := json.Marshal(auctionJSON)
|
||||
|
||||
err = ctx.GetStub().PutState(auctionID, newAuctionBytes)
|
||||
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
|
||||
auctionBytes, err := ctx.GetStub().GetState(auctionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get auction %v: %v", auctionID, err)
|
||||
}
|
||||
if auctionBytes == nil {
|
||||
return fmt.Errorf("Auction interest object %v not found", auctionID)
|
||||
}
|
||||
|
||||
var auctionJSON Auction
|
||||
err = json.Unmarshal(auctionBytes, &auctionJSON)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create auction object JSON: %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 := auctionJSON.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
|
||||
|
||||
bidders := auctionJSON.PrivateBids
|
||||
privateBidHashString := bidders[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 identity
|
||||
clientID, err := ctx.GetClientIdentity().GetID()
|
||||
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 := make(map[string]FullBid)
|
||||
revealedBids = auctionJSON.RevealedBids
|
||||
revealedBids[bidKey] = NewBid
|
||||
auctionJSON.RevealedBids = revealedBids
|
||||
|
||||
newAuctionBytes, _ := json.Marshal(auctionJSON)
|
||||
|
||||
// put auction with bid added back into state
|
||||
err = ctx.GetStub().PutState(auctionID, newAuctionBytes)
|
||||
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 {
|
||||
|
||||
auctionBytes, err := ctx.GetStub().GetState(auctionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get auction %v: %v", auctionID, err)
|
||||
}
|
||||
|
||||
if auctionBytes == nil {
|
||||
return fmt.Errorf("Auction interest object %v not found", auctionID)
|
||||
}
|
||||
|
||||
var auctionJSON Auction
|
||||
err = json.Unmarshal(auctionBytes, &auctionJSON)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create auction object JSON: %v", err)
|
||||
}
|
||||
|
||||
// the auction can only be closed by the seller
|
||||
|
||||
// get ID of submitting client
|
||||
clientID, err := ctx.GetClientIdentity().GetID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get client identity %v", err)
|
||||
}
|
||||
|
||||
Seller := auctionJSON.Seller
|
||||
if Seller != clientID {
|
||||
return fmt.Errorf("auction can only be closed by seller: %v", err)
|
||||
}
|
||||
|
||||
Status := auctionJSON.Status
|
||||
if Status != "open" {
|
||||
return fmt.Errorf("cannot close auction that is not open")
|
||||
}
|
||||
|
||||
auctionJSON.Status = string("closed")
|
||||
|
||||
closedAuction, _ := json.Marshal(auctionJSON)
|
||||
|
||||
err = ctx.GetStub().PutState(auctionID, closedAuction)
|
||||
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 {
|
||||
|
||||
auctionBytes, err := ctx.GetStub().GetState(auctionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get auction %v: %v", auctionID, err)
|
||||
}
|
||||
|
||||
if auctionBytes == nil {
|
||||
return fmt.Errorf("Auction interest object %v not found", auctionID)
|
||||
}
|
||||
|
||||
var auctionJSON Auction
|
||||
err = json.Unmarshal(auctionBytes, &auctionJSON)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create auction object JSON: %v", err)
|
||||
}
|
||||
|
||||
// Check that the auction is being ended by the seller
|
||||
|
||||
// get ID of submitting client
|
||||
clientID, err := ctx.GetClientIdentity().GetID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get client identity %v", err)
|
||||
}
|
||||
|
||||
Seller := auctionJSON.Seller
|
||||
if Seller != clientID {
|
||||
return fmt.Errorf("auction can only be ended by seller: %v", err)
|
||||
}
|
||||
|
||||
Status := auctionJSON.Status
|
||||
if Status != "closed" {
|
||||
return fmt.Errorf("Can only end a closed auction")
|
||||
}
|
||||
|
||||
// get the list of revealed bids
|
||||
|
||||
revealedBidMap := auctionJSON.RevealedBids
|
||||
if len(auctionJSON.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 := auctionJSON.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
|
||||
auctionJSON.Winners = append(auctionJSON.Winners, winner)
|
||||
auctionJSON.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 {
|
||||
auctionJSON.Winners[i].Quantity = remainingQuantity
|
||||
remainingQuantity = 0
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
// check if there is a winning bid that has yet to be revealed
|
||||
err = queryAllBids(ctx, auctionJSON.Price, auctionJSON.RevealedBids, auctionJSON.PrivateBids)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot close auction: %v", err)
|
||||
}
|
||||
|
||||
auctionJSON.Status = string("ended")
|
||||
|
||||
closedAuction, _ := json.Marshal(auctionJSON)
|
||||
|
||||
err = ctx.GetStub().PutState(auctionID, closedAuction)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to close auction: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
148
auction/chaincode-go/smart-contract/auctionQueries.go
Normal file
148
auction/chaincode-go/smart-contract/auctionQueries.go
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package auction
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/hyperledger/fabric-chaincode-go/shim"
|
||||
"github.com/hyperledger/fabric-contract-api-go/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 := ctx.GetClientIdentity().GetID()
|
||||
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
|
||||
}
|
||||
|
||||
// GetID is an internal helper function to allow users to get their identity
|
||||
func (s *SmartContract) GetID(ctx contractapi.TransactionContextInterface) (string, error) {
|
||||
|
||||
// Get the MSP ID of submitting client identity
|
||||
clientID, err := ctx.GetClientIdentity().GetID()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get verified MSPID: %v", err)
|
||||
}
|
||||
|
||||
return clientID, nil
|
||||
}
|
||||
|
||||
// queryAllBids is an internal function that is used to determine if a winning bid has yet to be revealed
|
||||
func queryAllBids(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
|
||||
}
|
||||
107
auction/chaincode-go/smart-contract/utils.go
Normal file
107
auction/chaincode-go/smart-contract/utils.go
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package auction
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hyperledger/fabric-chaincode-go/pkg/statebased"
|
||||
"github.com/hyperledger/fabric-chaincode-go/shim"
|
||||
"github.com/hyperledger/fabric-contract-api-go/contractapi"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
23
auction/chaincode-go/smartContract.go
Normal file
23
auction/chaincode-go/smartContract.go
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/hyperledger/fabric-contract-api-go/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)
|
||||
}
|
||||
}
|
||||
431
auction/dutch-auction/README.md
Normal file
431
auction/dutch-auction/README.md
Normal file
|
|
@ -0,0 +1,431 @@
|
|||
## Running a Dutch auction
|
||||
|
||||
The auction sample smart contract can be used to implement a [Dutch auction](https://en.wikipedia.org/wiki/Dutch_auction) to sell multiple items of the same good. All items are sold at the price that clears the auction. This tutorial creates an auction to sell 100 tickets to multiple bidders.
|
||||
|
||||
## 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 auction smart contract.
|
||||
```
|
||||
./network.sh deployCC -ccn auction -ccp ../auction/chaincode-go/ -ccep "OR('Org1MSP.peer','Org2MSP.peer')"
|
||||
```
|
||||
|
||||
## Install the application dependencies
|
||||
|
||||
Change into the `application-javascript` directory:
|
||||
```
|
||||
cd fabric-samples/auction/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 2 bidders from Org1 and another 3 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 ID for the auction, the item to be sold, and the quantity to be sold to create the auction:
|
||||
```
|
||||
node createAuction.js org1 seller auction1 tickets 100
|
||||
```
|
||||
|
||||
You will see the application query the auction after it is created.
|
||||
|
||||
## 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": "eDUwOTo6Q049YmlkZGVyMSxPVT1jbGllbnQrT1U9b3JnMStPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMS5leGFtcGxlLmNvbSxPPW9yZzEuZXhhbXBsZS5jb20sTD1EdXJoYW0sU1Q9Tm9ydGggQ2Fyb2xpbmEsQz1VUw=="
|
||||
}
|
||||
```
|
||||
|
||||
The `bid.js` application also prints the bidID:
|
||||
```
|
||||
*** Result ***SAVE THIS VALUE*** BidID: 61e9b0fc1913f10872625bea4a6555522c70070416209848cc1d8fb6101133ad
|
||||
```
|
||||
|
||||
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=61e9b0fc1913f10872625bea4a6555522c70070416209848cc1d8fb6101133ad
|
||||
```
|
||||
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": "eDUwOTo6Q049c2VsbGVyLE9VPWNsaWVudCtPVT1vcmcxK09VPWRlcGFydG1lbnQxOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT",
|
||||
"quantity": 100,
|
||||
"organizations": [
|
||||
"Org1MSP"
|
||||
],
|
||||
"privateBids": {
|
||||
"\u0000bid\u0000auction1\u000061e9b0fc1913f10872625bea4a6555522c70070416209848cc1d8fb6101133ad\u0000": {
|
||||
"org": "Org1MSP",
|
||||
"hash": "584dbad2269a44afb42bdbc7a7a4c08a7cd50deece1eb3fc38d4e49b9342f270"
|
||||
}
|
||||
},
|
||||
"revealedBids": {},
|
||||
"winners": [],
|
||||
"price": 0,
|
||||
"status": "open"
|
||||
}
|
||||
```
|
||||
|
||||
### 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=911c7920a7ba4643a531cb2d5d274d303fdb2e6800f50aeb6b725af0b7162ea2
|
||||
```
|
||||
|
||||
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=93a8164628fa28290554b5dc6f505cbb8c7498d8f7c60f7df33d4a1cffb8fa47
|
||||
```
|
||||
|
||||
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": "eDUwOTo6Q049c2VsbGVyLE9VPWNsaWVudCtPVT1vcmcxK09VPWRlcGFydG1lbnQxOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT",
|
||||
"quantity": 100,
|
||||
"organizations": [
|
||||
"Org1MSP",
|
||||
"Org2MSP"
|
||||
],
|
||||
"privateBids": {
|
||||
"\u0000bid\u0000auction1\u000061e9b0fc1913f10872625bea4a6555522c70070416209848cc1d8fb6101133ad\u0000": {
|
||||
"org": "Org1MSP",
|
||||
"hash": "584dbad2269a44afb42bdbc7a7a4c08a7cd50deece1eb3fc38d4e49b9342f270"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u0000911c7920a7ba4643a531cb2d5d274d303fdb2e6800f50aeb6b725af0b7162ea2\u0000": {
|
||||
"org": "Org1MSP",
|
||||
"hash": "bbcd0c7c376e6681a76d8c5482c97f8bdcda55c90c5478100c3aef17815c4fd3"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u000093a8164628fa28290554b5dc6f505cbb8c7498d8f7c60f7df33d4a1cffb8fa47\u0000": {
|
||||
"org": "Org2MSP",
|
||||
"hash": "4446c7eb0e2d64165a916ee996348a18716f4c97e632d58d5a8c20eeec5a9238"
|
||||
}
|
||||
},
|
||||
"revealedBids": {},
|
||||
"winners": [],
|
||||
"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 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=324de04c459c5a38f103e9096dea06e19faca88f84dd5175ba0e6fd6a9d7d140
|
||||
```
|
||||
|
||||
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 bidder4 auction1 20 60
|
||||
```
|
||||
|
||||
Save the Bid ID returned by the application:
|
||||
```
|
||||
export BIDDER5_BID_ID=3245049bd81a8cecfbc006f29eed1df0570e385e63daedd19c06b2c92d2067ae
|
||||
```
|
||||
|
||||
Add bidder4'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": "eDUwOTo6Q049c2VsbGVyLE9VPWNsaWVudCtPVT1vcmcxK09VPWRlcGFydG1lbnQxOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT",
|
||||
"quantity": 100,
|
||||
"organizations": [
|
||||
"Org1MSP",
|
||||
"Org2MSP"
|
||||
],
|
||||
"privateBids": {
|
||||
"\u0000bid\u0000auction1\u00003245049bd81a8cecfbc006f29eed1df0570e385e63daedd19c06b2c92d2067ae\u0000": {
|
||||
"org": "Org2MSP",
|
||||
"hash": "5a51070f188e6480fe606acdc5ad1a1c36330adc3547eb021aaf87aa00ec79f7"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u0000324de04c459c5a38f103e9096dea06e19faca88f84dd5175ba0e6fd6a9d7d140\u0000": {
|
||||
"org": "Org2MSP",
|
||||
"hash": "516c775f7da2fd653dab71d20d60a6e8bea8ff856803af4b59f943ca2ba40699"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u000061e9b0fc1913f10872625bea4a6555522c70070416209848cc1d8fb6101133ad\u0000": {
|
||||
"org": "Org1MSP",
|
||||
"hash": "584dbad2269a44afb42bdbc7a7a4c08a7cd50deece1eb3fc38d4e49b9342f270"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u0000911c7920a7ba4643a531cb2d5d274d303fdb2e6800f50aeb6b725af0b7162ea2\u0000": {
|
||||
"org": "Org1MSP",
|
||||
"hash": "bbcd0c7c376e6681a76d8c5482c97f8bdcda55c90c5478100c3aef17815c4fd3"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u000093a8164628fa28290554b5dc6f505cbb8c7498d8f7c60f7df33d4a1cffb8fa47\u0000": {
|
||||
"org": "Org2MSP",
|
||||
"hash": "4446c7eb0e2d64165a916ee996348a18716f4c97e632d58d5a8c20eeec5a9238"
|
||||
}
|
||||
},
|
||||
"revealedBids": {
|
||||
"\u0000bid\u0000auction1\u000061e9b0fc1913f10872625bea4a6555522c70070416209848cc1d8fb6101133ad\u0000": {
|
||||
"objectType": "bid",
|
||||
"quantity": 50,
|
||||
"price": 80,
|
||||
"org": "Org1MSP",
|
||||
"buyer": "eDUwOTo6Q049YmlkZGVyMSxPVT1jbGllbnQrT1U9b3JnMStPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMS5leGFtcGxlLmNvbSxPPW9yZzEuZXhhbXBsZS5jb20sTD1EdXJoYW0sU1Q9Tm9ydGggQ2Fyb2xpbmEsQz1VUw=="
|
||||
}
|
||||
},
|
||||
"winners": [],
|
||||
"price": 0,
|
||||
"status": "closed"
|
||||
}
|
||||
```
|
||||
|
||||
All bidders will reveal their bid to participate in the auction. Run the following commands to reveal the bids of the remaining four bidders:
|
||||
```
|
||||
node revealBid.js org1 bidder2 auction1 $BIDDER2_BID_ID
|
||||
node revealBid.js org2 bidder3 auction1 $BIDDER3_BID_ID
|
||||
node revealBid.js org2 bidder4 auction1 $BIDDER4_BID_ID
|
||||
node revealBid.js org2 bidder5 auction1 $BIDDER5_BID_ID
|
||||
```
|
||||
|
||||
## End the auction
|
||||
|
||||
Now that the winning bids have been revealed, we can end the auction:
|
||||
```
|
||||
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": "eDUwOTo6Q049c2VsbGVyLE9VPWNsaWVudCtPVT1vcmcxK09VPWRlcGFydG1lbnQxOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT",
|
||||
"quantity": 100,
|
||||
"organizations": [
|
||||
"Org1MSP",
|
||||
"Org2MSP"
|
||||
],
|
||||
"privateBids": {
|
||||
"\u0000bid\u0000auction1\u00003245049bd81a8cecfbc006f29eed1df0570e385e63daedd19c06b2c92d2067ae\u0000": {
|
||||
"org": "Org2MSP",
|
||||
"hash": "5a51070f188e6480fe606acdc5ad1a1c36330adc3547eb021aaf87aa00ec79f7"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u0000324de04c459c5a38f103e9096dea06e19faca88f84dd5175ba0e6fd6a9d7d140\u0000": {
|
||||
"org": "Org2MSP",
|
||||
"hash": "516c775f7da2fd653dab71d20d60a6e8bea8ff856803af4b59f943ca2ba40699"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u000061e9b0fc1913f10872625bea4a6555522c70070416209848cc1d8fb6101133ad\u0000": {
|
||||
"org": "Org1MSP",
|
||||
"hash": "584dbad2269a44afb42bdbc7a7a4c08a7cd50deece1eb3fc38d4e49b9342f270"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u0000911c7920a7ba4643a531cb2d5d274d303fdb2e6800f50aeb6b725af0b7162ea2\u0000": {
|
||||
"org": "Org1MSP",
|
||||
"hash": "bbcd0c7c376e6681a76d8c5482c97f8bdcda55c90c5478100c3aef17815c4fd3"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u000093a8164628fa28290554b5dc6f505cbb8c7498d8f7c60f7df33d4a1cffb8fa47\u0000": {
|
||||
"org": "Org2MSP",
|
||||
"hash": "4446c7eb0e2d64165a916ee996348a18716f4c97e632d58d5a8c20eeec5a9238"
|
||||
}
|
||||
},
|
||||
"revealedBids": {
|
||||
"\u0000bid\u0000auction1\u00003245049bd81a8cecfbc006f29eed1df0570e385e63daedd19c06b2c92d2067ae\u0000": {
|
||||
"objectType": "bid",
|
||||
"quantity": 20,
|
||||
"price": 60,
|
||||
"org": "Org2MSP",
|
||||
"buyer": "eDUwOTo6Q049YmlkZGVyNCxPVT1jbGllbnQrT1U9b3JnMitPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMi5leGFtcGxlLmNvbSxPPW9yZzIuZXhhbXBsZS5jb20sTD1IdXJzbGV5LFNUPUhhbXBzaGlyZSxDPVVL"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u0000324de04c459c5a38f103e9096dea06e19faca88f84dd5175ba0e6fd6a9d7d140\u0000": {
|
||||
"objectType": "bid",
|
||||
"quantity": 15,
|
||||
"price": 60,
|
||||
"org": "Org2MSP",
|
||||
"buyer": "eDUwOTo6Q049YmlkZGVyNCxPVT1jbGllbnQrT1U9b3JnMitPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMi5leGFtcGxlLmNvbSxPPW9yZzIuZXhhbXBsZS5jb20sTD1IdXJzbGV5LFNUPUhhbXBzaGlyZSxDPVVL"
|
||||
},
|
||||
"\u0000bid\u0000auction1\u000061e9b0fc1913f10872625bea4a6555522c70070416209848cc1d8fb6101133ad\u0000": {
|
||||
"objectType": "bid",
|
||||
"quantity": 50,
|
||||
"price": 80,
|
||||
"org": "Org1MSP",
|
||||
"buyer": "eDUwOTo6Q049YmlkZGVyMSxPVT1jbGllbnQrT1U9b3JnMStPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMS5leGFtcGxlLmNvbSxPPW9yZzEuZXhhbXBsZS5jb20sTD1EdXJoYW0sU1Q9Tm9ydGggQ2Fyb2xpbmEsQz1VUw=="
|
||||
},
|
||||
"\u0000bid\u0000auction1\u0000911c7920a7ba4643a531cb2d5d274d303fdb2e6800f50aeb6b725af0b7162ea2\u0000": {
|
||||
"objectType": "bid",
|
||||
"quantity": 40,
|
||||
"price": 50,
|
||||
"org": "Org1MSP",
|
||||
"buyer": "eDUwOTo6Q049YmlkZGVyMixPVT1jbGllbnQrT1U9b3JnMStPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMS5leGFtcGxlLmNvbSxPPW9yZzEuZXhhbXBsZS5jb20sTD1EdXJoYW0sU1Q9Tm9ydGggQ2Fyb2xpbmEsQz1VUw=="
|
||||
},
|
||||
"\u0000bid\u0000auction1\u000093a8164628fa28290554b5dc6f505cbb8c7498d8f7c60f7df33d4a1cffb8fa47\u0000": {
|
||||
"objectType": "bid",
|
||||
"quantity": 30,
|
||||
"price": 70,
|
||||
"org": "Org2MSP",
|
||||
"buyer": "eDUwOTo6Q049YmlkZGVyMyxPVT1jbGllbnQrT1U9b3JnMitPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMi5leGFtcGxlLmNvbSxPPW9yZzIuZXhhbXBsZS5jb20sTD1IdXJzbGV5LFNUPUhhbXBzaGlyZSxDPVVL"
|
||||
}
|
||||
},
|
||||
"winners": [
|
||||
{
|
||||
"buyer": "eDUwOTo6Q049YmlkZGVyMSxPVT1jbGllbnQrT1U9b3JnMStPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMS5leGFtcGxlLmNvbSxPPW9yZzEuZXhhbXBsZS5jb20sTD1EdXJoYW0sU1Q9Tm9ydGggQ2Fyb2xpbmEsQz1VUw==",
|
||||
"quantity": 50
|
||||
},
|
||||
{
|
||||
"buyer": "eDUwOTo6Q049YmlkZGVyMyxPVT1jbGllbnQrT1U9b3JnMitPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMi5leGFtcGxlLmNvbSxPPW9yZzIuZXhhbXBsZS5jb20sTD1IdXJzbGV5LFNUPUhhbXBzaGlyZSxDPVVL",
|
||||
"quantity": 30
|
||||
},
|
||||
{
|
||||
"buyer": "eDUwOTo6Q049YmlkZGVyNCxPVT1jbGllbnQrT1U9b3JnMitPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMi5leGFtcGxlLmNvbSxPPW9yZzIuZXhhbXBsZS5jb20sTD1IdXJzbGV5LFNUPUhhbXBzaGlyZSxDPVVL",
|
||||
"quantity": 15
|
||||
},
|
||||
{
|
||||
"buyer": "eDUwOTo6Q049YmlkZGVyNCxPVT1jbGllbnQrT1U9b3JnMitPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMi5leGFtcGxlLmNvbSxPPW9yZzIuZXhhbXBsZS5jb20sTD1IdXJzbGV5LFNUPUhhbXBzaGlyZSxDPVVL",
|
||||
"quantity": 5
|
||||
}
|
||||
],
|
||||
"price": 60,
|
||||
"status": "ended"
|
||||
}
|
||||
```
|
||||
|
||||
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/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
|
||||
````
|
||||
Loading…
Reference in a new issue