Skip to content

Commit

Permalink
Merge pull request #25 from GoLedgerDev/develop
Browse files Browse the repository at this point in the history
Feature: Events
  • Loading branch information
samuelvenzi authored Aug 17, 2023
2 parents 09b1abc + 9cd9916 commit f385056
Show file tree
Hide file tree
Showing 18 changed files with 680 additions and 13 deletions.
20 changes: 20 additions & 0 deletions assets/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,26 @@ func (k *Key) Get(stub *sw.StubWrapper) (*Asset, errors.ICCError) {
return get(stub, pvtCollection, k.Key(), false)
}

// GetMany fetches assets entries from write set or ledger.
func GetMany(stub *sw.StubWrapper, keys []Key) ([]*Asset, errors.ICCError) {
var assets []*Asset

for _, k := range keys {
var pvtCollection string
if k.IsPrivate() {
pvtCollection = k.TypeTag()
}

asset, err := get(stub, pvtCollection, k.Key(), false)
if err != nil {
return nil, err
}
assets = append(assets, asset)
}

return assets, nil
}

// GetCommitted fetches asset entry from ledger.
func (a *Asset) GetCommitted(stub *sw.StubWrapper) (*Asset, errors.ICCError) {
var pvtCollection string
Expand Down
62 changes: 62 additions & 0 deletions assets/history.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package assets

import (
"encoding/json"
"net/http"

"github.com/goledgerdev/cc-tools/errors"
sw "github.com/goledgerdev/cc-tools/stubwrapper"
"github.com/hyperledger/fabric-chaincode-go/shim"
pb "github.com/hyperledger/fabric-protos-go/peer"
)

type HistoryResponse struct {
Result []map[string]interface{} `json:"result"`
Metadata *pb.QueryResponseMetadata `json:"metadata"`
}

func History(stub *sw.StubWrapper, key string, resolve bool) (*HistoryResponse, errors.ICCError) {
var resultsIterator shim.HistoryQueryIteratorInterface

resultsIterator, err := stub.GetHistoryForKey(key)
if err != nil {
return nil, errors.WrapErrorWithStatus(err, "failed to get history for key", http.StatusInternalServerError)
}
defer resultsIterator.Close()

historyResult := make([]map[string]interface{}, 0)

for resultsIterator.HasNext() {
queryResponse, err := resultsIterator.Next()
if err != nil {
return nil, errors.WrapErrorWithStatus(err, "error iterating response", 500)
}

var data map[string]interface{}

err = json.Unmarshal(queryResponse.Value, &data)
if err != nil {
return nil, errors.WrapErrorWithStatus(err, "failed to unmarshal queryResponse values", 500)
}

if resolve {
key, err := NewKey(data)
if err != nil {
return nil, errors.WrapError(err, "failed to create key object to resolve result")
}
asset, err := key.GetRecursive(stub)
if err != nil {
return nil, errors.WrapError(err, "failed to resolve result")
}
data = asset
}

historyResult = append(historyResult, data)
}

response := HistoryResponse{
Result: historyResult,
}

return &response, nil
}
16 changes: 14 additions & 2 deletions assets/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package assets

import (
"encoding/json"
"strings"

"github.com/goledgerdev/cc-tools/errors"
)
Expand Down Expand Up @@ -49,9 +50,20 @@ func NewKey(m map[string]interface{}) (k Key, err errors.ICCError) {
k[t] = v
}

// Validate if @key corresponds to asset type
key, keyExists := k["@key"]
if keyExists && key != nil {
_, typeExists := k["@assetType"].(string)
if typeExists {
index := strings.Index(k["@key"].(string), k["@assetType"].(string))
if index != 0 {
keyExists = false
}
}
}

// Generate object key
_, keyExists := k["@key"]
if !keyExists {
if !keyExists || key == nil {
var keyStr string
keyStr, err = GenerateKey(k)
if err != nil {
Expand Down
33 changes: 24 additions & 9 deletions assets/references.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,18 +127,20 @@ func (k Key) Refs(stub *sw.StubWrapper) ([]Key, errors.ICCError) {
}

// Referrers returns an array of Keys of all the assets pointing to asset.
func (a Asset) Referrers(stub *sw.StubWrapper) ([]Key, errors.ICCError) {
// assetTypeFilter can be used to filter the results by asset type.
func (a Asset) Referrers(stub *sw.StubWrapper, assetTypeFilter ...string) ([]Key, errors.ICCError) {
assetKey := a.Key()
return referrers(stub, assetKey)
return referrers(stub, assetKey, assetTypeFilter)
}

// Referrers returns an array of Keys of all the assets pointing to key.
func (k Key) Referrers(stub *sw.StubWrapper) ([]Key, errors.ICCError) {
// assetTypeFilter can be used to filter the results by asset type.
func (k Key) Referrers(stub *sw.StubWrapper, assetTypeFilter ...string) ([]Key, errors.ICCError) {
assetKey := k.Key()
return referrers(stub, assetKey)
return referrers(stub, assetKey, assetTypeFilter)
}

func referrers(stub *sw.StubWrapper, assetKey string) ([]Key, errors.ICCError) {
func referrers(stub *sw.StubWrapper, assetKey string, assetTypeFilter []string) ([]Key, errors.ICCError) {
queryIt, err := stub.GetStateByPartialCompositeKey(assetKey, []string{})
if err != nil {
return nil, errors.WrapErrorWithStatus(err, "failed to check reference index", 500)
Expand Down Expand Up @@ -194,15 +196,28 @@ func referrers(stub *sw.StubWrapper, assetKey string) ([]Key, errors.ICCError) {

var ret []Key
for _, retKey := range retKeys {
ret = append(ret, Key{
"@assetType": strings.Split(retKey, ":")[0],
"@key": retKey,
})
assetType := strings.Split(retKey, ":")[0]
if len(assetTypeFilter) <= 0 || contains(assetTypeFilter, assetType) {
ret = append(ret, Key{
"@assetType": strings.Split(retKey, ":")[0],
"@key": retKey,
})
}
}

return ret, nil
}

func contains(s []string, str string) bool {
for _, v := range s {
if v == str {
return true
}
}

return false
}

// validateRefs checks if subAsset references exist in blockchain.
func (a Asset) validateRefs(stub *sw.StubWrapper) errors.ICCError {
// Fetch references contained in asset
Expand Down
79 changes: 79 additions & 0 deletions events/event.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package events

import (
"fmt"
"net/http"

"github.com/goledgerdev/cc-tools/errors"
sw "github.com/goledgerdev/cc-tools/stubwrapper"
)

type EventType float64

const (
EventLog EventType = iota
EventTransaction
EventCustom
)

// Event is the struct defining a primitive event.
type Event struct {
// Tag is how the event will be referenced
Tag string `json:"tag"`

// Label is the pretty event name for logs
Label string `json:"label"`

// Description is a simple explanation describing the meaning of the event.
Description string `json:"description"`

// BaseLog is the basisc log message for the event
BaseLog string `json:"baseLog"`

// Type is the type of event
Type EventType `json:"type"`

// Receivers is an array that specifies which organizations will receive the event.
// Accepts either basic strings for exact matches
// eg. []string{'org1MSP', 'org2MSP'}
// or regular expressions
// eg. []string{`$org\dMSP`} and cc-tools will
// check for a match with regular expression `org\dMSP`
Receivers []string `json:"receivers,omitempty"`

// Transaction is the transaction that the event triggers (if of type EventTransaction)
Transaction string `json:"transaction"`

// Channel is the channel of the transaction that the event triggers (if of type EventTransaction)
// If empty, the event will trigger on the same channel as the transaction that calls the event
Channel string `json:"channel"`

// Chaincode is the chaincode of the transaction that the event triggers (if of type EventTransaction)
// If empty, the event will trigger on the same chaincode as the transaction that calls the event
Chaincode string `json:"chaincode"`

// CustomFunction is used an event of type "EventCustom" is called.
// It is a function that receives a stub and a payload and returns an error.
CustomFunction func(*sw.StubWrapper, []byte) error `json:"-"`

// ReadOnly indicates if the CustomFunction has the ability to alter the world state (if of type EventCustom).
ReadOnly bool `json:"readOnly"`
}

func (event Event) CallEvent(stub *sw.StubWrapper, payload []byte) errors.ICCError {
err := stub.SetEvent(event.Tag, payload)
if err != nil {
return errors.WrapError(err, "stub.SetEvent call error")
}

return nil
}

func CallEvent(stub *sw.StubWrapper, eventTag string, payload []byte) errors.ICCError {
event := FetchEvent(eventTag)
if event == nil {
return errors.NewCCError(fmt.Sprintf("event named %s does not exist", eventTag), http.StatusBadRequest)
}

return event.CallEvent(stub, payload)
}
26 changes: 26 additions & 0 deletions events/eventList.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package events

// eventList is the list which should contain all defined events
var eventList = []Event{}

// EventList returns a copy of the eventList variable.
func EventList() []Event {
listCopy := make([]Event, len(eventList))
copy(listCopy, eventList)
return listCopy
}

// InitEventList appends custom events to eventList to avoid initialization loop.
func InitEventList(l []Event) {
eventList = l
}

// FetchEvent returns a pointer to the event object or nil if event is not found.
func FetchEvent(eventTag string) *Event {
for _, event := range eventList {
if event.Tag == eventTag {
return &event
}
}
return nil
}
9 changes: 9 additions & 0 deletions stubwrapper/stubWrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,12 @@ func (sw *StubWrapper) SplitCompositeKey(compositeKey string) (string, []string,
}
return key, keys, nil
}

func (sw *StubWrapper) SetEvent(name string, payload []byte) errors.ICCError {
err := sw.Stub.SetEvent(name, payload)
if err != nil {
return errors.WrapError(err, "stub.SetEvent call error")
}

return nil
}
7 changes: 6 additions & 1 deletion test/assets_assetType_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ func TestArrayFromAssetTypeList(t *testing.T) {
"required": true,
"label": "Library Name",
"dataType": "string",
"writers": []interface{}{"org3MSP"},
"writers": []interface{}{`$org\dMSP`},
},
map[string]interface{}{
"tag": "books",
Expand All @@ -289,6 +289,11 @@ func TestArrayFromAssetTypeList(t *testing.T) {
"label": "Entrance Code for the Library",
"dataType": "->secret",
},
map[string]interface{}{
"tag": "librarian",
"label": "Librarian",
"dataType": "->person",
},
},
},
}
Expand Down
33 changes: 33 additions & 0 deletions test/assets_events_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package test

import (
"log"
"reflect"
"testing"

"github.com/goledgerdev/cc-tools/events"
)

func TestFetchEvent(t *testing.T) {
event := *events.FetchEvent("createLibraryLog")
expectedEvent := testEventTypeList[0]

if !reflect.DeepEqual(event, expectedEvent) {
log.Println("these should be deeply equal")
log.Println(event)
log.Println(expectedEvent)
t.FailNow()
}
}

func TestFetchEventList(t *testing.T) {
eventList := events.EventList()
expectedEventList := testEventTypeList

if !reflect.DeepEqual(eventList, expectedEventList) {
log.Println("these should be deeply equal")
log.Println(eventList)
log.Println(expectedEventList)
t.FailNow()
}
}
Loading

0 comments on commit f385056

Please sign in to comment.