mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-17 07:25:10 +00:00
Build failures because the startFabric.sh script specified that the chaincode required initialization, which is both legacy behavior and is unnecessary. The chaincode is updated to use the Contract API instead of the legacy / low-level chaincode API. The client application is also simplified. Signed-off-by: Mark S. Lewis <Mark.S.Lewis@outlook.com>
315 lines
10 KiB
Go
315 lines
10 KiB
Go
/*
|
|
* Copyright IBM Corp All Rights Reserved
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Demonstrates how to handle data in an application with a high transaction volume where the transactions
|
|
* all attempt to change the same key-value pair in the ledger. Such an application will have trouble
|
|
* as multiple transactions may read a value at a certain version, which will then be invalid when the first
|
|
* transaction updates the value to a new version, thus rejecting all other transactions until they're
|
|
* re-executed.
|
|
* Rather than relying on serialization of the transactions, which is slow, this application initializes
|
|
* a value and then accepts deltas of that value which are added as rows to the ledger. The actual value
|
|
* is then an aggregate of the initial value combined with all of the deltas. Additionally, a pruning
|
|
* function is provided which aggregates and deletes the deltas to update the initial value. This should
|
|
* be done during a maintenance window or when there is a lowered transaction volume, to avoid the proliferation
|
|
* of millions of rows of data.
|
|
*
|
|
* @author Alexandre Pauwels for IBM
|
|
* @created 17 Aug 2017
|
|
*/
|
|
|
|
package main
|
|
|
|
/* Imports
|
|
* 4 utility libraries for formatting, handling bytes, reading and writing JSON, and string manipulation
|
|
* 2 specific Hyperledger Fabric specific libraries for Smart Contracts
|
|
*/
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"strconv"
|
|
|
|
"github.com/hyperledger/fabric-contract-api-go/v2/contractapi"
|
|
)
|
|
|
|
func main() {
|
|
chaincode, err := contractapi.NewChaincode(&SmartContract{})
|
|
if err != nil {
|
|
log.Panicf("Error creating chaincode: %s", err)
|
|
}
|
|
|
|
if err := chaincode.Start(); err != nil {
|
|
log.Panicf("Error starting chaincode: %s", err)
|
|
}
|
|
}
|
|
|
|
// SmartContract is the data structure which represents this contract and its functions
|
|
type SmartContract struct {
|
|
contractapi.Contract
|
|
}
|
|
|
|
/**
|
|
* Updates the ledger to include a new delta for a particular variable. If this is the first time
|
|
* this variable is being added to the ledger, then its initial value is assumed to be 0. The arguments
|
|
* to give in the args array are as follows:
|
|
* - args[0] -> name of the variable
|
|
* - args[1] -> new delta (float)
|
|
* - args[2] -> operation (currently supported are addition "+" and subtraction "-")
|
|
*
|
|
* Returns a response indicating success or failure with a message.
|
|
*/
|
|
func (s *SmartContract) Update(ctx contractapi.TransactionContextInterface, name string, delta string, op string) (string, error) {
|
|
_, err := strconv.ParseFloat(delta, 64)
|
|
if err != nil {
|
|
return "", fmt.Errorf("provided value was not a number: %s", delta)
|
|
}
|
|
|
|
// Make sure a valid operator is provided
|
|
if op != "+" && op != "-" {
|
|
return "", fmt.Errorf("operator %s is unrecognized", op)
|
|
}
|
|
|
|
// Retrieve info needed for the update procedure
|
|
txid := ctx.GetStub().GetTxID()
|
|
compositeIndexName := "varName~op~value~txID"
|
|
|
|
// Create the composite key that will allow us to query for all deltas on a particular variable
|
|
compositeKey, compositeErr := ctx.GetStub().CreateCompositeKey(compositeIndexName, []string{name, op, delta, txid})
|
|
if compositeErr != nil {
|
|
return "", fmt.Errorf("could not create a composite key for %s: %w", name, compositeErr)
|
|
}
|
|
|
|
// Save the composite key index
|
|
compositePutErr := ctx.GetStub().PutState(compositeKey, []byte{0x00})
|
|
if compositePutErr != nil {
|
|
return "", fmt.Errorf("could not put operation for %s in the ledger: %w", name, compositePutErr)
|
|
}
|
|
|
|
return fmt.Sprintf("Successfully added %s%s to %s", op, delta, name), nil
|
|
}
|
|
|
|
/**
|
|
* Retrieves the aggregate value of a variable in the ledger. Gets all delta rows for the variable
|
|
* and computes the final value from all deltas. The args array for the invocation must contain the
|
|
* following argument:
|
|
* - args[0] -> The name of the variable to get the value of
|
|
*
|
|
* Returns a response indicating success or failure with a message
|
|
*/
|
|
func (s *SmartContract) Get(ctx contractapi.TransactionContextInterface, name string) (string, error) {
|
|
// Get all deltas for the variable
|
|
deltaResultsIterator, deltaErr := ctx.GetStub().GetStateByPartialCompositeKey("varName~op~value~txID", []string{name})
|
|
if deltaErr != nil {
|
|
return "", fmt.Errorf("could not retrieve value for %s: %w", name, deltaErr)
|
|
}
|
|
defer deltaResultsIterator.Close()
|
|
|
|
// Check the variable existed
|
|
if !deltaResultsIterator.HasNext() {
|
|
return "", fmt.Errorf("no variable by the name %s exists", name)
|
|
}
|
|
|
|
// Iterate through result set and compute final value
|
|
var finalVal float64
|
|
for deltaResultsIterator.HasNext() {
|
|
// Get the next row
|
|
responseRange, nextErr := deltaResultsIterator.Next()
|
|
if nextErr != nil {
|
|
return "", nextErr
|
|
}
|
|
|
|
// Split the composite key into its component parts
|
|
_, keyParts, splitKeyErr := ctx.GetStub().SplitCompositeKey(responseRange.Key)
|
|
if splitKeyErr != nil {
|
|
return "", splitKeyErr
|
|
}
|
|
|
|
// Retrieve the delta value and operation
|
|
operation := keyParts[1]
|
|
valueStr := keyParts[2]
|
|
|
|
// Convert the value string and perform the operation
|
|
value, convErr := strconv.ParseFloat(valueStr, 64)
|
|
if convErr != nil {
|
|
return "", convErr
|
|
}
|
|
|
|
switch operation {
|
|
case "+":
|
|
finalVal += value
|
|
case "-":
|
|
finalVal -= value
|
|
default:
|
|
return "", fmt.Errorf("unrecognized operation %s", operation)
|
|
}
|
|
}
|
|
|
|
return strconv.FormatFloat(finalVal, 'f', -1, 64), nil
|
|
}
|
|
|
|
/**
|
|
* Prunes a variable by deleting all of its delta rows while computing the final value. Once all rows
|
|
* have been processed and deleted, a single new row is added which defines a delta containing the final
|
|
* computed value of the variable. The args array contains the following argument:
|
|
* - args[0] -> The name of the variable to prune
|
|
*
|
|
* Returns a response indicating success or failure with a message
|
|
*/
|
|
func (s *SmartContract) Prune(ctx contractapi.TransactionContextInterface, name string) (string, error) {
|
|
// Get all delta rows for the variable
|
|
deltaResultsIterator, deltaErr := ctx.GetStub().GetStateByPartialCompositeKey("varName~op~value~txID", []string{name})
|
|
if deltaErr != nil {
|
|
return "", fmt.Errorf("could not retrieve value for %s: %w", name, deltaErr)
|
|
}
|
|
defer deltaResultsIterator.Close()
|
|
|
|
// Check the variable existed
|
|
if !deltaResultsIterator.HasNext() {
|
|
return "", fmt.Errorf("no variable by the name %s exists", name)
|
|
}
|
|
|
|
// Iterate through result set computing final value while iterating and deleting each key
|
|
var finalVal float64
|
|
var i int
|
|
for ; deltaResultsIterator.HasNext(); i++ {
|
|
// Get the next row
|
|
responseRange, nextErr := deltaResultsIterator.Next()
|
|
if nextErr != nil {
|
|
return "", nextErr
|
|
}
|
|
|
|
// Split the key into its composite parts
|
|
_, keyParts, splitKeyErr := ctx.GetStub().SplitCompositeKey(responseRange.Key)
|
|
if splitKeyErr != nil {
|
|
return "", splitKeyErr
|
|
}
|
|
|
|
// Retrieve the operation and value
|
|
operation := keyParts[1]
|
|
valueStr := keyParts[2]
|
|
|
|
// Convert the value to a float
|
|
value, convErr := strconv.ParseFloat(valueStr, 64)
|
|
if convErr != nil {
|
|
return "", convErr
|
|
}
|
|
|
|
// Delete the row from the ledger
|
|
deltaRowDelErr := ctx.GetStub().DelState(responseRange.Key)
|
|
if deltaRowDelErr != nil {
|
|
return "", fmt.Errorf("could not delete delta row: %w", deltaRowDelErr)
|
|
}
|
|
|
|
// Add the value of the deleted row to the final aggregate
|
|
switch operation {
|
|
case "+":
|
|
finalVal += value
|
|
case "-":
|
|
finalVal -= value
|
|
default:
|
|
return "", fmt.Errorf("unrecognized operation %s", operation)
|
|
}
|
|
}
|
|
|
|
// Update the ledger with the final value
|
|
if updateMessage, err := s.Update(ctx, name, strconv.FormatFloat(finalVal, 'f', -1, 64), "+"); err != nil {
|
|
return "", fmt.Errorf("could not update the final value of the variable after pruning: %s", updateMessage)
|
|
}
|
|
|
|
return fmt.Sprintf("Successfully pruned variable %s, final value is %f, %d rows pruned", name, finalVal, i), nil
|
|
}
|
|
|
|
/**
|
|
* Deletes all rows associated with an aggregate variable from the ledger. The args array
|
|
* contains the following argument:
|
|
* - args[0] -> The name of the variable to delete
|
|
*
|
|
* Returns a response indicating success or failure with a message
|
|
*/
|
|
func (s *SmartContract) Delete(ctx contractapi.TransactionContextInterface, name string) (string, error) {
|
|
// Delete all delta rows
|
|
deltaResultsIterator, deltaErr := ctx.GetStub().GetStateByPartialCompositeKey("varName~op~value~txID", []string{name})
|
|
if deltaErr != nil {
|
|
return "", fmt.Errorf("could not retrieve delta rows for %s: %w", name, deltaErr)
|
|
}
|
|
defer deltaResultsIterator.Close()
|
|
|
|
// Ensure the variable exists
|
|
if !deltaResultsIterator.HasNext() {
|
|
return "", fmt.Errorf("no variable by the name %s exists", name)
|
|
}
|
|
|
|
// Iterate through result set and delete all indices
|
|
var i int
|
|
for ; deltaResultsIterator.HasNext(); i++ {
|
|
responseRange, nextErr := deltaResultsIterator.Next()
|
|
if nextErr != nil {
|
|
return "", fmt.Errorf("could not retrieve next delta row: %w", nextErr)
|
|
}
|
|
|
|
deltaRowDelErr := ctx.GetStub().DelState(responseRange.Key)
|
|
if deltaRowDelErr != nil {
|
|
return "", fmt.Errorf("could not delete delta row: %w", deltaRowDelErr)
|
|
}
|
|
}
|
|
|
|
return fmt.Sprintf("Deleted %s, %d rows removed", name, i), nil
|
|
}
|
|
|
|
/**
|
|
* All functions below this are for testing traditional editing of a single row
|
|
*/
|
|
func (s *SmartContract) UpdateStandard(ctx contractapi.TransactionContextInterface, name string, delta string, operation string) (float64, error) {
|
|
deltaValue, err := strconv.ParseFloat(delta, 64)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
var currentValue float64
|
|
if valueBytes, err := ctx.GetStub().GetState(name); err == nil && len(valueBytes) > 0 {
|
|
currentValue, err = strconv.ParseFloat(string(valueBytes), 64)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
|
|
switch operation {
|
|
case "+":
|
|
currentValue += deltaValue
|
|
case "-":
|
|
currentValue -= deltaValue
|
|
default:
|
|
return 0, fmt.Errorf("unrecognized operation %s", operation)
|
|
}
|
|
|
|
valueStr := strconv.FormatFloat(currentValue, 'f', -1, 64)
|
|
|
|
if err := ctx.GetStub().PutState(name, []byte(valueStr)); err != nil {
|
|
return 0, fmt.Errorf("failed to put state: %w", err)
|
|
}
|
|
|
|
return currentValue, nil
|
|
}
|
|
|
|
func (s *SmartContract) GetStandard(ctx contractapi.TransactionContextInterface, name string) (string, error) {
|
|
valueBytes, err := ctx.GetStub().GetState(name)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get state: %w", err)
|
|
}
|
|
|
|
value, err := strconv.ParseFloat(string(valueBytes), 64)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return strconv.FormatFloat(value, 'f', -1, 64), nil
|
|
}
|
|
|
|
func (s *SmartContract) DelStandard(ctx contractapi.TransactionContextInterface, name string) error {
|
|
if err := ctx.GetStub().DelState(name); err != nil {
|
|
return fmt.Errorf("failed to delete state: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|