Skip to content

Latest commit

 

History

History
732 lines (574 loc) · 28.4 KB

File metadata and controls

732 lines (574 loc) · 28.4 KB

Gas Station Server Access Controller

The Gas Station Server includes an Access Controller mechanism to manage access to the /execute_tx endpoint. This feature allows you to implement filtering logic based on properties derived from transactions. Currently, the Access Controller supports filtering based on the sender's address, enabling you to block or allow specific addresses.

Access Controller Rule syntax

parameter mandatory possible values
sender-address yes "0x0000...", ["0x0000..", "0x1111..."], "*"
transaction-gas-budget no "=100", "<100", "<=100", ">100", ">=100", "!=100"
move-call-package-address no "0x0000...", ["0x0000...", "0x1111..."], "*"
ptb-command-count no "=10", "<10", "<=10", ">10", ">=10", "!=10"
action yes allow, deny, Hook Server URL
gas-usage no See Gas Usage Filter
rego-expression no See Gas Rego Expression

Access Controller Examples

  • Disable All Requests and Allow Only a Specific Address

    The following configuration denies all incoming transactions except for move calls to package (0x0202....) originating from the specified sender address (0x0101....):

    access-controller:
       access-policy: deny-all
       rules:
          - sender-address: "0x0101010101010101010101010101010101010101010101010101010101010101"
            move-call-package-address: "0x0202020202020202020202020202020202020202020202020202020202020202"
            action: allow # allowed actions: `allow`, `deny`, a hook url (see "Hook Server" section)

  • Enables All Requests and Deny Only a Specific Address

    The following configuration allows all incoming transactions except those from the specified sender address (0x0101...):

    access-controller:
       access-policy: allow-all
       rules:
          - sender-address: "0x0101010101010101010101010101010101010101010101010101010101010101"
            action: deny

  • Gas Budget Constraints

    The following configuration denies all incoming transactions except those from the specified sender address (0x0101...) and the transaction gas budget below the limit 1000000

    access-controller:
       access-policy: deny-all
       rules:
          - sender-address: "0x0101010101010101010101010101010101010101010101010101010101010101"
            transaction-gas-budget: "<1000000" # allowed operators: =, !=, <, >, <=, >=
            action: allow

  • Advanced budgeting management

    The following configuration accept all incoming transactions with gas budget below 500000. For address sender address (0x0101...) the allowed gas budget is increased to 1000000

    access-controller:
       access-policy: deny-all
       rules:
          - sender-address: "0x0101010101010101010101010101010101010101010101010101010101010101"
            transaction-gas-budget: "<=10000000"
            action: allow
    
          - sender-address: "*"
            transaction-gas-budget: "<500000"
            action: allow

  • Programmable Transaction Command Count Limits

    To avoid users submitting transactions blocks with a large number of transactions, limits for the commands in the programmable transaction can be configured. In the following example, the sender may only submit up to one command in the programmable transaction.

    Note that this rule condition is only applied to transactions, that include a programmable transaction and will be ignored for other transaction kinds.

    access-controller:
       access-policy: deny-all
       rules:
          - sender-address: "0x0101010101010101010101010101010101010101010101010101010101010101"
            ptb-command-count: "<=1" # allowed operators: =, !=, <, >, <=, >=
            action: allow

Rego Expression Filter

The Rego Expression Filter allows you to evaluate incoming transaction payloads against custom logic by using the Rego language. This gives you the flexibility to check properties like the sender address or any other field available in the transaction data.

Rego Expression Input Payload

Below is an example JSON payload against which a Rego rule is evaluated:

