Skip to content
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

Update entity cluster bot to cluster highly suspicious contract creations #649

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
71 changes: 61 additions & 10 deletions entity-cluster-bot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,27 +34,46 @@ It store the metadata of the shared graph and also manage the mutex (There is on
The mutex also has a expire time of 10s at application level and a dynamo ttl at infrastructure level to avoid the mutex being lock if an intance is shutdown in the forta network.
The Mutex only use dynamo write capacity

Definition:
#### set up a dynamo db on AWS
1. create table
table name : your-table-name
Table class: DynamoDB standard
Partition Key: itemId(String)
Sort Key: sortKey(String)
Customize settings
DynamoDB standard
Read capacity : minimum 2
Write capacity : minimum 2
leave the rest as default
Create table

2. Set up the ttl:
ttl feature: on
ttl name field: expiresAt
Capacity: 2 write, 2 read

place the instance in the same region as the s3 bucket

### S3_BUCKET= "prod-research-bot-data"
S3 bucket to store the shared graph, one chared graph per chain.
Definition:
Just the standard s3

1. create a S3 bucket
bucket name: your-bucket-name
Leave the rest as default
creatre bucket

### Infrastructure cost
Each instance updates the graph every TX_SAVE_STEP, asking for the mutex, saving metadata, reading from s3, writing in s3 and releasing the mutex all these operations cost $$$. The lower the TX_SAVE_STEP, the more accurate the alert are as the
instances "knows what is hapening in the other" but there are more operation over the infrastructure so the cost are higher. By design there is a relationship betweeen cost and accuracy.
### Set up IAM permission

#### Create a policy for the bot
1. go to IAM
2. go to policies
3. create policy
4. go to JSON tab
5. paste the following policy
6. replace the bucket name and the table name with the ones you created (S3_ARN and TABLE_ARN) ARN(Amazon Ressource Name) can be found in the bucket and table details.
7. click on next
8. Set up the name of the policy
9. click on create policy

### IAM permission

Keep them as low as possible, here a template:

Expand All @@ -73,7 +92,7 @@ Keep them as low as possible, here a template:
"dynamodb:Query",
"dynamodb:UpdateItem"
],
"Resource": "HIDDEN"
"Resource": "TABLE_ARN"
},
{
"Sid": "VisualEditor1",
Expand All @@ -82,11 +101,40 @@ Keep them as low as possible, here a template:
"s3:PutObject",
"s3:GetObject"
],
"Resource": "HIDDEN"
"Resource": "S3_ARN"
}
]
}

#### Assign permission to a user
1. go to IAM
2. go to users
3. create or select a user
4. go to permissions tab
5. click on add permission
6. click on attach existing policies directly
7. search for the policy you created
8. click on next
9. click on add permission


#### Get the credentials
1. go to IAM
2. go to users
3. select a user
4. go to security credentials tab
5. click on create access key
6. click on show
7. copy the access key and the secret key
8. paste them in the secret.json file


### Infrastructure cost
Each instance updates the graph every TX_SAVE_STEP, asking for the mutex, saving metadata, reading from s3, writing in s3 and releasing the mutex all these operations cost $$$. The lower the TX_SAVE_STEP, the more accurate the alert are as the
instances "knows what is hapening in the other" but there are more operation over the infrastructure so the cost are higher. By design there is a relationship betweeen cost and accuracy.


## Credentials configuration
Credential should be in secret.json, a template to start could be:

{
Expand All @@ -102,6 +150,9 @@ Credential should be in secret.json, a template to start could be:
ZETTABLOCK for alert stats
AWS.* infrastructure

### constants.py
you may have to change the constants.py file to match your infrastructure, zone and ressource names.


## RPC Timeout
Web3 RPC call are time consuming, there is now a HTTP_RPC_TIMEOUT=2 config to avoid waiting to long if we hit a slow server from the provider. If the rpc call doesn't finish in 2 second it will raise a exception and will continue with the next transaction. usually a rpc call should be 500ms
Expand Down
108 changes: 104 additions & 4 deletions entity-cluster-bot/src/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,38 @@
load_dotenv()

try:
from src.constants import MAX_AGE_IN_DAYS, MAX_NONCE, GRAPH_KEY, ONE_WAY_WEI_TRANSFER_THRESHOLD, NEW_FUNDED_MAX_WEI_TRANSFER_THRESHOLD, NEW_FUNDED_MAX_NONCE, TX_SAVE_STEP, HTTP_RPC_TIMEOUT, PROFILING, BOT_ID, PROD_TAG
from src.constants import (
MAX_AGE_IN_DAYS,
MAX_NONCE,
GRAPH_KEY,
ONE_WAY_WEI_TRANSFER_THRESHOLD,
NEW_FUNDED_MAX_WEI_TRANSFER_THRESHOLD,
NEW_FUNDED_MAX_NONCE,
TX_SAVE_STEP,
HTTP_RPC_TIMEOUT,
PROFILING,
BOT_ID,
PROD_TAG,
SEVERITY_ALERT_FILTER,
MALICIOUS_SMART_CONTRACT_BOT_ID,
CLUSTER_SENDER,
)
from src.persistance import DynamoPersistance
from src.storage import get_secrets
except ModuleNotFoundError:
from constants import MAX_AGE_IN_DAYS, MAX_NONCE, GRAPH_KEY, ONE_WAY_WEI_TRANSFER_THRESHOLD, NEW_FUNDED_MAX_WEI_TRANSFER_THRESHOLD, NEW_FUNDED_MAX_NONCE, TX_SAVE_STEP, HTTP_RPC_TIMEOUT, PROFILING, BOT_ID, PROD_TAG
from constants import (
MAX_AGE_IN_DAYS,
MAX_NONCE,
GRAPH_KEY,
ONE_WAY_WEI_TRANSFER_THRESHOLD,
NEW_FUNDED_MAX_WEI_TRANSFER_THRESHOLD,
NEW_FUNDED_MAX_NONCE,
TX_SAVE_STEP,
HTTP_RPC_TIMEOUT,
PROFILING,
BOT_ID,
PROD_TAG,
)
from persistance import DynamoPersistance
from storage import get_secrets

Expand Down Expand Up @@ -337,6 +364,79 @@ def real_handle_transaction(self, transaction_event):
def persist_state(self):
self.persistance.persist(self.GRAPH, GRAPH_KEY, EntityClusterAgent.prune_graph)

entity_cluster_agent = EntityClusterAgent(DynamoPersistance(PROD_TAG, web3.eth.chain_id), TX_SAVE_STEP, web3.eth.chain_id)
def handle_transaction(transaction_event: forta_agent.transaction_event.TransactionEvent) -> list:

def provide_handle_alert(self, alert_event: forta_agent.alert_event.AlertEvent, cluster_sender=CLUSTER_SENDER):
logging.debug("provide_handle_alert called")

if alert_event.chain_id != self.chain_id:
logging.debug("Alert not processed because it is not from the same chain")
else:
if alert_event.bot_id != MALICIOUS_SMART_CONTRACT_BOT_ID:
logging.debug(
f"Alert not processed not monitoring that bot {alert_event.bot_id} - alert id : {alert_event.alert.alert_id} - chain id {self.chain_id}"
)
else:
if alert_event.alert.severity != SEVERITY_ALERT_FILTER:
logging.debug(
f"Alert not processed not monitoring that severity {alert_event.alert.severity} - alert id : {alert_event.alert.alert_id} - chain id {self.chain_id}"
)
else:
logging.debug(
f"Processing alert {alert_event.alert.alert_id} - chain id {self.chain_id} - bot id {alert_event.bot_id} - severity {alert_event.alert.severity}"
)
return self.process_alert(alert_event, cluster_sender=cluster_sender)
return []

def process_alert(self, alert_event: forta_agent.alert_event.AlertEvent, cluster_sender=CLUSTER_SENDER):
findings = []
finding = None
description = alert_event.alert.description
if "created contract" in description:
description_split = description.split(" ")
creator_address = description_split[0]
contract_address = description_split[-1]
self.add_address(contract_address)
self.add_address(creator_address)
self.add_directed_edge(web3, creator_address, contract_address)
self.add_directed_edge(web3, contract_address, creator_address) #fake bidirectionnal edge to prevent filtering
finding = self.create_finding(
creator_address, "Triggered by high risk contract creation"
)
if finding is not None:
findings.append(finding)
finding = None

if cluster_sender:
transaction = web3.eth.get_transaction(
alert_event.transaction_hash
)
sender_address = transaction["from"]
if str.lower(sender_address) != str.lower(creator_address):
self.add_address(sender_address)
self.add_directed_edge(web3, sender_address, creator_address)
self.add_directed_edge(web3, creator_address, sender_address)
finding = self.create_finding(
sender_address, "Triggered by high risk contract creation, transaction sender"
)
if finding is not None:
findings.append(finding)


return findings


entity_cluster_agent = EntityClusterAgent(
DynamoPersistance(PROD_TAG, web3.eth.chain_id), TX_SAVE_STEP, web3.eth.chain_id
)


def handle_transaction(
transaction_event: forta_agent.transaction_event.TransactionEvent,
) -> list:
return entity_cluster_agent.real_handle_transaction(transaction_event)


def handle_alert(alert_event: forta_agent.alert_event.AlertEvent):
logging.debug("handle_alert called")

return entity_cluster_agent.provide_handle_alert(alert_event)
Loading