OSDN Git Service

Hulk did something
[bytom/vapor.git] / asset / asset.go
diff --git a/asset/asset.go b/asset/asset.go
new file mode 100644 (file)
index 0000000..cf3fef5
--- /dev/null
@@ -0,0 +1,421 @@
+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
+}