OSDN Git Service

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