{
  "http_headers" : {
    "content-type":  [ "application/json"]
  },

  "transaction_data": {
    "V1": {
      "kind": {
        "ProgrammableTransaction": {
          "inputs": [
            {
              "Pure": [
                5,
                104,
                101,
                108,
                108,
                111
              ]
            }
          ],
          "commands": [
            {
              "MoveCall": {
                "package": "0xb674e2ed79db3c25fa4c00d5c7d62a9c18089e1fc4c2de5b5ee8b2836a85ae26",
                "module": "allowed_module_name",
                "function": "allowed_module_function",
                "type_arguments": [],
                "arguments": [
                  {
                    "Input": 0
                  }
                ]
              }
            }
          ]
        }
      },
      "sender": "0xa2e17e20f97355af6491580ff5c11ecefcdcf76ea224d163e5cb92389adf2311",
      "gas_data": {
        "payment": [
          [
            "0x1369ab7cca1c229d093060d66666db7c0db1edf43310d5e56acec2dafa492ffb",
            9521034,
            "9knpRtwCc9LK1BCJ222KZwgr9ZVHDyZhiLUTPt82FtmV"
          ],
          [
            "0x1932fbdf314cb263f4d2a00656144e0951edd99fe5489b07b799b7087f8de20d",
            9521055,
            "BAubkJuEwD1PRGL1CLBRjv3GCP83p93J7ZAdSsdVtifk"
          ],
          [
            "0x19d732e1b95c8b6af724ba026f8f61e4b72b9931777a0e1d0b8d3054de1b2ef4",
            9521050,
            "GRyLJcRSepVk3r9Fjdmv9MxauX6mrzYVptAhPwzVRu61"
          ],
          [
            "0x1a5f7fc0f977a3020f8c5bd30c635fadab9b1ea0003e8255d90ec9ee48ff09ea",
            9521042,
            "BbrFR6tykrnKzskVJRcBJbWfDRcCeBqMweotE4bXVXLo"
          ],
          [
            "0x1d02df222b5149497a4d722514bf33a8bf755011724cdd3ced7ecb174d833690",
            9521046,
            "DcwkhBh2sdw8yJW1SH4tv5mVq6Ec7z4EmAaRL2H3AS27"
          ],
          [
            "0x1d8bf3742256d434ae9f1305eab214c5af458539c293ee51a25af8826b692589",
            9521046,
            "GG9mzv9JpXvAMeAC2FWPMXSqWqyTJiqeFepwLAzEpKgL"
          ],
          [
            "0x1e878332e7fa2e6fa6120a395bc8ad207ba5f5fa7cc8359ae4b56da20b50ff54",
            9521056,
            "GrK2met5oxjdVP3qJuXtNAs7hkajFFUwbe512LVxBBmW"
          ],
          [
            "0x1ed6d2105135371d901d24e4acd6fd9025b24d4d3d75277f2ad059825a5e5b38",
            9521040,
            "DJurdiKP82LVADxNrxsYnYJW9aCcDP1bxLbNbdcvdMHV"
          ],
          [
            "0x1f551af1739e258b5ed17c48e348c494de7598dc2a42aa2d5c0f284589de61e7",
            9521037,
            "4rpZtAY2eC2YyAJiDcDfq1BbCnV4sPKQQBU9rGCyTpmD"
          ],
          [
            "0x1fcf200bc9b970c9c877ddadddf28cdff19165c246914444b2968c7287587e8a",
            9521056,
            "5DDvPhXHRzmjdT9FRNqYe5T5LwkBpov2XKRpnNevhMJf"
          ]
        ],
        "owner": "0x27147325dafdae103c7e8f09a82654ae7a4654c3042e1e278187013065be47b7",
        "price": 1000,
        "budget": 3000000
      },
      "expiration": "None"
    }
  }
}

Rego Filtering Code Example

The following Rego expression validates that only a specific move call can be sponsored by the Gas Station.

The expression does the following:

  1. Extract the Commands Array: Retrieve the array of commands from the payload.

  2. Restrict to a Single Command: Ensure that only one command is allowed to prevent piggybacking, where the sender might attach an additional unauthorized move-call to another contract.

  3. Verify Expected Fields: Confirm that the package, module, and function fields match the expected values.

  4. Decodes the First Argument: Checks that the first argument of the move-call, when decoded as a string using bcs.decode_typed, equals "hello".

package matchers

default move_call_matches = false

move_call_matches if {
    cmds := input.transaction_data.V1.kind.ProgrammableTransaction.commands
    count(cmds) == 1


    mc := cmds[0].MoveCall
    mc["package"]  == "0xb674e2ed79db3c25fa4c00d5c7d62a9c18089e1fc4c2de5b5ee8b2836a85ae26"
    mc.module   == "allowed_module_name"
    mc.function == "allowed_function_name"

    argv_1 := input.transaction_data.V1.kind.ProgrammableTransaction.inputs[0].Pure
    bcs.decode_typed(argv_1, "string") == "hello"
}

Note: All field addresses in the Rego expression should begin with input.

Note: For full Rego syntax details please see the Reference.

