Skip to content

Add ptb command count rule option #63

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 39 additions & 22 deletions docs/access-controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ The **Gas Station Server** includes an **Access Controller** mechanism to manage

```yaml
access-controller:
access-policy: deny-all
rules:
- sender-address: "0x0101010101010101010101010101010101010101010101010101010101010101"
move-call-package-address: "0x0202020202020202020202020202020202020202020202020202020202020202"
action: 'allow' # allowed actions: 'allow', 'deny'
access-policy: deny-all
rules:
- sender-address: "0x0101010101010101010101010101010101010101010101010101010101010101"
move-call-package-address: "0x0202020202020202020202020202020202020202020202020202020202020202"
action: 'allow' # allowed actions: 'allow', 'deny'
```

---
Expand All @@ -25,10 +25,10 @@ The **Gas Station Server** includes an **Access Controller** mechanism to manage

```yaml
access-controller:
access-policy: deny-all
rules:
- sender-address: "0x0101010101010101010101010101010101010101010101010101010101010101"
action: 'deny'
access-policy: deny-all
rules:
- sender-address: "0x0101010101010101010101010101010101010101010101010101010101010101"
action: 'deny'
```

---
Expand All @@ -39,11 +39,11 @@ The **Gas Station Server** includes an **Access Controller** mechanism to manage

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

---
Expand All @@ -54,16 +54,32 @@ The **Gas Station Server** includes an **Access Controller** mechanism to manage

```yaml
access-controller:
access-policy: deny-all
rules:
- sender-address: "0x0101010101010101010101010101010101010101010101010101010101010101"
transaction-gas-budget: '<=10000000'
action: 'allow'
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

- sender-address: '*'
transaction-gas-budget: '<500000'
action: 'allow'
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.

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

---
Expand All @@ -75,6 +91,7 @@ The **Gas Station Server** includes an **Access Controller** mechanism to manage
| `sender-address` | yes | `'0x0000...'`, `[0x0000.., 0x1111...]`, `'*'` |
| `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'` |

## Learn More
Expand Down
47 changes: 47 additions & 0 deletions src/access_controller/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,13 +229,54 @@ mod test {
assert!(ac.check_access(&blocked_tx).is_err());
}

#[test]
fn test_allow_policy_rules_ptb_command_count() {
let sender_address = IotaAddress::new([1; 32]);
let deny_rule = AccessRuleBuilder::new()
.sender_address(sender_address)
.ptb_command_count(ValueNumber::GreaterThan(1))
.deny()
.build();
let denied_tx = TransactionDescription::default()
.with_sender_address(sender_address)
.with_ptb_command_count(5);
let allowed_tx = TransactionDescription::default()
.with_sender_address(sender_address)
.with_ptb_command_count(1);

let ac = AccessController::new(AccessPolicy::AllowAll, [deny_rule]);
assert!(ac.check_access(&allowed_tx).is_ok());
assert!(ac.check_access(&denied_tx).is_err());
}

#[test]
fn test_deny_policy_rules_ptb_command_count() {
let sender_address = IotaAddress::new([1; 32]);
let allow_rule = AccessRuleBuilder::new()
.sender_address(sender_address)
.ptb_command_count(ValueNumber::LessThanOrEqual(1))
.allow()
.build();
let allowed_tx = TransactionDescription::default()
.with_sender_address(sender_address)
.with_ptb_command_count(1);
let blocked_tx = TransactionDescription::default()
.with_sender_address(sender_address)
.with_ptb_command_count(5);

let ac = AccessController::new(AccessPolicy::DenyAll, [allow_rule]);
assert!(ac.check_access(&allowed_tx).is_ok());
assert!(ac.check_access(&blocked_tx).is_err());
}

#[test]
fn deserialize_access_controller() {
let yaml = r#"
access-policy: "deny-all"
rules:
- sender-address: ['0x0101010101010101010101010101010101010101010101010101010101010101']
transaction-gas-budget: <=10000
ptb-command-count: <=5
action: allow
"#;
let ac: AccessController = serde_yaml::from_str(yaml).unwrap();
Expand All @@ -249,6 +290,10 @@ rules:
ac.rules[0].transaction_gas_budget,
Some(ValueNumber::LessThanOrEqual(10000))
);
assert_eq!(
ac.rules[0].ptb_command_count,
Some(ValueNumber::LessThanOrEqual(5))
);
assert_eq!(ac.rules[0].action, Action::Allow);
}

