func (s *SmartContract) updateStatusToAcknowledged(ctx contractapi.TransactionContextInterface, proposal ChaincodeUpdateProposal) error {
	proposal.Status = Acknowledged
	if err := s.putProposal(ctx, proposal); err != nil {
		return err
	}
	mspID, err := s.getMSPID(ctx)
	if err != nil {
		return fmt.Errorf("failed to get MSP ID: %v", err)
	}
	eventDetail := DeploymentEventDetail{
		OperationTargets: []string{mspID},
		Proposal:         proposal,
	}
	eventDetailJSON, err := json.Marshal(eventDetail)
	if err != nil {
		return err
	}
	if err = ctx.GetStub().SetEvent(fmt.Sprintf("%s.%s", DeployEvent, proposal.ID), eventDetailJSON); err != nil {
		return fmt.Errorf("error happened emitting event: %v", err)
	}
	return nil
}
Functions to manage propsal status
package core
import (
	"encoding/json"
	"fmt"
	"net/url"
	"strconv"
	"time"
	"github.com/golang/protobuf/proto"
	"github.com/hyperledger/fabric-chaincode-go/shim"
	"github.com/hyperledger/fabric-contract-api-go/contractapi"
	"github.com/hyperledger/fabric-protos-go/msp"
	"github.com/hyperledger/fabric/common/util"
)
type SmartContract struct {
	contractapi.Contract
}
type ChaincodePackage struct {
	Repository        string `json:"repository"`
	CommitID          string `json:"commitID"`
	PathToSourceFiles string `json:"pathToSourceFiles,omitempty" metadata:",optional"`
	Type              string `json:"type"`
}
type ChaincodeDefinition struct {
	Sequence            int64  `json:"sequence"`
	InitRequired        bool   `json:"initRequired"`
	ValidationParameter string `json:"validationParameter"`
	Collections         string `json:"collections,omitempty" metadata:",optional"`
}
type ChaincodeUpdateProposal struct {
	ObjectType          string              `json:"docType"`  
	ID                  string              `json:"ID"`
	Creator             string              `json:"creator"`
	ChannelID           string              `json:"channelID"`
	ChaincodeName       string              `json:"chaincodeName"`
	ChaincodePackage    ChaincodePackage    `json:"chaincodePackage"`
	ChaincodeDefinition ChaincodeDefinition `json:"chaincodeDefinition"`
	Status              string              `json:"status"`
	Time                string              `json:"time"`
}
type ChaincodeUpdateProposalInput struct {
	ID                  string              `json:"ID"`
	ChannelID           string              `json:"channelID"`
	ChaincodeName       string              `json:"chaincodeName"`
	ChaincodePackage    ChaincodePackage    `json:"chaincodePackage"`
	ChaincodeDefinition ChaincodeDefinition `json:"chaincodeDefinition"`
}
type History struct {
	ObjectType string `json:"docType"`  
	ProposalID string `json:"proposalID"`
	TaskID     string `json:"taskID"`
	OrgID      string `json:"orgID"`
	Status     string `json:"status"`
	Data       string `json:"data"`
	Time       string `json:"time"`
}
type TaskStatusUpdateRequest struct {
	ProposalID string `json:"proposalID"`
	Status     string `json:"status,omitempty" metadata:",optional"`
	Data       string `json:"data,omitempty" metadata:",optional"`
}
type HistoryQueryParams struct {
	ProposalID string `json:"proposalID,omitempty"`
	TaskID     string `json:"taskID,omitempty" metadata:",optional"`
	OrgID      string `json:"orgID,omitempty" metadata:",optional"`
}
type DeploymentEventDetail struct {
	Proposal         ChaincodeUpdateProposal `json:"proposal"`
	OperationTargets []string                `json:"operationTargets"`
}
const (
	ProposalObjectType = "proposal"
	HistoryObjectType  = "history"
)
const (
	NewProposalEvent     = "newProposalEvent"
	NewVoteEvent         = "newVoteEvent"
	PrepareToDeployEvent = "prepareToDeployEvent"
	DeployEvent          = "deployEvent"
	CommittedEvent       = "committedEvent"
	RejectedEvent        = "rejectedEvent"
	WithdrawnEvent       = "withdrawnEvent"
)
const (
	Vote        = "vote"
	Acknowledge = "acknowledge"
	Commit      = "commit"
)
const (
	Proposed     = "proposed"
	Approved     = "approved"
	Rejected     = "rejected"
	Acknowledged = "acknowledged"
	Committed    = "committed"
	Failed       = "failed"  
	Withdrawn    = "withdrawn"
)
const (
	Agreed    = "agreed"
	Disagreed = "disagreed"
)
const (
	Success = "success"
	Failure = "failure"
)
const (
	ALL      = "all"
	MAJORITY = "majority"
)
var (
	ErrProposalNotFound = fmt.Errorf("proposal not found")
	ErrProposalIDAreadyInUse = fmt.Errorf("proposalID already in use")
)
func (s *SmartContract) RequestProposal(ctx contractapi.TransactionContextInterface, input ChaincodeUpdateProposalInput) (*ChaincodeUpdateProposal, error) {
	if input.ID == "" {
		return nil, fmt.Errorf("the required parameter proposal 'ID' is empty")
	}
	if input.ChannelID == "" {
		return nil, fmt.Errorf("the required parameter 'ChannelID' is empty")
	}
	if input.ChaincodeName == "" {
		return nil, fmt.Errorf("the required parameter 'ChaincodeName' is empty")
	}
	if input.ChaincodeDefinition.Sequence < 1 {
		return nil, fmt.Errorf("the parameter 'ChaincodeDefinition.Sequence' should be >= 1")
	}
	if input.ChaincodeDefinition.ValidationParameter == "" {
		return nil, fmt.Errorf("the required parameter 'ChaincodeDefinition.ValidationParameter' is empty")
	}
	url, err := url.Parse(input.ChaincodePackage.Repository)
	if err != nil || url.Scheme != "" {
		return nil, fmt.Errorf("the parameter 'ChaincodePackage.Repository' should be repository path (e.g., github.com/project_name/repository_name)")
	}
	if input.ChaincodePackage.Type == "" {
		return nil, fmt.Errorf("the required parameter 'ChaincodePackage.Type' is empty")
	}
	if input.ChaincodePackage.CommitID == "" {
		return nil, fmt.Errorf("the required parameter 'ChaincodePackage.CommitID' is empty")
	}
	mspID, err := s.getMSPID(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to get MSP ID: %v", err)
	}
	txTimestamp, err := getTxTimestampRFC3339(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to get tx timestamp: %v", err)
	}
	proposal := ChaincodeUpdateProposal{
		ObjectType:          ProposalObjectType,
		ID:                  input.ID,
		Creator:             mspID,
		Time:                txTimestamp,
		Status:              Proposed,
		ChannelID:           input.ChannelID,
		ChaincodeName:       input.ChaincodeName,
		ChaincodePackage:    input.ChaincodePackage,
		ChaincodeDefinition: input.ChaincodeDefinition,
	}
	if p, _ := s.GetProposal(ctx, input.ID); p != nil {
		return nil, ErrProposalIDAreadyInUse
	}
	if err = s.putProposal(ctx, proposal); err != nil {
		return nil, fmt.Errorf("failed to put the proposal: %v", err)
	}
	if _, err = s.putHistory(ctx, proposal.ID, Vote, Agreed, "", false); err != nil {
		return nil, fmt.Errorf("failed to put the history that the org votes for: %v", err)
	}
	proposalJSON, err := json.Marshal(proposal)
	if err != nil {
		return nil, fmt.Errorf("error happened unmarshalling a proposal JSON representation to struct: %v", err)
	}
	if err = ctx.GetStub().SetEvent(fmt.Sprintf("%s.%s", NewProposalEvent, proposal.ID), []byte(proposalJSON)); err != nil {
		return nil, fmt.Errorf("error happened emitting event: %v", err)
	}
	return &proposal, nil
}
func (s *SmartContract) Vote(ctx contractapi.TransactionContextInterface, taskStatusUpdateRequest TaskStatusUpdateRequest) error {
	if taskStatusUpdateRequest.Status == "" {
		taskStatusUpdateRequest.Status = Agreed
	}
	if taskStatusUpdateRequest.ProposalID == "" {
		return fmt.Errorf("the required parameter 'ProposalID' is empty")
	}
	if taskStatusUpdateRequest.Status != Agreed && taskStatusUpdateRequest.Status != Disagreed {
		return fmt.Errorf("task status for vote should be %s or %s", Agreed, Disagreed)
	}
	proposal, err := s.GetProposal(ctx, taskStatusUpdateRequest.ProposalID)
	if err != nil {
		return fmt.Errorf("failed to get the proposal: %v", err)
	}
	if proposal.Status != Proposed {
		return fmt.Errorf("the voting is already closed")
	}
	history, err := s.putHistory(ctx, taskStatusUpdateRequest.ProposalID, Vote, taskStatusUpdateRequest.Status, taskStatusUpdateRequest.Data, false)
	if err != nil {
		return fmt.Errorf("failed to put the history: %v", err)
	}
	switch taskStatusUpdateRequest.Status {
	case Agreed:
		votePassed, err := s.meetCriteria(ctx, *history, MAJORITY, false, proposal.ChannelID)
		if err != nil {
			return fmt.Errorf("failed to do meetCriteria: %v", err)
		}
		if votePassed {
			if err = s.updateStatusToApproved(ctx, *proposal); err != nil {
				return fmt.Errorf("failed to update the status: %v", err)
			}
			return nil
		}
	case Disagreed:
		voteRejected, err := s.meetCriteria(ctx, *history, MAJORITY, true, proposal.ChannelID)
		if err != nil {
			return fmt.Errorf("failed to do meetCriteria: %v", err)
		}
		if voteRejected {
			if err = s.updateStatusToRejected(ctx, *proposal); err != nil {
				return fmt.Errorf("failed to update the status: %v", err)
			}
			return nil
		}
	}
	if err := ctx.GetStub().SetEvent(fmt.Sprintf("%s.%s", NewVoteEvent, proposal.ID), []byte(nil)); err != nil {
		return fmt.Errorf("error happened emitting event: %v", err)
	}
	return nil
}
func (s *SmartContract) WithdrawProposal(ctx contractapi.TransactionContextInterface, proposalID string) error {
	if proposalID == "" {
		return fmt.Errorf("the required parameter 'proposalID' is empty")
	}
	proposal, err := s.GetProposal(ctx, proposalID)
	if err != nil {
		return ErrProposalNotFound
	}
	if proposal.Status != Proposed {
		return fmt.Errorf("the voting is already closed")
	}
	mspID, err := s.getMSPID(ctx)
	if err != nil {
		return fmt.Errorf("failed to get MSP ID: %v", err)
	}
	if proposal.Creator != mspID {
		return fmt.Errorf("only the proposer (%v) can withdraw the proposal", proposal.Creator)
	}
	if err = s.updateStatusToWithdrawn(ctx, *proposal); err != nil {
		return fmt.Errorf("failed to update the status: %v", err)
	}
	return nil
}
func (s *SmartContract) Acknowledge(ctx contractapi.TransactionContextInterface, taskStatusUpdateRequest TaskStatusUpdateRequest) error {
	if taskStatusUpdateRequest.Status == "" {
		taskStatusUpdateRequest.Status = Success
	}
	if taskStatusUpdateRequest.ProposalID == "" {
		return fmt.Errorf("the required parameter 'ProposalID' is empty")
	}
	if taskStatusUpdateRequest.Status != Success && taskStatusUpdateRequest.Status != Failure {
		return fmt.Errorf("task status for acknowledge should be %s or %s", Success, Failure)
	}
	proposal, err := s.GetProposal(ctx, taskStatusUpdateRequest.ProposalID)
	if err != nil {
		return fmt.Errorf("failed to get the proposal: %v", err)
	}
	if proposal.Status != Approved {
		return nil
	}
	history, err := s.putHistory(ctx, taskStatusUpdateRequest.ProposalID, Acknowledge, taskStatusUpdateRequest.Status, taskStatusUpdateRequest.Data, true)
	if err != nil {
		return fmt.Errorf("failed to put the history: %v", err)
	}
	isAcknowledgedByAllOrgs, err := s.meetCriteria(ctx, *history, ALL, false, proposal.ChannelID)
	if err != nil {
		return fmt.Errorf("failed to do meetCriteria: %v", err)
	}
	if taskStatusUpdateRequest.Status == Success && isAcknowledgedByAllOrgs {
		if err = s.updateStatusToAcknowledged(ctx, *proposal); err != nil {
			return fmt.Errorf("failed to update the status: %v", err)
		}
	}
	return nil
}
func (s *SmartContract) NotifyCommitResult(ctx contractapi.TransactionContextInterface, taskStatusUpdateRequest TaskStatusUpdateRequest) error {
	if taskStatusUpdateRequest.Status == "" {
		taskStatusUpdateRequest.Status = Success
	}
	if taskStatusUpdateRequest.ProposalID == "" {
		return fmt.Errorf("the required parameter 'ProposalID' is empty")
	}
	if taskStatusUpdateRequest.Status != Success && taskStatusUpdateRequest.Status != Failure {
		return fmt.Errorf("task status for commit should be %s or %s", Success, Failure)
	}
	proposal, err := s.GetProposal(ctx, taskStatusUpdateRequest.ProposalID)
	if err != nil {
		return fmt.Errorf("failed to get the proposal: %v", err)
	}
	_, err = s.putHistory(ctx, taskStatusUpdateRequest.ProposalID, Commit, taskStatusUpdateRequest.Status, taskStatusUpdateRequest.Data, true)
	if err != nil {
		return fmt.Errorf("failed to put the history: %v", err)
	}
	if taskStatusUpdateRequest.Status != Success {
		return nil
	}
	if proposal.Status != Acknowledged {
		return nil
	}
	if err = s.updateStatusToCommitted(ctx, *proposal); err != nil {
		return fmt.Errorf("failed to update the status: %v", err)
	}
	return nil
}
func (s *SmartContract) GetAllProposals(ctx contractapi.TransactionContextInterface) (map[string]*ChaincodeUpdateProposal, error) {
	proposals := make(map[string]*ChaincodeUpdateProposal)
	proposalIterator, err := ctx.GetStub().GetStateByPartialCompositeKey(ProposalObjectType, []string{})
	if err != nil {
		return nil, fmt.Errorf("error happened reading keys from ledger: %v", err)
	}
	defer proposalIterator.Close()
	for proposalIterator.HasNext() {
		proposalJSON, err := proposalIterator.Next()
		if err != nil {
			return nil, fmt.Errorf("error happened iterating over available proposals: %v", err)
		}
		proposal := &ChaincodeUpdateProposal{}
		if err = json.Unmarshal(proposalJSON.Value, proposal); err != nil {
			return nil, fmt.Errorf("error happened unmarshalling a proposal JSON representation to struct: %v", err)
		}
		proposals[proposalJSON.Key] = proposal
	}
	return proposals, nil
}
func buildAttributesForGetHistories(params HistoryQueryParams) []string {
	args := []string{}
	if params.ProposalID == "" {
		return args
	}
	args = append(args, params.ProposalID)
	if params.TaskID == "" {
		return args
	}
	args = append(args, params.TaskID)
	if params.OrgID == "" {
		return args
	}
	args = append(args, params.OrgID)
	return args
}
func (s *SmartContract) GetHistories(ctx contractapi.TransactionContextInterface, params HistoryQueryParams) (map[string]*History, error) {
	histories := make(map[string]*History)
	iterator, err := ctx.GetStub().GetStateByPartialCompositeKey(HistoryObjectType, buildAttributesForGetHistories(params))
	if err != nil {
		return nil, fmt.Errorf("error happened reading keys from ledger: %v", err)
	}
	defer iterator.Close()
	for iterator.HasNext() {
		historyJSON, err := iterator.Next()
		if err != nil {
			return nil, fmt.Errorf("error happened iterating over available histories: %v", err)
		}
		history := &History{}
		if err = json.Unmarshal(historyJSON.Value, history); err != nil {
			return nil, fmt.Errorf("error happened unmarshalling a history JSON representation to struct: %v", err)
		}
		histories[historyJSON.Key] = history
	}
	return histories, nil
}
func (s *SmartContract) GetProposal(ctx contractapi.TransactionContextInterface, proposalID string) (*ChaincodeUpdateProposal, error) {
	compositeKey, err := ctx.GetStub().CreateCompositeKey(ProposalObjectType, []string{proposalID})
	if err != nil {
		return nil, fmt.Errorf("error happend creating composite key for proposal: %v", err)
	}
	proposalJSON, err := ctx.GetStub().GetState(compositeKey)
	if err != nil {
		return nil, fmt.Errorf("error happened reading proposal with id (%v): %v", proposalID, err)
	}
	if proposalJSON == nil {
		return nil, ErrProposalNotFound
	}
	var proposal ChaincodeUpdateProposal
	err = json.Unmarshal(proposalJSON, &proposal)
	if err != nil {
		return nil, fmt.Errorf("error happened unmarshalling a proposal JSON representation to struct: %v", err)
	}
	return &proposal, nil
}
func (s *SmartContract) meetCriteria(ctx contractapi.TransactionContextInterface, currentHistory History, criteria string, checkUnachivable bool, targetChannel string) (bool, error) {
	iterator, err := ctx.GetStub().GetStateByPartialCompositeKey(HistoryObjectType, []string{currentHistory.ProposalID, currentHistory.TaskID})
	if err != nil {
		return false, fmt.Errorf("error happened reading keys from ledger: %v", err)
	}
	defer iterator.Close()
	channelOpsArgs := util.ToChaincodeArgs("CountOrganizationsInChannel", targetChannel)
	response := ctx.GetStub().InvokeChaincode("channel_ops", channelOpsArgs, "")
	if response.Status != shim.OK {
		return false, fmt.Errorf("failed to call count organization in channel (code: %d, message: %v)",
			response.Status, response.Message)
	}
	totalOrgNum, err := strconv.Atoi(string(response.Payload))
	if err != nil {
		return false, fmt.Errorf("failed to call count organization in channel: %v", err)
	}
	criteriaNum := totalOrgNum
	switch criteria {
	case MAJORITY:
		criteriaNum = criteriaNum/2 + 1
	case ALL:
	default:
		return false, fmt.Errorf("invalid criteria type: %v", criteria)
	}
	if checkUnachivable {
		criteriaNum = totalOrgNum - criteriaNum
	}
	orgs := map[string]string{}
	orgs[currentHistory.OrgID] = currentHistory.Status
	for iterator.HasNext() {
		result, err := iterator.Next()
		if err != nil {
			return false, nil
		}
		var resultHistory History
		err = json.Unmarshal(result.Value, &resultHistory)
		if err != nil {
			return false, nil
		}
		if resultHistory.Status == currentHistory.Status {
			orgs[resultHistory.OrgID] = resultHistory.Status
		}
	}
	if len(orgs) >= criteriaNum {
		return true, nil
	}
	return false, nil
}