Rego extensions

bcs.decode_typed

The helper function bcs.decode_typed enables decoding of bytes from BCS-serialized data. This function is particularly useful when you need to evaluate input parameters that are provided in byte format. Since BCS-encoded data cannot always be reliably reversed without knowing the precise type, you must specify the type to decode before invoking the function.

Usage Example:

output := bcs.decode(data_bytes, data_type)

Supported Data Types:

Data Type
string
u8
u16
u32
u64
address
bool
vector_string
vector_u8
vector_u16
vector_u32
vector_u64
vector_address
vector_bool

Rego Expression Sources

The Rego expressions may come from different sources: file, redis and http.

Rego from File

access-controller:
  access-policy: deny-all
  rules:
    - sender-address: "*"
      rego-expression:
        location-type: file
        path: ./source_file.rego
        rego-rule-path: data.matchers.move_call_matches
      action: allow

Rego from Redis

access-controller:
  access-policy: allow-all
  rules:
    - sender-address: "*"
    rego-expression:
        location-type: redis
        url: "redis://localhost"
        redis-key: "source.rego"
        rego-rule-path: data.matchers.move_call_matches
      action: allow

Rego from HTTP

access-controller:
  access-policy: allow-all
  rules:
    - sender-address: "*"
      rego-expression:
        location-type: http
        url: "http://localhost:8080/source.rego"
        rego-rule-path: data.matchers.move_call_matches
      action: deny

Gas Usage Filter

The Gas Usage Limit feature enables you to track gas consumption based on predefined parameters. When enabled, the gas tracking applies to the entire rule. The configuration syntax is:

gas-usage:
  value: [range_of_numbers]
  window: [duration]
  count-by: [ sender-address, http-header::header_name ] # optional

Note: The syntax of duration follows the specification used in the humantime crate

Gas Usage Examples

Below are two examples that demonstrate how to enforce gas usage limits.


1. Limit Gas Usage per Address

This configuration sets a daily gas usage limit for a specific address. In the example below, the sender at address 0x0101... is restricted to a maximum daily usage of 10000000 gas units.

The time window is reset not on a daily reset time, but 24 hours (as configured below) after the first transaction of sender at address 0x0101..., allowing to have flexible usage based scheduling across different time zones.

Note that gas usage for other addresses remains unconstrained.

access-controller:
  access-policy: deny-all
  rules:
    - sender-address: "0x0101010101010101010101010101010101010101010101010101010101010101"
      gas-usage:
        window: 1day
        value: ">1000000"
      action: deny

    - sender-address: "*"
      action: allow

2. Limit Gas Usage per Address and Module

In this example, the configuration restricts the daily gas usage for a specific address when calling a designated module. The sender at address 0x0101... is limited to a maximum daily usage of 10000000 gas units when accessing package 0x0202.... For all other interactions, gas usage is blocked.

access-controller:
  access-policy: deny-all
  rules:
    - sender-address: "0x0101010101010101010101010101010101010101010101010101010101010101"
      move-call-package-address: "0x0202020202020202020202020202020202020202020202020202020202020202"
      gas-usage:
        value: "<1000000"
        window: 1day
      action: allow

3. Limit Gas Usage per Address

In this example, each user is limited to 10000000 gas units per day. The additional property count-by allows you to maintain individual counters for each sender-address. Without count-by, all users would share a daily limit of 1000000 gas units.

access-controller:
  access-policy: deny-all
  rules:
    - sender-address: "*"
      gas-usage:
        value: "<1000000"
        window: 1day
        count-by: [ sender-address ]
      action: allow

4. Limit Gas Usage per HTTP Header

When implementing multi-tenant or account-based gas station usage, you can track gas consumption per account using HTTP headers. This allows each account to have its own gas usage limit, even when using the same sender address.

The account ID is passed via an HTTP header (e.g., X-Account-Id) by your account management system:

POST /v1/execute_tx
X-Account-Id: 123
Content-Type: application/json

{
  "transaction": "..."
}

Configure the access controller to count gas usage separately for each unique header value:

access-controller:
  access-policy: deny-all
  rules:
    - sender-address: "*"
      gas-usage:
        value: "<1000000"
        window: 1day
        count-by: [ http-header::x-account-id ]
      action: allow

This configuration ensures that, each unique X-Account-Id value gets its own 1,000,000 gas limit per day