Expand All @@ -259,6 +304,7 @@ rules:
[AccessRuleBuilder::new()
.sender_address(IotaAddress::new([1; 32]))
.gas_budget(ValueNumber::LessThanOrEqual(10000))
.ptb_command_count(ValueNumber::LessThanOrEqual(5))
.allow()
.build()],
);
Expand All @@ -271,6 +317,7 @@ rules:
rules:
- sender-address: 0x0101010101010101010101010101010101010101010101010101010101010101
transaction-gas-budget: <=10000
ptb-command-count: <=5
action: allow
"#
);
Expand Down
52 changes: 32 additions & 20 deletions src/access_controller/predicates/number.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use std::{fmt::Display, str::FromStr};

use serde::{Deserialize, Serialize};

pub const OP_GE: &str = ">=";
Expand All @@ -12,24 +14,27 @@ pub const OP_LT: &str = "<";

// The ValueNumber represents the number value in the rule. It can represent a single number or a range of number
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValueNumber {
GreaterThan(u64),
LessThan(u64),
Equal(u64),
NotEqual(u64),
GreaterThanOrEqual(u64),
LessThanOrEqual(u64),
pub enum ValueNumber<T> {
GreaterThan(T),
LessThan(T),
Equal(T),
NotEqual(T),
GreaterThanOrEqual(T),
LessThanOrEqual(T),
}

impl From<u64> for ValueNumber {
fn from(value: u64) -> Self {
impl<T> From<T> for ValueNumber<T> {
fn from(value: T) -> Self {
ValueNumber::Equal(value)
}
}

impl ValueNumber {
impl<T> ValueNumber<T>
where
T: PartialOrd,
{
/// Check if the value matches the number.
pub fn matches(&self, value: u64) -> bool {
pub fn matches(&self, value: T) -> bool {
match self {
ValueNumber::GreaterThan(number) => value > *number,
ValueNumber::LessThan(number) => value < *number,
Expand All @@ -41,7 +46,10 @@ impl ValueNumber {
}
}

impl Serialize for ValueNumber {
impl<T> Serialize for ValueNumber<T>
where
T: Display,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
Expand All @@ -67,8 +75,12 @@ impl Serialize for ValueNumber {
}
}

impl<'de> Deserialize<'de> for ValueNumber {
fn deserialize<D>(deserializer: D) -> Result<ValueNumber, D::Error>
impl<'de, T> Deserialize<'de> for ValueNumber<T>
where
T: FromStr,
<T as FromStr>::Err: Display,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Expand Down Expand Up @@ -135,7 +147,7 @@ mod test {
let serialized = serde_json::to_string(&number).unwrap();
assert_eq!(serialized, "\"=42\"");

let deserialized: super::ValueNumber = serde_json::from_str(&serialized).unwrap();
let deserialized: super::ValueNumber<u64> = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized, number);
}

Expand All @@ -145,7 +157,7 @@ mod test {
let serialized = serde_json::to_string(&number).unwrap();
assert_eq!(serialized, "\"!=42\"");

let deserialized: super::ValueNumber = serde_json::from_str(&serialized).unwrap();
let deserialized: super::ValueNumber<u64> = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized, number);
}

Expand All @@ -155,7 +167,7 @@ mod test {
let serialized = serde_json::to_string(&number).unwrap();
assert_eq!(serialized, "\">42\"");

let deserialized: super::ValueNumber = serde_json::from_str(&serialized).unwrap();
let deserialized: super::ValueNumber<u64> = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized, number);
}

Expand All @@ -165,7 +177,7 @@ mod test {
let serialized = serde_json::to_string(&number).unwrap();
assert_eq!(serialized, "\"<42\"");

let deserialized: super::ValueNumber = serde_json::from_str(&serialized).unwrap();
let deserialized: super::ValueNumber<u64> = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized, number);
}

Expand All @@ -175,7 +187,7 @@ mod test {
let serialized = serde_json::to_string(&number).unwrap();
assert_eq!(serialized, "\">=42\"");

let deserialized: super::ValueNumber = serde_json::from_str(&serialized).unwrap();
let deserialized: super::ValueNumber<u64> = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized, number);
}

Expand All @@ -185,7 +197,7 @@ mod test {
let serialized = serde_json::to_string(&number).unwrap();
assert_eq!(serialized, "\"<=42\"");

let deserialized: super::ValueNumber = serde_json::from_str(&serialized).unwrap();
let deserialized: super::ValueNumber<u64> = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized, number);
}
}
Loading