10 "github.com/golang/groupcache/lru"
11 dbm "github.com/tendermint/tmlibs/db"
12 "golang.org/x/crypto/sha3"
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"
25 // DefaultNativeAsset native BTM asset
26 var DefaultNativeAsset *Asset
31 //AliasPrefix is asset alias prefix
33 //ExternalAssetPrefix is external definition assets prefix
34 ExternalAssetPrefix = "EXA"
35 indexPrefix = "ASSIDX:"
38 func initNativeAsset() {
39 signer := &signers.Signer{Type: "internal"}
40 alias := consensus.BTMAlias
42 definitionBytes, _ := serializeAssetDef(consensus.BTMDefinitionMap)
43 DefaultNativeAsset = &Asset{
45 AssetID: *consensus.BTMAssetID,
48 DefinitionMap: consensus.BTMDefinitionMap,
49 RawDefinitionByte: definitionBytes,
53 // AliasKey store asset alias prefix
54 func AliasKey(name string) []byte {
55 return []byte(AliasPrefix + name)
58 //Key asset store prefix
59 func Key(id *bc.AssetID) []byte {
61 return []byte(assetPrefix + name)
64 func indexKey(xpub chainkd.XPub) []byte {
65 return []byte(indexPrefix + xpub.String())
68 //CalcExtAssetKey return store external assets key
69 func CalcExtAssetKey(id *bc.AssetID) []byte {
71 return []byte(ExternalAssetPrefix + name)
74 // pre-define errors for supporting bytom errorFormatter
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")
85 //NewRegistry create new registry
86 func NewRegistry(db dbm.DB, chain *protocol.Chain) *Registry {
91 cache: lru.New(maxAssetCache),
92 aliasCache: lru.New(maxAssetCache),
96 // Registry tracks and stores all known assets on a blockchain.
97 type Registry struct {
103 aliasCache *lru.Cache
105 assetIndexMu sync.Mutex
108 //Asset describe asset on bytom chain
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"`
119 func (reg *Registry) getNextAssetIndex(xpubs []chainkd.XPub) (*uint64, error) {
120 reg.assetIndexMu.Lock()
121 defer reg.assetIndexMu.Unlock()
123 var nextIndex uint64 = 1
125 if rawIndex := reg.db.Get(indexKey(xpubs[0])); rawIndex != nil {
126 nextIndex = binary.LittleEndian.Uint64(rawIndex) + 1
129 buf := make([]byte, 8)
130 binary.LittleEndian.PutUint64(buf, nextIndex)
131 reg.db.Set(indexKey(xpubs[0]), buf)
133 return &nextIndex, nil
136 // Define defines a new Asset.
137 func (reg *Registry) Define(xpubs []chainkd.XPub, quorum int, definition map[string]interface{}, alias string) (*Asset, error) {
139 return nil, errors.Wrap(signers.ErrNoXPubs)
142 normalizedAlias := strings.ToUpper(strings.TrimSpace(alias))
143 if normalizedAlias == "" {
144 return nil, errors.Wrap(ErrNullAlias)
147 if normalizedAlias == consensus.BTMAlias {
148 return nil, ErrInternalAsset
151 if existed := reg.db.Get(AliasKey(normalizedAlias)); existed != nil {
152 return nil, ErrDuplicateAlias
155 nextAssetIndex, err := reg.getNextAssetIndex(xpubs)
157 return nil, errors.Wrap(err, "get asset index error")
160 assetSigner, err := signers.Create("asset", xpubs, quorum, *nextAssetIndex)
165 rawDefinition, err := serializeAssetDef(definition)
167 return nil, ErrSerializing
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)
178 defHash := bc.NewHash(sha3.Sum256(rawDefinition))
180 DefinitionMap: definition,
181 RawDefinitionByte: rawDefinition,
183 IssuanceProgram: issuanceProgram,
184 AssetID: bc.ComputeAssetID(issuanceProgram, vmver, &defHash),
186 Alias: &normalizedAlias,
189 if existAsset := reg.db.Get(Key(&asset.AssetID)); existAsset != nil {
190 return nil, ErrDuplicateAsset
193 ass, err := json.Marshal(asset)
195 return nil, ErrMarshalAsset
198 storeBatch := reg.db.NewBatch()
199 storeBatch.Set(AliasKey(normalizedAlias), []byte(asset.AssetID.String()))
200 storeBatch.Set(Key(&asset.AssetID), ass)
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) {
209 cached, ok := reg.cache.Get(id.String())
212 return cached.(*Asset), nil
215 bytes := reg.db.Get(Key(id))
217 return nil, ErrFindAsset
221 if err := json.Unmarshal(bytes, asset); err != nil {
226 reg.cache.Add(id.String(), asset)
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))
235 return "", ErrFindAsset
237 return string(rawID), nil
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) {
244 cachedID, ok := reg.aliasCache.Get(alias)
247 return reg.FindByID(ctx, cachedID.(*bc.AssetID))
250 rawID := reg.db.Get(AliasKey(alias))
252 return nil, errors.Wrapf(ErrFindAsset, "no such asset, alias: %s", alias)
255 assetID := &bc.AssetID{}
256 if err := assetID.UnmarshalText(rawID); err != nil {
261 reg.aliasCache.Add(alias, assetID)
263 return reg.FindByID(ctx, assetID)
266 //GetAliasByID return asset alias string by AssetID string
267 func (reg *Registry) GetAliasByID(id string) string {
269 if id == consensus.BTMAssetID.String() {
270 return consensus.BTMAlias
273 assetID := &bc.AssetID{}
274 if err := assetID.UnmarshalText([]byte(id)); err != nil {
278 asset, err := reg.FindByID(nil, assetID)
286 // GetAsset get asset by assetID
287 func (reg *Registry) GetAsset(id string) (*Asset, error) {
290 if strings.Compare(id, DefaultNativeAsset.AssetID.String()) == 0 {
291 return DefaultNativeAsset, nil
294 if interAsset := reg.db.Get([]byte(assetPrefix + id)); interAsset != nil {
295 if err := json.Unmarshal(interAsset, asset); err != nil {
301 if extAsset := reg.db.Get([]byte(ExternalAssetPrefix + id)); extAsset != nil {
302 if err := json.Unmarshal(extAsset, asset); err != nil {
308 return nil, errors.WithDetailf(ErrFindAsset, "no such asset, assetID: %s", id)
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()
317 for assetIter.Next() {
319 if err := json.Unmarshal(assetIter.Value(), asset); err != nil {
322 assets = append(assets, asset)
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) {
335 def = make(map[string]interface{}, 0)
337 return json.MarshalIndent(def, "", " ")
340 func multisigIssuanceProgram(pubkeys []ed25519.PublicKey, nrequired int) (program []byte, vmversion uint64, err error) {
341 issuanceProg, err := vmutil.P2SPMultiSigProgram(pubkeys, nrequired)
345 builder := vmutil.NewBuilder()
346 builder.AddRawBytes(issuanceProg)
347 prog, err := builder.Build()
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))
356 if oldAlias == consensus.BTMAlias || newAlias == consensus.BTMAlias {
357 return ErrInternalAsset
360 if oldAlias == "" || normalizedAlias == "" {
364 if _, err := reg.GetIDByAlias(normalizedAlias); err == nil {
365 return ErrDuplicateAlias
368 findAsset, err := reg.FindByAlias(nil, oldAlias)
373 storeBatch := reg.db.NewBatch()
374 findAsset.Alias = &normalizedAlias
375 assetID := &findAsset.AssetID
376 rawAsset, err := json.Marshal(findAsset)
381 storeBatch.Set(Key(assetID), rawAsset)
382 storeBatch.Set(AliasKey(newAlias), []byte(assetID.String()))
383 storeBatch.Delete(AliasKey(oldAlias))
387 reg.aliasCache.Add(newAlias, assetID)
388 reg.aliasCache.Remove(oldAlias)