Hook Server

An external server (a hook), that decides whether a transaction should be executed or not can be configured. The hook receives the same input as the gas station allowing to parse inspect the transaction the same way, as the gas station does.

Hook server(s) can be configured as a term in the access controller rules, allowing to integrate hooks into existing rule sets or replacing the gas station built in access controller by a using hook only configuration.

Hooks are configured as values for the "action" keyword, by setting the action value to a URL or a mapping with URL and headers instead of allow/deny.

Hooks are the last thing that is called in an access controller rule (just before the gas usage check due to safety reasons). This reduces the amount of calls against a hook server and leads to a few possible scenarios as shown below.

flowchart TD
    Start(rule with hook<br>is processed)
    CallHook(call<br>hook)
    IgnoreHook(ignore<br>hook)
    AllowTx(allow tx)
    DenyTx(deny tx)
    CheckNextRule(check<br>next<br>rule)
    CheckPreviousTerm{previous<br>terms<br>apply}
    CheckResponse{process<br>response}

    Start --> CheckPreviousTerms
    CheckPreviousTerms -->|yes| CallHook
    CheckPreviousTerms -->|no| IgnoreHook

    IgnoreHook --> CheckNextRule

    CallHook --> CheckResponse
    CheckResponse -->|allow| AllowTx
    CheckResponse -->|deny| DenyTx
    CheckResponse -->|noDecision| CheckNextRule
Loading

A hook server has to follow the api spec defined here. Also, an example server that can be used as a starting point for an own hook can be found in our examples.


  • Hook only configuration

Having a single rule with a hook action replaces the access controller completely:

access-controller:
  access-policy: deny-all
  rules:
    - sender-address: "*"
      action: "http://127.0.0.1:8080"

or even shorter:

access-controller:
  access-policy: deny-all
  rules:
    - action: "http://127.0.0.1:8080"

It is also possible to pass header values to be used in the request against the hook, e.g. if you want to use an authentication header. For example:

access-controller:
  access-policy: deny-all
  rules:
    - action:
        url: "http://example.org/"
        headers:
          authorization:
            - Bearer an-auth-token-for-a-hook
          other_header:
            - value1
            - value2

As you usually might want to reduce the number of calls against the hook a bit, you can already apply access controller logic before the hook is called (all other terms except gas-usage are checked before the hook call). To do so, add terms as documented above, for example:

   access-controller:
      access-policy: deny-all
      rules:
        - sender-address: "0x0101010101010101010101010101010101010101010101010101010101010101"
          transaction-gas-budget: "<1000000" # allowed operators: =, !=, <, >, <=, >=
          action: "http://127.0.0.1:8080"

Hook actions don't have to be used as standalone rules and can integrate seamlessly with other rules, e.g.

   access-controller:
      access-policy: deny-all
      rules:
        - sender-address: "0x0101010101010101010101010101010101010101010101010101010101010101"
          transaction-gas-budget: "<1000000"
          action: allow
        - action: "http://127.0.0.1:8080"
        - sender-address: "*"
          gas-usage:
            value: "<1000000"
            window: 1day
            count-by: [ sender-address ]

As this might look a bit confusing, let's break this one down:

  • we have a privileged address 0x0101010101010101010101010101010101010101010101010101010101010101, that can send transaction below a certain threshold
  • other addresses or larger transactions by the privileged address have to go through a hook check
  • the hook can then react with:
    • allowing the transaction
    • denying the transaction
    • letting the next rule decide if the transaction should be executed or not
  • assuming, the hook decides not to decide about the transaction, we would now check the sender address based gas usage and decide based on this if the transaction is executed or not

Debugging Access Controller Filtering

The Access Controller engine, which is part of the gas station, enables you to define complex filtering and limiting logic. As rule complexity increases, the likelihood of unintended behavior increases. To help diagnose and prevent such issues, the gas station provides a tracking mechanism that allows you to inspect exactly what happens when a transaction is processed by the access controller.

Prerequisites:

Simple Example

Let's start with a very simple rule and assume you want to enable access for a selected address:

access-controller:
  access-policy: deny-all
  rules:
    - sender-address: ["0xa2e17e20f97355af6491580ff5c11ecefcdcf76ea224d163e5cb92389adf2311"]
      action: allow

This configuration uses a whitelisting strategy: the default policy denies all transactions, and we define a rule that allows transactions from a specific address.

