-
Notifications
You must be signed in to change notification settings - Fork 0
/
transactor.go
357 lines (310 loc) · 10.6 KB
/
transactor.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package plain
import (
"bytes"
"fmt"
"strconv"
"strings"
"github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric/protos/ledger/queryresult"
"github.com/hyperledger/fabric/protos/token"
"github.com/hyperledger/fabric/token/ledger"
"github.com/pkg/errors"
)
// A Transactor that can transfer tokens.
type Transactor struct {
PublicCredential []byte
Ledger ledger.LedgerReader
}
// RequestTransfer creates a TokenTransaction of type transfer request
//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
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, "", 0, errors.New(fmt.Sprintf("error splitting input composite key: '%s'", err))
}
if namespace != tokenOutput {
return nil, "", 0, errors.New(fmt.Sprintf("namespace not '%s': '%s'", tokenOutput, namespace))
}
if len(components) != 2 {
return nil, "", 0, errors.New(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, "", 0, errors.New(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, "", 0, err
}
if inBytes == nil {
return nil, "", 0, errors.New(fmt.Sprintf("input '%s' does not exist", inKey))
}
input := &token.PlainOutput{}
err = proto.Unmarshal(inBytes, input)
if err != nil {
return nil, "", 0, errors.New(fmt.Sprintf("error unmarshaling input bytes: '%s'", err))
}
// check the owner of the token
if !bytes.Equal(t.PublicCredential, input.Owner) {
return nil, "", 0, errors.New(fmt.Sprintf("the requestor does not own inputs"))
}
// check the token type - only one type allowed per transfer
if tokenType == "" {
tokenType = input.Type
} else if tokenType != input.Type {
return nil, "", 0, errors.New(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)})
// sum up the quantity
quantitySum += input.Quantity
}
return inputs, tokenType, quantitySum, nil
}
// ListTokens creates a TokenTransaction that lists the unspent tokens owned by owner.
func (t *Transactor) ListTokens() (*token.UnspentTokens, error) {
iterator, err := t.Ledger.GetStateRangeScanIterator(tokenNameSpace, "", "")
if err != nil {
return nil, err
}
tokens := make([]*token.TokenOutput, 0)
prefix, err := createPrefix(tokenOutput)
if err != nil {
return nil, err
}
for {
next, err := iterator.Next()
switch {
case err != nil:
return nil, err
case next == nil:
// nil response from iterator indicates end of query results
return &token.UnspentTokens{Tokens: tokens}, nil
default:
result, ok := next.(*queryresult.KV)
if !ok {
return nil, errors.New("failed to retrieve unspent tokens: casting error")
}
if strings.HasPrefix(result.Key, prefix) {
output := &token.PlainOutput{}
err = proto.Unmarshal(result.Value, output)
if err != nil {
return nil, errors.New("failed to retrieve unspent tokens: casting error")
}
if string(output.Owner) == string(t.PublicCredential) {
spent, err := t.isSpent(result.Key)
if err != nil {
return nil, err
}
if !spent {
tokens = append(tokens,
&token.TokenOutput{
Type: output.Type,
Quantity: output.Quantity,
Id: getCompositeKeyBytes(result.Key),
})
}
}
}
}
}
}
func (t *Transactor) RequestApprove(request *token.ApproveRequest) (*token.TokenTransaction, error) {
if len(request.GetTokenIds()) == 0 {
return nil, errors.New("no token ids in ApproveAllowanceRequest")
}
if len(request.AllowanceShares) == 0 {
return nil, errors.New("no recipient shares in ApproveAllowanceRequest")
}
var delegatedOutputs []*token.PlainDelegatedOutput
inputs, tokenType, sumQuantity, err := t.getInputsFromTokenIds(request.GetTokenIds())
if err != nil {
return nil, err
}
// prepare approve tx
delegatedQuantity := uint64(0)
for _, share := range request.GetAllowanceShares() {
if len(share.Recipient) == 0 {
return nil, errors.Errorf("the recipient in approve must be specified")
}
if share.Quantity <= 0 {
return nil, errors.Errorf("the quantity to approve [%d] must be greater than 0", share.GetQuantity())
}
delegatedOutputs = append(delegatedOutputs, &token.PlainDelegatedOutput{
Owner: []byte(request.Credential),
Delegatees: [][]byte{share.Recipient},
Type: tokenType,
Quantity: share.Quantity,
})
delegatedQuantity = delegatedQuantity + share.Quantity
}
if sumQuantity < delegatedQuantity {
return nil, errors.Errorf("insufficient funds: %v < %v", sumQuantity, delegatedQuantity)
}
var output *token.PlainOutput
if sumQuantity != delegatedQuantity {
output = &token.PlainOutput{
Owner: request.Credential,
Type: tokenType,
Quantity: sumQuantity - delegatedQuantity,
}
}
transaction := &token.TokenTransaction{
Action: &token.TokenTransaction_PlainAction{
PlainAction: &token.PlainTokenAction{
Data: &token.PlainTokenAction_PlainApprove{
PlainApprove: &token.PlainApprove{
Inputs: inputs,
DelegatedOutputs: delegatedOutputs,
Output: output,
},
},
},
},
}
return transaction, nil
}
func (t *Transactor) RequestTransferFrom(request *token.TransferRequest) (*token.TokenTransaction, error) {
panic("implement me!")
}
// RequestExpectation allows indirect transfer based on the expectation.
// It creates a token transaction based on the outputs as specified in the expectation.
func (t *Transactor) RequestExpectation(request *token.ExpectationRequest) (*token.TokenTransaction, error) {
panic("not implemented yet")
}
// Done releases any resources held by this transactor
func (t *Transactor) Done() {
if t.Ledger != nil {
t.Ledger.Done()
}
}
// isSpent checks whether an output token with identifier outputID has been spent.
func (t *Transactor) isSpent(outputID string) (bool, error) {
key, err := createInputKey(outputID)
if err != nil {
return false, err
}
result, err := t.Ledger.GetState(tokenNameSpace, key)
if err != nil {
return false, err
}
if result == nil {
return false, nil
}
return true, nil
}
// Create a ledger key for an individual input in a token transaction, as a function of
// the outputID
func createInputKey(outputID string) (string, error) {
att := strings.Split(outputID, string(minUnicodeRuneValue))
return createCompositeKey(tokenInput, att[1:])
}
// Create a prefix as a function of the string passed as argument
func createPrefix(keyword string) (string, error) {
return createCompositeKey(keyword, nil)
}
// GenerateKeyForTest is here only for testing purposes, to be removed later.
func GenerateKeyForTest(txID string, index int) (string, error) {
return createOutputKey(txID, index)
}
func splitCompositeKey(compositeKey string) (string, []string, error) {
componentIndex := 1
components := []string{}
for i := 1; i < len(compositeKey); i++ {
if compositeKey[i] == minUnicodeRuneValue {
components = append(components, compositeKey[componentIndex:i])
componentIndex = i + 1
}
}
if len(components) < 2 {
return "", nil, errors.New("invalid composite key - no components found")
}
return components[0], components[1:], nil
}