Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CC-Tools 1.0 #47

Merged
merged 48 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
517373b
:arrow_up: Bump gopkg.in/yaml.v3
dependabot[bot] Aug 30, 2023
9869b5b
Initial '@asset' datatype implementation
andremacedopv Sep 6, 2023
16c57cc
Key generation for @asset datatype
andremacedopv Sep 6, 2023
a1e8d23
Add '@asset' datatype tests
andremacedopv Sep 6, 2023
1292890
Modify '@asset' type to '->@asset'
andremacedopv Sep 6, 2023
87866c3
Improve tests and validation for '->@asset'
andremacedopv Sep 6, 2023
337337b
Improve tx tests with generic associations
andremacedopv Sep 6, 2023
c5815fd
Create permissioning by msp, org unit and attributes
mikaellafs Sep 11, 2023
b54edd6
Refactor tx permissioning check to use new model
mikaellafs Sep 11, 2023
fa60f83
Initialize creator in mock stub
mikaellafs Sep 12, 2023
4570f75
Remove @lastTouchBy from everything but history queries
mikaellafs Sep 13, 2023
ba0b37f
Add collection name property to assetType
mikaellafs Sep 15, 2023
aa76fb6
Fix ->@asset prop validation
andremacedopv Sep 15, 2023
c8e1362
Improve ->@asset prop validation
andremacedopv Sep 15, 2023
0a240ac
Add collection to getSchema
andremacedopv Sep 18, 2023
191214f
Check if asset need to be updated in updateRecursive
mikaellafs Sep 19, 2023
fd6c110
Add test case to updateRecursive test
mikaellafs Sep 19, 2023
3b227b9
Avoid getRecursive in updateRecursive
mikaellafs Sep 20, 2023
9b956c9
Merge branch 'develop' into dependabot/go_modules/gopkg.in/yaml.v3-3.0.0
andremacedopv Sep 25, 2023
436e77d
Merge branch 'develop' into feature/permissioning
andremacedopv Sep 25, 2023
efaed23
Merge branch 'main' into refactor/proplasttouchby
andremacedopv Sep 25, 2023
47b940c
Merge branch 'develop' into refactor/private-coll-names
andremacedopv Sep 25, 2023
b60ed6b
Merge branch 'develop' into feature/asset-datatype
andremacedopv Sep 25, 2023
4868ae4
Merge branch 'develop' into refactor/update-recursive
andremacedopv Sep 25, 2023
f97e1ad
Create CODEOWNERS file
AlineLermen Oct 13, 2023
a6ada05
Create MAINTAINERS file
AlineLermen Oct 13, 2023
4a70de6
Fix table in MAINTAINERS
AlineLermen Oct 13, 2023
44e7c5b
Fix resolve history reference
mikaellafs Oct 19, 2023
c43acbc
Merge pull request #37 from hyperledger-labs/repo-files
samuelvenzi Oct 20, 2023
8853db5
Merge pull request #39 from hyperledger-labs/fix/resolve-history
samuelvenzi Oct 25, 2023
15fc599
Merge pull request #26 from hyperledger-labs/dependabot/go_modules/go…
samuelvenzi Oct 27, 2023
463aa4f
Merge pull request #29 from hyperledger-labs/refactor/proplasttouchby
samuelvenzi Oct 27, 2023
6ea1747
Merge pull request #30 from hyperledger-labs/refactor/private-coll-names
samuelvenzi Oct 27, 2023
4bcc3dc
Merge pull request #31 from hyperledger-labs/feature/asset-datatype
samuelvenzi Oct 27, 2023
6546b51
Update Create Asset with generic association test to remove lastTouch…
andremacedopv Oct 27, 2023
3dd7a41
Merge branch 'develop' into refactor/update-recursive
andremacedopv Oct 27, 2023
d04e2d6
Fix update recursive expected response
andremacedopv Oct 27, 2023
b7ef139
Remove root param from putRecursive
mikaellafs Oct 27, 2023
01e3bd7
Merge pull request #32 from hyperledger-labs/refactor/update-recursive
andremacedopv Dec 12, 2023
b44550e
Fix resolve history reference
mikaellafs Jan 10, 2024
808a594
Merge pull request #42 from hyperledger-labs/fix/resolve-history
samuelvenzi May 7, 2024
3c78ac1
Merge pull request #28 from hyperledger-labs/feature/permissioning
samuelvenzi May 7, 2024
a1c9712
Bump go version to 1.21
andremacedopv May 8, 2024
70ed47b
Update CC-Tools version
andremacedopv May 8, 2024
b7274bf
Update depedencies
andremacedopv May 8, 2024
ed1264a
Update tested versions on Readme
andremacedopv May 8, 2024
10894c1
Return lastTouchedBy to returns
andremacedopv May 9, 2024
a32bd38
Merge pull request #45 from hyperledger-labs/bump-to-go-1-21
samuelvenzi Jun 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.16
go-version: 1.21

