Skip to content

Commit

Permalink
improve(acl): allow for a json acl to be unmarshalled and updated. (#…
Browse files Browse the repository at this point in the history
…1742)

* improve(acl): allow for a json acl to be unmarshalled and updated in the db

improve(acl): only update with flag

improve(acl): update improvements

improve(acl): update improvements

* improve(acl): ignore dbinfo in unwind tests

* improvement(acl): use in memory acl list without mdbx

* improvement(acl): multiple test cases for acl rules
  • Loading branch information
elliothllm authored Feb 24, 2025
1 parent b91e9d3 commit 9ffc4be
Show file tree
Hide file tree
Showing 14 changed files with 445 additions and 13 deletions.
5 changes: 5 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,11 @@ var (
Usage: "Number of entries to print from the ACL history on node start up",
Value: 10,
}
ACLJsonLocation = cli.StringFlag{
Name: "acl.json-location",
Usage: "Location of the ACL JSON file",
Value: "",
}
DebugTimers = cli.BoolFlag{
Name: "debug.timers",
Usage: "Enable debug timers",
Expand Down
1 change: 1 addition & 0 deletions eth/ethconfig/config_zkevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ type Zk struct {

InitialBatchCfgFile string
ACLPrintHistory int
ACLJsonLocation string
InfoTreeUpdateInterval time.Duration
BadBatches []uint64
IgnoreBadBatchesCheck bool
Expand Down
1 change: 1 addition & 0 deletions turbo/cli/default_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ var DefaultFlags = []cli.Flag{
&utils.InitialBatchCfgFile,

&utils.ACLPrintHistory,
&utils.ACLJsonLocation,
&utils.InfoTreeUpdateInterval,
&utils.SealBatchImmediatelyOnOverflow,
&utils.MockWitnessGeneration,
Expand Down
1 change: 1 addition & 0 deletions turbo/cli/flags_zkevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ func ApplyFlagsForZkConfig(ctx *cli.Context, cfg *ethconfig.Config) {
IgnoreBadBatchesCheck: ctx.Bool(utils.IgnoreBadBatchesCheck.Name),
InitialBatchCfgFile: ctx.String(utils.InitialBatchCfgFile.Name),
ACLPrintHistory: ctx.Int(utils.ACLPrintHistory.Name),
ACLJsonLocation: ctx.String(utils.ACLJsonLocation.Name),
InfoTreeUpdateInterval: ctx.Duration(utils.InfoTreeUpdateInterval.Name),
SealBatchImmediatelyOnOverflow: ctx.Bool(utils.SealBatchImmediatelyOnOverflow.Name),
MockWitnessGeneration: ctx.Bool(utils.MockWitnessGeneration.Name),
Expand Down
170 changes: 170 additions & 0 deletions zk/acl/acl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package acl

import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/ledgerwatch/erigon-lib/common"
"io"
"os"
)

type Acl struct {
Allow *Rules `json:"allow"`
Deny *Rules `json:"deny"`
}

type Rules struct {
Deploy []common.Address `json:"deploy"`
Send []common.Address `json:"send"`
}

func UnmarshalAcl(path string) (*Acl, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()

data, err := io.ReadAll(file)
if err != nil {
return nil, err
}

var acl Acl
if err = json.Unmarshal(data, &acl); err != nil {
return nil, err
}

return &acl, nil
}

func (a *Acl) AllowExists() bool {
if a.Allow == nil {
return false
}
return len(a.Allow.Deploy) > 0 || len(a.Allow.Send) > 0
}

func (a *Acl) DenyExists() bool {
if a.Deny == nil {
return false
}
return len(a.Deny.Deploy) > 0 || len(a.Deny.Send) > 0
}

type Policy byte

const (
SendTx Policy = iota
Deploy
)

func (p Policy) ToByte() byte {
return byte(p)
}

var errUnknownPolicy = errors.New("unknown policy")

func resolvePolicyByte(policy byte) (Policy, error) {
switch policy {
case 0:
return SendTx, nil
case 1:
return Deploy, nil
default:
return SendTx, errUnknownPolicy
}
}

type Validator struct {
acl *Acl
}

func NewPolicyValidator(acl *Acl) *Validator {
return &Validator{acl: acl}
}

func (v *Validator) IsActionAllowed(ctx context.Context, addr common.Address, policy byte) (bool, error) {
p, err := resolvePolicyByte(policy)
if err != nil {
return false, err
}

hasDenyPolicy, err := v.acl.AddressHasDenyPolicy(p, addr)
if err != nil {
return false, err
}

// if we have Deny policy, we should return false
// and not even check for Allow policy
if hasDenyPolicy {
return false, nil
}

if !v.acl.AllowExists() {
return true, nil
}

hasAllowPolicy, err := v.acl.AddressHasAllowPolicy(p, addr)
if err != nil {
return false, err
}

if hasAllowPolicy {
return true, nil
}

return false, nil
}

func (a *Acl) AddressHasDenyPolicy(policy Policy, addr common.Address) (bool, error) {
switch policy {
case SendTx:
if a.DenyExists() {
for _, allowed := range a.Deny.Send {
if allowed == addr {
return true, nil
}
}
}
return false, nil
case Deploy:
if a.DenyExists() {
for _, allowed := range a.Deny.Deploy {
if allowed == addr {
return true, nil
}
}
}
return false, nil
default:
return false, fmt.Errorf("invalid policy: %v", policy)
}
}

func (a *Acl) AddressHasAllowPolicy(policy Policy, addr common.Address) (bool, error) {
switch policy {
case SendTx:
if a.AllowExists() {
for _, allowed := range a.Allow.Send {
if allowed == addr {
return true, nil
}
}
}
return false, nil
case Deploy:
if a.AllowExists() {
for _, allowed := range a.Allow.Deploy {
if allowed == addr {
return true, nil
}
}
}
return false, nil
default:
return false, fmt.Errorf("invalid policy: %v", policy)
}
}
155 changes: 155 additions & 0 deletions zk/acl/acl_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package acl

import (
"context"
"github.com/ledgerwatch/erigon-lib/common"
"github.com/stretchr/testify/assert"
"testing"
)

func TestUnmarshalAcl(t *testing.T) {
a, err := UnmarshalAcl("./test_acl_json/test-acl.json")
assert.NoError(t, err)
assert.NotNil(t, a)
assert.NotNil(t, a.Allow)
assert.NotNil(t, a.Allow.Deploy)
assert.NotNil(t, a.Allow.Send)
assert.NotNil(t, a.Deny)
assert.NotNil(t, a.Deny.Deploy)
assert.NotNil(t, a.Deny.Send)
}

func TestCheckAllow(t *testing.T) {
a, err := UnmarshalAcl("./test_acl_json/test-acl-allow-only.json")
assert.NoError(t, err)
assert.NotNil(t, a)
assert.True(t, a.AllowExists())
assert.False(t, a.DenyExists())
}

func TestCheckDeny(t *testing.T) {
a, err := UnmarshalAcl("./test_acl_json/test-acl-deny-only.json")
assert.NoError(t, err)
assert.NotNil(t, a)
assert.True(t, a.DenyExists())
assert.False(t, a.AllowExists())
}

func TestRuleTypeBlockAll(t *testing.T) {
acl, err := UnmarshalAcl("./test_acl_json/test-acl.json")
assert.NoError(t, err)
assert.NotNil(t, acl)

// Address is present in both so we should deny (deny trumps allow)
v := NewPolicyValidator(acl)
allowed, err := v.IsActionAllowed(context.Background(), common.HexToAddress("0x0000000000000000000000000000000000000000"), 0)
assert.NoError(t, err)
assert.False(t, allowed)
}

func TestIsActionAllowed(t *testing.T) {
scenarios := map[string]struct {
aclPath string
addr common.Address
policy Policy
expected bool
}{
"[Both List] Address is in both allow/deny. (Deny)": {
aclPath: "./test_acl_json/test-acl.json",
addr: common.HexToAddress("0x0000000000000000000000000000000000000000"),
policy: SendTx,
expected: false,
},
"[Deny Only List] Address is in deny only. (Deny)": {
aclPath: "./test_acl_json/test-acl-deny-only.json",
addr: common.HexToAddress("0x0000000000000000000000000000000000000000"),
policy: SendTx,
expected: false,
},
"[Allow Only List] Address is in allow only. (Allow)": {
aclPath: "./test_acl_json/test-acl-allow-only.json",
addr: common.HexToAddress("0x0000000000000000000000000000000000000000"),
policy: SendTx,
expected: true,
},
"[Both List] Address is not found in either. (Deny)": {
aclPath: "./test_acl_json/test-acl.json",
addr: common.HexToAddress("0x0000000000000000000000000000000000000001"),
policy: SendTx,
expected: false,
},
"[Deny Only List] Address is not found in deny list. (Allow)": {
aclPath: "./test_acl_json/test-acl-deny-only.json",
addr: common.HexToAddress("0x0000000000000000000000000000000000000001"),
policy: SendTx,
expected: true,
},
"[Allow Only List] Address is not found in allow list. (Deny)": {
aclPath: "./test_acl_json/test-acl-allow-only.json",
addr: common.HexToAddress("0x0000000000000000000000000000000000000001"),
policy: SendTx,
expected: false,
},
"[Both List] Address is on allow but not deny. (Allow)": {
aclPath: "./test_acl_json/test-acl-both-multiple-addr.json",
addr: common.HexToAddress("0x0000000000000000000000000000000000000001"),
policy: SendTx,
expected: true,
},
"[Both List] Address is on allow for deploy allow for send. (Deny)": {
aclPath: "./test_acl_json/test-acl-both-multiple-addr.json",
addr: common.HexToAddress("0x0000000000000000000000000000000000000000"),
policy: SendTx,
expected: false,
},
"[Both List] Address is on deny list but not allow and allow exists. (Deny)": {
aclPath: "./test_acl_json/test-acl-both-multiple-addr.json",
addr: common.HexToAddress("0x0000000000000000000000000000000000000004"),
policy: SendTx,
expected: false,
},
"[Both List] Address is on deny for deploy but not send and allow list exists. (Deny)": {
aclPath: "./test_acl_json/test-acl-both-multiple-addr.json",
addr: common.HexToAddress("0x0000000000000000000000000000000000000003"),
policy: SendTx,
expected: false,
},
"[Both List] Address is in both for deploy. (Deny)": {
aclPath: "./test_acl_json/test-acl.json",
addr: common.HexToAddress("0x0000000000000000000000000000000000000000"),
policy: Deploy,
expected: false,
},
"[Both List] Address is on deny for send but not deploy and allow list exists. (Deny)": {
aclPath: "./test_acl_json/test-acl-both-multiple-addr.json",
addr: common.HexToAddress("0x0000000000000000000000000000000000000004"),
policy: Deploy,
expected: false,
},
"[Both List] Address found in deny for deploy. (Deny)": {
aclPath: "./test_acl_json/test-acl-both-multiple-addr.json",
addr: common.HexToAddress("0x0000000000000000000000000000000000000003"),
policy: Deploy,
expected: false,
},
"[Both List] Address not found in deny for deploy and address is in allow for deploy. (Allow)": {
aclPath: "./test_acl_json/test-acl-both-multiple-addr.json",
addr: common.HexToAddress("0x0000000000000000000000000000000000000000"),
policy: Deploy,
expected: true,
},
}

for name, scenario := range scenarios {
t.Run(name, func(t *testing.T) {
acl, err := UnmarshalAcl(scenario.aclPath)
assert.NoError(t, err)
assert.NotNil(t, acl)

v := NewPolicyValidator(acl)
allowed, err := v.IsActionAllowed(context.Background(), scenario.addr, scenario.policy.ToByte())
assert.NoError(t, err)
assert.Equal(t, scenario.expected, allowed)
})
}
}
10 changes: 10 additions & 0 deletions zk/acl/test_acl_json/test-acl-allow-only.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"allow": {
"deploy": [
"0x0000000000000000000000000000000000000000"
],
"send": [
"0x0000000000000000000000000000000000000000"
]
}
}
18 changes: 18 additions & 0 deletions zk/acl/test_acl_json/test-acl-both-multiple-addr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"allow": {
"deploy": [
"0x0000000000000000000000000000000000000000"
],
"send": [
"0x0000000000000000000000000000000000000001"
]
},
"deny": {
"deploy": [
"0x0000000000000000000000000000000000000003"
],
"send": [
"0x0000000000000000000000000000000000000004"
]
}
}
Loading

0 comments on commit 9ffc4be

Please sign in to comment.