Yes, in Substrate, transactions can generally be thought of as any piece of data that's intended for the transaction pool (and consequently, could potentially be included in a block). But there are technical nuances needed to be spelled out: the term "transaction" is in fact quite misleading, although does have merit from a user friendliness perspective (yes, technically extrinsic pool would be more accurate).
Extrinsics (a.k.a. Types of Transactions)
In Substrate, a "thing that could exist in the transaction pool" — more commonly known as "a transaction" in the non-Substrate world — can be one of 3 distinct types, all of which fall under a broader category called "extrinsics". Extrinsics is just a general term to mean "any information that originates from outside a runtime". These are:
- Signed transactions: these must contain the signature of the account sending the inbound request to the runtime.
With signed transactions, the account used to submit the request typically pays a transaction fee and must sign it using the account's private key.
- Unsigned transactions: these don't carry any information about who submitted the transaction, since the format of this type of transaction doesn't require a signature.
You can define what conditions must be met for such a transaction to be valid (FRAME has an attribute macro to do this).
- Inherents: are a special type of unsigned transaction made by block authors which carry information required to build a block such as timestamps, storage proofs and uncle blocks.
Key differences
Here are some key differences between the different types of extrinsics:
- Contrary to signed transactions, unsigned transaction types require implementing custom validation logic which can consume more resources for checking validity compared to signed transactions.
- Unsigned transactions have no economic deterrent to prevent spam or replay attacks, so custom logic must account for protecting the network from these types of transactions being misused.
- Inherents exist to address the need of adding some data to a block, whereas signed or unsigned transactions exist to potentially change the state of the blockchain.
Examples
Here are some examples of each type of extrinsic being used in different scenarios:
Scenario |
Function call example |
Type |
Reason |
Bob wants to send some tokens to Alice and tip the block author to give this transaction more priority, using the Balances pallet. |
pallet_balances::Call::transfer in Polkadot |
Signed transaction |
This function can be called by any account, so we must ensure that the caller signs the transaction and pays a fee for it to be processed. |
Charlie proposes a tip to reward Dave using the Tipping pallet. |
pallet_tips::Call::report_awesome in Polkadot |
Signed transaction |
This function is designed so that any account can call it, by depositing some amount and giving a reason for the tip which will be stored on-chain. |
The on-chain council passes some motion that includes submitting a batch of valid transactions to be executed. |
pallet_utility::Call::batch in Polkadot |
Unsigned transaction |
This type of extrinsic can only be submitted if a majority of the council approves it and cannot be executed by any single account. |
A validator node sends a signal to the network to indicate it's online. |
pallet_im_online::Call::heartbeat in Kusama |
Unsigned transaction |
This can only be called by a node that's registered as a validator in the network, which is verified as part of an internal check of the function's logic and allows the node to call it without paying any fees. |
The network wants block authoring nodes to include the current timestamp into the blocks they produce. |
pallet_timestamp::Call::now in Kusama |
Inherent |
This is a special extrinsic that can only be included in a block by an authoring node. |
A parachain needs to send its relay chain the validation data the relay chain expects. |
paras_inherent::Call::enter in Kusama |
Inherent |
This is a special type of extrinsic that can only be sent by a collator node. |