Skip to content
Snippets Groups Projects
Commit 1767f078 authored by Gomez Goiri, Aitor's avatar Gomez Goiri, Aitor
Browse files

Implementing discard operation in the chaincode

Redefining asset-archive operation functionality.
parent a4c9f2e8
No related branches found
No related tags found
No related merge requests found
...@@ -89,7 +89,7 @@ Moved to stock caaa642c25d96a5d8f14cf031a898fed ...@@ -89,7 +89,7 @@ Moved to stock caaa642c25d96a5d8f14cf031a898fed
$ cd ../cement-cli $ cd ../cement-cli
$ node index.js bid $ node index.js bid
? Select asset 19e2f7a99288dfc9a2902e71bfc9dfe6 (Stock, type: 1002, status: stocked) ? Select asset 19e2f7a99288dfc9a2902e71bfc9dfe6 (Stock, type: 1002, status: stocked)
? Choose an ammount of slag 34 ? Choose an amount of slag 34
? Provide the offered price 3 ? Provide the offered price 3
Bid for slag stock 19e2f7a99288dfc9a2902e71bfc9dfe6 Bid for slag stock 19e2f7a99288dfc9a2902e71bfc9dfe6
...@@ -100,8 +100,29 @@ $ node index.js bid-accept ...@@ -100,8 +100,29 @@ $ node index.js bid-accept
0LWNvbXBhbnkxLmNvbSxPPWNlbWVudC1jb21wYW55MS5jb20sTD1SYWxlaWdoLFNUPU5vcnRoIENhcm9saW5hLEM9VVM= (quantity: 34, price: 3) 0LWNvbXBhbnkxLmNvbSxPPWNlbWVudC1jb21wYW55MS5jb20sTD1SYWxlaWdoLFNUPU5vcnRoIENhcm9saW5hLEM9VVM= (quantity: 34, price: 3)
Bid from cement-company1-com:eDUwOTo6Q049dXNlcixPVT1jbGllbnQrT1U9b3JnMStPVT1kZXBhcnRtZW50MTo6Q049Y2EuY2VtZW50LWNvbXBhbnkxLmNvbSxPPWNlbWVudC1jb21wYW55MS5jb20sTD1SYWxlaWdoLFNUPU5vcnRoIENhcm9saW5hLEM9VVM= accepted for asset 19e2f7a99288dfc9a2902e71bfc9dfe6 Bid from cement-company1-com:eDUwOTo6Q049dXNlcixPVT1jbGllbnQrT1U9b3JnMStPVT1kZXBhcnRtZW50MTo6Q049Y2EuY2VtZW50LWNvbXBhbnkxLmNvbSxPPWNlbWVudC1jb21wYW55MS5jb20sTD1SYWxlaWdoLFNUPU5vcnRoIENhcm9saW5hLEM9VVM= accepted for asset 19e2f7a99288dfc9a2902e71bfc9dfe6
$ node index.js discard-stock
? Select asset 7bbed24d305800c2f2e61b7f14539249 (Stock, type: 1002, status: stocked)
? Choose an amount of slag (max: 7297 kg): 729
Discarded stock 15fb63d1d6071f9ee101bc41e5b4c75f (new stock e511a4dbb32669282e14838ec012e2e0)
$ cd ../pubadmin-cli $ cd ../pubadmin-cli
$ node index.js stats $ node index.js stats
┌─────────────┬────────────┬─────────────────┬────────────────┬───────────────────────┐
│ │ Total slag │ Reused │ Discarded │ Price (€/t)
│ │ t │ t [%] │ t [%] │ Avg [Min, Max] │
├─────────────┼────────────┼─────────────────┼────────────────┼───────────────────────┤
│ sidenor.com │ 17.375 │ 3.763 [21.66 %] │ 1.302 [7.49 %] │ 18.13 [49.74, 522.61] │
└─────────────┴────────────┴─────────────────┴────────────────┴───────────────────────┘
┌─────────────────────┬────────────┬─────────────────────────┐
│ │ Total slag │ Price (€/t)
│ │ t │ Avg [Min, Max] │
├─────────────────────┼────────────┼─────────────────────────┤
│ cement-company1.com │ 0.667 │ 241.38 [121.79, 522.61] │
├─────────────────────┼────────────┼─────────────────────────┤
│ cement-company2.com │ 3.096 │ 49.74 [49.74, 49.74] │
└─────────────────────┴────────────┴─────────────────────────┘
# Or to generate/simulate a complete cycle: # Or to generate/simulate a complete cycle:
$ cd ../batch-sim $ cd ../batch-sim
......
...@@ -9,7 +9,8 @@ const { ...@@ -9,7 +9,8 @@ const {
editComposition, editComposition,
sendToCone, sendToCone,
sendToStock, sendToStock,
acceptBid acceptBid,
discardStock
} = require("@hypercog/sidenor-cli") } = require("@hypercog/sidenor-cli")
const { bidAsset } = require("@hypercog/cement-cli") const { bidAsset } = require("@hypercog/cement-cli")
...@@ -42,23 +43,27 @@ const simulateCycle = async (arg1, cmd) => { ...@@ -42,23 +43,27 @@ const simulateCycle = async (arg1, cmd) => {
//batchId = await createAssetAndSendToCone(apiClient, "simulated asset 2") //batchId = await createAssetAndSendToCone(apiClient, "simulated asset 2")
//batchId = await createAssetAndSendToCone(apiClient, "simulated asset 3") //batchId = await createAssetAndSendToCone(apiClient, "simulated asset 3")
const selectedStock = await sendToStock(apiClient, { assetId: batchId }) const mergedStock = await sendToStock(apiClient, { assetId: batchId })
const selectedStockId = await discardStock(apiClient, {
assetId: mergedStock.id
})
const selectedStock = await apiClient.getAsset(selectedStockId)
await signInAs(apiClient, otherArgs.cement1User, password) await signInAs(apiClient, otherArgs.cement1User, password)
await bidAsset(apiClient, { await bidAsset(apiClient, {
assetId: selectedStock.id, assetId: selectedStockId,
price: faker.datatype.number({ min: 50, max: 150 }) price: faker.datatype.number({ min: 50, max: 150 })
}) })
await signInAs(apiClient, otherArgs.cement2User, password) await signInAs(apiClient, otherArgs.cement2User, password)
await bidAsset(apiClient, { await bidAsset(apiClient, {
assetId: selectedStock.id, assetId: selectedStockId,
quantity: selectedStock.quantity - 500, quantity: selectedStock.quantity - 500,
price: faker.datatype.number({ min: 25, max: 200 }) price: faker.datatype.number({ min: 25, max: 200 })
}) })
await signInAs(apiClient, otherArgs.sidenorUser, password) await signInAs(apiClient, otherArgs.sidenorUser, password)
await acceptBid(apiClient, { assetId: selectedStock.id }) await acceptBid(apiClient, { assetId: selectedStockId })
} }
const program = createCLIApp() const program = createCLIApp()
......
...@@ -19,7 +19,7 @@ const bidAsset = async ( ...@@ -19,7 +19,7 @@ const bidAsset = async (
{ {
type: "number", type: "number",
name: "inputQuantity", name: "inputQuantity",
message: `Choose an ammount of slag (max: ${assetToBid.quantity} ${assetToBid.units}):`, message: `Choose an amount of slag to bid for (max: ${assetToBid.quantity} ${assetToBid.units}):`,
default: Math.floor(assetToBid.quantity * 0.1) // Default: 10% default: Math.floor(assetToBid.quantity * 0.1) // Default: 10%
} }
]) ])
......
...@@ -234,37 +234,20 @@ const discardStock = async (apiClient, { assetId: paramAssetId, quantity }) => { ...@@ -234,37 +234,20 @@ const discardStock = async (apiClient, { assetId: paramAssetId, quantity }) => {
{ {
type: "number", type: "number",
name: "inputQuantity", name: "inputQuantity",
message: `Choose an ammount of slag (max: ${assetToDiscard.quantity} ${assetToDiscard.units}):`, message: `Choose an amount of slag to discard (max: ${assetToDiscard.quantity} ${assetToDiscard.units}):`,
default: Math.floor(assetToDiscard.quantity * 0.1) // Default: 10% default: Math.floor(assetToDiscard.quantity * 0.1) // Default: 10%
} }
]) ])
chosenQuantity = inputQuantity chosenQuantity = inputQuantity
} }
const newAssets = await apiClient.splitAsset(assetId, 2, { const [newStock, discardedBatch] = await apiClient.deleteAsset(
archiveOld: true, assetId,
bidirectional: true, chosenQuantity
base: { )
type: assetToDiscard.type,
fields: assetToDiscard.fields
}
})
await apiClient.modifyAsset({
id: newAssets[0],
quantity: assetToDiscard.quantity - chosenQuantity,
units: assetToDiscard.units
})
await apiClient.modifyAsset({
id: newAssets[1],
quantity: chosenQuantity,
units: assetToDiscard.units
})
await apiClient.deleteAsset(newAssets[1])
console.log(`Discarded stock ${newAssets[1]} (new stock ${newAssets[0]})`) console.log(`Discarded stock ${discardedBatch} (new stock ${newStock})`)
return newStock
} }
// To be reused as a library in @hypercog/batch-sim // To be reused as a library in @hypercog/batch-sim
......
...@@ -38,6 +38,25 @@ class HypercogApiClient extends TraceblockApiClient { ...@@ -38,6 +38,25 @@ class HypercogApiClient extends TraceblockApiClient {
body: { id, bidder } body: { id, bidder }
}) })
} }
/**
* Archives an asset (overrides original definition).
*
* @param {string} id Asset identifier.
* @param {number} quantity Magnitude measured in the units specified in the associated asset definition.
*/
async deleteAsset(id, quantity) {
const { payload } = await this._invoke("asset-archive", {
body: { id, quantity }
})
return payload
}
Bid(id, bidder) {
return this._invoke("asset-archive", {
body: { id, bidder }
})
}
} }
module.exports = HypercogApiClient module.exports = HypercogApiClient
/**
* acl.go
*
* COPYRIGHT: FUNDACIÓN TECNALIA RESEARCH & INNOVATION, 2022.
*/
package discard
import (
"git.code.tecnalia.com/ledgerbuilder/sdk/core/api"
"git.code.tecnalia.com/ledgerbuilder/sdk/core/fabric/protos"
"git.code.tecnalia.com/ledgerbuilder/sdk/shared"
"git.code.tecnalia.com/traceblock/sdk/controller/utils"
)
func (c DiscardController) CheckACL(validations []utils.OwnershipACL, trigger shared.TriggerFunction) shared.TriggerFunction {
return func(stub shared.LedgerBuildrStubInterface, requestAsset shared.LedgerBuildrAsset) protos.Response {
const fnName = "ACL"
storedAsset, err := c.readAsset(stub, requestAsset)
if err != nil {
return api.NewApiResponsePtr(fnName, err, nil).SendResponse()
}
for _, validation := range validations {
if err := validation(storedAsset.Owner, stub); err != nil {
return api.NewApiResponsePtr(fnName, err, nil).SendResponse()
}
}
return trigger(stub, requestAsset)
}
}
func (c DiscardController) OnlySameOrgAndRole(trigger shared.TriggerFunction) shared.TriggerFunction {
return c.CheckACL([]utils.OwnershipACL{utils.OwnedBySameOrg, utils.OwnedBySameRoleOrAdmin}, trigger)
}
\ No newline at end of file
/**
* controller.go
*
* COPYRIGHT: FUNDACIÓN TECNALIA RESEARCH & INNOVATION, 2022.
*/
package discard
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"
"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/model"
)
type DiscardController struct {
base.TraceblockBaseController
}
var (
errInvalidInputData = errors.New("failed to read model")
errInvalidDiscardQuantity = errors.New("quantity to discard is greater than the available quantity")
)
// constructor like function
func NewDiscardController() *DiscardController {
ctl := new(DiscardController)
ctl.LowLevelController = controller.NewLowLevelController()
ctl.SetDataModelClosure(ctl.modelClosure)
return ctl
}
func (c DiscardController) modelClosure(stub shared.LedgerBuildrStubInterface) shared.LedgerBuildrAsset {
return NewDiscardParams()
}
func (c DiscardController) 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 DiscardController) readAsset(stub shared.LedgerBuildrStubInterface, requestAsset shared.LedgerBuildrAsset) (*model.TraceableAsset, error) {
return c.readTraceableAsset(stub, requestAsset.GetID())
}
// HyperCOG specific discard operation
func (c DiscardController) _discardAsset(stub shared.LedgerBuildrStubInterface, params *DiscardParams, discardableAsset *model.TraceableAsset) (*protos.Response, error) {
if discardableAsset.Quantity < params.Quantity {
return nil, errInvalidDiscardQuantity
}
var discardedStock *model.TraceableAsset
generatedIds := make([]string, 2)
if params.Quantity == discardableAsset.Quantity {
discardedStock = discardableAsset
} else {
// discardableAsset.Quantity > discardParams.Quantity
splitParams := new(split.SplitParams)
splitParams.SetID(discardableAsset.GetID())
splitParams.SplitConfig.ArchiveOld = true
splitParams.SplitConfig.Bidirectional = true
splitParams.SplitConfig.ChildCount = 2
splitParams.SplitConfig.BaseAsset.AssetType = discardableAsset.AssetType
splitParams.SplitConfig.BaseAsset.ArbitraryDataFields = discardableAsset.ArbitraryDataFields
subAssets, err := split.SplitAsset(c.TraceblockBaseController, stub, discardableAsset, *splitParams)
if err != nil {
return nil, err
}
for i, c := range subAssets {
generatedIds[i] = c.GetID()
}
newStock := subAssets[0]
newStock.Quantity = discardableAsset.Quantity - params.Quantity
newStock.Units = discardableAsset.Units
respNewStock:= c.SaveAbstractAsset(stub, &newStock)
if respNewStock.Status != shared.OK {
return &respNewStock, nil
}
discardedStock = &subAssets[1]
discardedStock.Quantity = params.Quantity
discardedStock.Units = discardableAsset.Units
}
discardedStock.ArbitraryDataFields["status"] = "discarded"
err := stats.RegisterDiscard(stub, discardedStock.Quantity, discardedStock.Units)
if err != nil {
return nil, err
}
respDiscarted := c.SaveAbstractAsset(stub, discardedStock)
if respDiscarted.Status != shared.OK {
return &respDiscarted, nil
}
ret := api.NewAPIGenericResponsePtr("DiscardController:Discard", nil, generatedIds).SendResponse()
return &ret, nil
}
func (c DiscardController) _archiveAsset(stub shared.LedgerBuildrStubInterface, params shared.LedgerBuildrAsset) (*protos.Response, error) {
discardParams, ok := params.(*DiscardParams)
if discardParams == nil || !ok {
//failed when casting/fetching input data
return nil, errInvalidInputData
}
discardableAsset, err := c.readAsset(stub, discardParams)
if err != nil {
return nil, err
}
status, ok := discardableAsset.Get("status")
if status != "stocked" || status == "stocked" && discardParams.Quantity == 0 {
// Done in MarkAssetAsDeleted middleware in normal operation
discardableAsset.MarkModification(stub)
discardableAsset.MarkAsDeleted()
responseArchive := c.DeleteAbstractAsset(stub, discardableAsset)
return &responseArchive, nil
}
// HyperCOG specific discard operation
return c._discardAsset(stub, discardParams, discardableAsset)
}
func (c DiscardController) Discard(stub shared.LedgerBuildrStubInterface, params shared.LedgerBuildrAsset) protos.Response {
ret, err := c._archiveAsset(stub, params)
if err != nil {
return api.NewApiResponsePtr("DiscardController:Discard", err, nil).SendResponse()
}
return *ret
}
\ No newline at end of file
/**
* input.go
*
* COPYRIGHT: FUNDACIÓN TECNALIA RESEARCH & INNOVATION, 2022.
*/
package discard
import (
"git.code.tecnalia.com/ledgerbuilder/sdk/core/model/io"
"git.code.tecnalia.com/ledgerbuilder/sdk/shared"
errs "git.code.tecnalia.com/traceblock/sdk/constants"
)
type DiscardParams struct {
shared.LedgerBuildrAsset
AssetId string `json:"id"`
Quantity uint32 `json:"quantity,omitempty"`
}
func (p *DiscardParams) GetID() string {
return p.AssetId
}
func (p *DiscardParams) Get(key string) (interface{}, bool) {
return nil, false
}
func (p *DiscardParams) SetID(id string) {
p.AssetId = id
}
func (p *DiscardParams) StubBytes() []byte {
return []byte{}
}
func (p *DiscardParams) LedgerBytes() []byte {
return nil
}
func (p *DiscardParams) ReadValidation() bool {
return false
}
// set write validations to false to avoid ledger persistency
func (p *DiscardParams) WriteValidation() bool {
return false
}
func (p *DiscardParams) StubReader(stub shared.LedgerBuildrStubInterface) (shared.LedgerBuildrAsset, error) {
if stub != nil {
parseErr := io.NewJSONSerializer().LoadFromByteArray(&p, stub.GetPayload())
return p, parseErr
}
return nil, errs.StubReader
}
func (p *DiscardParams) LedgerReader(data []byte) (shared.LedgerBuildrAsset, error) {
if data != nil {
parseErr := io.NewJSONSerializer().LoadFromByteArray(&p, data)
return p, parseErr
}
return nil, errs.LedgerReader
}
func NewDiscardParams() shared.LedgerBuildrAsset {
return new(DiscardParams)
}
...@@ -21,6 +21,7 @@ import ( ...@@ -21,6 +21,7 @@ import (
"git.code.tecnalia.com/traceblock/sdk/middleware" "git.code.tecnalia.com/traceblock/sdk/middleware"
"git.code.tecnalia.com/blockchain/hypercog/controller/bid" "git.code.tecnalia.com/blockchain/hypercog/controller/bid"
"git.code.tecnalia.com/blockchain/hypercog/controller/discard"
"git.code.tecnalia.com/blockchain/hypercog/controller/stats" "git.code.tecnalia.com/blockchain/hypercog/controller/stats"
m2 "git.code.tecnalia.com/blockchain/hypercog/middleware" m2 "git.code.tecnalia.com/blockchain/hypercog/middleware"
) )
...@@ -40,24 +41,22 @@ var ( ...@@ -40,24 +41,22 @@ var (
// bid controller // bid controller
bidController *bid.BidController bidController *bid.BidController
bidDecisionController *bid.BidDecisionController bidDecisionController *bid.BidDecisionController
discardController *discard.DiscardController
) )
func init() { func init() {
assetController = asset.NewTraceableAssetController() assetController = asset.NewTraceableAssetController()
// for debug/testing functions
testController = controller.NewHelperController()
// for ui related helper calls
uiController = controller.NewUIController()
// transfer controller
transferController = transfer.NewTransferAssetController() transferController = transfer.NewTransferAssetController()
// asset split controller
splitController = split.NewSplitController() splitController = split.NewSplitController()
// asset join controller
joinController = join.NewJoinController() joinController = join.NewJoinController()
// qr controller
qrController = qr.NewQRController() qrController = qr.NewQRController()
// barcode controller
barcodeController = barcode.NewBarcodeController() barcodeController = barcode.NewBarcodeController()
discardController = discard.NewDiscardController()
// for ui related helper calls
uiController = controller.NewUIController()
// for debug/testing functions
testController = controller.NewHelperController()
// bid controllers // bid controllers
bidController = bid.NewBidController() bidController = bid.NewBidController()
bidDecisionController = bid.NewBidDecisionController() bidDecisionController = bid.NewBidDecisionController()
...@@ -113,14 +112,15 @@ func ContextOperations(m shared.AbstractChaincodeOperationManager) error { ...@@ -113,14 +112,15 @@ func ContextOperations(m shared.AbstractChaincodeOperationManager) error {
nil, nil,
assetController.OnlySameOrgAndRole(assetController.UpdateAsset), assetController.OnlySameOrgAndRole(assetController.UpdateAsset),
). ).
AddOperation( AddOperationWithoutDefaults(
"asset-archive", "asset-archive",
shared.WRITE_OP, shared.WRITE_OP,
[]shared.MiddlewareInterface{
middleware.MarkAssetAsDeleted,
},
nil, nil,
assetController.OnlySameOrgAndRole(assetController.DeleteAbstractAsset), discardController.StubReader,
discardController.LedgerReader,
nil,
nil,
discardController.OnlySameOrgAndRole(discardController.Discard),
). ).
AddOperationWithoutDefaults( AddOperationWithoutDefaults(
"asset-transfer", "asset-transfer",
......
...@@ -220,6 +220,23 @@ func RegisterSlag(stub shared.LedgerBuildrStubInterface, quantity uint32, units ...@@ -220,6 +220,23 @@ func RegisterSlag(stub shared.LedgerBuildrStubInterface, quantity uint32, units
return nil return nil
} }
func RegisterDiscard(stub shared.LedgerBuildrStubInterface, quantity uint32, units string) (error) {
orgStats, err := retrieveExistingStats(stub)
if err != nil {
return err
}
orgStats.RegisterDiscard(quantity, units)
err = upgradeSteelStats(stub, orgStats)
if err != nil {
return err
}
return nil
}
func RegisterSale(stub shared.LedgerBuildrStubInterface, acceptedBid *model.Bid, units string) (error) { func RegisterSale(stub shared.LedgerBuildrStubInterface, acceptedBid *model.Bid, units string) (error) {
senderOrgStats, err := retrieveExistingStats(stub) senderOrgStats, err := retrieveExistingStats(stub)
if err != nil { if err != nil {
......
...@@ -28,6 +28,10 @@ func (raw *RawSteelOrgStats) RegisterSlag(quantity uint32, units string) { ...@@ -28,6 +28,10 @@ func (raw *RawSteelOrgStats) RegisterSlag(quantity uint32, units string) {
raw.TotalSlag += toTons(quantity, units) raw.TotalSlag += toTons(quantity, units)
} }
func (raw *RawSteelOrgStats) RegisterDiscard(quantity uint32, units string) {
raw.SlagDiscarded += toTons(quantity, units)
}
func (raw *RawSteelOrgStats) UpgradeSale(quantity uint32, price float32, units string) { func (raw *RawSteelOrgStats) UpgradeSale(quantity uint32, price float32, units string) {
soldBatch := toTons(quantity, units) soldBatch := toTons(quantity, units)
raw.SlagReused += soldBatch raw.SlagReused += soldBatch
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment