Skip to content

Commit b18048b

Browse files
authored
Merge pull request #93 from api3dao/fix-delegate-badge
fix: incorrect assignment of delegate badge
2 parents 9125074 + 1b6f10d commit b18048b

File tree

5 files changed

+195
-11
lines changed

5 files changed

+195
-11
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
.DS_Store
2020
*.pem
2121
.idea
22+
.vscode
2223

2324
# debug
2425
npm-debug.log*

README.md

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,89 @@ on-chain details of the API3 DAO, including:
88
- All events from the smart contracts of the API3 DAO
99
- DAO Treasuries status
1010

11-
## Local Installation
11+
## Architecture
12+
13+
The app relies on Terraform to configure a generic Linux EC2 instance.
14+
The EC2 instance in-turn hosts Docker, and app-services are orchestrated by Docker directly (eg. `restart=always`).
15+
16+
The services are:
17+
18+
```
19+
(end user) -> Cloudflare -> EC2 IP -> traefik (load balancer) -> api3-tracker (container)
20+
```
21+
22+
Containers:
23+
- api3tracker: The FE and BE-service
24+
- postgres: The database the FE and BE rely on
25+
- traefik: A load balancer that encrypts HTTP responses (using the CF origin server key pair)
26+
- postgres-exporter: a service that exports the database as a backup on an interval
27+
28+
Host services:
29+
The host OS also runs some cron services, these are:
30+
```bash
31+
*/10 * * * * root cd /home/ubuntu/src/github.com/api3dao/api3-tracker/terraform/workspaces/api3tracker-prod && ./bin/job_logs_download.sh >> /var/log/api3-logs-download.log 2>&1
32+
15,45 * * * * root cd /home/ubuntu/src/github.com/api3dao/api3-tracker/terraform/workspaces/api3tracker-prod && ./bin/job_supply_download.sh >> /var/log/api3-supply-download.log 2>&1
33+
0 * * * * root cd /home/ubuntu/src/github.com/api3dao/api3-tracker/terraform/workspaces/api3tracker-prod && ./bin/job_treasuries_download.sh >> /var/log/api3-treasuries-download.log 2>&1
34+
2,12,22,32,42,52 * * * * root cd /home/ubuntu/src/github.com/api3dao/api3-tracker/terraform/workspaces/api3tracker-prod && ./bin/job_state_update.sh >> /var/log/api3-state-update.log 2>&1
35+
10 0 * * * root cd /home/ubuntu/src/github.com/api3dao/api3-tracker/terraform/workspaces/api3tracker-prod && ./bin/job_shares_download.sh --tag . > /var/log/api3-shares-download.log 2>&1
36+
24 4 * * */3 root cd /home/ubuntu/src/github.com/api3dao/api3-tracker/terraform/workspaces/api3tracker-prod && bash ./bin/postgres-backup.sh >> /var/log/postgres-backups.log 2>&1
37+
```
38+
39+
## Local developement using Docker
40+
Developers can run some or all services locally using Docker Swarm, or even bare-bones, without containerisation.
41+
42+
One combination is running just postgres locally using Docker, eg:
43+
```bash
44+
docker run --rm -ti -p 5432:5432 postgres:15
45+
```
46+
and then running the FE and BE services directly (refer to Cron jobs below and `yarn next dev` in `package.json`).
47+
48+
Alternatively, one can run services using Docker Swarm, but this lacks hot-reloading.
49+
50+
### Local development using Docker Swarm
51+
If you haven't already enabled Swarm mode on your Docker instance, do so now (only has to be done once):
52+
```bash
53+
docker swarm init
54+
```
55+
The result of the above command can be ignored.
56+
57+
Build the FE/BE image:
58+
```bash
59+
docker build -t api3dao/api3-tracker:latest .
60+
```
61+
62+
Run the stack:
63+
```bash
64+
docker stack deploy -c dev-tools/docker-compose.yml tracker-stack
65+
```
66+
67+
If all goes well the application will be served at http://localhost:3000
68+
69+
Some commands for visualising the services:
70+
```bash
71+
docker ps # all docker containers
72+
docker service ls # all swarm services
73+
docker service ps tracker-stack_postgres --no-trunc # show status of postgres
74+
docker stack rm tracker-stack # tear down the stack
75+
```
76+
77+
Initialise the DB:
78+
```bash
79+
DATABASE_URL="postgres://postgres:[email protected]:5432/postgres?sslmode=disable" yarn prisma migrate deploy
80+
```
81+
82+
Cron jobs (unwrapped versions of cronjobs):
83+
```bash
84+
DATABASE_URL="postgres://postgres:[email protected]:5432/postgres?sslmode=disable" TS_NODE_PROJECT=./tsconfig.cli.json yarn ts-node cli.ts logs download
85+
DATABASE_URL="postgres://postgres:[email protected]:5432/postgres?sslmode=disable" TS_NODE_PROJECT=./tsconfig.cli.json yarn ts-node cli.ts supply download
86+
DATABASE_URL="postgres://postgres:[email protected]:5432/postgres?sslmode=disable" TS_NODE_PROJECT=./tsconfig.cli.json yarn ts-node cli.ts treasuries download
87+
DATABASE_URL="postgres://postgres:[email protected]:5432/postgres?sslmode=disable" TS_NODE_PROJECT=./tsconfig.cli.json yarn ts-node cli.ts shares download
88+
DATABASE_URL="postgres://postgres:[email protected]:5432/postgres?sslmode=disable" API3TRACKER_ENDPOINT="ARCHIVE RPC URL" TS_NODE_PROJECT=./tsconfig.cli.json yarn ts-node cli.ts state update --rps-limit
89+
```
90+
91+
Keep in mind that the Postgres DB in the docker-compose file is not configured with a volume by default, so changes will be lost on service restart.
92+
93+
## Local Installation and Deployment (Terraform only)
1294

