github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/asset/asset.go (about)

     1  package asset
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"strings"
     7  	"sync"
     8  
     9  	"github.com/golang/groupcache/lru"
    10  	"golang.org/x/crypto/sha3"
    11  
    12  	"github.com/bytom/bytom/blockchain/signers"
    13  	"github.com/bytom/bytom/common"
    14  	"github.com/bytom/bytom/consensus"
    15  	"github.com/bytom/bytom/crypto/ed25519"
    16  	"github.com/bytom/bytom/crypto/ed25519/chainkd"
    17  	dbm "github.com/bytom/bytom/database/leveldb"
    18  	chainjson "github.com/bytom/bytom/encoding/json"
    19  	"github.com/bytom/bytom/errors"
    20  	"github.com/bytom/bytom/protocol"
    21  	"github.com/bytom/bytom/protocol/bc"
    22  	"github.com/bytom/bytom/protocol/vm/vmutil"
    23  )
    24  
    25  // DefaultNativeAsset native BTM asset
    26  var DefaultNativeAsset *Asset
    27  
    28  const (
    29  	maxAssetCache = 1000
    30  )
    31  
    32  var (
    33  	assetIndexKey  = []byte("AssetIndex")
    34  	assetPrefix    = []byte("Asset:")
    35  	aliasPrefix    = []byte("AssetAlias:")
    36  	extAssetPrefix = []byte("EXA:")
    37  )
    38  
    39  func initNativeAsset() {
    40  	signer := &signers.Signer{Type: "internal"}
    41  	alias := consensus.BTMAlias
    42  
    43  	definitionBytes, _ := serializeAssetDef(consensus.BTMDefinitionMap)
    44  	DefaultNativeAsset = &Asset{
    45  		Signer:            signer,
    46  		AssetID:           *consensus.BTMAssetID,
    47  		Alias:             &alias,
    48  		VMVersion:         1,
    49  		DefinitionMap:     consensus.BTMDefinitionMap,
    50  		RawDefinitionByte: definitionBytes,
    51  	}
    52  }
    53  
    54  // AliasKey store asset alias prefix
    55  func aliasKey(name string) []byte {
    56  	return append(aliasPrefix, []byte(name)...)
    57  }
    58  
    59  // Key store asset prefix
    60  func Key(id *bc.AssetID) []byte {
    61  	return append(assetPrefix, id.Bytes()...)
    62  }
    63  
    64  // ExtAssetKey return store external assets key
    65  func ExtAssetKey(id *bc.AssetID) []byte {
    66  	return append(extAssetPrefix, id.Bytes()...)
    67  }
    68  
    69  // pre-define errors for supporting bytom errorFormatter
    70  var (
    71  	ErrDuplicateAlias = errors.New("duplicate asset alias")
    72  	ErrDuplicateAsset = errors.New("duplicate asset id")
    73  	ErrSerializing    = errors.New("serializing asset definition")
    74  	ErrMarshalAsset   = errors.New("failed marshal asset")
    75  	ErrFindAsset      = errors.New("fail to find asset")
    76  	ErrInternalAsset  = errors.New("btm has been defined as the internal asset")
    77  	ErrNullAlias      = errors.New("null asset alias")
    78  )
    79  
    80  //NewRegistry create new registry
    81  func NewRegistry(db dbm.DB, chain *protocol.Chain) *Registry {
    82  	initNativeAsset()
    83  	return &Registry{
    84  		db:         db,
    85  		chain:      chain,
    86  		cache:      lru.New(maxAssetCache),
    87  		aliasCache: lru.New(maxAssetCache),
    88  	}
    89  }
    90  
    91  // Registry tracks and stores all known assets on a blockchain.
    92  type Registry struct {
    93  	db    dbm.DB
    94  	chain *protocol.Chain
    95  
    96  	cacheMu    sync.Mutex
    97  	cache      *lru.Cache
    98  	aliasCache *lru.Cache
    99  
   100  	assetIndexMu sync.Mutex
   101  	assetMu      sync.Mutex
   102  }
   103  
   104  //Asset describe asset on bytom chain
   105  type Asset struct {
   106  	*signers.Signer
   107  	AssetID           bc.AssetID             `json:"id"`
   108  	Alias             *string                `json:"alias"`
   109  	VMVersion         uint64                 `json:"vm_version"`
   110  	IssuanceProgram   chainjson.HexBytes     `json:"issue_program"`
   111  	RawDefinitionByte chainjson.HexBytes     `json:"raw_definition_byte"`
   112  	DefinitionMap     map[string]interface{} `json:"definition"`
   113  }
   114  
   115  func (reg *Registry) getNextAssetIndex() uint64 {
   116  	reg.assetIndexMu.Lock()
   117  	defer reg.assetIndexMu.Unlock()
   118  
   119  	nextIndex := uint64(1)
   120  	if rawIndex := reg.db.Get(assetIndexKey); rawIndex != nil {
   121  		nextIndex = common.BytesToUnit64(rawIndex) + 1
   122  	}
   123  
   124  	reg.db.Set(assetIndexKey, common.Unit64ToBytes(nextIndex))
   125  	return nextIndex
   126  }
   127  
   128  // Define defines a new Asset.
   129  func (reg *Registry) Define(xpubs []chainkd.XPub, quorum int, definition map[string]interface{}, limitHeight int64, alias string, issuanceProgram chainjson.HexBytes) (*Asset, error) {
   130  	var err error
   131  	var assetSigner *signers.Signer
   132  
   133  	alias = strings.ToUpper(strings.TrimSpace(alias))
   134  	if alias == "" {
   135  		return nil, errors.Wrap(ErrNullAlias)
   136  	}
   137  
   138  	if alias == consensus.BTMAlias {
   139  		return nil, ErrInternalAsset
   140  	}
   141  
   142  	rawDefinition, err := serializeAssetDef(definition)
   143  	if err != nil {
   144  		return nil, ErrSerializing
   145  	}
   146  
   147  	vmver := uint64(1)
   148  	if len(issuanceProgram) == 0 {
   149  		if len(xpubs) == 0 {
   150  			return nil, errors.Wrap(signers.ErrNoXPubs)
   151  		}
   152  
   153  		nextAssetIndex := reg.getNextAssetIndex()
   154  		assetSigner, err = signers.Create("asset", xpubs, quorum, nextAssetIndex, signers.BIP0032)
   155  		if err != nil {
   156  			return nil, err
   157  		}
   158  
   159  		path := signers.GetBip0032Path(assetSigner, signers.AssetKeySpace)
   160  		derivedXPubs := chainkd.DeriveXPubs(assetSigner.XPubs, path)
   161  		derivedPKs := chainkd.XPubKeys(derivedXPubs)
   162  		issuanceProgram, vmver, err = multisigIssuanceProgram(derivedPKs, assetSigner.Quorum, limitHeight)
   163  		if err != nil {
   164  			return nil, err
   165  		}
   166  	}
   167  
   168  	defHash := bc.NewHash(sha3.Sum256(rawDefinition))
   169  	a := &Asset{
   170  		DefinitionMap:     definition,
   171  		RawDefinitionByte: rawDefinition,
   172  		VMVersion:         vmver,
   173  		IssuanceProgram:   issuanceProgram,
   174  		AssetID:           bc.ComputeAssetID(issuanceProgram, vmver, &defHash),
   175  		Signer:            assetSigner,
   176  		Alias:             &alias,
   177  	}
   178  	return a, reg.SaveAsset(a, alias)
   179  }
   180  
   181  // SaveAsset store asset
   182  func (reg *Registry) SaveAsset(a *Asset, alias string) error {
   183  	reg.assetMu.Lock()
   184  	defer reg.assetMu.Unlock()
   185  
   186  	aliasKey := aliasKey(alias)
   187  	if existed := reg.db.Get(aliasKey); existed != nil {
   188  		return ErrDuplicateAlias
   189  	}
   190  
   191  	assetKey := Key(&a.AssetID)
   192  	if existAsset := reg.db.Get(assetKey); existAsset != nil {
   193  		return ErrDuplicateAsset
   194  	}
   195  
   196  	rawAsset, err := json.Marshal(a)
   197  	if err != nil {
   198  		return ErrMarshalAsset
   199  	}
   200  
   201  	storeBatch := reg.db.NewBatch()
   202  	storeBatch.Set(aliasKey, []byte(a.AssetID.String()))
   203  	storeBatch.Set(assetKey, rawAsset)
   204  	storeBatch.Write()
   205  	return nil
   206  }
   207  
   208  // FindByID retrieves an Asset record along with its signer, given an assetID.
   209  func (reg *Registry) FindByID(ctx context.Context, id *bc.AssetID) (*Asset, error) {
   210  	reg.cacheMu.Lock()
   211  	cached, ok := reg.cache.Get(id.String())
   212  	reg.cacheMu.Unlock()
   213  	if ok {
   214  		return cached.(*Asset), nil
   215  	}
   216  
   217  	bytes := reg.db.Get(Key(id))
   218  	if bytes == nil {
   219  		return nil, ErrFindAsset
   220  	}
   221  
   222  	asset := &Asset{}
   223  	if err := json.Unmarshal(bytes, asset); err != nil {
   224  		return nil, err
   225  	}
   226  
   227  	reg.cacheMu.Lock()
   228  	reg.cache.Add(id.String(), asset)
   229  	reg.cacheMu.Unlock()
   230  	return asset, nil
   231  }
   232  
   233  // FindByAlias retrieves an Asset record along with its signer,
   234  // given an asset alias.
   235  func (reg *Registry) FindByAlias(alias string) (*Asset, error) {
   236  	reg.cacheMu.Lock()
   237  	cachedID, ok := reg.aliasCache.Get(alias)
   238  	reg.cacheMu.Unlock()
   239  	if ok {
   240  		return reg.FindByID(nil, cachedID.(*bc.AssetID))
   241  	}
   242  
   243  	rawID := reg.db.Get(aliasKey(alias))
   244  	if rawID == nil {
   245  		return nil, errors.Wrapf(ErrFindAsset, "no such asset, alias: %s", alias)
   246  	}
   247  
   248  	assetID := &bc.AssetID{}
   249  	if err := assetID.UnmarshalText(rawID); err != nil {
   250  		return nil, err
   251  	}
   252  
   253  	reg.cacheMu.Lock()
   254  	reg.aliasCache.Add(alias, assetID)
   255  	reg.cacheMu.Unlock()
   256  	return reg.FindByID(nil, assetID)
   257  }
   258  
   259  //GetAliasByID return asset alias string by AssetID string
   260  func (reg *Registry) GetAliasByID(id string) string {
   261  	//btm
   262  	if id == consensus.BTMAssetID.String() {
   263  		return consensus.BTMAlias
   264  	}
   265  
   266  	assetID := &bc.AssetID{}
   267  	if err := assetID.UnmarshalText([]byte(id)); err != nil {
   268  		return ""
   269  	}
   270  
   271  	asset, err := reg.FindByID(nil, assetID)
   272  	if err != nil {
   273  		return ""
   274  	}
   275  
   276  	return *asset.Alias
   277  }
   278  
   279  // GetAsset get asset by assetID
   280  func (reg *Registry) GetAsset(id string) (*Asset, error) {
   281  	var assetID bc.AssetID
   282  	if err := assetID.UnmarshalText([]byte(id)); err != nil {
   283  		return nil, err
   284  	}
   285  
   286  	if assetID.String() == DefaultNativeAsset.AssetID.String() {
   287  		return DefaultNativeAsset, nil
   288  	}
   289  
   290  	asset := &Asset{}
   291  	if interAsset := reg.db.Get(Key(&assetID)); interAsset != nil {
   292  		if err := json.Unmarshal(interAsset, asset); err != nil {
   293  			return nil, err
   294  		}
   295  		return asset, nil
   296  	}
   297  
   298  	if extAsset := reg.db.Get(ExtAssetKey(&assetID)); extAsset != nil {
   299  		definitionMap := make(map[string]interface{})
   300  		if err := json.Unmarshal(extAsset, &definitionMap); err != nil {
   301  			return nil, err
   302  		}
   303  		alias := assetID.String()
   304  		asset.Alias = &alias
   305  		asset.AssetID = assetID
   306  		asset.DefinitionMap = definitionMap
   307  		return asset, nil
   308  	}
   309  
   310  	return nil, errors.WithDetailf(ErrFindAsset, "no such asset, assetID: %s", id)
   311  }
   312  
   313  // ListAssets returns the accounts in the db
   314  func (reg *Registry) ListAssets(id string) ([]*Asset, error) {
   315  	assets := []*Asset{DefaultNativeAsset}
   316  
   317  	assetIDStr := strings.TrimSpace(id)
   318  	if assetIDStr == DefaultNativeAsset.AssetID.String() {
   319  		return assets, nil
   320  	}
   321  
   322  	if assetIDStr != "" {
   323  		assetID := &bc.AssetID{}
   324  		if err := assetID.UnmarshalText([]byte(assetIDStr)); err != nil {
   325  			return nil, err
   326  		}
   327  
   328  		asset := &Asset{}
   329  		interAsset := reg.db.Get(Key(assetID))
   330  		if interAsset != nil {
   331  			if err := json.Unmarshal(interAsset, asset); err != nil {
   332  				return nil, err
   333  			}
   334  			return []*Asset{asset}, nil
   335  		}
   336  
   337  		return []*Asset{}, nil
   338  	}
   339  
   340  	assetIter := reg.db.IteratorPrefix(assetPrefix)
   341  	defer assetIter.Release()
   342  
   343  	for assetIter.Next() {
   344  		asset := &Asset{}
   345  		if err := json.Unmarshal(assetIter.Value(), asset); err != nil {
   346  			return nil, err
   347  		}
   348  		assets = append(assets, asset)
   349  	}
   350  
   351  	return assets, nil
   352  }
   353  
   354  // serializeAssetDef produces a canonical byte representation of an asset
   355  // definition. Currently, this is implemented using pretty-printed JSON.
   356  // As is the standard for Go's map[string] serialization, object keys will
   357  // appear in lexicographic order. Although this is mostly meant for machine
   358  // consumption, the JSON is pretty-printed for easy reading.
   359  func serializeAssetDef(def map[string]interface{}) ([]byte, error) {
   360  	if def == nil {
   361  		def = make(map[string]interface{}, 0)
   362  	}
   363  	return json.MarshalIndent(def, "", "  ")
   364  }
   365  
   366  func multisigIssuanceProgram(pubkeys []ed25519.PublicKey, nrequired int, blockHeight int64) (program []byte, vmversion uint64, err error) {
   367  	issuanceProg, err := vmutil.P2SPMultiSigProgramWithHeight(pubkeys, nrequired, blockHeight)
   368  	if err != nil {
   369  		return nil, 0, err
   370  	}
   371  	builder := vmutil.NewBuilder()
   372  	builder.AddRawBytes(issuanceProg)
   373  	prog, err := builder.Build()
   374  	return prog, 1, err
   375  }
   376  
   377  //UpdateAssetAlias updates asset alias
   378  func (reg *Registry) UpdateAssetAlias(id, newAlias string) error {
   379  	oldAlias := reg.GetAliasByID(id)
   380  	newAlias = strings.ToUpper(strings.TrimSpace(newAlias))
   381  
   382  	if oldAlias == consensus.BTMAlias || newAlias == consensus.BTMAlias {
   383  		return ErrInternalAsset
   384  	}
   385  
   386  	if oldAlias == "" || newAlias == "" {
   387  		return ErrNullAlias
   388  	}
   389  
   390  	reg.assetMu.Lock()
   391  	defer reg.assetMu.Unlock()
   392  
   393  	if _, err := reg.FindByAlias(newAlias); err == nil {
   394  		return ErrDuplicateAlias
   395  	}
   396  
   397  	findAsset, err := reg.FindByAlias(oldAlias)
   398  	if err != nil {
   399  		return err
   400  	}
   401  
   402  	storeBatch := reg.db.NewBatch()
   403  	findAsset.Alias = &newAlias
   404  	assetID := &findAsset.AssetID
   405  	rawAsset, err := json.Marshal(findAsset)
   406  	if err != nil {
   407  		return err
   408  	}
   409  
   410  	storeBatch.Set(Key(assetID), rawAsset)
   411  	storeBatch.Set(aliasKey(newAlias), []byte(assetID.String()))
   412  	storeBatch.Delete(aliasKey(oldAlias))
   413  	storeBatch.Write()
   414  
   415  	reg.cacheMu.Lock()
   416  	reg.aliasCache.Add(newAlias, assetID)
   417  	reg.aliasCache.Remove(oldAlias)
   418  	reg.cacheMu.Unlock()
   419  
   420  	return nil
   421  }