package asset import ( "context" "encoding/json" "strings" "sync" "github.com/golang/groupcache/lru" dbm "github.com/tendermint/tmlibs/db" "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" 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{}, 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) 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) (program []byte, vmversion uint64, err error) { issuanceProg, err := vmutil.P2SPMultiSigProgram(pubkeys, nrequired) 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 }