OSDN Git Service

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