Skip to content
Snippets Groups Projects
Select Git revision
  • 22058fa289e0e11da1d50bdd40eff0fa55f9125f
  • master default
2 results

lib.rs

Blame
  • bid_controller.go 8.86 KiB
    // controller.go
    // COPYRIGHT: FUNDACIÓN TECNALIA RESEARCH & INNOVATION, 2018.
    // controller.go is part of the Computer file TRACEBLOCK. Copyrighted under U.S. Copyright Office Registration Number TXu002105160, registered on 2018-06-25 by FUNDACIÓN TECNALIA RESEARCH & INNOVATION.
    // This license is effective worldwide.
    
    package bid
    
    import (
    	"errors"
    
    	"git.code.tecnalia.com/ledgerbuilder/sdk/core/api"
    	"git.code.tecnalia.com/ledgerbuilder/sdk/core/controller"
    	"git.code.tecnalia.com/ledgerbuilder/sdk/core/fabric/protos"
    	"git.code.tecnalia.com/ledgerbuilder/sdk/shared"
    	"github.com/mitchellh/mapstructure"
    
    	"git.code.tecnalia.com/blockchain/hypercog/controller/stats"
    	errs "git.code.tecnalia.com/traceblock/sdk/constants"
    	"git.code.tecnalia.com/traceblock/sdk/controller/base"
    	"git.code.tecnalia.com/traceblock/sdk/controller/split"
    	"git.code.tecnalia.com/traceblock/sdk/controller/transfer"
    	"git.code.tecnalia.com/traceblock/sdk/model"
    )
    
    type BidController struct {
    	base.TraceblockBaseController
    }
    
    type BidDecisionController struct {
    	BidController
    }
    
    type BidStatus string
    
    const (
    	Proposed = "proposed"
    	Accepted = "accepted"
    	Rejected = "rejected"
    )
    
    type Bidder struct {
    	Id	 	string `json:"id"`
    	Role	string `json:"role"`
    	Org		string `json:"org"`
    }
    
    type Bid struct {
    	// Not renaming any of these fields in json to make mapstructure.decode work
    	Quantity    uint32		`json:"quantity"`
    	Price   	float32		`json:"price"`
    	Bidder	 	Bidder		`json:"bidder"`
    	Status	 	BidStatus	`json:"status"`
    }
    
    var (
    	errInvalidInputData     = errors.New("failed to read model")
    	errParamsMissing        = errors.New("missing split params. assetId and splitParams config must be send in split request")
    	errChildrenMissmatch    = errors.New("number of requested childs and proposed id count mismatch")
    	errBidirectionalLink	= errors.New("failed to bidirectonally link base asset on current transaction")
    	errInvalidBidQuantity   = errors.New("bid quantity is greater than the available quantity")
    	errInvalidBidder   		= errors.New("bidder has no registered bid")
    )
    
    // constructor like function
    func NewBidController() *BidController {
    	ctl := new(BidController)
    	ctl.LowLevelController = controller.NewLowLevelController()
    	ctl.SetDataModelClosure(ctl.modelClosure)
    	return ctl
    }
    
    func (c BidController) modelClosure(stub shared.LedgerBuildrStubInterface) shared.LedgerBuildrAsset {
    	return NewBidParams()
    }
    
    func NewBidDecisionController() *BidDecisionController {
    	ctl := new(BidDecisionController)
    	ctl.LowLevelController = controller.NewLowLevelController()
    	ctl.SetDataModelClosure(func(stub shared.LedgerBuildrStubInterface) shared.LedgerBuildrAsset {
    		return NewBidDecisionParams()
    	})
    	return ctl
    }
    
    func (c BidController) readTraceableAsset(stub shared.LedgerBuildrStubInterface, assetId string) (*model.TraceableAsset, error) {
    	readedModel, err := c.ReadAbstractAssetAndReturn(stub, assetId, model.NewTraceableAsset())
    	if err != nil {
    		return nil, err
    	}
    
    	// asset data successfully readed
    	asset, ok := readedModel.(*model.TraceableAsset)
    	if !ok {
    		return nil, errs.NotExistingAsset
    	}
    
    	return asset, nil
    }
    
    func (c BidController) readAsset(stub shared.LedgerBuildrStubInterface, requestAsset shared.LedgerBuildrAsset) (*model.TraceableAsset, error) {
    	return c.readTraceableAsset(stub, requestAsset.GetID())
    }
    
    func (c BidController) getBids(biddableAsset *model.TraceableAsset) ([]Bid, error) {
    	bidArr := []Bid{}
    	if _, ok := biddableAsset.ArbitraryDataFields["bids"]; ok {
    		// to avoid interface conversion errors ([]interface {} is not []bid.Bid)
    		err := mapstructure.Decode(biddableAsset.ArbitraryDataFields["bids"], &bidArr)
    		if err != nil {
    			return nil, err
    		}
    	}
    
    	return bidArr, nil
    }
    
    func (c BidController) BidAsset(stub shared.LedgerBuildrStubInterface, params shared.LedgerBuildrAsset) protos.Response {
    	const fnName = "BidController:BidAsset"
    
    	bidParams, ok := params.(*BidParams) // already checked
    	if bidParams == nil || !ok {
    		//failed when casting/fetching input data
    		return api.NewApiResponsePtr(fnName, errInvalidInputData, nil).SendResponse()
    	}
    
    	biddableAsset, err := c.readAsset(stub, bidParams)
    	if err != nil {
    		return api.NewApiResponsePtr(fnName, err, nil).SendResponse()
    	}
    
    	if biddableAsset.Quantity < bidParams.Bid.Quantity {
    		return api.NewApiResponsePtr(fnName, errInvalidBidQuantity, nil).SendResponse()
    	}
    
    	auxArr, err := c.getBids(biddableAsset)
    	if err != nil {
    		return api.NewApiResponsePtr(fnName, err, nil).SendResponse()
    	}
    
    	org, err := stub.GetOrganization()
    	if err != nil {
    		return api.NewApiResponsePtr(fnName, err, nil).SendResponse()
    	}
    
    	role, err := stub.GetRole()
    	if err != nil {
    		return api.NewApiResponsePtr(fnName, err, nil).SendResponse()
    	}
    
    	newBid := Bid{
    		Quantity: bidParams.Bid.Quantity,
    		Price: bidParams.Bid.Price,
    		Bidder: Bidder{
    			Id: stub.GetUniqueUserId(),
    			Org: org,
    			Role: role,
    		},
    		Status: Proposed,
    	}
    
    	alreadyExists := false
    	for i, bid := range auxArr {
            if bid.Bidder == newBid.Bidder {
    			auxArr[i] = newBid
    			alreadyExists = true
    			break
            }
        }
    	
    	if !alreadyExists {
    		auxArr = append(auxArr, newBid)
    	}
    
    	biddableAsset.Add("bids", auxArr)
    	biddableAsset.MarkModification(stub)
    
    	return c.SaveAbstractAsset(stub, biddableAsset)
    }
    
    func (c BidDecisionController) bidResponse(stub shared.LedgerBuildrStubInterface, params shared.LedgerBuildrAsset, acceptBid bool)  (*model.TraceableAsset, *Bid, error) {
    	bidDecisionParams, ok := params.(*BidDecisionParams)
    	if bidDecisionParams == nil || !ok {
    		//failed when casting/fetching input data
    		return nil, nil, errInvalidInputData
    	}
    
    	biddableAsset, err := c.readAsset(stub, bidDecisionParams)
    	if err != nil {
    		return nil, nil, err
    	}
    
    	auxArr, err := c.getBids(biddableAsset)
    	if err != nil {
    		return nil, nil, err
    	}
    
    	var selectedBid *Bid = nil
    	for i, bid := range auxArr {
            if bid.Bidder.Id == bidDecisionParams.Bidder  {
    			selectedBid = &bid
    			if acceptBid {
    				auxArr[i].Status = Accepted
    			} else {
    				auxArr[i].Status = Rejected
    			}
    			break
            }
        }
    
    	if selectedBid == nil {
    		return nil, nil, errInvalidBidder
    	}
    
    	biddableAsset.Add("bids", auxArr)
    	biddableAsset.MarkModification(stub)
    
    	return biddableAsset, selectedBid, nil
    }
    
    func (c BidDecisionController) RejectBid(stub shared.LedgerBuildrStubInterface, params shared.LedgerBuildrAsset) protos.Response {
    	biddableAsset, _, err := c.bidResponse(stub, params, false)
    	if err != nil {
    		return api.NewApiResponsePtr("BidController:RejectBid", err, nil).SendResponse()
    	}
    	return c.SaveAbstractAsset(stub, biddableAsset)
    }
    
    func  (c BidDecisionController) _acceptBid(stub shared.LedgerBuildrStubInterface, params shared.LedgerBuildrAsset) (*protos.Response, error) {
    	biddableAsset, bid, err := c.bidResponse(stub, params, true)
    	if err != nil {
    		return nil, err
    	}
    
    	if biddableAsset.Quantity < bid.Quantity {
    		return nil, errInvalidBidQuantity
    	}
    
    	// TODO check is this transactional?
    	respAcceptedStock:= c.SaveAbstractAsset(stub, biddableAsset)
    	if respAcceptedStock.Status != shared.OK {
    		return &respAcceptedStock, nil
    	}
    
    	var soldStock *model.TraceableAsset
    	if (bid.Quantity == biddableAsset.Quantity) {
    		// TODO remove old bids before transferring asset?
    		soldStock = biddableAsset
    	} else if (bid.Quantity < biddableAsset.Quantity) {
    		splitParams := new(split.SplitParams)
    		splitParams.SetID(biddableAsset.GetID())
    		splitParams.SplitConfig.ArchiveOld = true
    		splitParams.SplitConfig.Bidirectional = true
    		splitParams.SplitConfig.ChildCount = 2
    		splitParams.SplitConfig.BaseAsset.AssetType = "stocked"
    
    		subAssets, err := split.SplitAsset(c.TraceblockBaseController, stub, biddableAsset, *splitParams)
    		if err != nil {
    			return nil, err
    		}
    
    		newStock := subAssets[0]
    		newStock.Quantity = biddableAsset.Quantity - bid.Quantity
    		newStock.Units = biddableAsset.Units
    		newStock.ArbitraryDataFields = biddableAsset.ArbitraryDataFields
    		delete(newStock.ArbitraryDataFields, "bids")
    
    		respNewStock:= c.SaveAbstractAsset(stub, &newStock)
    		if respNewStock.Status != shared.OK {
    			return &respNewStock, nil
    		}
    
    		soldStock = &subAssets[1]
    
    		soldStock.Quantity = bid.Quantity
    		soldStock.Units = biddableAsset.Units
    	}
    
    	soldStock.ArbitraryDataFields["status"] = "sold"
    
    	transferParams := new(transfer.TransferParams)
    	transferParams.SetID(soldStock.GetID())
    	transferParams.NewRole = bid.Bidder.Role
    	transferParams.NewOrganization = bid.Bidder.Org
    	transferParams.Location = soldStock.Location
    
    	transferredAsset := transfer.TransferAsset(stub, soldStock, transferParams)
    	
    	err = stats.RegisterSale(stub, soldStock.Quantity, bid.Price, soldStock.Units)
    	if err != nil {
    		return nil, err
    	}
    
    	ret := c.SaveAbstractAsset(stub, transferredAsset)
    	return &ret, nil
    }
    
    func (c BidDecisionController) AcceptBid(stub shared.LedgerBuildrStubInterface, params shared.LedgerBuildrAsset) protos.Response {
    	ret, err := c._acceptBid(stub, params)
    	if err != nil {
    		return api.NewApiResponsePtr("BidController:AcceptBid", err, nil).SendResponse()
    	}
    	return *ret
    }