Repository: ITPeople-Blockchain/auction Branch: v1.1.0 Commit: 49d510b6741d Files: 30 Total size: 196.0 KB Directory structure: gitextract_0d7q4fb2/ ├── README.md ├── art/ │ ├── artchaincode/ │ │ ├── art_app.go │ │ └── vendor/ │ │ └── itpUtils/ │ │ ├── image_proc_0.6api.go │ │ └── table_1.0api.go │ └── scripts/ │ ├── CloseOpenAuction │ ├── DeployAuction │ ├── InitAuc │ ├── InstAuc │ ├── OpenAuctionRequestForBids │ ├── PostAuctionRequest │ ├── PostItems │ ├── PostUsers │ ├── Script4Demo.sh │ ├── SubmitBids │ ├── SubmitQueries │ ├── TransferItem │ ├── ValidateItemOwnerShip │ ├── auction_e2e_script.sh │ ├── downloadImages.sh │ ├── env.sh │ ├── getaucrequest │ ├── getbids │ ├── getitem │ ├── getitemlog │ ├── getlistofaucs │ ├── getuser │ └── setup.sh ├── docs/ │ ├── Datastructures.txt │ └── index.md └── mkdocs.yml ================================================ FILE CONTENTS ================================================ ================================================ FILE: README.md ================================================ [![Documentation Status](https://readthedocs.org/projects/itpeople-blockchain-auction/badge/?version=latest)](http://itpeople-blockchain-auction.readthedocs.io/en/latest/?badge=latest) #Art Auction Blockchain Application Credits: Ratnakar Asara, Nishi Nidamarty, Ramesh Thoomu, Adam Gordon and Mohan Venkataraman **Disclaimer:** The images used in this sample PoC application have been downloaded from publicly available images on the internet, and the copyright belongs to the respective owners. The usage here is strictly for non-commercial purposes as sample data. We recommend that users create their own sample data as appropriate. All names, addresses and accounts numbers used in the sample data are fictitous. The information provided in this README.md is subject to change. Auction Chaincode is migrated to fabric v1.0 and these changes being tested on **fabric commit#** 5b59e0652f9edf5bd12a6ff7fd2e9991495190fe ##Introduction This Hyperledger/Fabric is an implementation of blockchain technology, leveraging familiar and proven technologies. It is a modular architecture allowing pluggable implementations of various function. It features powerful container technology to host any mainstream language for smart contracts development. Chaincode (smart contracts) or blockchain applications run on the fabric. Chaincode is written in Go language The original intention of this application was to understand how to write a Go application on the Hyperledger/Fabric. This initial version was written to understand the different chaincode api's, the boundary that separates what goes into the blockchain and what lives within the enterprise application, usage of database features, error management etc.

## Application Description This application deals with auctioning ART on the block chain. The blockchain makes sense here as there are many different stakeholders and can leverage the benefits of the "Network Effect". This application deals with the following stake holders: * Buyers and Sellers or Traders (TRD) * Banks (BNK) * Insurance Companies (INS) * Shipping and Forwarding (SHP) * Auction Houses (AH) * Art Dealers (DEL) * Artists (ART) The typical business process is shown below ![Business Process](docs/images/art_process.png) ###Registering Stakeholder Accounts Artists, Traders, Dealers own **Assets** (Items). Auction Houses, Banks, Insurance Companies and Service Providers play a role in the auction process. To conduct business on the block chain, the stakeholder has to open an account on the block chain. In the production world, prior to opening an account, all of the stake-holder details may be authenticated by another blockchain like an IDaaS (Identity-as-a-Service). There are various stake holders as listed above, each with a different interest. ###Registering Assets or Items The Seller (Trader) who owns **Assets** must register the asset on the block chain in order to conduct business. When an **Asset** is submitted for registration, the chaincode does the following: * Checks if the owner has a registered account * Converts any presented "Certificate of Authenticity" or a credibly issued image to a byte stream, generates a key, encrypts the byte stream using the key and stores the image on the BC * Provides the key to the **owner** for safe keeping and future reference * Makes entries into the Item History so that the lifecycle of the Asset can be reviewed at any time ###Making a Request to Auction an Asset When the owner of an Asset sees an opportunity to make money, they would like to auction the Asset. They engage an Auction House and make a **request to auction** the Asset. The request always specifies a **"Reserve Price"**. Sometimes, the owner may additionally specify a **"Buy It Now"** price as well. When the request is made, the item, owner and the auction house are all validated. (The chaincode simply validates that they are all registered on the BC). The Auction House will most likely, behind the scene, get the Asset authenticated and determine the valuation before deciding to accept the item. One of the ways by which they could do some preliminary authentication is to request the **seller** to enter his **private key**, account-id and the registered item number into the client application. While the item number and account identifier is a straight validation, the key will be used to decrypt and retrieve the stored "certificate of authenticity or image". The state of the Auction is set to **INIT** at this point, until the Auction House is ready to **OPEN** the Asset for bids. ###Opening the Auction for Bids The Auction House will choose a time frame to place the item on auction and **OPEN** it up for accepting bids from potential Buyers. They may, if applicable, advertise the **BuyItNow** price. ### "Buy It Now" and Accepting Bids During the window of the auction, potential buyers can place bids. If a Buyer wishes to exercise the "Buy It Now", they can buy the item right away provided there is no bid higher than the "BuyItNow" price. Bids are accepted from buyers if * The Bids have are equal or greater than the **Reserve Price"** * The auction is still **OPEN** * The Buyer has a registered account ### Buy It Now When a buyer chooses this option, the chain code does the following * Validates the Buyer * Checks if there are any bidders whose bid is higher than the **"Buy It Now"** price. If so, the offer is rejected * If the **"Buy It Now"** price is applicable, it immediately **"CLOSES"** the auctions, creates a **transaction** * It assigns the Asset to the new owner * It also generates a new **Key**, re-encrypts the "Certificate of Ownership or Image", and provides the key to the new buyer * The new price of the Asset is set to the **"Buy It Now"** price if not higher ### Auction Expiry When the auction expires, the Auction House retrieves the highest bid and converts it to a **transaction** ( A transaction in the real world could mean creating insurance and shipping docs, collecting payments and commissions, issuing a new title or certificate to the new owner etc.), transfers ownership to the buyer and updates the price with the new **"Hammer"** price. It also generates a new **Key**, re-encrypts the "Certificate of Ownership or Image", and provides the key to the new buyer. ### Transfer an Item to another User The chain code supports this scenario, by allowing a **owner** of an Asset to transfer **"ownership"** to another person. The receiving person has to be registered on the block-chain. Currently the chain code does not execute any regulatory or compliance rules. ### Validating Asset Ownership The chain code supports this. In order to accomplish this, it does preliminary authentication by requesting the **seller** to enter his **private key**, account-id and the registered item number. While the item number and account identifier is a straight validation, the key will be used to decrypt and view the stored **"Certificate of Authenticity or image"**. If decryption fails, then it assumes that the owner is not the legal owner of the Asset. ## APIs Available The following Invoke and Query APIs are available from both CLI and REST, and have the following signature func(stub *shim.ChaincodeStub, function string, args []string) ([]byte, error) ### Invoke * iPostUser * iPostItem * iPostAuctionRequest * iPostTransaction * iPostBid * iOpenAuctionForBids * iBuyItNow * iTransferItem * iCloseAuction * iCloseOpenAuctions ### Query * qGetItem * qGetUser * qGetAuctionRequest * qGetTransaction * qGetBid * qGetLastBid * qGetHighestBid * qGetNoOfBidsReceived * qGetListOfBids * qGetItemLog * qGetItemListByCat * qGetUserListByCat * qGetListOfItemsOnAuc * qGetListOfOpenAucs * qValidateItemOwnership * qIsItemOnAuction ##Environment Setup Please review instructions on setting up the [Development Environment](https://github.com/hyperledger/fabric/blob/master/docs/source/dev-setup/devenv.rst) as well as the setting up the [Sandbox Environment](https://github.com/hyperledger/fabric/blob/master/docs/source/Setup/Chaincode-setup.rst) to execute the chaincode. ## Running the Application ** Fork and clone the fabric** ``` git clone https://github.com/hyperledger/fabric.git cd $GOPATH/src/github.com/hyperledger/fabric Fork Auction git repository from here https://github.com/ITPeople-Blockchain/auction.git and clone from your remote auction repository git clone https://github.com//auction.git ``` Execute below command to clear production data `rm -rf /var/hyperledger/*` ###Terminal 1 Build peer and Orderer binaries and Start Orderer ``` cd $GOPATH/src/github.com/hyperledger/fabric make native orderer ``` ###Terminal 2 Start Peer ``` cd $GOPATH/src/github.com/hyperledger/fabric peer node start -o 127.0.0.1:7050 ``` ###Terminal 3 ``` cd $GOPATH/src/github.com/hyperledger/fabric/auction/art/scripts Run env.sh script to export CORE_PEER_MSPCONFIGPATH `source env.sh` ``` ###Run the following shell scripts The scripts are provided for the convenience of understanding how to use the application as well as testing in your specific cloned environment. The scripts execute all API calls in CLI mode. In CLI mode, the peer and the chaincode live within the same container. However, these scripts will not work well in NET mode. To test the application in NET mode, follow the instructions on using the UI to make the API calls. #### PostUsers The PostUsers script inserts a set of users into the database. Today, the only validation done is to check if the user ID is an integer. TODO: In a future version, the user identity will be validated against the IDaaS Blockchain prior to inserting into the database `./PostUsers` #### PostItems The PostItems script inserts a set of ART ASSETS into the database. Before inserting the asset the chaincode checks if the CurrentOwner is registered as a User. Based on the image file name (in future this could be a title or some ownership document) is retrieved and converted to a byte array ([]byte). An AES Key is generated, the byte array is encrypted and both key and the byte array are saved in the database.A log entry is made in the Item Log. Please see code for detailed comments `./PostItems` In the business process, the owner (User ID# 100) of the ASSET (Item# 1000) requests an entity like an Auction House (User ID# 200) to put the item on auction. Before Posting the auction request, the Asset is validated against the database. The Auction House ID is verified in the User Table. A log entry is made in the Item Log. TODO: In future, the owner of the asset will present his key to help with validation. The AES key will be used to un-encrypt the stored image and authenticate ASSET ownership. #### PostAuctionRequest When the ASSET OWNER of an item is ready to place his item on auction, he/she would identify an Auction House, determine what the reserve price should be and send a request to the Auction House expressing interest in placing their item on the auction block. `./PostAuctionRequest` #### OpenAuctionRequestForBids The Auction House, we assume will inspect the physical item, the certificate of authenticity, the ownership key and other details. They would also run a valuation of the item to determine if the reserve price is valid. The application assumes these have occurred outside of the scope of the application Even though the ASSET OWNER has requested the Auction House to place the item on auction, the Auction is not yet open for acceptance of user bids. Hence any bid submitted against the item will be rejected if the auction is not open This script opens the Auction Request for bids. It sets the status of the AuctionRequest to "OPEN". It opens a timer for the duration of the auction which in the example is 3 minutes. During this window, any user can submit bids against the AuctionID. Once the timer expires, a script is created and saved called "CloseAuction.sh". The script gets triggered. ##### CloseAuction The CloseAuction.sh script invokes CloseAuction. CloseAuction will first change the status of the AuctionRequest to "CLOSED". It then fetches the highest bid from the list of bids received, and converts it to a Transaction. The transaction is posted, the ASSET is retrieved from the database, its price is set to the new Hammer Price and the CurrentOwner is set to the new buyer. The ASSET image is un-encrypted with the old key, a new Key is generated and the image is encrypted with the new key. The ASSET is updated in the database. An log entry is made in the Item Log. TODO: In future, the Transaction will be a business document that triggers payments, shipping,insurance and commissions `./OpenAuctionRequestForBids` Opens the auction request for bids for 3 minutes - Auction Request ID used for testing is 1111 and Item 1000 This opens a timer for 3 minutes and once timer expires, writes a shell script to invoke CloseAuction... As described above, once the auction is "OPEN", this script submits bids against that auctionID. Both the auctionID and the buyerID are validated before the bid is posted. Once the auction is "CLOSED", new bids will be rejected `./Submitbids` submits a series of bids against auction# 1111 and item# 1000 `./SubmitQueries` This is list of queries that can be issued and must be used via cut and paste on command line (CLI) After the timer expires, the Close auction should get invoked and the highest bid should be posted as a transaction ## Invoke APIs and usage **PostUser**: This function is used to register an Account for any of the stakeholders defined earlier **Usage (CLI mode)** The call takes 9 arguments: User ID, Record Type, Name, Type, Address, Phone, Email, Bank Name, Account#, Routing# peer chaincode invoke -l golang -n mycc -c '{"Function": "PostUser", "Args":["100", "USER", "Ashley Hart", "TRD", "Morrisville Parkway, #216, Morrisville, NC 27560", "9198063535", "ashley@itpeople.com", "SUNTRUST", "00017102345", "0234678"]}' **PostItem**: This function is used to register an Asset with the blockchain. The owner of the Asset must have a registered account prior to registering the Asset. No validation of the "User Type" is done by the chaincode and should be managed outside by the client. The name of the image (f6.png) is uploaded by the chaincode from a pre-determined directory, converted to []byte, encrypted using AES and stored in the blockchain. **Usage (CLI mode)** The call takes 12 arguments: Item ID, Record Type, Description, Detail, Date of Origin, Original or Reprint, Subject, Media, Size, Image File, Price, Current Owner ID peer chaincode invoke -l golang -n mycc -c '{"Function": "PostItem", "Args":["1400", "ARTINV", "Nature", "James Thomas", "19900115", "Original", "modern", "Water Color", "12 x 17 in", "f6.png","1800", "100"]}' **PostAuctionRequest**: This function is used by the owner of an Asset to request an Auction House to accept the Asset for auction. The Auction House ID, the owner ID and the asset ID are all validated before a request can be posted. Posting a request does not mean bids can be accepted. The auction has to be opened in order for bids to be accepted or "Buy It Now" to happen. **Usage (CLI mode)** This call takes 11 argumnets: Auction ID, Record Type, Item ID, Auction House ID, Owner ID, Date of Request, Reserve Price, Buy-It-Now Price, Status, Dummy Open Date, Dummy Close Date peer chaincode invoke -l golang -n mycc -c '{"Function": "PostAuctionRequest", "Args":["1113", "AUCREQ", "1000", "200", "400", "04012016", "15000", "16000", "INIT", "2016-05-20 11:00:00.3 +0000 UTC","2016-05-23 11:00:00.3 +0000 UTC"]}' The Auction Opendate and CloseDate are dummy dates and will be set when the auction is opened. The Auction ID must be unique and cannot be repeated. We assume that the client will generate a unique auction id prior to posting the request. The state of the Auction is "INIT" at this point. **OpenAuctionForBids**: This function is assumed to be invoked by the role of "Auction House" which is one of the types of accounts registered using PostUser. It allows the auctioner to open an auction for bids. The auction request must be in "INIT" state to be "OPEN"ed. When the auction is opened for bids, both the open and close date and time are set. The following example opens the bid for 3 minutes. Auction open durations are currently provided in minutes to support testing. **Usage (CLI mode)** The call takes 3 arguments: Auction ID, Record Type, Duration in Minutes peer chaincode invoke -l golang -n mycc -c '{"Function": "OpenAuctionForBids", "Args":["1111", "OPENAUC", "3"]}' **PostBid**: This function allows a potential buyer to bid on the Asset once the auction is open. Every bid is checked for valid auction ID, asset ID and buyer ID. Bids must be higher than reserve price. Bids are accepted as long as the auction is "OPEN". Bid numbers must be unique and generated by the client. **Usage (CLI mode)** This call takes 6 arguments: Auction ID, Record Type, Bid Number, Item ID, Buyer ID, Buyer Offer Price peer chaincode invoke -l golang -n mycc -c '{"Function": "PostBid", "Args":["1111", "BID", "5", "1000", "400", "5000"]}' **PostTransaction**: Even though the Transaction is automatically generated and posted when one of the following events occur: * Auction closes after the auction timer expires (Only CLI Mode) * A message to close all expired auctions are close is issued by the client via "CloseOpenAuctions" * A "BuyItNow" call is made which goes through this function is exposed as a matter of convenience just in case transactions don't get posted properly. When a Transaction is posted, it updates the Asset with the new buyer ID, re-generates a new key and encrypts the image/certificate and updates the asset price to the "Hammer Price". It also adds a new entry into the item history table. **Usage (CLI mode)** This call takes 8 arguments: Auction ID, Record Type, Item ID, Transaction Type, Buyer ID, Transaction Time, Hammer Time, Hammer Price peer chaincode invoke -l golang -n mycc -c '{"Function": "PostBid", "Args":["1111", "POSTTRAN", "1000", "SALE","500", "2016-05-24 11:00:00","2016-05-23 14:25:00", "12000"]}' **BuyItNow**: Sometimes, an asset owner can specify a "Buy It Now" price in addition to the "Reserve Price" while posting an auction request. If an asset has a "Buy It Now" price, a buyer can issue a "BuyItNow" call. If there are no bids higher than the "BuyItNow" price, the request will be rejected. If the request goes through, the auction is closed and a transaction is posted against the buyer. **Usage (CLI mode)** This call is similar to the PostBid, except the price is set to the Buy-It-Now price. peer chaincode invoke -l golang -n mycc -c '{"Function": "BuyItNow", "Args":["1111", "BID", "1", "1000", "300", "1800"]}' **TransferItem**: At any time, the current owner of an asset can transfer the item at no additional cost or change in value to another user, provided the asset is not initiated for auctions, or is in the process of an auction. Smart contract rules may have to be written to comply with local regulations and taxes. The current owner has to prove ownership by providing his key. The owner id, item id and the key are all validated before transfer can take place. The new owner will receive a new key. **Usage (CLI mode)** This call takes 5 arguments: Item ID, Current Owner ID, Owner Key, Transferee ID, Record Type peer chaincode invoke -l golang -n mycc -c '{"Function": "TransferItem", "Args": ["1000", "100", "218MC/ipIsIrDhE9TKXqG2NsWl7KSE59Y3UmwBzSrQo=", "300", "XFER"]}' **CloseAuction**: This function call closes an auction once the time expires. This call is automatically issued in "CLI" mode since the "OpenAuctionForBids" call triggers a go routine that sleeps for the duration of the auction and the automatically issues a "CloseAuction" call. This functions closes the auction request, picks the highest bid and creates a transaction an then posts the transaction. **Usage (CLI mode)** This call takes two arguments: Auction ID, Record Type peer chaincode invoke -l golang -n mycc -c '{"Function": "CloseAuction", "Args": ["1111","AUCREQ"]}' **CloseOpenAuctions**: This is a function designed specifically for Bluemix situations where the peer and the chaincode run in different containers, hence a call as shown in each of "Usage (CLI mode)" will not work. This function is issued periodically by the client (UI or applications consuming the blockchain) as a REST call. The functions checks for auctions whose time has run-out and closes them. **Usage (CLI mode)** This call takes two arguments: 2016, Record Type peer chaincode invoke -l golang -n mycc -c '{"Function": "CloseOpenAuctions", "Args": ["2016", "CLAUC"]}' ###Query APIs and Usage **GetItem**: Retrieves an Asset record by asset ID. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetItem", "Args": ["1000"]}' **GetUser**: Retrieves an user record by user or stakeholder ID. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetUser", "Args": ["100"]}' **GetAuctionRequest**: Retrieves an auction request by auction request ID. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetAuctionRequest", "Args": ["1111"]}' **GetTransaction**: Retrieves an transaction posted against an auction by auction request ID and asset ID. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetTransaction", "Args": ["1111", "1000"]}' **GetBid**: Retrieves a single bid by auction ID and bid number **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetBid", "Args": ["1111", "5"]}' **GetLastBid**: Retrieves the last submitted bid. Since bids are submitted in random , and the only requirement is that the bid price be higher than the reserve price, the last received bid need not be the highest bid. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetLastBid","Args": ["1111"]}' **GetHighestBid**: Retrieves the highest bid submitted against the auction thus far. If the auction has expired, then the highest bid is the highest bid for the auction. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetHighestBid", "Args": ["1111"]}' **GetNoOfBidsReceived**: Retrieves the total number of bids received at any point in time. If the auction has expired, it represents the total number of bids received against that auction. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetNoOfBidsReceived", "Args": ["1111"]}' **GetListOfBids**: Retrieves all the bids received against an auction. each row in the list represents a bid object. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetListOfBids", "Args": ["1111"]}' **GetItemLog**: Retrieves the history of an asset. The log is updated when an asset is registered, put on auction, post auction, transfered etc. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetItemLog","Args": ["1000"]}' **GetItemListByCat**: Retrieves a list of assets by asset category. If only the first key is provided, the query retrieves all assets. "2016" is hard-coded as a fixed first key. This is a band-aid solution to retrieve all records. This is a band-aid solution to retrieve all records. The following query retrieves all assets of category "modern". **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetItemListByCat","Args": ["2016", "modern"]}' **GetUserListByCat**: Retrieves a list of stakeholders or account holders by stakeholder type. If only the first key is provided, the query retrieves all assets. "2016" is hard-coded as a fixed first key. This is a band-aid solution to retrieve all records. The following query retrieves all stakeholders of type "AH" or auction houses. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetItemListByCat","Args": ["2016", "AH"]}' **GetListOfInitAucs**: This query retrieves all assets which have been submitted for auction. Their status is "Init". The "2016" is a fixed key to denote all auctions in 2016. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetListOfInitAucs","Args": ["2016"]}' **GetListOfOpenAucs**: This query retrieves a list of all assets whose auctions have been "OPEN"ed for receiving bids. The "2016" is a fixed key to denote all auctions in 2016. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetListOfOpenAucs", "Args": ["2016"]}' **ValidateItemOwnership**: Validates the ownership of an asset. Checks for valid account id, asset id and retrieves asset from blockchain using the owners's key. The arguments are Item ID, Current Owner ID and Owner's Key. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "ValidateItemOwnership", "Args": ["1000", "500", "avQX6JfTnELAY4mkRhOr8P7vmz0H3aAIuFGsGiSD5UQ="]}' **IsItemOnAuction**: Checks whether an Asset has an **auction request** posted, or currently on auction and returns a boolean true or false. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "IsItemOnAuction", "Args": ["1999", "VERIFY"]}' ##Notes * Once an auction request is posted or an auction is open for bids, there is no api to remove the auction request or close the auction prematurely and rejecting all bids received * In the current version, the image is encrypted and stored in the blockchain. However, in future, it is envisioned that only the hash of the image along with the URI to the location of the image will be saved ##Runnning the Application using the Web Browser The chaincode functions can be accessed via the browser, Please refer [auction-app](https://github.com/ITPeople-Blockchain/auction-app) for more details. ================================================ FILE: art/artchaincode/art_app.go ================================================ /****************************************************************** Copyright IT People Corp. 2017 All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ******************************************************************/ /////////////////////////////////////////////////////////////////////// // Author : IT People - Mohan - table API for v1.0 // Enable CouchDb as the database.. // Purpose: Explore the Hyperledger/fabric and understand // how to write an chain code, application/chain code boundaries // The code is not the best as it has just hammered out in a day or two // Feedback and updates are appreciated /////////////////////////////////////////////////////////////////////// package main import ( "encoding/json" "errors" "fmt" "itpUtils" "os" "net/http" "io" "runtime" "strconv" "time" "github.com/hyperledger/fabric/core/chaincode/shim" pb "github.com/hyperledger/fabric/protos/peer" ) /////////////////////////////////////////////////////////////////////////////////////// // This creates a record of the Asset (Inventory) // Includes Description, title, certificate of authenticity or image whatever..idea is to checkin a image and store it // in encrypted form // Example: // Item { 113869, "Flower Urn on a Patio", "Liz Jardine", "10102007", "Original", "Floral", "Acrylic", "15 x 15 in", "sample_9.png","$600", "My Gallery } /////////////////////////////////////////////////////////////////////////////////////// type ItemObject struct { ItemID string RecType string ItemDesc string ItemDetail string // Could included details such as who created the Art work if item is a Painting ItemDate string ItemType string ItemSubject string ItemMedia string ItemSize string ItemPicFN string ItemImage []byte // This has to be generated AES encrypted using the file name AES_Key []byte // This is generated by the AES Algorithms ItemImageType string // should be used to regenerate the appropriate image type ItemBasePrice string // Reserve Price at Auction must be greater than this price CurrentOwnerID string // This is validated for a user registered record TimeStamp string // This is the time stamp } //////////////////////////////////////////////////////////////////////////////// // Has an item entry every time the item changes hands //////////////////////////////////////////////////////////////////////////////// type ItemLog struct { ItemID string // PRIMARY KEY Status string // SECONDARY KEY - OnAuc, OnSale, NA AuctionedBy string // SECONDARY KEY - Auction House ID if applicable RecType string // ITEMHIS ItemDesc string CurrentOwner string Date string // Date when status changed } ///////////////////////////////////////////////////////////// // Create Buyer, Seller , Auction House, Authenticator // Could establish valid UserTypes - // AH (Auction House) // TR (Buyer or Seller) // AP (Appraiser) // IN (Insurance) // BK (bank) // SH (Shipper) ///////////////////////////////////////////////////////////// type UserObject struct { UserID string RecType string // Type = USER Name string UserType string // Auction House (AH), Bank (BK), Buyer or Seller (TR), Shipper (SH), Appraiser (AP) Address string Phone string Email string Bank string AccountNo string RoutingNo string Timestamp string } ///////////////////////////////////////////////////////////////////////////// // Register a request for participating in an auction // Usually posted by a seller who owns a piece of ITEM // The Auction house will determine when to open the item for Auction // The Auction House may conduct an appraisal and genuineness of the item ///////////////////////////////////////////////////////////////////////////// type AuctionRequest struct { AuctionID string RecType string // AUCREQ ItemID string AuctionHouseID string // ID of the Auction House managing the auction SellerID string // ID Of Seller - to verified against the Item CurrentOwnerId RequestDate string // Date on which Auction Request was filed ReservePrice string // reserver price > previous purchase price BuyItNowPrice string // 0 (Zero) if not applicable else specify price Status string // INIT, OPEN, CLOSED (To be Updated by Trgger Auction) OpenDate string // Date on which auction will occur (To be Updated by Trigger Auction) CloseDate string // Date and time when Auction will close (To be Updated by Trigger Auction) TimeStamp string // The transaction Date and Time } ///////////////////////////////////////////////////////////// // POST the transaction after the Auction Completes // Post an Auction Transaction // Post an Updated Item Object // Once an auction request is opened for auctions, a timer is kicked // off and bids are accepted. When the timer expires, the highest bid // is selected and converted into a Transaction // This transaction is a simple view ///////////////////////////////////////////////////////////// type ItemTransaction struct { AuctionID string RecType string // POSTTRAN ItemID string TransType string // Sale, Buy, Commission UserId string // Buyer or Seller ID TransDate string // Date of Settlement (Buyer or Seller) HammerTime string // Time of hammer strike - SOLD HammerPrice string // Total Settlement price Details string // Details about the Transaction } //////////////////////////////////////////////////////////////// // This is a Bid. Bids are accepted only if an auction is OPEN //////////////////////////////////////////////////////////////// type Bid struct { AuctionID string RecType string // BID BidNo string ItemID string BuyerID string // ID Of Buyer - to be verified against the Item CurrentOwnerId BidPrice string // BidPrice > Previous Bid BidTime string // Time the bid was received } ////////////////////////////////////////////////////////////// // Invoke Functions based on Function name // The function name gets resolved to one of the following calls // during an invoke // ////////////////////////////////////////////////////////////// func InvokeFunction(fname string) func(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { InvokeFunc := map[string]func(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response{ "iPostItem": PostItem, "iPostUser": PostUser, "iPostAuctionRequest": PostAuctionRequest, "iPostTransaction": PostTransaction, "iPostBid": PostBid, "iOpenAuctionForBids": OpenAuctionForBids, "iBuyItNow": BuyItNow, "iTransferItem": TransferItem, "iCloseAuction": CloseAuction, "iCloseOpenAuctions": CloseOpenAuctions, } return InvokeFunc[fname] } ////////////////////////////////////////////////////////////// // Query Functions based on Function name // ////////////////////////////////////////////////////////////// func QueryFunction(fname string) func(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { QueryFunc := map[string]func(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response{ "qGetItem": GetItem, "qGetUser": GetUser, "qGetAuctionRequest": GetAuctionRequest, "qGetTransaction": GetTransaction, "qGetBid": GetBid, "qGetLastBid": GetLastBid, "qGetHighestBid": GetHighestBid, "qGetNoOfBidsReceived": GetNoOfBidsReceived, "qGetListOfBids": GetListOfBids, "qGetItemLog": GetItemLog, "qGetItemListByCat": GetItemListByCat, "qGetUserListByCat": GetUserListByCat, "qGetListOfInitAucs": GetListOfInitAucs, "qGetListOfOpenAucs": GetListOfOpenAucs, "qValidateItemOwnership": ValidateItemOwnership, } return QueryFunc[fname] } ///////////////////////////////////////////////////////////////////////////////////////////////////// // A Map that holds ObjectNames and the number of Keys // This information is used to dynamically Create, Update // Replace , and Query the Ledger // In this model all attributes in a table are strings // The chain code does both validation // A dummy key like 2016 in some cases is used for a query to get all rows // // "User": 1, Key: UserID // "Item": 1, Key: ItemID // "UserCat": 3, Key: "2016", UserType, UserID // "ItemCat": 3, Key: "2016", ItemSubject, ItemID // "Auction": 1, Key: AuctionID // "AucInit": 2, Key: Year, AuctionID // "AucOpen": 2, Key: Year, AuctionID // "Trans": 2, Key: AuctionID, ItemID // "Bid": 2, Key: AuctionID, BidNo // "ItemHistory": 4, Key: ItemID, Status, AuctionHouseID(if applicable),date-time // // The additional key is the ObjectType (aka ObjectName or Object). The keys would be // keys: {"picname", "https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/art1.png"} ///////////////////////////////////////////////////////////////////////////////////////////////////// //func GetPictureUrl(picname string) string { var PictureMap = map[string]string { "art1.png": "https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/art1.png", "art2.png": "https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/art2.png", "art3.png": "https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/art3.png", "art4.png": "https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/art4.png", "art5.png": "https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/art5.png", "art6.png": "https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/art6.png", "art7.png": "https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/art7.png", "item-001.jpg": "https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/item-001.jpg", "item-002.jpg": "https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/item-002.jpg", "item-003.jpg": "https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/item-003.jpg", "item-004.jpg": "https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/item-004.jpg", "item-005.jpg": "https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/item-005.jpg", "item-006.jpg": "https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/item-006.jpg", "item-007.jpg": "https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/item-007.jpg", "item-008.jpg": "https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/item-008.jpg", "people.gif": "https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/people.gif", "mad-fb.jpg": "https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/mad-fb.gif", "sample.png": "https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/sample.png", } // return PictureMap[picname] //} //var myLogger = logging.MustGetLogger("auction_trading") type SimpleChaincode struct { } //////////////////////////////////////////////////////////////////////////////// // Chain Code Kick-off Main function //////////////////////////////////////////////////////////////////////////////// func main() { // maximize CPU usage for maximum performance runtime.GOMAXPROCS(runtime.NumCPU()) fmt.Println("Starting Item Auction Application chaincode BlueMix ver 21 Dated 2016-07-02 09.45.00: ") //ccPath = fmt.Sprintf("%s/src/github.com/hyperledger/fabric/auction/art/artchaincode/", gopath) // Start the shim -- running the fabric err := shim.Start(new(SimpleChaincode)) if err != nil { fmt.Println("Error starting Item Fun Application chaincode: %s", err) } } func downloadFile(filepath string, url string) (err error) { // Create the file out, err := os.Create(filepath) if err != nil { return err } defer out.Close() // Get the data resp, err := http.Get(url) if err != nil { return err } defer resp.Body.Close() // Writer the body to file _, err = io.Copy(out, resp.Body) if err != nil { return err } return nil } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // SimpleChaincode - Init Chaincode implementation - The following sequence of transactions can be used to test the Chaincode //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response { // TODO - Include all initialization to be complete before Invoke and Query // Uses aucTables to delete tables if they exist and re-create them //myLogger.Info("[Trade and Auction Application] Init") fmt.Println("[Trade and Auction Application] Init") var err error // TODO: could we rather save the hash of the picture on the BC ? for k, v := range PictureMap { fmt.Printf("\n Downloading Image '%s' from URL: %s", k, v) err = downloadFile(k, v) if err != nil { fmt.Println(err) return shim.Error("Invoke: Invalid Function Name - function names begin with a q or i") } } fmt.Println("\nInit() Initialization Complete ") return shim.Success(nil) } //////////////////////////////////////////////////////////////// // SimpleChaincode - INVOKE Chaincode implementation // User Can Invoke // - Register a user using PostUser // - Register an item using PostItem // - The Owner of the item (User) can request that the item be put on auction using PostAuctionRequest // - The Auction House can request that the auction request be Opened for bids using OpenAuctionForBids // - One the auction is OPEN, registered buyers (Buyers) can send in bids vis PostBid // - No bid is accepted when the status of the auction request is INIT or CLOSED // - Either manually or by OpenAuctionRequest, the auction can be closed using CloseAuction // - The CloseAuction creates a transaction and invokes PostTransaction //////////////////////////////////////////////////////////////// func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { function, args := stub.GetFunctionAndParameters() fmt.Println("==========================================================") fmt.Println("BEGIN Function ====> ", function) if function[0:1] == "i" { fmt.Println("==========================================================") return t.invoke(stub, function, args) } if function[0:1] == "q" { fmt.Println("==========================================================") return t.query(stub, function, args) } fmt.Println("==========================================================") return shim.Error("Invoke: Invalid Function Name - function names begin with a q or i") } //////////////////////////////////////////////////////////////// // SimpleChaincode - INVOKE Chaincode implementation // User Can Invoke // - Register a user using PostUser // - Register an item using PostItem // - The Owner of the item (User) can request that the item be put on auction using PostAuctionRequest // - The Auction House can request that the auction request be Opened for bids using OpenAuctionForBids // - One the auction is OPEN, registered buyers (Buyers) can send in bids vis PostBid // - No bid is accepted when the status of the auction request is INIT or CLOSED // - Either manually or by OpenAuctionRequest, the auction can be closed using CloseAuction // - The CloseAuction creates a transaction and invokes PostTransaction //////////////////////////////////////////////////////////////// func (t *SimpleChaincode) invoke(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Check Type of Transaction and apply business rules // before adding record to the block chain // In this version, the assumption is that args[1] specifies recType for all defined structs // Newer structs - the recType can be positioned anywhere and ChkReqType will check for recType // example: // ./peer chaincode invoke -l golang -n mycc -c '{"Function": "PostBid", "Args":["1111", "BID", "1", "1000", "300", "1200"]}' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// if itpUtils.ChkRecType(args) == true { InvokeRequest := InvokeFunction(function) if InvokeRequest != nil { response := InvokeRequest(stub, function, args) return (response) } } else { fmt.Println("Invoke() Invalid recType : ", args, "\n") error_str := "Invoke() : Invalid recType : " + args[0] return shim.Error(error_str) } return shim.Success(nil) } ////////////////////////////////////////////////////////////////////////////////////////// // SimpleChaincode - query Chaincode implementation // Client Can Query // Sample Data // ./peer chaincode query -l golang -n mycc -c '{"Function": "GetUser", "Args": ["4000"]}' // ./peer chaincode query -l golang -n mycc -c '{"Function": "GetItem", "Args": ["2000"]}' ////////////////////////////////////////////////////////////////////////////////////////// func (t *SimpleChaincode) query(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { // var buff []byte var response pb.Response fmt.Println("Query() : ID Extracted and Type = ", args[0]) fmt.Println("Query() : Args supplied : ", args) if len(args) < 1 { fmt.Println("Query() : Include at least 1 arguments Key ") return shim.Error("Query() : Expecting Transation type and Key value for query") } QueryRequest := QueryFunction(function) if QueryRequest != nil { response = QueryRequest(stub, function, args) } else { fmt.Println("Query() Invalid function call : ", function) response_str := "Query() : Invalid function call : " + function return shim.Error(response_str) } if response.Status != shim.OK { fmt.Println("Query() Object not found : ", args[0]) response_str := "Query() : Object not found : " + args[0] return shim.Error(response_str) } return response } ////////////////////////////////////////////////////////////////////////////////////////// // Retrieve User Information // example: // ./peer chaincode query -l golang -n mycc -c '{"Function": "GetUser", "Args": ["100"]}' // ////////////////////////////////////////////////////////////////////////////////////////// func GetUser(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { var err error // Get the Object and Display it Avalbytes, err := itpUtils.QueryObject(stub, "User", args) if err != nil { fmt.Println("GetUser() : Failed to Query Object ") jsonResp := "{\"Error\":\"Failed to get Object Data for " + args[0] + "\"}" return shim.Error(jsonResp) } if Avalbytes == nil { fmt.Println("GetUser() : Incomplete Query Object ") jsonResp := "{\"Error\":\"Incomplete information about the key for " + args[0] + "\"}" return shim.Error(jsonResp) } fmt.Println("GetUser() : Response : Successfull -") return shim.Success(Avalbytes) } ///////////////////////////////////////////////////////////////////////////////////////// // Query callback representing the query of a chaincode // Retrieve a Item by Item ID // itpUtils.QueryObjectWithProcessingFunction takes a post processing function as argument // peer chaincode query -l golang -n mycc -c '{"Args": ["qGetItem", "1000"]} // ///////////////////////////////////////////////////////////////////////////////////////// func GetItem(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { var err error // Get the Objects and Display it Avalbytes, err := itpUtils.QueryObjectWithProcessingFunction(stub, "Item", args, ProcessQueryResult) if err != nil { fmt.Println("GetItem() : Failed to Query Object ") jsonResp := "{\"Error\":\"Failed to get Object Data for " + args[0] + "\"}" return shim.Error(jsonResp) } if Avalbytes == nil { fmt.Println("GetItem() : Incomplete Query Object ") jsonResp := "{\"Error\":\"Incomplete information about the key for " + args[0] + "\"}" return shim.Error(jsonResp) } fmt.Println("GetItem() : Response : Successfull ") return shim.Success(Avalbytes) } ///////////////////////////////////////////////////////////////////////////////////////// // Validates The Ownership of an Asset using ItemID, OwnerID, and HashKey // // ./peer chaincode query -l golang -n mycc -c '{"Function": "ValidateItemOwnership", "Args": ["1000", "100", "tGEBaZuKUBmwTjzNEyd+nr/fPUASuVJAZ1u7gha5fJg="]}' // ///////////////////////////////////////////////////////////////////////////////////////// func ValidateItemOwnership(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { var err error if len(args) < 3 { fmt.Println("ValidateItemOwnership() : Requires 3 arguments Item#, Owner# and Key ") return shim.Error("ValidateItemOwnership() : Requires 3 arguments Item#, Owner# and Key") } // Get the Object Information Avalbytes, err := itpUtils.QueryObject(stub, "Item", []string{args[0]}) if err != nil { fmt.Println("ValidateItemOwnership() : Failed to Query Object ") jsonResp := "{\"Error\":\"Failed to get Object Data for " + args[0] + "\"}" return shim.Error(jsonResp) } if Avalbytes == nil { fmt.Println("ValidateItemOwnership() : Incomplete Query Object ") jsonResp := "{\"Error\":\"Incomplete information about the key for " + args[0] + "\"}" return shim.Error(jsonResp) } myItem, err := JSONtoAR(Avalbytes) if err != nil { fmt.Println("ValidateItemOwnership() : Failed to Query Object ") jsonResp := "{\"Error\":\"Failed to get Object Data for " + args[0] + "\"}" return shim.Error(jsonResp) } myKey := GetKeyValue(Avalbytes, "AES_Key") fmt.Println("Key String := ", myKey) if myKey != args[2] { fmt.Println("ValidateItemOwnership() : Key does not match supplied key ", args[2], " - ", myKey) jsonResp := "{\"Error\":\"ValidateItemOwnership() : Key does not match asset owner supplied key " + args[0] + "\"}" return shim.Error(jsonResp) } if myItem.CurrentOwnerID != args[1] { fmt.Println("ValidateItemOwnership() : ValidateItemOwnership() : Owner-Id does not match supplied ID ", args[1]) jsonResp := "{\"Error\":\"ValidateItemOwnership() : Owner-Id does not match supplied ID " + args[0] + "\"}" return shim.Error(jsonResp) } fmt.Print("ValidateItemOwnership() : Response : Successfull - \n") return shim.Success(Avalbytes) } ///////////////////////////////////////////////////////////////////////////////////////////////////// // Retrieve Auction Information // This query runs against the AuctionTable // ./peer chaincode query -l golang -n mycc -c '{"Function": "GetAuctionRequest", "Args": ["1111"]}' // There are two other tables just for query purposes - AucInitTable, AucOpenTable // ///////////////////////////////////////////////////////////////////////////////////////////////////// func GetAuctionRequest(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { var err error // Get the Objects and Display it Avalbytes, err := itpUtils.QueryObject(stub, "Auction", args) if err != nil { fmt.Println("GetAuctionRequest() : Failed to Query Object ") jsonResp := "{\"Error\":\"Failed to get Object Data for " + args[0] + "\"}" return shim.Error(jsonResp) } if Avalbytes == nil { fmt.Println("GetAuctionRequest() : Incomplete Query Object ") jsonResp := "{\"Error\":\"Incomplete information about the key for " + args[0] + "\"}" return shim.Error(jsonResp) } fmt.Println("GetAuctionRequest() : Response : Successfull - \n") return shim.Success(Avalbytes) } /////////////////////////////////////////////////////////////////////////////////////////////////// // Retrieve a Bid based on two keys - AucID, BidNo // A Bid has two Keys - The Auction Request Number and Bid Number // ./peer chaincode query -l golang -n mycc -c '{"Function": "GetLastBid", "Args": ["1111", "1"]}' // /////////////////////////////////////////////////////////////////////////////////////////////////// func GetBid(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { var err error // Check there are 2 Arguments provided as per the the struct - two are computed // See example if len(args) < 2 { fmt.Println("GetBid(): Incorrect number of arguments. Expecting 2 ") fmt.Println("GetBid(): ./peer chaincode query -l golang -n mycc -c '{\"Function\": \"GetBid\", \"Args\": [\"1111\",\"6\"]}'") return shim.Error("GetBid(): Incorrect number of arguments. Expecting 2 ") } // Get the Objects and Display it Avalbytes, err := itpUtils.QueryObject(stub, "Bid", args) if err != nil { fmt.Println("GetBid() : Failed to Query Object ") jsonResp := "{\"Error\":\"Failed to get Object Data for " + args[0] + "\"}" return shim.Error(jsonResp) } if Avalbytes == nil { fmt.Println("GetBid() : Incomplete Query Object ") jsonResp := "{\"Error\":\"Incomplete information about the key for " + args[0] + "\"}" return shim.Error(jsonResp) } fmt.Println("GetBid() : Response : Successfull -") return shim.Success(Avalbytes) } /////////////////////////////////////////////////////////////////////////////////////////////////// // Retrieve Auction Closeout Information. When an Auction closes // The highest bid is retrieved and converted to a Transaction // ./peer chaincode query -l golang -n mycc -c '{"Function": "GetTransaction", "Args": ["1111"]}' // /////////////////////////////////////////////////////////////////////////////////////////////////// func GetTransaction(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { //var err error // Get the Objects and Display it Avalbytes, err := itpUtils.QueryObject(stub, "Trans", args) if Avalbytes == nil { fmt.Println("GetTransaction() : Incomplete Query Object ") jsonResp := "{\"Error\":\"Incomplete information about the key for " + args[0] + "\"}" return shim.Error(jsonResp) } if err != nil { fmt.Println("GetTransaction() : Failed to Query Object ") jsonResp := "{\"Error\":\"Failed to get Object Data for " + args[0] + "\"}" return shim.Error(jsonResp) } fmt.Println("GetTransaction() : Response : Successfull") return shim.Success(Avalbytes) } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Create a User Object. The first step is to have users // registered // There are different types of users - Traders (TRD), Auction Houses (AH) // Shippers (SHP), Insurance Companies (INS), Banks (BNK) // While this version of the chain code does not enforce strict validation // the business process recomends validating each persona for the service // they provide or their participation on the auction blockchain, future enhancements will do that // ./peer chaincode invoke -l golang -n mycc -c '{"Function": "PostUser", "Args":["100", "USER", "Ashley Hart", "TRD", "Morrisville Parkway, #216, Morrisville, NC 27560", "9198063535", "ashley@itpeople.com", "SUNTRUST", "00017102345", "0234678"]}' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// func PostUser(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { record, err := CreateUserObject(args[0:]) // if err != nil { return shim.Error(err.Error()) } buff, err := UsertoJSON(record) // if err != nil { error_str := "PostuserObject() : Failed Cannot create object buffer for write : " + args[1] fmt.Println(error_str) return shim.Error(error_str) } else { // Update the ledger with the Buffer Data // err = stub.PutState(args[0], buff) keys := []string{args[0]} err = itpUtils.UpdateObject(stub, "User", keys, buff) if err != nil { fmt.Println("PostUser() : write error while inserting record") return shim.Error("PostUser() : write error while inserting record : Error - " + err.Error()) } // Post Entry into UserCat- i.e. User Category Table keys = []string{"2016", args[3], args[0]} err = itpUtils.UpdateObject(stub, "UserCat", keys, buff) if err != nil { error_str := "PostUser() : write error while inserting recordinto UserCat" fmt.Println(error_str) return shim.Error(error_str) } } return shim.Success(buff) } func CreateUserObject(args []string) (UserObject, error) { var err error var aUser UserObject // Check there are 11 Arguments if len(args) != 11 { fmt.Println("CreateUserObject(): Incorrect number of arguments. Expecting 11 ") return aUser, errors.New("CreateUserObject() : Incorrect number of arguments. Expecting 11 ") } // Validate UserID is an integer _, err = strconv.Atoi(args[0]) if err != nil { return aUser, errors.New("CreateUserObject() : User ID should be an integer") } aUser = UserObject{args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10]} fmt.Println("CreateUserObject() : User Object : ", aUser) return aUser, nil } ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // Create a master Object of the Item // Since the Owner Changes hands, a record has to be written for each // Transaction with the updated Encryption Key of the new owner // Example //./peer chaincode invoke -l golang -n mycc -c '{"Function": "PostItem", "Args":["1000", "ARTINV", "Shadows by Asppen", "Asppen Messer", "20140202", "Original", "Landscape" , "Canvas", "15 x 15 in", "sample_7.png","$600", "100", "2016-02-02 03:000:00"]}' ///////////////////////////////////////////////////////////////////////////////////////////////////////////// func PostItem(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { itemObject, err := CreateItemObject(args[0:]) if err != nil { fmt.Println("PostItem(): Cannot create item object \n") return shim.Error("PostItem(): Cannot create item object") } // Check if the Owner ID specified is registered and valid response := ValidateMember(stub, itemObject.CurrentOwnerID) ownerInfo := response.Payload fmt.Println("Owner information ", ownerInfo, itemObject.CurrentOwnerID) if response.Status != shim.OK { error_str := "PostItem() : Failed Owner information not found for " + itemObject.CurrentOwnerID fmt.Println(error_str) return shim.Error(error_str) } // Convert Item Object to JSON buff, err := ARtoJSON(itemObject) // if err != nil { error_str := "PostItem() : Failed Cannot create object buffer for write : " + args[1] fmt.Println(error_str) return shim.Error(error_str) } else { // Update the ledger with the Buffer Data // err = stub.PutState(args[0], buff) keys := []string{args[0]} err = itpUtils.UpdateObject(stub, "Item", keys, buff) if err != nil { fmt.Println("PostItem() : write error while inserting record\n") return shim.Error("PostItem() : write error while inserting record : " + err.Error()) } // Put an entry into the Item History Table response := PostItemLog(stub, itemObject, "INITIAL", "DEFAULT", args[12]) if response.Status != shim.OK { fmt.Println("PostItemLog() : write error while inserting record\n") return shim.Error("PostItemLog() : write error while inserting record : Error : " + err.Error()) } // Post Entry into ItemCatTable - i.e. Item Category Table // The first key 2016 is a dummy (band aid) key to extract all values keys = []string{"2016", args[6], args[0]} err = itpUtils.UpdateObject(stub, "ItemCat", keys, buff) if err != nil { fmt.Println("PostItem() : Write error while inserting record into ItemCat \n") return shim.Error("PostItem() : Write error while inserting record into ItemCat : Error : " + err.Error()) } } secret_key, _ := json.Marshal(itemObject.AES_Key) fmt.Println(string(secret_key)) return shim.Success(secret_key) } func CreateItemObject(args []string) (ItemObject, error) { var err error var myItem ItemObject // Check there are 13 Arguments provided as per the the struct - two are computed if len(args) != 13 { fmt.Println("CreateItemObject(): Incorrect number of arguments. Expecting 13 ") return myItem, errors.New("CreateItemObject(): Incorrect number of arguments. Expecting 13 ") } // Validate ItemID is an integer _, err = strconv.Atoi(args[0]) if err != nil { fmt.Println("CreateItemObject(): ART ID should be an integer create failed! ") return myItem, errors.New("CreateItemObject(): ART ID should be an integer create failed!") } // Validate Picture File exists based on the name provided // Looks for file in current directory of application and must be fixed for other locations // Validate Picture File exists based on the name provided // Looks for file in current directory of application and must be fixed for other locations imagePath := args[9] if _, err := os.Stat(imagePath); err == nil { fmt.Println(imagePath, " exists!") } else { fmt.Println("CreateItemObject(): Cannot find or load Picture File = %s : %s\n", imagePath, err) return myItem, errors.New("CreateItemObject(): ART Picture File not found " + imagePath) } // Get the Item Image and convert it to a byte array imagebytes, fileType := itpUtils.ImageToByteArray(imagePath) // Generate a new key and encrypt the image AES_key, _ := itpUtils.GenAESKey() AES_enc := itpUtils.Encrypt(AES_key, imagebytes) // Append the AES Key, The Encrypted Image Byte Array and the file type myItem = ItemObject{args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], AES_enc, AES_key, fileType, args[10], args[11], args[12]} fmt.Println("CreateItemObject(): Item Object created: ", myItem.ItemID, myItem.AES_Key) // Code to Validate the Item Object) // If User presents Crypto Key then key is used to validate the picture that is stored as part of the title // TODO return myItem, nil } /////////////////////////////////////////////////////////////////////////////////// // Since the Owner Changes hands, a record has to be written for each // Transaction with the updated Encryption Key of the new owner // This function is internally invoked by PostTransaction and is not a Public API /////////////////////////////////////////////////////////////////////////////////// func UpdateItemObject(stub shim.ChaincodeStubInterface, ar []byte, hammerPrice string, buyer string) pb.Response { var err error myItem, err := JSONtoAR(ar) if err != nil { fmt.Println("UpdateItemObject() : Failed to create Art Record Object from JSON ") return shim.Error("UpdateItemObject() : Failed to create Art Record Object from JSON : Error : " + err.Error()) } // Insert logic to re-encrypt image by first fetching the current Key CurrentAES_Key := myItem.AES_Key // Decrypt Image and Save Image in a file image := itpUtils.Decrypt(CurrentAES_Key, myItem.ItemImage) // Get a New Key & Encrypt Image with New Key myItem.AES_Key, _ = itpUtils.GenAESKey() myItem.ItemImage = itpUtils.Encrypt(myItem.AES_Key, image) // Update the owner to the Buyer and update price to auction hammer price myItem.ItemBasePrice = hammerPrice myItem.CurrentOwnerID = buyer ar, err = ARtoJSON(myItem) // keys := []string{myItem.ItemID, myItem.CurrentOwnerID} // Was the original in v0.6 keys := []string{myItem.ItemID} err = itpUtils.ReplaceObject(stub, "Item", keys, ar) if err != nil { fmt.Println("UpdateItemObject() : Failed ReplaceObject in ItemTable into Blockchain ") return shim.Error("UpdateItemObject() : Failed ReplaceObject in ItemTable into Blockchain : Error : " + err.Error()) } fmt.Println("UpdateItemObject() : ReplaceObject in Item successful ") // Update entry in Item Category Table as it holds the Item object as wekk keys = []string{"2016", myItem.ItemSubject, myItem.ItemID} err = itpUtils.ReplaceObject(stub, "ItemCat", keys, ar) if err != nil { fmt.Println("UpdateItemObject() : Failed ReplaceObject in ItemCategory into Blockchain ") return shim.Error("UpdateItemObject() : Failed ReplaceObject in ItemCategory into Blockchain : Error : " + err.Error()) } fmt.Println("UpdateItemObject() : ReplaceObject in ItemCategory successful ") return shim.Success(myItem.AES_Key) } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Obtain Asset Details and Validate Item // Transfer Item to new owner - no change in price - In the example XFER is the recType // ./peer chaincode invoke -l golang -n mycc -c '{"Function": "TransferItem", "Args": ["1000", "100", "tGEBaZuKUBmwTjzNEyd+nr/fPUASuVJAZ1u7gha5fJg=", "300", "XFER"]}' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// func TransferItem(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { var err error if len(args) < 6 { fmt.Println("TransferItem() : Requires 6 arguments Item#, Owner#, Key#, newOwnerID#, XFER \n") return shim.Error("TransferItem() : Requires 6 arguments Item#, Owner#, Key#, newOwnerID#, XFER") } // Let us make sure that the Item is not on Auction err = VerifyIfItemIsOnAuction(stub, args[0]) if err != nil { error_str := "TransferItem() : Failed Item is either initiated or opened for Auction " + args[0] fmt.Println(error_str) return shim.Error(error_str + ": Error : " + err.Error()) } // Validate New Owner's ID response := ValidateMember(stub, args[3]) if response.Status != shim.OK { error_str := "TransferItem() : Failed transferee not Registered in Blockchain " + args[3] fmt.Println(error_str) return shim.Error(error_str + ": Error : " + response.Message) } // Validate Item or Asset Ownership response = ValidateItemOwnership(stub, "ValidateItemOwnership", args[:3]) if response.Status != shim.OK { error_str := "TransferItem() : ValidateItemOwnership() : Failed to authenticate item or asset ownership" fmt.Println(error_str) return shim.Error(error_str + ": Error : " + response.Message) } ar := response.Payload myItem, err := JSONtoAR(ar) if err != nil { error_str := "TransferItem() : Failed to create item Object from JSON " fmt.Println(error_str) return shim.Error(error_str + ": Error : " + err.Error()) } // Insert logic to re-encrypt image by first fetching the current Key CurrentAES_Key := myItem.AES_Key // Decrypt Image and Save Image in a file image := itpUtils.Decrypt(CurrentAES_Key, myItem.ItemImage) // Get a New Key & Encrypt Image with New Key myItem.AES_Key, _ = itpUtils.GenAESKey() myItem.ItemImage = itpUtils.Encrypt(myItem.AES_Key, image) // Update the owner to the new owner transfered to myItem.CurrentOwnerID = args[3] ar, err = ARtoJSON(myItem) keys := []string{myItem.ItemID} err = itpUtils.ReplaceObject(stub, "Item", keys, ar) if err != nil { fmt.Println("TransferAsset() : Failed ReplaceObject in ItemTable into Blockchain ") return shim.Error(err.Error()) } fmt.Println("TransferAsset() : ReplaceObject in Item successful ") // Update entry in Item Category Table as it holds the Item object as well keys = []string{"2016", myItem.ItemSubject, myItem.ItemID} err = itpUtils.ReplaceObject(stub, "ItemCat", keys, ar) if err != nil { fmt.Println("TransferAsset() : Failed ReplaceObject in ItemCategoryTable into Blockchain ") return shim.Error(err.Error()) } response = PostItemLog(stub, myItem, "Transfer", args[1], args[5]) if response.Status != shim.OK { fmt.Println("TransferItem() : PostItemLog() write error while inserting record\n") return shim.Error(err.Error()) } fmt.Println("TransferAsset() : ReplaceObject in ItemCategory successful ") return shim.Success(myItem.AES_Key) } //////////////////////////////////////////////////////////////////////////////////// // Validate Item Status - Is it currently on Auction, if so Reject Transfer Request // This can be written better - will do so if things work // The function return the Auction ID and the Status = OPEN or INIT //////////////////////////////////////////////////////////////////////////////////// func VerifyIfItemIsOnAuction(stub shim.ChaincodeStubInterface, itemID string) error { response := GetListOfOpenAucs(stub, "AucOpen", []string{"2016"}) if response.Status != shim.OK { return fmt.Errorf("VerifyIfItemIsOnAuction() operation failed. Error retrieving values from AucOpen: %s", response.Message) } rows := response.Payload tlist := make([]AuctionRequest, len(rows)) err := json.Unmarshal([]byte(rows), &tlist) if err != nil { fmt.Println("VerifyIfItemIsOnAuction: Unmarshal failed : ", err) return fmt.Errorf("VerifyIfItemIsOnAuction: operation failed. Error un-marshaling JSON: %s", err) } for i := 0; i < len(tlist); i++ { ar := tlist[i] // Compare Auction IDs if ar.ItemID == itemID { fmt.Println("VerifyIfItemIsOnAuction() Failed : Ummarshall error") return fmt.Errorf("VerifyIfItemIsOnAuction() operation failed. %s", itemID) } } // Now Check if an Auction Has been inititiated // If so , it has to be removed from Auction for a Transfer response = GetListOfInitAucs(stub, "AucInit", []string{"2016"}) if response.Status != shim.OK { return fmt.Errorf("VerifyIfItemIsOnAuction() operation failed. Error retrieving values from AucInit: %s", err) } rows = response.Payload tlist = make([]AuctionRequest, len(rows)) err = json.Unmarshal([]byte(rows), &tlist) if err != nil { fmt.Println("VerifyIfItemIsOnAuction() Unmarshal failed : ", err) return fmt.Errorf("VerifyIfItemIsOnAuction: operation failed. Error un-marshaling JSON: %s", err) } for i := 0; i < len(tlist); i++ { ar := tlist[i] if err != nil { fmt.Println("VerifyIfItemIsOnAuction() Failed : Ummarshall error") return fmt.Errorf("VerifyIfItemIsOnAuction() operation failed. %s", err) } // Compare Auction IDs if ar.ItemID == itemID { return fmt.Errorf("VerifyIfItemIsOnAuction() operation failed.") } } return nil } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // POSTS A LOG ENTRY Every Time the Item is transacted // Valid Status for ItemLog = OnAuc, OnSale, NA, INITIAL // Valid AuctionedBy: This value is set to "DEFAULT" but when it is put on auction Auction House ID is assigned // PostItemLog IS NOT A PUBLIC API and is invoked every time some event happens in the Item's life // The currentDateTime must be provided by Client ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// func PostItemLog(stub shim.ChaincodeStubInterface, item ItemObject, status string, ah string, currentDateTime string) pb.Response { iLog := ItemToItemLog(item, currentDateTime) iLog.Status = status iLog.AuctionedBy = ah buff, err := ItemLogtoJSON(iLog) if err != nil { fmt.Println("PostItemLog() : Failed Cannot create object buffer for write : ", item.ItemID) return shim.Error("PostItemLog(): Failed Cannot create object buffer for write : " + item.ItemID) } else { // Update the ledger with the Buffer Data keys := []string{iLog.ItemID, iLog.Status, iLog.AuctionedBy, currentDateTime} err = itpUtils.UpdateObject(stub, "ItemHistory", keys, buff) if err != nil { fmt.Println("PostItemLog() : write error while inserting record\n") return shim.Error(err.Error()) } } return shim.Success(buff) } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Create an Auction Request // The owner of an Item, when ready to put the item on an auction // will create an auction request and specify a auction house. // // ./peer chaincode invoke -l golang -n mycc -c '{"Function": "PostAuctionRequest", "Args":["1111", "AUCREQ", "1700", "200", "400", "04012016", "1200", "INIT", "2016-05-20 11:00:00.3 +0000 UTC","2016-05-23 11:00:00.3 +0000 UTC", "2016-05-23 11:00:00.3 +0000 UTC"]}' // // The start and end time of the auction are actually assigned when the auction is opened by OpenAuctionForBids() /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// func PostAuctionRequest(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { ar, err := CreateAuctionRequest(args[0:]) if err != nil { return shim.Error(err.Error()) } // Let us make sure that the Item is not on Auction err = VerifyIfItemIsOnAuction(stub, ar.ItemID) if err != nil { fmt.Println("PostAuctionRequest() : Failed Item is either initiated or opened for Auction ", args[0]) return shim.Error(err.Error()) } // Validate Auction House to check it is a registered User response := ValidateMember(stub, ar.AuctionHouseID) if response.Status != shim.OK { fmt.Println("PostAuctionRequest() : Failed Auction House not Registered in Blockchain ", ar.AuctionHouseID) return shim.Error(err.Error()) } aucHouse := response.Payload fmt.Println("Auction House information ", aucHouse, " ID: ", ar.AuctionHouseID) // Validate Item record response = ValidateItemSubmission(stub, ar.ItemID) if response.Status != shim.OK { fmt.Println("PostAuctionRequest() : Failed Could not Validate Item Object in Blockchain ", ar.ItemID) return shim.Error(err.Error()) } itemObject := response.Payload // Convert AuctionRequest to JSON buff, err := AucReqtoJSON(ar) // Converting the auction request struct to []byte array if err != nil { fmt.Println("PostAuctionRequest() : Failed Cannot create object buffer for write : ", args[1]) return shim.Error("PostAuctionRequest(): Failed Cannot create object buffer for write : " + args[1]) } else { // Update the ledger with the Buffer Data //err = stub.PutState(args[0], buff) keys := []string{args[0]} err = itpUtils.UpdateObject(stub, "Auction", keys, buff) if err != nil { fmt.Println("PostAuctionRequest() : write error while inserting record\n") return shim.Error(err.Error()) } // Post an Item Log and the Auction House ID is included in the log // Recall -- that by default that value is "DEFAULT" io, err := JSONtoAR(itemObject) response := PostItemLog(stub, io, "ReadyForAuc", ar.AuctionHouseID, ar.TimeStamp) if response.Status != shim.OK { fmt.Println("PostItemLog() : write error while inserting record\n") return shim.Error(err.Error()) } //An entry is made in the AuctionInitTable that this Item has been placed for Auction // The UI can pull all items available for auction and the item can be Opened for accepting bids // The 2016 is a dummy key and has notr value other than to get all rows keys = []string{"2016", args[0]} err = itpUtils.UpdateObject(stub, "AucInit", keys, buff) if err != nil { fmt.Println("PostAuctionRequest() : write error while inserting record into AucInit\n") return shim.Error(err.Error()) } } return shim.Success(buff) } func CreateAuctionRequest(args []string) (AuctionRequest, error) { var err error var aucReg AuctionRequest // Check there are 12 Arguments // See example -- The Open and Close Dates are Dummy, and will be set by open auction // '{"Function": "PostAuctionRequest", "Args":["1111", "AUCREQ", "1000", "200", "100", "04012016", "1200", "1800", // "INIT", "2016-05-20 11:00:00.3 +0000 UTC","2016-05-23 11:00:00.3 +0000 UTC", "2016-05-23 11:00:00.3 +0000 UTC"]}' if len(args) != 12 { fmt.Println("CreateAuctionRegistrationObject(): Incorrect number of arguments. Expecting 11 ") return aucReg, errors.New("CreateAuctionRegistrationObject() : Incorrect number of arguments. Expecting 11 ") } // Validate UserID is an integer . I think this redundant and can be avoided err = validateID(args[0]) if err != nil { return aucReg, errors.New("CreateAuctionRequest() : User ID should be an integer") } aucReg = AuctionRequest{args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11]} fmt.Println("CreateAuctionObject() : Auction Registration : ", aucReg) return aucReg, nil } ////////////////////////////////////////////////////////// // Create an Item Transaction record to process Request // This is invoked by the CloseAuctionRequest // // //////////////////////////////////////////////////////////// func PostTransaction(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { if function != "PostTransaction" { return shim.Error("PostTransaction(): Invalid function name. Expecting \"PostTransaction\"") } ar, err := CreateTransactionRequest(args[0:]) // if err != nil { return shim.Error(err.Error()) } // Validate buyer's ID response := ValidateMember(stub, ar.UserId) if response.Status != shim.OK { fmt.Println("PostTransaction() : Failed Buyer not Registered in Blockchain ", ar.UserId) return shim.Error(err.Error()) } buyer := response.Payload fmt.Println("PostTransaction(): Validated Buyer information successfully ", buyer, ar.UserId) // Validate Item record response = ValidateItemSubmission(stub, ar.ItemID) if response.Status != shim.OK { fmt.Println("PostTransaction() : Failed Could not Validate Item Object in Blockchain ", ar.ItemID) return shim.Error(err.Error()) } lastUpdatedItemOBCObject := response.Payload fmt.Println("PostTransaction() : Validated Item Object in Blockchain successfully", ar.ItemID) // Update Item Object with new Owner Key response = UpdateItemObject(stub, lastUpdatedItemOBCObject, ar.HammerPrice, ar.UserId) newKey := response.Payload if response.Status != shim.OK { fmt.Println("PostTransaction() : Failed to update Item Master Object in Blockchain ", ar.ItemID) return shim.Error(err.Error()) } else { // Write New Key to file fmt.Println("PostTransaction() : New encryption Key is ", newKey) } fmt.Println("PostTransaction() : Updated Item Master Object in Blockchain successfully", ar.ItemID) // Post an Item Log itemObject, err := JSONtoAR(lastUpdatedItemOBCObject) if err != nil { fmt.Println("PostTransaction() : Conversion error JSON to ItemRecord\n") return shim.Error(err.Error()) } // A life cycle event is added to say that the Item is no longer on auction itemObject.ItemBasePrice = ar.HammerPrice itemObject.CurrentOwnerID = ar.UserId response = PostItemLog(stub, itemObject, "NA", "DEFAULT", args[5]) if response.Status != shim.OK { fmt.Println("PostTransaction() : write error while inserting item log record\n") return shim.Error(err.Error()) } fmt.Println("PostTransaction() : Inserted item log record successfully", ar.ItemID) // Convert Transaction Object to JSON buff, err := TrantoJSON(ar) // if err != nil { fmt.Println("GetObjectBuffer() : Failed to convert Transaction Object to JSON ", args[0]) return shim.Error(err.Error()) } // Update the ledger with the Buffer Data keys := []string{args[0], args[3]} err = itpUtils.UpdateObject(stub, "Trans", keys, buff) if err != nil { fmt.Println("PostTransaction() : write error while inserting record\n") return shim.Error(err.Error()) } fmt.Println("PostTransaction() : Posted Transaction Record successfully\n") // Returns New Key. To get Transaction Details, run GetTransaction secret_key, _ := json.Marshal(newKey) fmt.Println(string(secret_key)) return shim.Success(secret_key) } func CreateTransactionRequest(args []string) (ItemTransaction, error) { var at ItemTransaction // Check there are 9 Arguments if len(args) != 9 { fmt.Println("CreateTransactionRequest(): Incorrect number of arguments. Expecting 9 ") return at, errors.New("CreateTransactionRequest() : Incorrect number of arguments. Expecting 9 ") } at = ItemTransaction{args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]} fmt.Println("CreateTransactionRequest() : Transaction Request: ", at) return at, nil } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Create a Bid Object // Once an Item has been opened for auction, bids can be submitted as long as the auction is "OPEN" //./peer chaincode invoke -l golang -n mycc -c '{"Function": "PostBid", "Args":["1111", "BID", "1", "1000", "300", "1200", "2017-01-23 14:00:00.3 +0000 UTC"]}' //./peer chaincode invoke -l golang -n mycc -c '{"Function": "PostBid", "Args":["1111", "BID", "2", "1000", "400", "3000","2017-01-23 14:00:00.3 +0000 UTC"]}' // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// func PostBid(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { bid, err := CreateBidObject(args[0:]) // if err != nil { return shim.Error(err.Error()) } // Reject the Bid if the Buyer Information Is not Valid or not registered on the Block Chain response := ValidateMember(stub, args[4]) if response.Status != shim.OK { fmt.Println("PostBid() : Failed Buyer not registered on the block-chain ", args[4]) return shim.Error(err.Error()) } buyerInfo := response.Payload fmt.Println("Buyer information ", buyerInfo, " ", args[4]) /////////////////////////////////////// // Reject Bid if Auction is not "OPEN" /////////////////////////////////////// response = GetAuctionRequest(stub, "GetAuctionRequest", []string{args[0]}) if response.Status != shim.OK { fmt.Println("PostBid() : Cannot find Auction record ", args[0]) return shim.Error("PostBid(): Cannot find Auction record : " + args[0]) } RBytes := response.Payload aucR, err := JSONtoAucReq(RBytes) if err != nil { fmt.Println("PostBid() : Cannot UnMarshall Auction record") return shim.Error("PostBid(): Cannot UnMarshall Auction record: " + args[0]) } if aucR.Status != "OPEN" { fmt.Println("PostBid() : Cannot accept Bid as Auction is not OPEN ", args[0]) return shim.Error("PostBid(): Cannot accept Bid as Auction is not OPEN : " + args[0]) } /////////////////////////////////////////////////////////////////// // Reject Bid if the time bid was received is > Auction Close Time /////////////////////////////////////////////////////////////////// if tCompare(bid.BidTime, aucR.CloseDate) == false { fmt.Println("PostBid() Failed : BidTime past the Auction Close Time") error_str := fmt.Sprintf("PostBid() Failed : BidTime past the Auction Close Time %s, %s", bid.BidTime, aucR.CloseDate) return shim.Error(error_str) } ////////////////////////////////////////////////////////////////// // Reject Bid if Item ID on Bid does not match Item ID on Auction ////////////////////////////////////////////////////////////////// if aucR.ItemID != bid.ItemID { fmt.Println("PostBid() Failed : Item ID mismatch on bid. Bid Rejected") return shim.Error("PostBid() : Item ID mismatch on Bid. Bid Rejected") } ////////////////////////////////////////////////////////////////////// // Reject Bid if Bid Price is less than Reserve Price // Convert Bid Price and Reserve Price to Integer (TODO - Float) ////////////////////////////////////////////////////////////////////// bp, err := strconv.Atoi(bid.BidPrice) if err != nil { fmt.Println("PostBid() Failed : Bid price should be an integer") return shim.Error("PostBid() : Bid price should be an integer") } hp, err := strconv.Atoi(aucR.ReservePrice) if err != nil { return shim.Error("PostItem() : Reserve Price should be an integer") } // Check if Bid Price is > Auction Request Reserve Price if bp < hp { return shim.Error("PostItem() : Bid Price must be greater than Reserve Price") } //////////////////////////// // Post or Accept the Bid //////////////////////////// buff, err := BidtoJSON(bid) // if err != nil { fmt.Println("PostBid() : Failed Cannot create object buffer for write : ", args[1]) return shim.Error("PostBid(): Failed Cannot create object buffer for write : " + args[1]) } else { // Update the ledger with the Buffer Data // err = stub.PutState(args[0], buff) keys := []string{args[0], args[2]} err = itpUtils.UpdateObject(stub, "Bid", keys, buff) if err != nil { fmt.Println("PostBid() : write error while inserting record\n") return shim.Error(err.Error()) } } return shim.Success(buff) } func CreateBidObject(args []string) (Bid, error) { var err error var aBid Bid // Check there are 7 Arguments // See example if len(args) != 7 { fmt.Println("CreateBidObject(): Incorrect number of arguments. Expecting 7 ") return aBid, errors.New("CreateBidObject() : Incorrect number of arguments. Expecting 7 ") } // Validate Bid is an integer _, err = strconv.Atoi(args[0]) if err != nil { return aBid, errors.New("CreateBidObject() : Bid ID should be an integer") } _, err = strconv.Atoi(args[2]) if err != nil { return aBid, errors.New("CreateBidObject() : Bid ID should be an integer") } // bidTime = args[6] sent by the client aBid = Bid{args[0], args[1], args[2], args[3], args[4], args[5], args[6]} fmt.Println("CreateBidObject() : Bid Object : ", aBid) return aBid, nil } ////////////////////////////////////////////////////////// // JSON To args[] - return a map of the JSON string ////////////////////////////////////////////////////////// func JSONtoArgs(Avalbytes []byte) (map[string]interface{}, error) { var data map[string]interface{} if err := json.Unmarshal(Avalbytes, &data); err != nil { return nil, err } return data, nil } ////////////////////////////////////////////////////////// // Variation of the above - return value from a JSON string ////////////////////////////////////////////////////////// func GetKeyValue(Avalbytes []byte, key string) string { var dat map[string]interface{} if err := json.Unmarshal(Avalbytes, &dat); err != nil { panic(err) } val := dat[key].(string) return val } ////////////////////////////////////////////////////////// // Time and Date Comparison // tCompare("2016-06-28 18:40:57", "2016-06-27 18:45:39") ////////////////////////////////////////////////////////// func tCompare(t1 string, t2 string) bool { layout := "2006-01-02 15:04:05" bidTime, err := time.Parse(layout, t1) if err != nil { fmt.Println("tCompare() Failed : time Conversion error on t1") return false } aucCloseTime, err := time.Parse(layout, t2) if err != nil { fmt.Println("tCompare() Failed : time Conversion error on t2") return false } if bidTime.Before(aucCloseTime) { return true } return false } ////////////////////////////////////////////////////////// // Converts JSON String to an ART Object ////////////////////////////////////////////////////////// func JSONtoAR(data []byte) (ItemObject, error) { ar := ItemObject{} err := json.Unmarshal([]byte(data), &ar) if err != nil { fmt.Println("Unmarshal failed : ", err) } return ar, err } ////////////////////////////////////////////////////////// // Converts an ART Object to a JSON String ////////////////////////////////////////////////////////// func ARtoJSON(ar ItemObject) ([]byte, error) { ajson, err := json.Marshal(ar) if err != nil { fmt.Println(err) return nil, err } return ajson, nil } ////////////////////////////////////////////////////////// // Converts an BID to a JSON String ////////////////////////////////////////////////////////// func ItemLogtoJSON(item ItemLog) ([]byte, error) { ajson, err := json.Marshal(item) if err != nil { fmt.Println(err) return nil, err } return ajson, nil } ////////////////////////////////////////////////////////// // Converts an User Object to a JSON String ////////////////////////////////////////////////////////// func JSONtoItemLog(ithis []byte) (ItemLog, error) { item := ItemLog{} err := json.Unmarshal(ithis, &item) if err != nil { fmt.Println("JSONtoAucReq error: ", err) return item, err } return item, err } ////////////////////////////////////////////////////////// // Converts an Auction Request to a JSON String ////////////////////////////////////////////////////////// func AucReqtoJSON(ar AuctionRequest) ([]byte, error) { ajson, err := json.Marshal(ar) if err != nil { fmt.Println(err) return nil, err } return ajson, nil } ////////////////////////////////////////////////////////// // Converts an User Object to a JSON String ////////////////////////////////////////////////////////// func JSONtoAucReq(areq []byte) (AuctionRequest, error) { ar := AuctionRequest{} err := json.Unmarshal(areq, &ar) if err != nil { fmt.Println("JSONtoAucReq error: ", err) return ar, err } return ar, err } ////////////////////////////////////////////////////////// // Converts an BID to a JSON String ////////////////////////////////////////////////////////// func BidtoJSON(myHand Bid) ([]byte, error) { ajson, err := json.Marshal(myHand) if err != nil { fmt.Println(err) return nil, err } return ajson, nil } ////////////////////////////////////////////////////////// // Converts an User Object to a JSON String ////////////////////////////////////////////////////////// func JSONtoBid(areq []byte) (Bid, error) { myHand := Bid{} err := json.Unmarshal(areq, &myHand) if err != nil { fmt.Println("JSONtoAucReq error: ", err) return myHand, err } return myHand, err } ////////////////////////////////////////////////////////// // Converts an User Object to a JSON String ////////////////////////////////////////////////////////// func UsertoJSON(user UserObject) ([]byte, error) { ajson, err := json.Marshal(user) if err != nil { fmt.Println("UsertoJSON error: ", err) return nil, err } fmt.Println("UsertoJSON created: ", ajson) return ajson, nil } ////////////////////////////////////////////////////////// // Converts an User Object to a JSON String ////////////////////////////////////////////////////////// func JSONtoUser(user []byte) (UserObject, error) { ur := UserObject{} err := json.Unmarshal(user, &ur) if err != nil { fmt.Println("UsertoJSON error: ", err) return ur, err } fmt.Println("UsertoJSON created: ", ur) return ur, err } ////////////////////////////////////////////////////////// // Converts an Item Transaction to a JSON String ////////////////////////////////////////////////////////// func TrantoJSON(at ItemTransaction) ([]byte, error) { ajson, err := json.Marshal(at) if err != nil { fmt.Println(err) return nil, err } return ajson, nil } ////////////////////////////////////////////////////////// // Converts an Trans Object to a JSON String ////////////////////////////////////////////////////////// func JSONtoTran(areq []byte) (ItemTransaction, error) { at := ItemTransaction{} err := json.Unmarshal(areq, &at) if err != nil { fmt.Println("JSONtoTran error: ", err) return at, err } return at, err } ////////////////////////////////////////////// // Validates an ID for Well Formed ////////////////////////////////////////////// func validateID(id string) error { // Validate UserID is an integer _, err := strconv.Atoi(id) if err != nil { return errors.New("validateID(): User ID should be an integer") } return nil } ////////////////////////////////////////////// // Create an ItemLog from Item ////////////////////////////////////////////// func ItemToItemLog(io ItemObject, cdt string) ItemLog { iLog := ItemLog{} iLog.ItemID = io.ItemID iLog.Status = "INITIAL" iLog.AuctionedBy = "DEFAULT" iLog.RecType = "ILOG" iLog.ItemDesc = io.ItemDesc iLog.CurrentOwner = io.CurrentOwnerID iLog.Date = cdt return iLog } ////////////////////////////////////////////// // Convert Bid to Transaction for Posting ////////////////////////////////////////////// func BidtoTransaction(bid Bid) ItemTransaction { var t ItemTransaction t.AuctionID = bid.AuctionID t.RecType = "POSTTRAN" t.ItemID = bid.ItemID t.TransType = "SALE" t.UserId = bid.BuyerID // Ideally SystemChain Code must provide a TimeStamp Function t.TransDate = bid.BidTime t.HammerTime = bid.BidTime t.HammerPrice = bid.BidPrice t.Details = "The Highest Bidder does not always win" return t } //////////////////////////////////////////////////////////////////////////// // Validate if the User Information Exists // in the block-chain //////////////////////////////////////////////////////////////////////////// func ValidateMember(stub shim.ChaincodeStubInterface, owner string) pb.Response { // Get the Item Objects and Display it // Avalbytes, err := stub.GetState(owner) args := []string{owner} Avalbytes, err := itpUtils.QueryObject(stub, "User", args) if err != nil { fmt.Println("ValidateMember() : Failed - Cannot find valid owner record for ART ", owner) jsonResp := "{\"Error\":\"Failed to get Owner Object Data for " + owner + "\"}" return shim.Error(jsonResp) } if Avalbytes == nil { fmt.Println("ValidateMember() : Failed - Incomplete owner record for ART ", owner) jsonResp := "{\"Error\":\"Failed - Incomplete information about the owner for " + owner + "\"}" return shim.Error(jsonResp) } fmt.Println("ValidateMember() : Validated Item Owner:\n", owner) return shim.Success(Avalbytes) } //////////////////////////////////////////////////////////////////////////// // Validate if the User Information Exists // in the block-chain //////////////////////////////////////////////////////////////////////////// func ValidateItemSubmission(stub shim.ChaincodeStubInterface, artId string) pb.Response { // Get the Item Objects and Display it args := []string{artId} Avalbytes, err := itpUtils.QueryObject(stub, "Item", args) if err != nil { fmt.Println("ValidateItemSubmission() : Failed - Cannot find valid owner record for ART ", artId) jsonResp := "{\"Error\":\"Failed to get Owner Object Data for " + artId + "\"}" return shim.Error(jsonResp) } if Avalbytes == nil { fmt.Println("ValidateItemSubmission() : Failed - Incomplete owner record for ART ", artId) jsonResp := "{\"Error\":\"Failed - Incomplete information about the owner for " + artId + "\"}" return shim.Error(jsonResp) } //fmt.Println("ValidateItemSubmission() : Validated Item Owner:", Avalbytes) return shim.Success(Avalbytes) } ///////////////////////////////////////////////////////////////////////////////////////////////////// // Get List of Bids for an Auction // in the block-chain -- // ./peer chaincode query -l golang -n mycc -c '{"Function": "GetListOfBids", "Args": ["1111"]}' // ./peer chaincode query -l golang -n mycc -c '{"Function": "GetLastBid", "Args": ["1111"]}' // ./peer chaincode query -l golang -n mycc -c '{"Function": "GetHighestBid", "Args": ["1111"]}' ///////////////////////////////////////////////////////////////////////////////////////////////////// func GetListOfBids(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { rs, err := itpUtils.GetList(stub, "Bid", args) if err != nil { error_str := fmt.Sprintf("GetListOfBids operation failed. Error marshaling JSON: %s", err) return shim.Error(error_str) } defer rs.Close() // Iterate through result set var i int var tlist []Bid // Define a list for i = 0; rs.HasNext(); i++ { // We can process whichever return value is of interest record, err := rs.Next() if err != nil { return shim.Success(nil) } bid, err := JSONtoBid(record.Value) if err != nil { error_str := fmt.Sprintf("GetListOfBids() operation failed - Unmarshall Error. %s", err) fmt.Println(error_str) return shim.Error(error_str) } fmt.Println("GetList() : my Value : ", bid) tlist = append(tlist, bid) } jsonRows, err := json.Marshal(tlist) if err != nil { error_str := fmt.Sprintf("GetListOfBids() operation failed - Unmarshall Error. %s", err) fmt.Println(error_str) return shim.Error(error_str) } fmt.Println("List of Bids Requested : ", jsonRows) return shim.Success(jsonRows) } //////////////////////////////////////////////////////////////////////////////////////////////////////// // Get List of Auctions that have been initiated // in the block-chain // This is a fixed Query to be issued as below // peer chaincode query -l golang -n mycc -c '{"Args": ["qGetListOfInitAucs", "2016"]}' //////////////////////////////////////////////////////////////////////////////////////////////////////// func GetListOfInitAucs(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { rs, err := itpUtils.GetList(stub, "AucInit", args) if err != nil { error_str := fmt.Sprintf("GetListOfInitAucs operation failed. Error marshaling JSON: %s", err) return shim.Error(error_str) } defer rs.Close() // Iterate through result set var i int var tlist []AuctionRequest // Define a list for i = 0; rs.HasNext(); i++ { // We can process whichever return value is of interest record, err := rs.Next() if err != nil { return shim.Success(nil) } ar, err := JSONtoAucReq(record.Value) if err != nil { error_str := fmt.Sprintf("GetListOfInitAucs() operation failed - Unmarshall Error. %s", err) fmt.Println(error_str) return shim.Error(error_str) } fmt.Println("GetListOfInitAucs() : my Value : ", ar) tlist = append(tlist, ar) } jsonRows, err := json.Marshal(tlist) if err != nil { error_str := fmt.Sprintf("GetListOfInitAucs() operation failed - Unmarshall Error. %s", err) fmt.Println(error_str) return shim.Error(error_str) } //fmt.Println("List of Auctions Requested : ", jsonRows) return shim.Success(jsonRows) } //////////////////////////////////////////////////////////////////////////// // Get List of Open Auctions for which bids can be supplied // in the block-chain // This is a fixed Query to be issued as below // peer chaincode query -l golang -n mycc -c '{"Args": ["qGetListOfOpenAucs", "2016"]}' //////////////////////////////////////////////////////////////////////////// func GetListOfOpenAucs(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { rs, err := itpUtils.GetList(stub, "AucOpen", args) if err != nil { error_str := fmt.Sprintf("GetListOfOpenAucs operation failed. Error marshaling JSON: %s", err) return shim.Error(error_str) } defer rs.Close() // Iterate through result set var i int var tlist []AuctionRequest // Define a list for i = 0; rs.HasNext(); i++ { // We can process whichever return value is of interest record, err := rs.Next() if err != nil { return shim.Success(nil) } ar, err := JSONtoAucReq(record.Value) if err != nil { error_str := fmt.Sprintf("GetListOfOpenAucs() operation failed - Unmarshall Error. %s", err) fmt.Println(error_str) return shim.Error(error_str) } fmt.Println("GetListOfOpenAucs() : my Value : ", ar) tlist = append(tlist, ar) } jsonRows, err := json.Marshal(tlist) if err != nil { error_str := fmt.Sprintf("GetListOfInitAucs() operation failed - Unmarshall Error. %s", err) fmt.Println(error_str) return shim.Error(error_str) } //fmt.Println("List of Open Auctions : ", jsonRows) return shim.Success(jsonRows) } //////////////////////////////////////////////////////////////////////////// // Get the Item History for an Item // in the block-chain .. Pass the Item ID // ./peer chaincode query -l golang -n mycc -c '{"Function": "GetItemLog", "Args": ["1000"]}' //////////////////////////////////////////////////////////////////////////// func GetItemLog(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { // Check there are 1 Arguments provided as per the the struct - two are computed // See example if len(args) < 1 { fmt.Println("GetItemLog(): Incorrect number of arguments. Expecting 1 ") fmt.Println("GetItemLog(): ./peer chaincode query -l golang -n mycc -c '{\"Function\": \"GetItem\", \"Args\": [\"1111\"]}'") return shim.Error("CreateItemObject(): Incorrect number of arguments. Expecting 12 ") } rs, err := itpUtils.GetList(stub, "ItemHistory", args) if err != nil { error_str := fmt.Sprintf("GetItemLog operation failed. Error marshaling JSON: %s", err) return shim.Error(error_str) } defer rs.Close() // Iterate through result set var i int var tlist []ItemLog // Define a list for i = 0; rs.HasNext(); i++ { // We can process whichever return value is of interest record, err := rs.Next() if err != nil { return shim.Success(nil) } il, err := JSONtoItemLog(record.Value) if err != nil { error_str := fmt.Sprintf("GetItemLog() operation failed - Unmarshall Error. %s", err) fmt.Println(error_str) return shim.Error(error_str) } fmt.Println("GetItemLog() : my Value : ", il) tlist = append(tlist, il) } jsonRows, err := json.Marshal(tlist) if err != nil { error_str := fmt.Sprintf("GetItemLog() operation failed - Unmarshall Error. %s", err) fmt.Println(error_str) return shim.Error(error_str) } //fmt.Println("All History : ", jsonRows) return shim.Success(jsonRows) } //////////////////////////////////////////////////////////////////////////// // Get a List of Items by Category // in the block-chain // Input is 2016 + Category // Categories include whatever has been defined in the Item Tables - Landscape, Modern, ... // See Sample data // ./peer chaincode query -l golang -n mycc -c '{"Function": "GetItemListByCat", "Args": ["2016", "Modern"]}' //////////////////////////////////////////////////////////////////////////// func GetItemListByCat(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { // Check there are 1 Arguments provided as per the the struct - two are computed // See example if len(args) < 1 { fmt.Println("GetItemListByCat(): Incorrect number of arguments. Expecting 1 ") fmt.Println("GetItemListByCat(): ./peer chaincode query -l golang -n mycc -c '{\"Function\": \"GetItemListByCat\", \"Args\": [\"Modern\"]}'") return shim.Error("CreateItemObject(): Incorrect number of arguments. Expecting 1 ") } rs, err := itpUtils.GetList(stub, "ItemCat", args) if err != nil { error_str := fmt.Sprintf("GetItemListByCat operation failed. Error marshaling JSON: %s", err) return shim.Error(error_str) } defer rs.Close() // Iterate through result set var i int var tlist []ItemObject // Define a list for i = 0; rs.HasNext(); i++ { // We can process whichever return value is of interest record, err := rs.Next() if err != nil { return shim.Success(nil) } io, err := JSONtoAR(record.Value) if err != nil { error_str := fmt.Sprintf("GetItemListByCat() operation failed - Unmarshall Error. %s", err) fmt.Println(error_str) return shim.Error(error_str) } fmt.Println("GetItemListByCat() : my Value : ", io) tlist = append(tlist, io) } jsonRows, err := json.Marshal(tlist) if err != nil { error_str := fmt.Sprintf("GetItemListByCat() operation failed - Unmarshall Error. %s", err) fmt.Println(error_str) return shim.Error(error_str) } //fmt.Println("All Items : ", jsonRows) return shim.Success(jsonRows) } //////////////////////////////////////////////////////////////////////////// // Get a List of Users by Category // in the block-chain //////////////////////////////////////////////////////////////////////////// func GetUserListByCat(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { // Check there are 1 Arguments provided as per the the struct - two are computed // See example if len(args) < 1 { fmt.Println("GetUserListByCat(): Incorrect number of arguments. Expecting 1 ") fmt.Println("GetUserListByCat(): ./peer chaincode query -l golang -n mycc -c '{\"Function\": \"GetUserListByCat\", \"Args\": [\"AH\"]}'") return shim.Error("CreateUserObject(): Incorrect number of arguments. Expecting 1 ") } rs, err := itpUtils.GetList(stub, "UserCat", args) if err != nil { error_str := fmt.Sprintf("GetUserListByCat operation failed. Error marshaling JSON: %s", err) return shim.Error(error_str) } defer rs.Close() // Iterate through result set var i int var tlist []UserObject // Define a list for i = 0; rs.HasNext(); i++ { // We can process whichever return value is of interest record, err := rs.Next() if err != nil { return shim.Success(nil) } uo, err := JSONtoUser(record.Value) if err != nil { error_str := fmt.Sprintf("GetUserListByCat() operation failed - Unmarshall Error. %s", err) fmt.Println(error_str) return shim.Error(error_str) } fmt.Println("GetUserListByCat() : my Value : ", uo) tlist = append(tlist, uo) } jsonRows, err := json.Marshal(tlist) if err != nil { error_str := fmt.Sprintf("GetUserListByCat() operation failed - Unmarshall Error. %s", err) fmt.Println(error_str) return shim.Error(error_str) } //fmt.Println("All Users : ", jsonRows) return shim.Success(jsonRows) } //////////////////////////////////////////////////////////////////////////// // Get The Highest Bid Received so far for an Auction // in the block-chain //////////////////////////////////////////////////////////////////////////// func GetLastBid(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { var Avalbytes []byte layout := "2006-01-02 15:04:05" highestTime, err := time.Parse(layout, layout) rs, err := itpUtils.GetList(stub, "Bid", args) if err != nil { error_str := fmt.Sprintf("GetListOfBids operation failed. Error marshaling JSON: %s", err) return shim.Error(error_str) } defer rs.Close() // Iterate through result set for i := 0; rs.HasNext(); i++ { // We can process whichever return value is of interest record, err := rs.Next() if err != nil { return shim.Success(nil) } currentBid, err := JSONtoBid(record.Value) if err != nil { error_str := fmt.Sprintf("GetHighestBid(0 operation failed. %s", err) fmt.Println(error_str) return shim.Error(error_str) } bidTime, err := time.Parse(layout, currentBid.BidTime) if err != nil { error_str := fmt.Sprintf("GetLastBid() Failed : time Conversion error on BidTime %s", err) fmt.Println(error_str) return shim.Error(error_str) } if bidTime.Sub(highestTime) > 0 { highestTime = bidTime Avalbytes = record.Value } return shim.Success(Avalbytes) } return shim.Error("GetLastBid() : Failed - No Bids Found") } //////////////////////////////////////////////////////////////////////////// // Get The Highest Bid Received so far for an Auction // in the block-chain //////////////////////////////////////////////////////////////////////////// func GetNoOfBidsReceived(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { rs, err := itpUtils.GetList(stub, "Bid", args) if err != nil { error_str := fmt.Sprintf("GetListOfBids operation failed. Error marshaling JSON: %s", err) return shim.Error(error_str) } defer rs.Close() // Iterate through result set var i int for i = 0; rs.HasNext(); i++ { // We can process whichever return value is of interest _, err := rs.Next() if err != nil { return shim.Success(nil) } } return shim.Success([]byte(strconv.Itoa(i))) } //////////////////////////////////////////////////////////////////////////// // Get the Highest Bid in the List // //////////////////////////////////////////////////////////////////////////// func GetHighestBid(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { var Avalbytes []byte highestBid := 0 rs, err := itpUtils.GetList(stub, "Bid", args) if err != nil { error_str := fmt.Sprintf("GetListOfBids operation failed. Error marshaling JSON: %s", err) return shim.Error(error_str) } defer rs.Close() // Iterate through result set var i int for i = 0; rs.HasNext(); i++ { // We can process whichever return value is of interest record, err := rs.Next() if err != nil { return shim.Success(nil) } currentBid, err := JSONtoBid(record.Value) if err != nil { error_str := fmt.Sprintf("GetHighestBid(0 operation failed. %s", err) fmt.Println(error_str) return shim.Error(error_str) } bidPrice, err := strconv.Atoi(currentBid.BidPrice) if err != nil { error_str := fmt.Sprintf("GetHighestBid() Int Conversion error on BidPrice! failed. %s", err) fmt.Println(error_str) return shim.Error(error_str) } if bidPrice >= highestBid { highestBid = bidPrice Avalbytes = record.Value } } return shim.Success(Avalbytes) } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Trigger the Auction // Structure of args auctionReqID, RecType, AucStartDateTime, Duration in Minutes ( 3 = 3 minutes) // ./peer chaincode invoke -l golang -n mycc -c '{"Function": "OpenAuctionForBids", "Args":["1111", "OPENAUC", "3", "2006-01-02 15:04:05"]}' /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// func OpenAuctionForBids(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { // Fetch Auction Object and check its Status Avalbytes, err := itpUtils.QueryObject(stub, "Auction", []string{args[0]}) if err != nil { fmt.Println("OpenAuctionForBids(): Auction Object Retrieval Failed ") return shim.Error("OpenAuctionForBids(): Auction Object Retrieval Failed ") } aucR, err := JSONtoAucReq(Avalbytes) if err != nil { fmt.Println("OpenAuctionForBids(): Auction Object Unmarshalling Failed ") return shim.Error("OpenAuctionForBids(): Auction Object UnMarshalling Failed ") } if aucR.Status == "CLOSED" { fmt.Println("OpenAuctionForBids(): Auction is Closed - Cannot Open for new bids ") return shim.Error("OpenAuctionForBids(): is Closed - Cannot Open for new bids Failed ") } // Calculate Time Now and Duration of Auction // Validate arg[1] is an integer as it represents Duration in Minutes aucDuration, err := strconv.Atoi(args[2]) if err != nil { fmt.Println("OpenAuctionForBids(): Auction Duration is an integer that represents minute! OpenAuctionForBids() Failed ") return shim.Error("OpenAuctionForBids(): Auction Duration is an integer that represents minute! OpenAuctionForBids() Failed ") } aucStartDate, err := time.Parse("2006-01-02 15:04:05", args[3]) aucEndDate := aucStartDate.Add(time.Duration(aucDuration) * time.Minute) // We don't use go routines anymore to time the auction //sleepTime := time.Duration(aucDuration * 60 * 1000 * 1000 * 1000) // Update Auction Object aucR.OpenDate = aucStartDate.Format("2006-01-02 15:04:05") aucR.CloseDate = aucEndDate.Format("2006-01-02 15:04:05") aucR.Status = "OPEN" response := UpdateAuctionStatus(stub, "Auction", aucR) if response.Status != shim.OK { fmt.Println("OpenAuctionForBids(): UpdateAuctionStatus() Failed ") return shim.Error("OpenAuctionForBids(): UpdateAuctionStatus() Failed ") } buff := response.Payload // Remove the Auction from INIT Bucket and move to OPEN bucket // This was designed primarily to help the UI keys := []string{"2016", aucR.AuctionID} err = itpUtils.DeleteObject(stub, "AucInit", keys) if err != nil { fmt.Println("OpenAuctionForBids(): DeleteFromLedger() Failed ") return shim.Error("OpenAuctionForBids(): DeleteFromLedger() Failed ") } // Add the Auction to Open Bucket err = itpUtils.UpdateObject(stub, "AucOpen", keys, buff) if err != nil { fmt.Println("OpenAuctionForBids() : write error while inserting record into AucInit\n") return shim.Error(err.Error()) } return shim.Success(buff) } ////////////////////////////////////////////////////////////////////////// // Close Open Auctions // 1. Read OpenAucTable // 2. Compare now with expiry time with now // 3. If now is > expiry time call CloseAuction // ./peer chaincode invoke -l golang -n mycc -c '{"Function": "CloseOpenAuctions", "Args": ["2016", "CLAUC", currentDateTime]}' ////////////////////////////////////////////////////////////////////////// func CloseOpenAuctions(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { response := GetListOfOpenAucs(stub, "AucOpen", []string{"2016"}) if response.Status != shim.OK { error_str := fmt.Sprintf("CloseOpenAuctions() operation failed. Error retrieving values from AucOpen: %s", response.Message) fmt.Println(error_str) return shim.Error(error_str) } rows := response.Payload tlist := make([]AuctionRequest, len(rows)) err := json.Unmarshal([]byte(rows), &tlist) if err != nil { error_str := fmt.Sprintf("CloseOpenAuctions() Unmarshal operation failed. Error retrieving values from AucOpen: %s", response.Message) fmt.Println(error_str) return shim.Error(error_str) } for i := 0; i < len(tlist); i++ { ar := tlist[i] fmt.Println("CloseOpenAuctions() ", ar) // Compare Auction Times where args[2] = the CurrentTime sent by the client fmt.Println("CloseOpenAuctions() : ", args[2], ": ar.CloseDate : ", ar.CloseDate) if tCompare(args[2], ar.CloseDate) == false { // Request Closing Auction response := CloseAuction(stub, "CloseAuction", []string{ar.AuctionID}) if response.Status != shim.OK { error_str := fmt.Sprintf("CloseOpenAuctions() operation failed. %s", err) fmt.Println(error_str) return shim.Error(error_str) } } } return shim.Success(nil) } ////////////////////////////////////////////////////////////////////////// // Close the Auction // This is invoked by OpenAuctionForBids // which kicks-off a go-routine timer for the duration of the auction // When the timer expires, it creates a shell script to CloseAuction() and triggers it // This function can also be invoked via CLI - the intent was to close as and when I implement BuyItNow() // CloseAuction // - Sets the status of the Auction to "CLOSED" // - Removes the Auction from the Open Auction list (AucOpenTable) // - Retrieves the Highest Bid and creates a Transaction // - Posts The Transaction // // To invoke from Command Line via CLI or REST API // ./peer chaincode invoke -l golang -n mycc -c '{"Function": "CloseAuction", "Args": ["1111", "AUCREQ"]}' // ./peer chaincode invoke -l golang -n mycc -c '{"Function": "CloseAuction", "Args": ["1111", "AUCREQ"]}' // ////////////////////////////////////////////////////////////////////////// func CloseAuction(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { // Close The Auction - Fetch Auction Object Avalbytes, err := itpUtils.QueryObject(stub, "Auction", []string{args[0]}) if err != nil { fmt.Println("CloseAuction(): Auction Object Retrieval Failed ") return shim.Error("CloseAuction(): Auction Object Retrieval Failed ") } aucR, err := JSONtoAucReq(Avalbytes) if err != nil { fmt.Println("CloseAuction(): Auction Object Unmarshalling Failed ") return shim.Error("CloseAuction(): Auction Object UnMarshalling Failed ") } // Update Auction Status aucR.Status = "CLOSED" fmt.Println("CloseAuction(): UpdateAuctionStatus() successful ", aucR) response := UpdateAuctionStatus(stub, "Auction", aucR) if response.Status != shim.OK { fmt.Println("CloseAuction(): UpdateAuctionStatus() Failed ") return shim.Error("CloseAuction(): UpdateAuctionStatus() Failed ") } Avalbytes = response.Payload // Remove the Auction from Open Bucket keys := []string{"2016", aucR.AuctionID} err = itpUtils.DeleteObject(stub, "AucOpen", keys) if err != nil { fmt.Println("CloseAuction(): DeleteFromLedger(AucOpenTable) Failed ") return shim.Error("CloseAuction(): DeleteFromLedger(AucOpen) Failed ") } fmt.Println("CloseAuction(): Proceeding to process the highest bid ") // Process Final Bid - Turn it into a Transaction response = GetHighestBid(stub, "GetHighestBid", []string{args[0]}) Avalbytes = response.Payload if Avalbytes == nil { fmt.Println("CloseAuction(): No bids available, no change in Item Status - PostTransaction() Completed Successfully ") return shim.Success(Avalbytes) } if response.Status != shim.OK { fmt.Println("CloseAuction(): No bids available, error encountered - PostTransaction() failed ") return shim.Error(err.Error()) } bid, _ := JSONtoBid(Avalbytes) fmt.Println("CloseAuction(): Proceeding to process the highest bid ", bid) tran := BidtoTransaction(bid) fmt.Println("CloseAuction(): Converting Bid to tran ", tran) // Process the last bid once Time Expires tranArgs := []string{tran.AuctionID, tran.RecType, tran.ItemID, tran.TransType, tran.UserId, tran.TransDate, tran.HammerTime, tran.HammerPrice, tran.Details} fmt.Println("CloseAuction(): Proceeding to process the Transaction ", tranArgs) response = PostTransaction(stub, "PostTransaction", tranArgs) if response.Status != shim.OK { fmt.Println("CloseAuction(): PostTransaction() Failed ") return shim.Error("CloseAuction(): PostTransaction() Failed ") } Avalbytes = response.Payload fmt.Println("CloseAuction(): PostTransaction() Completed Successfully ") return shim.Success(Avalbytes) } //////////////////////////////////////////////////////////////////////////////////////////// // Buy It Now // Rules: // If Buy IT Now Option is available then a Buyer has the option to buy the ITEM // before the bids exceed BuyITNow Price . Normally, The application should take of this // at the UI level and this chain-code assumes application has validated that //////////////////////////////////////////////////////////////////////////////////////////// func BuyItNow(stub shim.ChaincodeStubInterface, function string, args []string) pb.Response { // Process Final Bid - Turn it into a Transaction response := GetHighestBid(stub, "GetHighestBid", []string{args[0]}) bid, err := JSONtoBid(response.Payload) if err != nil { return shim.Error("BuyItNow() : JSONtoBid Error") } // Check if BuyItNow Price > Highest Bid so far binP, err := strconv.Atoi(args[5]) if err != nil { return shim.Error("BuyItNow() : Invalid BuyItNow Price") } hbP, err := strconv.Atoi(bid.BidPrice) if err != nil { return shim.Error("BuyItNow() : Invalid Highest Bid Price") } if hbP > binP { return shim.Error("BuyItNow() : Highest Bid Price > BuyItNow Price - BuyItNow Rejected") } // Close The Auction - Fetch Auction Object Avalbytes, err := itpUtils.QueryObject(stub, "Auction", []string{args[0]}) if err != nil { fmt.Println("BuyItNow(): Auction Object Retrieval Failed ") return shim.Error("BuyItNow(): Auction Object Retrieval Failed ") } aucR, err := JSONtoAucReq(Avalbytes) if err != nil { fmt.Println("BuyItNow(): Auction Object Unmarshalling Failed ") return shim.Error("BuyItNow(): Auction Object UnMarshalling Failed ") } // Update Auction Status aucR.Status = "CLOSED" fmt.Println("BuyItNow(): UpdateAuctionStatus() successful ", aucR) response = UpdateAuctionStatus(stub, "Auction", aucR) if response.Status != shim.OK { fmt.Println("BuyItNow(): UpdateAuctionStatus() Failed ") return shim.Error("BuyItNow(): UpdateAuctionStatus() Failed ") } Avalbytes = response.Payload // Remove the Auction from Open Bucket keys := []string{"2016", aucR.AuctionID} err = itpUtils.DeleteObject(stub, "AucOpen", keys) if err != nil { fmt.Println("BuyItNow(): DeleteFromLedger(AucOpen) Failed ") return shim.Error("BuyItNow(): DeleteFromLedger(AucOpen) Failed ") } fmt.Println("BuyItNow(): Proceeding to process the highest bid ") // Convert the BuyITNow to a Bid type struct buyItNowBid, err := CreateBidObject(args[0:]) if err != nil { return shim.Error(err.Error()) } // Reject the offer if the Buyer Information Is not Valid or not registered on the Block Chain response = ValidateMember(stub, args[4]) if response.Status != shim.OK { fmt.Println("BuyItNow() : Failed Buyer not registered on the block-chain ", args[4]) return shim.Error(err.Error()) } buyerInfo := response.Payload fmt.Println("Buyer information ", buyerInfo, args[4]) tran := BidtoTransaction(buyItNowBid) fmt.Println("BuyItNow(): Converting Bid to tran ", tran) // Process the buy-it-now offer tranArgs := []string{tran.AuctionID, tran.RecType, tran.ItemID, tran.TransType, tran.UserId, tran.TransDate, tran.HammerTime, tran.HammerPrice, tran.Details} fmt.Println("BuyItNow(): Proceeding to process the Transaction ", tranArgs) response = PostTransaction(stub, "PostTransaction", tranArgs) if response.Status != shim.OK { fmt.Println("BuyItNow(): PostTransaction() Failed ") return shim.Error("CloseAuction(): PostTransaction() Failed ") } fmt.Println("BuyItNow(): PostTransaction() Completed Successfully ") return response } ////////////////////////////////////////////////////////////////////////// // Update the Auction Object // This function updates the status of the auction // from INIT to OPEN to CLOSED ////////////////////////////////////////////////////////////////////////// func UpdateAuctionStatus(stub shim.ChaincodeStubInterface, tableName string, ar AuctionRequest) pb.Response { buff, err := AucReqtoJSON(ar) if err != nil { fmt.Println("UpdateAuctionStatus() : Failed Cannot create object buffer for write : ", ar.AuctionID) return shim.Error("UpdateAuctionStatus(): Failed Cannot create object buffer for write : " + ar.AuctionID) } // Update the ledger with the Buffer Data //keys := []string{ar.AuctionID, ar.ItemID} keys := []string{ar.AuctionID} err = itpUtils.ReplaceObject(stub, "Auction", keys, buff) if err != nil { fmt.Println("UpdateAuctionStatus() : write error while inserting record\n") return shim.Error(err.Error()) } return shim.Success(buff) } ///////////////////////////////////////////////////////////////////////////////////////////// // Return the right Object Buffer after validation to write to the ledger // var recType = []string{"ARTINV", "USER", "BID", "AUCREQ", "POSTTRAN", "OPENAUC", "CLAUC"} ///////////////////////////////////////////////////////////////////////////////////////////// func ProcessQueryResult(stub shim.ChaincodeStubInterface, Avalbytes []byte, args []string) error { // Identify Record Type by scanning the args for one of the recTypes // This is kind of a post-processor once the query fetches the results // RecType is the style of programming in the punch card days .. // ... well var dat map[string]interface{} if err := json.Unmarshal(Avalbytes, &dat); err != nil { panic(err) } var recType string recType = dat["RecType"].(string) switch recType { case "ARTINV": ar, err := JSONtoAR(Avalbytes) // if err != nil { fmt.Println("ProcessRequestType(): Cannot create itemObject \n") return err } // Decrypt Image and Save Image in a file image := itpUtils.Decrypt(ar.AES_Key, ar.ItemImage) if err != nil { fmt.Println("ProcessRequestType() : Image decrytion failed ") return err } fmt.Println("ProcessRequestType() : Image conversion from byte[] to file successfull ") err = itpUtils.ByteArrayToImage(image, "copy."+ar.ItemPicFN) if err != nil { fmt.Println("ProcessRequestType() : Image conversion from byte[] to file failed ") return err } return err case "USER": ur, err := JSONtoUser(Avalbytes) // if err != nil { return err } fmt.Println("ProcessRequestType() : ", ur) return err case "AUCREQ": ar, err := JSONtoAucReq(Avalbytes) // if err != nil { return err } fmt.Println("ProcessRequestType() : ", ar) return err case "OPENAUC": ar, err := JSONtoAucReq(Avalbytes) // if err != nil { return err } fmt.Println("ProcessRequestType() : ", ar) return err case "CLAUC": ar, err := JSONtoAucReq(Avalbytes) // if err != nil { return err } fmt.Println("ProcessRequestType() : ", ar) return err case "POSTTRAN": atr, err := JSONtoTran(Avalbytes) // if err != nil { return err } fmt.Println("ProcessRequestType() : ", atr) return err case "BID": bid, err := JSONtoBid(Avalbytes) // if err != nil { return err } fmt.Println("ProcessRequestType() : ", bid) return err case "DEFAULT": return nil case "XFER": return nil default: return errors.New("Unknown") } return nil } ================================================ FILE: art/artchaincode/vendor/itpUtils/image_proc_0.6api.go ================================================ /****************************************************************** Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ******************************************************************/ /////////////////////////////////////////////////////////////////////// // Author : Mohan Venkataraman // Purpose: Explore the Hyperledger/fabric and understand // how to write an chain code, application/chain code boundaries // The code is not the best as it has just hammered out in a day or two // Feedback and updates are appreciated /////////////////////////////////////////////////////////////////////// package itpUtils import ( "bufio" "bytes" "crypto/aes" "crypto/cipher" "crypto/rand" "errors" "fmt" "image" "image/gif" "image/jpeg" "image/png" "io" "net/http" "os" ) /////////////////////////////////////////////////////////// // Convert Image to []bytes and viceversa // Detect Image Filetype // Image Function to read an image and create a byte array // Currently only PNG images are supported /////////////////////////////////////////////////////////// func ImageToByteArray(imageFile string) ([]byte, string) { file, err := os.Open(imageFile) if err != nil { fmt.Println("imageToByteArray() : cannot OPEN image file ", err) return nil, string("imageToByteArray() : cannot OPEN image file ") } defer file.Close() fileInfo, _ := file.Stat() var size int64 = fileInfo.Size() bytes := make([]byte, size) // read file into bytes buff := bufio.NewReader(file) _, err = buff.Read(bytes) if err != nil { fmt.Println("imageToByteArray() : cannot READ image file") return nil, string("imageToByteArray() : cannot READ image file ") } filetype := http.DetectContentType(bytes) fmt.Println("imageToByteArray() : ", filetype) //filetype := GetImageType(bytes) return bytes, filetype } ////////////////////////////////////////////////////// // If Valid fileType, will have "image" as first word ////////////////////////////////////////////////////// func GetImageType(buff []byte) string { filetype := http.DetectContentType(buff) switch filetype { case "image/jpeg", "image/jpg": return filetype case "image/gif": return filetype case "image/png": return filetype case "application/pdf": // not image, but application ! filetype = "application/pdf" default: filetype = "Unknown" } return filetype } //////////////////////////////////////////////////////////// // Converts a byteArray into an image and saves it // into an appropriate file // It is important to get the file type before saving the // file by call the GetImageType //////////////////////////////////////////////////////////// func ByteArrayToImage(imgByte []byte, imageFile string) error { // convert []byte to image for saving to file img, _, _ := image.Decode(bytes.NewReader(imgByte)) fmt.Println("ProcessQueryResult ByteArrayToImage : proceeding to create image ") //save the imgByte to file out, err := os.Create(imageFile) if err != nil { fmt.Println("ByteArrayToImage() : cannot CREATE image file ", err) return errors.New("ByteArrayToImage() : cannot CREATE image file ") } fmt.Println("ProcessRequestType ByteArrayToImage : proceeding to Encode image ") //err = png.Encode(out, img) filetype := http.DetectContentType(imgByte) switch filetype { case "image/jpeg", "image/jpg": var opt jpeg.Options opt.Quality = 100 err = jpeg.Encode(out, img, &opt) case "image/gif": var opt gif.Options opt.NumColors = 256 err = gif.Encode(out, img, &opt) case "image/png": err = png.Encode(out, img) default: err = errors.New("Only PMNG, JPG and GIF Supported ") } if err != nil { fmt.Println("ByteArrayToImage() : cannot ENCODE image file ", err) return errors.New("ByteArrayToImage() : cannot ENCODE image file ") } // everything ok fmt.Println("Image file generated and saved to ", imageFile) return nil } /////////////////////////////////////////////////////////////////////// // Encryption and Decryption Section // Images will be Encrypted and stored and the key will be part of the // certificate that is provided to the Owner /////////////////////////////////////////////////////////////////////// const ( AESKeyLength = 32 // AESKeyLength is the default AES key length NonceSize = 24 // NonceSize is the default NonceSize ) /////////////////////////////////////////////////// // GetRandomBytes returns len random looking bytes /////////////////////////////////////////////////// func GetRandomBytes(len int) ([]byte, error) { key := make([]byte, len) _, err := rand.Read(key) if err != nil { return nil, err } return key, nil } //////////////////////////////////////////////////////////// // GenAESKey returns a random AES key of length AESKeyLength // 3 Functions to support Encryption and Decryption // GENAESKey() - Generates AES symmetric key // Encrypt() Encrypts a [] byte // Decrypt() Decryts a [] byte //////////////////////////////////////////////////////////// func GenAESKey() ([]byte, error) { return GetRandomBytes(AESKeyLength) } func PKCS5Pad(src []byte) []byte { padding := aes.BlockSize - len(src)%aes.BlockSize pad := bytes.Repeat([]byte{byte(padding)}, padding) return append(src, pad...) } func PKCS5Unpad(src []byte) []byte { len := len(src) unpad := int(src[len-1]) return src[:(len - unpad)] } func Decrypt(key []byte, ciphertext []byte) []byte { // Create the AES cipher block, err := aes.NewCipher(key) if err != nil { panic(err) } // Before even testing the decryption, // if the text is too small, then it is incorrect if len(ciphertext) < aes.BlockSize { panic("Text is too short") } // Get the 16 byte IV iv := ciphertext[:aes.BlockSize] // Remove the IV from the ciphertext ciphertext = ciphertext[aes.BlockSize:] // Return a decrypted stream stream := cipher.NewCFBDecrypter(block, iv) // Decrypt bytes from ciphertext stream.XORKeyStream(ciphertext, ciphertext) return ciphertext } func Encrypt(key []byte, ba []byte) []byte { // Create the AES cipher block, err := aes.NewCipher(key) if err != nil { panic(err) } // Empty array of 16 + ba length // Include the IV at the beginning ciphertext := make([]byte, aes.BlockSize+len(ba)) // Slice of first 16 bytes iv := ciphertext[:aes.BlockSize] // Write 16 rand bytes to fill iv if _, err := io.ReadFull(rand.Reader, iv); err != nil { panic(err) } // Return an encrypted stream stream := cipher.NewCFBEncrypter(block, iv) // Encrypt bytes from ba to ciphertext stream.XORKeyStream(ciphertext[aes.BlockSize:], ba) return ciphertext } ================================================ FILE: art/artchaincode/vendor/itpUtils/table_1.0api.go ================================================ /****************************************************************** Copyright IT People Corp. 2017 All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ******************************************************************/ /////////////////////////////////////////////////////////////////////// // Author : IT People - Mohan - table API for v1.0 // Enable CouchDb as the database.. // Purpose: Explore the Hyperledger/fabric and understand // how to write an chain code, application/chain code boundaries // The code is not the best as it has just hammered out in a day or two // Feedback and updates are appreciated /////////////////////////////////////////////////////////////////////// package itpUtils import ( "errors" "bytes" "fmt" "github.com/hyperledger/fabric/core/chaincode/shim" ) ////////////////////////////////////////////////////////////////////////////////////////////////// // The recType is a mandatory attribute. The original app was written with a single table // in mind. The only way to know how to process a record was the 70's style 80 column punch card // which used a record type field. The array below holds a list of valid record types. // This could be stored on a blockchain table or an application ////////////////////////////////////////////////////////////////////////////////////////////////// var recType = []string{"ARTINV", "USER", "BID", "AUCREQ", "POSTTRAN", "OPENAUC", "CLAUC", "XFER", "VERIFY"} ////////////////////////////////////////////////////////////////////////////////////////////////// // The following array holds the list of tables that should be created // The deploy/init deletes the tables and recreates them every time a deploy is invoked ////////////////////////////////////////////////////////////////////////////////////////////////// var Objects = []string{"PARTY", "CASHTXN", "User", "UserCat", "Item", "ItemCat", "ItemHistory", "Auction", "AucInit", "AucOpen", "Bid", "Trans"} ///////////////////////////////////////////////////////////////////////////////////////////////////// // A Map that holds ObjectNames and the number of Keys // This information is used to dynamically Create, Update // Replace , and Query the Ledger // In this model all attributes in a table are strings // The chain code does both validation // A dummy key like 2016 in some cases is used for a query to get all rows // // "User": 1, Key: UserID // "Item": 1, Key: ItemID // "UserCat": 3, Key: "2016", UserType, UserID // "ItemCat": 3, Key: "2016", ItemSubject, ItemID // "Auction": 1, Key: AuctionID // "AucInit": 2, Key: Year, AuctionID // "AucOpen": 2, Key: Year, AuctionID // "Trans": 2, Key: AuctionID, ItemID // "Bid": 2, Key: AuctionID, BidNo // "ItemHistory": 4, Key: ItemID, Status, AuctionHouseID(if applicable),date-time // // The additional key is the ObjectType (aka ObjectName or Object). The keys would be // keys: {"User", UserId} or keys: {"AuctInit", "2016", "1134"} ///////////////////////////////////////////////////////////////////////////////////////////////////// func GetNumberOfKeys(tname string) int { ObjectMap := map[string]int{ "User": 1, "Item": 1, "UserCat": 3, "ItemCat": 3, "Auction": 1, "AucInit": 2, "AucOpen": 2, "Trans": 2, "Bid": 2, "ItemHistory": 4, "PARTY": 2, "CASHTXN": 1, } return ObjectMap[tname] } ///////////////////////////////////////////////////////////////// // This function checks the incoming args for a valid record // type entry as per the declared array recType[] // The rectType attribute can be anywhere in the args or struct // not necessarily in args[1] as per my old logic // The Request type is used to direct processing // the record accordingly e: recType is "USER" // "Args":["PostUser","100", "USER", "Ashley Hart", "TRD", "Morrisville Parkway, #216, Morrisville, NC 27560", // "9198063535", "ashley@it people.com", "SUNTRUST", "0001732345", "0234678", "2017-01-02 15:04:05"]}' ///////////////////////////////////////////////////////////////// func ChkRecType(args []string) bool { for _, rt := range args { for _, val := range recType { if val == rt { return true } } } return false } ///////////////////////////////////////////////////////////////// // Checks if the incoming invoke has a valid requesType // The Request type is used to process the record accordingly // Old Logic (see new logic up) ///////////////////////////////////////////////////////////////// func CheckRecType(rt string) bool { for _, val := range recType { if val == rt { fmt.Println("CheckRequestType() : Valid Request Type , val : ", val, rt, "\n") return true } } fmt.Println("CheckRequestType() : Invalid Request Type , val : ", rt, "\n") return false } ///////////////////////////////////////////////////////////////// // Checks if the args contain a valid Record Type. Typically, this // model expects the Object Type to be args[2] but // for the sake of flexibility, it scans the input data for // a valid type if available ///////////////////////////////////////////////////////////////// func IdentifyRecType(args []string) (string, error) { for _, rt := range args { for _, val := range recType { if val == rt { return rt, nil } } } return "", fmt.Errorf("IdentifyRecType: Not Found") } ///////////////////////////////////////////////////////////////// // Checks if the args contain a valid Object Type. Typically, this // model expects the Object Type to be args[0] but // for the sake of flexibility, it scans the input data for // a valid type if available ///////////////////////////////////////////////////////////////// func IdentifyObjectType(args []string) (string, error) { for _, rt := range args { for _, val := range Objects { if val == rt { return rt, nil } } } return "", fmt.Errorf("IdentifyObjectType: Object Not Found") } //////////////////////////////////////////////////////////////////////////// // Open a Ledgers if one does not exist // These ledgers will be used to write / read data //////////////////////////////////////////////////////////////////////////// func InitObject(stub shim.ChaincodeStubInterface, objectType string, keys []string) error { fmt.Println(">> Not Implemented Yet << Initializing Object : " , objectType, " Keys: ", keys) return nil } //////////////////////////////////////////////////////////////////////////// // Update the Object - Replace current data with replacement // Register users into this table //////////////////////////////////////////////////////////////////////////// func UpdateObject(stub shim.ChaincodeStubInterface, objectType string, keys []string, objectData []byte) error { // Check how many keys err := VerifyAtLeastOneKeyIsPresent(objectType, keys ) if err != nil { return err } // Convert keys to compound key compositeKey, _ := stub.CreateCompositeKey(objectType, keys) // Add Object JSON to state err = stub.PutState(compositeKey, objectData) if err != nil { fmt.Println("UpdateObject() : Error inserting Object into State Database %s", err) return err } return nil } //////////////////////////////////////////////////////////////////////////////////////////////////////////// // Retrieve the object based on the key and simply delete it // //////////////////////////////////////////////////////////////////////////////////////////////////////////// func DeleteObject(stub shim.ChaincodeStubInterface, objectType string, keys []string) error { // Check how many keys err := VerifyAtLeastOneKeyIsPresent(objectType, keys ) if err != nil { return err } // Convert keys to compound key compositeKey, _ := stub.CreateCompositeKey(objectType, keys) // Remove object from the State Database err = stub.DelState(compositeKey) if err != nil { fmt.Println("DeleteObject() : Error deleting Object into State Database %s", err) return err } fmt.Println("DeleteObject() : ", "Object : " , objectType, " Key : ", compositeKey) return nil } //////////////////////////////////////////////////////////////////////////////////////////////////////////// // Delete all objects of ObjectType // //////////////////////////////////////////////////////////////////////////////////////////////////////////// func DeleteAllObjects(stub shim.ChaincodeStubInterface, objectType string) error { // Convert keys to compound key compositeKey, _ := stub.CreateCompositeKey(objectType, []string{""}) // Remove object from the State Database err := stub.DelState(compositeKey) if err != nil { fmt.Println("DeleteAllObjects() : Error deleting all Object into State Database %s", err) return err } fmt.Println("DeleteObject() : ", "Object : " , objectType, " Key : ", compositeKey) return nil } //////////////////////////////////////////////////////////////////////////////////////////////////////////// // Replaces the Entry in the Ledger // The existing object is simply queried and the data contents is replaced with // new content //////////////////////////////////////////////////////////////////////////////////////////////////////////// func ReplaceObject(stub shim.ChaincodeStubInterface, objectType string, keys []string, objectData []byte) error { // Check how many keys err := VerifyAtLeastOneKeyIsPresent(objectType, keys ) if err != nil { return err } // Convert keys to compound key compositeKey, _ := stub.CreateCompositeKey(objectType, keys) // Add Party JSON to state err = stub.PutState(compositeKey, objectData) if err != nil { fmt.Println("ReplaceObject() : Error replacing Object in State Database %s", err) return err } fmt.Println("ReplaceObject() : - end init object ", objectType) return nil } //////////////////////////////////////////////////////////////////////////// // Query a User Object by Object Name and Key // This has to be a full key and should return only one unique object //////////////////////////////////////////////////////////////////////////// func QueryObject(stub shim.ChaincodeStubInterface, objectType string, keys []string) ([]byte, error) { // Check how many keys err := VerifyAtLeastOneKeyIsPresent(objectType, keys ) if err != nil { return nil, err } compoundKey, _ := stub.CreateCompositeKey(objectType, keys) fmt.Println("QueryObject() : Compound Key : ", compoundKey) Avalbytes, err := stub.GetState(compoundKey) if err != nil { return nil, err } return Avalbytes, nil } //////////////////////////////////////////////////////////////////////////// // Query a User Object by Object Name and Key // This has to be a full key and should return only one unique object //////////////////////////////////////////////////////////////////////////// func QueryObjectWithProcessingFunction(stub shim.ChaincodeStubInterface, objectType string, keys []string, fname func(shim.ChaincodeStubInterface, []byte, []string)(error)) ([]byte, error) { // Check how many keys err := VerifyAtLeastOneKeyIsPresent(objectType, keys ) if err != nil { return nil, err } compoundKey, _ := stub.CreateCompositeKey(objectType, keys) fmt.Println("QueryObject: Compound Key : ", compoundKey) Avalbytes, err := stub.GetState(compoundKey) if err != nil { return nil, err } if Avalbytes == nil { return nil, fmt.Errorf("QueryObject: No Data Found for Compound Key : ", compoundKey) } // Perform Any additional processing of data fmt.Println("fname() : Successful - Proceeding to fname" ) err = fname(stub, Avalbytes, keys) if err != nil { fmt.Println("QueryLedger() : Cannot execute : ", fname) jsonResp := "{\"fname() Error\":\" Cannot create Object for key " + compoundKey + "\"}" return Avalbytes, errors.New(jsonResp) } return Avalbytes, nil } //////////////////////////////////////////////////////////////////////////// // Get a List of Rows based on query criteria from the OBC // The getList Function //////////////////////////////////////////////////////////////////////////// func GetKeyList(stub shim.ChaincodeStubInterface, args []string) (shim.StateQueryIteratorInterface, error) { // Define partial key to query within objects namespace (objectType) objectType := args[0] // Check how many keys err := VerifyAtLeastOneKeyIsPresent(objectType, args[1:] ) if err != nil { return nil, err } // Execute the Query // This will execute a key range query on all keys starting with the compound key resultsIterator, err := stub.GetStateByPartialCompositeKey(objectType, args[1:]) if err != nil { return nil, err } defer resultsIterator.Close() // Iterate through result set var i int for i = 0; resultsIterator.HasNext(); i++ { // Retrieve the Key and Object myCompositeKey, err := resultsIterator.Next() if err != nil { return nil, err } fmt.Println("GetList() : my Value : ", myCompositeKey.Key) } return resultsIterator, nil } /////////////////////////////////////////////////////////////////////////////////////////// // GetQueryResultForQueryString executes the passed in query string. // Result set is built and returned as a byte array containing the JSON results. /////////////////////////////////////////////////////////////////////////////////////////// func GetQueryResultForQueryString(stub shim.ChaincodeStubInterface, queryString string) ([]byte, error) { fmt.Println("GetQueryResultForQueryString() : getQueryResultForQueryString queryString:\n%s\n", queryString) resultsIterator, err := stub.GetQueryResult(queryString) if err != nil { return nil, err } defer resultsIterator.Close() // buffer is a JSON array containing QueryRecords var buffer bytes.Buffer buffer.WriteString("[") bArrayMemberAlreadyWritten := false for resultsIterator.HasNext() { queryResult, err := resultsIterator.Next() if err != nil { return nil, err } // Add a comma before array members, suppress it for the first array member if bArrayMemberAlreadyWritten == true { buffer.WriteString(",") } buffer.WriteString("{\"Key\":") buffer.WriteString("\"") buffer.WriteString(queryResult.Key) buffer.WriteString("\"") buffer.WriteString(", \"Record\":") // Record is a JSON object, so we write as-is buffer.WriteString(string(queryResult.Value)) buffer.WriteString("}") bArrayMemberAlreadyWritten = true } buffer.WriteString("]") fmt.Println("GetQueryResultForQueryString(): getQueryResultForQueryString queryResult:\n%s\n", buffer.String()) return buffer.Bytes(), nil } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Retrieve a list of Objects from the Query // The function returns an iterator from which objects can be retrieved. // defer rs.Close() // // // Iterate through result set // var i int // for i = 0; rs.HasNext(); i++ { // // // We can process whichever return value is of interest // myKey , myKeyVal , err := rs.Next() // if err != nil { // return shim.Success(nil) // } // bob, _ := JSONtoUser(myKeyVal) // fmt.Println("GetList() : my Value : ", bob) // } // // eg: Args":["fetchlist", "PARTY","CHK"]} // fetchList is the function that calls getList : ObjectType = "Party" and key is "CHK" ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// func GetList(stub shim.ChaincodeStubInterface, objectType string, keys []string) (shim.StateQueryIteratorInterface , error) { // Check how many keys err := VerifyAtLeastOneKeyIsPresent(objectType, keys ) if err != nil { return nil, err } // Get Result set resultIter, err := stub.GetStateByPartialCompositeKey(objectType, keys) fmt.Println("GetList(): Retrieving Objects into an array") if err != nil { return nil, err } // Return iterator for result set // Use code above to retrieve objects return resultIter, nil } //////////////////////////////////////////////////////////////////////////// // This function verifies if the number of key provided is at least 1 and // < the the max keys defined for the Object //////////////////////////////////////////////////////////////////////////// func VerifyAtLeastOneKeyIsPresent(objectType string, args []string) error { // Check how many keys nKeys := GetNumberOfKeys(objectType) nCol := len(args) if nCol == 1 { return nil } if nCol < 1 { error_str := fmt.Sprintf("VerifyAtLeastOneKeyIsPresent() Failed: Atleast 1 Key must is needed : nKeys : %s, nCol : %s ", nKeys, nCol) fmt.Println(error_str) return errors.New(error_str) } return nil } ================================================ FILE: art/scripts/CloseOpenAuction ================================================ peer chaincode invoke -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args": ["iCloseOpenAuctions", "2016", "CLAUC", "2017-01-23 13:53:00.3 +0000 UTC"]}' ================================================ FILE: art/scripts/DeployAuction ================================================ peer chaincode deploy -n mycc -p github.com/hyperledger/fabric/auction/art/artchaincode -c '{"Args":["init", "A", "200","B", "200"]}' ================================================ FILE: art/scripts/InitAuc ================================================ peer chaincode install -C testchainid -n mycc -p github.com/hyperledger/fabric/auction/art/artchaincode -c '{"Args":["init"]}' -v 1.0 ================================================ FILE: art/scripts/InstAuc ================================================ peer chaincode instantiate -C testchainid -n mycc -p github.com/hyperledger/fabric/auction/art/artchaincode -c '{"Args":["init"]}' -v 1.0 ================================================ FILE: art/scripts/OpenAuctionRequestForBids ================================================ peer chaincode invoke -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args":["iOpenAuctionForBids", "1111", "OPENAUC", "10", "2017-02-13 09:18:00"]}' #peer chaincode invoke -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args":["iOpenAuctionForBids", "1112", "OPENAUC", "10", "2017-02-13 09:18:00"]}' ================================================ FILE: art/scripts/PostAuctionRequest ================================================ peer chaincode invoke -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostAuctionRequest", "1111", "AUCREQ", "1000", "200", "100", "04012016", "1200", "1800", "INIT", "2017-02-13 09:05:00","2017-02-13 09:05:00", "2017-02-13 09:10:00"]}' peer chaincode invoke -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostAuctionRequest", "1112", "AUCREQ", "1100", "200", "300", "04012016", "1200", "1800", "INIT", "2017-02-13 09:05:00","2017-02-13 09:05:00", "2017-02-13 09:10:00"]}' ================================================ FILE: art/scripts/PostItems ================================================ peer chaincode invoke -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostItem", "1000", "ARTINV", "Shadows by Asppen", "Asppen Messer", "20140202", "Original", "landscape", "Canvas", "15 x 15 in", "art1.png","600", "100", "2017-01-23 14:04:05"]}' sleep 5 peer chaincode invoke -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostItem", "1100", "ARTINV", "modern Wall Painting", "Scott Palmer", "20140202", "Reprint", "landscape", "Acrylic", "10 x 10 in", "art2.png","2600", "300", "2017-01-23 14:04:05"]}' sleep 5 peer chaincode invoke -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostItem", "1200", "ARTINV", "Splash of Color", "Jennifer Drew", "20160115", "Reprint", "modern", "Water Color", "15 x 15 in", "art3.png","1600", "100", "2017-01-23 14:04:05"]}' sleep 5 peer chaincode invoke -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostItem", "1300", "ARTINV", "Female Water Color", "David Crest", "19900115", "Original", "modern", "Water Color", "12 x 17 in", "art4.png","9600", "100", "2017-01-23 14:04:05"]}' sleep 5 peer chaincode invoke -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostItem", "1400", "ARTINV", "Nature", "James Thomas", "19900115", "Original", "modern", "Water Color", "12 x 17 in", "item-001.jpg","1800", "100", "2017-01-23 14:04:05"]}' sleep 5 peer chaincode invoke -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostItem", "1500", "ARTINV", "Ladys Hair", "James Thomas", "19900115", "Original", "landscape", "Acrylic", "12 x 17 in", "item-002.jpg","1200", "300", "2017-01-23 14:04:05"]}' sleep 5 peer chaincode invoke -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostItem", "1600", "ARTINV", "Flowers", "James Thomas", "19900115", "Original", "modern", "Acrylic", "12 x 17 in", "item-003.jpg","1000", "300", "2017-01-23 14:04:05"]}' sleep 5 peer chaincode invoke -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostItem", "1700", "ARTINV", "Women at work", "James Thomas", "19900115", "Original", "modern", "Acrylic", "12 x 17 in", "item-004.jpg","1500", "400", "2017-01-23 14:04:05"]}' sleep 5 peer chaincode invoke -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostItem", "1800", "ARTINV", "People", "James Thomas", "19900115", "Original", "modern", "Acrylic", "12 x 17 in", "people.gif","900", "400", "2017-01-23 14:04:05"]}' sleep 5 peer chaincode invoke -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostItem", "1900", "ARTINV", "Mad fb", "James Thomas", "19900115", "Original", "modern", "Acrylic", "12 x 17 in", "mad-fb.jpg","1100", "500", "2017-01-23 14:04:05"]}' sleep 5 ================================================ FILE: art/scripts/PostUsers ================================================ peer chaincode invoke -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostUser","100", "USER", "Ashley Hart", "TRD", "Morrisville Parkway, #216, Morrisville, NC 27560", "9198063535", "ashley@itpeople.com", "SUNTRUST", "0001732345", "0234678", "2017-01-02 15:04:05"]}' sleep 5 peer chaincode invoke -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostUser","200", "USER", "Sotheby", "AH", "One Picadally Circus , #216, London, UK ", "9198063535", "admin@sotheby.com", "Standard Chartered", "0001732345", "0234678", "2017-01-02 15:04:05"]}' sleep 5 peer chaincode invoke -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostUser","300", "USER", "Barry Smith", "TRD", "155 Regency Parkway, #111, Cary, 27518 ", "9198063535", "barry@us.ibm.com", "RBC Centura", "0001732345", "0234678", "2017-01-02 15:04:05"]}' sleep 5 peer chaincode invoke -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostUser","400", "USER", "Cindy Patterson", "TRD", "155 Sunset Blvd, Beverly Hills, CA, USA ", "9058063535", "cpatterson@hotmail.com", "RBC Centura", "0001732345", "0234678", "2017-01-02 15:04:05"]}' sleep 5 peer chaincode invoke -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostUser","500", "USER", "Tamara Haskins", "TRD", "155 Sunset Blvd, Beverly Hills, CA, USA ", "9058063535", "tamara@yahoo.com", "RBC Centura", "0001732345", "0234678", "2017-01-02 15:04:05"]}' sleep 5 peer chaincode invoke -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostUser","600", "USER", "NY Life", "INS", "155 Broadway, New York, NY, USA ", "9058063535", "barry@nyl.com", "RBC Centura", "0001732345", "0234678", "2017-01-02 15:04:05"]}' sleep 5 peer chaincode invoke -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostUser","700", "USER", "J B Hunt", "SHP", "One Johnny Blvd, Rogers, AR, USA ", "9058063535", "jess@jbhunt.com", "RBC Centura", "0001732345", "0234678", "2017-01-02 15:04:05"]}' sleep 5 peer chaincode invoke -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostUser","800", "USER", "R&R Trading", "AH", "155 Sunset Blvd, Beverly Hills, CA, USA ", "9058063535", "larry@rr.com", "RBC Centura", "0001732345", "0234678", "2017-01-02 15:04:05"]}' ================================================ FILE: art/scripts/Script4Demo.sh ================================================ #!/bin/bash echo '################# STARTED POST USERS #################' peer chaincode deploy -l golang -n mycc -c '{"Function": "init", "Args":["INITIALIZE"]}' sleep 1 peer chaincode invoke -l golang -n mycc -c '{"Function": "PostUser", "Args":["200", "USER", "Heritage Auctions", "AH", "3500 Maple Avenue, 17th Floor Dallas, Texas 75219 ", "214-528-3500", "admin@ha.com", "Standard Chartered", "0001732345", "0234678"]}' sleep 1 peer chaincode invoke -l golang -n mycc -c '{"Function": "PostUser", "Args":["300", "USER", "Barry Smith", "TRD", "155 Regency Parkway, #111, Cary, 27518 ", "919-806-3535", "barrys@us.ibm.com", "RBC Centura", "0001732345", "1634678"]}' sleep 1 peer chaincode invoke -l golang -n mycc -c '{"Function": "PostUser", "Args":["400", "USER", "Cindy Patterson", "TRD", "155 Sunset Blvd, Beverly Hills, CA, USA ", "905-806-3535", "cpatterson@hotmail.com", "RBC Centura", "0001732345", "0234678"]}' sleep 1 peer chaincode invoke -l golang -n mycc -c '{"Function": "PostUser", "Args":["500", "USER", "Tamara Haskins", "TRD", "155 Sunset Blvd, Beverly Hills, CA, USA ", "9058063535", "tamara@yahoo.com", "RBC Centura", "0001732345", "5235678"]}' sleep 1 peer chaincode invoke -l golang -n mycc -c '{"Function": "PostUser", "Args":["600", "USER", "Jacob", "TRD", "155 Broadway, New York, NY, USA ", "118-806-3535", "jacob@gmail.com", "RBC Centura", "0001732345", "0234678"]}' sleep 1 peer chaincode invoke -l golang -n mycc -c '{"Function": "PostUser", "Args":["700", "USER", "Suntrust", "BNK", "155 Sunset Blvd, Beverly Hills, CA, USA ", "9058063535", "admin@suntrust.com", "Suntrust", "0001732345", "2023678"]}' sleep 1 peer chaincode invoke -l golang -n mycc -c '{"Function": "PostUser", "Args":["800", "USER", "J B Hunt", "SHP", "One Johnny Blvd, Rogers, AR, USA ", "9058063535", "jess@jbhunt.com", "RBC Centura", "0001732345", "1023687"]}' sleep 1 peer chaincode invoke -l golang -n mycc -c '{"Function": "PostUser", "Args":["900", "USER", "Metlife", "MET", "201 MetLife Way Cary, NC 27513", "215-806-3535", "admin@metlife.com", "Standard Chartered", "3001745345", "9734678"]}' sleep 1 echo '################# COMPLETED POST USERS #################' echo '################# STARTED POST ITEMS #################' peer chaincode invoke -l golang -n mycc -c '{"Function": "PostItem", "Args":["1100", "ARTINV", "Bowl Of Grapes", "jacques Linard", "01-08-2008", "Reprint", "landscape", "Canvas", "45 x 45 in", "item-004.jpg","8500", "300"]}' sleep 3 peer chaincode invoke -l golang -n mycc -c '{"Function": "PostItem", "Args":["1200", "ARTINV", "Modern Wall Painting", "Scott Palmer", "02-02-2010", "Reprint", "modern", "Acrylic", "10 x 10 in", "art1.png","6000", "300"]}' sleep 3 peer chaincode invoke -l golang -n mycc -c '{"Function": "PostItem", "Args":["1300", "ARTINV", "Splash of Color", "Jennifer Drew", "06-05-2016", "Reprint", "modern", "Water Color", "15 x 15 in", "art2.png","8000", "300"]}' sleep 3 peer chaincode invoke -l golang -n mycc -c '{"Function": "PostItem", "Args":["1400", "ARTINV", "Female Water Color", "David Crest", "01-15-1990", "Reprint", "modern", "Water Color", "15 x 15 in", "art3.png","5000", "300"]}' sleep 3 peer chaincode invoke -l golang -n mycc -c '{"Function": "PostItem", "Args":["1500", "ARTINV", "Mother and Child", "Hugues Merle", "02-08-1600", "Original", "landscape", "Canvas", "17 x 17 in", "item-003.jpg","4000", "300"]}' sleep 3 echo '################# COMPLETED POST ITEMS #################' ================================================ FILE: art/scripts/SubmitBids ================================================ peer chaincode invoke -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostBid", "1111", "BID", "1", "1000", "300", "1200", "2017-02-13 09:19:01"]}' sleep 5 peer chaincode invoke -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostBid", "1111", "BID", "2", "1000", "400", "3000", "2017-02-13 09:19:02"]}' sleep 5 peer chaincode invoke -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostBid", "1111", "BID", "3", "1000", "400", "6000", "2017-02-13 09:19:03"]}' sleep 5 peer chaincode invoke -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostBid", "1111", "BID", "4", "1000", "500", "7000", "2017-02-13 09:19:04"]}' sleep 5 peer chaincode invoke -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostBid", "1111", "BID", "5", "1000", "400", "8000", "2017-02-13 09:19:05"]}' sleep 5 peer chaincode invoke -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostBid", "1111", "BID", "6", "1000", "600", "8200", "2017-02-13 09:19:06"]}' sleep 5 peer chaincode invoke -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostBid", "1111", "BID", "7", "1000", "700", "8400", "2017-02-13 09:19:07"]}' sleep 5 peer chaincode invoke -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostBid", "1111", "BID", "8", "1000", "300", "8900", "2017-02-13 09:19:08"]}' sleep 5 peer chaincode invoke -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostBid", "1111", "BID", "9", "1000", "400", "9000", "2017-02-13 09:19:09"]}' sleep 5 peer chaincode invoke -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args":["iPostBid", "1111", "BID", "10", "1000", "500", "12000", "2017-02-12 09:20:00"]}' sleep 5 ================================================ FILE: art/scripts/SubmitQueries ================================================ peer chaincode query -l golang -n mycc -c '{"Args": ["qGetUser", "100"]}' peer chaincode query -l golang -n mycc -c '{"Args": ["qGetUser", "200"]}' peer chaincode query -l golang -n mycc -c '{"Args": ["qGetUser", "300"]}' peer chaincode query -l golang -n mycc -c '{"Args": ["qGetUser", "400"]}' peer chaincode query -l golang -n mycc -c '{"Args": ["qGetUser", "500"]}' peer chaincode query -l golang -n mycc -c '{"Args": ["qGetAuctionRequest", "1111"]}' peer chaincode query -l golang -n mycc -c '{"Args": ["qGetListOfBids", "1111"]}' peer chaincode query -l golang -n mycc -c '{"Args": ["qGetLastBid", "1111"]}' peer chaincode query -l golang -n mycc -c '{"Args": ["qGetHighestBid", "1111"]}' peer chaincode query -l golang -n mycc -c '{"Args": ["qGetItem", "1000"]}' peer chaincode query -l golang -n mycc -c '{"Args": ["qGetAuctionRequest", "1111"]}' peer chaincode query -l golang -n mycc -c '{"Args": ["qGetItemLog", "1000"]}' peer chaincode query -l golang -n mycc -c '{"Args": ["qGetAuctionRequest", "1111"]}' peer chaincode query -l golang -n mycc -c '{"Args": ["qGetListOfInitAucs", "2016"]}' peer chaincode query -l golang -n mycc -c '{"Args": ["qGetListOfOpenAucs", "2016"]}' ================================================ FILE: art/scripts/TransferItem ================================================ # Please update data with correct item id, owner and key peer chaincode invoke -l golang -o 127.0.0.1:7050 -n mycc -c '{"Function": "iTransferItem", "Args": ["1000", "500", "Ub0GsO1F8wsCv2LCCMudITn2Z61ZOeOH+kXs9ZsbMv0=", "300", "XFER","2017-01-24 11:00:00"]}' ================================================ FILE: art/scripts/ValidateItemOwnerShip ================================================ # Update the itemid, ownerid and key as appropriate peer chaincode query -l golang -o 127.0.0.1:7050 -n mycc -c '{"Function": "qValidateItemOwnership", "Args": ["1000", "300", "s2knz/slWPjhPFdHnZTk2hZ863TPXqn/L1AYR8DyYEo="]}' peer chaincode query -l golang -o 127.0.0.1:7050 -n mycc -c '{"Function": "qValidateItemOwnership", "Args": ["1900", "500", "Ac0o41Lg19fFcrT02Bo4gI1NM0c3QDtVmZM+ODqYsxY="]}' ================================================ FILE: art/scripts/auction_e2e_script.sh ================================================ #!/bin/bash ORDERER_IP=orderer.example.com:7050 ORDERER_CA=$GOPATH/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem CHANNEL_NAME=mychannel CHAINCODE_NAME=mycc ## Install peer chaincode install -n $CHAINCODE_NAME -v 1 -p github.com/hyperledger/fabric/examples/chaincode/go/auction ##Instantiate peer chaincode instantiate -o $ORDERER_IP --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n $CHAINCODE_NAME -v 1 -c '{"Args":["init"]}' -P "OR ('Org1MSP.peer','Org2MSP.peer')" ##Post Users - invoke peer chaincode invoke -o $ORDERER_IP --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n $CHAINCODE_NAME -c '{"Args":["iPostUser","100", "USER", "Ashley Hart", "TRD", "Morrisville Parkway, #216, Morrisville, NC 27560", "9198063535", "ashley@itpeople.com", "SUNTRUST", "0001732345", "0234678", "2017-01-02 15:04:05"]}' peer chaincode invoke -o $ORDERER_IP --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n $CHAINCODE_NAME -c '{"Args":["iPostUser","200", "USER", "Sotheby", "AH", "One Picadally Circus , #216, London, UK ", "9198063535", "admin@sotheby.com", "Standard Chartered", "0001732345", "0234678", "2017-01-02 15:04:05"]}' ##Get Users - query peer chaincode query -C $CHANNEL_NAME -n $CHAINCODE_NAME -c "{\"Args\": [\"qGetUser\", \"100\"]}" peer chaincode query -C $CHANNEL_NAME -n $CHAINCODE_NAME -c "{\"Args\": [\"qGetUser\", \"200\"]}" ##Post Items - invoke peer chaincode invoke -o $ORDERER_IP --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n $CHAINCODE_NAME -c '{"Args":["iPostItem", "100", "ARTINV", "Shadows by Asppen", "Asppen Messer", "20140202", "Original", "landscape", "Canvas", "15 x 15 in", "art1.png","600", "100", "2017-01-23 14:04:05"]}' peer chaincode invoke -o $ORDERER_IP --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n $CHAINCODE_NAME -c '{"Args":["iPostItem", "200", "ARTINV", "modern Wall Painting", "Scott Palmer", "20140202", "Reprint", "landscape", "Acrylic", "10 x 10 in", "art2.png","2600", "200", "2017-01-23 14:04:05"]}' ##Get Items - query peer chaincode query -C $CHANNEL_NAME -n $CHAINCODE_NAME -c "{\"Args\": [\"qGetItem\", \"100\"]}" peer chaincode query -C $CHANNEL_NAME -n $CHAINCODE_NAME -c "{\"Args\": [\"qGetItem\", \"200\"]}" ## postAuction peer chaincode invoke -o $ORDERER_IP --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n $CHAINCODE_NAME -c "{\"Args\":[\"iPostAuctionRequest\", \"1000\", \"AUCREQ\", \"100\", \"200\", \"100\", \"04012016\", \"1000\", \"1000\", \"INIT\", \"2017-02-13 09:05:00\", \"2017-02-13 09:05:00\", \"2017-02-13 09:10:00\"]}" ## openAuctionRequestForBids peer chaincode invoke -o $ORDERER_IP --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n $CHAINCODE_NAME -c "{\"Args\":[\"iOpenAuctionForBids\", \"1000\", \"OPENAUC\", \"10\", \"2017-02-13 09:18:00\"]}" ## submitBids $ch $chain $(((RANDOM % 3))) $auctionindex $bidNumber $userid $biduserid $bidPrice peer chaincode invoke -o $ORDERER_IP --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n $CHAINCODE_NAME -c "{\"Args\":[\"iPostBid\", \"1000\", \"BID\", \"1000\", \"100\", \"100\", \"5000\", \"2017-02-13 09:19:01\"]}" # peer chaincode invoke -o $ORDERER_IP --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n $CHAINCODE_NAME -c "{\"Args\":[\"iPostBid\", \"1000\", \"BID\", \"1001\", \"200\", \"100\", \"5001\", \"2017-02-13 09:19:01\"]}" ##closeAuction peer chaincode invoke -o $ORDERER_IP --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n $CHAINCODE_NAME -c '{"Args": ["iCloseOpenAuctions", "2016", "CLAUC", "2017-01-23 13:53:00.3 +0000 UTC"]}' #Query for the item peer chaincode query -C $CHANNEL_NAME -n $CHAINCODE_NAME -c "{\"Args\": [\"qGetItem\", \"1000\"]}" AES_KEY="/UucdM++9gqy3X7TztFTbPbSiEsbj4WudUi2CuSZ4OU=" ## transferItem peer chaincode invoke -o $ORDERER_IP --tls --cafile $ORDERER_CA -C $CHANNEL_NAME -n $CHAINCODE_NAME -c "{\"Args\": [\"iTransferItem\", \"100\", \"100\", $AES_KEY, \"200\", \"XFER\",\"2017-01-24 11:00:00\"]}" ================================================ FILE: art/scripts/downloadImages.sh ================================================ #!/bin/bash wget -q https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/art1.png wget -q https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/art1.png wget -q https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/art1.png wget -q https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/art1.png wget -q https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/art1.png wget -q https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/art1.png wget -q https://raw.githubusercontent.com/ITPeople-Blockchain/auction/v0.6/art/artchaincode/art1.png ================================================ FILE: art/scripts/env.sh ================================================ #!/bin/bash export CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/msp/sampleconfig ================================================ FILE: art/scripts/getaucrequest ================================================ peer chaincode query -l golang -n mycc -c '{"Args": ["qGetAuctionRequest", "1111"]}' peer chaincode query -l golang -n mycc -c '{"Args": ["qGetAuctionRequest", "1112"]}' ================================================ FILE: art/scripts/getbids ================================================ peer chaincode query -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args": ["qGetListOfBids", "1111"]}' peer chaincode query -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args": ["qGetLastBid", "1111"]}' peer chaincode query -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args": ["qGetHighestBid", "1111"]}' ================================================ FILE: art/scripts/getitem ================================================ peer chaincode query -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args": ["qGetItem", "1000"]}' ================================================ FILE: art/scripts/getitemlog ================================================ peer chaincode query -l golang -n mycc -c '{"Args": ["qGetItemLog", "1000"]}' ================================================ FILE: art/scripts/getlistofaucs ================================================ peer chaincode query -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args": ["qGetListOfInitAucs", "2017"]}' peer chaincode query -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args": ["qGetListOfOpenAucs", "2017"]}' ================================================ FILE: art/scripts/getuser ================================================ peer chaincode query -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args": ["qGetUser", "100"]}' peer chaincode query -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args": ["qGetUser", "200"]}' peer chaincode query -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args": ["qGetUser", "300"]}' peer chaincode query -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args": ["qGetUser", "400"]}' peer chaincode query -l golang -o 127.0.0.1:7050 -n mycc -c '{"Args": ["qGetUser", "500"]}' ================================================ FILE: art/scripts/setup.sh ================================================ #!/bin/sh export PEER=$GOPATH/src/github.com/hyperledger/build/bin/peer ================================================ FILE: docs/Datastructures.txt ================================================ Data Structures # User Object UserID, RecType, Name, UserType, Address, Phone, Email, Bank, AccountNo, RoutingNo UserID RecType // Type = USER Name UserType // Auction House (AH), Bank (BK), Buyer or Seller (TR), Shipper (SH), Appraiser (AP) Address Phone Email Bank AccountNo RoutingNo # Item Object Item ID, RecType, Description, Details,Date of Origin, Type (Original or Reprint), Subject Area, Media (Canvas or Acrylic ..), Dimensions, File name (Certificate or image), Price, Current OwnerID # The following attributes are inserted during processing after the image file name. ItemID // AN Item identifier RecType // ARTINV ItemDesc // Description of the Item ItemDetail // Could included details such as who created the Art work if item is a Painting ItemDate // Date of Origin ItemType // Original or Reprint ItemSubject // Subject Area - Landscape, Modern, Potrait ItemMedia // Media like Canvas, Acrylic, Water Color ItemSize // Dimensions ItemPicFN // The file name of the picture or certificate ItemImage // This has to be generated AES encrypted using the file name AES_Key // This is generated by the AES Algorithms ItemImageType // should be used to regenerate the appropriate image type ItemBasePrice // Reserve Price at Auction must be greater than this price CurrentOwnerID // This is validated for a user registered record # Auction Request AuctionID RecType // AUCREQ ItemID AuctionHouseID // ID of the Auction House managing the auction SellerID // ID Of Seller - to verified against the Item CurrentOwnerId RequestDate // Date on which Auction Request was filed ReservePrice // reserve price > previous purchase price Status // INIT, OPEN, CLOSED (Updated by Open Auction) OpenDate // Date on which auction will occur (Updated by Open Auction) CloseDate // Date and time when Auction will close (Updated by Open Auction) # BID AuctionID RecType // BID BidNo // In future - chaincode generated ItemID // ID of Item (In future - one auction many items ) BuyerID // ID Of Buyer - to be verified against the Item CurrentOwnerId BidPrice // BidPrice > Previous Bid BidTime // Time the bid was received # Transaction AuctionID RecType // POSTTRAN ItemID // ID of item that got sold TransType // Sale, Buy, Commission UserId // Buyer ID TransDate // Date of Settlement (Buyer or Seller) HammerTime // Time of hammer strike - SOLD HammerPrice // Total Settlement price Details // Details about the Transaction ================================================ FILE: docs/index.md ================================================ [![Documentation Status](https://readthedocs.org/projects/itpeople-blockchain-auction/badge/?version=latest)](http://itpeople-blockchain-auction.readthedocs.io/en/latest/?badge=latest) #Art Auction Blockchain Application Credits: Ratnakar Asara, Nishi Nidamarty, Ramesh Thoomu, Adam Gordon and Mohan Venkataraman **Disclaimer:** The images used in this sample PoC application have been downloaded from publicly available images on the internet, and the copyright belongs to the respective owners. The usage here is strictly for non-commercial purposes as sample data. We recommend that users create their own sample data as appropriate. All names, addresses and accounts numbers used in the sample data are fictitous. The information provided in this README.md is subject to change. Auction Chaincode is migrated to fabric v1.0 and these changes being tested on **fabric commit#** 5b59e0652f9edf5bd12a6ff7fd2e9991495190fe ##Introduction This Hyperledger/Fabric is an implementation of blockchain technology, leveraging familiar and proven technologies. It is a modular architecture allowing pluggable implementations of various function. It features powerful container technology to host any mainstream language for smart contracts development. Chaincode (smart contracts) or blockchain applications run on the fabric. Chaincode is written in Go language The original intention of this application was to understand how to write a Go application on the Hyperledger/Fabric. This initial version was written to understand the different chaincode api's, the boundary that separates what goes into the blockchain and what lives within the enterprise application, usage of database features, error management etc.

## Application Description This application deals with auctioning ART on the block chain. The blockchain makes sense here as there are many different stakeholders and can leverage the benefits of the "Network Effect". This application deals with the following stake holders: * Buyers and Sellers or Traders (TRD) * Banks (BNK) * Insurance Companies (INS) * Shipping and Forwarding (SHP) * Auction Houses (AH) * Art Dealers (DEL) * Artists (ART) The typical business process is shown below ![Business Process](docs/images/art_process.png) ###Registering Stakeholder Accounts Artists, Traders, Dealers own **Assets** (Items). Auction Houses, Banks, Insurance Companies and Service Providers play a role in the auction process. To conduct business on the block chain, the stakeholder has to open an account on the block chain. In the production world, prior to opening an account, all of the stake-holder details may be authenticated by another blockchain like an IDaaS (Identity-as-a-Service). There are various stake holders as listed above, each with a different interest. ###Registering Assets or Items The Seller (Trader) who owns **Assets** must register the asset on the block chain in order to conduct business. When an **Asset** is submitted for registration, the chaincode does the following: * Checks if the owner has a registered account * Converts any presented "Certificate of Authenticity" or a credibly issued image to a byte stream, generates a key, encrypts the byte stream using the key and stores the image on the BC * Provides the key to the **owner** for safe keeping and future reference * Makes entries into the Item History so that the lifecycle of the Asset can be reviewed at any time ###Making a Request to Auction an Asset When the owner of an Asset sees an opportunity to make money, they would like to auction the Asset. They engage an Auction House and make a **request to auction** the Asset. The request always specifies a **"Reserve Price"**. Sometimes, the owner may additionally specify a **"Buy It Now"** price as well. When the request is made, the item, owner and the auction house are all validated. (The chaincode simply validates that they are all registered on the BC). The Auction House will most likely, behind the scene, get the Asset authenticated and determine the valuation before deciding to accept the item. One of the ways by which they could do some preliminary authentication is to request the **seller** to enter his **private key**, account-id and the registered item number into the client application. While the item number and account identifier is a straight validation, the key will be used to decrypt and retrieve the stored "certificate of authenticity or image". The state of the Auction is set to **INIT** at this point, until the Auction House is ready to **OPEN** the Asset for bids. ###Opening the Auction for Bids The Auction House will choose a time frame to place the item on auction and **OPEN** it up for accepting bids from potential Buyers. They may, if applicable, advertise the **BuyItNow** price. ### "Buy It Now" and Accepting Bids During the window of the auction, potential buyers can place bids. If a Buyer wishes to exercise the "Buy It Now", they can buy the item right away provided there is no bid higher than the "BuyItNow" price. Bids are accepted from buyers if * The Bids have are equal or greater than the **Reserve Price"** * The auction is still **OPEN** * The Buyer has a registered account ### Buy It Now When a buyer chooses this option, the chain code does the following * Validates the Buyer * Checks if there are any bidders whose bid is higher than the **"Buy It Now"** price. If so, the offer is rejected * If the **"Buy It Now"** price is applicable, it immediately **"CLOSES"** the auctions, creates a **transaction** * It assigns the Asset to the new owner * It also generates a new **Key**, re-encrypts the "Certificate of Ownership or Image", and provides the key to the new buyer * The new price of the Asset is set to the **"Buy It Now"** price if not higher ### Auction Expiry When the auction expires, the Auction House retrieves the highest bid and converts it to a **transaction** ( A transaction in the real world could mean creating insurance and shipping docs, collecting payments and commissions, issuing a new title or certificate to the new owner etc.), transfers ownership to the buyer and updates the price with the new **"Hammer"** price. It also generates a new **Key**, re-encrypts the "Certificate of Ownership or Image", and provides the key to the new buyer. ### Transfer an Item to another User The chain code supports this scenario, by allowing a **owner** of an Asset to transfer **"ownership"** to another person. The receiving person has to be registered on the block-chain. Currently the chain code does not execute any regulatory or compliance rules. ### Validating Asset Ownership The chain code supports this. In order to accomplish this, it does preliminary authentication by requesting the **seller** to enter his **private key**, account-id and the registered item number. While the item number and account identifier is a straight validation, the key will be used to decrypt and view the stored **"Certificate of Authenticity or image"**. If decryption fails, then it assumes that the owner is not the legal owner of the Asset. ## APIs Available The following Invoke and Query APIs are available from both CLI and REST, and have the following signature func(stub *shim.ChaincodeStub, function string, args []string) ([]byte, error) ### Invoke * iPostUser * iPostItem * iPostAuctionRequest * iPostTransaction * iPostBid * iOpenAuctionForBids * iBuyItNow * iTransferItem * iCloseAuction * iCloseOpenAuctions ### Query * qGetItem * qGetUser * qGetAuctionRequest * qGetTransaction * qGetBid * qGetLastBid * qGetHighestBid * qGetNoOfBidsReceived * qGetListOfBids * qGetItemLog * qGetItemListByCat * qGetUserListByCat * qGetListOfItemsOnAuc * qGetListOfOpenAucs * qValidateItemOwnership * qIsItemOnAuction ##Environment Setup Please review instructions on setting up the [Development Environment](https://github.com/hyperledger/fabric/blob/master/docs/source/dev-setup/devenv.rst) as well as the setting up the [Sandbox Environment](https://github.com/hyperledger/fabric/blob/master/docs/source/Setup/Chaincode-setup.rst) to execute the chaincode. ## Running the Application ** Fork and clone the fabric** ``` git clone https://github.com/hyperledger/fabric.git cd $GOPATH/src/github.com/hyperledger/fabric Fork Auction git repository from here https://github.com/ITPeople-Blockchain/auction.git and clone from your remote auction repository git clone https://github.com//auction.git ``` Execute below command to clear production data `rm -rf /var/hyperledger/*` ###Terminal 1 Build peer and Orderer binaries and Start Orderer ``` cd $GOPATH/src/github.com/hyperledger/fabric make native orderer ``` ###Terminal 2 Start Peer ``` cd $GOPATH/src/github.com/hyperledger/fabric peer node start -o 127.0.0.1:7050 ``` ###Terminal 3 ``` cd $GOPATH/src/github.com/hyperledger/fabric/auction/art/scripts Run env.sh script to export CORE_PEER_MSPCONFIGPATH `source env.sh` ``` ###Run the following shell scripts The scripts are provided for the convenience of understanding how to use the application as well as testing in your specific cloned environment. The scripts execute all API calls in CLI mode. In CLI mode, the peer and the chaincode live within the same container. However, these scripts will not work well in NET mode. To test the application in NET mode, follow the instructions on using the UI to make the API calls. #### PostUsers The PostUsers script inserts a set of users into the database. Today, the only validation done is to check if the user ID is an integer. TODO: In a future version, the user identity will be validated against the IDaaS Blockchain prior to inserting into the database `./PostUsers` #### PostItems The PostItems script inserts a set of ART ASSETS into the database. Before inserting the asset the chaincode checks if the CurrentOwner is registered as a User. Based on the image file name (in future this could be a title or some ownership document) is retrieved and converted to a byte array ([]byte). An AES Key is generated, the byte array is encrypted and both key and the byte array are saved in the database.A log entry is made in the Item Log. Please see code for detailed comments `./PostItems` In the business process, the owner (User ID# 100) of the ASSET (Item# 1000) requests an entity like an Auction House (User ID# 200) to put the item on auction. Before Posting the auction request, the Asset is validated against the database. The Auction House ID is verified in the User Table. A log entry is made in the Item Log. TODO: In future, the owner of the asset will present his key to help with validation. The AES key will be used to un-encrypt the stored image and authenticate ASSET ownership. #### PostAuctionRequest When the ASSET OWNER of an item is ready to place his item on auction, he/she would identify an Auction House, determine what the reserve price should be and send a request to the Auction House expressing interest in placing their item on the auction block. `./PostAuctionRequest` #### OpenAuctionRequestForBids The Auction House, we assume will inspect the physical item, the certificate of authenticity, the ownership key and other details. They would also run a valuation of the item to determine if the reserve price is valid. The application assumes these have occurred outside of the scope of the application Even though the ASSET OWNER has requested the Auction House to place the item on auction, the Auction is not yet open for acceptance of user bids. Hence any bid submitted against the item will be rejected if the auction is not open This script opens the Auction Request for bids. It sets the status of the AuctionRequest to "OPEN". It opens a timer for the duration of the auction which in the example is 3 minutes. During this window, any user can submit bids against the AuctionID. Once the timer expires, a script is created and saved called "CloseAuction.sh". The script gets triggered. ##### CloseAuction The CloseAuction.sh script invokes CloseAuction. CloseAuction will first change the status of the AuctionRequest to "CLOSED". It then fetches the highest bid from the list of bids received, and converts it to a Transaction. The transaction is posted, the ASSET is retrieved from the database, its price is set to the new Hammer Price and the CurrentOwner is set to the new buyer. The ASSET image is un-encrypted with the old key, a new Key is generated and the image is encrypted with the new key. The ASSET is updated in the database. An log entry is made in the Item Log. TODO: In future, the Transaction will be a business document that triggers payments, shipping,insurance and commissions `./OpenAuctionRequestForBids` Opens the auction request for bids for 3 minutes - Auction Request ID used for testing is 1111 and Item 1000 This opens a timer for 3 minutes and once timer expires, writes a shell script to invoke CloseAuction... As described above, once the auction is "OPEN", this script submits bids against that auctionID. Both the auctionID and the buyerID are validated before the bid is posted. Once the auction is "CLOSED", new bids will be rejected `./Submitbids` submits a series of bids against auction# 1111 and item# 1000 `./SubmitQueries` This is list of queries that can be issued and must be used via cut and paste on command line (CLI) After the timer expires, the Close auction should get invoked and the highest bid should be posted as a transaction ## Invoke APIs and usage **PostUser**: This function is used to register an Account for any of the stakeholders defined earlier **Usage (CLI mode)** The call takes 9 arguments: User ID, Record Type, Name, Type, Address, Phone, Email, Bank Name, Account#, Routing# peer chaincode invoke -l golang -n mycc -c '{"Function": "PostUser", "Args":["100", "USER", "Ashley Hart", "TRD", "Morrisville Parkway, #216, Morrisville, NC 27560", "9198063535", "ashley@itpeople.com", "SUNTRUST", "00017102345", "0234678"]}' **PostItem**: This function is used to register an Asset with the blockchain. The owner of the Asset must have a registered account prior to registering the Asset. No validation of the "User Type" is done by the chaincode and should be managed outside by the client. The name of the image (f6.png) is uploaded by the chaincode from a pre-determined directory, converted to []byte, encrypted using AES and stored in the blockchain. **Usage (CLI mode)** The call takes 12 arguments: Item ID, Record Type, Description, Detail, Date of Origin, Original or Reprint, Subject, Media, Size, Image File, Price, Current Owner ID peer chaincode invoke -l golang -n mycc -c '{"Function": "PostItem", "Args":["1400", "ARTINV", "Nature", "James Thomas", "19900115", "Original", "modern", "Water Color", "12 x 17 in", "f6.png","1800", "100"]}' **PostAuctionRequest**: This function is used by the owner of an Asset to request an Auction House to accept the Asset for auction. The Auction House ID, the owner ID and the asset ID are all validated before a request can be posted. Posting a request does not mean bids can be accepted. The auction has to be opened in order for bids to be accepted or "Buy It Now" to happen. **Usage (CLI mode)** This call takes 11 argumnets: Auction ID, Record Type, Item ID, Auction House ID, Owner ID, Date of Request, Reserve Price, Buy-It-Now Price, Status, Dummy Open Date, Dummy Close Date peer chaincode invoke -l golang -n mycc -c '{"Function": "PostAuctionRequest", "Args":["1113", "AUCREQ", "1000", "200", "400", "04012016", "15000", "16000", "INIT", "2016-05-20 11:00:00.3 +0000 UTC","2016-05-23 11:00:00.3 +0000 UTC"]}' The Auction Opendate and CloseDate are dummy dates and will be set when the auction is opened. The Auction ID must be unique and cannot be repeated. We assume that the client will generate a unique auction id prior to posting the request. The state of the Auction is "INIT" at this point. **OpenAuctionForBids**: This function is assumed to be invoked by the role of "Auction House" which is one of the types of accounts registered using PostUser. It allows the auctioner to open an auction for bids. The auction request must be in "INIT" state to be "OPEN"ed. When the auction is opened for bids, both the open and close date and time are set. The following example opens the bid for 3 minutes. Auction open durations are currently provided in minutes to support testing. **Usage (CLI mode)** The call takes 3 arguments: Auction ID, Record Type, Duration in Minutes peer chaincode invoke -l golang -n mycc -c '{"Function": "OpenAuctionForBids", "Args":["1111", "OPENAUC", "3"]}' **PostBid**: This function allows a potential buyer to bid on the Asset once the auction is open. Every bid is checked for valid auction ID, asset ID and buyer ID. Bids must be higher than reserve price. Bids are accepted as long as the auction is "OPEN". Bid numbers must be unique and generated by the client. **Usage (CLI mode)** This call takes 6 arguments: Auction ID, Record Type, Bid Number, Item ID, Buyer ID, Buyer Offer Price peer chaincode invoke -l golang -n mycc -c '{"Function": "PostBid", "Args":["1111", "BID", "5", "1000", "400", "5000"]}' **PostTransaction**: Even though the Transaction is automatically generated and posted when one of the following events occur: * Auction closes after the auction timer expires (Only CLI Mode) * A message to close all expired auctions are close is issued by the client via "CloseOpenAuctions" * A "BuyItNow" call is made which goes through this function is exposed as a matter of convenience just in case transactions don't get posted properly. When a Transaction is posted, it updates the Asset with the new buyer ID, re-generates a new key and encrypts the image/certificate and updates the asset price to the "Hammer Price". It also adds a new entry into the item history table. **Usage (CLI mode)** This call takes 8 arguments: Auction ID, Record Type, Item ID, Transaction Type, Buyer ID, Transaction Time, Hammer Time, Hammer Price peer chaincode invoke -l golang -n mycc -c '{"Function": "PostBid", "Args":["1111", "POSTTRAN", "1000", "SALE","500", "2016-05-24 11:00:00","2016-05-23 14:25:00", "12000"]}' **BuyItNow**: Sometimes, an asset owner can specify a "Buy It Now" price in addition to the "Reserve Price" while posting an auction request. If an asset has a "Buy It Now" price, a buyer can issue a "BuyItNow" call. If there are no bids higher than the "BuyItNow" price, the request will be rejected. If the request goes through, the auction is closed and a transaction is posted against the buyer. **Usage (CLI mode)** This call is similar to the PostBid, except the price is set to the Buy-It-Now price. peer chaincode invoke -l golang -n mycc -c '{"Function": "BuyItNow", "Args":["1111", "BID", "1", "1000", "300", "1800"]}' **TransferItem**: At any time, the current owner of an asset can transfer the item at no additional cost or change in value to another user, provided the asset is not initiated for auctions, or is in the process of an auction. Smart contract rules may have to be written to comply with local regulations and taxes. The current owner has to prove ownership by providing his key. The owner id, item id and the key are all validated before transfer can take place. The new owner will receive a new key. **Usage (CLI mode)** This call takes 5 arguments: Item ID, Current Owner ID, Owner Key, Transferee ID, Record Type peer chaincode invoke -l golang -n mycc -c '{"Function": "TransferItem", "Args": ["1000", "100", "218MC/ipIsIrDhE9TKXqG2NsWl7KSE59Y3UmwBzSrQo=", "300", "XFER"]}' **CloseAuction**: This function call closes an auction once the time expires. This call is automatically issued in "CLI" mode since the "OpenAuctionForBids" call triggers a go routine that sleeps for the duration of the auction and the automatically issues a "CloseAuction" call. This functions closes the auction request, picks the highest bid and creates a transaction an then posts the transaction. **Usage (CLI mode)** This call takes two arguments: Auction ID, Record Type peer chaincode invoke -l golang -n mycc -c '{"Function": "CloseAuction", "Args": ["1111","AUCREQ"]}' **CloseOpenAuctions**: This is a function designed specifically for Bluemix situations where the peer and the chaincode run in different containers, hence a call as shown in each of "Usage (CLI mode)" will not work. This function is issued periodically by the client (UI or applications consuming the blockchain) as a REST call. The functions checks for auctions whose time has run-out and closes them. **Usage (CLI mode)** This call takes two arguments: 2016, Record Type peer chaincode invoke -l golang -n mycc -c '{"Function": "CloseOpenAuctions", "Args": ["2016", "CLAUC"]}' ###Query APIs and Usage **GetItem**: Retrieves an Asset record by asset ID. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetItem", "Args": ["1000"]}' **GetUser**: Retrieves an user record by user or stakeholder ID. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetUser", "Args": ["100"]}' **GetAuctionRequest**: Retrieves an auction request by auction request ID. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetAuctionRequest", "Args": ["1111"]}' **GetTransaction**: Retrieves an transaction posted against an auction by auction request ID and asset ID. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetTransaction", "Args": ["1111", "1000"]}' **GetBid**: Retrieves a single bid by auction ID and bid number **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetBid", "Args": ["1111", "5"]}' **GetLastBid**: Retrieves the last submitted bid. Since bids are submitted in random , and the only requirement is that the bid price be higher than the reserve price, the last received bid need not be the highest bid. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetLastBid","Args": ["1111"]}' **GetHighestBid**: Retrieves the highest bid submitted against the auction thus far. If the auction has expired, then the highest bid is the highest bid for the auction. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetHighestBid", "Args": ["1111"]}' **GetNoOfBidsReceived**: Retrieves the total number of bids received at any point in time. If the auction has expired, it represents the total number of bids received against that auction. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetNoOfBidsReceived", "Args": ["1111"]}' **GetListOfBids**: Retrieves all the bids received against an auction. each row in the list represents a bid object. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetListOfBids", "Args": ["1111"]}' **GetItemLog**: Retrieves the history of an asset. The log is updated when an asset is registered, put on auction, post auction, transfered etc. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetItemLog","Args": ["1000"]}' **GetItemListByCat**: Retrieves a list of assets by asset category. If only the first key is provided, the query retrieves all assets. "2016" is hard-coded as a fixed first key. This is a band-aid solution to retrieve all records. This is a band-aid solution to retrieve all records. The following query retrieves all assets of category "modern". **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetItemListByCat","Args": ["2016", "modern"]}' **GetUserListByCat**: Retrieves a list of stakeholders or account holders by stakeholder type. If only the first key is provided, the query retrieves all assets. "2016" is hard-coded as a fixed first key. This is a band-aid solution to retrieve all records. The following query retrieves all stakeholders of type "AH" or auction houses. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetItemListByCat","Args": ["2016", "AH"]}' **GetListOfInitAucs**: This query retrieves all assets which have been submitted for auction. Their status is "Init". The "2016" is a fixed key to denote all auctions in 2016. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetListOfInitAucs","Args": ["2016"]}' **GetListOfOpenAucs**: This query retrieves a list of all assets whose auctions have been "OPEN"ed for receiving bids. The "2016" is a fixed key to denote all auctions in 2016. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "GetListOfOpenAucs", "Args": ["2016"]}' **ValidateItemOwnership**: Validates the ownership of an asset. Checks for valid account id, asset id and retrieves asset from blockchain using the owners's key. The arguments are Item ID, Current Owner ID and Owner's Key. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "ValidateItemOwnership", "Args": ["1000", "500", "avQX6JfTnELAY4mkRhOr8P7vmz0H3aAIuFGsGiSD5UQ="]}' **IsItemOnAuction**: Checks whether an Asset has an **auction request** posted, or currently on auction and returns a boolean true or false. **Usage (CLI mode)** peer chaincode query -l golang -n mycc -c '{"Function": "IsItemOnAuction", "Args": ["1999", "VERIFY"]}' ##Notes * Once an auction request is posted or an auction is open for bids, there is no api to remove the auction request or close the auction prematurely and rejecting all bids received * In the current version, the image is encrypted and stored in the blockchain. However, in future, it is envisioned that only the hash of the image along with the URI to the location of the image will be saved ##Runnning the Application using the Web Browser The chaincode functions can be accessed via the browser, Please refer [auction-app](https://github.com/ITPeople-Blockchain/auction-app) for more details. ================================================ FILE: mkdocs.yml ================================================ site_name: ITPeople Blockchain Auction site_url: http://itpeople-blockchain-auction.readthedocs.org theme: readthedocs repo_url: https://github.com/ITPeople-Blockchain/auction site_description: 'Welcome to the ITPeople Blockchain Art Auction Application documentation' markdown_extensions: # Convert ASCII dashes, quotes to HTML entity - smarty pages: - HOME: index.md