- name: Build
run: go build -v ./...
Expand Down
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @hyperledger-labs/cc-tools-committers
18 changes: 18 additions & 0 deletions MANTAINERS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Maintainers

### **Active Maintainers**

| Nome | Github |
|:-------|:--------|
| André Macedo | [andremacedopv](https://github.com/andremacedopv) |
| Samuel Venzi | [samuelvenzi](https://github.com/samuelvenzi) |
| Lucas Campelo | [lucas-campelo](https://github.com/lucas-campelo) |
| Marcos Sarres | [goledger](https://github.com/goledger) |


### **Retired Maintainers**
| Nome | Github |
|:-------|:--------|
| Bruno Andreghetti | [bandreghetti](https://github.com/bandreghetti) |
| João Pedro | [JoaoPedroAssis](https://github.com/JoaoPedroAssis) |
| Arthur Paiva | [ArthurPaivaT](https://github.com/ArthurPaivaT) |
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/hyperledger-labs/cc-tools)](https://goreportcard.com/report/github.com/hyperledger-labs/cc-tools)
[![GoDoc](https://godoc.org/github.com/hyperledger-labs/cc-tools?status.svg)](https://godoc.org/github.com/hyperledger-labs/cc-tools)

This project is a GoLedger open-source project aimed at providing tools for Hyperledger Fabric chaincode development in Golang. This might have breaking changes before we arrive at release v1.0.0.
This project is a GoLedger open-source project aimed at providing tools for Hyperledger Fabric chaincode development in Golang.

## Getting Started

Make sure you visit the repository [hyperledger-labs/cc-tools-demo](https://github.com/hyperledger-labs/cc-tools-demo), which is a template of a functional chaincode that uses cc-tools and provides ready-to-use scripts to deploy development networks. This is our preferred way of working, but you can feel free to import the package and assemble the chaincode as you choose.

CC Tools has been tested with Hyperledger Fabric 1.x and 2.x realeases.
CC Tools has been tested with Hyperledger Fabric v2.2, v2.4 and v2.5 releases.

## Features
- Standard asset data mapping (and their properties)
Expand Down
89 changes: 89 additions & 0 deletions accesscontrol/allowCaller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package accesscontrol

import (
"regexp"

"github.com/hyperledger-labs/cc-tools/errors"
"github.com/hyperledger/fabric-chaincode-go/pkg/cid"
"github.com/hyperledger/fabric-chaincode-go/shim"
)

func AllowCaller(stub shim.ChaincodeStubInterface, allowedCallers []Caller) (bool, error) {
if allowedCallers == nil {
return true, nil
}

callerMSP, err := cid.GetMSPID(stub)
if err != nil {
return false, errors.WrapError(err, "could not get MSP id")
}

var grantedPermission bool
for i := 0; i < len(allowedCallers) && !grantedPermission; i++ {
allowed := allowedCallers[i]
isAllowedMSP, err := checkMSP(callerMSP, allowed.MSP)
if err != nil {
return false, errors.WrapError(err, "failed to check MSP")
}

isAllowedOU, err := checkOU(stub, allowed.OU)
if err != nil {
return false, errors.WrapError(err, "failed to check OU")
}

isAllowedAttrs, err := checkAttributes(stub, allowed.Attributes)
if err != nil {
return false, errors.WrapError(err, "failed to check attributes")
}

grantedPermission = isAllowedMSP && isAllowedOU && isAllowedAttrs
}

return grantedPermission, nil
}

func checkMSP(callerMsp, allowedMSP string) (bool, error) {
if len(allowedMSP) <= 1 {
return true, nil
}

// if caller is regexp
if allowedMSP[0] == '$' {
match, err := regexp.MatchString(allowedMSP[1:], callerMsp)
if err != nil {
return false, errors.NewCCError("failed to check if caller matches regexp", 500)
}

return match, nil
}

// if caller is not regexss
return callerMsp == allowedMSP, nil
}

func checkOU(stub shim.ChaincodeStubInterface, allowedOU string) (bool, error) {
if allowedOU == "" {
return true, nil
}

return cid.HasOUValue(stub, allowedOU)
}

func checkAttributes(stub shim.ChaincodeStubInterface, allowedAttrs map[string]string) (bool, error) {
if allowedAttrs == nil {
return true, nil
}

for key, value := range allowedAttrs {
callerValue, _, err := cid.GetAttributeValue(stub, key)
if err != nil {
return false, err
}

if callerValue != value {
return false, nil
}
}

return true, nil
}
7 changes: 7 additions & 0 deletions accesscontrol/caller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package accesscontrol

type Caller struct {
MSP string `json:"msp"`
OU string `json:"ou"`
Attributes map[string]string `json:"attributes"`
}
10 changes: 10 additions & 0 deletions assets/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,16 @@ func (a Asset) IsPrivate() bool {
return assetTypeDef.IsPrivate()
}

func (a Asset) CollectionName() string {
// Fetch asset properties
assetTypeDef := a.Type()
if assetTypeDef == nil {
return ""
}

return assetTypeDef.CollectionName()
}

// TypeTag returns the @assetType attribute.
func (a Asset) TypeTag() string {
assetType, _ := a["@assetType"].(string)
Expand Down
1 change: 1 addition & 0 deletions assets/assetProp.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type AssetProp struct {
// Primary types: "string", "number", "integer", "boolean", "datetime"
// Special types:
// -><assetType>: the specific asset type key (reference) as defined by <assetType> in the assets packages
// ->@asset: an arbitrary asset type key (reference)
// []<type>: an array of elements specified by <type> as any of the above valid types
DataType string `json:"dataType"`

Expand Down
16 changes: 16 additions & 0 deletions assets/assetType.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ type AssetType struct {

// Dynamic is a flag that indicates if the asset type is dynamic.
Dynamic bool `json:"dynamic,omitempty"`

// Private collection name it belongs to. When empty and len(readers) > 0,
// Tag is considered instead
Collection string `json:"collection,omitempty"`
}

// Keys returns a list of asset properties which are defined as primary keys. (IsKey == true)
Expand All @@ -48,6 +52,9 @@ func (t AssetType) SubAssets() (subAssets []AssetProp) {
dataType := prop.DataType
dataType = strings.TrimPrefix(dataType, "[]")
dataType = strings.TrimPrefix(dataType, "->")
if dataType == "@asset" {
subAssets = append(subAssets, prop)
}
subAssetType := FetchAssetType(dataType)
if subAssetType != nil {
subAssets = append(subAssets, prop)
Expand Down Expand Up @@ -81,6 +88,15 @@ func (t AssetType) IsPrivate() bool {
return len(t.Readers) > 0
}

// CollectionName returns the private collection name. Default is tag.
func (t AssetType) CollectionName() string {
if t.Collection == "" {
return t.Tag
}

return t.Collection
}

// ToMap returns a map representation of the asset type.
func (t AssetType) ToMap() map[string]interface{} {
return map[string]interface{}{
Expand Down
51 changes: 51 additions & 0 deletions assets/dataType.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"math"
"net/http"
"strconv"
"strings"
"time"

"github.com/hyperledger-labs/cc-tools/errors"
Expand All @@ -29,6 +30,11 @@ type DataType struct {

// CustomDataTypes allows cc developer to inject custom primitive data types
func CustomDataTypes(m map[string]DataType) error {
// Avoid initialization cycle
if FetchAssetType("->@asset") == nil {
dataTypeMap["->@asset"] = &assetDatatype
}

for k, v := range m {
if v.Parse == nil {
return errors.NewCCError(fmt.Sprintf("invalid custom data type '%s': nil Parse function", k), 500)
Expand Down Expand Up @@ -192,3 +198,48 @@ var dataTypeMap = map[string]*DataType{
},
},
}

var assetDatatype = DataType{
AcceptedFormats: []string{"->@asset"},
Parse: func(data interface{}) (string, interface{}, errors.ICCError) {
dataVal, ok := data.(map[string]interface{})
if !ok {
switch v := data.(type) {
case []byte:
err := json.Unmarshal(v, &dataVal)
if err != nil {
return "", nil, errors.WrapErrorWithStatus(err, "failed to unmarshal []byte into map[string]interface{}", http.StatusBadRequest)
}
case string:
err := json.Unmarshal([]byte(v), &dataVal)
if err != nil {
return "", nil, errors.WrapErrorWithStatus(err, "failed to unmarshal string into map[string]interface{}", http.StatusBadRequest)
}
default:
return "", nil, errors.NewCCError(fmt.Sprintf("asset property must be either a byte array or a string, but received type is: %T", data), http.StatusBadRequest)
}
}

key, er := GenerateKey(dataVal)
if er != nil {
return "", nil, errors.WrapError(er, "failed to generate key")
}
dataVal["@key"] = key

assetType, ok := dataVal["@assetType"].(string)
if ok {
if !strings.Contains(key, assetType) {
return "", nil, errors.NewCCError(fmt.Sprintf("asset type '%s' doesnt match key '%s'", assetType, key), http.StatusBadRequest)
}
} else {
dataVal["@assetType"] = key[:strings.IndexByte(key, ':')]
}

retVal, err := json.Marshal(dataVal)
if err != nil {
return "", nil, errors.WrapErrorWithStatus(err, "failed to marshal return value", http.StatusInternalServerError)
}

return string(retVal), dataVal, nil
},
}
2 changes: 1 addition & 1 deletion assets/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func (a *Asset) delete(stub *sw.StubWrapper) ([]byte, errors.ICCError) {
return nil, errors.WrapError(err, "failed to marshal asset")
}
} else {
err = stub.DelPrivateData(a.TypeTag(), a.Key())
err = stub.DelPrivateData(a.CollectionName(), a.Key())
if err != nil {
return nil, errors.WrapError(err, "failed to delete state from private collection")
}
Expand Down
3 changes: 3 additions & 0 deletions assets/dynamicAssetTypeFuncs.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ func CheckDataType(dataType string, newTypesList []interface{}) errors.ICCError

if strings.HasPrefix(trimDataType, "->") {
trimDataType = strings.TrimPrefix(trimDataType, "->")
if trimDataType == "@asset" {
return nil
}

assetType := FetchAssetType(trimDataType)
if assetType == nil {
Expand Down
20 changes: 10 additions & 10 deletions assets/existsInLedger.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import (
sw "github.com/hyperledger-labs/cc-tools/stubwrapper"
)

func existsInLedger(stub *sw.StubWrapper, isPrivate bool, typeTag, key string) (bool, errors.ICCError) {
func existsInLedger(stub *sw.StubWrapper, isPrivate bool, collection, key string) (bool, errors.ICCError) {
var assetBytes []byte
var err error
if isPrivate {
_, isMock := stub.Stub.(*mock.MockStub)
if isMock {
assetBytes, err = stub.GetPrivateData(typeTag, key)
assetBytes, err = stub.GetPrivateData(collection, key)
} else {
assetBytes, err = stub.GetPrivateDataHash(typeTag, key)
assetBytes, err = stub.GetPrivateDataHash(collection, key)
}
} else {
assetBytes, err = stub.GetState(key)
Expand All @@ -34,28 +34,28 @@ func (a *Asset) ExistsInLedger(stub *sw.StubWrapper) (bool, errors.ICCError) {
if a.Key() == "" {
return false, errors.NewCCError("asset key is empty", 500)
}
return existsInLedger(stub, a.IsPrivate(), a.TypeTag(), a.Key())
return existsInLedger(stub, a.IsPrivate(), a.CollectionName(), a.Key())
}

// ExistsInLedger checks if asset referenced by a key object currently has a state.
func (k *Key) ExistsInLedger(stub *sw.StubWrapper) (bool, errors.ICCError) {
if k.Key() == "" {
return false, errors.NewCCError("key is empty", 500)
}
return existsInLedger(stub, k.IsPrivate(), k.TypeTag(), k.Key())
return existsInLedger(stub, k.IsPrivate(), k.CollectionName(), k.Key())
}

// ----------------------------------------

func committedInLedger(stub *sw.StubWrapper, isPrivate bool, typeTag, key string) (bool, errors.ICCError) {
func committedInLedger(stub *sw.StubWrapper, isPrivate bool, collection, key string) (bool, errors.ICCError) {
var assetBytes []byte
var err error
if isPrivate {
_, isMock := stub.Stub.(*mock.MockStub)
if isMock {
assetBytes, err = stub.Stub.GetPrivateData(typeTag, key)
assetBytes, err = stub.Stub.GetPrivateData(collection, key)
} else {
assetBytes, err = stub.Stub.GetPrivateDataHash(typeTag, key)
assetBytes, err = stub.Stub.GetPrivateDataHash(collection, key)
}
} else {
assetBytes, err = stub.Stub.GetState(key)
Expand All @@ -75,13 +75,13 @@ func (a *Asset) CommittedInLedger(stub *sw.StubWrapper) (bool, errors.ICCError)
if a.Key() == "" {
return false, errors.NewCCError("asset key is empty", 500)
}
return committedInLedger(stub, a.IsPrivate(), a.TypeTag(), a.Key())
return committedInLedger(stub, a.IsPrivate(), a.CollectionName(), a.Key())
}

// CommittedInLedger checks if asset referenced by a key object currently has a state committed in ledger.
func (k *Key) CommittedInLedger(stub *sw.StubWrapper) (bool, errors.ICCError) {
if k.Key() == "" {
return false, errors.NewCCError("key is empty", 500)
}
return committedInLedger(stub, k.IsPrivate(), k.TypeTag(), k.Key())
return committedInLedger(stub, k.IsPrivate(), k.CollectionName(), k.Key())
}
13 changes: 8 additions & 5 deletions assets/generateKey.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,6 @@ func GenerateKey(asset map[string]interface{}) (string, errors.ICCError) {
keySeed += seed
} else {
// If key is a subAsset, generate subAsset's key to append to seed
assetTypeDef := FetchAssetType(dataTypeName)
if assetTypeDef == nil {
return "", errors.NewCCError(fmt.Sprintf("internal error: invalid sub asset type %s", prop.DataType), 500)
}

var propMap map[string]interface{}
switch t := propInterface.(type) {
case map[string]interface{}:
Expand All @@ -108,6 +103,14 @@ func GenerateKey(asset map[string]interface{}) (string, errors.ICCError) {
return "", errors.NewCCError(errMsg, 400)
}

if dataTypeName == "@asset" {
dataTypeName = propMap["@assetType"].(string)
}
assetTypeDef := FetchAssetType(dataTypeName)
if assetTypeDef == nil {
return "", errors.NewCCError(fmt.Sprintf("internal error: invalid sub asset type %s", prop.DataType), 500)
}

propMap["@assetType"] = dataTypeName
subAssetKey, err := GenerateKey(propMap)
if err != nil {
Expand Down
Loading
Loading