Skip to content

#87_Writing_Smart_Filters

AhmadBAmzah edited this page Apr 12, 2019 · 8 revisions

Spike #87 - Writing a Smart Filter

Ahmad Bin Amzah – 11/04/2019

Goals / Deliverables

  • Basics of Smart Filters
  • Examples
  • Deploy filters to blockchain

Technologies, Tools and Resources Used

  • Documentation

What we found out

MultiChain Smart Filters are written in JavaScript, embedded within the blockchain to allow customs rules of the validity of transaction or stream items. MultiChain supports two types of smart filters;

  • Transaction filters - Rules defining the validity of transactions by examining a transaction's inputs, outputs and metadata. Transactions that do not pass the filter will be rejected by all nodes on the blockchain.
  • Stream filters - Rules defining the validity of stream items by examining its data as well as its publishers and keys. Stream items that do not pass through the filter cannot be published, and will have their data hidden and flagged with an error.

Smart Filter Functions

Smart filters are written with functions with fixed names that will obtain all the information required by using callbacks rather than function parameters. Some callbacks, such as getlasblockinfo() and verifypermission(), are shared with the node's JSON-RPC API. Other callbacks such as getfiltertransaction(), getfilterstreamitem() and getfiltertxinput() are used only within filters. A full list of callbacks that used by smart filters are listed in the "Smart Filter Callbacks" section of the Smart filter documentation. If a filter does not allow a transaction or a stream item, it should return a non-empty string containing an explanation for the rejection. If a filter function does not return anything other than a non-empty string, or completes return no value at all, then the transaction or item is accepted.

If a callback function is called with invalid parameters, it will return the undefined JavaScript value.

Creating Transaction Filters

Below is an example of a transaction filter which rejects a transaction with less than one output;

function filtertransaction()
{
    var tx=getfiltertransaction();
    if (tx.vout.length<1)
        return "At least one output required";
}

From this example, it demonstrates a few key principles of transaction filters;

  1. They must define a transaction function with the name filtertransaction().
  2. Information is obtained via callbacks getfiltertransaction().
  3. For a transaction to be rejected, a non-empty string needs to be returned return "At least one output required";.

More examples are available here.

testtxfilter API command helps in the development of transaction filters, before they are deloyed to the chain. Their behaviour can be tested by passing the hexidecimal for a transaction or the txid of a past transaction. However filter code using getfiltertxinput() or getfilterassetbalances() can only be tested on new and not-yet sent transaction. These can be sent via raw transactions using createrawsendfrom with sign passed in the action parameter. See more about raw transactions.

Once transaction filters are ready for deployment, use create txfilter to add to the blockchain. This can only be done by addresses with admin or create permissions. Created transaction filters can be restricted to certain transactions referencing assets and/or streams. Filters can be queried via listtxfilters and getfiltercode commands, and tested using runtxfilter.

Transaction filters can be approved and activated by administrators via the approvefrom command. A sufficient number of admins need to approve in order for a filter to be deployed. The number is determined by the admin-consensus-txfilter blockchain parameter multiplied by the number of addresses with admin permissions.

To deactivate a filter, it should be approved by a suffiecient number of admins using the approvefrom command, but passing false instead of true in the third parameter.

For more information about these commands mentioned see API documentation here.

Creating Stream Filters

Below is an example of a stream filter which rejects items with less that two keys;

function filterstreamitem()
{
    var item=getfilterstreamitem();
    if (item.keys.length<2)
        return "At least two keys required";
}

From this example, it demonstrates a few key principles of stream filters;

  1. They must define a stream function with the name filterstreamitem().
  2. Information is obtained via callbacks getfilterstreamitem().
  3. For an item to be rejected, a non-empty string needs to be returned return "At least two keys required";.

More examples are available here.

Similar to testtxfilter to test transaction filters, stream filters can be tested before deployment via teststreamfilter passing a hexidecimal item's transaction or the pass publish transaction txid. If a transaction contains multiple stream items, also pass the index of the output containing the item.

create streamfilter is used to add the filter to the blockchain. This can only be done by addresses with create permissions. Filters can be queried via liststreamfilters and getfiltercode, and tested using runstreamfilter.

Approval is required to activate the filter on a stream using approvefrom by addresses with admin permissions for the stream. This is achieved by passing {"for":"stream1", "approve":true} structure in the third parameter to approvefrom.

