OSDN Git Service

chmod -x for source files (#557)
[bytom/bytom.git] / asset / asset.go
1 package asset
2
3 import (
4         "context"
5         "encoding/binary"
6         "encoding/json"
7         "strings"
8         "sync"
9
10         "github.com/golang/groupcache/lru"
11         dbm "github.com/tendermint/tmlibs/db"
12         "golang.org/x/crypto/sha3"
13
14         "github.com/bytom/blockchain/signers"
15         "github.com/bytom/consensus"
16         "github.com/bytom/crypto/ed25519"
17         "github.com/bytom/crypto/ed25519/chainkd"
18         chainjson "github.com/bytom/encoding/json"
19         "github.com/bytom/errors"
20         "github.com/bytom/protocol"
21         "github.com/bytom/protocol/bc"
22         "github.com/bytom/protocol/vm/vmutil"
23 )
24
25 var DefaultNativeAsset *Asset
26
27 const (
28         maxAssetCache = 1000
29         assetPrefix   = "ASS:"
30         //AliasPrefix is asset alias prefix
31         AliasPrefix = "ALS:"
32         //ExternalAssetPrefix is external definition assets prefix
33         ExternalAssetPrefix = "EXA"
34         indexPrefix         = "ASSIDX:"
35 )
36
37 func initNativeAsset() {
38         signer := &signers.Signer{Type: "internal"}
39         alias := consensus.BTMAlias
40
41         definitionBytes, _ := serializeAssetDef(consensus.BTMDefinitionMap)
42         DefaultNativeAsset = &Asset{
43                 Signer:            signer,
44                 AssetID:           *consensus.BTMAssetID,
45                 Alias:             &alias,
46                 VMVersion:         1,
47                 DefinitionMap:     consensus.BTMDefinitionMap,
48                 RawDefinitionByte: definitionBytes,
49         }
50 }
51
52 func AliasKey(name string) []byte {
53         return []byte(AliasPrefix + name)
54 }
55
56 //Key asset store prefix
57 func Key(id *bc.AssetID) []byte {
58         name := id.String()
59         return []byte(assetPrefix + name)
60 }
61
62 func indexKey(xpub chainkd.XPub) []byte {
63         return []byte(indexPrefix + xpub.String())
64 }
65
66 //CalcExtAssetKey return store external assets key
67 func CalcExtAssetKey(id *bc.AssetID) []byte {
68         name := id.String()
69         return []byte(ExternalAssetPrefix + name)
70 }
71
72 // pre-define errors for supporting bytom errorFormatter
73 var (
74         ErrDuplicateAlias = errors.New("duplicate asset alias")
75         ErrDuplicateAsset = errors.New("duplicate asset id")
76         ErrSerializing    = errors.New("serializing asset definition")
77         ErrMarshalAsset   = errors.New("failed marshal asset")
78         ErrFindAsset      = errors.New("fail to find asset")
79         ErrInternalAsset  = errors.New("btm has been defined as the internal asset")
80         ErrNullAlias      = errors.New("null asset alias")
81 )
82
83 //NewRegistry create new registry
84 func NewRegistry(db dbm.DB, chain *protocol.Chain) *Registry {
85         initNativeAsset()
86         return &Registry{
87                 db:         db,
88                 chain:      chain,
89                 cache:      lru.New(maxAssetCache),
90                 aliasCache: lru.New(maxAssetCache),
91         }
92 }
93
94 // Registry tracks and stores all known assets on a blockchain.
95 type Registry struct {
96         db    dbm.DB
97         chain *protocol.Chain
98
99         cacheMu    sync.Mutex
100         cache      *lru.Cache
101         aliasCache *lru.Cache
102
103         assetIndexMu sync.Mutex
104 }
105
106 //Asset describe asset on bytom chain
107 type Asset struct {
108         *signers.Signer
109         AssetID           bc.AssetID             `json:"id"`
110         Alias             *string                `json:"alias"`
111         VMVersion         uint64                 `json:"vm_version"`
112         IssuanceProgram   chainjson.HexBytes     `json:"issue_program"`
113         Tags              map[string]interface{} `json:"tags"`
114         RawDefinitionByte chainjson.HexBytes     `json:"raw_definition_byte"`
115         DefinitionMap     map[string]interface{} `json:"definition"`
116 }
117
118 func (reg *Registry) getNextAssetIndex(xpubs []chainkd.XPub) (*uint64, error) {
119         reg.assetIndexMu.Lock()
120         defer reg.assetIndexMu.Unlock()
121
122         var nextIndex uint64 = 1
123
124         if rawIndex := reg.db.Get(indexKey(xpubs[0])); rawIndex != nil {
125                 nextIndex = binary.LittleEndian.Uint64(rawIndex) + 1
126         }
127
128         buf := make([]byte, 8)
129         binary.LittleEndian.PutUint64(buf, nextIndex)
130         reg.db.Set(indexKey(xpubs[0]), buf)
131
132         return &nextIndex, nil
133 }
134
135 // Define defines a new Asset.
136 func (reg *Registry) Define(xpubs []chainkd.XPub, quorum int, definition map[string]interface{}, alias string, tags map[string]interface{}) (*Asset, error) {
137         if len(xpubs) == 0 {
138                 return nil, errors.Wrap(signers.ErrNoXPubs)
139         }
140
141         normalizedAlias := strings.ToUpper(strings.TrimSpace(alias))
142         if normalizedAlias == consensus.BTMAlias {
143                 return nil, ErrInternalAsset
144         }
145
146         if existed := reg.db.Get(AliasKey(normalizedAlias)); existed != nil {
147                 return nil, ErrDuplicateAlias
148         }
149
150         nextAssetIndex, err := reg.getNextAssetIndex(xpubs)
151         if err != nil {
152                 return nil, errors.Wrap(err, "get asset index error")
153         }
154
155         assetSigner, err := signers.Create("asset", xpubs, quorum, *nextAssetIndex)
156         if err != nil {
157                 return nil, err
158         }
159
160         rawDefinition, err := serializeAssetDef(definition)
161         if err != nil {
162                 return nil, ErrSerializing
163         }
164
165         path := signers.Path(assetSigner, signers.AssetKeySpace)
166         derivedXPubs := chainkd.DeriveXPubs(assetSigner.XPubs, path)
167         derivedPKs := chainkd.XPubKeys(derivedXPubs)
168         issuanceProgram, vmver, err := multisigIssuanceProgram(derivedPKs, assetSigner.Quorum)
169         if err != nil {
170                 return nil, err
171         }
172
173         defHash := bc.NewHash(sha3.Sum256(rawDefinition))
174         asset := &Asset{
175                 DefinitionMap:     definition,
176                 RawDefinitionByte: rawDefinition,
177                 VMVersion:         vmver,
178                 IssuanceProgram:   issuanceProgram,
179                 AssetID:           bc.ComputeAssetID(issuanceProgram, vmver, &defHash),
180                 Signer:            assetSigner,
181                 Tags:              tags,
182         }
183
184         if existAsset := reg.db.Get(Key(&asset.AssetID)); existAsset != nil {
185                 return nil, ErrDuplicateAsset
186         }
187
188         if alias != "" {
189                 asset.Alias = &normalizedAlias
190         }
191
192         ass, err := json.Marshal(asset)
193         if err != nil {
194                 return nil, ErrMarshalAsset
195         }
196
197         storeBatch := reg.db.NewBatch()
198         storeBatch.Set(AliasKey(normalizedAlias), []byte(asset.AssetID.String()))
199         storeBatch.Set(Key(&asset.AssetID), ass)
200         storeBatch.Write()
201
202         return asset, nil
203 }
204
205 // UpdateTags modifies the tags of the specified asset. The asset may be
206 // identified either by id or alias, but not both.
207 func (reg *Registry) UpdateTags(ctx context.Context, assetInfo string, tags map[string]interface{}) (err error) {
208         asset := &Asset{}
209         if asset, err = reg.FindByAlias(ctx, assetInfo); err != nil {
210                 assetID := &bc.AssetID{}
211                 if err := assetID.UnmarshalText([]byte(assetInfo)); err != nil {
212                         return err
213                 }
214                 if asset, err = reg.FindByID(ctx, assetID); err != nil {
215                         return err
216                 }
217         }
218
219         asset.Tags = tags
220         rawAsset, err := json.Marshal(asset)
221         if err != nil {
222                 return ErrMarshalAsset
223         }
224
225         reg.db.Set(Key(&asset.AssetID), rawAsset)
226         reg.cacheMu.Lock()
227         reg.cache.Add(asset.AssetID, asset)
228         reg.cacheMu.Unlock()
229         return nil
230 }
231
232 // findByID retrieves an Asset record along with its signer, given an assetID.
233 func (reg *Registry) FindByID(ctx context.Context, id *bc.AssetID) (*Asset, error) {
234         reg.cacheMu.Lock()
235         cached, ok := reg.cache.Get(id.String())
236         reg.cacheMu.Unlock()
237         if ok {
238                 return cached.(*Asset), nil
239         }
240
241         bytes := reg.db.Get(Key(id))
242         if bytes == nil {
243                 return nil, ErrFindAsset
244         }
245
246         asset := &Asset{}
247         if err := json.Unmarshal(bytes, asset); err != nil {
248                 return nil, err
249         }
250
251         reg.cacheMu.Lock()
252         reg.cache.Add(id.String(), asset)
253         reg.cacheMu.Unlock()
254         return asset, nil
255 }
256
257 //GetIDByAlias return AssetID string and nil by asset alias,if err ,return "" and err
258 func (reg *Registry) GetIDByAlias(alias string) (string, error) {
259         rawID := reg.db.Get(AliasKey(alias))
260         if rawID == nil {
261                 return "", ErrFindAsset
262         }
263         return string(rawID), nil
264 }
265
266 // FindByAlias retrieves an Asset record along with its signer,
267 // given an asset alias.
268 func (reg *Registry) FindByAlias(ctx context.Context, alias string) (*Asset, error) {
269         reg.cacheMu.Lock()
270         cachedID, ok := reg.aliasCache.Get(alias)
271         reg.cacheMu.Unlock()
272         if ok {
273                 return reg.FindByID(ctx, cachedID.(*bc.AssetID))
274         }
275
276         rawID := reg.db.Get(AliasKey(alias))
277         if rawID == nil {
278                 return nil, errors.Wrapf(ErrFindAsset, "no such asset, alias: %s", alias)
279         }
280
281         assetID := &bc.AssetID{}
282         if err := assetID.UnmarshalText(rawID); err != nil {
283                 return nil, err
284         }
285
286         reg.cacheMu.Lock()
287         reg.aliasCache.Add(alias, assetID)
288         reg.cacheMu.Unlock()
289         return reg.FindByID(ctx, assetID)
290 }
291
292 //GetAliasByID return asset alias string by AssetID string
293 func (reg *Registry) GetAliasByID(id string) string {
294         //btm
295         if id == consensus.BTMAssetID.String() {
296                 return consensus.BTMAlias
297         }
298
299         assetID := &bc.AssetID{}
300         if err := assetID.UnmarshalText([]byte(id)); err != nil {
301                 return ""
302         }
303
304         asset, err := reg.FindByID(nil, assetID)
305         if err != nil {
306                 return ""
307         }
308
309         return *asset.Alias
310 }
311
312 // ListAssets returns the accounts in the db
313 func (reg *Registry) ListAssets(id string) ([]*Asset, error) {
314         assets := []*Asset{DefaultNativeAsset}
315         assetIter := reg.db.IteratorPrefix([]byte(assetPrefix + id))
316         defer assetIter.Release()
317
318         for assetIter.Next() {
319                 asset := &Asset{}
320                 if err := json.Unmarshal(assetIter.Value(), asset); err != nil {
321                         return nil, err
322                 }
323                 assets = append(assets, asset)
324         }
325         return assets, nil
326 }
327
328 // serializeAssetDef produces a canonical byte representation of an asset
329 // definition. Currently, this is implemented using pretty-printed JSON.
330 // As is the standard for Go's map[string] serialization, object keys will
331 // appear in lexicographic order. Although this is mostly meant for machine
332 // consumption, the JSON is pretty-printed for easy reading.
333 // The empty asset def is an empty byte slice.
334 func serializeAssetDef(def map[string]interface{}) ([]byte, error) {
335         if def == nil {
336                 return []byte{}, nil
337         }
338         return json.MarshalIndent(def, "", "  ")
339 }
340
341 func multisigIssuanceProgram(pubkeys []ed25519.PublicKey, nrequired int) (program []byte, vmversion uint64, err error) {
342         issuanceProg, err := vmutil.P2SPMultiSigProgram(pubkeys, nrequired)
343         if err != nil {
344                 return nil, 0, err
345         }
346         builder := vmutil.NewBuilder()
347         builder.AddRawBytes(issuanceProg)
348         prog, err := builder.Build()
349         return prog, 1, err
350 }
351
352 //UpdateAssetAlias updates oldAlias to newAlias
353 func (reg *Registry) UpdateAssetAlias(oldAlias, newAlias string) error {
354         if oldAlias == consensus.BTMAlias || newAlias == consensus.BTMAlias {
355                 return ErrInternalAsset
356         }
357
358         if oldAlias == "" || newAlias == "" {
359                 return ErrNullAlias
360         }
361
362         if _, err := reg.GetIDByAlias(newAlias); err == nil {
363                 return ErrDuplicateAlias
364         }
365
366         findAsset, err := reg.FindByAlias(nil, oldAlias)
367         if err != nil {
368                 return err
369         }
370
371         storeBatch := reg.db.NewBatch()
372         findAsset.Alias = &newAlias
373         assetID := &findAsset.AssetID
374         rawAsset, err := json.Marshal(findAsset)
375         if err != nil {
376                 return err
377         }
378
379         storeBatch.Set(Key(assetID), rawAsset)
380         storeBatch.Set(AliasKey(newAlias), []byte(assetID.String()))
381         storeBatch.Delete(AliasKey(oldAlias))
382         storeBatch.Write()
383
384         reg.cacheMu.Lock()
385         reg.aliasCache.Add(newAlias, assetID)
386         reg.aliasCache.Remove(oldAlias)
387         reg.cacheMu.Unlock()
388
389         return nil
390 }