Code Monkey home page Code Monkey logo

cckit's Introduction

Hyperledger Fabric chaincode kit (CCKit)

We're pleased to announce that ССKit has officially joined https://github.com/hyperledger-labs.

CCKit development continues here https://github.com/hyperledger-labs/cckit




Go Report Card Build Coverage Status

Overview

A smart contract is code, invoked by a client application external to the blockchain network – that manages access and modifications to a set of key-value pairs in the World State. In Hyperledger Fabric, smart contracts are referred to as chaincode.

CCKit is a programming toolkit for developing and testing Hyperledger Fabric golang chaincodes. It enhances the development experience while providing developers components for creating more readable and secure smart contracts.

Chaincode examples

There are several chaincode "official" examples available:

and others

Main problems with existing examples are:

  • Working with chaincode state at very low level
  • Lots of code duplication (JSON marshalling / unmarshalling, validation, access control, etc)
  • Chaincode methods routing appeared only in HLF 1.4 and only in Node.Js chaincode
  • Uncompleted testing tools (MockStub)

CCKit features

Publications with usage examples

Examples based on CCKit

Installation

CCKit requires Go 1.11+ with modules support

Standalone

git clone [email protected]:s7techlab/cckit.git

go mod vendor

As dependency

go get -u github.com/s7techlab/cckit

Example - Commercial Paper chaincode

Scenario

Commercial paper scenario from official documentation describes a Hyperledger Fabric network, aimed to issue, buy and redeem commercial paper.

commercial paper network

5 steps to develop chaincode

Chaincode is a domain specific program which relates to specific business process. The job of a smart contract developer is to take an existing business process and express it as a smart contract in a programming language. Steps of chaincode development:

  1. Define chaincode model - schema for state entries, transaction payload and events
  2. Define chaincode interface
  3. Implement chaincode instantiate method
  4. Implement chaincode methods with business logic
  5. Create tests

Define chaincode model

With protocol buffers, you write a .proto description of the data structure you wish to store. From that, the protocol buffer compiler creates a golang struct that implements automatic encoding and parsing of the protocol buffer data with an efficient binary format (or json).

Code generation can be simplified with a short Makefile:

.: generate

