// CreateAsset creates a new asset by placing the main asset details in the assetCollection // that can be read by both organizations. The appraisal value is stored in the owners org specific collection. func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface) error { // Get new asset from transient map transientMap, err := ctx.GetStub().GetTransient() if err != nil { return fmt.Errorf("error getting transient: %v", err) } // Asset properties are private, therefore they get passed in transient field, instead of func args transientAssetJSON, ok := transientMap["asset_properties"] if !ok { //log error to stdout return fmt.Errorf("asset not found in the transient map input") } type assetTransientInput struct { Type string `json:"objectType"` //Type is used to distinguish the various types of objects in state database ID string `json:"assetID"` Color string `json:"color"` Size int `json:"size"` AppraisedValue int `json:"appraisedValue"` } var assetInput assetTransientInput err = json.Unmarshal(transientAssetJSON, &assetInput) if err != nil { return fmt.Errorf("failed to unmarshal JSON: %v", err) } if len(assetInput.Type) == 0 { return fmt.Errorf("objectType field must be a non-empty string") } if len(assetInput.ID) == 0 { return fmt.Errorf("assetID field must be a non-empty string") } if len(assetInput.Color) == 0 { return fmt.Errorf("color field must be a non-empty string") } if assetInput.Size <= 0 { return fmt.Errorf("size field must be a positive integer") } if assetInput.AppraisedValue <= 0 { return fmt.Errorf("appraisedValue field must be a positive integer") } // Check if asset already exists assetAsBytes, err := ctx.GetStub().GetPrivateData(assetCollection, assetInput.ID) if err != nil { return fmt.Errorf("failed to get asset: %v", err) } else if assetAsBytes != nil { fmt.Println("Asset already exists: " + assetInput.ID) return fmt.Errorf("this asset already exists: " + assetInput.ID) } // Get ID of submitting client identity clientID, err := submittingClientIdentity(ctx) if err != nil { return err } // Verify that the client is submitting request to peer in their organization // This is to ensure that a client from another org doesn't attempt to read or // write private data from this peer. err = verifyClientOrgMatchesPeerOrg(ctx) if err != nil { return fmt.Errorf("CreateAsset cannot be performed: Error %v", err) } // Make submitting client the owner asset := Asset{ Type: assetInput.Type, ID: assetInput.ID, Color: assetInput.Color, Size: assetInput.Size, Owner: clientID, } assetJSONasBytes, err := json.Marshal(asset) if err != nil { return fmt.Errorf("failed to marshal asset into JSON: %v", err) } // Save asset to private data collection // Typical logger, logs to stdout/file in the fabric managed docker container, running this chaincode // Look for container name like dev-peer0.org1.example.com-{chaincodename_version}-xyz log.Printf("CreateAsset Put: collection %v, ID %v, owner %v", assetCollection, assetInput.ID, clientID) err = ctx.GetStub().PutPrivateData(assetCollection, assetInput.ID, assetJSONasBytes) if err != nil { return fmt.Errorf("failed to put asset into private data collection: %v", err) } // Save asset details to collection visible to owning organization assetPrivateDetails := AssetPrivateDetails{ ID: assetInput.ID, AppraisedValue: assetInput.AppraisedValue, } assetPrivateDetailsAsBytes, err := json.Marshal(assetPrivateDetails) // marshal asset details to JSON if err != nil { return fmt.Errorf("failed to marshal into JSON: %v", err) } // Get collection name for this organization. orgCollection, err := getCollectionName(ctx) if err != nil { return fmt.Errorf("failed to infer private collection name for the org: %v", err) } // Put asset appraised value into owners org specific private data collection log.Printf("Put: collection %v, ID %v", orgCollection, assetInput.ID) err = ctx.GetStub().PutPrivateData(orgCollection, assetInput.ID, assetPrivateDetailsAsBytes) if err != nil { return fmt.Errorf("failed to put asset private details: %v", err) } return nil }