[FAB-11951] Interest-rate swap example for SBE

This is an example on how to represent and implement basic interest
rate swap handling using fabric. It demonstrates the
usage of state-based endorsement.

Change-Id: I04e631299d95262e54e1532489766aa20477064c
Signed-off-by: Matthias Neugschwandtner <eug@zurich.ibm.com>
Signed-off-by: Alessandro Sorniotti <ale.linux@sopit.net>
Signed-off-by: David Enyeart <enyeart@us.ibm.com>
This commit is contained in:
Matthias Neugschwandtner 2018-09-11 18:17:42 +02:00 committed by David Enyeart
parent 9567985d29
commit 5cd277fdc0
7 changed files with 1258 additions and 0 deletions

View file

@ -0,0 +1,176 @@
# Interest-rate swaps
This is a sample of how interest-rate swaps can be handled on a blockchain using
fabric and state-based endorsement. [State-based endorsement](https://hyperledger-fabric.readthedocs.io/en/release-1.3/endorsement-policies.html#key-level-endorsement)
is a new feature released in Hyperledger Fabric 1.3.
An interest-rate swap is a financial swap traded over the counter. It is a
contractual agreement between two parties, where two parties (A and B) exchange
payments. The amount of individual payments is based on the principal amount of the
swap and an interest rate. The interest rates of the two parties differ. In a
typical scenario, one payment (A to B) is based on a fixed rate set in the
contract. The other payment (B to A) is based on a floating rate. This rate is
defined through a reference rate, such as LIBOR from LSE, and an offset to this
rate.
## Network
We assume organizations of the following roles participate in our network:
* Parties that want to exchange payments
* Parties that provide reference rates
* Auditors that need to audit certain swaps
The chaincode-level endorsement policy is set to require an endorsement from an
auditor as well as an endorsement from any swap participant.
## Data model
We represent a swap on the ledger as a JSON with the following fields:
* `StartDate` and `EndDate` of the swap
* `PaymentInterval` - the time interval of the payments
* `PrincipalAmount` - the principal amount of the swap
* `FixedRate` - the fixed rate of the swap
* `FloatingRate` - the floating rate of the swap (offset to the reference rate)
* `ReferenceRate` - the key name of the KVS pair that holds the reference rate
The key for the swap is a unique identifier combined with a common prefix `swap`
that identifies swap entries in the KVS namespace. Upon creation the key-level
endorsement policy for the swap is set to the participants of the swap and,
potentially, an auditor.
We represent the payment information as a single KVS entry per swap with the
same unique identifier as the swap itself and a common prefix `payment` for payments.
If payments are due, the entry states the amount due. Otherwise, it is "none".
A payment information KVS entry has the same key-level endorsement policy
set as its corresponding swap entry.
We represent the reference rates as a KVS entry per rate with an identifier per
rate and a common prefix for reference rates. The key-level endorsement policy
for a reference rate entry is set to the provider of the corresponding reference
rate, such as LSE for LIBOR.
The reference rate could also be modeled via a separate chaincode, where the
chaincode-level endorsement policies only allows reference rate providers to
create keys.
Taken together, here is an example of the KVS entries involved in a swap:
```
KEY | VALUE
-------------|-----------------------------------------------------
swap1 | {StartDate: 2018-10-01, ..., ReferenceRate: "libor"}
payment1 | "none"
rr_libor | 0.27
```
In this example, the swap with ID 1 is represented by the `swap1` and `payment1`
KVS entries. The reference rate is set to `libor`, which will cause the chaincode
to look up the `rr_libor` entry in the KVS to calculate the rate for the
floating leg of the swap.
## Chaincode
The interest-rate swap chaincode provides the following API:
* `createSwap(swapID, swap_info, partyA, partyB)` - create a new swap with the
given identifier and swap parameters among the two parties specified. This
function creates the entry for the swap and the corresponding payment. It
also sets the key-level endorsement policies for both keys to the participants
to the swap. In case the swap's prinicpal amount exceeds a certain threshold,
it adds an auditor to the endorsement policy for the keys.
* `calculatePayment(swapID)` - calculate the net payment from party A to party
B and set the payment entry accordingly. If the payment information is negative,
the payment due flows from B to A. The payment information is calculated based
on the rates specified in the swap and the principal amount. If the payment
key is not "none", this function returns an error, indicating that a prior
payment has not been settled yet.
* `settlePayment(swapID)` - set the payment entry for the given swap ID to "none".
This function is supposed to be invoked after the two parties have settled the
payment off-chain.
* `setReferenceRate(rrID, value)` - set a given reference rate to a given value.
* `Init(auditor, threshold, rrProviders...)` - the chaincode namespace is initialized
with a threshold for the principal amount above which a designated auditor
needs to be involved as well as a list of reference rate providers and rate IDs.
## Trust model
The state-based endorsement policies used in this sample ensure the following
trust model:
* All operations related to a specific swap need to be endorsed (at least) by
the participants to that swap. This includes both creation of a swap, as well
as calculating the payment information and agreeing that the payments have
been settled.
* Operations related to a reference rate need to be endorsed by the provider of
a reference rate.
* Under certain circumstances an auditor needs to endorse operations for a swap,
e.g., if it exceeds a threshold for the principal amount.
The chaincode-level endorsement policy requires at least one potential swap
participant and an auditor. This endorsement policy sets the trust relationship
for creating a swap.
## Sample network
The `network` subdirectory contains scripts that will launch a sample network
and run a swap transaction flow from creation to settlement.
### Prerequesites
The following prerequisites are needed to run this sample:
* Fabric docker images. By default the `network/network.sh` script will look for
fabric images with the `latest` tag, this can be adapted with the `-i` command
line parameter of the script.
* A local installation of `configtxgen` and `cryptogen` in the `PATH` environment,
or included in `fabric-samples/bin` directory.
* Vendoring the chaincode. In the chaincode directory, run `govendor init` and
`govendor add +external` to vendor the shim from your local copy of fabric.
### Bringing up the network
Simply run `network.sh up` to bring up the network. This will spawn docker
containers running a network of 3 "regular" organizations, one auditor
organization and one reference rate provider as well as a solo orderer.
An additional CLI container will run `network/scripts/script.sh` to join the
peers to the `irs` channel and deploy the chaincode. In the init parameters it
supplies the audit threshold, the auditor organization and the reference rate
provider with the corresponding reference rate ID. In the following transactions
it sets the reference rate, creates a swap, calculates payment information for
the swap and marks them as settled afterwards. We will show the corresponding
commands in the following section.
### Transactions
The chaincode is instantiated as follows:
```
peer chaincode instantiate -o irs-orderer:7050 -C irs -n irscc -l golang -v 0 -c '{"Args":["init","auditor","100000","rrprovider","myrr"]}' -P "AND(OR('partya.peer','partyb.peer','partyc.peer'), 'auditor.peer')"
```
This sets an auditing threshold of 1M, above which the `auditor` organization
needs to be involved. It also specifies the `myrr` reference rate provided by
the `rrprovider` organization.
To set a reference rate:
```
peer chaincode invoke -o irs-orderer:7050 -C irs --waitForEvent -n irscc --peerAddresses irs-rrprovider:7051 -c '{"Args":["setReferenceRate","myrr","3"]}'
```
Note that the transaction is endorsed by a peer of the organization we have
specified as providing this reference rate in the init parameters.
To create a swap named "myswap":
```
peer chaincode invoke -o irs-orderer:7050 -C irs --waitForEvent -n irscc --peerAddresses irs-partya:7051 --peerAddresses irs-partyb:7051 --peerAddresses irs-auditor:7051 -c '{"Args":["createSwap","myswap","{\"StartDate\":\"2018-09-27T15:04:05Z\",\"EndDate\":\"2018-09-30T15:04:05Z\",\"PaymentInterval\":365,\"PrincipalAmount\":10000000,\"FixedRate\":4,\"FloatingRate\":5,\"ReferenceRate\":\"myrr\"}", "partya", "partyb"]}'
```
Note that the transaction is endorsed by both parties that are part of this
swap as well as the auditor. Since the principal amount in this case is lower
than the audit threshold we set as init parameters, no auditor will be required
to endorse changes to the payment info or swap details.
To calculate payment info for "myswap":
```
peer chaincode invoke -o irs-orderer:7050 -C irs --waitForEvent -n irscc --peerAddresses irs-partya:7051 --peerAddresses irs-partyb:7051 -c '{"Args":["calculatePayment","myswap"]}'
```
Note that we target only peers of
party A and party B, since the swap is below the auditing threshold.
To settle payment of "myswap":
```
peer chaincode invoke -o irs-orderer:7050 -C irs --waitForEvent -n irscc `--peerAddresses irs-partya:7051 --peerAddresses irs-partyb:7051 -c '{"Args":["settlePayment","myswap"]}'
```
As an exercise, try to create a new swap above the auditing threshold and see
how validation fails if the auditor is not involved in every operation on the
swap. Also try to calculate payment info before settling a prior payment to a
swap.

View file

@ -0,0 +1,313 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"encoding/json"
"fmt"
"strconv"
"time"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/core/chaincode/shim/ext/statebased"
pb "github.com/hyperledger/fabric/protos/peer"
)
/* InterestRateSwap represents an interest rate swap on the ledger
* The swap is active between its start- and end-date.
* At the specified interval, two parties A and B exchange the following payments:
* A->B (PrincipalAmount * FixedRateBPS) / 100
* B->A (PrincipalAmount * (ReferenceRateBPS + FloatingRateBPS)) / 100
* We represent rates as basis points, with one basis point being equal to 1/100th
* of 1% (see https://www.investopedia.com/terms/b/basispoint.asp)
*/
type InterestRateSwap struct {
StartDate time.Time
EndDate time.Time
PaymentInterval time.Duration
PrincipalAmount uint64
FixedRateBPS uint64
FloatingRateBPS uint64
ReferenceRate string
}
/*
SwapManager is the chaincode that handles interest rate swaps.
The chaincode endorsement policy includes an auditing organization.
It provides the following functions:
-) createSwap: create swap with participants
-) calculatePayment: calculate what needs to be paid
-) settlePayment: mark payment done
-) setReferenceRate: for providers to set the reference rate
The SwapManager stores three different kinds of information on the ledger:
-) the actual swap data ("swap" + ID)
-) the payment information ("payment" + ID), if "none", the payment has been settled
-) the reference rate ("rr" + ID)
*/
type SwapManager struct {
}
// Init callback
func (cc *SwapManager) Init(stub shim.ChaincodeStubInterface) pb.Response {
args := stub.GetArgs()
if len(args) < 5 {
return shim.Error("Insufficient number of arguments. Expected: <function> <auditor_MSPID> <audit_threshold> <rr_provider1_MSPID> <rr_provider1_rateID> ... <rr_providerN_MSPID> <rr_providerN_rateID>")
}
// set the limit above which the auditor needs to be involved, require it
// to be endorsed by the auditor
err := stub.PutState("audit_limit", args[2])
if err != nil {
return shim.Error(err.Error())
}
auditorEP, err := statebased.NewStateEP(nil)
if err != nil {
return shim.Error(err.Error())
}
err = auditorEP.AddOrgs(statebased.RoleTypePeer, string(args[1]))
if err != nil {
return shim.Error(err.Error())
}
epBytes, err := auditorEP.Policy()
if err != nil {
return shim.Error(err.Error())
}
err = stub.SetStateValidationParameter("audit_limit", epBytes)
if err != nil {
return shim.Error(err.Error())
}
// create the reference rates, require them to be endorsed by the provider
for i := 3; i+1 < len(args); i += 2 {
org := string(args[i])
rrID := "rr" + string(args[i+1])
err = stub.PutState(rrID, []byte("0"))
if err != nil {
return shim.Error(err.Error())
}
ep, err := statebased.NewStateEP(nil)
if err != nil {
return shim.Error(err.Error())
}
err = ep.AddOrgs(statebased.RoleTypePeer, org)
if err != nil {
return shim.Error(err.Error())
}
epBytes, err = ep.Policy()
if err != nil {
return shim.Error(err.Error())
}
err = stub.SetStateValidationParameter(rrID, epBytes)
if err != nil {
return shim.Error(err.Error())
}
}
return shim.Success([]byte{})
}
// Invoke dispatcher
func (cc *SwapManager) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
funcName, _ := stub.GetFunctionAndParameters()
if function, ok := functions[funcName]; ok {
fmt.Printf("Invoking %s\n", funcName)
return function(stub)
}
return shim.Error(fmt.Sprintf("Unknown function %s", funcName))
}
var functions = map[string]func(stub shim.ChaincodeStubInterface) pb.Response{
"createSwap": createSwap,
"calculatePayment": calculatePayment,
"settlePayment": settlePayment,
"setReferenceRate": setReferenceRate,
}
// Create a new swap among participants.
// The creation of the swap needs to be endorsed by the chaincode endorsement policy.
// Once created, the swap needs to be endorsed by its participants as well as the
// auditor in case the principal amount of the swap exceeds the audit threshold.
// This is enforced through the state-based endorsement policy that is set in this
// function.
// Parameters: swap ID, a JSONized InterestRateSwap, MSP ID of participant 1,
// MSP ID of participant 2
func createSwap(stub shim.ChaincodeStubInterface) pb.Response {
_, parameters := stub.GetFunctionAndParameters()
if len(parameters) != 4 {
return shim.Error("Wrong number of arguments supplied. Expected: <swap_ID> <interest_rate_swap_json> <participant1_MSPID> <participant2_MSPID>")
}
// create the swap
swapID := "swap" + string(parameters[0])
irsJSON := []byte(parameters[1])
var irs InterestRateSwap
err := json.Unmarshal(irsJSON, &irs)
if err != nil {
return shim.Error(err.Error())
}
err = stub.PutState(swapID, irsJSON)
if err != nil {
return shim.Error(err.Error())
}
// get the auditing threshold
auditLimit, err := stub.GetState("audit_limit")
if err != nil {
return shim.Error(err.Error())
}
threshold, err := strconv.Atoi(string(auditLimit))
if err != nil {
return shim.Error(err.Error())
}
// set endorsers
ep, err := statebased.NewStateEP(nil)
if err != nil {
return shim.Error(err.Error())
}
err = ep.AddOrgs(statebased.RoleTypePeer, parameters[2], parameters[3])
if err != nil {
return shim.Error(err.Error())
}
// if the swap principal amount exceeds the audit threshold set in init, the auditor needs to endorse as well
if irs.PrincipalAmount > uint64(threshold) {
fmt.Printf("Adding auditor for swap %s with prinicipal amount %v above threshold %v\n", parameters[0], irs.PrincipalAmount, uint64(threshold))
err = ep.AddOrgs(statebased.RoleTypePeer, "auditor")
if err != nil {
return shim.Error(err.Error())
}
}
// set the endorsement policy for the swap
epBytes, err := ep.Policy()
if err != nil {
return shim.Error(err.Error())
}
err = stub.SetStateValidationParameter(swapID, epBytes)
if err != nil {
return shim.Error(err.Error())
}
// create and set the key for the payment
paymentID := "payment" + string(parameters[0])
err = stub.PutState(paymentID, []byte("none"))
if err != nil {
return shim.Error(err.Error())
}
err = stub.SetStateValidationParameter(paymentID, epBytes)
if err != nil {
return shim.Error(err.Error())
}
return shim.Success([]byte{})
}
// Calculate the payment due for a given swap
func calculatePayment(stub shim.ChaincodeStubInterface) pb.Response {
_, parameters := stub.GetFunctionAndParameters()
if len(parameters) != 1 {
return shim.Error("Wrong number of arguments supplied. Expected: <swap_ID>")
}
// retrieve swap
swapID := "swap" + parameters[0]
irsJSON, err := stub.GetState(swapID)
if err != nil {
return shim.Error(err.Error())
}
if irsJSON == nil {
return shim.Error(fmt.Sprintf("Swap %s does not exist", parameters[0]))
}
var irs InterestRateSwap
err = json.Unmarshal(irsJSON, &irs)
if err != nil {
return shim.Error(err.Error())
}
// check if the previous payment has been settled
paymentID := "payment" + parameters[0]
paid, err := stub.GetState(paymentID)
if err != nil {
return shim.Error(err.Error())
}
if paid == nil {
return shim.Error("Unexpected error: payment entry is nil. This should not happen.")
}
if string(paid) != "none" {
return shim.Error("Previous payment has not been settled yet")
}
// get reference rate
referenceRateBytes, err := stub.GetState("rr" + irs.ReferenceRate)
if err != nil {
return shim.Error(err.Error())
}
if referenceRateBytes == nil {
return shim.Error(fmt.Sprintf("Reference rate %s not found", irs.ReferenceRate))
}
referenceRate, err := strconv.Atoi(string(referenceRateBytes))
if err != nil {
return shim.Error(err.Error())
}
// calculate payment
p1 := int((irs.PrincipalAmount * irs.FixedRateBPS) / 100)
p2 := int((irs.PrincipalAmount * (irs.FloatingRateBPS + uint64(referenceRate))) / 100)
payment := strconv.Itoa(p1 - p2)
err = stub.PutState(paymentID, []byte(payment))
if err != nil {
return shim.Error(err.Error())
}
return shim.Success([]byte(payment))
}
// Settle the payment for a given swap
func settlePayment(stub shim.ChaincodeStubInterface) pb.Response {
_, parameters := stub.GetFunctionAndParameters()
if len(parameters) != 1 {
return shim.Error("Wrong number of arguments supplied. Expected: <swap_ID>")
}
paymentID := "payment" + parameters[0]
paid, err := stub.GetState(paymentID)
if err != nil {
return shim.Error(err.Error())
}
if paid == nil {
return shim.Error("Unexpected error: payment entry is nil. This should not happen.")
}
if string(paid) == "none" {
return shim.Error("Payment has already been settled.")
}
err = stub.PutState(paymentID, []byte("none"))
if err != nil {
return shim.Error(err.Error())
}
return shim.Success([]byte{})
}
// Set the reference rate for a given rate provider
func setReferenceRate(stub shim.ChaincodeStubInterface) pb.Response {
_, parameters := stub.GetFunctionAndParameters()
if len(parameters) != 2 {
return shim.Error("Wrong number of arguments supplied. Expected: <reference_rate_ID> <reference_rate_BPS>")
}
rrID := "rr" + parameters[0]
err := stub.PutState(rrID, []byte(parameters[1]))
if err != nil {
return shim.Error(err.Error())
}
return shim.Success([]byte{})
}
func main() {
err := shim.Start(new(SwapManager))
if err != nil {
fmt.Printf("Error starting IRS chaincode: %s", err)
}
}

View file

@ -0,0 +1,192 @@
# Copyright IBM Corp. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
#
Organizations:
- &orderer
Name: orderer
ID: orderer
MSPDir: crypto-config/ordererOrganizations/example.com/msp
Policies:
Readers:
Type: Signature
Rule: OR('orderer.member')
Writers:
Type: Signature
Rule: OR('orderer.member')
Admins:
Type: Signature
Rule: OR('orderer.admin')
- &partya
Name: partya
ID: partya
MSPDir: crypto-config/peerOrganizations/partya.example.com/msp
Policies:
Readers:
Type: Signature
Rule: OR('partya.admin', 'partya.peer', 'partya.client')
Writers:
Type: Signature
Rule: OR('partya.admin', 'partya.client')
Admins:
Type: Signature
Rule: OR('partya.admin')
AnchorPeers:
- Host: irs-partya
Port: 7051
- &partyb
Name: partyb
ID: partyb
MSPDir: crypto-config/peerOrganizations/partyb.example.com/msp
Policies:
Readers:
Type: Signature
Rule: OR('partyb.admin', 'partyb.peer', 'partyb.client')
Writers:
Type: Signature
Rule: OR('partyb.admin', 'partyb.client')
Admins:
Type: Signature
Rule: OR('partyb.admin')
AnchorPeers:
- Host: irs-partyb
Port: 7051
- &partyc
Name: partyc
ID: partyc
MSPDir: crypto-config/peerOrganizations/partyc.example.com/msp
Policies:
Readers:
Type: Signature
Rule: OR('partyc.admin', 'partyc.peer', 'partyc.client')
Writers:
Type: Signature
Rule: OR('partyc.admin', 'partyc.client')
Admins:
Type: Signature
Rule: OR('partyc.admin')
AnchorPeers:
- Host: irs-partyc
Port: 7051
- &auditor
Name: auditor
ID: auditor
MSPDir: crypto-config/peerOrganizations/auditor.example.com/msp
Policies:
Readers:
Type: Signature
Rule: OR('auditor.admin', 'auditor.peer', 'auditor.client')
Writers:
Type: Signature
Rule: OR('auditor.admin', 'auditor.client')
Admins:
Type: Signature
Rule: OR('auditor.admin')
AnchorPeers:
- Host: irs-auditor
Port: 7051
- &rrprovider
Name: rrprovider
ID: rrprovider
MSPDir: crypto-config/peerOrganizations/rrprovider.example.com/msp
Policies:
Readers:
Type: Signature
Rule: OR('rrprovider.admin', 'rrprovider.peer', 'rrprovider.client')
Writers:
Type: Signature
Rule: OR('rrprovider.admin', 'rrprovider.client')
Admins:
Type: Signature
Rule: OR('rrprovider.admin')
AnchorPeers:
- Host: irs-rrprovider
Port: 7051
Channel: &ChannelDefaults
Capabilities:
V1_3: true
Policies:
Readers:
Type: ImplicitMeta
Rule: ANY Readers
Writers:
Type: ImplicitMeta
Rule: ANY Writers
Admins:
Type: ImplicitMeta
Rule: MAJORITY Admins
Orderer: &OrdererDefaults
OrdererType: solo
Capabilities:
V1_1: true
Addresses:
- irs-orderer:7050
BatchTimeout: 2s
BatchSize:
MaxMessageCount: 10
AbsoluteMaxBytes: 99 MB
PreferredMaxBytes: 512 KB
Policies:
Readers:
Type: ImplicitMeta
Rule: ANY Readers
Writers:
Type: ImplicitMeta
Rule: ANY Writers
Admins:
Type: ImplicitMeta
Rule: MAJORITY Admins
BlockValidation:
Type: ImplicitMeta
Rule: ANY Writers
Organizations:
Application: &ApplicationDefaults
Capabilities:
V1_3: true
Policies:
Readers:
Type: ImplicitMeta
Rule: ANY Readers
Writers:
Type: ImplicitMeta
Rule: ANY Writers
Admins:
Type: ImplicitMeta
Rule: MAJORITY Admins
Organizations:
Profiles:
IRSNetGenesis:
<<: *ChannelDefaults
Orderer:
<<: *OrdererDefaults
Organizations:
- *orderer
Consortiums:
SampleConsortium:
Organizations:
- *partya
- *partyb
- *partyc
- *rrprovider
- *auditor
IRSChannel:
Consortium: SampleConsortium
Application:
<<: *ApplicationDefaults
Organizations:
- *partya
- *partyb
- *partyc
- *rrprovider
- *auditor

View file

@ -0,0 +1,51 @@
# Copyright IBM Corp. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
#
OrdererOrgs:
- Name: orderer
Domain: example.com
Specs:
- Hostname: orderer
PeerOrgs:
- Name: partya
Domain: partya.example.com
EnableNodeOUs: true
Template:
Count: 1
Users:
Count: 1
- Name: partyb
Domain: partyb.example.com
EnableNodeOUs: true
Template:
Count: 1
Users:
Count: 1
- Name: partyc
Domain: partyc.example.com
EnableNodeOUs: true
Template:
Count: 1
Users:
Count: 1
- Name: auditor
Domain: auditor.example.com
EnableNodeOUs: true
Template:
Count: 1
Users:
Count: 1
- Name: rrprovider
Domain: rrprovider.example.com
EnableNodeOUs: true
Template:
Count: 1
Users:
Count: 1

View file

@ -0,0 +1,163 @@
# Copyright IBM Corp. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
#
version: '2'
volumes:
orderer.example.com:
peer0.partya.example.com:
peer0.partyb.example.com:
peer0.partyc.example.com:
peer0.auditor.example.com:
peer0.rrprovider.example.com:
services:
peer-base:
image: hyperledger/fabric-peer:$IMAGE_TAG
environment:
- CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
- FABRIC_LOGGING_SPEC=INFO
- CORE_PEER_TLS_ENABLED=false
- CORE_PEER_GOSSIP_USELEADERELECTION=true
- CORE_PEER_GOSSIP_ORGLEADER=false
- CORE_PEER_PROFILE_ENABLED=true
- CORE_PEER_ADDRESSAUTODETECT=true
working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
command: peer node start
volumes:
- /var/run/:/host/var/run/
orderer:
container_name: irs-orderer
image: hyperledger/fabric-orderer:$IMAGE_TAG
environment:
- FABRIC_LOGGING_SPEC=INFO
- ORDERER_GENERAL_LISTENADDRESS=0.0.0.0
- ORDERER_GENERAL_GENESISMETHOD=file
- ORDERER_GENERAL_GENESISFILE=/var/hyperledger/orderer/orderer.genesis.block
- ORDERER_GENERAL_LOCALMSPID=orderer
- ORDERER_GENERAL_LOCALMSPDIR=/var/hyperledger/orderer/msp
- ORDERER_GENERAL_TLS_ENABLED=false
working_dir: /opt/gopath/src/github.com/hyperledger/fabric
command: orderer
volumes:
- ./channel-artifacts/genesis.block:/var/hyperledger/orderer/orderer.genesis.block
- ./crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/msp:/var/hyperledger/orderer/msp
- orderer.example.com:/var/hyperledger/production/orderer
ports:
- 7050:7050
partya:
container_name: irs-partya
extends:
service: peer-base
environment:
- CORE_PEER_ID=partya.peer0
- CORE_PEER_ADDRESS=irs-partya:7051
- CORE_PEER_GOSSIP_EXTERNALENDPOINT=irs-partya:7051
- CORE_PEER_LOCALMSPID=partya
- CORE_CHAINCODE_LOGGING_SHIM=INFO
volumes:
- ./crypto-config/peerOrganizations/partya.example.com/peers/peer0.partya.example.com/msp:/etc/hyperledger/fabric/msp
- peer0.partya.example.com:/var/hyperledger/production
ports:
- 7051:7051
- 7053:7053
partyb:
container_name: irs-partyb
extends:
service: peer-base
environment:
- CORE_PEER_ID=partyb.peer0
- CORE_PEER_ADDRESS=irs-partyb:7051
- CORE_PEER_GOSSIP_EXTERNALENDPOINT=irs-partyb:7051
- CORE_PEER_LOCALMSPID=partyb
volumes:
- ./crypto-config/peerOrganizations/partyb.example.com/peers/peer0.partyb.example.com/msp:/etc/hyperledger/fabric/msp
- peer0.partyb.example.com:/var/hyperledger/production
ports:
- 8051:7051
- 8053:7053
partyc:
container_name: irs-partyc
extends:
service: peer-base
environment:
- CORE_PEER_ID=partyc.peer0
- CORE_PEER_ADDRESS=irs-partyc:7051
- CORE_PEER_GOSSIP_EXTERNALENDPOINT=irs-partyc:7051
- CORE_PEER_LOCALMSPID=partyc
volumes:
- ./crypto-config/peerOrganizations/partyc.example.com/peers/peer0.partyc.example.com/msp:/etc/hyperledger/fabric/msp
- peer0.partyc.example.com:/var/hyperledger/production
ports:
- 9051:7051
- 9053:7053
auditor:
container_name: irs-auditor
extends:
service: peer-base
environment:
- CORE_PEER_ID=auditor.peer0
- CORE_PEER_ADDRESS=irs-auditor:7051
- CORE_PEER_GOSSIP_EXTERNALENDPOINT=irs-auditor:7051
- CORE_PEER_LOCALMSPID=auditor
volumes:
- ./crypto-config/peerOrganizations/auditor.example.com/peers/peer0.auditor.example.com/msp:/etc/hyperledger/fabric/msp
- peer0.auditor.example.com:/var/hyperledger/production
ports:
- 10051:7051
- 10053:7053
rrprovider:
container_name: irs-rrprovider
extends:
service: peer-base
environment:
- CORE_PEER_ID=rrprovider.peer0
- CORE_PEER_ADDRESS=irs-rrprovider:7051
- CORE_PEER_GOSSIP_EXTERNALENDPOINT=irs-rrprovider:7051
- CORE_PEER_LOCALMSPID=rrprovider
- CORE_LOGGING_LEVEL=DEBUG
volumes:
- ./crypto-config/peerOrganizations/rrprovider.example.com/peers/peer0.rrprovider.example.com/msp:/etc/hyperledger/fabric/msp
- peer0.rrprovider.example.com:/var/hyperledger/production
ports:
- 11051:7051
- 11053:7053
cli:
container_name: cli
image: hyperledger/fabric-tools:$IMAGE_TAG
tty: true
stdin_open: true
environment:
- GOPATH=/opt/gopath
- CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
- FABRIC_LOGGING_SPEC=INFO
- CORE_PEER_ID=cli
- CORE_PEER_ADDRESS=irs-partya:7051
- CORE_PEER_LOCALMSPID=partya
- CORE_PEER_TLS_ENABLED=false
- CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/partya.example.com/users/Admin@partya.example.com/msp
working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
command: /bin/bash
volumes:
- /var/run/:/host/var/run/
- ../chaincode/:/opt/gopath/src/irscc
- ./crypto-config:/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/
- ./scripts:/opt/gopath/src/github.com/hyperledger/fabric/peer/scripts/
- ./channel-artifacts:/opt/gopath/src/github.com/hyperledger/fabric/peer/channel-artifacts
depends_on:
- orderer
- partya
- partyb
- partyc
- auditor
- rrprovider

View file

@ -0,0 +1,240 @@
#!/bin/bash
#
# Copyright IBM Corp All Rights Reserved
#
# SPDX-License-Identifier: Apache-2.0
#
# This script brings up a test network for the interest-rate swap fabric example.
# It relies on two tools:
# * cryptogen - generates the x509 certificates used to identify and
# authenticate the various components in the network.
# * configtxgen - generates the requisite configuration artifacts for orderer
# bootstrap and channel creation.
#
# Each tool consumes a configuration yaml file, within which we specify the topology
# of our network (cryptogen) and the location of our certificates for various
# configuration operations (configtxgen). Once the tools have been successfully run,
# we are able to launch our network. More detail on the tools and the structure of
# the network will be provided later in this document. For now, let's get going...
# prepending $PWD/../bin to PATH to ensure we are picking up the correct binaries
# this may be commented out to resolve installed version of tools if desired
export PATH=${PWD}/../../bin:${PWD}:$PATH
export FABRIC_CFG_PATH=${PWD}
export VERBOSE=false
# Print the usage message
function printHelp() {
echo "Usage: "
echo " start_network.sh <mode> [-t <timeout>] [-i <imagetag>] [-v]"
echo " <mode> - one of 'up', 'down' or 'generate'"
echo " - 'up' - bring up the network with docker-compose up"
echo " - 'down' - clear the network with docker-compose down"
echo " - 'generate' - generate required certificates and genesis block"
echo " -t <timeout> - CLI timeout duration in seconds (defaults to 10)"
echo " -i <imagetag> - the tag to be used to launch the network (defaults to \"latest\")"
echo " -v - verbose mode"
echo
echo "Typically, one would first generate the required certificates and "
echo "genesis block, then bring up the network."
}
# Obtain CONTAINER_IDS and remove them
# TODO Might want to make this optional - could clear other containers
function clearContainers() {
CONTAINER_IDS=$(docker ps -a | awk '($2 ~ /dev-.*irscc.*/) {print $1}')
if [ -z "$CONTAINER_IDS" -o "$CONTAINER_IDS" == " " ]; then
echo "---- No containers available for deletion ----"
else
docker rm -f $CONTAINER_IDS
fi
}
# Delete any images that were generated as a part of this setup
# specifically the following images are often left behind:
# TODO list generated image naming patterns
function removeUnwantedImages() {
DOCKER_IMAGE_IDS=$(docker images | awk '($1 ~ /dev.*irscc.*/) {print $3}')
if [ -z "$DOCKER_IMAGE_IDS" -o "$DOCKER_IMAGE_IDS" == " " ]; then
echo "---- No images available for deletion ----"
else
docker rmi -f $DOCKER_IMAGE_IDS
fi
}
# Versions of fabric known not to work with this release of first-network
BLACKLISTED_VERSIONS="^1\.0\. ^1\.1\.0-preview ^1\.1\.0-alpha"
# Do some basic sanity checking to make sure that the appropriate versions of fabric
# binaries/images are available. In the future, additional checking for the presence
# of go or other items could be added.
function checkPrereqs() {
# Note, we check configtxlator externally because it does not require a config file, and peer in the
# docker image because of FAB-8551 that makes configtxlator return 'development version' in docker
LOCAL_VERSION=$(configtxgen -version | sed -ne 's/ Version: //p')
DOCKER_IMAGE_VERSION=$(docker run --rm hyperledger/fabric-tools:$IMAGETAG peer version | sed -ne 's/ Version: //p' | head -1)
echo "LOCAL_VERSION=$LOCAL_VERSION"
echo "DOCKER_IMAGE_VERSION=$DOCKER_IMAGE_VERSION"
if [ "$LOCAL_VERSION" != "$DOCKER_IMAGE_VERSION" ]; then
echo "=================== WARNING ==================="
echo " Local fabric binaries and docker images are "
echo " out of sync. This may cause problems. "
echo "==============================================="
fi
for UNSUPPORTED_VERSION in $BLACKLISTED_VERSIONS; do
echo "$LOCAL_VERSION" | grep -q $UNSUPPORTED_VERSION
if [ $? -eq 0 ]; then
echo "ERROR! Local Fabric binary version of $LOCAL_VERSION does not match this newer version of BYFN and is unsupported. Either move to a later version of Fabric or checkout an earlier version of fabric-samples."
exit 1
fi
echo "$DOCKER_IMAGE_VERSION" | grep -q $UNSUPPORTED_VERSION
if [ $? -eq 0 ]; then
echo "ERROR! Fabric Docker image version of $DOCKER_IMAGE_VERSION does not match this newer version of BYFN and is unsupported. Either move to a later version of Fabric or checkout an earlier version of fabric-samples."
exit 1
fi
done
}
# Generate the needed certificates, the genesis block and start the network.
function networkUp() {
checkPrereqs
# generate artifacts if they don't exist
if [ ! -d "crypto-config" ]; then
generateCerts
generateChannelArtifacts
fi
IMAGE_TAG=$IMAGETAG docker-compose -f $COMPOSE_FILE up -d orderer partya partyb partyc auditor rrprovider cli 2>&1
if [ $? -ne 0 ]; then
echo "ERROR !!!! Unable to start network"
exit 1
fi
# now run the end to end script
docker exec cli scripts/script.sh
if [ $? -ne 0 ]; then
echo "ERROR !!!! Test failed"
exit 1
fi
}
# Tear down running network
function networkDown() {
# stop org3 containers also in addition to org1 and org2, in case we were running sample to add org3
docker-compose -f $COMPOSE_FILE down --volumes --remove-orphans
# Bring down the network, deleting the volumes
#Delete any ledger backups
docker run -v $PWD:/tmp/first-network --rm hyperledger/fabric-tools:$IMAGETAG rm -Rf /tmp/first-network/ledgers-backup
#Cleanup the chaincode containers
clearContainers
#Cleanup images
removeUnwantedImages
# remove orderer block and other channel configuration transactions and certs
rm -rf channel-artifacts/*.block channel-artifacts/*.tx crypto-config
}
# Generates Org certs using cryptogen tool
function generateCerts() {
which cryptogen
if [ "$?" -ne 0 ]; then
echo "cryptogen tool not found. exiting"
exit 1
fi
echo "##### Generate certificates using cryptogen tool #########"
if [ -d "crypto-config" ]; then
rm -Rf crypto-config
fi
cryptogen generate --config=./crypto-config.yaml
res=$?
if [ $res -ne 0 ]; then
echo "Failed to generate certificates..."
exit 1
fi
echo
}
# Generate orderer genesis block and channel configuration transaction with configtxgen
function generateChannelArtifacts() {
which configtxgen
if [ "$?" -ne 0 ]; then
echo "configtxgen tool not found. exiting"
exit 1
fi
echo "######### Generating Orderer Genesis block ##############"
mkdir channel-artifacts
configtxgen -profile IRSNetGenesis -outputBlock ./channel-artifacts/genesis.block
res=$?
if [ $res -ne 0 ]; then
echo "Failed to generate orderer genesis block..."
exit 1
fi
echo
echo "### Generating channel configuration transaction 'channel.tx' ###"
configtxgen -profile IRSChannel -outputCreateChannelTx ./channel-artifacts/channel.tx -channelID $CHANNEL_NAME
res=$?
if [ $res -ne 0 ]; then
echo "Failed to generate channel configuration transaction..."
exit 1
fi
}
# Obtain the OS and Architecture string that will be used to select the correct
# native binaries for your platform, e.g., darwin-amd64 or linux-amd64
OS_ARCH=$(echo "$(uname -s | tr '[:upper:]' '[:lower:]' | sed 's/mingw64_nt.*/windows/')-$(uname -m | sed 's/x86_64/amd64/g')" | awk '{print tolower($0)}')
CHANNEL_NAME="irs"
COMPOSE_FILE=docker-compose.yaml
COMPOSE_PROJECT_NAME=fabric-irs
#
# default image tag
IMAGETAG="latest"
# Parse commandline args
MODE=$1
shift
# Determine whether starting, stopping, generating
if [ "$MODE" == "up" ]; then
EXPMODE="Starting"
elif [ "$MODE" == "down" ]; then
EXPMODE="Stopping"
elif [ "$MODE" == "generate" ]; then
EXPMODE="Generating certs and genesis block"
else
printHelp
exit 1
fi
while getopts "t:i:v" opt; do
case "$opt" in
t)
CLI_TIMEOUT=$OPTARG
;;
i)
IMAGETAG=$(go env GOARCH)"-"$OPTARG
;;
v)
VERBOSE=true
;;
esac
done
# Announce what was requested
echo "${EXPMODE} for channel '${CHANNEL_NAME}'"
#Create the network using docker compose
if [ "${MODE}" == "up" ]; then
networkUp
elif [ "${MODE}" == "down" ]; then ## Clear the network
networkDown
elif [ "${MODE}" == "generate" ]; then ## Generate Artifacts
generateCerts
generateChannelArtifacts
else
printHelp
exit 1
fi

View file

@ -0,0 +1,123 @@
#!/bin/bash
DELAY="3"
TIMEOUT="10"
VERBOSE="false"
COUNTER=1
MAX_RETRY=5
CC_SRC_PATH="irscc/"
createChannel() {
CORE_PEER_LOCALMSPID=partya
CORE_PEER_ADDRESS=irs-partya:7051
CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/partya.example.com/users/Admin@partya.example.com/msp
echo "===================== Creating channel ===================== "
peer channel create -o irs-orderer:7050 -c irs -f ./channel-artifacts/channel.tx
echo "===================== Channel created ===================== "
}
joinChannel () {
for org in partya partyb partyc auditor rrprovider
do
CORE_PEER_LOCALMSPID=$org
CORE_PEER_ADDRESS=irs-$org:7051
CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/$org.example.com/users/Admin@$org.example.com/msp
echo "===================== Org $org joining channel ===================== "
peer channel join -b irs.block -o irs-orderer:7050
echo "===================== Channel joined ===================== "
done
}
installChaincode() {
for org in partya partyb partyc auditor rrprovider
do
CORE_PEER_LOCALMSPID=$org
CORE_PEER_ADDRESS=irs-$org:7051
CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/$org.example.com/users/Admin@$org.example.com/msp
echo "===================== Org $org installing chaincode ===================== "
peer chaincode install -n irscc -v 0 -l golang -p ${CC_SRC_PATH}
echo "===================== Org $org chaincode installed ===================== "
done
}
instantiateChaincode() {
CORE_PEER_LOCALMSPID=partya
CORE_PEER_ADDRESS=irs-partya:7051
CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/partya.example.com/users/Admin@partya.example.com/msp
echo "===================== Instantiating chaincode ===================== "
peer chaincode instantiate -o irs-orderer:7050 -C irs -n irscc -l golang -v 0 -c '{"Args":["init","auditor","100000","rrprovider","myrr"]}' -P "AND(OR('partya.peer','partyb.peer','partyc.peer'), 'auditor.peer')"
echo "===================== Chaincode instantiated ===================== "
}
setReferenceRate() {
CORE_PEER_LOCALMSPID=rrprovider
CORE_PEER_ADDRESS=irs-rrprovider:7051
CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/rrprovider.example.com/users/User1@rrprovider.example.com/msp
echo "===================== Invoking chaincode ===================== "
peer chaincode invoke -o irs-orderer:7050 -C irs --waitForEvent -n irscc --peerAddresses irs-rrprovider:7051 -c '{"Args":["setReferenceRate","myrr","300"]}'
echo "===================== Chaincode invoked ===================== "
}
createSwap() {
CORE_PEER_LOCALMSPID=partya
CORE_PEER_ADDRESS=irs-partya:7051
CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/partya.example.com/users/User1@partya.example.com/msp
echo "===================== Invoking chaincode ===================== "
peer chaincode invoke -o irs-orderer:7050 -C irs --waitForEvent -n irscc --peerAddresses irs-partya:7051 --peerAddresses irs-partyb:7051 --peerAddresses irs-auditor:7051 -c '{"Args":["createSwap","myswap","{\"StartDate\":\"2018-09-27T15:04:05Z\",\"EndDate\":\"2018-09-30T15:04:05Z\",\"PaymentInterval\":395,\"PrincipalAmount\":10,\"FixedRate\":400,\"FloatingRate\":500,\"ReferenceRate\":\"myrr\"}", "partya", "partyb"]}'
echo "===================== Chaincode invoked ===================== "
}
calculatePayment() {
CORE_PEER_LOCALMSPID=partya
CORE_PEER_ADDRESS=irs-partya:7051
CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/partya.example.com/users/User1@partya.example.com/msp
echo "===================== Invoking chaincode ===================== "
peer chaincode invoke -o irs-orderer:7050 -C irs --waitForEvent -n irscc --peerAddresses irs-partya:7051 --peerAddresses irs-partyb:7051 -c '{"Args":["calculatePayment","myswap"]}'
echo "===================== Chaincode invoked ===================== "
}
settlePayment() {
CORE_PEER_LOCALMSPID=partyb
CORE_PEER_ADDRESS=irs-partyb:7051
CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/partyb.example.com/users/User1@partyb.example.com/msp
echo "===================== Invoking chaincode ===================== "
peer chaincode invoke -o irs-orderer:7050 -C irs --waitForEvent -n irscc --peerAddresses irs-partya:7051 --peerAddresses irs-partyb:7051 -c '{"Args":["settlePayment","myswap"]}'
echo "===================== Chaincode invoked ===================== "
}
## Create channel
sleep 1
echo "Creating channel..."
createChannel
## Join all the peers to the channel
echo "Having all peers join the channel..."
joinChannel
## Install chaincode on all peers
echo "Installing chaincode..."
installChaincode
# Instantiate chaincode
echo "Instantiating chaincode..."
instantiateChaincode
echo "Setting myrr reference rate"
sleep 3
setReferenceRate
echo "Creating swap between A and B"
createSwap
echo "Calculate payment information"
calculatePayment
echo "Mark payment settled"
settlePayment
echo
echo "========= IRS network sample setup completed =========== "
echo
exit 0