generate:
	@echo "schema"
	@protoc -I=./ --go_out=./ ./*.proto

Chaincode state

The following file shows how to define the world state schema using protobuf.

examples/cpaper_extended/schema/state.proto

syntax = "proto3";

package cckit.examples.cpaper_extended.schema;
option go_package = "schema";

import "google/protobuf/timestamp.proto";

// Commercial Paper state entry
message CommercialPaper {

    enum State {
        ISSUED = 0;
        TRADING = 1;
        REDEEMED = 2;
    }

    // Issuer and Paper number comprises composite primary key of Commercial paper entry
    string issuer = 1;
    string paper_number = 2;

    string owner = 3;
    google.protobuf.Timestamp issue_date = 4;
    google.protobuf.Timestamp maturity_date = 5;
    int32 face_value = 6;
    State state = 7;

    // Additional unique field for entry
    string external_id = 8;
}

// CommercialPaperId identifier part
message CommercialPaperId {
    string issuer = 1;
    string paper_number = 2;
}

// Container for returning multiple entities
message CommercialPaperList {
    repeated CommercialPaper items = 1;
}

Chaincode transaction and events payload

This file defines the data payload used in business logic methods. In this example transaction and event payloads are exactly the same for the sake of brevity, but you could create a different schema for each type of payload.

examples/cpaper_extended/schema/payload.proto

// IssueCommercialPaper event
syntax = "proto3";

package cckit.examples.cpaper_extended.schema;
option go_package = "schema";

import "google/protobuf/timestamp.proto";
import "github.com/mwitkow/go-proto-validators/validator.proto";

// IssueCommercialPaper event
message IssueCommercialPaper {
    string issuer = 1;
    string paper_number = 2;
    google.protobuf.Timestamp issue_date = 3;
    google.protobuf.Timestamp maturity_date = 4;
    int32 face_value = 5;

    // external_id - another unique constraint
    string external_id = 6;
}

// BuyCommercialPaper event
message BuyCommercialPaper {
    string issuer = 1;
    string paper_number = 2;
    string current_owner = 3;
    string new_owner = 4;
    int32 price = 5;
    google.protobuf.Timestamp purchase_date = 6;
}

// RedeemCommercialPaper event
message RedeemCommercialPaper {
    string issuer = 1;
    string paper_number = 2;
    string redeeming_owner = 3;
    google.protobuf.Timestamp redeem_date = 4;
}

Define chaincode interface

In examples/cpaper_extended/chaincode.go file we will define the mappings, chaincode initialization method and business logic in the transaction methods. For brevity, we will only display snippets of the code here, please refer to the original file for full example.

Firstly we define mapping rules. These specify the struct used to hold a specific chaincode state, it's primary key, list mapping, unique keys, etc. Then we define the schemas used for emitting events.

var (
	// State mappings
	StateMappings = m.StateMappings{}.
		// Create mapping for Commercial Paper entity
		Add(&schema.CommercialPaper{},
			// Key namespace will be <"CommercialPaper", Issuer, PaperNumber>
			m.PKeySchema(&schema.CommercialPaperId{}),
			// Structure of result for List method
			m.List(&schema.CommercialPaperList{}),
			// External Id is unique
			m.UniqKey("ExternalId"),
		)

	// EventMappings
	EventMappings = m.EventMappings{}.
		// Event name will be "IssueCommercialPaper", payload - same as issue payload
		Add(&schema.IssueCommercialPaper{}).
		// Event name will be "BuyCommercialPaper"
		Add(&schema.BuyCommercialPaper{}).
		// Event name will be "RedeemCommercialPaper"
		Add(&schema.RedeemCommercialPaper{})
)

CCKit uses router to define rules about how to map chaincode invocation to a particular handler, as well as what kind of middleware needs to be used during a request, for example how to convert incoming argument from []byte to target type (string, struct, etc).

func NewCC() *router.Chaincode {

	r := router.New(`commercial_paper`)

	// Mappings for chaincode state
	r.Use(m.MapStates(StateMappings))

	// Mappings for chaincode events
	r.Use(m.MapEvents(EventMappings))

	// Store in chaincode state information about chaincode first instantiator
	r.Init(owner.InvokeSetFromCreator)

	// Method for debug chaincode state
	debug.AddHandlers(r, `debug`, owner.Only)

	r.
		// read methods
		Query(`list`, cpaperList).

		Query(`get`, cpaperGet, defparam.Proto(&schema.CommercialPaperId{})).

		// 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{}))

	return router.NewChaincode(r)
}

Implement chaincode init method

In many cases during chaincode instantiation we need to define permissions for chaincode functions - "who is allowed to do this thing", incredibly important in the world of smart contracts. The most common and basic form of access control is the concept of ownership: there's one account (combination of MSP and certificate identifiers) that is the owner and can do administrative tasks on contracts. This approach is perfectly reasonable for contracts that only have a single administrative user.

CCKit provides owner extension for implementing ownership and access control in Hyperledger Fabric chaincodes. In the previous snippet, as an init method, we used owner.InvokeSetFromCreator, storing information which stores the information about who is the owner into the world state upon chaincode instantiation.

Implement business rules as chaincode methods

Now we have to define the actual business logic which will modify the world state when a transaction occurs. In this example we will show only the buy method for brevity. Please refer to examples/cpaper_extended/chaincode.go for full implementation.

func invokeCPaperBuy(c router.Context) (interface{}, error) {
	var (
		cpaper *schema.CommercialPaper

		// Buy transaction payload
		buyData = c.Param().(*schema.BuyCommercialPaper)

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

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

	cpaper = cp.(*schema.CommercialPaper)

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

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

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

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

	return cpaper, c.State().Put(cpaper)
}

Test chaincode functionality

And finally we should write tests to ensure our business logic is behaving as it should. Again, for brevity, we omitted most of the code from examples/cpaper_extended/chaincode_test.go. CCKit support chaincode testing with MockStub.

var _ = Describe(`CommercialPaper`, func() {
	paperChaincode := testcc.NewMockStub(`commercial_paper`, NewCC())

	BeforeSuite(func() {
		// Init chaincode with admin identity
		expectcc.ResponseOk(
			paperChaincode.
				From(testdata.GetTestIdentity(MspName, path.Join("testdata", "admin", "admin.pem"))).
				Init())
	})

	Describe("Commercial Paper lifecycle", func() {
		// ...

		It("Allow buyer to buy commercial paper", func() {
			buyTransactionData := &schema.BuyCommercialPaper{
				Issuer:       IssuerName,
				PaperNumber:  "0001",
				CurrentOwner: IssuerName,
				NewOwner:     BuyerName,
				Price:        95000,
				PurchaseDate: ptypes.TimestampNow(),
			}

			expectcc.ResponseOk(paperChaincode.Invoke(`buy`, buyTransactionData))

			queryResponse := paperChaincode.Query("get", &schema.CommercialPaperId{
				Issuer:      IssuerName,
				PaperNumber: "0001",
			})

			paper := expectcc.PayloadIs(queryResponse, &schema.CommercialPaper{}).(*schema.CommercialPaper)

			Expect(paper.Owner).To(Equal(BuyerName))
			Expect(paper.State).To(Equal(schema.CommercialPaper_TRADING))

			Expect(<-paperChaincode.ChaincodeEventsChannel).To(BeEquivalentTo(&peer.ChaincodeEvent{
				EventName: `BuyCommercialPaper`,
				Payload:   testcc.MustProtoMarshal(buyTransactionData),
			}))

			paperChaincode.ClearEvents()
		})

		// ...

	})
})

cckit's People

Contributors

bogatyr285 avatar criro1 avatar czar0 avatar darkleaf avatar inotnako avatar kolayuk avatar kukgini avatar larsvliet avatar stepanshurkov avatar tophatcroat avatar uraldav avatar vitiko avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cckit's Issues

router handler error: write: key from field *message.Commodity.XXX_NoUnkeyedLiteral: field type not supported for key extraction

there is a bug,I use protoc command generate struct like this:

type Commodity struct {
    ....   // other fileds skip 
    XXX_NoUnkeyedLiteral struct{} `json:"-"`
    XXX_unrecognized     []byte   `json:"-"`
   XXX_sizecache        int32    `json:"-"`
}

and I store the instance of Commodity,but get an error: *router handler error: write: key from field message.Commodity.XXX_NoUnkeyedLiteral: field type not supported for key extraction

I find that cckit would also handle the struct fileds like "XXX_NoUnkeyedLiteral" ,"XXX_unrecognized". I think they should be skipped.

I changed the source code file "state/mapping/state_mapping_opt.go" like this:

func attrsPKeyer(attrs []string) InstanceKeyer {
    return func(instance interface{}) (key state.Key, err error) {
        inst := reflect.Indirect(reflect.ValueOf(instance))
        var pkey state.Key
        for _, attr := range attrs {
            v := inst.FieldByName(attr)
            if !v.IsValid() {
                return nil, fmt.Errorf(`%s: %s`, ErrFieldNotExists, attr)
            }
            // **********************************************************************
            //   skip the fields who start with  "XXX_"
            if state,_ := regexp.Match("XXX_.*",[]byte(attr)); state {
                continue
            }
           // **************************************************************************
            if key, err = keyFromValue(v); err != nil {
                return nil, fmt.Errorf(`key from field %s.%s: %s`, mapKey(instance), attr, err)
            }
            pkey = pkey.Append(key)
        }
        return pkey, nil
    }
}

Can not run tests (Windows 10, go 1.14.2)

I try to run examples but they fail with an error C:\Users\User1\go\pkg\mod\github.com\docker\[email protected]\pkg\system\filesys_windows.go:113:24: cannot use uintptr(unsafe.Pointer(&sd[0])) (type uintptr) as type *"golang.org/x/sys/windows".SECURITY_DESCRIPTOR in assignment

Problem while running tests

Description

When trying to run tests based on NewMockStub + chaincode permissioning, I receive the following error:

s7techlab/cckit/testing/tx.go:79:19: cannot use p.MockStub.MockStub.TxTimestamp (type *"github.com/hyperledger/fabric/vendor/github.com/golang/protobuf/ptypes/timestamp".Timestamp) as type *"github.com/golang/protobuf/ptypes/timestamp".Timestamp in return argument

Initial Commands

Dear CCKit
Thanks so much for this great repo; it clearly is a more complex and nuanced tool than the HL commercial paper tutorial. I can see considerable potential for my own work and students at UCLA. However: there's an assumption of considerable expertise in your ReadMe.

Could you possibly:

  1. Offer some additional, simpler commands for people beginning to work with CCKit (and who have minimal/medium coding experience)?
  2. Then explain ––for the same amateurs––how best to combine this repo with Fabric or the older commercial paper tutorials? I'd like to think we're talking about half a page, maximum, and that extra information would make CCKit accessible/useful to a lot more people! It would also increase its educational value.

Thanks so much
David
UCLA
https://www.davidmacfadyen.com/

invokeTransferFrom() is problem in erc20 example

Hi, I ran the erc20 example and it seems to be a problem with invokeTransferFrom().

allowance, err := getAllowance(c, fromMspId, fromCertId, invoker.GetMSPID(), invoker.GetID())

In the above code, the argument value corresponding to 'from' and the argument value position of 'invoker' seem to be reversed.

Please confirm.

Thanks

Couple Of Questions !

Thanks for the wonderful work !

Q1. Where is main function in this program ?https://gist.github.com/vitiko/ca8ec2631e3978fcdaa3a237b4c4b43d#file-cars_chaincode_example-go 

Q-2 : Is Owner really important ?

Q-3. I tried to use this , but received on following : return t.router.Handle(stub) on Invoke
2018–09–04 06:09:15.443 UTC [grpc] Printf -> DEBU 006 pickfirstBalancer: HandleSubConnStateChange: 0xc4203c5270, CONNECTING
2018–09–04 06:09:15.444 UTC [grpc] Printf -> DEBU 007 pickfirstBalancer: HandleSubConnStateChange: 0xc4203c5270, READY
panic: runtime error: invalid memory address or nil pointer dereference
 panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x30 pc=0x8cac06]

Q-4: On init can we remove it : return owner.SetFromCreator(cc.router.Context(init, stub)) and keep return shim.Success(nil) ?

Q-5 : How to Debug it with breakpoint , it without creating HLF network and deploying there ?

Q-6 : Can you send running full program without owner ? and with main included too.

cckit unit test returns "not implemented"

I am trying to test 'RecordCreate' function which calls a query function context.Stub().GetQueryResult(). This function is returning an error with message "not implemented". The function calls context.State().Get() is working fine.

Text execution logs:

Running Suite: Chaincode
========================
Random Seed: 1603439528
Will run 1 of 1 specs

***** BEFORE SUITE *****
Init() called
2020-10-23 13:22:08.188 IST [TEST] Debugf -> DEBU 001 state KEY: OWNER
2020-10-23 13:22:08.188 IST [TEST] Debugf -> DEBU 002 state check EXISTENCE OWNER
2020-10-23 13:22:08.188 IST [TEST] Debugf -> DEBU 003 state KEY: OWNER
2020-10-23 13:22:08.188 IST [TEST] Debugf -> DEBU 004 state check EXISTENCE OWNER
2020-10-23 13:22:08.188 IST [TEST] Debugf -> DEBU 005 state KEY: OWNER
2020-10-23 13:22:08.188 IST [TEST] Debugf -> DEBU 006 state PUT with string key: OWNER
2020-10-23 13:22:08.188 IST [TEST] Debug -> DEBU 007 router handler:  RecordCreate
********** CreateRecord Called **********
2020-10-23 13:22:08.188 IST [TEST] Errorf -> ERRO 008 router handler error: RecordCreate: {"code":500,"msg":"not implemented"}
• Failure [0.001 seconds]
TEST
/home/akshay/chaincode/chaincode_test.go:37
  /home/akshay/TEST/chaincode/chaincode_test.go:48
    Should be able to create record [It]
    /home/akshay/TEST/chaincode/chaincode_test.go:49

    {"code":500,"msg":"not implemented"}
    Expected
        <int>: 500
    to equal
        <int>: 200

    /home/akshay/pkg/mod/github.com/s7techlab/[email protected]/testing/expect/response.go:16
------------------------------


Summarizing 1 Failure:

[Fail] TEST Record Flow [It] Should be able to create record 
/home/akshay/pkg/mod/github.com/s7techlab/[email protected]/testing/expect/response.go:16

Ran 1 of 1 Specs in 0.001 seconds
FAIL! -- 0 Passed | 1 Failed | 0 Pending | 0 Skipped
--- FAIL: TestChaincode (0.00s)
FAIL
exit status 1
FAIL	chaincode	0.061s

chaincode to chaincode invokes dumps data into state on the wrong moment

How it works in Fabric
Execution flow
backend -> init tx -> cc1 (initial call) -> cc2 (cc2cc invokation) -> cc1 (processes result of cc2 execution) -> tx ends, rwset contains data from cc1 and cc2 -> peer commits block with data from cc1 and cc2 (state updated)

How it works in cckit's testing package
init tx -> cc1 (initial call) -> cc2 (cc2cc, and it saves data HERE to mockstub's putState), cc1 (processes result of cc2 execution with the updated state)

Upgrade to fabric-go-chaincode

The shim package used by cckit is the only and deprecated github.com/hyperledger/fabric/core/chaincode/shim which has since been archived. Is there any plans to update the service to utilise the new and supported fabric-go-chaincode/shim packages?

ERC20 Contract invokeTransferFrom

if err = setAllowance(c, fromMspId, fromCertId, invoker.GetID(), invoker.GetID(), allowance-amount); err != nil {
		return nil, err
	}

should be

if err = setAllowance(c, fromMspId, fromCertId, invoker.GetMSPID(), invoker.GetID(), allowance-amount); err != nil {
		return nil, err
	}

Getting a deprecation warning message while deploying a chaincode

I am getting the follwing warning message while trying to deploy chaincode in dev mode:

WARNING: Package "github.com/golang/protobuf/protoc-gen-go/generator" is deprecated.
A future release of golang/protobuf will delete this package,
which has long been excluded from the compatibility promise.

If it's not fatal, Is there a way to remove this warning?

ERC20 contract allowance check

if allowance <= amount {
		return nil, ErrSpenderNotHaveAllowance
}

should be

if allowance < amount {
		return nil, ErrSpenderNotHaveAllowance
	}

It there any way to use Stub.GetQueryResult?

I want to read data as a sorted list. I added CouchDB query:

func list(c router.Context) (interface{}, error) {
	queryString := `{
        "selector": {},
        "sort": [
            {"id": "asc"}
        ]
	}`

	iter, err1 := c.Stub().GetQueryResult(queryString)
	if err1 != nil {
		return nil, errors.Wrap(err1, "failed to GetQueryResult")
	}
	defer iter.Close()

	items := []interface{}{}
	for iter.HasNext() {
		kvResult, err := iter.Next()
		if err != nil {
			return nil, errors.Wrap(err1, "fetch error")
		}
		item := QueueItem{}
		err2 := json.Unmarshal(kvResult.Value, &item)
		if err2 != nil {
			return nil, errors.Wrap(err1, "value unmarshal error")
		}
		items = append(items, item)
	}
	return items, nil
}

But the test by mock tells me `failed to GetQueryResult: not implemented.
Is it so because I run the test with mock?

Support for Private Data and Unit testing

Hi Victor,

Simply marvelous work on this kit. Had few questions on Private Data and Unit testing around same. Are these supported?
Any examples around these?

Best Regards,
Govinda

NULL characters appearing in CouchDB when using composite keys

Hi,

I noticed that when using the Key() interface for user-defined structs, the objects get stored in the CouchDB state with some Unicode NULL characters.

Example:

 "id": "\u0000ASSET\u00001c730708-70b2-4f99-b4c1-cf6689006e32\u0000",
 "key": "\u0000ASSET\u00001c730708-70b2-4f99-b4c1-cf6689006e32\u0000",

This does not happen when the composite key is passed directly as an []string.

Testing with mock data?

Hi! First of all, congratiulations on this kit, it's really helping me out to understand Fabric contracts and its quirks.

However, I couldn't find any documentation on how to perform tests with state mock data. Do the contracts need to be deployed to a HLF network to verify that the written business logic is actually working rather than just relying on test results?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.