Skip to content
This repository has been archived by the owner on Nov 8, 2023. It is now read-only.

Commit

Permalink
Tests for extended cpaper example and improved docs (#16)
Browse files Browse the repository at this point in the history
* Improve example comments

* Fix some grammar and formatting issues
* Better explained what List method does

* Simplify cpaper example and add tests

* cpaper example follows the basic logic from original example
* remove proto schema data from basic example
* cpaper tests provide usage and further explanation

* Fix docs function names, add single identity load method

* Implement revoke for cpaper extended example and add tests

* cpaper extended contains additional features
* minor formatting fixes in proto files

* Fix lint error about SNAKE_CASE, correct BuyerName

* Better variable names, add event checks in cpaper_extended tests

* Add more info into READMEs for cpaper and cpaper_extended

* Add new mapping test proto schema and regenerate proto files

* Add test chaincode for proto mapping and uniq key

* Add tests with proto mapping and uniq key
  • Loading branch information
TopHatCroat authored and vitiko committed May 17, 2019
1 parent c59a3ee commit bed9949
Show file tree
Hide file tree
Showing 27 changed files with 1,099 additions and 873 deletions.
9 changes: 3 additions & 6 deletions examples/cpaper/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
# Commercial paper Hyperledger Fabric chaincode example with protobuf based state schema
# Commercial paper Hyperledger Fabric chaincode example

https://hyperledger-fabric.readthedocs.io/en/release-1.4/developapps/scenario.html
This is an faithful reimplementation of the official example [Commercial paper scenario](https://hyperledger-fabric.readthedocs.io/en/release-1.4/developapps/scenario.html)


At the heart of a blockchain network is a smart contract. In PaperNet, the code in the commercial paper smart
contract defines the valid states for commercial paper, and the transaction logic that transition
a paper from one state to another.
Original code written in JavaScript using the Fabric Chaincode Node SDK is available [here](https://github.com/hyperledger/fabric-samples/tree/release-1.4/commercial-paper/organization/digibank/contract)
241 changes: 148 additions & 93 deletions examples/cpaper/chaincode.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,145 +2,200 @@ package cpaper

import (
"fmt"
"github.com/s7techlab/cckit/router/param"
"time"

"github.com/pkg/errors"
"github.com/s7techlab/cckit/examples/cpaper/schema"
"github.com/s7techlab/cckit/extensions/debug"
"github.com/s7techlab/cckit/extensions/owner"
"github.com/s7techlab/cckit/router"
"github.com/s7techlab/cckit/router/param/defparam"
m "github.com/s7techlab/cckit/state/mapping"
)

var (
// State mappings
StateMappings = m.StateMappings{}.Add(
&schema.CommercialPaper{}, // define mapping for this structure
m.PKeySchema(&schema.CommercialPaperId{}), // key will be <`CommercialPaper`, Issuer, PaperNumber>
m.List(&schema.CommercialPaperList{})) // structure-result of list method

// EventMappings
EventMappings = m.EventMappings{}.
Add(&schema.IssueCommercialPaper{}). // event name will be `IssueCommercialPaper`, payload - same as issue payload
Add(&schema.BuyCommercialPaper{}).
Add(&schema.RedeemCommercialPaper{})
type TaskState string

const (
CommercialPaperTypeName = "CommercialPaper"

CommercialPaperIssued TaskState = "issued"
CommercialPaperTrading TaskState = "trading"
CommercialPaperRedeemed TaskState = "redeemed"
)

func NewCC() *router.Chaincode {
type CommercialPaper struct {
Issuer string `json:"issuer,omitempty"`
PaperNumber string `json:"paper_number,omitempty"`
Owner string `json:"owner,omitempty"`
IssueDate time.Time `json:"issue_date,omitempty"`
MaturityDate time.Time `json:"maturity_date,omitempty"`
FaceValue int32 `json:"face_value,omitempty"`
State TaskState `json:"state,omitempty"`
}

r := router.New(`commercial_paper`)
// Comercial paper has a composite key <`CommercialPaper`, Issuer, PaperNumber>
func (c CommercialPaper) Key() ([]string, error) {
return []string{CommercialPaperTypeName, c.Issuer, c.PaperNumber}, nil
}

// Mappings for chaincode state
r.Use(m.MapStates(StateMappings))
type IssueCommercialPaper struct {
Issuer string `json:"issuer,omitempty"`
PaperNumber string `json:"paper_number,omitempty"`
IssueDate time.Time `json:"issue_date,omitempty"`
MaturityDate time.Time `json:"maturity_date,omitempty"`
FaceValue int32 `json:"face_value,omitempty"`
}

// Mappings for chaincode events
r.Use(m.MapEvents(EventMappings))
type BuyCommercialPaper struct {
Issuer string `json:"issuer,omitempty"`
PaperNumber string `json:"paper_number,omitempty"`
CurrentOwner string `json:"current_owner,omitempty"`
NewOwner string `json:"new_owner,omitempty"`
Price int32 `json:"price,omitempty"`
PurchaseDate time.Time `json:"purchase_date,omitempty"`
}

// store in chaincode state information about chaincode first instantiator
r.Init(owner.InvokeSetFromCreator)
type RedeemCommercialPaper struct {
Issuer string `json:"issuer,omitempty"`
PaperNumber string `json:"paper_number,omitempty"`
RedeemingOwner string `json:"redeeming_owner,omitempty"`
RedeemDate time.Time `json:"redeem_date,omitempty"`
}

// method for debug chaincode state
debug.AddHandlers(r, `debug`, owner.Only)
func NewCC() *router.Chaincode {
r := router.New(`commercial_paper`)

r.
// read methods
Query(`list`, cpaperList).
r.Init(func(context router.Context) (i interface{}, e error) {
// No implementation required with this example
// It could be where data migration is performed, if necessary
return nil, nil
})

r.
// Read methods
Query(`list`, list).
// Get method has 2 params - commercial paper primary key components
Query(`get`, cpaperGet, defparam.Proto(&schema.CommercialPaperId{})).
Query(`get`, get, param.String("issuer"), param.String("paper_number")).

// txn methods
Invoke(`issue`, cpaperIssue, defparam.Proto(&schema.IssueCommercialPaper{})).
Invoke(`buy`, cpaperBuy, defparam.Proto(&schema.BuyCommercialPaper{})).
Invoke(`redeem`, cpaperRedeem, defparam.Proto(&schema.RedeemCommercialPaper{})).
Invoke(`delete`, cpaperDelete, defparam.Proto(&schema.CommercialPaperId{}))
// Transaction methods
Invoke(`issue`, issue, param.Struct("issueData", &IssueCommercialPaper{})).
Invoke(`buy`, buy, param.Struct("buyData", &BuyCommercialPaper{})).
Invoke(`redeem`, redeem, param.Struct("redeemData", &RedeemCommercialPaper{}))

return router.NewChaincode(r)
}

func cpaperList(c router.Context) (interface{}, error) {
// commercial paper key is composite key <`CommercialPaper`>, {Issuer}, {PaperNumber} >
// where `CommercialPaper` - namespace of this type
// list method retrieves entries from chaincode state
// using GetStateByPartialCompositeKey method, then unmarshal received from state bytes via proto.Ummarshal method
// and creates slice of *schema.CommercialPaper
return c.State().List(&schema.CommercialPaper{})
}

func cpaperIssue(c router.Context) (interface{}, error) {
func get(c router.Context) (interface{}, error) {
var (
issue = c.Param().(*schema.IssueCommercialPaper) //default parameter
cpaper = &schema.CommercialPaper{
Issuer: issue.Issuer,
PaperNumber: issue.PaperNumber,
Owner: issue.Issuer,
IssueDate: issue.IssueDate,
MaturityDate: issue.MaturityDate,
FaceValue: issue.FaceValue,
State: schema.CommercialPaper_ISSUED, // initial state
}
err error
issuer = c.ParamString("issuer")
paperNumber = c.ParamString("paper_number")
)

if err = c.Event().Set(issue); err != nil {
return nil, err
}

return cpaper, c.State().Insert(cpaper)
return c.State().Get(&CommercialPaper{
Issuer: issuer,
PaperNumber: paperNumber,
})
}

func cpaperBuy(c router.Context) (interface{}, error) {
func list(c router.Context) (interface{}, error) {
// List method retrieves all entries from the ledger using GetStateByPartialCompositeKey method and passing it the
// namespace of our contract type, in this example that's `CommercialPaper`, then it unmarshals received bytes via
// json.Ummarshal method and creates a JSON array of CommercialPaper entities
return c.State().List(CommercialPaperTypeName, &CommercialPaper{})
}

func issue(c router.Context) (interface{}, error) {
var (
cpaper *schema.CommercialPaper
issueData = c.Param("issueData").(IssueCommercialPaper) // Assert the chaincode parameter
commercialPaper = &CommercialPaper{
Issuer: issueData.Issuer,
PaperNumber: issueData.PaperNumber,
Owner: issueData.Issuer,
IssueDate: issueData.IssueDate,
MaturityDate: issueData.MaturityDate,
FaceValue: issueData.FaceValue,
State: CommercialPaperIssued, // Initial state
}
)

// but tx payload
buy = c.Param().(*schema.BuyCommercialPaper)
return commercialPaper, c.State().Insert(commercialPaper)
}

// current commercial paper state
cp, err = c.State().Get(
&schema.CommercialPaperId{Issuer: buy.Issuer, PaperNumber: buy.PaperNumber},
&schema.CommercialPaper{})
func buy(c router.Context) (interface{}, error) {
var (
commercialPaper CommercialPaper
// Buy transaction payload
buyData = c.Param("buyData").(BuyCommercialPaper)

// Get the current commercial paper state
cp, err = c.State().Get(&CommercialPaper{
Issuer: buyData.Issuer,
PaperNumber: buyData.PaperNumber,
}, &CommercialPaper{})
)

if err != nil {
return nil, errors.Wrap(err, `not found`)
return nil, errors.Wrap(err, "paper not found")
}
cpaper = cp.(*schema.CommercialPaper)

commercialPaper = cp.(CommercialPaper)

// Validate current owner
if cpaper.Owner != buy.CurrentOwner {
return nil, fmt.Errorf(`paper %s %s is not owned by %s`, cpaper.Issuer, cpaper.PaperNumber, buy.CurrentOwner)
if commercialPaper.Owner != buyData.CurrentOwner {
return nil, fmt.Errorf(
"paper %s %s is not owned by %s",
commercialPaper.Issuer, commercialPaper.PaperNumber, buyData.CurrentOwner)
}

// First buy moves state from ISSUED to TRADING
if cpaper.State == schema.CommercialPaper_ISSUED {
cpaper.State = schema.CommercialPaper_TRADING
// First buyData moves state from ISSUED to TRADING
if commercialPaper.State == CommercialPaperIssued {
commercialPaper.State = CommercialPaperTrading
}

// Check paper is not already REDEEMED
if cpaper.State == schema.CommercialPaper_TRADING {
cpaper.Owner = buy.NewOwner
if commercialPaper.State == CommercialPaperTrading {
commercialPaper.Owner = buyData.NewOwner
} else {
return nil, fmt.Errorf(`paper %s %s is not trading.current state = %s`, cpaper.Issuer, cpaper.PaperNumber, cpaper.State)
return nil, fmt.Errorf(
"paper %s %s is not trading.current state = %s",
commercialPaper.Issuer, commercialPaper.PaperNumber, commercialPaper.State)
}

if err = c.Event().Set(buy); err != nil {
return nil, err
return commercialPaper, c.State().Put(commercialPaper)
}

func redeem(c router.Context) (interface{}, error) {
var (
commercialPaper CommercialPaper

// Buy transaction payload
redeemData = c.Param("redeemData").(RedeemCommercialPaper)

// Get the current commercial paper state
cp, err = c.State().Get(&CommercialPaper{
Issuer: redeemData.Issuer,
PaperNumber: redeemData.PaperNumber,
}, &CommercialPaper{})
)

if err != nil {
return nil, errors.Wrap(err, "paper not found")
}

return cpaper, c.State().Put(cpaper)
}
commercialPaper = cp.(CommercialPaper)

func cpaperRedeem(c router.Context) (interface{}, error) {
// implement me
return nil, nil
}
// Check paper is not REDEEMED
if commercialPaper.State == CommercialPaperRedeemed {
return nil, fmt.Errorf(
"paper %s %s is already redeemed",
commercialPaper.Issuer, commercialPaper.PaperNumber)
}

func cpaperGet(c router.Context) (interface{}, error) {
return c.State().Get(c.Param().(*schema.CommercialPaperId))
}
// Verify that the redeemer owns the commercial paper before redeeming it
if commercialPaper.Owner == redeemData.RedeemingOwner {
commercialPaper.Owner = redeemData.Issuer
commercialPaper.State = CommercialPaperRedeemed
} else {
return nil, fmt.Errorf(
"redeeming owner does not own paper %s %s",
commercialPaper.Issuer, commercialPaper.PaperNumber)
}

func cpaperDelete(c router.Context) (interface{}, error) {
return nil, c.State().Delete(c.Param().(*schema.CommercialPaperId))
return commercialPaper, c.State().Put(commercialPaper)
}
Loading

0 comments on commit bed9949

Please sign in to comment.