Transaction

A transaction generally represents a unit of work within a database system. In the case of blockchain, that is when an action signed by an account changes its state.

Transaction types

Symbol supports many different transaction types. For example, there are transactions to transfer mosaics between accounts, messages or configure the ownership of accounts (including the use of multisig rules), and more.

The following transaction types are included in Symbol based networks by default:

TransactionType

Size: 2 bytes = 0x2
schema

Enumeration of Transaction types

0x414c
ACCOUNT_KEY_LINK
0x424c
NODE_KEY_LINK
0x4141
AGGREGATE_COMPLETE
0x4241
AGGREGATE_BONDED
0x4143
VOTING_KEY_LINK
0x4243
VRF_KEY_LINK
0x4148
HASH_LOCK
0x4152
SECRET_LOCK
0x4252
SECRET_PROOF
0x4144
ACCOUNT_METADATA
0x4244
MOSAIC_METADATA
0x4344
NAMESPACE_METADATA
0x414d
MOSAIC_DEFINITION
0x424d
MOSAIC_SUPPLY_CHANGE
0x434d
MOSAIC_SUPPLY_REVOCATION
0x4155
MULTISIG_ACCOUNT_MODIFICATION
0x424e
ADDRESS_ALIAS
0x434e
MOSAIC_ALIAS
0x414e
NAMESPACE_REGISTRATION
0x4150
ACCOUNT_ADDRESS_RESTRICTION
0x4250
ACCOUNT_MOSAIC_RESTRICTION
0x4350
ACCOUNT_OPERATION_RESTRICTION
0x4251
MOSAIC_ADDRESS_RESTRICTION
0x4151
MOSAIC_GLOBAL_RESTRICTION
0x4154
TRANSFER

Every base transaction type available in Symbol is defined as a separate plugin. The plugin approach allows developers to introduce new transaction types without modifying the core engine or disrupting other features.

Defining a transaction

Transactions are defined in a serialized form. Every transaction extends from the base Transaction schema, adding the type’s particular properties.

All transactions should define a deadline and a max_fee:

  • deadline: A transaction has a time window to be accepted before it reaches its deadline. The transaction expires when the deadline is reached and all the nodes reject the transaction. By default, the SDK sets the deadline to 2 hours, but it can be extended up to 6 hours (or 48 for Aggregate bonded transactions).

  • max_fee: The maximum amount of network currency that the sender of the transaction is willing to pay to get the transaction accepted. The next documentation shows you how to set the optimal max_fee value.

Note

The catbuffer schemas repository defines how each transaction type should be serialized. In combination with the catbuffer generators, developers can generate builder classes for a given set of programming languages.

We recommend using the SDK to define new transactions.

  // replace with recipient address
  const rawAddress = 'TB6Q5E-YACWBP-CXKGIL-I6XWCH-DRFLTB-KUK34I-YJQ';
  const recipientAddress = Address.createFromRawAddress(rawAddress);

  const transferTransaction = TransferTransaction.create(
    Deadline.create(epochAdjustment),
    recipientAddress,
    [currency.createRelative(10)],
    PlainMessage.create('This is a test message'),
    networkType,
    UInt64.fromUint(2000000),
  );

Signing a transaction

Accounts must sign transactions before announcing them to the network. Signing a transaction expresses the account’s agreement to change the network state as defined.

For example, a TransferTransaction describes who the recipient is and the number of mosaics to transfer. In this case, signing the transaction means to accept moving those mosaics from one account’s balance to another.

An account has to follow the next steps to sign a transaction:

  1. Get the signing bytes, which are all the bytes of the transaction except the size, signature, and signer.

  2. Get the nemesis block generation hash. You can query NODE_URL /node/info' and copy meta.networkGenerationHash value.

  3. Prepend the nemesis block generation hash to the signing bytes.

  4. Sign the resulting string with the signer’s private key. This will give you the transaction signature.

  5. Append the signer’s signature and public key to the transaction to obtain the payload.

  6. Calculate the transaction hash by applying SHA3-512 hashing algorithm to the first 32 bytes of signature, the signer public key, nemesis block generation hash, and the remaining transaction payload.

  // replace with sender private key
  const privateKey =
    '1111111111111111111111111111111111111111111111111111111111111111';
  const account = Account.createFromPrivateKey(privateKey, networkType);
  const signedTransaction = account.sign(
    transferTransaction,
    networkGenerationHash,
  );
  console.log('Payload:', signedTransaction.payload);
  console.log('Transaction Hash:', signedTransaction.hash);

Announcing a transaction

Signed transactions are ready to be announced to the network. You can either use the SDK TransactionHttp service or append the payload to the request of the transaction endpoint.

  const transactionRepository = repositoryFactory.createTransactionRepository();
  const response = await transactionRepository
    .announce(signedTransaction)
    .toPromise();
  console.log(response);
curl -X PUT -H "Content-type: application/json" -d '{"payload":"B3000000F77A8DCFCB57B81F9BE5B46738F7132998F55123BFF4D89DC8E5CAE1F071A040E5571F4D8DA125B243C785DA5261F878E3DE898815F6E8F12A2C0A5F0A9C3504FA6249E8334E3F83E972461125504AFFD3E7750AFBB3371E7B2D22A599A3D0E3039054410000000000000000265DEE3F1700000090FA39EC47E05600AFA74308A7EA607D145E371B5F4F1447BC0F00010057656C636F6D6520546F204E454D44B262C46CEABB858096980000000000"}' http://localhost:3000/transaction

After announcing the transaction, the REST API will always return an OK response immediately. At this point, it is still unknown whether the transaction is valid.

../_images/transaction-cycle.png

Transaction cycle

Validation

The first stage of validation happens in the API nodes. If the transaction encounters an error, the WebSocket throws a notification through the status channel. If not, the transaction reaches the P2P network with an unconfirmed status. In this state, it is not yet clear if the transaction will get included in a block. Thus, an unconfirmed transaction should never be relied upon.

The second validation happens before the transaction is added in a harvested block. If successful, the harvester stores the transaction in a block and the transaction reaches the confirmed status. At this state, the transaction is officially recorded in the blockchain ledger, but has not yet reached finality.

Under certain circumstances, such as a network failure or partition, the most recently confirmed blocks can be rolled back. Hence, confirmed transactions that have not been finalized are recognized by the network but are not immutable because they can still be reversed.

For a block to be immutable, it needs to complete the finalization process. Once a block is finalized, the block and the included transactions are permanently recorded on the blockchain ledger.

Spam Throttle

The node’s cache holds unconfirmed transactions until they can be included in a block. Since cache is a valuable resource, Symbol implements a spam throttle that prevents an attacker from filling the cache with unconfirmed transactions while still letting honest actors successfully submit new unconfirmed transactions.

The spam throttle controls the amount of unconfirmed transactions accounts can submit by calculating the fair share of cache for each account relative to its importance score. If an account has surpassed its fair share of the cache and the node cache contains more unconfirmed transactions than the amount that can be included in a single block, the transaction will be rejected. This effectively blocks malicious actors from spamming a node with transactions while allowing other users to continue using the node normally.