When you call the /execute_tx endpoint with a transaction from the allowed sender address (0xa2e17e20f97355af6491580ff5c11ecefcdcf76ea224d163e5cb92389adf2311), you should see the following output:

2025-09-28T13:51:33.092979Z TRACE iota_gas_station::access_controller:
=================================
Access Report for transaction:
{
  "V1": {
    "kind": {
      "ProgrammableTransaction": {
        "commands": []
      }
    },
    "sender": "0xa2e17e20f97355af6491580ff5c11ecefcdcf76ea224d163e5cb92389adf2311",
    ...
  }
}
Rule 1:
        sender_address                 : [MATCHED] : sender address 0xa2e17e20f97355af6491580ff5c11ecefcdcf76ea224d163e5cb92389adf2311 is in the list: 'List([0xa2e17e20f97355af6491580ff5c11ecefcdcf76ea224d163e5cb92389adf2311])'
        gas_budget                     : [MATCHED] : gas budget is not defined
        move_call_package_address      : [MATCHED] : move call package address is not defined
        ptb_command_count              : [MATCHED] : ptb command count is not defined
        rego_expression                : [MATCHED] : rego expression is not defined
        gas_usage                      : [MATCHED] : gas usage is not defined
        ---> Action applied: ✅ Allow
🟢 Allow

The output contains two sections:

  • A dump of the transaction being evaluated by the access controller
  • Detailed execution results for each rule processed by the access controller engine

Since we have only one rule, only one rule is shown in the output. As you can see, the rule displays all available predicates, including those you didn't explicitly define in the configuration. This is because an undefined predicate in a rule means "accept everything" for that predicate. All possible predicates that can be added to a rule are listed, including the sender-address that we explicitly defined.

Predicates act as matchers: all predicates in a rule must match for the rule's action to be applied. Since all predicates matched in this case, the allow action is applied and the transaction will be accepted by the access controller.

It's important to note that the order of printed predicates reflects the order in which they are executed by the access controller engine.

Multiple rules

Next, let's examine what happens when the sender address does not match the allowed list:

2025-09-28T13:51:33.092979Z TRACE iota_gas_station::access_controller:
=================================
Access Report for transaction:
{
  ...
}

Rule 1:
        sender_address                 : [NOT MATCHED] : sender address 0xe3071870e0bf9f5a5e9c1985775b61da9c83ccbf1423d7293d8f5de16858d7f5 is not in the list: 'List([0xa2e17e20f97355af6491580ff5c11ecefcdcf76ea224d163e5cb92389adf2311])'
        ---> Action applied: [No action applied]
🔴 Deny (Default policy applied)

In this situation, the sender address does not match the allowed list. The remaining predicates are not evaluated because the access controller engine uses short-circuit evaluation: once a predicate fails to match, it stops evaluating the remaining predicates in that rule to avoid unnecessary computation. The engine then moves to the next rule. Since there is no next rule, the default policy (deny-all) is applied and the transaction is rejected.

Now that we've covered the basic tracing output for both allowed and denied transactions, let's examine a more complex example:

access-controller:
  access-policy: deny-all
  rules:
    - sender-address: ["0xe3071870e0bf9f5a5e9c1985775b61da9c83ccbf1423d7293d8f5de16858d7f5"]
      action: allow
    - sender-address: "*"
      gas-usage:
        value: <=6000000
        window: 1day
      move-call-package-address: "0x8a7f6cb67600e9c676dee1d8e7dcc178fad4b2dd4af7513b313eda3d2a2f91bd"
      action: allow

This configuration demonstrates a more complex scenario: Rule 1 allows a specific sender address with no gas usage restrictions. Rule 2 applies to all other senders (*), allowing them to call a specific package address, but with a daily gas usage limit of 6,000,000.

2025-09-28T13:51:33.092979Z TRACE iota_gas_station::access_controller:
=================================
Access Report for transaction:
{
  ...
}

Rule 1:
        sender_address                 : [NOT MATCHED] : sender address 0xa2e17e20f97355af6491580ff5c11ecefcdcf76ea224d163e5cb92389adf2311 is not in the list: 'List([0xa2e17e20f97355af6491580ff5c11ecefcdcf76ea224d163e5cb92389adf2312])'
        ---> Action applied: [No action applied]

