diff --git a/interest_rate_swaps/README.md b/interest_rate_swaps/README.md new file mode 100644 index 0000000000..40c332da5f --- /dev/null +++ b/interest_rate_swaps/README.md @@ -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. diff --git a/interest_rate_swaps/chaincode/chaincode.go b/interest_rate_swaps/chaincode/chaincode.go new file mode 100644 index 0000000000..6cca88fdb5 --- /dev/null +++ b/interest_rate_swaps/chaincode/chaincode.go @@ -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: ... ") + } + // 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: ") + } + + // 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: ") + } + + // 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: ") + } + 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: ") + } + + 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) + } +} diff --git a/interest_rate_swaps/network/configtx.yaml b/interest_rate_swaps/network/configtx.yaml new file mode 100644 index 0000000000..5adeab1140 --- /dev/null +++ b/interest_rate_swaps/network/configtx.yaml @@ -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 diff --git a/interest_rate_swaps/network/crypto-config.yaml b/interest_rate_swaps/network/crypto-config.yaml new file mode 100644 index 0000000000..b170194333 --- /dev/null +++ b/interest_rate_swaps/network/crypto-config.yaml @@ -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 diff --git a/interest_rate_swaps/network/docker-compose.yaml b/interest_rate_swaps/network/docker-compose.yaml new file mode 100644 index 0000000000..44fabf961d --- /dev/null +++ b/interest_rate_swaps/network/docker-compose.yaml @@ -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 diff --git a/interest_rate_swaps/network/network.sh b/interest_rate_swaps/network/network.sh new file mode 100755 index 0000000000..5338ecf045 --- /dev/null +++ b/interest_rate_swaps/network/network.sh @@ -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 [-t ] [-i ] [-v]" + echo " - 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 - CLI timeout duration in seconds (defaults to 10)" + echo " -i - 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 diff --git a/interest_rate_swaps/network/scripts/script.sh b/interest_rate_swaps/network/scripts/script.sh new file mode 100755 index 0000000000..cbb6ce212f --- /dev/null +++ b/interest_rate_swaps/network/scripts/script.sh @@ -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