Select Git revision
bid_controller.go
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
}