mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-17 07:25:10 +00:00
Commercial Paper 2.x sample - enhancements and documentation (#335)
* commercial paper enhancements Signed-off-by: Paul O'M <mahoney@uk.ibm.com> * commercial paper enhancements Signed-off-by: Paul O'M <mahoney@uk.ibm.com> * commercial paper enhancements Signed-off-by: Paul O'M <mahoney@uk.ibm.com> * Add further README changes from #335 Signed-off-by: Paul O'M <mahoney@uk.ibm.com> * Add further README changes from #335 Signed-off-by: Paul O'M <mahoney@uk.ibm.com> * Add further README changes from #335 Signed-off-by: Paul O'M <mahoney@uk.ibm.com>
This commit is contained in:
parent
75f491f2e4
commit
d2e2a8b683
18 changed files with 1754 additions and 92 deletions
|
|
@ -1,27 +1,110 @@
|
|||
# Commercial Paper Tutorial
|
||||
<a name="top"></a>
|
||||
|
||||
This folder contains the code for an introductory tutorial to Smart Contract development. It is based around the scenario of Commercial Paper.
|
||||
The full tutorial, including full scenario details and line by line code walk-through is in the [Hyperledger Fabric Commercial Paper Tutorial](https://hyperledger-fabric.readthedocs.io/en/latest/tutorial/commercial_paper.html).
|
||||
# Commercial Paper Tutorial & Samples
|
||||
|
||||
## Scenario
|
||||
## Introduction
|
||||
|
||||
In this tutorial two organizations, MagnetoCorp and DigiBank, trade commercial paper with each other using 'PaperNet', a Hyperledger Fabric blockchain network.
|
||||
This folder contains a structured set of smart contracts and application clients (ie. in a choice of languages, eg Node.js, Java, Go etc) relating to *Commercial Paper*, a finance 'instrument' (in Global Finance). At present, the Node.js sample contract in particular has further added functionality: an optional two-step authority check (see diagram below), when redeeming a commercial paper instance - and a range of sample ledger queries, to help consolidate your learning; both can be explored using the Node.js application client.
|
||||
|
||||
Once you’ve set up a basic network, you’ll act as Isabella, an employee of MagnetoCorp, who will issue a commercial paper on its behalf. You’ll then switch hats to take the role of Balaji, an employee of DigiBank, who will buy this commercial paper, hold it for a period of time, and then redeem it with MagnetoCorp for a small profit.
|
||||
While a more detailed 'explainer' of the Commercial Paper scenario (including use case analysis, code walkthrough & practices, logical/physical representation of ledger data etc) can be found in the [Hyperledger Fabric Commercial Paper Tutorial](https://hyperledger-fabric.readthedocs.io/en/latest/tutorial/commercial_paper.html), you don't have to read through this, just to try out this sample. There's also a [Wikipedia page](https://en.wikipedia.org/wiki/Commercial_paper)
|
||||
|
||||
<details><summary>Key Objectives </summary>
|
||||
|
||||
* see a Commercial Paper use case in action
|
||||
|
||||
* explore the key 'takeaways': understand differences between asset _state_ changes ('e.g. 'lifecycle') and transaction _inputs_* (e.g. 'inputs' during lifecycle)
|
||||
|
||||
* try out a number of different query types: asset history, asset state, ownership, partial key, named query (criteria-based), ad-hoc queries (you supply a query string) - presently available in the Node.js sample only.
|
||||
|
||||
\* the smart contract uses these (along with business logic) to decide outcomes; some inputs change the asset _state_ (like 'ownership') ; some don't.
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<details><summary>Blockchain: benefits to Commercial Paper marketplaces?</summary>
|
||||
|
||||
* replace long-winded, time consuming processing between multiple organisations - the network makes it one centralized hub and helps simplify workflow.
|
||||
|
||||
* full transparency, traceability and ownership of issued papers
|
||||
|
||||
* speed up a process that can take days - e. make same-day issuance a reality, or even a market paradigm.
|
||||
|
||||
* in asset-backed commercial paper markets, blockchain can help increase accessibility (to a marketplace) to SMEs to partake in issuance, where otherwise it was inaccessible.
|
||||
|
||||
* integration to other areas, like supply chain finance
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
|
||||
Expand the twisty below to see an overview diagram of a 'sample' Commercial paper marketplace - transactions, queries being executed by different organisations (we'll focus on two of these organisations)
|
||||
|
||||
<details><summary>PaperNet Overview diagram - The sample commercial paper marketplace</summary>
|
||||
|
||||

|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
But first, it might useful to explain Commercial Paper, an unsecured promissory note issued to obtain capital, and operates in global financial markets. A Commercial Paper instance is represented as an asset with a lifecycle, recorded on the blockchain - transactions change its _state_ (over time) and those transactions, naturally - have _inputs_.
|
||||
|
||||
|
||||
#### Explainers
|
||||
|
||||
<details><summary>Commercial Paper - what is it briefly?</summary>
|
||||
|
||||
</br>
|
||||
It is a type of unsecured promissory note, issued by established companies (eg big manufacturers, blue chip corporations) to gain short-term capital - usually no more than 6-9 months. Why? To meet short-term financial obligations. Commercial paper is generally purchased by money market funds and banks - in fact, it becomes a more important investment strategy during financial recessions :-) . A corporation issues a paper (in the form of a promissory note) for specific projects, such as big capital investments, to pay contractors or even to exercise debt restructuring. The tutorial describes MagnetoCorp (car manufacturer) who have landed a huge contract, and will need approx. $5m in capital (payroll obligations), to hire 1000 car workers for at least 6 months (with no car revenues yet - its a financial strain). Underpinning this, of course, is that MagnetoCorp, has every confidence that (say, in 6 months time) it will be in a position to pay out the face value ($5m in this case) when the commercial paper is redeemd by an owner of the paper, upon maturity :-).
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<details><summary>Ins and Outs, Attractions of Commercial Paper Investment?</summary>
|
||||
|
||||
</br>
|
||||
Investors (who buy Commercial Paper) are attracted as they agree to buy them at a discount (say $4.94m) on the face value (eg $5m) and moreso, they obtain a higher yield than if they were simply gaining interest in a bank (eg. 2% interest on $4.95m today = $5m in 6 months time). But there is a 'premium' attached, with carrying the risk of a debt/loan that is essentially unsecured (unlike a bank) - which is where credit risk and ratings comes in. As a result, the actual yield from the investment chosen is in effect $10k greater (than pure interest, in the example given).
|
||||
</p>
|
||||
Once an issuing corporation becomes established in the commercial paper marketplace, it builds a high credit rating (risk affects how much of a premium investors seek and therefore discount accordingly) - in fact, it is often cheaper (for a blue chip company) to draw on a commercial paper than on a bank line of credit. But that rating reflects the issuer's ability to pay back on maturity.
|
||||
</p>
|
||||
I mentioned marketplace: even during the typical 6-9 month period, a commercial paper can be bought and sold multiple times (its quoted, at the discounted price on money markets), before the Commercial Paper reaches its maturity date. On that date, the current investor (or owner) 'redeems' the paper bond with MagnetoCorp, the issuer and gets the face value of $5m.
|
||||
|
||||
</details>
|
||||
|
||||
<sup>[_back to top_](#top)</sup>
|
||||
|
||||
## Scenario Overview
|
||||
|
||||

|
||||
|
||||
In this tutorial two organizations, MagnetoCorp and DigiBank, trade commercial paper with each other on 'PaperNet', the marketplace represented by a Hyperledger Fabric blockchain network. Note that there are two alternative transaction flows - one which mirrors the [Commercial Paper Tutorial](https://hyperledger-fabric.readthedocs.io/en/latest/tutorial/commercial_paper.html) as described in Fabric documentation, and one which requires the authorised owner of the paper to complete a transfer following a request to buy the commercial paper - the latter example features an authorization check in the smart contract that ensures the transactor is from the organization that currently owns the commercial paper - they approve and complete the buy request. These are the commercial paper transaction lifecycles you can try out:
|
||||
|
||||

|
||||
|
||||
The tutorial exercises the commercial paper asset lifecycle: _issue_, _buy_ ( 1 to _n_ ) (or _buy_request_ / _transfer_ alternative), and _redeem_ transactions: the key 'takeaways' from the scenario are:
|
||||
|
||||
- understanding the _changes in state_ in the commercial paper asset (reflected in the ledger world state) which reaches maturity after 6 months.
|
||||
- understanding the _transaction inputs_ for each transaction (some inputs change the asset _state_ - eg. ownership) and some _don't_ (e.g. purchase price) and not part of the asset - but importantly, the _inputs_ for a given transaction are recorded on the blockchain).
|
||||
- understanding the importance of _queries_ such as: asset history, rich queries (criteria matching etc), transaction history (where the inputs are recorded)
|
||||
|
||||
Client applications (CLI based) are used:
|
||||
|
||||
- to perform the transactions
|
||||
- run queries (Node.js sample only)
|
||||
- examine the transaction inputs (as opposed to _states_) that are written to the ledger after you perform a transaction (using the Node.js listener).
|
||||
|
||||
This sample uses the `test-network` . You’ll act as Isabella, an employee of MagnetoCorp (Org2), who will issue a commercial paper on its behalf. You’ll then 'switch hats' to take the role of Balaji, an employee of DigiBank (Org1), who will buy this commercial paper, hold it for a period of time, and then redeem it with MagnetoCorp for a small profit or yield. Note that the smart contract sample doesn't enforce the actual hold period ; the user can, in fact, redeem the paper immediately.
|
||||
|
||||
|
||||
## Quick Start
|
||||
|
||||
You are strongly advised to read the full tutorial to get information about the code and the scenario. Below are the quick start instructions for running the tutorial, but without extensive details of what is happening.
|
||||
Below are the quick start instructions for running the tutorial. As mentioned, if you're interested in a 'deeper dive' analysis and importance of the concepts, design, structure and implementation of the smart contract, they can be found in the [Hyperledger Fabric Commercial Paper Tutorial](https://hyperledger-fabric.readthedocs.io/en/latest/tutorial/commercial_paper.html). Suffice to say, you DON'T have to have read this, to do this tutorial.
|
||||
|
||||
This `README.md` file is in the `commercial-paper` directory, the source code for client applications and the contracts is in the `organization` directory.
|
||||
|
||||
### Steps
|
||||
### High-Level Overview of Steps
|
||||
|
||||
1) Start the Hyperledger Fabric infrastructure
|
||||
1) Install Binaries, Start the Hyperledger Fabric infrastructure
|
||||
|
||||
The 'test-network' will be used - this has two organizations 'org1' and 'org2' DigiBank will be org1, and MagnetoCorp will be org2.
|
||||
The Fabric 'test-network' will be used - this has two organizations 'Org1' and 'Org2' DigiBank will be Org1, and MagnetoCorp will be Org2.
|
||||
|
||||
2) Install and Instantiate the Contracts
|
||||
|
||||
|
|
@ -31,6 +114,10 @@ This `README.md` file is in the `commercial-paper` directory, the source code fo
|
|||
- Buy the paper as DigiBank (org1)
|
||||
- Redeem the paper as DigiBank (org1)
|
||||
|
||||
See also the transaction flow and alternatives in the Scenario Overview below.
|
||||
|
||||
<sup>[_back to top_](#top)</sup>
|
||||
|
||||
## Setup
|
||||
|
||||
You will need a machine with the following
|
||||
|
|
@ -40,10 +127,26 @@ You will need a machine with the following
|
|||
- Java v8 if you want to run Java client applications
|
||||
- Maven to build the Java applications
|
||||
|
||||
You will need to install the peer cli binaries and this fabric-samples repository available. For more information
|
||||
[Install the Samples, Binaries and Docker Images](https://hyperledger-fabric.readthedocs.io/en/latest/install.html) in the Hyperledger Fabric documentation.
|
||||
You will need to install the `peer` cli binaries and cloned the `fabric-samples` repository. For more information see
|
||||
[Install the Samples, Binaries and Docker Images](https://hyperledger-fabric.readthedocs.io/en/latest/install.html) in the Hyperledger Fabric documentation. Once you have installed the cli binaries, ensure you have added the `bin` directory (for your `peer` commands used by scripts below) to your exported `PATH` variable in your `.bashrc` or `.profile` directory (per below). This is important as you will be opening a number of windows which will need PATH set. Finally, check that it finds the `peer` command in your PATH using the `which` command eg.
|
||||
|
||||
It is advised to have 3 console windows open; one to monitor the infrastructure and one each for MagnetoCorp and DigiBank. Once you've cloned the fabric-samples - change to the commercial-paper directory in each window.
|
||||
```
|
||||
export PATH=<path_to_bin_directory>:$PATH
|
||||
which peer
|
||||
```
|
||||
|
||||
|
||||
It is advised to have 3 terminal windows (consoles) open;
|
||||
|
||||
* one to monitor the infrastructure
|
||||
* one for MagnetoCorp
|
||||
* one for DigiBank.
|
||||
|
||||
Once you've cloned the `fabric-samples` - change to the `commercial-paper` directory in each window.
|
||||
|
||||
```
|
||||
git clone https://github.com/hyperledger/fabric-samples.git
|
||||
```
|
||||
|
||||
```
|
||||
cd fabric-samples/commercial-paper
|
||||
|
|
@ -51,9 +154,13 @@ cd fabric-samples/commercial-paper
|
|||
|
||||
## Running the Infrastructure
|
||||
|
||||
In one console window, run the `./network-starter.sh` script; this will start the basic infrastructure.
|
||||
In one console window, run the network starter script - this will start the two organization `test-network` blockchain network.
|
||||
|
||||
You can re-use this console window if you wish, but it is recommended to run a docker container monitoring script. This will let you view what Fabric is doing and help diagnose any failures.
|
||||
```
|
||||
./network-starter.sh
|
||||
```
|
||||
|
||||
You can re-use this console window if you wish, but it is recommended to run a docker container monitoring script in its own window. This will let you view what Fabric is doing and help diagnose any failures.
|
||||
|
||||
```bash
|
||||
./organization/magnetocorp/configuration/cli/monitordocker.sh net_test
|
||||
|
|
@ -63,11 +170,11 @@ You can re-use this console window if you wish, but it is recommended to run a d
|
|||
|
||||
The contract code is available as either JavaScript, Java or Go. You can use either one, and the choice of contract language does not affect the choice of client language. With the v2.0 Fabric chaincode lifecycle, this requires operations for both MagnetoCorp and Digibank admin. Open two windows in the fabric-samples/commercial paper directory, one for each organization.
|
||||
|
||||
In your 'MagnetoCorp' window run the following commands, to show the shell environment variables needed to act as that organization.
|
||||
In your 'MagnetoCorp' window run the following commands, to set the shell environment variables needed to act as that organization. The leading '.' in the command sequence sets in your current environment - if you do not run this, you will not be able to package the chaincode.
|
||||
|
||||
```
|
||||
cd fabric-samples/commercial-paper/organization/magnetocorp
|
||||
./magnetocorp.sh
|
||||
. ./magnetocorp.sh
|
||||
```
|
||||
|
||||
You can either copy and paste them directly into the terminal, or invoke directly in your own command shell. For example if you are using bash or zsh on Linux you can use this command.
|
||||
|
|
@ -76,21 +183,27 @@ You can either copy and paste them directly into the terminal, or invoke directl
|
|||
source <(./magnetocorp.sh)
|
||||
```
|
||||
|
||||
Similarly in your 'DigiBank' window run the following command
|
||||
Similarly in your 'DigiBank' window run the following commands as shown:
|
||||
|
||||
```
|
||||
cd fabric-samples/commercial-paper/organization/digibank
|
||||
./digibank.sh
|
||||
. ./digibank.sh
|
||||
```
|
||||
|
||||
<sup>[_back to top_](#top)</sup>
|
||||
|
||||
|
||||
### Deploy the smart contract to the channel
|
||||
|
||||
You need to perform similar operations for both organizations. For different contract languages the steps are very similar. The steps for JavaScript are shown first, with the details of different languages afterwards.
|
||||
You need to perform similar operations for _both_ organizations and for your language choice from the instructions below. For the different contract languages, the steps are very similar - the full set of steps are actually shown in the JavaScript section (see twisty). However, you will perform one or two different initial steps for Java or Go before completing the remaining common steps as instructed in those language sections.
|
||||
|
||||
Note that the commands below make use of the `jq` utility for parsing output - download and install it from [here](https://stedolan.github.io/jq/download/)
|
||||
|
||||
|
||||
**For a JavaScript Contract**
|
||||
**<details><summary>For a JavaScript Contract</summary>**
|
||||
|
||||
Running in MagnetoCorp:
|
||||
|
||||
Running in MagnetoCorp directory:
|
||||
|
||||
```
|
||||
# MAGNETOCORP
|
||||
|
|
@ -99,7 +212,10 @@ peer lifecycle chaincode package cp.tar.gz --lang node --path ./contract --label
|
|||
peer lifecycle chaincode install cp.tar.gz
|
||||
|
||||
export PACKAGE_ID=$(peer lifecycle chaincode queryinstalled --output json | jq -r '.installed_chaincodes[0].package_id')
|
||||
echo $PACKAGE_ID
|
||||
echo $PACKAGE_ID # FYI may look like this: 'cp_0:nnnxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
peer lifecycle chaincode approveformyorg --orderer localhost:7050 --ordererTLSHostnameOverride orderer.example.com \
|
||||
--channelID mychannel \
|
||||
|
|
@ -113,7 +229,7 @@ peer lifecycle chaincode approveformyorg --orderer localhost:7050 --ordererTLSH
|
|||
peer lifecycle chaincode checkcommitreadiness --channelID mychannel --name papercontract -v 0 --sequence 1
|
||||
```
|
||||
|
||||
Running in Digibank
|
||||
Running in Digibank directory:
|
||||
|
||||
```
|
||||
|
||||
|
|
@ -153,7 +269,7 @@ peer lifecycle chaincode commit -o localhost:7050 \
|
|||
|
||||
```
|
||||
|
||||
To test try sending some simple transactions.
|
||||
To test, try sending some simple transactions.
|
||||
|
||||
```
|
||||
|
||||
|
|
@ -169,50 +285,227 @@ peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.exa
|
|||
--name papercontract \
|
||||
-c '{"Args":["org.hyperledger.fabric:GetMetadata"]}' \
|
||||
--peerAddresses localhost:9051 --tlsRootCertFiles ${PEER0_ORG2_CA} \
|
||||
--tls --cafile $ORDERER_CA | jq -C | more
|
||||
--tls --cafile $ORDERER_CA | jq '.' -C | more
|
||||
```
|
||||
</p>
|
||||
</details>
|
||||
|
||||
**For a Java Contract:**
|
||||
|
||||
Before the `peer lifecycle chaincode package` command, you will need to change into each organization's `contract-java` directory and issue
|
||||
**<details><summary>For a Java Contract:</summary>**
|
||||
|
||||
|
||||
Before the `peer lifecycle chaincode package` command below, you will first need to change into each organization's `contract-java` directory and issue
|
||||
|
||||
```
|
||||
./gradlew build
|
||||
```
|
||||
|
||||
Then from the parent directory when you package the contract, use this variation of the command to specify the java specific contract
|
||||
Then complete the steps below.
|
||||
|
||||
|
||||
Running in MagnetoCorp contract directory:
|
||||
|
||||
```
|
||||
# MAGNETOCORP
|
||||
|
||||
peer lifecycle chaincode package cp.tar.gz --lang java --path ./contract-java --label cp_0
|
||||
peer lifecycle chaincode install cp.tar.gz
|
||||
|
||||
export PACKAGE_ID=$(peer lifecycle chaincode queryinstalled --output json | jq -r '.installed_chaincodes[0].package_id')
|
||||
echo $PACKAGE_ID # FYI may look like this: 'cp_0:nnnxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
||||
```
|
||||
|
||||
After this point the steps are exactly the same as for JavaScript
|
||||
```
|
||||
|
||||
**For a Go Contract:**
|
||||
peer lifecycle chaincode approveformyorg --orderer localhost:7050 --ordererTLSHostnameOverride orderer.example.com \
|
||||
--channelID mychannel \
|
||||
--name papercontract \
|
||||
-v 0 \
|
||||
--package-id $PACKAGE_ID \
|
||||
--sequence 1 \
|
||||
--tls \
|
||||
--cafile $ORDERER_CA
|
||||
|
||||
peer lifecycle chaincode checkcommitreadiness --channelID mychannel --name papercontract -v 0 --sequence 1
|
||||
```
|
||||
|
||||
Running in Digibank
|
||||
|
||||
```
|
||||
|
||||
# DIGIBANK
|
||||
|
||||
peer lifecycle chaincode package cp.tar.gz --lang java --path ./contract-java --label cp_0
|
||||
peer lifecycle chaincode install cp.tar.gz
|
||||
|
||||
export PACKAGE_ID=$(peer lifecycle chaincode queryinstalled --output json | jq -r '.installed_chaincodes[0].package_id')
|
||||
echo $PACKAGE_ID
|
||||
|
||||
peer lifecycle chaincode approveformyorg --orderer localhost:7050 --ordererTLSHostnameOverride orderer.example.com \
|
||||
--channelID mychannel \
|
||||
--name papercontract \
|
||||
-v 0 \
|
||||
--package-id $PACKAGE_ID \
|
||||
--sequence 1 \
|
||||
--tls \
|
||||
--cafile $ORDERER_CA
|
||||
|
||||
peer lifecycle chaincode checkcommitreadiness --channelID mychannel --name papercontract -v 0 --sequence 1
|
||||
|
||||
```
|
||||
|
||||
Once both organizations have installed, and approved the chaincode, it can be committed.
|
||||
|
||||
```
|
||||
# MAGNETOCORP
|
||||
|
||||
peer lifecycle chaincode commit -o localhost:7050 \
|
||||
--peerAddresses localhost:7051 --tlsRootCertFiles ${PEER0_ORG1_CA} \
|
||||
--peerAddresses localhost:9051 --tlsRootCertFiles ${PEER0_ORG2_CA} \
|
||||
--ordererTLSHostnameOverride orderer.example.com \
|
||||
--channelID mychannel --name papercontract -v 0 \
|
||||
--sequence 1 \
|
||||
--tls --cafile $ORDERER_CA --waitForEvent
|
||||
|
||||
```
|
||||
|
||||
To test, try sending some simple transactions.
|
||||
|
||||
```
|
||||
|
||||
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com \
|
||||
--peerAddresses localhost:7051 --tlsRootCertFiles ${PEER0_ORG1_CA} \
|
||||
--peerAddresses localhost:9051 --tlsRootCertFiles ${PEER0_ORG2_CA} \
|
||||
--channelID mychannel --name papercontract \
|
||||
-c '{"Args":["org.papernet.commercialpaper:instantiate"]}' ${PEER_ADDRESS_ORG1} ${PEER_ADDRESS_ORG2} \
|
||||
--tls --cafile $ORDERER_CA --waitForEvent
|
||||
|
||||
peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com \
|
||||
--channelID mychannel \
|
||||
--name papercontract \
|
||||
-c '{"Args":["org.hyperledger.fabric:GetMetadata"]}' \
|
||||
--peerAddresses localhost:9051 --tlsRootCertFiles ${PEER0_ORG2_CA} \
|
||||
--tls --cafile $ORDERER_CA | jq '.' -C | more
|
||||
```
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
**<details><summary>For a Go Contract</summary>**
|
||||
|
||||
|
||||
Before the `peer lifecycle chaincode package` command, you will need to change into each organization's `contract-go` directory and issue
|
||||
Before the `peer lifecycle chaincode package` command step, you will need to change into <ins>each</ins> organization's `contract-go` directory and issue
|
||||
|
||||
```
|
||||
go mod vendor
|
||||
```
|
||||
|
||||
Then from the parent directory when you package the contract, use this variation of the command to specify the go specific contract
|
||||
Then complete the steps below.
|
||||
|
||||
|
||||
Running in MagnetoCorp contract directory:
|
||||
|
||||
```
|
||||
# MAGNETOCORP
|
||||
|
||||
peer lifecycle chaincode package cp.tar.gz --lang golang --path ./contract-go --label cp_0
|
||||
peer lifecycle chaincode install cp.tar.gz
|
||||
|
||||
export PACKAGE_ID=$(peer lifecycle chaincode queryinstalled --output json | jq -r '.installed_chaincodes[0].package_id')
|
||||
echo $PACKAGE_ID # FYI may look like this: 'cp_0:nnnxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
||||
```
|
||||
|
||||
After this point the steps are exactly the same as for JavaScript
|
||||
```
|
||||
|
||||
peer lifecycle chaincode approveformyorg --orderer localhost:7050 --ordererTLSHostnameOverride orderer.example.com \
|
||||
--channelID mychannel \
|
||||
--name papercontract \
|
||||
-v 0 \
|
||||
--package-id $PACKAGE_ID \
|
||||
--sequence 1 \
|
||||
--tls \
|
||||
--cafile $ORDERER_CA
|
||||
|
||||
peer lifecycle chaincode checkcommitreadiness --channelID mychannel --name papercontract -v 0 --sequence 1
|
||||
```
|
||||
|
||||
Running in Digibank
|
||||
|
||||
```
|
||||
|
||||
# DIGIBANK
|
||||
|
||||
peer lifecycle chaincode package cp.tar.gz --lang golang --path ./contract-go --label cp_0
|
||||
peer lifecycle chaincode install cp.tar.gz
|
||||
|
||||
export PACKAGE_ID=$(peer lifecycle chaincode queryinstalled --output json | jq -r '.installed_chaincodes[0].package_id')
|
||||
echo $PACKAGE_ID
|
||||
|
||||
peer lifecycle chaincode approveformyorg --orderer localhost:7050 --ordererTLSHostnameOverride orderer.example.com \
|
||||
--channelID mychannel \
|
||||
--name papercontract \
|
||||
-v 0 \
|
||||
--package-id $PACKAGE_ID \
|
||||
--sequence 1 \
|
||||
--tls \
|
||||
--cafile $ORDERER_CA
|
||||
|
||||
peer lifecycle chaincode checkcommitreadiness --channelID mychannel --name papercontract -v 0 --sequence 1
|
||||
|
||||
```
|
||||
|
||||
Once both organizations have installed, and approved the chaincode, it can be committed.
|
||||
|
||||
```
|
||||
# MAGNETOCORP
|
||||
|
||||
peer lifecycle chaincode commit -o localhost:7050 \
|
||||
--peerAddresses localhost:7051 --tlsRootCertFiles ${PEER0_ORG1_CA} \
|
||||
--peerAddresses localhost:9051 --tlsRootCertFiles ${PEER0_ORG2_CA} \
|
||||
--ordererTLSHostnameOverride orderer.example.com \
|
||||
--channelID mychannel --name papercontract -v 0 \
|
||||
--sequence 1 \
|
||||
--tls --cafile $ORDERER_CA --waitForEvent
|
||||
|
||||
```
|
||||
|
||||
To test, try sending some simple transactions.
|
||||
|
||||
```
|
||||
|
||||
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com \
|
||||
--peerAddresses localhost:7051 --tlsRootCertFiles ${PEER0_ORG1_CA} \
|
||||
--peerAddresses localhost:9051 --tlsRootCertFiles ${PEER0_ORG2_CA} \
|
||||
--channelID mychannel --name papercontract \
|
||||
-c '{"Args":["org.papernet.commercialpaper:instantiate"]}' ${PEER_ADDRESS_ORG1} ${PEER_ADDRESS_ORG2} \
|
||||
--tls --cafile $ORDERER_CA --waitForEvent
|
||||
|
||||
peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com \
|
||||
--channelID mychannel \
|
||||
--name papercontract \
|
||||
-c '{"Args":["org.hyperledger.fabric:GetMetadata"]}' \
|
||||
--peerAddresses localhost:9051 --tlsRootCertFiles ${PEER0_ORG2_CA} \
|
||||
--tls --cafile $ORDERER_CA | jq '.' -C | more
|
||||
```
|
||||
|
||||
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
|
||||
<sup>[_back to top_](#top)</sup>
|
||||
|
||||
|
||||
## Client Applications
|
||||
|
||||
Note for Java applications you will need to compile the Java Code using maven. Use this command in each application-java directory
|
||||
Note for Java applications, you will need to compile the Java Code using `maven`. Use this command in each application-java directory
|
||||
|
||||
```
|
||||
mvn clean package
|
||||
```
|
||||
|
||||
Note for JavaScript applications you will need to install the dependencies first. Use this command in each application directory
|
||||
Note for JavaScript applications, you will need to install the dependencies first. Use this command in each application directory
|
||||
|
||||
```
|
||||
npm install
|
||||
|
|
@ -221,12 +514,22 @@ npm install
|
|||
|
||||
The docker containers don't contain the node or Java runtimes; so it is best to exit the docker containers - but keep the windows open and run the applications locally.
|
||||
|
||||
### Issue the paper
|
||||
As mentioned earlier in the Sample introduction section, transaction _inputs_ are recorded on the ledger, as well as any asset _state_ changes. Just *before* you run the _issue_ application script below - you need to launch a block 'listener' application that will show you these _inputs_, as you complete each transaction in the commercial paper lifecycle (eg. Paper Number: 00001, 00002 etc) .
|
||||
|
||||
This is running as *MagnetoCorp* These commands are to be run in the
|
||||
`commercial-paper/organization/magnetocorp/application` directory or the `commercial-paper/organization/magnetocorp/application-java`
|
||||
For the listener, its best to open a *new* terminal for this in the `commercial-paper/organization/magnetocorp/application` directory (javascript). Next, run the `addToWallet` step in the `issue` transaction below, to add Isabella's identity to the wallet - the listener will use this wallet. Once the listener is launched, it will show the inputs for transactions you will perform and which are committed to blocks (ie part of the ledger). Note: initially, the listener may show a spurious message, and then go into a _listening_ or 'wait' state. As transactions complete below, messages will be displayed by the listener - so keep an eye out. *After* adding Isabella's wallet, you can then launch the listener as follows:
|
||||
|
||||
*Add the Identity to be used*
|
||||
```
|
||||
node cpListener.js
|
||||
```
|
||||
|
||||
**<details><summary>Issue the commercial paper</summary>**
|
||||
|
||||
The paper is issued by *MagnetoCorp*
|
||||
|
||||
You can now run the applications to issue the commercial paper. Change to either the
|
||||
`commercial-paper/organization/magnetocorp/application` directory (javascript) or `commercial-paper/organization/magnetocorp/application-java` directory (java)
|
||||
|
||||
*Add the Identity to be used to the wallet*
|
||||
|
||||
```
|
||||
node addToWallet.js
|
||||
|
|
@ -242,11 +545,17 @@ node issue.js
|
|||
java -cp target/commercial-paper-0.0.1-SNAPSHOT.jar org.magnetocorp.Issue
|
||||
```
|
||||
|
||||
### Buy and Redeem the paper
|
||||
Don't forget to check the application listener for messages above!
|
||||
|
||||
This is running as *Digibank*;
|
||||
</p>
|
||||
</details>
|
||||
|
||||
You can now run the applications to buy and redeem the paper. Change to either the
|
||||
|
||||
**<details><summary>Buy the commercial paper</summary>**
|
||||
|
||||
_Buy_ is performed as *Digibank*;
|
||||
|
||||
You can now run the applications to buy the paper. Change to either the
|
||||
`commercial-paper/organization/digibank/application` directory or `commercial-paper/organization/digibank/application-java`
|
||||
|
||||
*Add the Identity to be used*
|
||||
|
|
@ -265,6 +574,33 @@ node buy.js
|
|||
java -cp target/commercial-paper-0.0.1-SNAPSHOT.jar org.digibank.Buy
|
||||
```
|
||||
|
||||
If you have just executed a `buy` transaction above - jump to the `redeem` transaction below - otherwise execute the _buy_/_transfer_ sequence as described earlier.
|
||||
|
||||
*Alternative: Request to Buy the paper (buy/transfer)*
|
||||
|
||||
```
|
||||
node buy_request.js
|
||||
```
|
||||
|
||||
Now complete the _request_ by switching to the `MagnetoCorp` application directory (javascript) and execute a `transfer` transaction as MagnetoCorp:
|
||||
|
||||
```
|
||||
cd ../../magnetocorp/application
|
||||
|
||||
node transfer.js
|
||||
```
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
**<details><summary>Redeem the commercial paper</summary>**
|
||||
|
||||
_Redeem_ is performed as *Digibank* - the current owner (buyer) in the lifecycle.
|
||||
|
||||
You can now run the applications to redeem the paper. Change to either the
|
||||
`commercial-paper/organization/digibank/application` directory or `commercial-paper/organization/digibank/application-java`
|
||||
|
||||
|
||||
*Redeem*
|
||||
|
||||
```
|
||||
|
|
@ -273,9 +609,38 @@ node redeem.js
|
|||
java -cp target/commercial-paper-0.0.1-SNAPSHOT.jar org.digibank.Redeem
|
||||
```
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
|
||||
**<details><summary>Perform Queries: Ownership, Asset History etc (Node.js sample only) </summary>**
|
||||
|
||||
Having completed the full commercial paper lifecycle for one paper (paper number: 00001) some queries below won't show a lot of data - as an optional exercise, you can change the scripts above (paper number: 00002) to create another paper lifecycle and run the `queryapp` application below (change query 1 to the new CP number FYI), with more data available. As indicated, the query transactions mentioned are presently only available in the Node.js sample.
|
||||
|
||||
Execute the Node.js application client script, which will run the following 5 queries, in order:
|
||||
|
||||
- History of Commercial Paper (Note: the paper state is shown more descriptively eg. 'ISSUED', 'TRADING' and based on currentState values on ledger)
|
||||
- Ownership of Commercial Papers
|
||||
- Partial Key query, for Commercial papers in org.papernet.papers namespace belonging to MagnetoCorp
|
||||
- Named Query: all redeemed papers in a state of 'redeemed' (currentState = 4)
|
||||
- Named Query: all commercial papers with a face value > $4m
|
||||
|
||||
From the `digibank/application` subdirectory run:
|
||||
|
||||
```
|
||||
node queryapp.js
|
||||
```
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
When you're done with this section, return to the terminal where your Node.js _listener_ application is running, and terminate the process.
|
||||
|
||||
## Clean up
|
||||
When you are finished using the Fabric test network and the commercial paper smart contract and applications, you can use the following command to clean up the network:
|
||||
|
||||
```
|
||||
./network-clean.sh
|
||||
```
|
||||
|
||||
<sup>[_back to top_](#top)</sup>
|
||||
|
|
|
|||
BIN
commercial-paper/img/overview.png
Normal file
BIN
commercial-paper/img/overview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
BIN
commercial-paper/img/transaction-flow.png
Normal file
BIN
commercial-paper/img/transaction-flow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* This application has 6 basic steps:
|
||||
* 1. Select an identity from a wallet
|
||||
* 2. Connect to network gateway
|
||||
* 3. Access PaperNet network
|
||||
* 4. Construct request to buy (buy_request) commercial paper
|
||||
* 5. Submit transaction
|
||||
* 6. Process response
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Bring key classes into scope, most importantly Fabric SDK network class
|
||||
const fs = require('fs');
|
||||
const yaml = require('js-yaml');
|
||||
const { Wallets, Gateway } = require('fabric-network');
|
||||
const CommercialPaper = require('../../magnetocorp/contract/lib/paper.js');
|
||||
|
||||
|
||||
// Main program function
|
||||
async function main () {
|
||||
|
||||
// A wallet stores a collection of identities for use
|
||||
const wallet = await Wallets.newFileSystemWallet('../identity/user/balaji/wallet');
|
||||
|
||||
|
||||
// A gateway defines the peers used to access Fabric networks
|
||||
const gateway = new Gateway();
|
||||
|
||||
// Main try/catch block
|
||||
try {
|
||||
|
||||
// Specify userName for network access
|
||||
const userName = 'balaji';
|
||||
|
||||
// Load connection profile; will be used to locate a gateway
|
||||
let connectionProfile = yaml.safeLoad(fs.readFileSync('../gateway/connection-org1.yaml', 'utf8'));
|
||||
|
||||
// Set connection options; identity and wallet
|
||||
let connectionOptions = {
|
||||
identity: userName,
|
||||
wallet: wallet,
|
||||
discovery: { enabled: true, asLocalhost: true }
|
||||
|
||||
};
|
||||
|
||||
// Connect to gateway using application specified parameters
|
||||
console.log('Connect to Fabric gateway.');
|
||||
|
||||
await gateway.connect(connectionProfile, connectionOptions);
|
||||
|
||||
// Access PaperNet network
|
||||
console.log('Use network channel: mychannel.');
|
||||
|
||||
const network = await gateway.getNetwork('mychannel');
|
||||
|
||||
// Get addressability to commercial paper contract
|
||||
console.log('Use org.papernet.commercialpaper smart contract.');
|
||||
|
||||
const contract = await network.getContract('papercontract', 'org.papernet.commercialpaper');
|
||||
|
||||
// request to buy commercial paper using buy_request / transfer two-part transaction
|
||||
console.log('Submit commercial paper buy_request transaction.');
|
||||
|
||||
const buyResponse = await contract.submitTransaction('buy_request', 'MagnetoCorp', '00001', 'MagnetoCorp', 'DigiBank', '4900000', '2020-05-31');
|
||||
|
||||
// process response
|
||||
console.log('Process buy_request transaction response.');
|
||||
|
||||
let paper = CommercialPaper.fromBuffer(buyResponse);
|
||||
|
||||
console.log(`${paper.issuer} commercial paper : ${paper.paperNumber} has been provisionally purchased : the transfer must now be completed by paper owner`);
|
||||
console.log('Transaction complete.');
|
||||
|
||||
} catch (error) {
|
||||
|
||||
console.log(`Error processing transaction. ${error}`);
|
||||
console.log(error.stack);
|
||||
|
||||
} finally {
|
||||
|
||||
// Disconnect from the gateway
|
||||
console.log('Disconnect from Fabric gateway.');
|
||||
gateway.disconnect();
|
||||
|
||||
}
|
||||
}
|
||||
main().then(() => {
|
||||
|
||||
console.log('Buy_request program complete.');
|
||||
|
||||
}).catch((e) => {
|
||||
|
||||
console.log('Buy_request program exception.');
|
||||
console.log(e);
|
||||
console.log(e.stack);
|
||||
process.exit(-1);
|
||||
|
||||
});
|
||||
155
commercial-paper/organization/digibank/application/queryapp.js
Normal file
155
commercial-paper/organization/digibank/application/queryapp.js
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* This application has 6 basic steps:
|
||||
* 1. Select an identity from a wallet
|
||||
* 2. Connect to network gateway
|
||||
* 3. Access PaperNet network
|
||||
* 4. Construct request to query the ledger
|
||||
* 5. Evaluate transactions (queries)
|
||||
* 6. Process responses
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Bring key classes into scope, most importantly Fabric SDK network class
|
||||
const fs = require('fs');
|
||||
const yaml = require('js-yaml');
|
||||
const { Wallets, Gateway } = require('fabric-network');
|
||||
|
||||
|
||||
// Main program function
|
||||
async function main() {
|
||||
|
||||
// A wallet stores a collection of identities for use
|
||||
const wallet = await Wallets.newFileSystemWallet('../identity/user/balaji/wallet');
|
||||
|
||||
|
||||
// A gateway defines the peers used to access Fabric networks
|
||||
const gateway = new Gateway();
|
||||
|
||||
// Main try/catch block
|
||||
try {
|
||||
|
||||
// Specify userName for network access
|
||||
const userName = 'balaji';
|
||||
|
||||
// Load connection profile; will be used to locate a gateway
|
||||
let connectionProfile = yaml.safeLoad(fs.readFileSync('../gateway/connection-org1.yaml', 'utf8'));
|
||||
|
||||
// Set connection options; identity and wallet
|
||||
let connectionOptions = {
|
||||
identity: userName,
|
||||
wallet: wallet,
|
||||
discovery: { enabled: true, asLocalhost: true }
|
||||
|
||||
};
|
||||
|
||||
// Connect to gateway using application specified parameters
|
||||
console.log('Connect to Fabric gateway.');
|
||||
|
||||
await gateway.connect(connectionProfile, connectionOptions);
|
||||
|
||||
// Access PaperNet network
|
||||
console.log('Use network channel: mychannel.');
|
||||
|
||||
const network = await gateway.getNetwork('mychannel');
|
||||
|
||||
// Get addressability to commercial paper contract
|
||||
console.log('Use org.papernet.commercialpaper smart contract.');
|
||||
|
||||
const contract = await network.getContract('papercontract', 'org.papernet.commercialpaper');
|
||||
|
||||
// queries - commercial paper
|
||||
console.log('-----------------------------------------------------------------------------------------');
|
||||
console.log('****** Submitting commercial paper queries ****** \n\n ');
|
||||
|
||||
|
||||
// 1 asset history
|
||||
console.log('1. Query Commercial Paper History....');
|
||||
console.log('-----------------------------------------------------------------------------------------\n');
|
||||
let queryResponse = await contract.evaluateTransaction('queryHistory', 'MagnetoCorp', '00001');
|
||||
|
||||
let json = JSON.parse(queryResponse.toString());
|
||||
console.log(json);
|
||||
console.log('\n\n');
|
||||
console.log('\n History query complete.');
|
||||
console.log('-----------------------------------------------------------------------------------------\n\n');
|
||||
|
||||
// 2 ownership query
|
||||
console.log('2. Query Commercial Paper Ownership.... Papers owned by MagnetoCorp');
|
||||
console.log('-----------------------------------------------------------------------------------------\n');
|
||||
let queryResponse2 = await contract.evaluateTransaction('queryOwner', 'MagnetoCorp');
|
||||
json = JSON.parse(queryResponse2.toString());
|
||||
console.log(json);
|
||||
|
||||
console.log('\n\n');
|
||||
console.log('\n Paper Ownership query complete.');
|
||||
console.log('-----------------------------------------------------------------------------------------\n\n');
|
||||
|
||||
// 3 partial key query
|
||||
console.log('3. Query Commercial Paper Partial Key.... Papers in org.papernet.papers namespace and prefixed MagnetoCorp');
|
||||
console.log('-----------------------------------------------------------------------------------------\n');
|
||||
let queryResponse3 = await contract.evaluateTransaction('queryPartial', 'MagnetoCorp');
|
||||
|
||||
json = JSON.parse(queryResponse3.toString());
|
||||
console.log(json);
|
||||
console.log('\n\n');
|
||||
|
||||
console.log('\n Partial Key query complete.');
|
||||
console.log('-----------------------------------------------------------------------------------------\n\n');
|
||||
|
||||
|
||||
// 4 Named query - all redeemed papers
|
||||
console.log('4. Named Query: ... All papers in org.papernet.papers that are in current state of redeemed');
|
||||
console.log('-----------------------------------------------------------------------------------------\n');
|
||||
let queryResponse4 = await contract.evaluateTransaction('queryNamed', 'redeemed');
|
||||
|
||||
json = JSON.parse(queryResponse4.toString());
|
||||
console.log(json);
|
||||
console.log('\n\n');
|
||||
|
||||
console.log('\n Named query "redeemed" complete.');
|
||||
console.log('-----------------------------------------------------------------------------------------\n\n');
|
||||
|
||||
|
||||
// 5 named query - by value
|
||||
console.log('5. Named Query:.... All papers in org.papernet.papers with faceValue > 4000000');
|
||||
console.log('-----------------------------------------------------------------------------------------\n');
|
||||
let queryResponse5 = await contract.evaluateTransaction('queryNamed', 'value');
|
||||
|
||||
json = JSON.parse(queryResponse5.toString());
|
||||
console.log(json);
|
||||
console.log('\n\n');
|
||||
|
||||
console.log('\n Named query by "value" complete.');
|
||||
console.log('-----------------------------------------------------------------------------------------\n\n');
|
||||
} catch (error) {
|
||||
|
||||
console.log(`Error processing transaction. ${error}`);
|
||||
console.log(error.stack);
|
||||
|
||||
} finally {
|
||||
|
||||
// Disconnect from the gateway
|
||||
console.log('Disconnect from Fabric gateway.');
|
||||
gateway.disconnect();
|
||||
|
||||
}
|
||||
}
|
||||
main().then(() => {
|
||||
|
||||
console.log('Queryapp program complete.');
|
||||
|
||||
}).catch((e) => {
|
||||
|
||||
console.log('Queryapp program exception.');
|
||||
console.log(e);
|
||||
console.log(e.stack);
|
||||
process.exit(-1);
|
||||
|
||||
});
|
||||
|
|
@ -68,7 +68,7 @@ async function main() {
|
|||
// redeem commercial paper
|
||||
console.log('Submit commercial paper redeem transaction.');
|
||||
|
||||
const redeemResponse = await contract.submitTransaction('redeem', 'MagnetoCorp', '00001', 'DigiBank', '2020-11-30');
|
||||
const redeemResponse = await contract.submitTransaction('redeem', 'MagnetoCorp', '00001', 'DigiBank', 'Org2MSP', '2020-11-30');
|
||||
|
||||
// process response
|
||||
console.log('Process redeem transaction response.');
|
||||
|
|
@ -76,6 +76,7 @@ async function main() {
|
|||
let paper = CommercialPaper.fromBuffer(redeemResponse);
|
||||
|
||||
console.log(`${paper.issuer} commercial paper : ${paper.paperNumber} successfully redeemed with ${paper.owner}`);
|
||||
|
||||
console.log('Transaction complete.');
|
||||
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -38,9 +38,11 @@ class State {
|
|||
return this.currentState;
|
||||
}
|
||||
|
||||
serialize() {
|
||||
// not used
|
||||
/* serialize() {
|
||||
|
||||
return State.serialize(this);
|
||||
}
|
||||
} */
|
||||
|
||||
/**
|
||||
* Convert object to buffer containing JSON data serialization
|
||||
|
|
@ -49,6 +51,8 @@ class State {
|
|||
* @return {buffer} buffer with the data to store
|
||||
*/
|
||||
static serialize(object) {
|
||||
// don't write the key:value passed in - we already have a real composite key, issuer and paper Number.
|
||||
delete object.key;
|
||||
return Buffer.from(JSON.stringify(object));
|
||||
}
|
||||
|
||||
|
|
@ -88,7 +92,8 @@ class State {
|
|||
* @param (String[]) keyParts
|
||||
*/
|
||||
static makeKey(keyParts) {
|
||||
return keyParts.map(part => JSON.stringify(part)).join(':');
|
||||
// return keyParts.map(part => JSON.stringify(part)).join(':');
|
||||
return keyParts.map(part => part).join(':');
|
||||
}
|
||||
|
||||
static splitKey(key){
|
||||
|
|
|
|||
|
|
@ -12,8 +12,9 @@ const State = require('./../ledger-api/state.js');
|
|||
// Enumerate commercial paper state values
|
||||
const cpState = {
|
||||
ISSUED: 1,
|
||||
TRADING: 2,
|
||||
REDEEMED: 3
|
||||
PENDING: 2,
|
||||
TRADING: 3,
|
||||
REDEEMED: 4
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -42,6 +43,14 @@ class CommercialPaper extends State {
|
|||
return this.owner;
|
||||
}
|
||||
|
||||
setOwnerMSP(mspid) {
|
||||
this.mspid = mspid;
|
||||
}
|
||||
|
||||
getOwnerMSP() {
|
||||
return this.mspid;
|
||||
}
|
||||
|
||||
setOwner(newOwner) {
|
||||
this.owner = newOwner;
|
||||
}
|
||||
|
|
@ -61,6 +70,10 @@ class CommercialPaper extends State {
|
|||
this.currentState = cpState.REDEEMED;
|
||||
}
|
||||
|
||||
setPending() {
|
||||
this.currentState = cpState.PENDING;
|
||||
}
|
||||
|
||||
isIssued() {
|
||||
return this.currentState === cpState.ISSUED;
|
||||
}
|
||||
|
|
@ -73,6 +86,10 @@ class CommercialPaper extends State {
|
|||
return this.currentState === cpState.REDEEMED;
|
||||
}
|
||||
|
||||
isPending() {
|
||||
return this.currentState === cpState.PENDING;
|
||||
}
|
||||
|
||||
static fromBuffer(buffer) {
|
||||
return CommercialPaper.deserialize(buffer);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ const { Contract, Context } = require('fabric-contract-api');
|
|||
// PaperNet specifc classes
|
||||
const CommercialPaper = require('./paper.js');
|
||||
const PaperList = require('./paperlist.js');
|
||||
const QueryUtils = require('./queries.js');
|
||||
|
||||
/**
|
||||
* A custom context provides easy access to list of all commercial papers
|
||||
|
|
@ -67,12 +68,16 @@ class CommercialPaperContract extends Contract {
|
|||
async issue(ctx, issuer, paperNumber, issueDateTime, maturityDateTime, faceValue) {
|
||||
|
||||
// create an instance of the paper
|
||||
let paper = CommercialPaper.createInstance(issuer, paperNumber, issueDateTime, maturityDateTime, faceValue);
|
||||
let paper = CommercialPaper.createInstance(issuer, paperNumber, issueDateTime, maturityDateTime, parseInt(faceValue));
|
||||
|
||||
// Smart contract, rather than paper, moves paper into ISSUED state
|
||||
paper.setIssued();
|
||||
|
||||
// Newly issued paper is owned by the issuer
|
||||
// save the owner's MSP
|
||||
let mspid = ctx.clientIdentity.getMSPID();
|
||||
paper.setOwnerMSP(mspid);
|
||||
|
||||
// Newly issued paper is owned by the issuer to begin with (recorded for reporting purposes)
|
||||
paper.setOwner(issuer);
|
||||
|
||||
// Add the paper to the list of all similar commercial papers in the ledger world state
|
||||
|
|
@ -85,14 +90,14 @@ class CommercialPaperContract extends Contract {
|
|||
/**
|
||||
* Buy commercial paper
|
||||
*
|
||||
* @param {Context} ctx the transaction context
|
||||
* @param {String} issuer commercial paper issuer
|
||||
* @param {Integer} paperNumber paper number for this issuer
|
||||
* @param {String} currentOwner current owner of paper
|
||||
* @param {String} newOwner new owner of paper
|
||||
* @param {Integer} price price paid for this paper
|
||||
* @param {String} purchaseDateTime time paper was purchased (i.e. traded)
|
||||
*/
|
||||
* @param {Context} ctx the transaction context
|
||||
* @param {String} issuer commercial paper issuer
|
||||
* @param {Integer} paperNumber paper number for this issuer
|
||||
* @param {String} currentOwner current owner of paper
|
||||
* @param {String} newOwner new owner of paper
|
||||
* @param {Integer} price price paid for this paper // transaction input - not written to asset
|
||||
* @param {String} purchaseDateTime time paper was purchased (i.e. traded) // transaction input - not written to asset
|
||||
*/
|
||||
async buy(ctx, issuer, paperNumber, currentOwner, newOwner, price, purchaseDateTime) {
|
||||
|
||||
// Retrieve the current paper using key fields provided
|
||||
|
|
@ -101,10 +106,10 @@ class CommercialPaperContract extends Contract {
|
|||
|
||||
// Validate current owner
|
||||
if (paper.getOwner() !== currentOwner) {
|
||||
throw new Error('Paper ' + issuer + paperNumber + ' is not owned by ' + currentOwner);
|
||||
throw new Error('\nPaper ' + issuer + paperNumber + ' is not owned by ' + currentOwner);
|
||||
}
|
||||
|
||||
// First buy moves state from ISSUED to TRADING
|
||||
// First buy moves state from ISSUED to TRADING (when running )
|
||||
if (paper.isIssued()) {
|
||||
paper.setTrading();
|
||||
}
|
||||
|
|
@ -112,8 +117,11 @@ class CommercialPaperContract extends Contract {
|
|||
// Check paper is not already REDEEMED
|
||||
if (paper.isTrading()) {
|
||||
paper.setOwner(newOwner);
|
||||
// save the owner's MSP
|
||||
let mspid = ctx.clientIdentity.getMSPID();
|
||||
paper.setOwnerMSP(mspid);
|
||||
} else {
|
||||
throw new Error('Paper ' + issuer + paperNumber + ' is not trading. Current state = ' + paper.getCurrentState());
|
||||
throw new Error('\nPaper ' + issuer + paperNumber + ' is not trading. Current state = ' + paper.getCurrentState());
|
||||
}
|
||||
|
||||
// Update the paper
|
||||
|
|
@ -121,6 +129,79 @@ class CommercialPaperContract extends Contract {
|
|||
return paper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Buy request: (2-phase confirmation: Commercial paper is 'PENDING' subject to completion of transfer by owning org)
|
||||
* Alternative to 'buy' transaction
|
||||
* Note: 'buy_request' puts paper in 'PENDING' state - subject to transfer confirmation [below].
|
||||
*
|
||||
* @param {Context} ctx the transaction context
|
||||
* @param {String} issuer commercial paper issuer
|
||||
* @param {Integer} paperNumber paper number for this issuer
|
||||
* @param {String} currentOwner current owner of paper
|
||||
* @param {String} newOwner new owner of paper // transaction input - not written to asset per se - but written to block
|
||||
* @param {Integer} price price paid for this paper // transaction input - not written to asset per se - but written to block
|
||||
* @param {String} purchaseDateTime time paper was requested // transaction input - ditto.
|
||||
*/
|
||||
async buy_request(ctx, issuer, paperNumber, currentOwner, newOwner, price, purchaseDateTime) {
|
||||
|
||||
|
||||
// Retrieve the current paper using key fields provided
|
||||
let paperKey = CommercialPaper.makeKey([issuer, paperNumber]);
|
||||
let paper = await ctx.paperList.getPaper(paperKey);
|
||||
|
||||
// Validate current owner - this is really information for the user trying the sample, rather than any 'authorisation' check per se FYI
|
||||
if (paper.getOwner() !== currentOwner) {
|
||||
throw new Error('\nPaper ' + issuer + paperNumber + ' is not owned by ' + currentOwner + ' provided as a paraneter');
|
||||
}
|
||||
// paper set to 'PENDING' - can only be transferred (confirmed) by identity from owning org (MSP check).
|
||||
paper.setPending();
|
||||
|
||||
// Update the paper
|
||||
await ctx.paperList.updatePaper(paper);
|
||||
return paper;
|
||||
}
|
||||
|
||||
/**
|
||||
* transfer commercial paper: only the owning org has authority to execute. It is the complement to the 'buy_request' transaction. '[]' is optional below.
|
||||
* eg. issue -> buy_request -> transfer -> [buy ...n | [buy_request...n | transfer ...n] ] -> redeem
|
||||
* this transaction 'pair' is an alternative to the straight issue -> buy -> [buy....n] -> redeem ...path
|
||||
*
|
||||
* @param {Context} ctx the transaction context
|
||||
* @param {String} issuer commercial paper issuer
|
||||
* @param {Integer} paperNumber paper number for this issuer
|
||||
* @param {String} newOwner new owner of paper
|
||||
* @param {String} newOwnerMSP MSP id of the transferee
|
||||
* @param {String} confirmDateTime confirmed transfer date.
|
||||
*/
|
||||
async transfer(ctx, issuer, paperNumber, newOwner, newOwnerMSP, confirmDateTime) {
|
||||
|
||||
// Retrieve the current paper using key fields provided
|
||||
let paperKey = CommercialPaper.makeKey([issuer, paperNumber]);
|
||||
let paper = await ctx.paperList.getPaper(paperKey);
|
||||
|
||||
// Validate current owner's MSP in the paper === invoking transferor's MSP id - can only transfer if you are the owning org.
|
||||
|
||||
if (paper.getOwnerMSP() !== ctx.clientIdentity.getMSPID()) {
|
||||
throw new Error('\nPaper ' + issuer + paperNumber + ' is not owned by the current invoking Organisation, and not authorised to transfer');
|
||||
}
|
||||
|
||||
// Paper needs to be 'pending' - which means you need to have run 'buy_pending' transaction first.
|
||||
if ( ! paper.isPending()) {
|
||||
throw new Error('\nPaper ' + issuer + paperNumber + ' is not currently in state: PENDING for transfer to occur: \n must run buy_request transaction first');
|
||||
}
|
||||
// else all good
|
||||
|
||||
paper.setOwner(newOwner);
|
||||
// set the MSP of the transferee (so that, that org may also pass MSP check, if subsequently transferred/sold on)
|
||||
paper.setOwnerMSP(newOwnerMSP);
|
||||
paper.setTrading();
|
||||
paper.confirmDateTime = confirmDateTime;
|
||||
|
||||
// Update the paper
|
||||
await ctx.paperList.updatePaper(paper);
|
||||
return paper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redeem commercial paper
|
||||
*
|
||||
|
|
@ -128,31 +209,131 @@ class CommercialPaperContract extends Contract {
|
|||
* @param {String} issuer commercial paper issuer
|
||||
* @param {Integer} paperNumber paper number for this issuer
|
||||
* @param {String} redeemingOwner redeeming owner of paper
|
||||
* @param {String} issuingOwnerMSP the MSP of the org that the paper will be redeemed with.
|
||||
* @param {String} redeemDateTime time paper was redeemed
|
||||
*/
|
||||
async redeem(ctx, issuer, paperNumber, redeemingOwner, redeemDateTime) {
|
||||
async redeem(ctx, issuer, paperNumber, redeemingOwner, issuingOwnerMSP, redeemDateTime) {
|
||||
|
||||
let paperKey = CommercialPaper.makeKey([issuer, paperNumber]);
|
||||
|
||||
let paper = await ctx.paperList.getPaper(paperKey);
|
||||
|
||||
// Check paper is not REDEEMED
|
||||
// Check paper is not alread in a state of REDEEMED
|
||||
if (paper.isRedeemed()) {
|
||||
throw new Error('Paper ' + issuer + paperNumber + ' already redeemed');
|
||||
throw new Error('\nPaper ' + issuer + paperNumber + ' has already been redeemed');
|
||||
}
|
||||
|
||||
// Verify that the redeemer owns the commercial paper before redeeming it
|
||||
// Validate current redeemer's MSP matches the invoking redeemer's MSP id - can only redeem if you are the owning org.
|
||||
|
||||
if (paper.getOwnerMSP() !== ctx.clientIdentity.getMSPID()) {
|
||||
throw new Error('\nPaper ' + issuer + paperNumber + ' cannot be redeemed by ' + ctx.clientIdentity.getMSPID() + ', as it is not the authorised owning Organisation');
|
||||
}
|
||||
|
||||
// As this is just a sample, can show additional verification check: that the redeemer provided matches that on record, before redeeming it
|
||||
if (paper.getOwner() === redeemingOwner) {
|
||||
paper.setOwner(paper.getIssuer());
|
||||
paper.setOwnerMSP(issuingOwnerMSP);
|
||||
paper.setRedeemed();
|
||||
paper.redeemDateTime = redeemDateTime; // record redemption date against the asset (the complement to 'issue date')
|
||||
} else {
|
||||
throw new Error('Redeeming owner does not own paper' + issuer + paperNumber);
|
||||
throw new Error('\nRedeeming owner: ' + redeemingOwner + ' organisation does not currently own paper: ' + issuer + paperNumber);
|
||||
}
|
||||
|
||||
await ctx.paperList.updatePaper(paper);
|
||||
return paper;
|
||||
}
|
||||
|
||||
// Query transactions
|
||||
|
||||
/**
|
||||
* Query history of a commercial paper
|
||||
* @param {Context} ctx the transaction context
|
||||
* @param {String} issuer commercial paper issuer
|
||||
* @param {Integer} paperNumber paper number for this issuer
|
||||
*/
|
||||
async queryHistory(ctx, issuer, paperNumber) {
|
||||
|
||||
// Get a key to be used for History query
|
||||
|
||||
let query = new QueryUtils(ctx, 'org.papernet.paper');
|
||||
let results = await query.getAssetHistory(issuer, paperNumber); // (cpKey);
|
||||
return results;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* queryOwner commercial paper: supply name of owning org, to find list of papers based on owner field
|
||||
* @param {Context} ctx the transaction context
|
||||
* @param {String} owner commercial paper owner
|
||||
*/
|
||||
async queryOwner(ctx, owner) {
|
||||
|
||||
let query = new QueryUtils(ctx, 'org.papernet.paper');
|
||||
let owner_results = await query.queryKeyByOwner(owner);
|
||||
|
||||
return owner_results;
|
||||
}
|
||||
|
||||
/**
|
||||
* queryPartial commercial paper - provide a prefix eg. "DigiBank" will list all papers _issued_ by DigiBank etc etc
|
||||
* @param {Context} ctx the transaction context
|
||||
* @param {String} prefix asset class prefix (added to paperlist namespace) eg. org.papernet.paperMagnetoCorp asset listing: papers issued by MagnetoCorp.
|
||||
*/
|
||||
async queryPartial(ctx, prefix) {
|
||||
|
||||
let query = new QueryUtils(ctx, 'org.papernet.paper');
|
||||
let partial_results = await query.queryKeyByPartial(prefix);
|
||||
|
||||
return partial_results;
|
||||
}
|
||||
|
||||
/**
|
||||
* queryAdHoc commercial paper - supply a custom mango query
|
||||
* eg - as supplied as a param:
|
||||
* ex1: ["{\"selector\":{\"faceValue\":{\"$lt\":8000000}}}"]
|
||||
* ex2: ["{\"selector\":{\"faceValue\":{\"$gt\":4999999}}}"]
|
||||
*
|
||||
* @param {Context} ctx the transaction context
|
||||
* @param {String} queryString querystring
|
||||
*/
|
||||
async queryAdhoc(ctx, queryString) {
|
||||
|
||||
let query = new QueryUtils(ctx, 'org.papernet.paper');
|
||||
let querySelector = JSON.parse(queryString);
|
||||
let adhoc_results = await query.queryByAdhoc(querySelector);
|
||||
|
||||
return adhoc_results;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* queryNamed - supply named query - 'case' statement chooses selector to build (pre-canned for demo purposes)
|
||||
* @param {Context} ctx the transaction context
|
||||
* @param {String} queryname the 'named' query (built here) - or - the adHoc query string, provided as a parameter
|
||||
*/
|
||||
async queryNamed(ctx, queryname) {
|
||||
let querySelector = {};
|
||||
switch (queryname) {
|
||||
case "redeemed":
|
||||
querySelector = { "selector": { "currentState": 4 } }; // 4 = redeemd state
|
||||
break;
|
||||
case "trading":
|
||||
querySelector = { "selector": { "currentState": 3 } }; // 3 = trading state
|
||||
break;
|
||||
case "value":
|
||||
// may change to provide as a param - fixed value for now in this sample
|
||||
querySelector = { "selector": { "faceValue": { "$gt": 4000000 } } }; // to test, issue CommPapers with faceValue <= or => this figure.
|
||||
break;
|
||||
default: // else, unknown named query
|
||||
throw new Error('invalid named query supplied: ' + queryname + '- please try again ');
|
||||
}
|
||||
|
||||
let query = new QueryUtils(ctx, 'org.papernet.paper');
|
||||
let adhoc_results = await query.queryByAdhoc(querySelector);
|
||||
|
||||
return adhoc_results;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = CommercialPaperContract;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ const CommercialPaper = require('./paper.js');
|
|||
class PaperList extends StateList {
|
||||
|
||||
constructor(ctx) {
|
||||
super(ctx, 'org.papernet.commercialpaperlist');
|
||||
super(ctx, 'org.papernet.paper');
|
||||
this.use(CommercialPaper);
|
||||
}
|
||||
|
||||
|
|
|
|||
215
commercial-paper/organization/digibank/contract/lib/queries.js
Normal file
215
commercial-paper/organization/digibank/contract/lib/queries.js
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
|
||||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const State = require('../ledger-api/state.js');
|
||||
//const CommercialPaper = require('./paper.js');
|
||||
/**
|
||||
* Query Class for query functions such as history etc
|
||||
*
|
||||
*/
|
||||
class QueryUtils {
|
||||
|
||||
constructor(ctx, listName) {
|
||||
this.ctx = ctx;
|
||||
this.name = listName;
|
||||
//this.supportedTypes = {};
|
||||
}
|
||||
|
||||
// =========================================================================================
|
||||
// getAssetHistory takes the composite key as arg, gets returns results as JSON to 'main contract'
|
||||
// =========================================================================================
|
||||
/**
|
||||
* Get Asset History for a commercial paper
|
||||
* @param {String} issuer the CP issuer
|
||||
* @param {String} paperNumber commercial paper number
|
||||
*/
|
||||
async getAssetHistory(issuer, paperNumber) {
|
||||
|
||||
let ledgerKey = await this.ctx.stub.createCompositeKey(this.name, [issuer, paperNumber]);
|
||||
const resultsIterator = await this.ctx.stub.getHistoryForKey(ledgerKey);
|
||||
let results = await this.getAllResults(resultsIterator, true);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// ===========================================================================================
|
||||
// queryKeyByPartial performs a partial query based on the namespace and asset key prefix provided
|
||||
|
||||
// Read-only function results are not typically submitted to ordering. If the read-only
|
||||
// results are submitted to ordering, or if the query is used in an update transaction
|
||||
// and submitted to ordering, then the committing peers will re-execute to guarantee that
|
||||
// result sets are stable between endorsement time and commit time. The transaction is
|
||||
// invalidated by the committing peers if the result set has changed between endorsement
|
||||
// time and commit time.
|
||||
//
|
||||
// ===========================================================================================
|
||||
/**
|
||||
* queryOwner commercial paper
|
||||
* @param {String} assetspace the asset space (eg MagnetoCorp's assets)
|
||||
*/
|
||||
async queryKeyByPartial(assetspace) {
|
||||
|
||||
if (arguments.length < 1) {
|
||||
throw new Error('Incorrect number of arguments. Expecting 1');
|
||||
}
|
||||
// ie namespace + prefix to assets etc eg
|
||||
// "Key":"org.papernet.paperMagnetoCorp0001" (0002, etc)
|
||||
// "Partial":'org.papernet.paperlistMagnetoCorp"' (using partial key, find keys "0001", "0002" etc)
|
||||
const resultsIterator = await this.ctx.stub.getStateByPartialCompositeKey(this.name, [assetspace]);
|
||||
let method = this.getAllResults;
|
||||
let results = await method(resultsIterator, false);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
// ===== Example: Parameterized rich query =================================================
|
||||
// queryKeyByOwner queries for assets based on a passed in owner.
|
||||
// This is an example of a parameterized query accepting a single query parameter (owner).
|
||||
// Only available on state databases that support rich query (e.g. CouchDB)
|
||||
// =========================================================================================
|
||||
/**
|
||||
* queryKeyByOwner commercial paper
|
||||
* @param {String} owner commercial paper owner
|
||||
*/
|
||||
async queryKeyByOwner(owner) {
|
||||
//
|
||||
let self = this;
|
||||
if (arguments.length < 1) {
|
||||
throw new Error('Incorrect number of arguments. Expecting owner name.');
|
||||
}
|
||||
let queryString = {};
|
||||
queryString.selector = {};
|
||||
// queryString.selector.docType = 'indexOwnerDoc';
|
||||
queryString.selector.owner = owner;
|
||||
// set to (eg) '{selector:{owner:MagnetoCorp}}'
|
||||
let method = self.getQueryResultForQueryString;
|
||||
let queryResults = await method(this.ctx, self, JSON.stringify(queryString));
|
||||
return queryResults;
|
||||
}
|
||||
|
||||
// ===== Example: Ad hoc rich query ========================================================
|
||||
// queryAdhoc uses a query string to perform a query for marbles..
|
||||
// Query string matching state database syntax is passed in and executed as is.
|
||||
// Supports ad hoc queries that can be defined at runtime by the client.
|
||||
// If this is not desired, follow the queryKeyByOwner example for parameterized queries.
|
||||
// Only available on state databases that support rich query (e.g. CouchDB)
|
||||
// example passed using VS Code ext: ["{\"selector\": {\"owner\": \"MagnetoCorp\"}}"]
|
||||
// =========================================================================================
|
||||
/**
|
||||
* query By AdHoc string (commercial paper)
|
||||
* @param {String} queryString actual MangoDB query string (escaped)
|
||||
*/
|
||||
async queryByAdhoc(queryString) {
|
||||
|
||||
if (arguments.length < 1) {
|
||||
throw new Error('Incorrect number of arguments. Expecting ad-hoc string, which gets stringified for mango query');
|
||||
}
|
||||
let self = this;
|
||||
|
||||
if (!queryString) {
|
||||
throw new Error('queryString must not be empty');
|
||||
}
|
||||
let method = self.getQueryResultForQueryString;
|
||||
let queryResults = await method(this.ctx, self, JSON.stringify(queryString));
|
||||
return queryResults;
|
||||
}
|
||||
|
||||
// WORKER functions are below this line: these are called by the above functions, where iterator is passed in
|
||||
|
||||
// =========================================================================================
|
||||
// getQueryResultForQueryString woerk function executes the passed-in query string.
|
||||
// Result set is built and returned as a byte array containing the JSON results.
|
||||
// =========================================================================================
|
||||
/**
|
||||
* Function getQueryResultForQueryString
|
||||
* @param {Context} ctx the transaction context
|
||||
* @param {any} self within scope passed in
|
||||
* @param {String} the query string created prior to calling this fn
|
||||
*/
|
||||
async getQueryResultForQueryString(ctx, self, queryString) {
|
||||
|
||||
// console.log('- getQueryResultForQueryString queryString:\n' + queryString);
|
||||
|
||||
const resultsIterator = await ctx.stub.getQueryResult(queryString);
|
||||
let results = await self.getAllResults(resultsIterator, false);
|
||||
|
||||
return results;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Function getAllResults
|
||||
* @param {resultsIterator} iterator within scope passed in
|
||||
* @param {Boolean} isHistory query string created prior to calling this fn
|
||||
*/
|
||||
async getAllResults(iterator, isHistory) {
|
||||
let allResults = [];
|
||||
let res = { done: false, value: null };
|
||||
|
||||
while (true) {
|
||||
res = await iterator.next();
|
||||
let jsonRes = {};
|
||||
if (res.value && res.value.value.toString()) {
|
||||
if (isHistory && isHistory === true) {
|
||||
//jsonRes.TxId = res.value.tx_id;
|
||||
jsonRes.TxId = res.value.txId;
|
||||
jsonRes.Timestamp = res.value.timestamp;
|
||||
jsonRes.Timestamp = new Date((res.value.timestamp.seconds.low * 1000));
|
||||
let ms = res.value.timestamp.nanos / 1000000;
|
||||
jsonRes.Timestamp.setMilliseconds(ms);
|
||||
if (res.value.is_delete) {
|
||||
jsonRes.IsDelete = res.value.is_delete.toString();
|
||||
} else {
|
||||
try {
|
||||
jsonRes.Value = JSON.parse(res.value.value.toString('utf8'));
|
||||
// report the commercial paper states during the asset lifecycle, just for asset history reporting
|
||||
switch (jsonRes.Value.currentState) {
|
||||
case 1:
|
||||
jsonRes.Value.currentState = 'ISSUED';
|
||||
break;
|
||||
case 2:
|
||||
jsonRes.Value.currentState = 'PENDING';
|
||||
break;
|
||||
case 3:
|
||||
jsonRes.Value.currentState = 'TRADING';
|
||||
break;
|
||||
case 4:
|
||||
jsonRes.Value.currentState = 'REDEEMED';
|
||||
break;
|
||||
default: // else, unknown named query
|
||||
jsonRes.Value.currentState = 'UNKNOWN';
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
jsonRes.Value = res.value.value.toString('utf8');
|
||||
}
|
||||
}
|
||||
} else { // non history query ..
|
||||
jsonRes.Key = res.value.key;
|
||||
try {
|
||||
jsonRes.Record = JSON.parse(res.value.value.toString('utf8'));
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
jsonRes.Record = res.value.value.toString('utf8');
|
||||
}
|
||||
}
|
||||
allResults.push(jsonRes);
|
||||
}
|
||||
// check to see if we have reached the end
|
||||
if (res.done) {
|
||||
// explicitly close the iterator
|
||||
console.log('iterator is done');
|
||||
await iterator.close();
|
||||
return allResults;
|
||||
}
|
||||
|
||||
} // while true
|
||||
}
|
||||
|
||||
}
|
||||
module.exports = QueryUtils;
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
"use strict";
|
||||
|
||||
const yaml = require('js-yaml');
|
||||
const { Wallets, Gateway } = require('fabric-network');
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
|
||||
let finished;
|
||||
async function main() {
|
||||
try {
|
||||
// Set up the wallet - just use Org2's wallet (isabella)
|
||||
const wallet = await Wallets.newFileSystemWallet('../identity/user/isabella/wallet');
|
||||
|
||||
// Create a new gateway for connecting to our peer node.
|
||||
const gateway = new Gateway();
|
||||
|
||||
const userName = 'isabella';
|
||||
|
||||
// Load connection profile; will be used to locate a gateway
|
||||
let connectionProfile = yaml.safeLoad(fs.readFileSync('../gateway/connection-org2.yaml', 'utf8'));
|
||||
|
||||
// Set connection options; identity and wallet
|
||||
let connectionOptions = {
|
||||
identity: userName,
|
||||
wallet: wallet,
|
||||
discovery: { enabled:true, asLocalhost: true }
|
||||
};
|
||||
|
||||
// connect to the gateway
|
||||
await gateway.connect(connectionProfile, connectionOptions);
|
||||
// get the channel and smart contract
|
||||
const network = await gateway.getNetwork('mychannel');
|
||||
|
||||
// Listen for blocks being added, display relevant contents: in particular, the transaction inputs
|
||||
finished = false;
|
||||
|
||||
const listener = async (event) => {
|
||||
if (event.blockData !== undefined) {
|
||||
for (const i in event.blockData.data.data) {
|
||||
if (event.blockData.data.data[i].payload.data.actions !== undefined) {
|
||||
const inputArgs = event.blockData.data.data[i].payload.data.actions[0].payload.chaincode_proposal_payload.input.chaincode_spec.input.args;
|
||||
// Print block details
|
||||
console.log('----------');
|
||||
console.log('Block:', parseInt(event.blockData.header.number), 'transaction', i);
|
||||
// Show ID and timestamp of the transaction
|
||||
const tx_id = event.blockData.data.data[i].payload.header.channel_header.tx_id;
|
||||
const txTime = new Date(event.blockData.data.data[i].payload.header.channel_header.timestamp).toUTCString();
|
||||
// Show ID, date and time of transaction
|
||||
console.log('Transaction ID:', tx_id);
|
||||
console.log('Timestamp:', txTime);
|
||||
// Show transaction inputs (formatted, as may contain binary data)
|
||||
let inputData = 'Inputs: ';
|
||||
for (let j = 0; j < inputArgs.length; j++) {
|
||||
const inputArgPrintable = inputArgs[j].toString().replace(/[^\x20-\x7E]+/g, '');
|
||||
inputData = inputData.concat(inputArgPrintable, ' ');
|
||||
}
|
||||
console.log(inputData);
|
||||
// Show the proposed writes to the world state
|
||||
let keyData = 'Keys updated: ';
|
||||
for (const l in event.blockData.data.data[i].payload.data.actions[0].payload.action.proposal_response_payload.extension.results.ns_rwset[1].rwset.writes) {
|
||||
// add a ' ' space between multiple keys in 'concat'
|
||||
keyData = keyData.concat(event.blockData.data.data[i].payload.data.actions[0].payload.action.proposal_response_payload.extension.results.ns_rwset[1].rwset.writes[l].key, ' ');
|
||||
}
|
||||
console.log(keyData);
|
||||
// Show which organizations endorsed
|
||||
let endorsers = 'Endorsers: ';
|
||||
for (const k in event.blockData.data.data[i].payload.data.actions[0].payload.action.endorsements) {
|
||||
endorsers = endorsers.concat(event.blockData.data.data[i].payload.data.actions[0].payload.action.endorsements[k].endorser.mspid, ' ');
|
||||
}
|
||||
console.log(endorsers);
|
||||
// Was the transaction valid or not?
|
||||
// (Invalid transactions are still logged on the blockchain but don't affect the world state)
|
||||
if ((event.blockData.metadata.metadata[2])[i] !== 0) {
|
||||
console.log('INVALID TRANSACTION');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const options = {
|
||||
type: 'full',
|
||||
startBlock: 1
|
||||
};
|
||||
await network.addBlockListener(listener, options);
|
||||
while (!finished) {
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
// Disconnect from the gateway after Promise is resolved.
|
||||
// ... do other things
|
||||
}
|
||||
gateway.disconnect();
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error: ', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
void main();
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Copyright IBM Corp. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* This application has 6 basic steps:
|
||||
* 1. Select an identity from a wallet
|
||||
* 2. Connect to network gateway
|
||||
* 3. Access PaperNet network
|
||||
* 4. Construct request to transfer commercial paper
|
||||
* 5. Submit transaction
|
||||
* 6. Process response
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Bring key classes into scope, most importantly Fabric SDK network class
|
||||
const fs = require('fs');
|
||||
const yaml = require('js-yaml');
|
||||
const { Wallets, Gateway } = require('fabric-network');
|
||||
const CommercialPaper = require('../contract/lib/paper.js');
|
||||
|
||||
// Main program function
|
||||
async function main() {
|
||||
|
||||
// A wallet stores a collection of identities for use
|
||||
const wallet = await Wallets.newFileSystemWallet('../identity/user/isabella/wallet');
|
||||
|
||||
// A gateway defines the peers used to access Fabric networks
|
||||
const gateway = new Gateway();
|
||||
|
||||
// Main try/catch block
|
||||
try {
|
||||
|
||||
// Specify userName for network access
|
||||
// const userName = 'isabella.issuer@magnetocorp.com';
|
||||
const userName = 'isabella';
|
||||
|
||||
// Load connection profile; will be used to locate a gateway
|
||||
let connectionProfile = yaml.safeLoad(fs.readFileSync('../gateway/connection-org2.yaml', 'utf8'));
|
||||
|
||||
// Set connection options; identity and wallet
|
||||
let connectionOptions = {
|
||||
identity: userName,
|
||||
wallet: wallet,
|
||||
discovery: { enabled:true, asLocalhost: true }
|
||||
};
|
||||
|
||||
// Connect to gateway using application specified parameters
|
||||
console.log('Connect to Fabric gateway.');
|
||||
|
||||
await gateway.connect(connectionProfile, connectionOptions);
|
||||
|
||||
// Access PaperNet network
|
||||
console.log('Use network channel: mychannel.');
|
||||
|
||||
const network = await gateway.getNetwork('mychannel');
|
||||
|
||||
// Get addressability to commercial paper contract
|
||||
console.log('Use org.papernet.commercialpaper smart contract.');
|
||||
|
||||
const contract = await network.getContract('papercontract');
|
||||
|
||||
// transfer commercial paper
|
||||
console.log('Submit commercial paper transfer transaction.');
|
||||
|
||||
const transferResponse = await contract.submitTransaction('transfer', 'MagnetoCorp', '00001', 'DigiBank', 'Org1MSP', '2020-06-01');
|
||||
|
||||
// process response
|
||||
console.log('Process transfer transaction response.'+ transferResponse);
|
||||
|
||||
let paper = CommercialPaper.fromBuffer(transferResponse);
|
||||
|
||||
console.log(`commercial paper issued by ${paper.issuer} : ${paper.paperNumber} was successfully transferred`);
|
||||
console.log('Transaction complete.');
|
||||
|
||||
} catch (error) {
|
||||
|
||||
console.log(`Error processing transaction. ${error}`);
|
||||
console.log(error.stack);
|
||||
|
||||
} finally {
|
||||
|
||||
// Disconnect from the gateway
|
||||
console.log('Disconnect from Fabric gateway.');
|
||||
gateway.disconnect();
|
||||
|
||||
}
|
||||
}
|
||||
main().then(() => {
|
||||
|
||||
console.log('Transfer program complete.');
|
||||
|
||||
}).catch((e) => {
|
||||
|
||||
console.log('Transfer program exception.');
|
||||
console.log(e);
|
||||
console.log(e.stack);
|
||||
process.exit(-1);
|
||||
|
||||
});
|
||||
|
|
@ -38,9 +38,11 @@ class State {
|
|||
return this.currentState;
|
||||
}
|
||||
|
||||
serialize() {
|
||||
// not used
|
||||
/* serialize() {
|
||||
|
||||
return State.serialize(this);
|
||||
}
|
||||
} */
|
||||
|
||||
/**
|
||||
* Convert object to buffer containing JSON data serialization
|
||||
|
|
@ -49,6 +51,8 @@ class State {
|
|||
* @return {buffer} buffer with the data to store
|
||||
*/
|
||||
static serialize(object) {
|
||||
// don't write the key:value passed in - we already have a real composite key, issuer and paper Number.
|
||||
delete object.key;
|
||||
return Buffer.from(JSON.stringify(object));
|
||||
}
|
||||
|
||||
|
|
@ -88,7 +92,8 @@ class State {
|
|||
* @param (String[]) keyParts
|
||||
*/
|
||||
static makeKey(keyParts) {
|
||||
return keyParts.map(part => JSON.stringify(part)).join(':');
|
||||
// return keyParts.map(part => JSON.stringify(part)).join(':');
|
||||
return keyParts.map(part => part).join(':');
|
||||
}
|
||||
|
||||
static splitKey(key){
|
||||
|
|
|
|||
|
|
@ -12,8 +12,9 @@ const State = require('./../ledger-api/state.js');
|
|||
// Enumerate commercial paper state values
|
||||
const cpState = {
|
||||
ISSUED: 1,
|
||||
TRADING: 2,
|
||||
REDEEMED: 3
|
||||
PENDING: 2,
|
||||
TRADING: 3,
|
||||
REDEEMED: 4
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -42,6 +43,14 @@ class CommercialPaper extends State {
|
|||
return this.owner;
|
||||
}
|
||||
|
||||
setOwnerMSP(mspid) {
|
||||
this.mspid = mspid;
|
||||
}
|
||||
|
||||
getOwnerMSP() {
|
||||
return this.mspid;
|
||||
}
|
||||
|
||||
setOwner(newOwner) {
|
||||
this.owner = newOwner;
|
||||
}
|
||||
|
|
@ -61,6 +70,10 @@ class CommercialPaper extends State {
|
|||
this.currentState = cpState.REDEEMED;
|
||||
}
|
||||
|
||||
setPending() {
|
||||
this.currentState = cpState.PENDING;
|
||||
}
|
||||
|
||||
isIssued() {
|
||||
return this.currentState === cpState.ISSUED;
|
||||
}
|
||||
|
|
@ -73,6 +86,10 @@ class CommercialPaper extends State {
|
|||
return this.currentState === cpState.REDEEMED;
|
||||
}
|
||||
|
||||
isPending() {
|
||||
return this.currentState === cpState.PENDING;
|
||||
}
|
||||
|
||||
static fromBuffer(buffer) {
|
||||
return CommercialPaper.deserialize(buffer);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ const { Contract, Context } = require('fabric-contract-api');
|
|||
// PaperNet specifc classes
|
||||
const CommercialPaper = require('./paper.js');
|
||||
const PaperList = require('./paperlist.js');
|
||||
const QueryUtils = require('./queries.js');
|
||||
|
||||
/**
|
||||
* A custom context provides easy access to list of all commercial papers
|
||||
|
|
@ -67,12 +68,16 @@ class CommercialPaperContract extends Contract {
|
|||
async issue(ctx, issuer, paperNumber, issueDateTime, maturityDateTime, faceValue) {
|
||||
|
||||
// create an instance of the paper
|
||||
let paper = CommercialPaper.createInstance(issuer, paperNumber, issueDateTime, maturityDateTime, faceValue);
|
||||
let paper = CommercialPaper.createInstance(issuer, paperNumber, issueDateTime, maturityDateTime, parseInt(faceValue));
|
||||
|
||||
// Smart contract, rather than paper, moves paper into ISSUED state
|
||||
paper.setIssued();
|
||||
|
||||
// Newly issued paper is owned by the issuer
|
||||
// save the owner's MSP
|
||||
let mspid = ctx.clientIdentity.getMSPID();
|
||||
paper.setOwnerMSP(mspid);
|
||||
|
||||
// Newly issued paper is owned by the issuer to begin with (recorded for reporting purposes)
|
||||
paper.setOwner(issuer);
|
||||
|
||||
// Add the paper to the list of all similar commercial papers in the ledger world state
|
||||
|
|
@ -85,14 +90,14 @@ class CommercialPaperContract extends Contract {
|
|||
/**
|
||||
* Buy commercial paper
|
||||
*
|
||||
* @param {Context} ctx the transaction context
|
||||
* @param {String} issuer commercial paper issuer
|
||||
* @param {Integer} paperNumber paper number for this issuer
|
||||
* @param {String} currentOwner current owner of paper
|
||||
* @param {String} newOwner new owner of paper
|
||||
* @param {Integer} price price paid for this paper
|
||||
* @param {String} purchaseDateTime time paper was purchased (i.e. traded)
|
||||
*/
|
||||
* @param {Context} ctx the transaction context
|
||||
* @param {String} issuer commercial paper issuer
|
||||
* @param {Integer} paperNumber paper number for this issuer
|
||||
* @param {String} currentOwner current owner of paper
|
||||
* @param {String} newOwner new owner of paper
|
||||
* @param {Integer} price price paid for this paper // transaction input - not written to asset
|
||||
* @param {String} purchaseDateTime time paper was purchased (i.e. traded) // transaction input - not written to asset
|
||||
*/
|
||||
async buy(ctx, issuer, paperNumber, currentOwner, newOwner, price, purchaseDateTime) {
|
||||
|
||||
// Retrieve the current paper using key fields provided
|
||||
|
|
@ -101,10 +106,10 @@ class CommercialPaperContract extends Contract {
|
|||
|
||||
// Validate current owner
|
||||
if (paper.getOwner() !== currentOwner) {
|
||||
throw new Error('Paper ' + issuer + paperNumber + ' is not owned by ' + currentOwner);
|
||||
throw new Error('\nPaper ' + issuer + paperNumber + ' is not owned by ' + currentOwner);
|
||||
}
|
||||
|
||||
// First buy moves state from ISSUED to TRADING
|
||||
// First buy moves state from ISSUED to TRADING (when running )
|
||||
if (paper.isIssued()) {
|
||||
paper.setTrading();
|
||||
}
|
||||
|
|
@ -112,8 +117,11 @@ class CommercialPaperContract extends Contract {
|
|||
// Check paper is not already REDEEMED
|
||||
if (paper.isTrading()) {
|
||||
paper.setOwner(newOwner);
|
||||
// save the owner's MSP
|
||||
let mspid = ctx.clientIdentity.getMSPID();
|
||||
paper.setOwnerMSP(mspid);
|
||||
} else {
|
||||
throw new Error('Paper ' + issuer + paperNumber + ' is not trading. Current state = ' +paper.getCurrentState());
|
||||
throw new Error('\nPaper ' + issuer + paperNumber + ' is not trading. Current state = ' + paper.getCurrentState());
|
||||
}
|
||||
|
||||
// Update the paper
|
||||
|
|
@ -121,6 +129,79 @@ class CommercialPaperContract extends Contract {
|
|||
return paper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Buy request: (2-phase confirmation: Commercial paper is 'PENDING' subject to completion of transfer by owning org)
|
||||
* Alternative to 'buy' transaction
|
||||
* Note: 'buy_request' puts paper in 'PENDING' state - subject to transfer confirmation [below].
|
||||
*
|
||||
* @param {Context} ctx the transaction context
|
||||
* @param {String} issuer commercial paper issuer
|
||||
* @param {Integer} paperNumber paper number for this issuer
|
||||
* @param {String} currentOwner current owner of paper
|
||||
* @param {String} newOwner new owner of paper // transaction input - not written to asset per se - but written to block
|
||||
* @param {Integer} price price paid for this paper // transaction input - not written to asset per se - but written to block
|
||||
* @param {String} purchaseDateTime time paper was requested // transaction input - ditto.
|
||||
*/
|
||||
async buy_request(ctx, issuer, paperNumber, currentOwner, newOwner, price, purchaseDateTime) {
|
||||
|
||||
|
||||
// Retrieve the current paper using key fields provided
|
||||
let paperKey = CommercialPaper.makeKey([issuer, paperNumber]);
|
||||
let paper = await ctx.paperList.getPaper(paperKey);
|
||||
|
||||
// Validate current owner - this is really information for the user trying the sample, rather than any 'authorisation' check per se FYI
|
||||
if (paper.getOwner() !== currentOwner) {
|
||||
throw new Error('\nPaper ' + issuer + paperNumber + ' is not owned by ' + currentOwner + ' provided as a paraneter');
|
||||
}
|
||||
// paper set to 'PENDING' - can only be transferred (confirmed) by identity from owning org (MSP check).
|
||||
paper.setPending();
|
||||
|
||||
// Update the paper
|
||||
await ctx.paperList.updatePaper(paper);
|
||||
return paper;
|
||||
}
|
||||
|
||||
/**
|
||||
* transfer commercial paper: only the owning org has authority to execute. It is the complement to the 'buy_request' transaction. '[]' is optional below.
|
||||
* eg. issue -> buy_request -> transfer -> [buy ...n | [buy_request...n | transfer ...n] ] -> redeem
|
||||
* this transaction 'pair' is an alternative to the straight issue -> buy -> [buy....n] -> redeem ...path
|
||||
*
|
||||
* @param {Context} ctx the transaction context
|
||||
* @param {String} issuer commercial paper issuer
|
||||
* @param {Integer} paperNumber paper number for this issuer
|
||||
* @param {String} newOwner new owner of paper
|
||||
* @param {String} newOwnerMSP MSP id of the transferee
|
||||
* @param {String} confirmDateTime confirmed transfer date.
|
||||
*/
|
||||
async transfer(ctx, issuer, paperNumber, newOwner, newOwnerMSP, confirmDateTime) {
|
||||
|
||||
// Retrieve the current paper using key fields provided
|
||||
let paperKey = CommercialPaper.makeKey([issuer, paperNumber]);
|
||||
let paper = await ctx.paperList.getPaper(paperKey);
|
||||
|
||||
// Validate current owner's MSP in the paper === invoking transferor's MSP id - can only transfer if you are the owning org.
|
||||
|
||||
if (paper.getOwnerMSP() !== ctx.clientIdentity.getMSPID()) {
|
||||
throw new Error('\nPaper ' + issuer + paperNumber + ' is not owned by the current invoking Organisation, and not authorised to transfer');
|
||||
}
|
||||
|
||||
// Paper needs to be 'pending' - which means you need to have run 'buy_pending' transaction first.
|
||||
if ( ! paper.isPending()) {
|
||||
throw new Error('\nPaper ' + issuer + paperNumber + ' is not currently in state: PENDING for transfer to occur: \n must run buy_request transaction first');
|
||||
}
|
||||
// else all good
|
||||
|
||||
paper.setOwner(newOwner);
|
||||
// set the MSP of the transferee (so that, that org may also pass MSP check, if subsequently transferred/sold on)
|
||||
paper.setOwnerMSP(newOwnerMSP);
|
||||
paper.setTrading();
|
||||
paper.confirmDateTime = confirmDateTime;
|
||||
|
||||
// Update the paper
|
||||
await ctx.paperList.updatePaper(paper);
|
||||
return paper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redeem commercial paper
|
||||
*
|
||||
|
|
@ -128,31 +209,131 @@ class CommercialPaperContract extends Contract {
|
|||
* @param {String} issuer commercial paper issuer
|
||||
* @param {Integer} paperNumber paper number for this issuer
|
||||
* @param {String} redeemingOwner redeeming owner of paper
|
||||
* @param {String} issuingOwnerMSP the MSP of the org that the paper will be redeemed with.
|
||||
* @param {String} redeemDateTime time paper was redeemed
|
||||
*/
|
||||
async redeem(ctx, issuer, paperNumber, redeemingOwner, redeemDateTime) {
|
||||
async redeem(ctx, issuer, paperNumber, redeemingOwner, issuingOwnerMSP, redeemDateTime) {
|
||||
|
||||
let paperKey = CommercialPaper.makeKey([issuer, paperNumber]);
|
||||
|
||||
let paper = await ctx.paperList.getPaper(paperKey);
|
||||
|
||||
// Check paper is not REDEEMED
|
||||
// Check paper is not alread in a state of REDEEMED
|
||||
if (paper.isRedeemed()) {
|
||||
throw new Error('Paper ' + issuer + paperNumber + ' already redeemed');
|
||||
throw new Error('\nPaper ' + issuer + paperNumber + ' has already been redeemed');
|
||||
}
|
||||
|
||||
// Verify that the redeemer owns the commercial paper before redeeming it
|
||||
// Validate current redeemer's MSP matches the invoking redeemer's MSP id - can only redeem if you are the owning org.
|
||||
|
||||
if (paper.getOwnerMSP() !== ctx.clientIdentity.getMSPID()) {
|
||||
throw new Error('\nPaper ' + issuer + paperNumber + ' cannot be redeemed by ' + ctx.clientIdentity.getMSPID() + ', as it is not the authorised owning Organisation');
|
||||
}
|
||||
|
||||
// As this is just a sample, can show additional verification check: that the redeemer provided matches that on record, before redeeming it
|
||||
if (paper.getOwner() === redeemingOwner) {
|
||||
paper.setOwner(paper.getIssuer());
|
||||
paper.setOwnerMSP(issuingOwnerMSP);
|
||||
paper.setRedeemed();
|
||||
paper.redeemDateTime = redeemDateTime; // record redemption date against the asset (the complement to 'issue date')
|
||||
} else {
|
||||
throw new Error('Redeeming owner does not own paper' + issuer + paperNumber);
|
||||
throw new Error('\nRedeeming owner: ' + redeemingOwner + ' organisation does not currently own paper: ' + issuer + paperNumber);
|
||||
}
|
||||
|
||||
await ctx.paperList.updatePaper(paper);
|
||||
return paper;
|
||||
}
|
||||
|
||||
// Query transactions
|
||||
|
||||
/**
|
||||
* Query history of a commercial paper
|
||||
* @param {Context} ctx the transaction context
|
||||
* @param {String} issuer commercial paper issuer
|
||||
* @param {Integer} paperNumber paper number for this issuer
|
||||
*/
|
||||
async queryHistory(ctx, issuer, paperNumber) {
|
||||
|
||||
// Get a key to be used for History query
|
||||
|
||||
let query = new QueryUtils(ctx, 'org.papernet.paper');
|
||||
let results = await query.getAssetHistory(issuer, paperNumber); // (cpKey);
|
||||
return results;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* queryOwner commercial paper: supply name of owning org, to find list of papers based on owner field
|
||||
* @param {Context} ctx the transaction context
|
||||
* @param {String} owner commercial paper owner
|
||||
*/
|
||||
async queryOwner(ctx, owner) {
|
||||
|
||||
let query = new QueryUtils(ctx, 'org.papernet.paper');
|
||||
let owner_results = await query.queryKeyByOwner(owner);
|
||||
|
||||
return owner_results;
|
||||
}
|
||||
|
||||
/**
|
||||
* queryPartial commercial paper - provide a prefix eg. "DigiBank" will list all papers _issued_ by DigiBank etc etc
|
||||
* @param {Context} ctx the transaction context
|
||||
* @param {String} prefix asset class prefix (added to paperlist namespace) eg. org.papernet.paperMagnetoCorp asset listing: papers issued by MagnetoCorp.
|
||||
*/
|
||||
async queryPartial(ctx, prefix) {
|
||||
|
||||
let query = new QueryUtils(ctx, 'org.papernet.paper');
|
||||
let partial_results = await query.queryKeyByPartial(prefix);
|
||||
|
||||
return partial_results;
|
||||
}
|
||||
|
||||
/**
|
||||
* queryAdHoc commercial paper - supply a custom mango query
|
||||
* eg - as supplied as a param:
|
||||
* ex1: ["{\"selector\":{\"faceValue\":{\"$lt\":8000000}}}"]
|
||||
* ex2: ["{\"selector\":{\"faceValue\":{\"$gt\":4999999}}}"]
|
||||
*
|
||||
* @param {Context} ctx the transaction context
|
||||
* @param {String} queryString querystring
|
||||
*/
|
||||
async queryAdhoc(ctx, queryString) {
|
||||
|
||||
let query = new QueryUtils(ctx, 'org.papernet.paper');
|
||||
let querySelector = JSON.parse(queryString);
|
||||
let adhoc_results = await query.queryByAdhoc(querySelector);
|
||||
|
||||
return adhoc_results;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* queryNamed - supply named query - 'case' statement chooses selector to build (pre-canned for demo purposes)
|
||||
* @param {Context} ctx the transaction context
|
||||
* @param {String} queryname the 'named' query (built here) - or - the adHoc query string, provided as a parameter
|
||||
*/
|
||||
async queryNamed(ctx, queryname) {
|
||||
let querySelector = {};
|
||||
switch (queryname) {
|
||||
case "redeemed":
|
||||
querySelector = { "selector": { "currentState": 4 } }; // 4 = redeemd state
|
||||
break;
|
||||
case "trading":
|
||||
querySelector = { "selector": { "currentState": 3 } }; // 3 = trading state
|
||||
break;
|
||||
case "value":
|
||||
// may change to provide as a param - fixed value for now in this sample
|
||||
querySelector = { "selector": { "faceValue": { "$gt": 4000000 } } }; // to test, issue CommPapers with faceValue <= or => this figure.
|
||||
break;
|
||||
default: // else, unknown named query
|
||||
throw new Error('invalid named query supplied: ' + queryname + '- please try again ');
|
||||
}
|
||||
|
||||
let query = new QueryUtils(ctx, 'org.papernet.paper');
|
||||
let adhoc_results = await query.queryByAdhoc(querySelector);
|
||||
|
||||
return adhoc_results;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = CommercialPaperContract;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ const CommercialPaper = require('./paper.js');
|
|||
class PaperList extends StateList {
|
||||
|
||||
constructor(ctx) {
|
||||
super(ctx, 'org.papernet.commercialpaperlist');
|
||||
super(ctx, 'org.papernet.paper');
|
||||
this.use(CommercialPaper);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,215 @@
|
|||
|
||||
/*
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const State = require('../ledger-api/state.js');
|
||||
//const CommercialPaper = require('./paper.js');
|
||||
/**
|
||||
* Query Class for query functions such as history etc
|
||||
*
|
||||
*/
|
||||
class QueryUtils {
|
||||
|
||||
constructor(ctx, listName) {
|
||||
this.ctx = ctx;
|
||||
this.name = listName;
|
||||
//this.supportedTypes = {};
|
||||
}
|
||||
|
||||
// =========================================================================================
|
||||
// getAssetHistory takes the composite key as arg, gets returns results as JSON to 'main contract'
|
||||
// =========================================================================================
|
||||
/**
|
||||
* Get Asset History for a commercial paper
|
||||
* @param {String} issuer the CP issuer
|
||||
* @param {String} paperNumber commercial paper number
|
||||
*/
|
||||
async getAssetHistory(issuer, paperNumber) {
|
||||
|
||||
let ledgerKey = await this.ctx.stub.createCompositeKey(this.name, [issuer, paperNumber]);
|
||||
const resultsIterator = await this.ctx.stub.getHistoryForKey(ledgerKey);
|
||||
let results = await this.getAllResults(resultsIterator, true);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// ===========================================================================================
|
||||
// queryKeyByPartial performs a partial query based on the namespace and asset key prefix provided
|
||||
|
||||
// Read-only function results are not typically submitted to ordering. If the read-only
|
||||
// results are submitted to ordering, or if the query is used in an update transaction
|
||||
// and submitted to ordering, then the committing peers will re-execute to guarantee that
|
||||
// result sets are stable between endorsement time and commit time. The transaction is
|
||||
// invalidated by the committing peers if the result set has changed between endorsement
|
||||
// time and commit time.
|
||||
//
|
||||
// ===========================================================================================
|
||||
/**
|
||||
* queryOwner commercial paper
|
||||
* @param {String} assetspace the asset space (eg MagnetoCorp's assets)
|
||||
*/
|
||||
async queryKeyByPartial(assetspace) {
|
||||
|
||||
if (arguments.length < 1) {
|
||||
throw new Error('Incorrect number of arguments. Expecting 1');
|
||||
}
|
||||
// ie namespace + prefix to assets etc eg
|
||||
// "Key":"org.papernet.paperMagnetoCorp0001" (0002, etc)
|
||||
// "Partial":'org.papernet.paperlistMagnetoCorp"' (using partial key, find keys "0001", "0002" etc)
|
||||
const resultsIterator = await this.ctx.stub.getStateByPartialCompositeKey(this.name, [assetspace]);
|
||||
let method = this.getAllResults;
|
||||
let results = await method(resultsIterator, false);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
// ===== Example: Parameterized rich query =================================================
|
||||
// queryKeyByOwner queries for assets based on a passed in owner.
|
||||
// This is an example of a parameterized query accepting a single query parameter (owner).
|
||||
// Only available on state databases that support rich query (e.g. CouchDB)
|
||||
// =========================================================================================
|
||||
/**
|
||||
* queryKeyByOwner commercial paper
|
||||
* @param {String} owner commercial paper owner
|
||||
*/
|
||||
async queryKeyByOwner(owner) {
|
||||
//
|
||||
let self = this;
|
||||
if (arguments.length < 1) {
|
||||
throw new Error('Incorrect number of arguments. Expecting owner name.');
|
||||
}
|
||||
let queryString = {};
|
||||
queryString.selector = {};
|
||||
// queryString.selector.docType = 'indexOwnerDoc';
|
||||
queryString.selector.owner = owner;
|
||||
// set to (eg) '{selector:{owner:MagnetoCorp}}'
|
||||
let method = self.getQueryResultForQueryString;
|
||||
let queryResults = await method(this.ctx, self, JSON.stringify(queryString));
|
||||
return queryResults;
|
||||
}
|
||||
|
||||
// ===== Example: Ad hoc rich query ========================================================
|
||||
// queryAdhoc uses a query string to perform a query for marbles..
|
||||
// Query string matching state database syntax is passed in and executed as is.
|
||||
// Supports ad hoc queries that can be defined at runtime by the client.
|
||||
// If this is not desired, follow the queryKeyByOwner example for parameterized queries.
|
||||
// Only available on state databases that support rich query (e.g. CouchDB)
|
||||
// example passed using VS Code ext: ["{\"selector\": {\"owner\": \"MagnetoCorp\"}}"]
|
||||
// =========================================================================================
|
||||
/**
|
||||
* query By AdHoc string (commercial paper)
|
||||
* @param {String} queryString actual MangoDB query string (escaped)
|
||||
*/
|
||||
async queryByAdhoc(queryString) {
|
||||
|
||||
if (arguments.length < 1) {
|
||||
throw new Error('Incorrect number of arguments. Expecting ad-hoc string, which gets stringified for mango query');
|
||||
}
|
||||
let self = this;
|
||||
|
||||
if (!queryString) {
|
||||
throw new Error('queryString must not be empty');
|
||||
}
|
||||
let method = self.getQueryResultForQueryString;
|
||||
let queryResults = await method(this.ctx, self, JSON.stringify(queryString));
|
||||
return queryResults;
|
||||
}
|
||||
|
||||
// WORKER functions are below this line: these are called by the above functions, where iterator is passed in
|
||||
|
||||
// =========================================================================================
|
||||
// getQueryResultForQueryString woerk function executes the passed-in query string.
|
||||
// Result set is built and returned as a byte array containing the JSON results.
|
||||
// =========================================================================================
|
||||
/**
|
||||
* Function getQueryResultForQueryString
|
||||
* @param {Context} ctx the transaction context
|
||||
* @param {any} self within scope passed in
|
||||
* @param {String} the query string created prior to calling this fn
|
||||
*/
|
||||
async getQueryResultForQueryString(ctx, self, queryString) {
|
||||
|
||||
// console.log('- getQueryResultForQueryString queryString:\n' + queryString);
|
||||
|
||||
const resultsIterator = await ctx.stub.getQueryResult(queryString);
|
||||
let results = await self.getAllResults(resultsIterator, false);
|
||||
|
||||
return results;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Function getAllResults
|
||||
* @param {resultsIterator} iterator within scope passed in
|
||||
* @param {Boolean} isHistory query string created prior to calling this fn
|
||||
*/
|
||||
async getAllResults(iterator, isHistory) {
|
||||
let allResults = [];
|
||||
let res = { done: false, value: null };
|
||||
|
||||
while (true) {
|
||||
res = await iterator.next();
|
||||
let jsonRes = {};
|
||||
if (res.value && res.value.value.toString()) {
|
||||
if (isHistory && isHistory === true) {
|
||||
//jsonRes.TxId = res.value.tx_id;
|
||||
jsonRes.TxId = res.value.txId;
|
||||
jsonRes.Timestamp = res.value.timestamp;
|
||||
jsonRes.Timestamp = new Date((res.value.timestamp.seconds.low * 1000));
|
||||
let ms = res.value.timestamp.nanos / 1000000;
|
||||
jsonRes.Timestamp.setMilliseconds(ms);
|
||||
if (res.value.is_delete) {
|
||||
jsonRes.IsDelete = res.value.is_delete.toString();
|
||||
} else {
|
||||
try {
|
||||
jsonRes.Value = JSON.parse(res.value.value.toString('utf8'));
|
||||
// report the commercial paper states during the asset lifecycle, just for asset history reporting
|
||||
switch (jsonRes.Value.currentState) {
|
||||
case 1:
|
||||
jsonRes.Value.currentState = 'ISSUED';
|
||||
break;
|
||||
case 2:
|
||||
jsonRes.Value.currentState = 'PENDING';
|
||||
break;
|
||||
case 3:
|
||||
jsonRes.Value.currentState = 'TRADING';
|
||||
break;
|
||||
case 4:
|
||||
jsonRes.Value.currentState = 'REDEEMED';
|
||||
break;
|
||||
default: // else, unknown named query
|
||||
jsonRes.Value.currentState = 'UNKNOWN';
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
jsonRes.Value = res.value.value.toString('utf8');
|
||||
}
|
||||
}
|
||||
} else { // non history query ..
|
||||
jsonRes.Key = res.value.key;
|
||||
try {
|
||||
jsonRes.Record = JSON.parse(res.value.value.toString('utf8'));
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
jsonRes.Record = res.value.value.toString('utf8');
|
||||
}
|
||||
}
|
||||
allResults.push(jsonRes);
|
||||
}
|
||||
// check to see if we have reached the end
|
||||
if (res.done) {
|
||||
// explicitly close the iterator
|
||||
console.log('iterator is done');
|
||||
await iterator.close();
|
||||
return allResults;
|
||||
}
|
||||
|
||||
} // while true
|
||||
}
|
||||
|
||||
}
|
||||
module.exports = QueryUtils;
|
||||
Loading…
Reference in a new issue