Commit dc7ff5b3 authored by Matthew Sykes's avatar Matthew Sykes Committed by Gerrit Code Review
Browse files

Merge "[FAB-11680] TMS transactor: RequestRedeem"

parents d0ee108e eacbc1ea
......@@ -33,82 +33,144 @@ type Transactor struct {
//func (t *Transactor) RequestTransfer(inTokens []*token.InputId, tokensToTransfer []*token.RecipientTransferShare) (*token.TokenTransaction, error) {
func (t *Transactor) RequestTransfer(request *token.TransferRequest) (*token.TokenTransaction, error) {
var outputs []*token.PlainOutput
inputs, tokenType, _, err := t.getInputsFromTokenIds(request.GetTokenIds())
if err != nil {
return nil, err
}
for _, ttt := range request.GetShares() {
outputs = append(outputs, &token.PlainOutput{
Owner: ttt.Recipient,
Type: tokenType,
Quantity: ttt.Quantity,
})
}
// prepare transfer request
transaction := &token.TokenTransaction{
Action: &token.TokenTransaction_PlainAction{
PlainAction: &token.PlainTokenAction{
Data: &token.PlainTokenAction_PlainTransfer{
PlainTransfer: &token.PlainTransfer{
Inputs: inputs,
Outputs: outputs,
},
},
},
},
}
return transaction, nil
}
// RequestRedeem creates a TokenTransaction of type redeem request
func (t *Transactor) RequestRedeem(request *token.RedeemRequest) (*token.TokenTransaction, error) {
if len(request.GetTokenIds()) == 0 {
return nil, errors.New("no token ids in RedeemRequest")
}
if request.GetQuantityToRedeem() <= 0 {
return nil, errors.Errorf("quantity to redeem [%d] must be greater than 0", request.GetQuantityToRedeem())
}
inputs, tokenType, quantitySum, err := t.getInputsFromTokenIds(request.GetTokenIds())
if err != nil {
return nil, err
}
if quantitySum < request.QuantityToRedeem {
return nil, errors.Errorf("total quantity [%d] from TokenIds is less than quantity [%d] to be redeemed", quantitySum, request.QuantityToRedeem)
}
// add the output for redeem itself
var outputs []*token.PlainOutput
outputs = append(outputs, &token.PlainOutput{
Type: tokenType,
Quantity: request.QuantityToRedeem,
})
// add another output if there is remaining quantity after redemption
if quantitySum > request.QuantityToRedeem {
outputs = append(outputs, &token.PlainOutput{
Owner: t.PublicCredential, // PublicCredential is serialized identity for the creator
Type: tokenType,
Quantity: quantitySum - request.QuantityToRedeem,
})
}
// PlainRedeem shares the same data structure as PlainTransfer
transaction := &token.TokenTransaction{
Action: &token.TokenTransaction_PlainAction{
PlainAction: &token.PlainTokenAction{
Data: &token.PlainTokenAction_PlainRedeem{
PlainRedeem: &token.PlainTransfer{
Inputs: inputs,
Outputs: outputs,
},
},
},
},
}
return transaction, nil
}
// read token data from ledger for each token ids and calculate the sum of quantities for all token ids
// Returns InputIds, token type, sum of token quantities, and error in the case of failure
func (t *Transactor) getInputsFromTokenIds(tokenIds [][]byte) ([]*token.InputId, string, uint64, error) {
var inputs []*token.InputId
tokenType := ""
for _, inKeyBytes := range request.GetTokenIds() {
var tokenType string = ""
var quantitySum uint64 = 0
for _, inKeyBytes := range tokenIds {
// parse the composite key bytes into a string
inKey := parseCompositeKeyBytes(inKeyBytes)
// check whether the composite key conforms to the composite key of an output
namespace, components, err := splitCompositeKey(inKey)
if err != nil {
return nil, &customtx.InvalidTxError{Msg: fmt.Sprintf("error splitting input composite key: '%s'", err)}
return nil, "", 0, &customtx.InvalidTxError{Msg: fmt.Sprintf("error splitting input composite key: '%s'", err)}
}
if namespace != tokenOutput {
return nil, &customtx.InvalidTxError{Msg: fmt.Sprintf("namespace not '%s': '%s'", tokenOutput, namespace)}
return nil, "", 0, &customtx.InvalidTxError{Msg: fmt.Sprintf("namespace not '%s': '%s'", tokenOutput, namespace)}
}
if len(components) != 2 {
return nil, &customtx.InvalidTxError{Msg: fmt.Sprintf("not enough components in output ID composite key; expected 2, received '%s'", components)}
return nil, "", 0, &customtx.InvalidTxError{Msg: fmt.Sprintf("not enough components in output ID composite key; expected 2, received '%s'", components)}
}
txID := components[0]
index, err := strconv.Atoi(components[1])
if err != nil {
return nil, &customtx.InvalidTxError{Msg: fmt.Sprintf("error parsing output index '%s': '%s'", components[1], err)}
return nil, "", 0, &customtx.InvalidTxError{Msg: fmt.Sprintf("error parsing output index '%s': '%s'", components[1], err)}
}
// make sure the output exists in the ledger
inBytes, err := t.Ledger.GetState(tokenNamespace, inKey)
if err != nil {
return nil, err
return nil, "", 0, err
}
if inBytes == nil {
return nil, &customtx.InvalidTxError{Msg: fmt.Sprintf("input '%s' does not exist", inKey)}
return nil, "", 0, &customtx.InvalidTxError{Msg: fmt.Sprintf("input '%s' does not exist", inKey)}
}
input := &token.PlainOutput{}
err = proto.Unmarshal(inBytes, input)
if err != nil {
return nil, &customtx.InvalidTxError{Msg: fmt.Sprintf("error unmarshaling input bytes: '%s'", err)}
return nil, "", 0, &customtx.InvalidTxError{Msg: fmt.Sprintf("error unmarshaling input bytes: '%s'", err)}
}
// check the token type - only one type allowed per transfer
if tokenType == "" {
tokenType = input.Type
} else if tokenType != input.Type {
return nil, &customtx.InvalidTxError{Msg: fmt.Sprintf("two or more token types specified in input: '%s', '%s'", tokenType, input.Type)}
return nil, "", 0, &customtx.InvalidTxError{Msg: fmt.Sprintf("two or more token types specified in input: '%s', '%s'", tokenType, input.Type)}
}
// add input to list of inputs
inputs = append(inputs, &token.InputId{TxId: txID, Index: uint32(index)})
}
for _, ttt := range request.GetShares() {
outputs = append(outputs, &token.PlainOutput{
Owner: ttt.Recipient,
Type: tokenType,
Quantity: ttt.Quantity,
})
}
// prepare transfer request
transaction := &token.TokenTransaction{
Action: &token.TokenTransaction_PlainAction{
PlainAction: &token.PlainTokenAction{
Data: &token.PlainTokenAction_PlainTransfer{
PlainTransfer: &token.PlainTransfer{
Inputs: inputs,
Outputs: outputs,
},
},
},
},
// sum up the quantity
quantitySum += input.Quantity
}
return transaction, nil
}
// RequestRedeem creates a TokenTransaction of type redeem request
func (t *Transactor) RequestRedeem(request *token.RedeemRequest) (*token.TokenTransaction, error) {
panic("not implemented yet")
return inputs, tokenType, quantitySum, nil
}
// ListTokens creates a TokenTransaction that lists the unspent tokens owned by owner.
......
......@@ -187,7 +187,7 @@ var _ = Describe("Transactor", func() {
{Recipient: []byte("R2"), Quantity: 1002},
{Recipient: []byte("R3"), Quantity: 1003},
}
transactor = &plain.Transactor{}
transactor = &plain.Transactor{PublicCredential: []byte("Alice")}
})
It("converts a transfer request with no inputs into a token transaction", func() {
......@@ -457,4 +457,102 @@ var _ = Describe("Transactor", func() {
})
})
Describe("RequestRedeem", func() {
var (
fakeLedger *mock.LedgerWriter
redeemRequest *token.RedeemRequest
inputBytes []byte
inputQuantity uint64
redeemQuantity uint64
)
BeforeEach(func() {
inputQuantity = 99
input := &token.PlainOutput{
Owner: []byte("owner-1"),
Type: "TOK1",
Quantity: inputQuantity,
}
var err error
inputBytes, err = proto.Marshal(input)
Expect(err).ToNot(HaveOccurred())
fakeLedger = &mock.LedgerWriter{}
fakeLedger.SetStateReturns(nil)
//fakeLedger.GetStateReturnsOnCall(0, inputBytes, nil)
fakeLedger.GetStateReturns(inputBytes, nil)
transactor.Ledger = fakeLedger
})
It("creates a token transaction with 1 output if all tokens are redeemed", func() {
redeemQuantity = inputQuantity
redeemRequest = &token.RedeemRequest{
Credential: []byte("credential"),
TokenIds: [][]byte{[]byte(string("\x00") + "tokenOutput" + string("\x00") + "robert" + string("\x00") + "0" + string("\x00"))},
QuantityToRedeem: redeemQuantity,
}
tt, err := transactor.RequestRedeem(redeemRequest)
Expect(err).NotTo(HaveOccurred())
Expect(tt).To(Equal(&token.TokenTransaction{
Action: &token.TokenTransaction_PlainAction{
PlainAction: &token.PlainTokenAction{
Data: &token.PlainTokenAction_PlainRedeem{
PlainRedeem: &token.PlainTransfer{
Inputs: []*token.InputId{
{TxId: "robert", Index: uint32(0)},
},
Outputs: []*token.PlainOutput{
{Type: "TOK1", Quantity: redeemQuantity},
},
},
},
},
},
}))
})
It("creates a token transaction with 2 outputs if some tokens are redeemed", func() {
redeemQuantity = 50
unredeemedQuantity := inputQuantity - 50
redeemRequest = &token.RedeemRequest{
Credential: []byte("credential"),
TokenIds: [][]byte{[]byte(string("\x00") + "tokenOutput" + string("\x00") + "robert" + string("\x00") + "0" + string("\x00"))},
QuantityToRedeem: redeemQuantity,
}
tt, err := transactor.RequestRedeem(redeemRequest)
Expect(err).NotTo(HaveOccurred())
Expect(tt).To(Equal(&token.TokenTransaction{
Action: &token.TokenTransaction_PlainAction{
PlainAction: &token.PlainTokenAction{
Data: &token.PlainTokenAction_PlainRedeem{
PlainRedeem: &token.PlainTransfer{
Inputs: []*token.InputId{
{TxId: "robert", Index: uint32(0)},
},
Outputs: []*token.PlainOutput{
{Type: "TOK1", Quantity: redeemQuantity},
{Owner: []byte("Alice"), Type: "TOK1", Quantity: unredeemedQuantity},
},
},
},
},
},
}))
})
Context("when quantity to redeem is greater than input quantity", func() {
BeforeEach(func() {
redeemQuantity = inputQuantity + 10
redeemRequest = &token.RedeemRequest{
Credential: []byte("credential"),
TokenIds: [][]byte{[]byte(string("\x00") + "tokenOutput" + string("\x00") + "robert" + string("\x00") + "0" + string("\x00"))},
QuantityToRedeem: redeemQuantity,
}
})
It("returns an error", func() {
_, err := transactor.RequestRedeem(redeemRequest)
Expect(err).To(MatchError(fmt.Sprintf("total quantity [%d] from TokenIds is less than quantity [%d] to be redeemed", inputQuantity, redeemQuantity)))
})
})
})
})
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment