OSDN Git Service

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