OSDN Git Service

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