1395
The only requirements for installation are [Docker](https://docs.docker.com/get-docker/)
1496
and [Terraform](https://learn.hashicorp.com/tutorials/terraform/install-cli).

dev-tools/docker-compose.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
version: '3.5'
2+
3+
services:
4+
postgres:
5+
image: postgres:15
6+
environment:
7+
POSTGRES_USER: postgres
8+
POSTGRES_PASSWORD: postgres
9+
ports:
10+
- "5432:5432"
11+
networks:
12+
- postgres
13+
# volumes:
14+
# - /custom/mount:/var/lib/postgresql/data
15+
16+
api3tracker:
17+
image: api3dao/api3-tracker:latest
18+
environment:
19+
API3TRACKER_ENDPOINT: "https://ethereum-rpc.publicnode.com"
20+
API3TRACKER_COINGECKO_HOST: api.coingecko.com
21+
API3TRACKER_COINGECKO_API_KEY: ""
22+
DATABASE_URL: "postgres://postgres:[email protected]:5432/postgres?sslmode=disable"
23+
ports:
24+
- "3000:3000"
25+
networks:
26+
- postgres
27+
28+
networks:
29+
postgres:

scripts/fix-delegate-badges.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import prisma from "../services/db";
2+
import { Batch } from "../services/members";
3+
import { Prisma } from "@prisma/client";
4+
import { Wordlist } from "../services/members";
5+
6+
async function fixDelegateBadges() {
7+
// Get all members with delegate badge
8+
const members = await prisma.member.findMany({
9+
where: {
10+
badges: {
11+
contains: "delegate",
12+
},
13+
},
14+
});
15+
16+
console.log(`Found ${members.length} members with delegate badge`);
17+
let fixCount = 0;
18+
19+
for (const member of members) {
20+
const addr = Buffer.from(member.address);
21+
const addrHex = addr.toString("hex");
22+
23+
// Calculate total delegated amount for this member
24+
const delegated = await Batch.readMemberDelegatedTotal(addr, false);
25+
26+
// console.log(`\nChecking member ${addrHex}:`);
27+
// console.log(`Current delegated amount: ${delegated}`);
28+
// console.log(`Current badges: ${member.badges}`);
29+
30+
// If no delegation, remove the badge
31+
if (delegated.equals(new Prisma.Decimal(0))) {
32+
fixCount++;
33+
// console.log(`Removing delegate badge for ${addrHex}`);
34+
35+
// Remove badges directly
36+
const updatedBadges = Wordlist.remove(member.badges, "delegate");
37+
const updatedTags = Wordlist.remove(member.tags || "", "delegate");
38+
39+
// Update database directly
40+
try {
41+
await prisma.member.update({
42+
where: { address: addr },
43+
data: {
44+
badges: updatedBadges,
45+
tags: updatedTags,
46+
userIsDelegated: new Prisma.Decimal(0),
47+
},
48+
});
49+
// console.log(`Successfully updated database for ${addrHex}`);
50+
// console.log(`New badges: ${updatedBadges}`);
51+
} catch (error) {
52+
console.error(`Failed to update database for ${addrHex}:`, error);
53+
}
54+
}
55+
}
56+
57+
console.log(`\nAttempted to fix delegate badges for ${fixCount} members`);
58+
59+
// Verify changes
60+
const remainingDelegates = await prisma.member.count({
61+
where: {
62+
badges: {
63+
contains: "delegate",
64+
},
65+
},
66+
});
67+
68+
console.log(`Members still with delegate badge: ${remainingDelegates}`);
69+
}
70+
71+
// Run the fix
72+
fixDelegateBadges()
73+
.catch(console.error)
74+
.finally(() => prisma.$disconnect());

services/members.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export type Badge =
1717
| "proposer"
1818
| "vested";
1919

20-
const Wordlist = {
20+
export const Wordlist = {
2121
has: (wordlist: string, word: string): boolean => {
2222
if (!wordlist) return false;
2323
const parts = wordlist.split(",");
@@ -255,7 +255,7 @@ export const Batch = {
255255
} else {
256256
const existing: IWallet = Wallets.from(members[0]);
257257
// only badge can be new in our case
258-
// nothing else is changing currenly
258+
// nothing else is changing currently
259259
if (badge) {
260260
if (!Wordlist.has(existing.badges, badge)) {
261261
existing.badges = Wordlist.add(existing.badges, badge);
@@ -456,7 +456,6 @@ export const Batch = {
456456
delegation ? delegation.userShares : 0
457457
);
458458
if (delegation) {
459-
// delegates badge should be received
460459
const badge = "delegator";
461460
member.badges = Wordlist.add(member.badges, badge);
462461
if (member.tags) {
@@ -467,14 +466,13 @@ export const Batch = {
467466
}
468467
// find what are the delegations TO the member
469468
member.userIsDelegated = delegated;
470-
if (delegated) {
471-
const badge = "delegate";
469+
const badge = "delegate";
470+
if (delegated > new Prisma.Decimal(0.0)) {
472471
member.badges = Wordlist.add(member.badges, badge);
473-
if (member.tags) {
474-
member.tags = Wordlist.add(member.tags, badge);
475-
} else {
476-
member.tags = badge;
477-
}
472+
member.tags = Wordlist.add(member.tags || "", badge);
473+
} else {
474+
member.badges = Wordlist.remove(member.badges, badge);
475+
member.tags = Wordlist.remove(member.tags || "", badge);
478476
}
479477
member.userVotingPower = new Prisma.Decimal(member.userShare).add(
480478
member.userIsDelegated

0 commit comments

Comments
 (0)