Skip to content

Commit ba25480

Browse files
committed
[FAB-11671] Token client: redeem function
- add RequestRedeem to Prover interface - add Redeem to client API Change-Id: Iad6c0d0fb54633bb8ac8955a94ab825c5aa6d5e8 Signed-off-by: Wenjian Qiao <wenjianq@gmail.com>
1 parent e8408b1 commit ba25480

File tree

5 files changed

+348
-0
lines changed

5 files changed

+348
-0
lines changed

token/client/client.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ type Prover interface {
3131
// request fails
3232
RequestTransfer(tokenIDs [][]byte, shares []*token.RecipientTransferShare, signingIdentity tk.SigningIdentity) ([]byte, error)
3333

34+
// RequestRedeem allows the redemption of the tokens in the input tokenIDs
35+
// It queries the ledger to read detail for each token id.
36+
// It creates a token transaction with an output for redeemed tokens and
37+
// possibly another output to transfer the remaining tokens, if any, to the same user
38+
RequestRedeem(tokenIDs [][]byte, quantity uint64, signingIdentity tk.SigningIdentity) ([]byte, error)
39+
3440
// ListTokens allows the client to submit a list request to a prover peer service;
3541
// it returns a list of TokenOutput and an error message in the case the request fails
3642
ListTokens(signingIdentity tk.SigningIdentity) ([]*token.TokenOutput, error)
@@ -139,6 +145,31 @@ func (c *Client) Transfer(tokenIDs [][]byte, shares []*token.RecipientTransferSh
139145
return txEnvelope, txid, ordererStatus, committed, err
140146
}
141147

148+
// Redeem allows the redemption of the tokens in the input tokenIDs
149+
// The 'waitTimeout' parameter defines the time to wait for the transaction to be committed.
150+
// If it is 0, the function will return immediately after receiving a response from the orderer
151+
// without waiting for the transaction to be committed.
152+
// If it is greater than 0, the function will wait until the transaction commit event is received or wait timed out, whichever is earlier.
153+
// This API submits the transaction to the orderer and returns envelope, transaction id, orderer status, committed boolean, and error.
154+
// When an error is returned, check the orderer status.
155+
// If it is SUCCESS (200), the transaction has been successfully submitted regardless of the error.
156+
// The application must analyze the error and get the transaction status to make sure the transaction is either committed or invalidated.
157+
// If the transaction is invalidated, the application may call the API again after fixing the error.
158+
func (c *Client) Redeem(tokenIDs [][]byte, quantity uint64, waitTimeout time.Duration) (*common.Envelope, string, *common.Status, bool, error) {
159+
serializedTokenTx, err := c.Prover.RequestRedeem(tokenIDs, quantity, c.SigningIdentity)
160+
if err != nil {
161+
return nil, "", nil, false, err
162+
}
163+
164+
txEnvelope, txid, err := c.TxSubmitter.CreateTxEnvelope(serializedTokenTx)
165+
if err != nil {
166+
return nil, "", nil, false, err
167+
}
168+
169+
ordererStatus, committed, err := c.TxSubmitter.Submit(txEnvelope, waitTimeout)
170+
return txEnvelope, txid, ordererStatus, committed, err
171+
}
172+
142173
// ListTokens allows the client to submit a list request to a prover peer service;
143174
// it returns a list of TokenOutput and an error in the case the request fails
144175
func (c *Client) ListTokens() ([]*token.TokenOutput, error) {

token/client/client_test.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ var _ = Describe("Client", func() {
4444
fakeProver = &mock.Prover{}
4545
fakeProver.RequestImportReturns(payload.Data, nil) // same data as payload
4646
fakeProver.RequestTransferReturns(payload.Data, nil)
47+
fakeProver.RequestRedeemReturns(payload.Data, nil)
4748

4849
fakeSigningIdentity = &mock.SigningIdentity{}
4950
fakeSigningIdentity.SerializeReturns([]byte("creator"), nil) // same signature as envelope
@@ -256,6 +257,100 @@ var _ = Describe("Client", func() {
256257
})
257258
})
258259

260+
Describe("Redeem", func() {
261+
var (
262+
tokenIDs [][]byte
263+
quantity uint64
264+
)
265+
266+
BeforeEach(func() {
267+
// input data for redeem
268+
tokenIDs = [][]byte{[]byte("id1"), []byte("id2")}
269+
quantity = 100
270+
})
271+
272+
It("returns tx envelope without error", func() {
273+
txEnvelope, txid, ordererStatus, committed, err := tokenClient.Redeem(tokenIDs, quantity, 10*time.Second)
274+
Expect(err).NotTo(HaveOccurred())
275+
Expect(txEnvelope).To(Equal(envelope))
276+
Expect(txid).To(Equal(expectedTxid))
277+
Expect(*ordererStatus).To(Equal(common.Status_SUCCESS))
278+
Expect(committed).To(Equal(true))
279+
280+
Expect(fakeProver.RequestRedeemCallCount()).To(Equal(1))
281+
ids, num, signingIdentity := fakeProver.RequestRedeemArgsForCall(0)
282+
Expect(ids).To(Equal(tokenIDs))
283+
Expect(num).To(Equal(quantity))
284+
Expect(signingIdentity).To(Equal(fakeSigningIdentity))
285+
286+
Expect(fakeTxSubmitter.CreateTxEnvelopeCallCount()).To(Equal(1))
287+
txBytes := fakeTxSubmitter.CreateTxEnvelopeArgsForCall(0)
288+
Expect(txBytes).To(Equal(payload.Data))
289+
290+
Expect(fakeTxSubmitter.SubmitCallCount()).To(Equal(1))
291+
txEnvelope, waitTime := fakeTxSubmitter.SubmitArgsForCall(0)
292+
Expect(txEnvelope).To(Equal(envelope))
293+
Expect(waitTime).To(Equal(10 * time.Second))
294+
})
295+
296+
Context("when prover.RequestRedeem fails", func() {
297+
BeforeEach(func() {
298+
fakeProver.RequestRedeemReturns(nil, errors.New("wild-banana"))
299+
})
300+
301+
It("returns an error", func() {
302+
envelope, txid, ordererStatus, committed, err := tokenClient.Redeem(tokenIDs, quantity, 0)
303+
Expect(err).To(MatchError("wild-banana"))
304+
Expect(envelope).To(BeNil())
305+
Expect(txid).To(Equal(""))
306+
Expect(ordererStatus).To(BeNil())
307+
Expect(committed).To(Equal(false))
308+
309+
Expect(fakeProver.RequestRedeemCallCount()).To(Equal(1))
310+
Expect(fakeTxSubmitter.CreateTxEnvelopeCallCount()).To(Equal(0))
311+
Expect(fakeTxSubmitter.SubmitCallCount()).To(Equal(0))
312+
})
313+
})
314+
315+
Context("when TxSubmitter CreateTxEnvelope fails", func() {
316+
BeforeEach(func() {
317+
fakeTxSubmitter.CreateTxEnvelopeReturns(nil, "", errors.New("wild-banana"))
318+
})
319+
320+
It("returns an error", func() {
321+
envelope, txid, ordererStatus, committed, err := tokenClient.Redeem(tokenIDs, quantity, 0)
322+
Expect(err).To(MatchError("wild-banana"))
323+
Expect(envelope).To(BeNil())
324+
Expect(txid).To(Equal(""))
325+
Expect(ordererStatus).To(BeNil())
326+
Expect(committed).To(Equal(false))
327+
328+
Expect(fakeProver.RequestRedeemCallCount()).To(Equal(1))
329+
Expect(fakeTxSubmitter.CreateTxEnvelopeCallCount()).To(Equal(1))
330+
Expect(fakeTxSubmitter.SubmitCallCount()).To(Equal(0))
331+
})
332+
})
333+
334+
Context("when TxSubmitter Submit fails", func() {
335+
BeforeEach(func() {
336+
fakeTxSubmitter.SubmitReturns(nil, false, errors.New("wild-banana"))
337+
})
338+
339+
It("returns an error", func() {
340+
txEnvelope, txid, ordererStatus, committed, err := tokenClient.Redeem(tokenIDs, quantity, 0)
341+
Expect(err).To(MatchError("wild-banana"))
342+
Expect(txEnvelope).To(Equal(envelope))
343+
Expect(txid).To(Equal(expectedTxid))
344+
Expect(ordererStatus).To(BeNil())
345+
Expect(committed).To(Equal(false))
346+
347+
Expect(fakeProver.RequestRedeemCallCount()).To(Equal(1))
348+
Expect(fakeTxSubmitter.CreateTxEnvelopeCallCount()).To(Equal(1))
349+
Expect(fakeTxSubmitter.SubmitCallCount()).To(Equal(1))
350+
})
351+
})
352+
})
353+
259354
Describe("ListTokens", func() {
260355
var (
261356
expectedTokens []*token.TokenOutput

token/client/mock/prover.go

Lines changed: 87 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

token/client/prover.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,25 @@ func (prover *ProverPeer) RequestTransfer(
123123
return prover.SendCommand(context.Background(), sc)
124124
}
125125

126+
// RequestRedeem allows the redemption of the tokens in the input tokenIDs
127+
// It queries the ledger to read detail for each token id.
128+
// It creates a token transaction with an output for redeemed tokens and
129+
// possibly another output to transfer the remaining tokens, if any, to the same user
130+
func (prover *ProverPeer) RequestRedeem(tokenIDs [][]byte, quantity uint64, signingIdentity tk.SigningIdentity) ([]byte, error) {
131+
rr := &token.RedeemRequest{
132+
QuantityToRedeem: quantity,
133+
TokenIds: tokenIDs,
134+
}
135+
payload := &token.Command_RedeemRequest{RedeemRequest: rr}
136+
137+
sc, err := prover.CreateSignedCommand(payload, signingIdentity)
138+
if err != nil {
139+
return nil, err
140+
}
141+
142+
return prover.SendCommand(context.Background(), sc)
143+
}
144+
126145
// ListTokens allows the client to submit a list request to a prover peer service;
127146
// it returns a list of TokenOutput and an error message in the case the request fails
128147
func (prover *ProverPeer) ListTokens(signingIdentity tk.SigningIdentity) ([]*token.TokenOutput, error) {
@@ -241,6 +260,8 @@ func commandFromPayload(payload interface{}) (*token.Command, error) {
241260
switch t := payload.(type) {
242261
case *token.Command_ImportRequest:
243262
return &token.Command{Payload: t}, nil
263+
case *token.Command_RedeemRequest:
264+
return &token.Command{Payload: t}, nil
244265
case *token.Command_TransferRequest:
245266
return &token.Command{Payload: t}, nil
246267
case *token.Command_ListRequest:

0 commit comments

Comments
 (0)