diff --git a/__tests__/unit/core-magistrate/handlers/entity.test.ts b/__tests__/unit/core-magistrate/handlers/entity.test.ts index 365c61db63..dffc78528d 100644 --- a/__tests__/unit/core-magistrate/handlers/entity.test.ts +++ b/__tests__/unit/core-magistrate/handlers/entity.test.ts @@ -6,6 +6,7 @@ import { EntityAction, EntityType } from "@arkecosystem/core-magistrate-crypto/s import { EntityAlreadyRegisteredError, EntityAlreadyResignedError, + EntityNameAlreadyRegisteredError, EntityNameDoesNotMatchDelegateError, EntityNotRegisteredError, EntitySenderIsNotDelegateError, @@ -178,6 +179,62 @@ describe("Entity handler", () => { ).rejects.toBeInstanceOf(StaticFeeMismatchError); }); + it.each([validRegisters])( + "should throw when entity name is already registered for same type", + async asset => { + const transaction = entityBuilder + .asset(asset) + .sign(senderPassphrase) + .build(); + + const randomPassphrase = "this is another passphrase"; + const randomWallet = new Wallets.Wallet(Identities.Address.fromPassphrase(randomPassphrase)); + randomWallet.balance = Utils.BigNumber.make(452765431200000); + randomWallet.publicKey = Identities.PublicKey.fromPassphrase(randomPassphrase); + // entity name already registered with different wallet and different tx id + randomWallet.setAttribute("entities", { + "7950c6a0d096eeb4883237feec12b9f37f36ab9343ff3640904befc75ce32ec2": { + type: asset.type, + subType: (asset.subType + 1) % 255, // different subType but still in the range [0, 255] + data: asset.data, + }, + }); + walletManager.reindex(randomWallet); + + await expect( + entityHandler.throwIfCannotBeApplied(transaction, senderWallet, walletManager), + ).rejects.toBeInstanceOf(EntityNameAlreadyRegisteredError); + }, + ); + + it.each([validRegisters])( + "should not throw when entity name is registered for a different type", + async asset => { + const transaction = entityBuilder + .asset(asset) + .sign(senderPassphrase) + .build(); + + const randomPassphrase = "this is another passphrase"; + const randomWallet = new Wallets.Wallet(Identities.Address.fromPassphrase(randomPassphrase)); + randomWallet.balance = Utils.BigNumber.make(452765431200000); + randomWallet.publicKey = Identities.PublicKey.fromPassphrase(randomPassphrase); + // entity name already registered with different wallet and different tx id + randomWallet.setAttribute("entities", { + "7950c6a0d096eeb4883237feec12b9f37f36ab9343ff3640904befc75ce32ec2": { + type: (asset.type + 1) % 255, // different subType but still in the range [0, 255] + subType: asset.subType, + data: asset.data, + }, + }); + walletManager.reindex(randomWallet); + + await expect( + entityHandler.throwIfCannotBeApplied(transaction, senderWallet, walletManager), + ).toResolve(); + }, + ); + describe("Entity delegate", () => { const createEntityDelegateTx = name => entityBuilder diff --git a/packages/core-magistrate-transactions/src/handlers/entity-subhandlers/register.ts b/packages/core-magistrate-transactions/src/handlers/entity-subhandlers/register.ts deleted file mode 100644 index ff3f5f5db0..0000000000 --- a/packages/core-magistrate-transactions/src/handlers/entity-subhandlers/register.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { Interfaces } from "@arkecosystem/crypto"; - -import { Database, EventEmitter, State } from "@arkecosystem/core-interfaces"; -import { EntityAlreadyRegisteredError, EntityNameAlreadyRegisteredError } from "../../errors"; -import { IEntitiesWallet, IEntityWallet } from "../../interfaces"; -import { MagistrateIndex } from "../../wallet-manager"; - -// Entity Register sub-handler : most of the sub-handler methods are implemented here -// but it is extended by the bridgechain, business, developer, plugin... subhandlers -export class EntityRegisterSubHandler { - public async bootstrap( - transactions: Database.IBootstrapTransaction[], - walletManager: State.IWalletManager, - ): Promise { - for (const transaction of transactions) { - const wallet: State.IWallet = walletManager.findByPublicKey(transaction.senderPublicKey); - const entities: IEntitiesWallet = wallet.getAttribute("entities", {}); - - entities[transaction.id] = { - type: transaction.asset.type, - subType: transaction.asset.subType, - data: transaction.asset.data, - }; - - wallet.setAttribute("entities", entities); - - walletManager.index([wallet]); - } - } - - public async throwIfCannotBeApplied( - transaction: Interfaces.ITransaction, - wallet: State.IWallet, - walletManager: State.IWalletManager, - ): Promise { - const walletEntities = wallet.getAttribute("entities", {}); - if (walletEntities[transaction.id]) { - throw new EntityAlreadyRegisteredError(); - } - - for (const wallet of walletManager.getIndex(MagistrateIndex.Entities).values()) { - if (wallet.hasAttribute("entities")) { - const entityValues: IEntityWallet[] = Object.values(wallet.getAttribute("entities")); - - if ( - entityValues.some( - entity => - entity.data.name!.toLowerCase() === transaction.data.asset!.data.name.toLowerCase() && - entity.type === transaction.data.asset!.type && - entity.subType === transaction.data.asset!.subType, - ) - ) { - throw new EntityNameAlreadyRegisteredError(); - } - } - } - } - - // tslint:disable-next-line:no-empty - public emitEvents(transaction: Interfaces.ITransaction, emitter: EventEmitter.EventEmitter): void {} - - public async applyToSender( - transaction: Interfaces.ITransaction, - walletManager: State.IWalletManager, - ): Promise { - const wallet = walletManager.findByPublicKey(transaction.data.senderPublicKey); - - const entities = wallet.getAttribute("entities", {}); - entities[transaction.id] = { - type: transaction.data.asset.type, - subType: transaction.data.asset.subType, - data: { ...transaction.data.asset.data }, - }; - - wallet.setAttribute("entities", entities); - - walletManager.index([wallet]); - } - - public async revertForSender( - transaction: Interfaces.ITransaction, - walletManager: State.IWalletManager, - connection: Database.IConnection, - ): Promise { - const wallet = walletManager.findByPublicKey(transaction.data.senderPublicKey); - - const entities = wallet.getAttribute("entities", {}); - delete entities[transaction.id]; - - wallet.setAttribute("entities", entities); - - walletManager.index([wallet]); - } - - public async applyToRecipient( - transaction: Interfaces.ITransaction, - walletManager: State.IWalletManager, - // tslint:disable-next-line: no-empty - ): Promise {} - - public async revertForRecipient( - transaction: Interfaces.ITransaction, - walletManager: State.IWalletManager, - // tslint:disable-next-line:no-empty - ): Promise {} -} diff --git a/packages/core-magistrate-transactions/src/handlers/entity-subhandlers/resign.ts b/packages/core-magistrate-transactions/src/handlers/entity-subhandlers/resign.ts deleted file mode 100644 index 7f49d49902..0000000000 --- a/packages/core-magistrate-transactions/src/handlers/entity-subhandlers/resign.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { Interfaces } from "@arkecosystem/crypto"; - -import { Database, EventEmitter, State } from "@arkecosystem/core-interfaces"; -import { - EntityAlreadyResignedError, - EntityNotRegisteredError, - EntityWrongSubTypeError, - EntityWrongTypeError, -} from "../../errors"; -import { IEntitiesWallet } from "../../interfaces"; - -// Entity Resign sub-handler : most of the sub-handler methods are implemented here -// but it is extended by the bridgechain, business, developer, plugin... subhandlers -export class EntityResignSubHandler { - public async bootstrap( - transactions: Database.IBootstrapTransaction[], - walletManager: State.IWalletManager, - ): Promise { - for (const transaction of transactions) { - const wallet: State.IWallet = walletManager.findByPublicKey(transaction.senderPublicKey); - const entities: IEntitiesWallet = wallet.getAttribute("entities"); - - entities[transaction.asset.registrationId] = { - ...entities[transaction.asset.registrationId], - resigned: true, - }; - - wallet.setAttribute("entities", entities); - - walletManager.index([wallet]); - } - } - - public async throwIfCannotBeApplied( - transaction: Interfaces.ITransaction, - wallet: State.IWallet, - walletManager: State.IWalletManager, - ): Promise { - const walletEntities = wallet.getAttribute("entities", {}); - if (!walletEntities[transaction.data.asset.registrationId]) { - throw new EntityNotRegisteredError(); - } - - if (walletEntities[transaction.data.asset.registrationId].resigned) { - throw new EntityAlreadyResignedError(); - } - - if (walletEntities[transaction.data.asset.registrationId].type !== transaction.data.asset.type) { - throw new EntityWrongTypeError(); - } - - if (walletEntities[transaction.data.asset.registrationId].subType !== transaction.data.asset.subType) { - throw new EntityWrongSubTypeError(); - } - } - - // tslint:disable-next-line:no-empty - public emitEvents(transaction: Interfaces.ITransaction, emitter: EventEmitter.EventEmitter): void {} - - public async applyToSender( - transaction: Interfaces.ITransaction, - walletManager: State.IWalletManager, - ): Promise { - const wallet = walletManager.findByPublicKey(transaction.data.senderPublicKey); - - const entities = wallet.getAttribute("entities", {}); - if (entities[transaction.data.asset.registrationId]) { - entities[transaction.data.asset.registrationId] = { - ...entities[transaction.data.asset.registrationId], - resigned: true, - }; - } - - wallet.setAttribute("entities", entities); - - walletManager.index([wallet]); - } - - public async revertForSender( - transaction: Interfaces.ITransaction, - walletManager: State.IWalletManager, - connection: Database.IConnection, - ): Promise { - const wallet = walletManager.findByPublicKey(transaction.data.senderPublicKey); - - const entities = wallet.getAttribute("entities", {}); - if (entities[transaction.data.asset.registrationId]) { - delete entities[transaction.data.asset.registrationId].resigned; - } - - wallet.setAttribute("entities", entities); - - walletManager.index([wallet]); - } - - public async applyToRecipient( - transaction: Interfaces.ITransaction, - walletManager: State.IWalletManager, - // tslint:disable-next-line: no-empty - ): Promise {} - - public async revertForRecipient( - transaction: Interfaces.ITransaction, - walletManager: State.IWalletManager, - // tslint:disable-next-line:no-empty - ): Promise {} -} diff --git a/packages/core-magistrate-transactions/src/handlers/entity-subhandlers/update.ts b/packages/core-magistrate-transactions/src/handlers/entity-subhandlers/update.ts deleted file mode 100644 index f771de58ef..0000000000 --- a/packages/core-magistrate-transactions/src/handlers/entity-subhandlers/update.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { Enums, Interfaces as MagistrateInterfaces } from "@arkecosystem/core-magistrate-crypto"; -import { Interfaces } from "@arkecosystem/crypto"; - -import { Database, EventEmitter, State } from "@arkecosystem/core-interfaces"; -import { - EntityAlreadyResignedError, - EntityNotRegisteredError, - EntityWrongSubTypeError, - EntityWrongTypeError, -} from "../../errors"; -import { IEntitiesWallet, IEntityWallet } from "../../interfaces"; - -// Entity Register sub-handler : most of the sub-handler methods are implemented here -// but it is extended by the bridgechain, business, developer, plugin... subhandlers -export class EntityUpdateSubHandler { - public async bootstrap( - transactions: Database.IBootstrapTransaction[], - walletManager: State.IWalletManager, - ): Promise { - for (const transaction of transactions) { - const wallet: State.IWallet = walletManager.findByPublicKey(transaction.senderPublicKey); - const entities: IEntitiesWallet = wallet.getAttribute("entities"); - - entities[transaction.asset.registrationId] = { - type: entities[transaction.asset.registrationId].type, - subType: entities[transaction.asset.registrationId].subType, - data: this.mergeAssetData(entities[transaction.asset.registrationId].data, transaction.asset.data), - }; - - wallet.setAttribute("entities", entities); - - walletManager.index([wallet]); - } - } - - public async throwIfCannotBeApplied( - transaction: Interfaces.ITransaction, - wallet: State.IWallet, - walletManager: State.IWalletManager, - ): Promise { - const walletEntities = wallet.getAttribute("entities", {}); - if (!walletEntities[transaction.data.asset.registrationId]) { - throw new EntityNotRegisteredError(); - } - - if (walletEntities[transaction.data.asset.registrationId].resigned) { - throw new EntityAlreadyResignedError(); - } - - if (walletEntities[transaction.data.asset.registrationId].type !== transaction.data.asset.type) { - throw new EntityWrongTypeError(); - } - - if (walletEntities[transaction.data.asset.registrationId].subType !== transaction.data.asset.subType) { - throw new EntityWrongSubTypeError(); - } - } - - // tslint:disable-next-line:no-empty - public emitEvents(transaction: Interfaces.ITransaction, emitter: EventEmitter.EventEmitter): void {} - - public async applyToSender( - transaction: Interfaces.ITransaction, - walletManager: State.IWalletManager, - ): Promise { - const assetData: MagistrateInterfaces.IEntityAssetData = transaction.data.asset.data; - - const wallet = walletManager.findByPublicKey(transaction.data.senderPublicKey); - - const entities = wallet.getAttribute("entities", {}); - if (entities[transaction.data.asset.registrationId]) { - entities[transaction.data.asset.registrationId] = { - ...entities[transaction.data.asset.registrationId], - data: this.mergeAssetData(entities[transaction.data.asset.registrationId].data, assetData), - }; - } - - wallet.setAttribute("entities", entities); - - walletManager.index([wallet]); - } - - public async revertForSender( - transaction: Interfaces.ITransaction, - walletManager: State.IWalletManager, - connection: Database.IConnection, - ): Promise { - // Here we have to "replay" entity registration and update transactions associated with the registration id - // (except the current one being reverted) to rebuild previous wallet state. - const sender: State.IWallet = walletManager.findByPublicKey(transaction.data.senderPublicKey); - - const entities = sender.getAttribute("entities", {}); - const registrationId: string = transaction.data.asset.registrationId; - - const dbEntityTransactions = await connection.transactionsRepository.search({ - parameters: [ - { - field: "senderPublicKey", - value: transaction.data.senderPublicKey, - operator: Database.SearchOperator.OP_EQ, - }, - { - field: "type", - value: Enums.MagistrateTransactionType.Entity, - operator: Database.SearchOperator.OP_EQ, - }, - { - field: "typeGroup", - value: transaction.data.typeGroup, - operator: Database.SearchOperator.OP_EQ, - }, - ], - orderBy: [ - { - direction: "asc", - field: "nonce", - }, - ], - }); - - let mergedEntity: IEntityWallet; - for (const dbEntityTx of dbEntityTransactions.rows) { - if (dbEntityTx.id === transaction.id) { - continue; - } - - if (!mergedEntity) { - // first register tx - mergedEntity = { - type: dbEntityTx.asset.type, - subType: dbEntityTx.asset.subType, - data: { ...dbEntityTx.asset.data }, - }; - } else { - mergedEntity.data = this.mergeAssetData(mergedEntity.data, dbEntityTx.asset!.data); - } - } - - entities[registrationId] = { - type: mergedEntity.type, - subType: mergedEntity.subType, - data: { ...mergedEntity.data }, - }; - - sender.setAttribute("entities", entities); - - walletManager.index([sender]); - } - - public async applyToRecipient( - transaction: Interfaces.ITransaction, - walletManager: State.IWalletManager, - // tslint:disable-next-line: no-empty - ): Promise {} - - public async revertForRecipient( - transaction: Interfaces.ITransaction, - walletManager: State.IWalletManager, - // tslint:disable-next-line:no-empty - ): Promise {} - - private mergeAssetData( - baseData: MagistrateInterfaces.IEntityAssetData, - dataToMerge: MagistrateInterfaces.IEntityAssetData, - ): MagistrateInterfaces.IEntityAssetData { - return { - ...baseData, - ...dataToMerge, - }; - } -} diff --git a/packages/core-magistrate-transactions/src/handlers/entity.ts b/packages/core-magistrate-transactions/src/handlers/entity.ts index 01255c0a47..816f312e47 100644 --- a/packages/core-magistrate-transactions/src/handlers/entity.ts +++ b/packages/core-magistrate-transactions/src/handlers/entity.ts @@ -94,8 +94,7 @@ export class EntityTransactionHandler extends IHandlers.TransactionHandler { entityValues.some( entity => entity.data.name!.toLowerCase() === transaction.data.asset!.data.name.toLowerCase() && - entity.type === transaction.data.asset!.type && - entity.subType === transaction.data.asset!.subType, + entity.type === transaction.data.asset!.type, ) ) { throw new EntityNameAlreadyRegisteredError(); diff --git a/packages/crypto/src/networks/devnet/exceptions.json b/packages/crypto/src/networks/devnet/exceptions.json index 22a77f01a0..9c15e24928 100644 --- a/packages/crypto/src/networks/devnet/exceptions.json +++ b/packages/crypto/src/networks/devnet/exceptions.json @@ -606,7 +606,10 @@ "1106e034536e2663d64f38b99ad21e10ce7a3da1e15abdbb0e6587253eea6d81", "0bf3fd2c566fb9178815b4015b412af3ed574b6b46a0a96b55ba7caba89386c6", "d65586923e287c108e1c475dd93e9cf4eb3a09c6596119bb28bf4274ad640e45", - "39f3f5312d64f858232c5b75b783f02cb2e52c43d0004744caea37a5a62123de" + "39f3f5312d64f858232c5b75b783f02cb2e52c43d0004744caea37a5a62123de", + "bf733eb2202fb46592a6972eea81f6ce7bf8d6391932bcd50d600ad942356b7b", + "cef2fa42a91875b5c617de2cc601a3bc454531974b6def5873e50fdd00eaca97", + "0a8848349d6e2441b7403a391590212d72a10e307506d38006c0e1cc0c34705a" ], "blocksTransactions": { "15895730198424359628": [],