OSDN Git Service

add one id generation which account and asset use by signer
[bytom/bytom.git] / blockchain / asset / asset.go
1 package asset
2
3 import (
4         "context"
5         //"database/sql"
6         "encoding/json"
7 //      "fmt"
8         "sync"
9
10         "golang.org/x/crypto/sha3"
11
12         "github.com/golang/groupcache/lru"
13         "github.com/golang/groupcache/singleflight"
14 //      "github.com/lib/pq"
15         dbm "github.com/tendermint/tmlibs/db"
16
17 //      "github.com/bytom/blockchain/pin"
18         "github.com/bytom/blockchain/signers"
19         "github.com/bytom/crypto/ed25519"
20         "github.com/bytom/crypto/ed25519/chainkd"
21 //      "chain/database/pg"
22         "github.com/bytom/errors"
23         "github.com/bytom/protocol"
24         "github.com/bytom/protocol/bc"
25         "github.com/bytom/protocol/vm/vmutil"
26 )
27
28 const maxAssetCache = 1000
29
30 var (
31         ErrDuplicateAlias = errors.New("duplicate asset alias")
32         ErrBadIdentifier  = errors.New("either ID or alias must be specified, and not both")
33 )
34
35 func NewRegistry(db dbm.DB, chain *protocol.Chain/*, pinStore *pin.Store*/) *Registry {
36         return &Registry{
37                 db:               db,
38                 chain:            chain,
39                 initialBlockHash: chain.InitialBlockHash,
40 //              pinStore:         pinStore,
41                 cache:            lru.New(maxAssetCache),
42                 aliasCache:       lru.New(maxAssetCache),
43         }
44 }
45
46 // Registry tracks and stores all known assets on a blockchain.
47 type Registry struct {
48         db               dbm.DB
49         chain            *protocol.Chain
50         indexer          Saver
51         initialBlockHash bc.Hash
52 //      pinStore         *pin.Store
53
54         idGroup    singleflight.Group
55         aliasGroup singleflight.Group
56
57         cacheMu    sync.Mutex
58         cache      *lru.Cache
59         aliasCache *lru.Cache
60 }
61
62 func (reg *Registry) IndexAssets(indexer Saver) {
63         reg.indexer = indexer
64 }
65
66 type Asset struct {
67         AssetID          bc.AssetID
68         Alias            *string
69         VMVersion        uint64
70         IssuanceProgram  []byte
71         InitialBlockHash bc.Hash
72         Signer           *signers.Signer
73         Tags             map[string]interface{}
74         rawDefinition    []byte
75         definition       map[string]interface{}
76         sortID           string
77 }
78
79 func (asset *Asset) Definition() (map[string]interface{}, error) {
80         if asset.definition == nil && len(asset.rawDefinition) > 0 {
81                 err := json.Unmarshal(asset.rawDefinition, &asset.definition)
82                 if err != nil {
83                         return nil, errors.Wrap(err)
84                 }
85         }
86         return asset.definition, nil
87 }
88
89 func (asset *Asset) RawDefinition() []byte {
90         return asset.rawDefinition
91 }
92
93 func (asset *Asset) SetDefinition(def map[string]interface{}) error {
94         rawdef, err := serializeAssetDef(def)
95         if err != nil {
96                 return err
97         }
98         asset.definition = def
99         asset.rawDefinition = rawdef
100         return nil
101 }
102
103 // Define defines a new Asset.
104 func (reg *Registry) Define(ctx context.Context, xpubs []chainkd.XPub, quorum int, definition map[string]interface{}, alias string, tags map[string]interface{}, clientToken string) (*Asset, error) {
105         assetSigner, err := signers.Create(ctx, reg.db, "asset", xpubs, quorum, clientToken)
106         if err != nil {
107                 return nil, err
108         }
109
110         rawDefinition, err := serializeAssetDef(definition)
111         if err != nil {
112                 return nil, errors.Wrap(err, "serializing asset definition")
113         }
114
115         path := signers.Path(assetSigner, signers.AssetKeySpace)
116         derivedXPubs := chainkd.DeriveXPubs(assetSigner.XPubs, path)
117         derivedPKs := chainkd.XPubKeys(derivedXPubs)
118         issuanceProgram, vmver, err := multisigIssuanceProgram(derivedPKs, assetSigner.Quorum)
119         if err != nil {
120                 return nil, err
121         }
122
123         defhash := bc.NewHash(sha3.Sum256(rawDefinition))
124         asset := &Asset{
125                 definition:       definition,
126                 rawDefinition:    rawDefinition,
127                 VMVersion:        vmver,
128                 IssuanceProgram:  issuanceProgram,
129                 InitialBlockHash: reg.initialBlockHash,
130                 AssetID:          bc.ComputeAssetID(issuanceProgram, &reg.initialBlockHash, vmver, &defhash),
131                 Signer:           assetSigner,
132                 Tags:             tags,
133         }
134         if alias != "" {
135                 asset.Alias = &alias
136         }
137
138         asset_id := []byte(assetSigner.ID)
139         ass, err := json.Marshal(asset)
140         if err != nil {
141                 return nil, errors.Wrap(err, "failed marshal asset")
142         }
143         if len(ass) > 0 {
144                 reg.db.Set(asset_id,json.RawMessage(ass))
145         }
146
147 /*      asset, err = reg.insertAsset(ctx, asset, clientToken)
148         if err != nil {
149                 return nil, errors.Wrap(err, "inserting asset")
150         }
151
152         err = insertAssetTags(ctx, reg.db, asset.AssetID, tags)
153         if err != nil {
154                 return nil, errors.Wrap(err, "inserting asset tags")
155         }
156 */
157         err = reg.indexAnnotatedAsset(ctx, asset)
158         if err != nil {
159                 return nil, errors.Wrap(err, "indexing annotated asset")
160         }
161
162         return asset, nil
163 }
164
165 // UpdateTags modifies the tags of the specified asset. The asset may be
166 // identified either by id or alias, but not both.
167
168 func (reg *Registry) UpdateTags(ctx context.Context, id, alias *string, tags map[string]interface{}) error {
169         if (id == nil) == (alias == nil) {
170                 return errors.Wrap(ErrBadIdentifier)
171         }
172
173         // Fetch the existing asset
174
175         var (
176                 asset *Asset
177                 err   error
178         )
179
180         if id != nil {
181                 var aid bc.AssetID
182                 err = aid.UnmarshalText([]byte(*id))
183                 if err != nil {
184                         return errors.Wrap(err, "deserialize asset ID")
185                 }
186
187                 asset, err = reg.findByID(ctx, aid)
188                 if err != nil {
189                         return errors.Wrap(err, "find asset by ID")
190                 }
191         } else {
192                 return nil
193                 asset, err = reg.FindByAlias(ctx, *alias)
194                 if err != nil {
195                         return errors.Wrap(err, "find asset by alias")
196                 }
197         }
198
199         // Revise tags in-memory
200
201         asset.Tags = tags
202
203         // Perform persistent updates
204 /*
205         err = insertAssetTags(ctx, reg.db, asset.AssetID, asset.Tags)
206         if err != nil {
207                 return errors.Wrap(err, "inserting asset tags")
208         }
209
210         err = reg.indexAnnotatedAsset(ctx, asset)
211         if err != nil {
212                 return errors.Wrap(err, "update asset index")
213         }
214 */
215         // Revise cache
216
217         reg.cacheMu.Lock()
218         reg.cache.Add(asset.AssetID, asset)
219         reg.cacheMu.Unlock()
220
221         return nil
222
223 }
224
225 // findByID retrieves an Asset record along with its signer, given an assetID.
226 func (reg *Registry) findByID(ctx context.Context, id bc.AssetID) (*Asset, error) {
227         reg.cacheMu.Lock()
228         cached, ok := reg.cache.Get(id)
229         reg.cacheMu.Unlock()
230         if ok {
231                 return cached.(*Asset), nil
232         }
233
234         untypedAsset, err := reg.idGroup.Do(id.String(), func() (interface{}, error) {
235 //              return assetQuery(ctx, reg.db, "assets.id=$1", id)
236                 return nil,nil
237 })
238
239         if err != nil {
240                 return nil, err
241         }
242
243         asset := untypedAsset.(*Asset)
244         reg.cacheMu.Lock()
245         reg.cache.Add(id, asset)
246         reg.cacheMu.Unlock()
247         return asset, nil
248 }
249
250 // FindByAlias retrieves an Asset record along with its signer,
251 // given an asset alias.
252
253 func (reg *Registry) FindByAlias(ctx context.Context, alias string) (*Asset, error) {
254         reg.cacheMu.Lock()
255         cachedID, ok := reg.aliasCache.Get(alias)
256         reg.cacheMu.Unlock()
257         if ok {
258                 return reg.findByID(ctx, cachedID.(bc.AssetID))
259         }
260
261         untypedAsset, err := reg.aliasGroup.Do(alias, func() (interface{}, error) {
262 //              asset, err := assetQuery(ctx, reg.db, "assets.alias=$1", alias)
263 //              return asset, err
264                 return nil,nil
265         })
266
267         if err != nil {
268                 return nil, err
269         }
270
271         a := untypedAsset.(*Asset)
272         reg.cacheMu.Lock()
273         reg.aliasCache.Add(alias, a.AssetID)
274         reg.cache.Add(a.AssetID, a)
275         reg.cacheMu.Unlock()
276         return a, nil
277
278 }
279
280 // insertAsset adds the asset to the database. If the asset has a client token,
281 // and there already exists an asset with that client token, insertAsset will
282 // lookup and return the existing asset instead.
283 /*
284 func (reg *Registry) insertAsset(ctx context.Context, asset *Asset, clientToken string) (*Asset, error) {
285         const q = `
286                 INSERT INTO assets
287                         (id, alias, signer_id, initial_block_hash, vm_version, issuance_program, definition, client_token)
288                 VALUES($1::bytea, $2, $3, $4, $5, $6, $7, $8)
289                 ON CONFLICT (client_token) DO NOTHING
290                 RETURNING sort_id
291   `
292         var signerID sql.NullString
293         if asset.Signer != nil {
294                 signerID = sql.NullString{Valid: true, String: asset.Signer.ID}
295         }
296
297         nullToken := sql.NullString{
298                 String: clientToken,
299                 Valid:  clientToken != "",
300         }
301
302         err := reg.db.QueryRowContext(
303                 ctx, q,
304                 asset.AssetID, asset.Alias, signerID,
305                 asset.InitialBlockHash, asset.VMVersion, asset.IssuanceProgram,
306                 asset.rawDefinition, nullToken,
307         ).Scan(&asset.sortID)
308
309         if pg.IsUniqueViolation(err) {
310                 return nil, errors.WithDetail(ErrDuplicateAlias, "an asset with the provided alias already exists")
311         } else if err == sql.ErrNoRows && clientToken != "" {
312                 // There is already an asset with the provided client
313                 // token. We should return the existing asset.
314                 asset, err = assetByClientToken(ctx, reg.db, clientToken)
315                 if err != nil {
316                         return nil, errors.Wrap(err, "retrieving existing asset")
317                 }
318         } else if err != nil {
319                 return nil, errors.Wrap(err)
320         }
321         return asset, nil
322 }
323
324 // insertAssetTags inserts a set of tags for the given assetID.
325 // It must take place inside a database transaction.
326
327 func insertAssetTags(ctx context.Context, db pg.DB, assetID bc.AssetID, tags map[string]interface{}) error {
328         tagsParam, err := mapToNullString(tags)
329         if err != nil {
330                 return errors.Wrap(err)
331         }
332
333         const q = `
334                 INSERT INTO asset_tags (asset_id, tags) VALUES ($1, $2)
335                 ON CONFLICT (asset_id) DO UPDATE SET tags = $2
336         `
337         _, err = db.ExecContext(ctx, q, assetID, tagsParam)
338         if err != nil {
339                 return errors.Wrap(err)
340         }
341
342         return nil
343 }
344
345 // assetByClientToken loads an asset from the database using its client token.
346
347 func assetByClientToken(ctx context.Context, db pg.DB, clientToken string) (*Asset, error) {
348         return assetQuery(ctx, db, "assets.client_token=$1", clientToken)
349 }
350
351 func assetQuery(ctx context.Context, db pg.DB, pred string, args ...interface{}) (*Asset, error) {
352         const baseQ = `
353                 SELECT assets.id, assets.alias, assets.vm_version, assets.issuance_program, assets.definition,
354                         assets.initial_block_hash, assets.sort_id,
355                         signers.id, COALESCE(signers.type, ''), COALESCE(signers.xpubs, '{}'),
356                         COALESCE(signers.quorum, 0), COALESCE(signers.key_index, 0),
357                         asset_tags.tags
358                 FROM assets
359                 LEFT JOIN signers ON signers.id=assets.signer_id
360                 LEFT JOIN asset_tags ON asset_tags.asset_id=assets.id
361                 WHERE %s
362                 LIMIT 1
363         `
364
365         var (
366                 a          Asset
367                 alias      sql.NullString
368                 signerID   sql.NullString
369                 signerType string
370                 quorum     int
371                 keyIndex   uint64
372                 xpubs      [][]byte
373                 tags       []byte
374         ) 
375         err := db.QueryRowContext(ctx, fmt.Sprintf(baseQ, pred), args...).Scan(
376                 &a.AssetID,
377                 &a.Alias,
378                 &a.VMVersion,
379                 &a.IssuanceProgram,
380                 &a.rawDefinition,
381                 &a.InitialBlockHash,
382                 &a.sortID,
383                 &signerID,
384                 &signerType,
385                 (*pq.ByteaArray)(&xpubs),
386                 &quorum,
387                 &keyIndex,
388                 &tags,
389         )
390         if err == sql.ErrNoRows {
391                 return nil, pg.ErrUserInputNotFound
392         } else if err != nil {
393                 return nil, err
394         }
395
396         if signerID.Valid {
397                 a.Signer, err = signers.New(signerID.String, signerType, xpubs, quorum, keyIndex)
398                 if err != nil {
399                         return nil, err
400                 }
401         }
402
403         if alias.Valid {
404                 a.Alias = &alias.String
405         }
406
407         if len(tags) > 0 {
408                 err := json.Unmarshal(tags, &a.Tags)
409                 if err != nil {
410                         return nil, errors.Wrap(err)
411                 }
412         }
413         if len(a.rawDefinition) > 0 {
414                 // ignore errors; non-JSON asset definitions can still end up
415                 // on the blockchain from non-Chain Core clients.
416                 _ = json.Unmarshal(a.rawDefinition, &a.definition)
417         }
418
419         return &a, nil
420
421 }
422 */
423 // serializeAssetDef produces a canonical byte representation of an asset
424 // definition. Currently, this is implemented using pretty-printed JSON.
425 // As is the standard for Go's map[string] serialization, object keys will
426 // appear in lexicographic order. Although this is mostly meant for machine
427 // consumption, the JSON is pretty-printed for easy reading.
428 // The empty asset def is an empty byte slice.
429 func serializeAssetDef(def map[string]interface{}) ([]byte, error) {
430         if def == nil {
431                 return []byte{}, nil
432         }
433         return json.MarshalIndent(def, "", "  ")
434 }
435
436 func multisigIssuanceProgram(pubkeys []ed25519.PublicKey, nrequired int) (program []byte, vmversion uint64, err error) {
437         issuanceProg, err := vmutil.P2SPMultiSigProgram(pubkeys, nrequired)
438         if err != nil {
439                 return nil, 0, err
440         }
441         builder := vmutil.NewBuilder()
442         builder.AddRawBytes(issuanceProg)
443         prog, err := builder.Build()
444         return prog, 1, err
445 }
446 /*
447 func mapToNullString(in map[string]interface{}) (*sql.NullString, error) {
448         var mapJSON []byte
449         if len(in) != 0 {
450                 var err error
451                 mapJSON, err = json.Marshal(in)
452                 if err != nil {
453                         return nil, errors.Wrap(err)
454                 }
455         }
456         return &sql.NullString{String: string(mapJSON), Valid: len(mapJSON) > 0}, nil
457 }
458 */