--- /dev/null
+package asset
+
+import (
+ "context"
+ "encoding/json"
+ "strings"
+ "sync"
+
+ "github.com/golang/groupcache/lru"
+ "golang.org/x/crypto/sha3"
+
+ "github.com/vapor/blockchain/signers"
+ "github.com/vapor/common"
+ "github.com/vapor/consensus"
+ "github.com/vapor/crypto/ed25519"
+ "github.com/vapor/crypto/ed25519/chainkd"
+ dbm "github.com/vapor/database/leveldb"
+ chainjson "github.com/vapor/encoding/json"
+ "github.com/vapor/errors"
+ "github.com/vapor/protocol"
+ "github.com/vapor/protocol/bc"
+ "github.com/vapor/protocol/vm/vmutil"
+)
+
+// DefaultNativeAsset native BTM asset
+var DefaultNativeAsset *Asset
+
+const (
+ maxAssetCache = 1000
+)
+
+var (
+ assetIndexKey = []byte("AssetIndex")
+ assetPrefix = []byte("Asset:")
+ aliasPrefix = []byte("AssetAlias:")
+ extAssetPrefix = []byte("EXA:")
+)
+
+func initNativeAsset() {
+ signer := &signers.Signer{Type: "internal"}
+ alias := consensus.BTMAlias
+
+ definitionBytes, _ := serializeAssetDef(consensus.BTMDefinitionMap)
+ DefaultNativeAsset = &Asset{
+ Signer: signer,
+ AssetID: *consensus.BTMAssetID,
+ Alias: &alias,
+ VMVersion: 1,
+ DefinitionMap: consensus.BTMDefinitionMap,
+ RawDefinitionByte: definitionBytes,
+ }
+}
+
+// AliasKey store asset alias prefix
+func aliasKey(name string) []byte {
+ return append(aliasPrefix, []byte(name)...)
+}
+
+// Key store asset prefix
+func Key(id *bc.AssetID) []byte {
+ return append(assetPrefix, id.Bytes()...)
+}
+
+// ExtAssetKey return store external assets key
+func ExtAssetKey(id *bc.AssetID) []byte {
+ return append(extAssetPrefix, id.Bytes()...)
+}
+
+// pre-define errors for supporting bytom errorFormatter
+var (
+ ErrDuplicateAlias = errors.New("duplicate asset alias")
+ ErrDuplicateAsset = errors.New("duplicate asset id")
+ ErrSerializing = errors.New("serializing asset definition")
+ ErrMarshalAsset = errors.New("failed marshal asset")
+ ErrFindAsset = errors.New("fail to find asset")
+ ErrInternalAsset = errors.New("btm has been defined as the internal asset")
+ ErrNullAlias = errors.New("null asset alias")
+)
+
+//NewRegistry create new registry
+func NewRegistry(db dbm.DB, chain *protocol.Chain) *Registry {
+ initNativeAsset()
+ return &Registry{
+ db: db,
+ chain: chain,
+ cache: lru.New(maxAssetCache),
+ aliasCache: lru.New(maxAssetCache),
+ }
+}
+
+// Registry tracks and stores all known assets on a blockchain.
+type Registry struct {
+ db dbm.DB
+ chain *protocol.Chain
+
+ cacheMu sync.Mutex
+ cache *lru.Cache
+ aliasCache *lru.Cache
+
+ assetIndexMu sync.Mutex
+ assetMu sync.Mutex
+}
+
+//Asset describe asset on bytom chain
+type Asset struct {
+ *signers.Signer
+ AssetID bc.AssetID `json:"id"`
+ Alias *string `json:"alias"`
+ VMVersion uint64 `json:"vm_version"`
+ IssuanceProgram chainjson.HexBytes `json:"issue_program"`
+ RawDefinitionByte chainjson.HexBytes `json:"raw_definition_byte"`
+ DefinitionMap map[string]interface{} `json:"definition"`
+}
+
+func (reg *Registry) getNextAssetIndex() uint64 {
+ reg.assetIndexMu.Lock()
+ defer reg.assetIndexMu.Unlock()
+
+ nextIndex := uint64(1)
+ if rawIndex := reg.db.Get(assetIndexKey); rawIndex != nil {
+ nextIndex = common.BytesToUnit64(rawIndex) + 1
+ }
+
+ reg.db.Set(assetIndexKey, common.Unit64ToBytes(nextIndex))
+ return nextIndex
+}
+
+// Define defines a new Asset.
+func (reg *Registry) Define(xpubs []chainkd.XPub, quorum int, definition map[string]interface{}, limitHeight int64, alias string, issuanceProgram chainjson.HexBytes) (*Asset, error) {
+ var err error
+ var assetSigner *signers.Signer
+
+ alias = strings.ToUpper(strings.TrimSpace(alias))
+ if alias == "" {
+ return nil, errors.Wrap(ErrNullAlias)
+ }
+
+ if alias == consensus.BTMAlias {
+ return nil, ErrInternalAsset
+ }
+
+ rawDefinition, err := serializeAssetDef(definition)
+ if err != nil {
+ return nil, ErrSerializing
+ }
+
+ vmver := uint64(1)
+ if len(issuanceProgram) == 0 {
+ if len(xpubs) == 0 {
+ return nil, errors.Wrap(signers.ErrNoXPubs)
+ }
+
+ nextAssetIndex := reg.getNextAssetIndex()
+ assetSigner, err = signers.Create("asset", xpubs, quorum, nextAssetIndex, signers.BIP0032)
+ if err != nil {
+ return nil, err
+ }
+
+ path := signers.GetBip0032Path(assetSigner, signers.AssetKeySpace)
+ derivedXPubs := chainkd.DeriveXPubs(assetSigner.XPubs, path)
+ derivedPKs := chainkd.XPubKeys(derivedXPubs)
+ issuanceProgram, vmver, err = multisigIssuanceProgram(derivedPKs, assetSigner.Quorum, limitHeight)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ defHash := bc.NewHash(sha3.Sum256(rawDefinition))
+ a := &Asset{
+ DefinitionMap: definition,
+ RawDefinitionByte: rawDefinition,
+ VMVersion: vmver,
+ IssuanceProgram: issuanceProgram,
+ AssetID: bc.ComputeAssetID(issuanceProgram, vmver, &defHash),
+ Signer: assetSigner,
+ Alias: &alias,
+ }
+ return a, reg.SaveAsset(a, alias)
+}
+
+// SaveAsset store asset
+func (reg *Registry) SaveAsset(a *Asset, alias string) error {
+ reg.assetMu.Lock()
+ defer reg.assetMu.Unlock()
+
+ aliasKey := aliasKey(alias)
+ if existed := reg.db.Get(aliasKey); existed != nil {
+ return ErrDuplicateAlias
+ }
+
+ assetKey := Key(&a.AssetID)
+ if existAsset := reg.db.Get(assetKey); existAsset != nil {
+ return ErrDuplicateAsset
+ }
+
+ rawAsset, err := json.Marshal(a)
+ if err != nil {
+ return ErrMarshalAsset
+ }
+
+ storeBatch := reg.db.NewBatch()
+ storeBatch.Set(aliasKey, []byte(a.AssetID.String()))
+ storeBatch.Set(assetKey, rawAsset)
+ storeBatch.Write()
+ return nil
+}
+
+// FindByID retrieves an Asset record along with its signer, given an assetID.
+func (reg *Registry) FindByID(ctx context.Context, id *bc.AssetID) (*Asset, error) {
+ reg.cacheMu.Lock()
+ cached, ok := reg.cache.Get(id.String())
+ reg.cacheMu.Unlock()
+ if ok {
+ return cached.(*Asset), nil
+ }
+
+ bytes := reg.db.Get(Key(id))
+ if bytes == nil {
+ return nil, ErrFindAsset
+ }
+
+ asset := &Asset{}
+ if err := json.Unmarshal(bytes, asset); err != nil {
+ return nil, err
+ }
+
+ reg.cacheMu.Lock()
+ reg.cache.Add(id.String(), asset)
+ reg.cacheMu.Unlock()
+ return asset, nil
+}
+
+// FindByAlias retrieves an Asset record along with its signer,
+// given an asset alias.
+func (reg *Registry) FindByAlias(alias string) (*Asset, error) {
+ reg.cacheMu.Lock()
+ cachedID, ok := reg.aliasCache.Get(alias)
+ reg.cacheMu.Unlock()
+ if ok {
+ return reg.FindByID(nil, cachedID.(*bc.AssetID))
+ }
+
+ rawID := reg.db.Get(aliasKey(alias))
+ if rawID == nil {
+ return nil, errors.Wrapf(ErrFindAsset, "no such asset, alias: %s", alias)
+ }
+
+ assetID := &bc.AssetID{}
+ if err := assetID.UnmarshalText(rawID); err != nil {
+ return nil, err
+ }
+
+ reg.cacheMu.Lock()
+ reg.aliasCache.Add(alias, assetID)
+ reg.cacheMu.Unlock()
+ return reg.FindByID(nil, assetID)
+}
+
+//GetAliasByID return asset alias string by AssetID string
+func (reg *Registry) GetAliasByID(id string) string {
+ //btm
+ if id == consensus.BTMAssetID.String() {
+ return consensus.BTMAlias
+ }
+
+ assetID := &bc.AssetID{}
+ if err := assetID.UnmarshalText([]byte(id)); err != nil {
+ return ""
+ }
+
+ asset, err := reg.FindByID(nil, assetID)
+ if err != nil {
+ return ""
+ }
+
+ return *asset.Alias
+}
+
+// GetAsset get asset by assetID
+func (reg *Registry) GetAsset(id string) (*Asset, error) {
+ var assetID bc.AssetID
+ if err := assetID.UnmarshalText([]byte(id)); err != nil {
+ return nil, err
+ }
+
+ if assetID.String() == DefaultNativeAsset.AssetID.String() {
+ return DefaultNativeAsset, nil
+ }
+
+ asset := &Asset{}
+ if interAsset := reg.db.Get(Key(&assetID)); interAsset != nil {
+ if err := json.Unmarshal(interAsset, asset); err != nil {
+ return nil, err
+ }
+ return asset, nil
+ }
+
+ if extAsset := reg.db.Get(ExtAssetKey(&assetID)); extAsset != nil {
+ definitionMap := make(map[string]interface{})
+ if err := json.Unmarshal(extAsset, &definitionMap); err != nil {
+ return nil, err
+ }
+ alias := assetID.String()
+ asset.Alias = &alias
+ asset.AssetID = assetID
+ asset.DefinitionMap = definitionMap
+ return asset, nil
+ }
+
+ return nil, errors.WithDetailf(ErrFindAsset, "no such asset, assetID: %s", id)
+}
+
+// ListAssets returns the accounts in the db
+func (reg *Registry) ListAssets(id string) ([]*Asset, error) {
+ assets := []*Asset{DefaultNativeAsset}
+
+ assetIDStr := strings.TrimSpace(id)
+ if assetIDStr == DefaultNativeAsset.AssetID.String() {
+ return assets, nil
+ }
+
+ if assetIDStr != "" {
+ assetID := &bc.AssetID{}
+ if err := assetID.UnmarshalText([]byte(assetIDStr)); err != nil {
+ return nil, err
+ }
+
+ asset := &Asset{}
+ interAsset := reg.db.Get(Key(assetID))
+ if interAsset != nil {
+ if err := json.Unmarshal(interAsset, asset); err != nil {
+ return nil, err
+ }
+ return []*Asset{asset}, nil
+ }
+
+ return []*Asset{}, nil
+ }
+
+ assetIter := reg.db.IteratorPrefix(assetPrefix)
+ defer assetIter.Release()
+
+ for assetIter.Next() {
+ asset := &Asset{}
+ if err := json.Unmarshal(assetIter.Value(), asset); err != nil {
+ return nil, err
+ }
+ assets = append(assets, asset)
+ }
+
+ return assets, nil
+}
+
+// serializeAssetDef produces a canonical byte representation of an asset
+// definition. Currently, this is implemented using pretty-printed JSON.
+// As is the standard for Go's map[string] serialization, object keys will
+// appear in lexicographic order. Although this is mostly meant for machine
+// consumption, the JSON is pretty-printed for easy reading.
+func serializeAssetDef(def map[string]interface{}) ([]byte, error) {
+ if def == nil {
+ def = make(map[string]interface{}, 0)
+ }
+ return json.MarshalIndent(def, "", " ")
+}
+
+func multisigIssuanceProgram(pubkeys []ed25519.PublicKey, nrequired int, blockHeight int64) (program []byte, vmversion uint64, err error) {
+ issuanceProg, err := vmutil.P2SPMultiSigProgramWithHeight(pubkeys, nrequired, blockHeight)
+ if err != nil {
+ return nil, 0, err
+ }
+ builder := vmutil.NewBuilder()
+ builder.AddRawBytes(issuanceProg)
+ prog, err := builder.Build()
+ return prog, 1, err
+}
+
+//UpdateAssetAlias updates asset alias
+func (reg *Registry) UpdateAssetAlias(id, newAlias string) error {
+ oldAlias := reg.GetAliasByID(id)
+ newAlias = strings.ToUpper(strings.TrimSpace(newAlias))
+
+ if oldAlias == consensus.BTMAlias || newAlias == consensus.BTMAlias {
+ return ErrInternalAsset
+ }
+
+ if oldAlias == "" || newAlias == "" {
+ return ErrNullAlias
+ }
+
+ reg.assetMu.Lock()
+ defer reg.assetMu.Unlock()
+
+ if _, err := reg.FindByAlias(newAlias); err == nil {
+ return ErrDuplicateAlias
+ }
+
+ findAsset, err := reg.FindByAlias(oldAlias)
+ if err != nil {
+ return err
+ }
+
+ storeBatch := reg.db.NewBatch()
+ findAsset.Alias = &newAlias
+ assetID := &findAsset.AssetID
+ rawAsset, err := json.Marshal(findAsset)
+ if err != nil {
+ return err
+ }
+
+ storeBatch.Set(Key(assetID), rawAsset)
+ storeBatch.Set(aliasKey(newAlias), []byte(assetID.String()))
+ storeBatch.Delete(aliasKey(oldAlias))
+ storeBatch.Write()
+
+ reg.cacheMu.Lock()
+ reg.aliasCache.Add(newAlias, assetID)
+ reg.aliasCache.Remove(oldAlias)
+ reg.cacheMu.Unlock()
+
+ return nil
+}