10 "golang.org/x/crypto/sha3"
12 "github.com/golang/groupcache/lru"
13 "github.com/golang/groupcache/singleflight"
14 // "github.com/lib/pq"
15 dbm "github.com/tendermint/tmlibs/db"
17 // "github.com/bytom/blockchain/pin"
18 "github.com/bytom/blockchain/signers"
19 "github.com/bytom/crypto/ed25519"
20 "github.com/bytom/crypto/ed25519/chainkd"
21 // "chain/database/pg"
22 "github.com/bytom/errors"
23 "github.com/bytom/protocol"
24 "github.com/bytom/protocol/bc"
25 "github.com/bytom/protocol/vm/vmutil"
28 const maxAssetCache = 1000
31 ErrDuplicateAlias = errors.New("duplicate asset alias")
32 ErrBadIdentifier = errors.New("either ID or alias must be specified, and not both")
35 func NewRegistry(db dbm.DB, chain *protocol.Chain/*, pinStore *pin.Store*/) *Registry {
39 initialBlockHash: chain.InitialBlockHash,
40 // pinStore: pinStore,
41 cache: lru.New(maxAssetCache),
42 aliasCache: lru.New(maxAssetCache),
46 // Registry tracks and stores all known assets on a blockchain.
47 type Registry struct {
51 initialBlockHash bc.Hash
52 // pinStore *pin.Store
54 idGroup singleflight.Group
55 aliasGroup singleflight.Group
62 func (reg *Registry) IndexAssets(indexer Saver) {
70 IssuanceProgram []byte
71 InitialBlockHash bc.Hash
72 Signer *signers.Signer
73 Tags map[string]interface{}
75 definition map[string]interface{}
79 func (asset *Asset) Definition() (map[string]interface{}, error) {
80 if asset.definition == nil && len(asset.rawDefinition) > 0 {
81 err := json.Unmarshal(asset.rawDefinition, &asset.definition)
83 return nil, errors.Wrap(err)
86 return asset.definition, nil
89 func (asset *Asset) RawDefinition() []byte {
90 return asset.rawDefinition
93 func (asset *Asset) SetDefinition(def map[string]interface{}) error {
94 rawdef, err := serializeAssetDef(def)
98 asset.definition = def
99 asset.rawDefinition = rawdef
103 // Define defines a new Asset.
104 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) {
105 assetSigner, err := signers.Create(ctx, reg.db, "asset", xpubs, quorum, clientToken)
110 rawDefinition, err := serializeAssetDef(definition)
112 return nil, errors.Wrap(err, "serializing asset definition")
115 path := signers.Path(assetSigner, signers.AssetKeySpace)
116 derivedXPubs := chainkd.DeriveXPubs(assetSigner.XPubs, path)
117 derivedPKs := chainkd.XPubKeys(derivedXPubs)
118 issuanceProgram, vmver, err := multisigIssuanceProgram(derivedPKs, assetSigner.Quorum)
123 defhash := bc.NewHash(sha3.Sum256(rawDefinition))
125 definition: definition,
126 rawDefinition: rawDefinition,
128 IssuanceProgram: issuanceProgram,
129 InitialBlockHash: reg.initialBlockHash,
130 AssetID: bc.ComputeAssetID(issuanceProgram, ®.initialBlockHash, vmver, &defhash),
138 asset_id := []byte(assetSigner.ID)
139 ass, err := json.Marshal(asset)
141 return nil, errors.Wrap(err, "failed marshal asset")
144 reg.db.Set(asset_id,json.RawMessage(ass))
147 /* asset, err = reg.insertAsset(ctx, asset, clientToken)
149 return nil, errors.Wrap(err, "inserting asset")
152 err = insertAssetTags(ctx, reg.db, asset.AssetID, tags)
154 return nil, errors.Wrap(err, "inserting asset tags")
157 err = reg.indexAnnotatedAsset(ctx, asset)
159 return nil, errors.Wrap(err, "indexing annotated asset")
165 // UpdateTags modifies the tags of the specified asset. The asset may be
166 // identified either by id or alias, but not both.
168 func (reg *Registry) UpdateTags(ctx context.Context, id, alias *string, tags map[string]interface{}) error {
169 if (id == nil) == (alias == nil) {
170 return errors.Wrap(ErrBadIdentifier)
173 // Fetch the existing asset
182 err = aid.UnmarshalText([]byte(*id))
184 return errors.Wrap(err, "deserialize asset ID")
187 asset, err = reg.findByID(ctx, aid)
189 return errors.Wrap(err, "find asset by ID")
193 asset, err = reg.FindByAlias(ctx, *alias)
195 return errors.Wrap(err, "find asset by alias")
199 // Revise tags in-memory
203 // Perform persistent updates
205 err = insertAssetTags(ctx, reg.db, asset.AssetID, asset.Tags)
207 return errors.Wrap(err, "inserting asset tags")
210 err = reg.indexAnnotatedAsset(ctx, asset)
212 return errors.Wrap(err, "update asset index")
218 reg.cache.Add(asset.AssetID, asset)
225 // findByID retrieves an Asset record along with its signer, given an assetID.
226 func (reg *Registry) findByID(ctx context.Context, id bc.AssetID) (*Asset, error) {
228 cached, ok := reg.cache.Get(id)
231 return cached.(*Asset), nil
234 untypedAsset, err := reg.idGroup.Do(id.String(), func() (interface{}, error) {
235 // return assetQuery(ctx, reg.db, "assets.id=$1", id)
243 asset := untypedAsset.(*Asset)
245 reg.cache.Add(id, asset)
250 // FindByAlias retrieves an Asset record along with its signer,
251 // given an asset alias.
253 func (reg *Registry) FindByAlias(ctx context.Context, alias string) (*Asset, error) {
255 cachedID, ok := reg.aliasCache.Get(alias)
258 return reg.findByID(ctx, cachedID.(bc.AssetID))
261 untypedAsset, err := reg.aliasGroup.Do(alias, func() (interface{}, error) {
262 // asset, err := assetQuery(ctx, reg.db, "assets.alias=$1", alias)
271 a := untypedAsset.(*Asset)
273 reg.aliasCache.Add(alias, a.AssetID)
274 reg.cache.Add(a.AssetID, a)
280 // insertAsset adds the asset to the database. If the asset has a client token,
281 // and there already exists an asset with that client token, insertAsset will
282 // lookup and return the existing asset instead.
284 func (reg *Registry) insertAsset(ctx context.Context, asset *Asset, clientToken string) (*Asset, error) {
287 (id, alias, signer_id, initial_block_hash, vm_version, issuance_program, definition, client_token)
288 VALUES($1::bytea, $2, $3, $4, $5, $6, $7, $8)
289 ON CONFLICT (client_token) DO NOTHING
292 var signerID sql.NullString
293 if asset.Signer != nil {
294 signerID = sql.NullString{Valid: true, String: asset.Signer.ID}
297 nullToken := sql.NullString{
299 Valid: clientToken != "",
302 err := reg.db.QueryRowContext(
304 asset.AssetID, asset.Alias, signerID,
305 asset.InitialBlockHash, asset.VMVersion, asset.IssuanceProgram,
306 asset.rawDefinition, nullToken,
307 ).Scan(&asset.sortID)
309 if pg.IsUniqueViolation(err) {
310 return nil, errors.WithDetail(ErrDuplicateAlias, "an asset with the provided alias already exists")
311 } else if err == sql.ErrNoRows && clientToken != "" {
312 // There is already an asset with the provided client
313 // token. We should return the existing asset.
314 asset, err = assetByClientToken(ctx, reg.db, clientToken)
316 return nil, errors.Wrap(err, "retrieving existing asset")
318 } else if err != nil {
319 return nil, errors.Wrap(err)
324 // insertAssetTags inserts a set of tags for the given assetID.
325 // It must take place inside a database transaction.
327 func insertAssetTags(ctx context.Context, db pg.DB, assetID bc.AssetID, tags map[string]interface{}) error {
328 tagsParam, err := mapToNullString(tags)
330 return errors.Wrap(err)
334 INSERT INTO asset_tags (asset_id, tags) VALUES ($1, $2)
335 ON CONFLICT (asset_id) DO UPDATE SET tags = $2
337 _, err = db.ExecContext(ctx, q, assetID, tagsParam)
339 return errors.Wrap(err)
345 // assetByClientToken loads an asset from the database using its client token.
347 func assetByClientToken(ctx context.Context, db pg.DB, clientToken string) (*Asset, error) {
348 return assetQuery(ctx, db, "assets.client_token=$1", clientToken)
351 func assetQuery(ctx context.Context, db pg.DB, pred string, args ...interface{}) (*Asset, error) {
353 SELECT assets.id, assets.alias, assets.vm_version, assets.issuance_program, assets.definition,
354 assets.initial_block_hash, assets.sort_id,
355 signers.id, COALESCE(signers.type, ''), COALESCE(signers.xpubs, '{}'),
356 COALESCE(signers.quorum, 0), COALESCE(signers.key_index, 0),
359 LEFT JOIN signers ON signers.id=assets.signer_id
360 LEFT JOIN asset_tags ON asset_tags.asset_id=assets.id
368 signerID sql.NullString
375 err := db.QueryRowContext(ctx, fmt.Sprintf(baseQ, pred), args...).Scan(
385 (*pq.ByteaArray)(&xpubs),
390 if err == sql.ErrNoRows {
391 return nil, pg.ErrUserInputNotFound
392 } else if err != nil {
397 a.Signer, err = signers.New(signerID.String, signerType, xpubs, quorum, keyIndex)
404 a.Alias = &alias.String
408 err := json.Unmarshal(tags, &a.Tags)
410 return nil, errors.Wrap(err)
413 if len(a.rawDefinition) > 0 {
414 // ignore errors; non-JSON asset definitions can still end up
415 // on the blockchain from non-Chain Core clients.
416 _ = json.Unmarshal(a.rawDefinition, &a.definition)
423 // serializeAssetDef produces a canonical byte representation of an asset
424 // definition. Currently, this is implemented using pretty-printed JSON.
425 // As is the standard for Go's map[string] serialization, object keys will
426 // appear in lexicographic order. Although this is mostly meant for machine
427 // consumption, the JSON is pretty-printed for easy reading.
428 // The empty asset def is an empty byte slice.
429 func serializeAssetDef(def map[string]interface{}) ([]byte, error) {
433 return json.MarshalIndent(def, "", " ")
436 func multisigIssuanceProgram(pubkeys []ed25519.PublicKey, nrequired int) (program []byte, vmversion uint64, err error) {
437 issuanceProg, err := vmutil.P2SPMultiSigProgram(pubkeys, nrequired)
441 builder := vmutil.NewBuilder()
442 builder.AddRawBytes(issuanceProg)
443 prog, err := builder.Build()
447 func mapToNullString(in map[string]interface{}) (*sql.NullString, error) {
451 mapJSON, err = json.Marshal(in)
453 return nil, errors.Wrap(err)
456 return &sql.NullString{String: string(mapJSON), Valid: len(mapJSON) > 0}, nil