Simplify auction sample (#367)

This PR changes the auction sample to a simple blind auction with just a
single item to be sold to the highest bidder.

Signed-off-by: Arnaud J Le Hors <lehors@us.ibm.com>
This commit is contained in:
Arnaud J Le Hors 2020-11-06 19:08:34 +01:00 committed by GitHub
parent bf1d9fc667
commit ff8a3c8d50
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 132 additions and 626 deletions

View file

@ -1,17 +1,17 @@
## Auction sample
## Simple blind 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.
The simple blind auction sample uses Hyperledger Fabric to run an auction where bids are kept private until the auction period is over. Instead of displaying the full bid on the public ledger, buyers can only see hashes of other bids while bidding is underway. This prevents buyers from changing their bids in response to bids submitted by others. After the bidding period ends, participants reveal their bid to try to win the auction. The organizations participating in the auction verify that a revealed bid matches the hash on the public ledger. Whichever has the highest bid wins.
A user that wants to sell one 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.
A user that wants to sell one item can use the smart contract to create an auction. The auction is stored on the channel ledger and can be read by all channel members. The auctions created by the smart contract are run in three steps:
1. Each auction is created with the status **open**. While the auction is open, buyers can add new bids to the auction. The full bids of each buyer are stored in the implicit private data collections of their organization. After the bid is created, the bidder can submit the hash of the bid to the auction. A bid is added to the auction in two steps because the transaction that creates the bid only needs to be endorsed by a peer of the bidders organization, while a transaction that updates the auction may need to be endorsed by multiple organizations. When the bid is added to the auction, the bidder's organization is added to the list of organizations that need to endorse any updates to the auction.
2. The auction is **closed** to prevent additional bids from being added to the auction. After the auction is closed, bidders that submitted bids to the auction can reveal their full bid. Only revealed bids can win the auction.
3. The auction is **ended** to calculate the winner from the set of revealed bids. All organizations participating in the auction calculate the price that clears the auction and the winning bid. The seller can end the auction only if all bidding organizations endorse the same winner and price.
Before endorsing the transaction that ends the auction, each organization queries the implicit private data collection on their peers to check if any organization member has a winning bid that has not yet been revealed. If a winning bid is found, the organization will withhold their endorsement and prevent the auction from being closed. This prevents the seller from ending the auction prematurely, or colluding with buyers to end the auction at an artificially low price.
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.
The sample uses several Fabric features to make the auction private and secure. Bids are stored in private data collections to prevent bids from being distributed to other peers in the channel. When bidding is closed, the auction smart contract uses the `GetPrivateDataHash()` API to verify that the bid stored in private data is the same bid that is being revealed. State based endorsement is used to add the organization of each bidder to the auction endorsement policy. The smart contract uses the `GetClientIdentity.GetID()` API to ensure that only the potential buyer can read their bid from private state and only the seller can close or end the auction.
This tutorial uses the auction smart contract in a scenario where one seller wants to 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).
This tutorial uses the auction smart contract in a scenario where one seller wants to auction a painting. Four potential buyers from two different organizations will submit bids to the auction and try to win the auction.
## Deploy the chaincode
@ -72,9 +72,9 @@ 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:
The seller from Org1 would like to create an auction to sell a vintage Matchbox painting. Run the following command to use the seller wallet to run the `createAuction.js` application. The program will submit a transaction to the network that creates the auction on the channel ledger. The organization and identity name are passed to the application to use the wallet that was created by the `registerEnrollUser.js` application. The seller needs to provide an ID for the auction and the item to be sold to create the auction:
```
node createAuction.js org1 seller auction1 painting 1
node createAuction.js org1 seller PaintingAuction painting
```
After the transaction is complete, the `createAuction.js` application will query the auction stored in the public channel ledger:
@ -83,13 +83,12 @@ After the transaction is complete, the `createAuction.js` application will query
"objectType": "auction",
"item": "painting",
"seller": "eDUwOTo6Q049c2VsbGVyLE9VPWNsaWVudCtPVT1vcmcxK09VPWRlcGFydG1lbnQxOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT",
"quantity": 1,
"organizations": [
"Org1MSP"
],
"privateBids": {},
"revealedBids": {},
"winners": [],
"winner": "",
"price": 0,
"status": "open"
}
@ -113,56 +112,54 @@ We can now use the bidder wallets to submit bids to the auction:
Bidder1 will create a bid to purchase the painting for 800 dollars.
```
node bid.js org1 bidder1 auction1 1 800
node bid.js org1 bidder1 PaintingAuction 800
```
The application will query the bid after it is created:
```
*** Result: Bid: {
"objectType": "bid",
"quantity": 1,
"price": 800,
"org": "Org1MSP",
"buyer": "eDUwOTo6Q049YmlkZGVyMSxPVT1jbGllbnQrT1U9b3JnMStPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMS5leGFtcGxlLmNvbSxPPW9yZzEuZXhhbXBsZS5jb20sTD1EdXJoYW0sU1Q9Tm9ydGggQ2Fyb2xpbmEsQz1VUw=="
"bidder": "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 is stored in the Org1 implicit data collection. The `"bidder"` parameter is the information from the certificate of the user that created the bid. Only this identity will be able can query the bid from private state or reveal the bid during the auction.
The `bid.js` application also prints the bidID:
```
*** Result ***SAVE THIS VALUE*** BidID: 68d3de99032700d92b2d753c79d5aff2ca79378b169efb6a70ca3e0ea43acb1b
*** Result ***SAVE THIS VALUE*** BidID: 8ef83011a5fb791f75ed008337839426f6b87981519e5d58ef5ada39c3044edd
```
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
export BIDDER1_BID_ID=8ef83011a5fb791f75ed008337839426f6b87981519e5d58ef5ada39c3044edd
```
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
node submitBid.js org1 bidder1 PaintingAuction $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:
The hash of bid will be added to the list private bids in that have been submitted to `PaintingAuction`. Storing the hash in the public auction allows users to accurately reveal the bid after bidding is closed. The application will query the auction to verify that the bid was added:
```
*** Result: Auction: {
"objectType": "auction",
"item": "painting",
"seller": "eDUwOTo6Q049c2VsbGVyLE9VPWNsaWVudCtPVT1vcmcxK09VPWRlcGFydG1lbnQxOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT",
"quantity": 1,
"organizations": [
"Org1MSP"
],
"privateBids": {
"\u0000bid\u0000auction1\u000003382872f8f9dc94f211385d3d127f155e22de1bee8e112dcb90beb3e78f2722\u0000": {
"\u0000bid\u0000PaintingAuction\u00008ef83011a5fb791f75ed008337839426f6b87981519e5d58ef5ada39c3044edd\u0000": {
"org": "Org1MSP",
"hash": "9345d28bb40b7026cc9e14743147356ae1cac99ced8190fe72c9be69fbfc4b71"
"hash": "5cb50a17b5a21c02fc01306e3e9b54f4db67e9a440552ce898bbd7daa62dce0f"
}
},
"revealedBids": {},
"winners": [],
"winner": "",
"price": 0,
"status": "open"
}
@ -172,34 +169,34 @@ The hash of bid is added to the list of private bids in that have been submitted
Let's submit another bid. Bidder2 would like to purchase the painting for 500 dollars.
```
node bid.js org1 bidder2 auction1 1 500
node bid.js org1 bidder2 PaintingAuction 500
```
Save the Bid ID returned by the application:
```
export BIDDER2_BID_ID=77dc57876a1bc5cb798863f07eaab6f041608e55c60900b2cbcf0acc7723e8ec
export BIDDER2_BID_ID=915a908c8f2c368f4a3aedd73176656af81ddfab000b11629503403f3d97b185
```
Submit bidder2's bid to the auction:
```
node submitBid.js org1 bidder2 auction1 $BIDDER2_BID_ID
node submitBid.js org1 bidder2 PaintingAuction $BIDDER2_BID_ID
```
### Bid as bidder3 from Org2
Bidder3 will bid 700 dollars for the painting:
```
node bid.js org2 bidder3 auction1 1 700
node bid.js org2 bidder3 PaintingAuction 700
```
Save the Bid ID returned by the application:
```
export BIDDER3_BID_ID=7e2d0c33d0ff1030d855e5fb76f2a4eb30589549b4cb6e581da17d3705cbf77e
export BIDDER3_BID_ID=5e4e637c68833b178739575f6fe09820b019551a8cfbb43a4d172e0aa864dfad
```
Add bidder3's bid to the auction:
```
node submitBid.js org2 bidder3 auction1 $BIDDER3_BID_ID
node submitBid.js org2 bidder3 PaintingAuction $BIDDER3_BID_ID
```
Because bidder3 belongs to Org2, submitting the bid will add Org2 to the list of participating organizations. You can see the Org2 MSP ID has been added to the list of `"organizations"` in the updated auction returned by the application:
@ -208,56 +205,55 @@ Because bidder3 belongs to Org2, submitting the bid will add Org2 to the list of
"objectType": "auction",
"item": "painting",
"seller": "eDUwOTo6Q049c2VsbGVyLE9VPWNsaWVudCtPVT1vcmcxK09VPWRlcGFydG1lbnQxOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT",
"quantity": 1,
"organizations": [
"Org1MSP",
"Org2MSP"
],
"privateBids": {
"\u0000bid\u0000auction1\u000003382872f8f9dc94f211385d3d127f155e22de1bee8e112dcb90beb3e78f2722\u0000": {
"org": "Org1MSP",
"hash": "9345d28bb40b7026cc9e14743147356ae1cac99ced8190fe72c9be69fbfc4b71"
},
"\u0000bid\u0000auction1\u0000347e08fbbf766a0f3678c3c6e05b0613026fa4d4619ad80869773938ed893f8d\u0000": {
"\u0000bid\u0000PaintingAuction\u00005e4e637c68833b178739575f6fe09820b019551a8cfbb43a4d172e0aa864dfad\u0000": {
"org": "Org2MSP",
"hash": "ecaf72ecd6daf27889434491b0e5580c94bd682d8b0283c142b085d51fcf1f69"
"hash": "40107eab7a99dfc2f25d02b8ab840f12fd802a9f86d8d42b78d7b4409b2c15bd"
},
"\u0000bid\u0000auction1\u0000e624a5f04fb8878e0d20bf10649f06113771eba6ea4c58f46d17264d1b492a46\u0000": {
"\u0000bid\u0000PaintingAuction\u00008ef83011a5fb791f75ed008337839426f6b87981519e5d58ef5ada39c3044edd\u0000": {
"org": "Org1MSP",
"hash": "8434bfe683c4436484176e6c9b21a5d684c0710fce9fd2506af4e05bb0f8a3ea"
"hash": "5cb50a17b5a21c02fc01306e3e9b54f4db67e9a440552ce898bbd7daa62dce0f"
},
"\u0000bid\u0000PaintingAuction\u0000915a908c8f2c368f4a3aedd73176656af81ddfab000b11629503403f3d97b185\u0000": {
"org": "Org1MSP",
"hash": "a458df18b12dffe4ae6d56a270134c2d55bd53fface034bd24381d0073d46a45"
}
},
"revealedBids": {},
"winners": [],
"winner": "",
"price": 0,
"status": "open"
}
```
Now that a bid from Org2 has been added to the auction, any updates to the auction need to be endorsed by the Org2 peer. The applications will use the `"organizations"` field to specify which organizations need to endorse submitting a new bid, revealing a bid, or updating the auction status.
Now that a bid from Org2 has been added to the auction, any updates to the auction need to be endorsed by the Org2 peer. The applications will use `"organizations"` field to specify which organizations need to endorse submitting a new bid, revealing a bid, or updating the auction status.
### Bid as bidder4
Bidder4 from Org2 would like to purchase the painting for 900 dollars:
```
node bid.js org2 bidder4 auction1 1 900
node bid.js org2 bidder4 PaintingAuction 900
```
Save the Bid ID returned by the application:
```
export BIDDER4_BID_ID=083478f1af2ba391a5b8d7c590cfb790aedf11578d64ebc4e5efe793555ff212
export BIDDER4_BID_ID=49466271ae879bd009e75a60730a12bfa986e75f263202ab81ccd3deec544a35
```
Add bidder4's bid to the auction:
```
node submitBid.js org2 bidder4 auction1 $BIDDER4_BID_ID
node submitBid.js org2 bidder4 PaintingAuction $BIDDER4_BID_ID
```
## Close the auction
Now that all four bidders have joined the auction, the seller would like to close the auction and allow buyers to reveal their bids. The seller identity that created the auction needs to submit the transaction:
```
node closeAuction.js org1 seller auction1
node closeAuction.js org1 seller PaintingAuction
```
The application will query the auction to allow you to verify that the auction status has changed to closed. As a test, you can try to create and submit a new bid to verify that no new bids can be added to the auction.
@ -272,48 +268,46 @@ After the auction is closed, bidders can try to win the auction by revealing the
Use the `revealBid.js` application to reveal the bid of Bidder1:
```
node revealBid.js org1 bidder1 auction1 $BIDDER1_BID_ID
node revealBid.js org1 bidder1 PaintingAuction $BIDDER1_BID_ID
```
The full bid details, including the quantity and price, are now visible:
The full bid details, including the price, are now visible:
```
*** Result: Auction: {
"objectType": "auction",
"item": "painting",
"seller": "eDUwOTo6Q049c2VsbGVyLE9VPWNsaWVudCtPVT1vcmcxK09VPWRlcGFydG1lbnQxOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT",
"quantity": 100,
"organizations": [
"Org1MSP",
"Org2MSP"
],
"privateBids": {
"\u0000bid\u0000auction1\u00002fcaee344b361a95701dc03ca4417c6c158845f9e4be3b2555b17c2b691c5ec6\u0000": {
"\u0000bid\u0000PaintingAuction\u000049466271ae879bd009e75a60730a12bfa986e75f263202ab81ccd3deec544a35\u0000": {
"org": "Org2MSP",
"hash": "d6f661d8b664244ce55065edc2fd95a982221888bb19afdad931b185e187ed4f"
"hash": "b8eaeb4422b93abdfe4ccb6aa11b745b3d1cb072a99bd3eb3618f081fb1b1f89"
},
"\u0000bid\u0000auction1\u000083d529d37ea3518c67135fa8b9bf0bff33053bed0e2f38cb6a7ff75882efcf95\u0000": {
"\u0000bid\u0000PaintingAuction\u00005e4e637c68833b178739575f6fe09820b019551a8cfbb43a4d172e0aa864dfad\u0000": {
"org": "Org2MSP",
"hash": "4446c7eb0e2d64165a916ee996348a18716f4c97e632d58d5a8c20eeec5a9238"
"hash": "40107eab7a99dfc2f25d02b8ab840f12fd802a9f86d8d42b78d7b4409b2c15bd"
},
"\u0000bid\u0000auction1\u00009e445520af04c8a3c05b484408f0406c9b09503903a9ed9b093f5d894f83dfea\u0000": {
"\u0000bid\u0000PaintingAuction\u00008ef83011a5fb791f75ed008337839426f6b87981519e5d58ef5ada39c3044edd\u0000": {
"org": "Org1MSP",
"hash": "584dbad2269a44afb42bdbc7a7a4c08a7cd50deece1eb3fc38d4e49b9342f270"
"hash": "5cb50a17b5a21c02fc01306e3e9b54f4db67e9a440552ce898bbd7daa62dce0f"
},
"\u0000bid\u0000auction1\u0000f3304fd81664f304706ff39b5a978af94bc3e055e5bf32aed1c25b35df24c136\u0000": {
"\u0000bid\u0000PaintingAuction\u0000915a908c8f2c368f4a3aedd73176656af81ddfab000b11629503403f3d97b185\u0000": {
"org": "Org1MSP",
"hash": "bbcd0c7c376e6681a76d8c5482c97f8bdcda55c90c5478100c3aef17815c4fd3"
"hash": "a458df18b12dffe4ae6d56a270134c2d55bd53fface034bd24381d0073d46a45"
}
},
"revealedBids": {
"\u0000bid\u0000auction1\u00009e445520af04c8a3c05b484408f0406c9b09503903a9ed9b093f5d894f83dfea\u0000": {
"\u0000bid\u0000PaintingAuction\u00008ef83011a5fb791f75ed008337839426f6b87981519e5d58ef5ada39c3044edd\u0000": {
"objectType": "bid",
"quantity": 1,
"price": 800,
"org": "Org1MSP",
"buyer": "eDUwOTo6Q049YmlkZGVyMSxPVT1jbGllbnQrT1U9b3JnMStPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMS5leGFtcGxlLmNvbSxPPW9yZzEuZXhhbXBsZS5jb20sTD1EdXJoYW0sU1Q9Tm9ydGggQ2Fyb2xpbmEsQz1VUw=="
"bidder": "eDUwOTo6Q049YmlkZGVyMSxPVT1jbGllbnQrT1U9b3JnMStPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMS5leGFtcGxlLmNvbSxPPW9yZzEuZXhhbXBsZS5jb20sTD1EdXJoYW0sU1Q9Tm9ydGggQ2Fyb2xpbmEsQz1VUw=="
}
},
"winners": [],
"winner": "",
"price": 0,
"status": "closed"
}
@ -321,20 +315,28 @@ The full bid details, including the quantity and price, are now visible:
Bidder3 from Org2 will also reveal their bid:
```
node revealBid.js org2 bidder3 auction1 $BIDDER3_BID_ID
node revealBid.js org2 bidder3 PaintingAuction $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.
If the auction ended now, Bidder1 would win. Let's try to end the auction using the seller identity and see what happens.
```
node endAuction.js org1 seller auction1
node endAuction.js org1 seller PaintingAuction
```
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.
The output should look something like the following:
```
--> Submit the transaction to end the auction
2020-11-06T13:16:11.591Z - warn: [TransactionEventHandler]: strategyFail: commit failure for transaction "99feade5b7ec223839200867b57d18971c3e9f923efc95aaeec720727f927366": TransactionError: Commit of transaction 99feade5b7ec223839200867b57d18971c3e9f923efc95aaeec720727f927366 failed on peer peer0.org1.example.com:7051 with status ENDORSEMENT_POLICY_FAILURE
******** FAILED to submit bid: TransactionError: Commit of transaction 99feade5b7ec223839200867b57d18971c3e9f923efc95aaeec720727f927366 failed on peer peer0.org1.example.com:7051 with status ENDORSEMENT_POLICY_FAILURE
```
Instead of ending the auction, the transaction results in an endorsement policy failure. The end of the auction needs to be endorsed by Org2. Before endorsing the transaction, the Org2 peer queries its private data collection for any winning bids that have not yet been revealed. Because Bidder4 created a bid that is above the winning price, the Org2 peer refuses to endorse the transaction that would end the auction.
Before we can end the auction, we need to reveal the bid from bidder4.
```
node revealBid.js org2 bidder4 auction1 $BIDDER4_BID_ID
node revealBid.js org2 bidder4 PaintingAuction $BIDDER4_BID_ID
```
Bidder2 from Org1 would not win the auction in either case. As a result, Bidder2 decides not to reveal their bid.
@ -343,67 +345,58 @@ Bidder2 from Org1 would not win the auction in either case. As a result, Bidder2
Now that the winning bids have been revealed, we can end the auction:
```
node endAuction org1 seller auction1
node endAuction org1 seller PaintingAuction
```
The transaction was successfully endorsed by both Org1 and Org2, who both calculated the same price and winner of the auction.
The transaction was successfully endorsed by both Org1 and Org2, who both calculated the same price and winner. The winning bidder is listed along with the price:
```
*** Result: Auction: {
"objectType": "auction",
"item": "painting",
"seller": "eDUwOTo6Q049c2VsbGVyLE9VPWNsaWVudCtPVT1vcmcxK09VPWRlcGFydG1lbnQxOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT",
"quantity": 1,
"organizations": [
"Org1MSP",
"Org2MSP"
],
"privateBids": {
"\u0000bid\u0000auction1\u000003382872f8f9dc94f211385d3d127f155e22de1bee8e112dcb90beb3e78f2722\u0000": {
"org": "Org1MSP",
"hash": "9345d28bb40b7026cc9e14743147356ae1cac99ced8190fe72c9be69fbfc4b71"
},
"\u0000bid\u0000auction1\u0000347e08fbbf766a0f3678c3c6e05b0613026fa4d4619ad80869773938ed893f8d\u0000": {
"\u0000bid\u0000PaintingAuction\u000049466271ae879bd009e75a60730a12bfa986e75f263202ab81ccd3deec544a35\u0000": {
"org": "Org2MSP",
"hash": "ecaf72ecd6daf27889434491b0e5580c94bd682d8b0283c142b085d51fcf1f69"
"hash": "b8eaeb4422b93abdfe4ccb6aa11b745b3d1cb072a99bd3eb3618f081fb1b1f89"
},
"\u0000bid\u0000auction1\u0000e624a5f04fb8878e0d20bf10649f06113771eba6ea4c58f46d17264d1b492a46\u0000": {
"org": "Org1MSP",
"hash": "8434bfe683c4436484176e6c9b21a5d684c0710fce9fd2506af4e05bb0f8a3ea"
},
"\u0000bid\u0000auction1\u0000f67eed2c00812d02482d40a652d353c505f3161cd6837077443ee2f425413f5c\u0000": {
"\u0000bid\u0000PaintingAuction\u00005e4e637c68833b178739575f6fe09820b019551a8cfbb43a4d172e0aa864dfad\u0000": {
"org": "Org2MSP",
"hash": "0c49d016c894b5623bc0be211409ebb0561c272c65f9faf2959002b0168b238c"
"hash": "40107eab7a99dfc2f25d02b8ab840f12fd802a9f86d8d42b78d7b4409b2c15bd"
},
"\u0000bid\u0000PaintingAuction\u00008ef83011a5fb791f75ed008337839426f6b87981519e5d58ef5ada39c3044edd\u0000": {
"org": "Org1MSP",
"hash": "5cb50a17b5a21c02fc01306e3e9b54f4db67e9a440552ce898bbd7daa62dce0f"
},
"\u0000bid\u0000PaintingAuction\u0000915a908c8f2c368f4a3aedd73176656af81ddfab000b11629503403f3d97b185\u0000": {
"org": "Org1MSP",
"hash": "a458df18b12dffe4ae6d56a270134c2d55bd53fface034bd24381d0073d46a45"
}
},
"revealedBids": {
"\u0000bid\u0000auction1\u000003382872f8f9dc94f211385d3d127f155e22de1bee8e112dcb90beb3e78f2722\u0000": {
"\u0000bid\u0000PaintingAuction\u000049466271ae879bd009e75a60730a12bfa986e75f263202ab81ccd3deec544a35\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"
"bidder": "eDUwOTo6Q049YmlkZGVyNCxPVT1jbGllbnQrT1U9b3JnMitPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMi5leGFtcGxlLmNvbSxPPW9yZzIuZXhhbXBsZS5jb20sTD1IdXJzbGV5LFNUPUhhbXBzaGlyZSxDPVVL"
},
"\u0000bid\u0000PaintingAuction\u00005e4e637c68833b178739575f6fe09820b019551a8cfbb43a4d172e0aa864dfad\u0000": {
"objectType": "bid",
"price": 700,
"org": "Org2MSP",
"bidder": "eDUwOTo6Q049YmlkZGVyMyxPVT1jbGllbnQrT1U9b3JnMitPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMi5leGFtcGxlLmNvbSxPPW9yZzIuZXhhbXBsZS5jb20sTD1IdXJzbGV5LFNUPUhhbXBzaGlyZSxDPVVL"
},
"\u0000bid\u0000PaintingAuction\u00008ef83011a5fb791f75ed008337839426f6b87981519e5d58ef5ada39c3044edd\u0000": {
"objectType": "bid",
"price": 800,
"org": "Org1MSP",
"bidder": "eDUwOTo6Q049YmlkZGVyMSxPVT1jbGllbnQrT1U9b3JnMStPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMS5leGFtcGxlLmNvbSxPPW9yZzEuZXhhbXBsZS5jb20sTD1EdXJoYW0sU1Q9Tm9ydGggQ2Fyb2xpbmEsQz1VUw=="
}
},
"winners": [
{
"buyer": "eDUwOTo6Q049YmlkZGVyNCxPVT1jbGllbnQrT1U9b3JnMitPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMi5leGFtcGxlLmNvbSxPPW9yZzIuZXhhbXBsZS5jb20sTD1IdXJzbGV5LFNUPUhhbXBzaGlyZSxDPVVL",
"quantity": 1
}
],
"winner": "eDUwOTo6Q049YmlkZGVyNCxPVT1jbGllbnQrT1U9b3JnMitPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMi5leGFtcGxlLmNvbSxPPW9yZzIuZXhhbXBsZS5jb20sTD1IdXJzbGV5LFNUPUhhbXBzaGlyZSxDPVVL",
"price": 900,
"status": "ended"
}

View file

@ -23,7 +23,7 @@ function prettyJSONString(inputString) {
}
}
async function bid(ccp,wallet,user,orgMSP,auctionID,quantity,price) {
async function bid(ccp,wallet,user,orgMSP,auctionID,price) {
try {
const gateway = new Gateway();
@ -36,10 +36,10 @@ async function bid(ccp,wallet,user,orgMSP,auctionID,quantity,price) {
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 bidder = await contract.evaluateTransaction('GetID');
console.log('*** Result: Bidder ID is ' + bidder.toString());
let bidData = { objectType: 'bid', quantity: parseInt(quantity) , price: parseInt(price), org: orgMSP, buyer: buyer.toString()};
let bidData = { objectType: 'bid', price: parseInt(price), org: orgMSP, bidder: bidder.toString()};
let statefulTxn = contract.createTransaction('Bid');
statefulTxn.setEndorsingOrganizations(orgMSP);
@ -50,7 +50,7 @@ async function bid(ccp,wallet,user,orgMSP,auctionID,quantity,price) {
let bidID = statefulTxn.getTransactionId();
console.log('\n--> Submit Transaction: Create the bid that is stored in your private data collection of your organization');
console.log('\n--> Submit Transaction: Create the bid that is stored in your organization\'s private data collection');
await statefulTxn.submit(auctionID);
console.log('*** Result: committed');
console.log('*** Result ***SAVE THIS VALUE*** BidID: ' + bidID.toString());
@ -73,17 +73,15 @@ 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.argv[4] == undefined || process.argv[5] == undefined) {
console.log("Usage: node bid.js org userID auctionID price");
process.exit(1);
}
const org = process.argv[2]
const user = process.argv[3];
const auctionID = process.argv[4];
const quantity = process.argv[5];
const price = process.argv[6];
const price = process.argv[5];
if (org == 'Org1' || org == 'org1') {
@ -91,7 +89,7 @@ async function main() {
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);
await bid(ccp,wallet,user,orgMSP,auctionID,price);
}
else if (org == 'Org2' || org == 'org2') {
@ -99,9 +97,9 @@ async function main() {
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);
await bid(ccp,wallet,user,orgMSP,auctionID,price);
} else {
console.log("Usage: node bid.js org userID auctionID quantity price");
console.log("Usage: node bid.js org userID auctionID price");
console.log("Org must be Org1 or Org2");
}
} catch (error) {

View file

@ -23,7 +23,7 @@ function prettyJSONString(inputString) {
}
}
async function createAuction(ccp,wallet,user,auctionID,item,quantity) {
async function createAuction(ccp,wallet,user,auctionID,item) {
try {
const gateway = new Gateway();
@ -38,7 +38,7 @@ async function createAuction(ccp,wallet,user,auctionID,item,quantity) {
let statefulTxn = contract.createTransaction('CreateAuction');
console.log('\n--> Submit Transaction: Propose a new auction');
await statefulTxn.submit(auctionID,item,parseInt(quantity));
await statefulTxn.submit(auctionID,item);
console.log('*** Result: committed');
console.log('\n--> Evaluate Transaction: query the auction that was just created');
@ -55,9 +55,8 @@ 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.argv[4] == undefined || process.argv[5] == undefined) {
console.log("Usage: node createAuction.js org userID auctionID item");
process.exit(1);
}
@ -65,7 +64,6 @@ async function main() {
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') {
@ -73,7 +71,7 @@ async function main() {
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await createAuction(ccp,wallet,user,auctionID,item,quantity);
await createAuction(ccp,wallet,user,auctionID,item);
}
else if (org == 'Org2' || org == 'org2') {
@ -81,9 +79,9 @@ async function main() {
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await createAuction(ccp,wallet,user,auctionID,item,quantity);
await createAuction(ccp,wallet,user,auctionID,item);
} else {
console.log("Usage: node createAuction.js org userID auctionID item quantity");
console.log("Usage: node createAuction.js org userID auctionID item");
console.log("Org must be Org1 or Org2");
}
} catch (error) {

View file

@ -44,7 +44,7 @@ async function addBid(ccp,wallet,user,auctionID,bidID) {
// 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};
let bidData = { objectType: 'bid', price: parseInt(bidJSON.price), org: bidJSON.org, bidder: bidJSON.bidder};
console.log('*** Result: Bid: ' + JSON.stringify(bidData,null,2));
let statefulTxn = contract.createTransaction('RevealBid');

View file

@ -9,7 +9,6 @@ import (
"crypto/sha256"
"encoding/json"
"fmt"
"sort"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
)
@ -23,11 +22,10 @@ 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"`
Winner string `json:"winner"`
Price int `json:"price"`
Status string `json:"status"`
}
@ -35,10 +33,9 @@ type Auction struct {
// 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"`
Bidder string `json:"bidder"`
}
// BidHash is the structure of a private bid
@ -47,17 +44,11 @@ type BidHash struct {
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 {
func (s *SmartContract) CreateAuction(ctx contractapi.TransactionContextInterface, auctionID string, itemsold string) error {
// get ID of submitting client
clientID, err := ctx.GetClientIdentity().GetID()
@ -78,13 +69,12 @@ func (s *SmartContract) CreateAuction(ctx contractapi.TransactionContextInterfac
auction := Auction{
Type: "auction",
ItemSold: itemsold,
Quantity: quantity,
Price: 0,
Seller: clientID,
Orgs: []string{clientOrgID},
PrivateBids: bidders,
RevealedBids: revealedBids,
Winners: []Winners{},
Winner: "",
Status: "open",
}
@ -108,7 +98,7 @@ func (s *SmartContract) CreateAuction(ctx contractapi.TransactionContextInterfac
return nil
}
// Bid is used to add a users bid to the auction. The bid is stored in the private
// Bid is used to add a user's bid to the auction. The bid is stored in the private
// data collection on the peer of the bidder's organization. The function returns
// the transaction ID so that users can identify and query their bid
func (s *SmartContract) Bid(ctx contractapi.TransactionContextInterface, auctionID string) (string, error) {
@ -196,7 +186,7 @@ func (s *SmartContract) SubmitBid(ctx contractapi.TransactionContextInterface, a
return fmt.Errorf("failed to create composite key: %v", err)
}
// get the hash of the bid if found in private collection
// get the hash of the bid stored in private data collection
bidHash, err := ctx.GetStub().GetPrivateDataHash(collection, bidKey)
if err != nil {
return fmt.Errorf("failed to read bid bash from collection: %v", err)
@ -216,7 +206,7 @@ func (s *SmartContract) SubmitBid(ctx contractapi.TransactionContextInterface, a
bidders[bidKey] = NewHash
auctionJSON.PrivateBids = bidders
// Add the bidding organization to the list of participating organization's if it is not already
// Add the bidding organization to the list of participating organizations if it is not already
Orgs := auctionJSON.Orgs
if !(contains(Orgs, clientOrgID)) {
newOrgs := append(Orgs, clientOrgID)
@ -290,7 +280,7 @@ func (s *SmartContract) RevealBid(ctx contractapi.TransactionContextInterface, a
// 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
// check 1: check that the auction is closed. We cannot reveal a
// bid to an open auction
Status := auctionJSON.Status
if Status != "closed" {
@ -332,10 +322,9 @@ func (s *SmartContract) RevealBid(ctx contractapi.TransactionContextInterface, a
// 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"`
Bidder string `json:"bidder"`
}
// unmarshal bid imput
@ -354,14 +343,13 @@ func (s *SmartContract) RevealBid(ctx contractapi.TransactionContextInterface, a
// 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,
Bidder: bidInput.Bidder,
}
// check 4: make sure that the transaction is being submitted is the bidder
if bidInput.Buyer != clientID {
if bidInput.Bidder != clientID {
return fmt.Errorf("Permission denied, client id %v is not the owner of the bid", clientID)
}
@ -468,57 +456,17 @@ func (s *SmartContract) EndAuction(ctx contractapi.TransactionContextInterface,
}
// 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
// determine the highest bid
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 bid.Price > auctionJSON.Price {
auctionJSON.Winner = bid.Bidder
auctionJSON.Price = bid.Price
}
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

View file

@ -70,7 +70,7 @@ func (s *SmartContract) QueryBid(ctx contractapi.TransactionContextInterface, au
}
// check that the client querying the bid is the bid owner
if bid.Buyer != clientID {
if bid.Bidder != clientID {
return nil, fmt.Errorf("Permission denied, client id %v is not the owner of the bid", clientID)
}

View file

@ -1,431 +0,0 @@
## 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
````