OSDN Git Service

update (#423)
[bytom/vapor.git] / asset / asset.go
1 package asset
2
3 import (
4         "context"
5         "encoding/json"
6         "strings"
7         "sync"
8
9         "github.com/golang/groupcache/lru"
10
11         "github.com/vapor/consensus"
12         dbm "github.com/vapor/database/leveldb"
13         chainjson "github.com/vapor/encoding/json"
14         "github.com/vapor/errors"
15         "github.com/vapor/protocol"
16         "github.com/vapor/protocol/bc"
17 )
18
19 // DefaultNativeAsset native BTM asset
20 var DefaultNativeAsset *Asset
21
22 const (
23         maxAssetCache = 1000
24 )
25
26 var (
27         assetIndexKey  = []byte("AssetIndex")
28         assetPrefix    = []byte("Asset:")
29         aliasPrefix    = []byte("AssetAlias:")
30         extAssetPrefix = []byte("EXA:")
31 )
32
33 func initNativeAsset() {
34         alias := consensus.BTMAlias
35
36         definitionBytes, _ := serializeAssetDef(consensus.BTMDefinitionMap)
37         DefaultNativeAsset = &Asset{
38                 AssetID:           *consensus.BTMAssetID,
39                 Alias:             &alias,
40                 VMVersion:         1,
41                 DefinitionMap:     consensus.BTMDefinitionMap,
42                 RawDefinitionByte: definitionBytes,
43         }
44 }
45
46 // AliasKey store asset alias prefix
47 func aliasKey(name string) []byte {
48         return append(aliasPrefix, []byte(name)...)
49 }
50
51 // Key store asset prefix
52 func Key(id *bc.AssetID) []byte {
53         return append(assetPrefix, id.Bytes()...)
54 }
55
56 // ExtAssetKey return store external assets key
57 func ExtAssetKey(id *bc.AssetID) []byte {
58         return append(extAssetPrefix, id.Bytes()...)
59 }
60
61 // pre-define errors for supporting bytom errorFormatter
62 var (
63         ErrDuplicateAlias = errors.New("duplicate asset alias")
64         ErrDuplicateAsset = errors.New("duplicate asset id")
65         ErrSerializing    = errors.New("serializing asset definition")
66         ErrMarshalAsset   = errors.New("failed marshal asset")
67         ErrFindAsset      = errors.New("fail to find asset")
68         ErrInternalAsset  = errors.New("btm has been defined as the internal asset")
69         ErrNullAlias      = errors.New("null asset alias")
70 )
71
72 //NewRegistry create new registry
73 func NewRegistry(db dbm.DB, chain *protocol.Chain) *Registry {
74         initNativeAsset()
75         return &Registry{
76                 db:         db,
77                 chain:      chain,
78                 cache:      lru.New(maxAssetCache),
79                 aliasCache: lru.New(maxAssetCache),
80         }
81 }
82
83 // Registry tracks and stores all known assets on a blockchain.
84 type Registry struct {
85         db    dbm.DB
86         chain *protocol.Chain
87
88         cacheMu    sync.Mutex
89         cache      *lru.Cache
90         aliasCache *lru.Cache
91
92         assetIndexMu sync.Mutex
93         assetMu      sync.Mutex
94 }
95
96 //Asset describe asset on bytom chain
97 type Asset struct {
98         AssetID           bc.AssetID             `json:"id"`
99         Alias             *string                `json:"alias"`
100         VMVersion         uint64                 `json:"vm_version"`
101         RawDefinitionByte chainjson.HexBytes     `json:"raw_definition_byte"`
102         DefinitionMap     map[string]interface{} `json:"definition"`
103 }
104
105 // SaveAsset store asset
106 func (reg *Registry) SaveAsset(a *Asset, alias string) error {
107         reg.assetMu.Lock()
108         defer reg.assetMu.Unlock()
109
110         aliasKey := aliasKey(alias)
111         if existed := reg.db.Get(aliasKey); existed != nil {
112                 return ErrDuplicateAlias
113         }
114
115         assetKey := Key(&a.AssetID)
116         if existAsset := reg.db.Get(assetKey); existAsset != nil {
117                 return ErrDuplicateAsset
118         }
119
120         rawAsset, err := json.Marshal(a)
121         if err != nil {
122                 return ErrMarshalAsset
123         }
124
125         storeBatch := reg.db.NewBatch()
126         storeBatch.Set(aliasKey, []byte(a.AssetID.String()))
127         storeBatch.Set(assetKey, rawAsset)
128         storeBatch.Write()
129         return nil
130 }
131
132 // FindByID retrieves an Asset record along with its signer, given an assetID.
133 func (reg *Registry) FindByID(ctx context.Context, id *bc.AssetID) (*Asset, error) {
134         reg.cacheMu.Lock()
135         cached, ok := reg.cache.Get(id.String())
136         reg.cacheMu.Unlock()
137         if ok {
138                 return cached.(*Asset), nil
139         }
140
141         bytes := reg.db.Get(Key(id))
142         if bytes == nil {
143                 return nil, ErrFindAsset
144         }
145
146         asset := &Asset{}
147         if err := json.Unmarshal(bytes, asset); err != nil {
148                 return nil, err
149         }
150
151         reg.cacheMu.Lock()
152         reg.cache.Add(id.String(), asset)
153         reg.cacheMu.Unlock()
154         return asset, nil
155 }
156
157 // FindByAlias retrieves an Asset record along with its signer,
158 // given an asset alias.
159 func (reg *Registry) FindByAlias(alias string) (*Asset, error) {
160         reg.cacheMu.Lock()
161         cachedID, ok := reg.aliasCache.Get(alias)
162         reg.cacheMu.Unlock()
163         if ok {
164                 return reg.FindByID(nil, cachedID.(*bc.AssetID))
165         }
166
167         rawID := reg.db.Get(aliasKey(alias))
168         if rawID == nil {
169                 return nil, errors.Wrapf(ErrFindAsset, "no such asset, alias: %s", alias)
170         }
171
172         assetID := &bc.AssetID{}
173         if err := assetID.UnmarshalText(rawID); err != nil {
174                 return nil, err
175         }
176
177         reg.cacheMu.Lock()
178         reg.aliasCache.Add(alias, assetID)
179         reg.cacheMu.Unlock()
180         return reg.FindByID(nil, assetID)
181 }
182
183 //GetAliasByID return asset alias string by AssetID string
184 func (reg *Registry) GetAliasByID(id string) string {
185         //btm
186         if id == consensus.BTMAssetID.String() {
187                 return consensus.BTMAlias
188         }
189
190         assetID := &bc.AssetID{}
191         if err := assetID.UnmarshalText([]byte(id)); err != nil {
192                 return ""
193         }
194
195         asset, err := reg.FindByID(nil, assetID)
196         if err != nil {
197                 return ""
198         }
199
200         return *asset.Alias
201 }
202
203 // GetAsset get asset by assetID
204 func (reg *Registry) GetAsset(id string) (*Asset, error) {
205         var assetID bc.AssetID
206         if err := assetID.UnmarshalText([]byte(id)); err != nil {
207                 return nil, err
208         }
209
210         if assetID.String() == DefaultNativeAsset.AssetID.String() {
211                 return DefaultNativeAsset, nil
212         }
213
214         asset := &Asset{}
215         if interAsset := reg.db.Get(Key(&assetID)); interAsset != nil {
216                 if err := json.Unmarshal(interAsset, asset); err != nil {
217                         return nil, err
218                 }
219                 return asset, nil
220         }
221
222         if extAsset := reg.db.Get(ExtAssetKey(&assetID)); extAsset != nil {
223                 definitionMap := make(map[string]interface{})
224                 if err := json.Unmarshal(extAsset, &definitionMap); err != nil {
225                         return nil, err
226                 }
227                 alias := strings.ToUpper(assetID.String())
228                 asset.Alias = &alias
229                 asset.AssetID = assetID
230                 asset.DefinitionMap = definitionMap
231                 return asset, nil
232         }
233
234         return nil, errors.WithDetailf(ErrFindAsset, "no such asset, assetID: %s", id)
235 }
236
237 // ListAssets returns the accounts in the db
238 func (reg *Registry) ListAssets(id string) ([]*Asset, error) {
239         assets := []*Asset{DefaultNativeAsset}
240
241         assetIDStr := strings.TrimSpace(id)
242         if assetIDStr == DefaultNativeAsset.AssetID.String() {
243                 return assets, nil
244         }
245
246         if assetIDStr != "" {
247                 assetID := &bc.AssetID{}
248                 if err := assetID.UnmarshalText([]byte(assetIDStr)); err != nil {
249                         return nil, err
250                 }
251
252                 asset := &Asset{}
253                 interAsset := reg.db.Get(Key(assetID))
254                 if interAsset != nil {
255                         if err := json.Unmarshal(interAsset, asset); err != nil {
256                                 return nil, err
257                         }
258                         return []*Asset{asset}, nil
259                 }
260
261                 return []*Asset{}, nil
262         }
263
264         assetIter := reg.db.IteratorPrefix(assetPrefix)
265         defer assetIter.Release()
266
267         for assetIter.Next() {
268                 asset := &Asset{}
269                 if err := json.Unmarshal(assetIter.Value(), asset); err != nil {
270                         return nil, err
271                 }
272                 assets = append(assets, asset)
273         }
274
275         return assets, nil
276 }
277
278 // serializeAssetDef produces a canonical byte representation of an asset
279 // definition. Currently, this is implemented using pretty-printed JSON.
280 // As is the standard for Go's map[string] serialization, object keys will
281 // appear in lexicographic order. Although this is mostly meant for machine
282 // consumption, the JSON is pretty-printed for easy reading.
283 func serializeAssetDef(def map[string]interface{}) ([]byte, error) {
284         if def == nil {
285                 def = make(map[string]interface{}, 0)
286         }
287         return json.MarshalIndent(def, "", "  ")
288 }
289
290 //UpdateAssetAlias updates asset alias
291 func (reg *Registry) UpdateAssetAlias(id, newAlias string) error {
292         oldAlias := reg.GetAliasByID(id)
293         newAlias = strings.ToUpper(strings.TrimSpace(newAlias))
294
295         if oldAlias == consensus.BTMAlias || newAlias == consensus.BTMAlias {
296                 return ErrInternalAsset
297         }
298
299         if oldAlias == "" || newAlias == "" {
300                 return ErrNullAlias
301         }
302
303         reg.assetMu.Lock()
304         defer reg.assetMu.Unlock()
305
306         if _, err := reg.FindByAlias(newAlias); err == nil {
307                 return ErrDuplicateAlias
308         }
309
310         findAsset, err := reg.FindByAlias(oldAlias)
311         if err != nil {
312                 return err
313         }
314
315         storeBatch := reg.db.NewBatch()
316         findAsset.Alias = &newAlias
317         assetID := &findAsset.AssetID
318         rawAsset, err := json.Marshal(findAsset)
319         if err != nil {
320                 return err
321         }
322
323         storeBatch.Set(Key(assetID), rawAsset)
324         storeBatch.Set(aliasKey(newAlias), []byte(assetID.String()))
325         storeBatch.Delete(aliasKey(oldAlias))
326         storeBatch.Write()
327
328         reg.cacheMu.Lock()
329         reg.aliasCache.Add(newAlias, assetID)
330         reg.aliasCache.Remove(oldAlias)
331         reg.cacheMu.Unlock()
332
333         return nil
334 }