9 "github.com/golang/groupcache/lru"
10 "github.com/golang/groupcache/singleflight"
11 dbm "github.com/tendermint/tmlibs/db"
12 "golang.org/x/crypto/sha3"
14 "github.com/bytom/blockchain/signers"
15 "github.com/bytom/crypto/ed25519"
16 "github.com/bytom/crypto/ed25519/chainkd"
17 "github.com/bytom/errors"
18 "github.com/bytom/protocol"
19 "github.com/bytom/protocol/bc"
20 "github.com/bytom/protocol/vm/vmutil"
23 const maxAssetCache = 1000
26 ErrDuplicateAlias = errors.New("duplicate asset alias")
27 ErrBadIdentifier = errors.New("either ID or alias must be specified, and not both")
30 func NewRegistry(db dbm.DB, chain *protocol.Chain) *Registry {
34 initialBlockHash: chain.InitialBlockHash,
35 cache: lru.New(maxAssetCache),
36 aliasCache: lru.New(maxAssetCache),
40 // Registry tracks and stores all known assets on a blockchain.
41 type Registry struct {
44 initialBlockHash bc.Hash
46 idGroup singleflight.Group
47 aliasGroup singleflight.Group
58 IssuanceProgram []byte
59 InitialBlockHash bc.Hash
61 Tags map[string]interface{}
62 RawDefinitionByte []byte
63 DefinitionMap map[string]interface{}
67 func (asset *Asset) Definition() (map[string]interface{}, error) {
68 if asset.DefinitionMap == nil && len(asset.RawDefinitionByte) > 0 {
69 err := json.Unmarshal(asset.RawDefinitionByte, &asset.DefinitionMap)
71 return nil, errors.Wrap(err)
74 return asset.DefinitionMap, nil
77 func (asset *Asset) RawDefinition() []byte {
78 return asset.RawDefinitionByte
81 func (asset *Asset) SetDefinition(def map[string]interface{}) error {
82 rawdef, err := serializeAssetDef(def)
86 asset.DefinitionMap = def
87 asset.RawDefinitionByte = rawdef
91 // Define defines a new Asset.
92 func (reg *Registry) Define(ctx context.Context, xpubs []chainkd.XPub, quorum int, definition map[string]interface{}, alias string, tags map[string]interface{}, clientToken string) (*Asset, error) {
93 assetSigner, err := signers.Create(ctx, reg.db, "asset", xpubs, quorum, clientToken)
98 rawDefinition, err := serializeAssetDef(definition)
100 return nil, errors.Wrap(err, "serializing asset definition")
103 path := signers.Path(assetSigner, signers.AssetKeySpace)
104 derivedXPubs := chainkd.DeriveXPubs(assetSigner.XPubs, path)
105 derivedPKs := chainkd.XPubKeys(derivedXPubs)
106 issuanceProgram, vmver, err := multisigIssuanceProgram(derivedPKs, assetSigner.Quorum)
111 defhash := bc.NewHash(sha3.Sum256(rawDefinition))
113 DefinitionMap: definition,
114 RawDefinitionByte: rawDefinition,
116 IssuanceProgram: issuanceProgram,
117 InitialBlockHash: reg.initialBlockHash,
118 AssetID: bc.ComputeAssetID(issuanceProgram, ®.initialBlockHash, vmver, &defhash),
126 assetID := []byte(asset.AssetID.String())
127 ass, err := json.Marshal(asset)
129 return nil, errors.Wrap(err, "failed marshal asset")
132 reg.db.Set(assetID, json.RawMessage(ass))
138 // UpdateTags modifies the tags of the specified asset. The asset may be
139 // identified either by id or alias, but not both.
141 func (reg *Registry) UpdateTags(ctx context.Context, id, alias *string, tags map[string]interface{}) error {
142 if (id == nil) == (alias == nil) {
143 return errors.Wrap(ErrBadIdentifier)
146 // Fetch the existing asset
155 err = aid.UnmarshalText([]byte(*id))
157 return errors.Wrap(err, "deserialize asset ID")
160 asset, err = reg.findByID(ctx, aid)
162 return errors.Wrap(err, "find asset by ID")
166 asset, err = reg.FindByAlias(ctx, *alias)
168 return errors.Wrap(err, "find asset by alias")
172 // Revise tags in-memory
177 reg.cache.Add(asset.AssetID, asset)
184 // findByID retrieves an Asset record along with its signer, given an assetID.
185 func (reg *Registry) findByID(ctx context.Context, id bc.AssetID) (*Asset, error) {
187 cached, ok := reg.cache.Get(id)
190 return cached.(*Asset), nil
193 bytes := reg.db.Get([]byte(id.String()))
195 return nil, errors.New("no exit this asset")
199 if err := json.Unmarshal(bytes, &asset); err != nil {
200 return nil, fmt.Errorf("err:%s,asset signer id:%s", err, id.String())
204 reg.cache.Add(id, &asset)
209 // FindByAlias retrieves an Asset record along with its signer,
210 // given an asset alias.
212 func (reg *Registry) FindByAlias(ctx context.Context, alias string) (*Asset, error) {
214 cachedID, ok := reg.aliasCache.Get(alias)
217 return reg.findByID(ctx, cachedID.(bc.AssetID))
220 untypedAsset, err := reg.aliasGroup.Do(alias, func() (interface{}, error) {
228 a := untypedAsset.(*Asset)
230 reg.aliasCache.Add(alias, a.AssetID)
231 reg.cache.Add(a.AssetID, a)
237 func (reg *Registry) QueryAll(ctx context.Context) (interface{}, error) {
238 ret := make([]interface{}, 0)
240 assetIter := reg.db.Iterator()
241 defer assetIter.Release()
243 for assetIter.Next() {
244 value := string(assetIter.Value())
245 ret = append(ret, value)
251 // serializeAssetDef produces a canonical byte representation of an asset
252 // definition. Currently, this is implemented using pretty-printed JSON.
253 // As is the standard for Go's map[string] serialization, object keys will
254 // appear in lexicographic order. Although this is mostly meant for machine
255 // consumption, the JSON is pretty-printed for easy reading.
256 // The empty asset def is an empty byte slice.
257 func serializeAssetDef(def map[string]interface{}) ([]byte, error) {
261 return json.MarshalIndent(def, "", " ")
264 func multisigIssuanceProgram(pubkeys []ed25519.PublicKey, nrequired int) (program []byte, vmversion uint64, err error) {
265 issuanceProg, err := vmutil.P2SPMultiSigProgram(pubkeys, nrequired)
269 builder := vmutil.NewBuilder()
270 builder.AddRawBytes(issuanceProg)
271 prog, err := builder.Build()