OSDN Git Service

dd38f1d9f584109116eb4a1b18cc37303e1fc3fa
[bytom/bytom.git] / blockchain / asset / asset.go
1 package asset
2
3 import (
4         "context"
5         "encoding/json"
6         "fmt"
7         "sync"
8
9         "github.com/golang/groupcache/lru"
10         "github.com/golang/groupcache/singleflight"
11         dbm "github.com/tendermint/tmlibs/db"
12         "golang.org/x/crypto/sha3"
13
14         "github.com/bytom/blockchain/signers"
15         "github.com/bytom/crypto/ed25519"
16         "github.com/bytom/crypto/ed25519/chainkd"
17         "github.com/bytom/errors"
18         "github.com/bytom/protocol"
19         "github.com/bytom/protocol/bc"
20         "github.com/bytom/protocol/vm/vmutil"
21 )
22
23 const maxAssetCache = 1000
24
25 var (
26         ErrDuplicateAlias = errors.New("duplicate asset alias")
27         ErrBadIdentifier  = errors.New("either ID or alias must be specified, and not both")
28 )
29
30 func NewRegistry(db dbm.DB, chain *protocol.Chain) *Registry {
31         return &Registry{
32                 db:               db,
33                 chain:            chain,
34                 initialBlockHash: chain.InitialBlockHash,
35                 cache:            lru.New(maxAssetCache),
36                 aliasCache:       lru.New(maxAssetCache),
37         }
38 }
39
40 // Registry tracks and stores all known assets on a blockchain.
41 type Registry struct {
42         db               dbm.DB
43         chain            *protocol.Chain
44         initialBlockHash bc.Hash
45
46         idGroup    singleflight.Group
47         aliasGroup singleflight.Group
48
49         cacheMu    sync.Mutex
50         cache      *lru.Cache
51         aliasCache *lru.Cache
52 }
53
54 type Asset struct {
55         AssetID          bc.AssetID
56         Alias            *string
57         VMVersion        uint64
58         IssuanceProgram  []byte
59         InitialBlockHash bc.Hash
60         *signers.Signer
61         Tags              map[string]interface{}
62         RawDefinitionByte []byte
63         DefinitionMap     map[string]interface{}
64         BlockHeight       uint64
65 }
66
67 func (asset *Asset) Definition() (map[string]interface{}, error) {
68         if asset.DefinitionMap == nil && len(asset.RawDefinitionByte) > 0 {
69                 err := json.Unmarshal(asset.RawDefinitionByte, &asset.DefinitionMap)
70                 if err != nil {
71                         return nil, errors.Wrap(err)
72                 }
73         }
74         return asset.DefinitionMap, nil
75 }
76
77 func (asset *Asset) RawDefinition() []byte {
78         return asset.RawDefinitionByte
79 }
80
81 func (asset *Asset) SetDefinition(def map[string]interface{}) error {
82         rawdef, err := serializeAssetDef(def)
83         if err != nil {
84                 return err
85         }
86         asset.DefinitionMap = def
87         asset.RawDefinitionByte = rawdef
88         return nil
89 }
90
91 // Define defines a new Asset.
92 func (reg *Registry) Define(ctx context.Context, xpubs []chainkd.XPub, quorum int, definition map[string]interface{}, alias string, tags map[string]interface{}, clientToken string) (*Asset, error) {
93         assetSigner, err := signers.Create(ctx, reg.db, "asset", xpubs, quorum, clientToken)
94         if err != nil {
95                 return nil, err
96         }
97
98         rawDefinition, err := serializeAssetDef(definition)
99         if err != nil {
100                 return nil, errors.Wrap(err, "serializing asset definition")
101         }
102
103         path := signers.Path(assetSigner, signers.AssetKeySpace)
104         derivedXPubs := chainkd.DeriveXPubs(assetSigner.XPubs, path)
105         derivedPKs := chainkd.XPubKeys(derivedXPubs)
106         issuanceProgram, vmver, err := multisigIssuanceProgram(derivedPKs, assetSigner.Quorum)
107         if err != nil {
108                 return nil, err
109         }
110
111         defhash := bc.NewHash(sha3.Sum256(rawDefinition))
112         asset := &Asset{
113                 DefinitionMap:     definition,
114                 RawDefinitionByte: rawDefinition,
115                 VMVersion:         vmver,
116                 IssuanceProgram:   issuanceProgram,
117                 InitialBlockHash:  reg.initialBlockHash,
118                 AssetID:           bc.ComputeAssetID(issuanceProgram, &reg.initialBlockHash, vmver, &defhash),
119                 Signer:            assetSigner,
120                 Tags:              tags,
121         }
122         if alias != "" {
123                 asset.Alias = &alias
124         }
125
126         assetID := []byte(asset.AssetID.String())
127         ass, err := json.Marshal(asset)
128         if err != nil {
129                 return nil, errors.Wrap(err, "failed marshal asset")
130         }
131         if len(ass) > 0 {
132                 reg.db.Set(assetID, json.RawMessage(ass))
133         }
134
135         return asset, nil
136 }
137
138 // UpdateTags modifies the tags of the specified asset. The asset may be
139 // identified either by id or alias, but not both.
140
141 func (reg *Registry) UpdateTags(ctx context.Context, id, alias *string, tags map[string]interface{}) error {
142         if (id == nil) == (alias == nil) {
143                 return errors.Wrap(ErrBadIdentifier)
144         }
145
146         // Fetch the existing asset
147
148         var (
149                 asset *Asset
150                 err   error
151         )
152
153         if id != nil {
154                 var aid bc.AssetID
155                 err = aid.UnmarshalText([]byte(*id))
156                 if err != nil {
157                         return errors.Wrap(err, "deserialize asset ID")
158                 }
159
160                 asset, err = reg.findByID(ctx, aid)
161                 if err != nil {
162                         return errors.Wrap(err, "find asset by ID")
163                 }
164         } else {
165                 return nil
166                 asset, err = reg.FindByAlias(ctx, *alias)
167                 if err != nil {
168                         return errors.Wrap(err, "find asset by alias")
169                 }
170         }
171
172         // Revise tags in-memory
173
174         asset.Tags = tags
175
176         reg.cacheMu.Lock()
177         reg.cache.Add(asset.AssetID, asset)
178         reg.cacheMu.Unlock()
179
180         return nil
181
182 }
183
184 // findByID retrieves an Asset record along with its signer, given an assetID.
185 func (reg *Registry) findByID(ctx context.Context, id bc.AssetID) (*Asset, error) {
186         reg.cacheMu.Lock()
187         cached, ok := reg.cache.Get(id)
188         reg.cacheMu.Unlock()
189         if ok {
190                 return cached.(*Asset), nil
191         }
192
193         bytes := reg.db.Get([]byte(id.String()))
194         if bytes == nil {
195                 return nil, errors.New("no exit this asset")
196         }
197         var asset Asset
198
199         if err := json.Unmarshal(bytes, &asset); err != nil {
200                 return nil, fmt.Errorf("err:%s,asset signer id:%s", err, id.String())
201         }
202
203         reg.cacheMu.Lock()
204         reg.cache.Add(id, &asset)
205         reg.cacheMu.Unlock()
206         return &asset, nil
207 }
208
209 // FindByAlias retrieves an Asset record along with its signer,
210 // given an asset alias.
211
212 func (reg *Registry) FindByAlias(ctx context.Context, alias string) (*Asset, error) {
213         reg.cacheMu.Lock()
214         cachedID, ok := reg.aliasCache.Get(alias)
215         reg.cacheMu.Unlock()
216         if ok {
217                 return reg.findByID(ctx, cachedID.(bc.AssetID))
218         }
219
220         untypedAsset, err := reg.aliasGroup.Do(alias, func() (interface{}, error) {
221                 return nil, nil
222         })
223
224         if err != nil {
225                 return nil, err
226         }
227
228         a := untypedAsset.(*Asset)
229         reg.cacheMu.Lock()
230         reg.aliasCache.Add(alias, a.AssetID)
231         reg.cache.Add(a.AssetID, a)
232         reg.cacheMu.Unlock()
233         return a, nil
234
235 }
236
237 func (reg *Registry) QueryAll(ctx context.Context) (interface{}, error) {
238         ret := make([]interface{}, 0)
239
240         assetIter := reg.db.Iterator()
241         defer assetIter.Release()
242
243         for assetIter.Next() {
244                 value := string(assetIter.Value())
245                 ret = append(ret, value)
246         }
247
248         return ret, nil
249 }
250
251 // serializeAssetDef produces a canonical byte representation of an asset
252 // definition. Currently, this is implemented using pretty-printed JSON.
253 // As is the standard for Go's map[string] serialization, object keys will
254 // appear in lexicographic order. Although this is mostly meant for machine
255 // consumption, the JSON is pretty-printed for easy reading.
256 // The empty asset def is an empty byte slice.
257 func serializeAssetDef(def map[string]interface{}) ([]byte, error) {
258         if def == nil {
259                 return []byte{}, nil
260         }
261         return json.MarshalIndent(def, "", "  ")
262 }
263
264 func multisigIssuanceProgram(pubkeys []ed25519.PublicKey, nrequired int) (program []byte, vmversion uint64, err error) {
265         issuanceProg, err := vmutil.P2SPMultiSigProgram(pubkeys, nrequired)
266         if err != nil {
267                 return nil, 0, err
268         }
269         builder := vmutil.NewBuilder()
270         builder.AddRawBytes(issuanceProg)
271         prog, err := builder.Build()
272         return prog, 1, err
273 }