github.com/hyperledger-labs/bdls@v2.1.1+incompatible/core/chaincode/chaincode_support.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package chaincode
     8  
     9  import (
    10  	"bytes"
    11  	"time"
    12  	"unicode/utf8"
    13  
    14  	"github.com/golang/protobuf/proto"
    15  	pb "github.com/hyperledger/fabric-protos-go/peer"
    16  	"github.com/hyperledger/fabric/common/util"
    17  	"github.com/hyperledger/fabric/core/chaincode/extcc"
    18  	"github.com/hyperledger/fabric/core/chaincode/lifecycle"
    19  	"github.com/hyperledger/fabric/core/common/ccprovider"
    20  	"github.com/hyperledger/fabric/core/container/ccintf"
    21  	"github.com/hyperledger/fabric/core/ledger"
    22  	"github.com/hyperledger/fabric/core/peer"
    23  	"github.com/hyperledger/fabric/core/scc"
    24  	"github.com/pkg/errors"
    25  )
    26  
    27  const (
    28  	// InitializedKeyName is the reserved key in a chaincode's namespace which
    29  	// records the ID of the chaincode which initialized the namespace.
    30  	// In this way, we can enforce Init exactly once semantics, whenever
    31  	// the backing chaincode bytes change (but not be required to re-initialize
    32  	// the chaincode say, when endorsement policy changes).
    33  	InitializedKeyName = "\x00" + string(utf8.MaxRune) + "initialized"
    34  )
    35  
    36  // Runtime is used to manage chaincode runtime instances.
    37  type Runtime interface {
    38  	Build(ccid string) (*ccintf.ChaincodeServerInfo, error)
    39  	Start(ccid string, ccinfo *ccintf.PeerConnection) error
    40  	Stop(ccid string) error
    41  	Wait(ccid string) (int, error)
    42  }
    43  
    44  // Launcher is used to launch chaincode runtimes.
    45  type Launcher interface {
    46  	Launch(ccid string, streamHandler extcc.StreamHandler) error
    47  	Stop(ccid string) error
    48  }
    49  
    50  // Lifecycle provides a way to retrieve chaincode definitions and the packages necessary to run them
    51  type Lifecycle interface {
    52  	// ChaincodeEndorsementInfo looks up the chaincode info in the given channel.  It is the responsibility
    53  	// of the implementation to add appropriate read dependencies for the information returned.
    54  	ChaincodeEndorsementInfo(channelID, chaincodeName string, qe ledger.SimpleQueryExecutor) (*lifecycle.ChaincodeEndorsementInfo, error)
    55  }
    56  
    57  // ChaincodeSupport responsible for providing interfacing with chaincodes from the Peer.
    58  type ChaincodeSupport struct {
    59  	ACLProvider            ACLProvider
    60  	AppConfig              ApplicationConfigRetriever
    61  	BuiltinSCCs            scc.BuiltinSCCs
    62  	DeployedCCInfoProvider ledger.DeployedChaincodeInfoProvider
    63  	ExecuteTimeout         time.Duration
    64  	InstallTimeout         time.Duration
    65  	HandlerMetrics         *HandlerMetrics
    66  	HandlerRegistry        *HandlerRegistry
    67  	Keepalive              time.Duration
    68  	Launcher               Launcher
    69  	Lifecycle              Lifecycle
    70  	Peer                   *peer.Peer
    71  	Runtime                Runtime
    72  	TotalQueryLimit        int
    73  	UserRunsCC             bool
    74  }
    75  
    76  // Launch starts executing chaincode if it is not already running. This method
    77  // blocks until the peer side handler gets into ready state or encounters a fatal
    78  // error. If the chaincode is already running, it simply returns.
    79  func (cs *ChaincodeSupport) Launch(ccid string) (*Handler, error) {
    80  	if h := cs.HandlerRegistry.Handler(ccid); h != nil {
    81  		return h, nil
    82  	}
    83  
    84  	if err := cs.Launcher.Launch(ccid, cs); err != nil {
    85  		return nil, errors.Wrapf(err, "could not launch chaincode %s", ccid)
    86  	}
    87  
    88  	h := cs.HandlerRegistry.Handler(ccid)
    89  	if h == nil {
    90  		return nil, errors.Errorf("claimed to start chaincode container for %s but could not find handler", ccid)
    91  	}
    92  
    93  	return h, nil
    94  }
    95  
    96  // LaunchInProc is a stopgap solution to be called by the inproccontroller to allow system chaincodes to register
    97  func (cs *ChaincodeSupport) LaunchInProc(ccid string) <-chan struct{} {
    98  	launchStatus, ok := cs.HandlerRegistry.Launching(ccid)
    99  	if ok {
   100  		chaincodeLogger.Panicf("attempted to launch a system chaincode which has already been launched")
   101  	}
   102  
   103  	return launchStatus.Done()
   104  }
   105  
   106  // HandleChaincodeStream implements ccintf.HandleChaincodeStream for all vms to call with appropriate stream
   107  func (cs *ChaincodeSupport) HandleChaincodeStream(stream ccintf.ChaincodeStream) error {
   108  	handler := &Handler{
   109  		Invoker:                cs,
   110  		Keepalive:              cs.Keepalive,
   111  		Registry:               cs.HandlerRegistry,
   112  		ACLProvider:            cs.ACLProvider,
   113  		TXContexts:             NewTransactionContexts(),
   114  		ActiveTransactions:     NewActiveTransactions(),
   115  		BuiltinSCCs:            cs.BuiltinSCCs,
   116  		QueryResponseBuilder:   &QueryResponseGenerator{MaxResultLimit: 100},
   117  		UUIDGenerator:          UUIDGeneratorFunc(util.GenerateUUID),
   118  		LedgerGetter:           cs.Peer,
   119  		DeployedCCInfoProvider: cs.DeployedCCInfoProvider,
   120  		AppConfig:              cs.AppConfig,
   121  		Metrics:                cs.HandlerMetrics,
   122  		TotalQueryLimit:        cs.TotalQueryLimit,
   123  	}
   124  
   125  	return handler.ProcessStream(stream)
   126  }
   127  
   128  // Register the bidi stream entry point called by chaincode to register with the Peer.
   129  func (cs *ChaincodeSupport) Register(stream pb.ChaincodeSupport_RegisterServer) error {
   130  	return cs.HandleChaincodeStream(stream)
   131  }
   132  
   133  // ExecuteLegacyInit is a temporary method which should be removed once the old style lifecycle
   134  // is entirely deprecated.  Ideally one release after the introduction of the new lifecycle.
   135  // It does not attempt to start the chaincode based on the information from lifecycle, but instead
   136  // accepts the container information directly in the form of a ChaincodeDeploymentSpec.
   137  func (cs *ChaincodeSupport) ExecuteLegacyInit(txParams *ccprovider.TransactionParams, ccName, ccVersion string, input *pb.ChaincodeInput) (*pb.Response, *pb.ChaincodeEvent, error) {
   138  	// FIXME: this is a hack, we shouldn't construct the
   139  	// ccid manually but rather let lifecycle construct it
   140  	// for us. However this is legacy code that will disappear
   141  	// so it is acceptable for now (FAB-14627)
   142  	ccid := ccName + ":" + ccVersion
   143  
   144  	h, err := cs.Launch(ccid)
   145  	if err != nil {
   146  		return nil, nil, err
   147  	}
   148  
   149  	resp, err := cs.execute(pb.ChaincodeMessage_INIT, txParams, ccName, input, h)
   150  	return processChaincodeExecutionResult(txParams.TxID, ccName, resp, err)
   151  }
   152  
   153  // Execute invokes chaincode and returns the original response.
   154  func (cs *ChaincodeSupport) Execute(txParams *ccprovider.TransactionParams, chaincodeName string, input *pb.ChaincodeInput) (*pb.Response, *pb.ChaincodeEvent, error) {
   155  	resp, err := cs.Invoke(txParams, chaincodeName, input)
   156  	return processChaincodeExecutionResult(txParams.TxID, chaincodeName, resp, err)
   157  }
   158  
   159  func processChaincodeExecutionResult(txid, ccName string, resp *pb.ChaincodeMessage, err error) (*pb.Response, *pb.ChaincodeEvent, error) {
   160  	if err != nil {
   161  		return nil, nil, errors.Wrapf(err, "failed to execute transaction %s", txid)
   162  	}
   163  	if resp == nil {
   164  		return nil, nil, errors.Errorf("nil response from transaction %s", txid)
   165  	}
   166  
   167  	if resp.ChaincodeEvent != nil {
   168  		resp.ChaincodeEvent.ChaincodeId = ccName
   169  		resp.ChaincodeEvent.TxId = txid
   170  	}
   171  
   172  	switch resp.Type {
   173  	case pb.ChaincodeMessage_COMPLETED:
   174  		res := &pb.Response{}
   175  		err := proto.Unmarshal(resp.Payload, res)
   176  		if err != nil {
   177  			return nil, nil, errors.Wrapf(err, "failed to unmarshal response for transaction %s", txid)
   178  		}
   179  		return res, resp.ChaincodeEvent, nil
   180  
   181  	case pb.ChaincodeMessage_ERROR:
   182  		return nil, resp.ChaincodeEvent, errors.Errorf("transaction returned with failure: %s", resp.Payload)
   183  
   184  	default:
   185  		return nil, nil, errors.Errorf("unexpected response type %d for transaction %s", resp.Type, txid)
   186  	}
   187  }
   188  
   189  // Invoke will invoke chaincode and return the message containing the response.
   190  // The chaincode will be launched if it is not already running.
   191  func (cs *ChaincodeSupport) Invoke(txParams *ccprovider.TransactionParams, chaincodeName string, input *pb.ChaincodeInput) (*pb.ChaincodeMessage, error) {
   192  	ccid, cctype, err := cs.CheckInvocation(txParams, chaincodeName, input)
   193  	if err != nil {
   194  		return nil, errors.WithMessage(err, "invalid invocation")
   195  	}
   196  
   197  	h, err := cs.Launch(ccid)
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  
   202  	return cs.execute(cctype, txParams, chaincodeName, input, h)
   203  }
   204  
   205  // CheckInvocation inspects the parameters of an invocation and determines if, how, and to where a that invocation should be routed.
   206  // First, we ensure that the target namespace is defined on the channel and invokable on this peer, according to the lifecycle implementation.
   207  // Then, if the chaincode definition requires it, this function enforces 'init exactly once' semantics.
   208  // Finally, it returns the chaincode ID to route to and the message type of the request (normal transation, or init).
   209  func (cs *ChaincodeSupport) CheckInvocation(txParams *ccprovider.TransactionParams, chaincodeName string, input *pb.ChaincodeInput) (ccid string, cctype pb.ChaincodeMessage_Type, err error) {
   210  	chaincodeLogger.Debugf("[%s] getting chaincode data for %s on channel %s", shorttxid(txParams.TxID), chaincodeName, txParams.ChannelID)
   211  	cii, err := cs.Lifecycle.ChaincodeEndorsementInfo(txParams.ChannelID, chaincodeName, txParams.TXSimulator)
   212  	if err != nil {
   213  		logDevModeError(cs.UserRunsCC)
   214  		return "", 0, errors.Wrapf(err, "[channel %s] failed to get chaincode container info for %s", txParams.ChannelID, chaincodeName)
   215  	}
   216  
   217  	needsInitialization := false
   218  	if cii.EnforceInit {
   219  
   220  		value, err := txParams.TXSimulator.GetState(chaincodeName, InitializedKeyName)
   221  		if err != nil {
   222  			return "", 0, errors.WithMessage(err, "could not get 'initialized' key")
   223  		}
   224  
   225  		needsInitialization = !bytes.Equal(value, []byte(cii.Version))
   226  	}
   227  
   228  	// Note, IsInit is a new field for v2.0 and should only be set for invocations of non-legacy chaincodes.
   229  	// Any invocation of a legacy chaincode with IsInit set will fail.  This is desirable, as the old
   230  	// InstantiationPolicy contract enforces which users may call init.
   231  	if input.IsInit {
   232  		if !cii.EnforceInit {
   233  			return "", 0, errors.Errorf("chaincode '%s' does not require initialization but called as init", chaincodeName)
   234  		}
   235  
   236  		if !needsInitialization {
   237  			return "", 0, errors.Errorf("chaincode '%s' is already initialized but called as init", chaincodeName)
   238  		}
   239  
   240  		err = txParams.TXSimulator.SetState(chaincodeName, InitializedKeyName, []byte(cii.Version))
   241  		if err != nil {
   242  			return "", 0, errors.WithMessage(err, "could not set 'initialized' key")
   243  		}
   244  
   245  		return cii.ChaincodeID, pb.ChaincodeMessage_INIT, nil
   246  	}
   247  
   248  	if needsInitialization {
   249  		return "", 0, errors.Errorf("chaincode '%s' has not been initialized for this version, must call as init first", chaincodeName)
   250  	}
   251  
   252  	return cii.ChaincodeID, pb.ChaincodeMessage_TRANSACTION, nil
   253  }
   254  
   255  // execute executes a transaction and waits for it to complete until a timeout value.
   256  func (cs *ChaincodeSupport) execute(cctyp pb.ChaincodeMessage_Type, txParams *ccprovider.TransactionParams, namespace string, input *pb.ChaincodeInput, h *Handler) (*pb.ChaincodeMessage, error) {
   257  	input.Decorations = txParams.ProposalDecorations
   258  
   259  	payload, err := proto.Marshal(input)
   260  	if err != nil {
   261  		return nil, errors.WithMessage(err, "failed to create chaincode message")
   262  	}
   263  
   264  	ccMsg := &pb.ChaincodeMessage{
   265  		Type:      cctyp,
   266  		Payload:   payload,
   267  		Txid:      txParams.TxID,
   268  		ChannelId: txParams.ChannelID,
   269  	}
   270  
   271  	timeout := cs.executeTimeout(namespace, input)
   272  	ccresp, err := h.Execute(txParams, namespace, ccMsg, timeout)
   273  	if err != nil {
   274  		return nil, errors.WithMessage(err, "error sending")
   275  	}
   276  
   277  	return ccresp, nil
   278  }
   279  
   280  func (cs *ChaincodeSupport) executeTimeout(namespace string, input *pb.ChaincodeInput) time.Duration {
   281  	operation := chaincodeOperation(input.Args)
   282  	switch {
   283  	case namespace == "lscc" && operation == "install":
   284  		return maxDuration(cs.InstallTimeout, cs.ExecuteTimeout)
   285  	case namespace == lifecycle.LifecycleNamespace && operation == lifecycle.InstallChaincodeFuncName:
   286  		return maxDuration(cs.InstallTimeout, cs.ExecuteTimeout)
   287  	default:
   288  		return cs.ExecuteTimeout
   289  	}
   290  }
   291  
   292  func maxDuration(durations ...time.Duration) time.Duration {
   293  	var result time.Duration
   294  	for _, d := range durations {
   295  		if d > result {
   296  			result = d
   297  		}
   298  	}
   299  	return result
   300  }
   301  
   302  func chaincodeOperation(args [][]byte) string {
   303  	if len(args) == 0 {
   304  		return ""
   305  	}
   306  	return string(args[0])
   307  }
   308  
   309  func logDevModeError(userRunsCC bool) {
   310  	if userRunsCC {
   311  		chaincodeLogger.Error("You are attempting to perform an action other than Deploy on Chaincode that is not ready and you are in developer mode. Did you forget to Deploy your chaincode?")
   312  	}
   313  }