To deactivate a stream filter, applying the same process as approvefrom but with "approve":false instead.

Smart Filter Determinism

Certain JavaScript features are omitted from filter code includes;

  • Checking the time - Date.now() returns as undefined
  • Random numbers - Math.random()returns as undefined
  • Complex math functions - Math.sin() and many others returns as undefined
  • External services - No access to network, disk or other processes
  • Multithreading - all code execute sequentially

Additionally, each filter uses a seperate JavaScript contexts, with a new context created for each filter at the start of every block.

Smart Filter Timeouts

Badly written filters can result into infinite loops. MultiChain offers several layers of infinite loop protection;

  • Filter approvals - Every filter must be approved before it becomes active, by the blockchain or stream admins as appropiate. getfiltercode can be used to review the code first.
  • Timeout on send - If a filter runs longer than the node's specified sendfiltertimeout runtime parameter (in miliseconds) upon sending the transaction, its execution will be aborted, the transaction will not be sent and an error will be returned.
  • Timeout on accept - If a filter runs longer than the node's specified acceptfiltertimeout runtime parameter (in miliseconds) upon accepting the transaction, its execution will be aborted and the transaction will not be accepted into the node's memory pool.
  • Timeout on stream retrieve - If a filter runs longer than the node's specified acceptfiltertimeout runtime parameter (in miliseconds) upon retrieving a item, its execution will be aborted and the item is flagged with an error.

Smart Filter Considerations

  • Custom permissions - MultiChain 2.0 supports six global custom permissions where it can be used to assign roles to addresses, and then check those roles in smart filters. high1, high2 and high3 can be set with admin permissions, and low1, low2 and low3 can be set with admin and activate permissions.
  • Validating stream items - Either transaction or stream filters can be used to validate stream items, with different advantages and disadvantages. Transaction filters ensure invalid stream items do not enter the blockchain, however at the cost of requiring every node to apply the filer. A stream filter only applies to nodes subscribe to the stream, but cannot prevent malicious nodes from publishing invalid data.
  • Checking the time - Filters cannot query the time directly. An approximation of the current time is given be the time field of getlastblockinfo(), so as long as blocks are being continually generated by multiple nodes, this wil be accurate within a few minutes at worst.
  • Inline metadata - MultiChain 2.0 allows metadata to be included inside transaction outputs containing assets. Inline metadata can be used in conjunction with transaction filters to place special restrictions on assets such as who can spend the asset, etc. Can be shown via getfiltertransaction() and getfiltertxinput().
  • Rescuin filter disasters - Transactions generated by approvefrom command which disapproves a filter will bypass all transaction filters active on the chain in the attempt to provide an option in the event that a buggy filter is blocking all transactions.
  • Integers preferred - It is recommended to use integers instead of floating points for calculations, without depending on how MultiChain might implement JavaScript floating point calculations in future protocols to allow for precision of calculations.

Deploy Smart Filter Tutorial

Below are a set of multichain-cli commands to demostrate the creation and application of filters;

  1. Run listpermissions admin and copy the displayed address.
  2. Create a transaction filter:
  • create txfilter filter1 '{}' 'function filtertransaction() { var tx=getfiltertransaction(); if (tx.create) return "Stream creation temporarily disabled"; }'
  1. See filter listed, retrieve its code and approve it using an admin address:
  • listtxfilters
  • getfiltercode filter1
  • approvefrom [admin address] filter1 true
  1. Create a stream that the filter blocks:
  • createfrom [admin address] stream stream1 true
  1. Error should be shown. Disable the filter and create a stream again:
  • approvefrom [admin address] filter1 false
  • createfrom [admin address] stream stream1 true
  1. Create a stream filter:
  • create streamfilter filter2 '{}' 'function filterstreamitem() { var item=getfilterstreamitem(); if (item.keys.length<2) return "At least two keys required"; }'
  1. See filter listed, approve it, and see its status:
  • liststreamfilters
  • approvefrom [admin address] filter2 '{"for":"stream1","approve":true}'
  • liststreams stream1 true
  1. Attempt to publish a stream item with only one key:
  • publish stream1 key1 012345
  1. Error should be shown. Disable the filter and publish the stream item again:
  • approvefrom [admin address] filter2 '{"for":"stream1","approve":false}'
  • publish stream1 key1 012345

Clone this wiki locally