Rule 2:
        sender_address                 : [MATCHED] : sender address 0xa2e17e20f97355af6491580ff5c11ecefcdcf76ea224d163e5cb92389adf2311 is in the list: 'List([0xa2e17e20f97355af6491580ff5c11ecefcdcf76ea224d163e5cb92389adf2311])'
        gas_budget                     : [MATCHED] : gas budget is not defined
        move_call_package_address      : [MATCHED] : move call package address [0x03b64c746b4a04313bf93e2b53490a1091be7c59c18105eef67f985ec1a0a430] is in the list: 'Single(0x03b64c746b4a04313bf93e2b53490a1091be7c59c18105eef67f985ec1a0a430)'
        ptb_command_count              : [MATCHED] : ptb command count is not defined
        rego_expression                : [MATCHED] : rego expression is not defined
        gas_usage                      : [MATCHED] : total gas usage 5000000 matches: <=6000000
        ---> Action applied: ✅ Allow
🟢 Allow

In this scenario with two rules, the output shows the following behavior. Rule 1 is evaluated first, but the sender address doesn't match the specified address, so the rule is skipped and no action is applied. The engine then evaluates Rule 2. All predicates in Rule 2 match: the sender address matches (because Rule 2 uses "*" to allow any sender), the package address matches, and the gas usage is within the allowed limit (5,000,000 ≤ 6,000,000). Therefore, the allow action from Rule 2 is applied to the transaction.

Note that the gas usage counter is shared across all gas station instances, so the reported gas usage reflects the current aggregated state across your entire gas station deployment, not just the current instance.

Using hooks

Access Controller tracing also supports custom messages when using hooks. If a hook response includes the message property, that message will be displayed in the trace output along with the action. Let's extend the previous example by replacing the allow action in Rule 2 with a hook:

Access Controller configuration:

access-controller:
  access-policy: deny-all
  rules:
    - sender-address: ["0xe3071870e0bf9f5a5e9c1985775b61da9c83ccbf1423d7293d8f5de16858d7f5"]
      action: allow
    - sender-address: "*"
      gas-usage:
        value: <=6000000
        window: 1day
      move-call-package-address: "0x8a7f6cb67600e9c676dee1d8e7dcc178fad4b2dd4af7513b313eda3d2a2f91bd"
      action: "http://127.0.0.1:8080"

Tracing message:

2025-09-28T13:51:33.092979Z TRACE iota_gas_station::access_controller:
=================================
Access Report for transaction:
{
  ...
}

Rule 1:
        sender_address                 : [NOT MATCHED] : sender address 0xa2e17e20f97355af6491580ff5c11ecefcdcf76ea224d163e5cb92389adf2311 is not in the list: 'List([0xa2e17e20f97355af6491580ff5c11ecefcdcf76ea224d163e5cb92389adf2312])'
        ---> Action applied: [No action applied]

Rule 2:
        sender_address                 : [MATCHED] : sender address 0xa2e17e20f97355af6491580ff5c11ecefcdcf76ea224d163e5cb92389adf2311 is in the list: 'List([0xa2e17e20f97355af6491580ff5c11ecefcdcf76ea224d163e5cb92389adf2311])'
        gas_budget                     : [MATCHED] : gas budget is not defined
        move_call_package_address      : [MATCHED] : move call package address [0x03b64c746b4a04313bf93e2b53490a1091be7c59c18105eef67f985ec1a0a430] is in the list: 'Single(0x03b64c746b4a04313bf93e2b53490a1091be7c59c18105eef67f985ec1a0a430)'
        ptb_command_count              : [MATCHED] : ptb command count is not defined
        rego_expression                : [MATCHED] : rego expression is not defined
        gas_usage                      : [MATCHED] : total gas usage 5000000 matches: <=6000000
        ---> Action applied: 🔗 http://127.0.0.1:8080/ (Details: hook message: deny transaction)
🔴 Deny

The trace output shows that all predicates in Rule 2 match, so the hook action is triggered. The hook executes its own internal logic and responds with a deny action. Additionally, the hook returns a custom message ("hook message: deny transaction"), which is displayed in the trace output alongside the action details.

As demonstrated in these examples, the tracing mechanism enables you to analyze the behavior of the access controller at a very granular level. This granular visibility provides you with precise control over permission management in your gas station deployment.

Learn More

For more information about how the rules are processed, please refer to this link.