Preventing spam attacks with account restrictions

Learn how to add and remove account restrictions.

Use case

Imagine you are a company using the public chain to certify the quality of your products.

When the quality verification process concludes, an operator sends a quality seal to the product account, which the customers can review by scanning a QR code. For the convenience of the customers, you only want to show relevant transactions and prevent spam from cluttering the product account.

The final customers can review the product mosaics scanning a QR code. For that reason, the company only wants to show related transactions, avoiding that others spam their products with non-related information.

../../_images/account-restrictions-spam1.png

Blocking spam attacks

Thus, you opt to configure the product account restrictions to only receive transactions that follow a set of conditions.

Prerequisites

Method #01: Using the SDK

Blocking transactions by address

An account can decide to receive transactions only from an allowed list of addresses. Similarly, an account can specify a blocked list of addresses to block transactions from.

Note

Allow and block restrictions are mutually exclusive per restriction type. In other words, an account can only be configured to have either an allowed or blocked list per type of restriction.

By default, when there is no restriction set, all the accounts in the network can announce transactions to the stated account.

Returning to our previous example, let us imagine that you want to configure the product account only to accept receiving transactions that come from the company’s account. You might take the following steps to do so:

  1. Define the company’s address TCWYXK-VYBMO4-NBCUF3-AXKJMX-CGVSYQ-OS7ZG2-TLI in a new variable.
// replace with company address
const companyRawAddress = 'TCWYXK-VYBMO4-NBCUF3-AXKJMX-CGVSYQ-OS7ZG2-TLI';
const companyAddress = Address.createFromRawAddress(companyRawAddress);
// replace with company address
const companyRawAddress = 'TCWYXK-VYBMO4-NBCUF3-AXKJMX-CGVSYQ-OS7ZG2-TLI';
const companyAddress = symbol_sdk_1.Address.createFromRawAddress(companyRawAddress);

2. Create an AccountRestrictionTransaction, with restrictionType AllowAddress. Add to the company’s address from the previous step to the allowed list.

// replace with network type
const networkType = NetworkType.TEST_NET;

const transaction = AccountRestrictionTransaction
    .createAddressRestrictionModificationTransaction(
        Deadline.create(),
        AddressRestrictionFlag.AllowIncomingAddress,
        [companyAddress],
        [],
        networkType,
        UInt64.fromUint(2000000));
// replace with network type
const networkType = symbol_sdk_1.NetworkType.TEST_NET;
const transaction = symbol_sdk_1.AccountRestrictionTransaction
    .createAddressRestrictionModificationTransaction(symbol_sdk_1.Deadline.create(), symbol_sdk_1.AddressRestrictionFlag.AllowIncomingAddress, [companyAddress], [], networkType, symbol_sdk_1.UInt64.fromUint(2000000));
  1. Sign and announce the transaction with the product’s account.
// replace with product private key
const productPrivateKey = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF';
const productAccount = Account.createFromPrivateKey(productPrivateKey, networkType);
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash = '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = productAccount.sign(transaction, networkGenerationHash);
// replace with node endpoint
const nodeUrl = 'http://api-01.us-east-1.096x.symboldev.network:3000';
const repositoryFactory = new RepositoryFactoryHttp(nodeUrl);
const transactionHttp = repositoryFactory.createTransactionRepository();

transactionHttp
    .announce(signedTransaction)
    .subscribe((x) => console.log(x), (err) => console.error(err));
// replace with product private key
const productPrivateKey = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF';
const productAccount = symbol_sdk_1.Account.createFromPrivateKey(productPrivateKey, networkType);
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash = '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = productAccount.sign(transaction, networkGenerationHash);
// replace with node endpoint
const nodeUrl = 'http://api-01.us-east-1.096x.symboldev.network:3000';
const repositoryFactory = new symbol_sdk_1.RepositoryFactoryHttp(nodeUrl);
const transactionHttp = repositoryFactory.createTransactionRepository();
transactionHttp
    .announce(signedTransaction)
    .subscribe((x) => console.log(x), (err) => console.error(err));

Now, if you send a TransferTransaction from another account, you will get an error since only TCWYXK-VYBMO4-NBCUF3-AXKJMX-CGVSYQ-OS7ZG2-TLI is allowed to send transactions to the product’s account.

Blocking transactions by mosaic id

Imagine that the account that represents the company owns the following mosaics:

  • company.share: represents shares of the company.
  • company.quality.seal: represents that the product has passed a quality test.
  • company.safety.seal: represents that the product has passed a safety test.

In this case, it might be useful if the product could only receive seals and not company shares.

Thus, you could narrow the type of transactions that the product can receive from the company’s account through the use of negation. Instead of specifically allowing the seals, the product can be set up to block receiving transactions that contain company.share. This is how it can be done:

1. Define the AccountRestrictionModification. Add the mosaic id you want to block to the blocked list.

// replace with mosaic id
const companyShareMosaicIdHex = '7cdf3b117a3c40cc';
const companyShareMosaicId = new MosaicId(companyShareMosaicIdHex);
// replace with mosaic id
const companyShareMosaicIdHex = '7cdf3b117a3c40cc';
const companyShareMosaicId = new symbol_sdk_1.MosaicId(companyShareMosaicIdHex);

