github.com/bytom/bytom@v1.1.2-0.20221014091027-bbcba3df6075/asset/asset.go (about) 1 package asset 2 3 import ( 4 "context" 5 "crypto/ed25519" 6 "encoding/json" 7 "strings" 8 "sync" 9 10 "github.com/golang/groupcache/lru" 11 "golang.org/x/crypto/sha3" 12 13 "github.com/bytom/bytom/blockchain/signers" 14 "github.com/bytom/bytom/common" 15 "github.com/bytom/bytom/consensus" 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 uint64, 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 uint64) (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 }