OSDN Git Service

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