2. Create an AccountRestrictionTransaction, with restrictionType BlockMosaic. Add to the array the modification created in the previous step.

// replace with network type
const networkType = NetworkType.TEST_NET;

const transaction = AccountRestrictionTransaction
    .createMosaicRestrictionModificationTransaction(
        Deadline.create(),
        MosaicRestrictionFlag.BlockMosaic,
        [companyShareMosaicId],
        [],
        networkType,
        UInt64.fromUint(2000000));
// replace with network type
const networkType = symbol_sdk_1.NetworkType.TEST_NET;
const transaction = symbol_sdk_1.AccountRestrictionTransaction
    .createMosaicRestrictionModificationTransaction(symbol_sdk_1.Deadline.create(), symbol_sdk_1.MosaicRestrictionFlag.BlockMosaic, [companyShareMosaicId], [], networkType, symbol_sdk_1.UInt64.fromUint(2000000));
  1. Sign and announce the transaction with the product’s account.
// replace with product private key
const productPrivateKey = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF';
const productAccount = Account.createFromPrivateKey(productPrivateKey, networkType);
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash = '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = productAccount.sign(transaction, networkGenerationHash);
// replace with node endpoint
const nodeUrl = 'http://api-01.us-east-1.096x.symboldev.network:3000';
const repositoryFactory = new RepositoryFactoryHttp(nodeUrl);
const transactionHttp = repositoryFactory.createTransactionRepository();

transactionHttp
    .announce(signedTransaction)
    .subscribe((x) => console.log(x), (err) => console.error(err));
// replace with product private key
const productPrivateKey = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF';
const productAccount = symbol_sdk_1.Account.createFromPrivateKey(productPrivateKey, networkType);
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash = '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = productAccount.sign(transaction, networkGenerationHash);
// replace with node endpoint
const nodeUrl = 'http://api-01.us-east-1.096x.symboldev.network:3000';
const repositoryFactory = new symbol_sdk_1.RepositoryFactoryHttp(nodeUrl);
const transactionHttp = repositoryFactory.createTransactionRepository();
transactionHttp
    .announce(signedTransaction)
    .subscribe((x) => console.log(x), (err) => console.error(err));

If the process was successful, the product account can now only receive transactions from the company’s account that does not include any company.share mosaic.

Removing a restriction

After the company sells the product to the final client, they want to remove the condition that only allowed the company’s account to send transactions to the product. The account restrictions can be removed as easily as they were set up:

1. Define the AccountRestrictionModification. Remove from the allowed list the company’s address.

const companyRawAddress = 'TCWYXK-VYBMO4-NBCUF3-AXKJMX-CGVSYQ-OS7ZG2-TLI';
const companyAddress = Address.createFromRawAddress(companyRawAddress);
const companyRawAddress = 'TCWYXK-VYBMO4-NBCUF3-AXKJMX-CGVSYQ-OS7ZG2-TLI';
const companyAddress = symbol_sdk_1.Address.createFromRawAddress(companyRawAddress);
  1. Create an AccountRestrictionTransaction, setting the type AllowAddress. Add as well the modification created.
// replace with network type
const networkType = NetworkType.TEST_NET;

const transaction = AccountRestrictionTransaction
    .createAddressRestrictionModificationTransaction(
        Deadline.create(),
        AddressRestrictionFlag.AllowIncomingAddress,
        [],
        [companyAddress],
        networkType,
        UInt64.fromUint(2000000));
// replace with network type
const networkType = symbol_sdk_1.NetworkType.TEST_NET;
const transaction = symbol_sdk_1.AccountRestrictionTransaction
    .createAddressRestrictionModificationTransaction(symbol_sdk_1.Deadline.create(), symbol_sdk_1.AddressRestrictionFlag.AllowIncomingAddress, [], [companyAddress], networkType, symbol_sdk_1.UInt64.fromUint(2000000));
  1. Sign and announce the transaction with the product’s account.
// replace with product private key
const productPrivateKey = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF';
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash = '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const productAccount = Account.createFromPrivateKey(productPrivateKey, networkType);
const signedTransaction = productAccount.sign(transaction, networkGenerationHash);
// replace with node endpoint
const nodeUrl = 'http://api-01.us-east-1.096x.symboldev.network:3000';
const repositoryFactory = new RepositoryFactoryHttp(nodeUrl);
const transactionHttp = repositoryFactory.createTransactionRepository();

transactionHttp
    .announce(signedTransaction)
    .subscribe((x) => console.log(x), (err) => console.error(err));
// replace with product private key
const productPrivateKey = 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF';
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash = '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const productAccount = symbol_sdk_1.Account.createFromPrivateKey(productPrivateKey, networkType);
const signedTransaction = productAccount.sign(transaction, networkGenerationHash);
// replace with node endpoint
const nodeUrl = 'http://api-01.us-east-1.096x.symboldev.network:3000';
const repositoryFactory = new symbol_sdk_1.RepositoryFactoryHttp(nodeUrl);
const transactionHttp = repositoryFactory.createTransactionRepository();
transactionHttp
    .announce(signedTransaction)
    .subscribe((x) => console.log(x), (err) => console.error(err));

After the transaction gets confirmed, you should be able to send transactions from any account to the product’s account again.