Skip to content

Commit

Permalink
Mainnet address tracker service (#137)
Browse files Browse the repository at this point in the history
* mainnet address tracker service

* error handling
  • Loading branch information
rajranjan0608 authored Feb 9, 2024
1 parent ddc799d commit 59bdfb7
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 5 deletions.
142 changes: 142 additions & 0 deletions mainnetCheckService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { DynamoDBClient } from "@aws-sdk/client-dynamodb"
import { DynamoDBDocumentClient, GetCommand, PutCommand } from "@aws-sdk/lib-dynamodb"
import { getNonce } from "./utils/mainnetBalanceCheck";

const MAINNET_CHECK_TRACKER_TABLE = "mainnet_check_tracker";
const MAX_ADDRESS_CHECK_COUNT = 3;

type AddressStatus = {
checkCount: number,
lastUsedNonce: number,
}

type ResponseType = {
isValid: boolean,
internalError: boolean,
message: string
}

export class MainnetCheckService {
private readonly documentClient: DynamoDBDocumentClient
private readonly RPC: string

constructor(rpc: string) {
const ddbClient = new DynamoDBClient({ region: 'us-east-1' })
this.documentClient = DynamoDBDocumentClient.from(ddbClient)
this.RPC = rpc
}

/**
* 1. Get check count and last used nonce (address status)
* 2. If checkCount < MAX_ADDRESS_CHECK_COUNT:
* a. Asynchronously increment check count and update nonce
* b. Return success
* 3. If checkCount == MAX_ADDRESS_CHECK_COUNT
* a. Fetch current nonce from network, and last used nonce from DB
* b. diff = currentNonce - lastUsedNonce
* c. If diff > 0
* i. checkCount = max(0, checkCount - diff)
* ii. asynchronously update address status
* iii. return success
* d. If diff == 0
* i. fail
*/
async checkAddressValidity(address: string): Promise<ResponseType> {
const response: ResponseType = { isValid: false, internalError: false, message: "" };

let addressStatus = await this.getAddressStatus(address)
if (!addressStatus) {
// update address status
addressStatus = await this.updateAddressStatus(address)
if (!addressStatus) {
response.internalError = true
return response
}
}

if (addressStatus.checkCount < MAX_ADDRESS_CHECK_COUNT) {
this.updateAddressStatus(address, ++addressStatus.checkCount, addressStatus.lastUsedNonce);
response.isValid = true
} else {
const currentNonce = await getNonce(this.RPC, address)
if (!currentNonce) {
response.internalError = true
return response
}
const diff = currentNonce - addressStatus.lastUsedNonce
if (diff > 0) {
const updatedCheckCount = Math.max(0, addressStatus.checkCount - diff) + 1
this.updateAddressStatus(address, updatedCheckCount, currentNonce)
response.isValid = true
}
}

return response
}

// Utility

async getAddressStatus(address: string): Promise<AddressStatus | undefined> {
const params = {
TableName: MAINNET_CHECK_TRACKER_TABLE,
Key: { address }
};
const command = new GetCommand(params);

try {
const data = await this.documentClient.send(command);
if (!data.Item) {
return undefined;
}

return {
checkCount: data.Item.checkCount,
lastUsedNonce: data.Item.lastUsedNonce,
}
} catch (error) {
console.error(JSON.stringify({
date: new Date(),
type: 'GetAddressStatusError',
item: error
}));
}
}

async updateAddressStatus(address: string, checkCount: number = 0, nonce?: number): Promise<AddressStatus | undefined> {
// if nonce is not provided, fetch from network
if (!nonce) {
const currentNonce = await getNonce(this.RPC, address)
if (!currentNonce) {
return
}
nonce = currentNonce
}

const params = {
TableName: MAINNET_CHECK_TRACKER_TABLE,
Item: {
address,
lastUsedNonce: nonce,
checkCount,
}
};

const command = new PutCommand(params);

try {
await this.documentClient.send(command);
} catch (error) {
console.error(JSON.stringify({
date: new Date(),
type: 'PuttingAddressTrackerError',
item: error
}));
return
}

return {
checkCount,
lastUsedNonce: nonce,
}
}
}
4 changes: 3 additions & 1 deletion server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
checkMainnetBalancePipeline,
pipelineFailureMessage
} from './utils/pipelineChecks'
import { MainnetCheckService } from './mainnetCheckService'

dotenv.config()

Expand Down Expand Up @@ -64,6 +65,7 @@ new RateLimiter(app, [
})

const couponService = new CouponService(couponConfig)
const mainnetCheckService = new MainnetCheckService(MAINNET_BALANCE_CHECK_RPC)

const captcha: VerifyCaptcha = new VerifyCaptcha(app, process.env.CAPTCHA_SECRET!, process.env.V2_CAPTCHA_SECRET!)

Expand Down Expand Up @@ -152,7 +154,7 @@ router.post('/sendToken', captcha.middleware, async (req: any, res: any) => {
!pipelineValidity.isValid && couponCheckEnabled && await checkCouponPipeline(couponService, pipelineValidity, faucetConfigId, coupon)

// don't check mainnet balance, if coupon is provided
!pipelineValidity.isValid && !coupon && mainnetCheckEnabled && await checkMainnetBalancePipeline(pipelineValidity, MAINNET_BALANCE_CHECK_RPC, address)
!pipelineValidity.isValid && !coupon && mainnetCheckEnabled && await checkMainnetBalancePipeline(mainnetCheckService, pipelineValidity, MAINNET_BALANCE_CHECK_RPC, address)

if (
(mainnetCheckEnabled || couponCheckEnabled) &&
Expand Down
19 changes: 19 additions & 0 deletions utils/mainnetBalanceCheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,22 @@ export async function checkMainnetBalance(rpc: string, address: string, threshol
}
return response
}

export async function getNonce(rpc: string, address: string): Promise<number | undefined> {
try {
const response = await axios.post<any, any>(rpc, {
jsonrpc: "2.0",
method: "eth_getTransactionCount",
params: [address, "latest"],
id: 1,
})
return parseInt(response.data.result)
} catch(err) {
console.error(JSON.stringify({
date: new Date(),
type: 'NonceCheckError',
item: err
}))
return undefined
}
}
21 changes: 17 additions & 4 deletions utils/pipelineChecks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CouponService } from '../CouponService/couponService'
import { MainnetCheckService } from '../mainnetCheckService'
import { checkMainnetBalance } from './mainnetBalanceCheck'

export enum PIPELINE_CHECKS {
Expand Down Expand Up @@ -48,12 +49,24 @@ export async function checkCouponPipeline(
}
}

export async function checkMainnetBalancePipeline(pipelineCheckValidity: PipelineCheckValidity, rpc: string, address: string) {
export async function checkMainnetBalancePipeline(
mainnetCheckService: MainnetCheckService,
pipelineCheckValidity: PipelineCheckValidity,
rpc: string,
address: string,
) {
const {isValid, balance} = await checkMainnetBalance(rpc, address)
if (isValid) {
pipelineCheckValidity.isValid = true
pipelineCheckValidity.checkPassedType = PIPELINE_CHECKS.MAINNET_BALANCE
pipelineCheckValidity.mainnetBalance = balance
const mainnetAddressValidity = await mainnetCheckService.checkAddressValidity(address)
if (mainnetAddressValidity.isValid) {
pipelineCheckValidity.isValid = true
pipelineCheckValidity.checkPassedType = PIPELINE_CHECKS.MAINNET_BALANCE
pipelineCheckValidity.mainnetBalance = balance
} else if (mainnetAddressValidity.internalError) {
pipelineCheckValidity.errorMessage = "Some internal error occurred during mainnet check. "
} else {
pipelineCheckValidity.errorMessage = "Address has exhausted maximum balance checks! Please do some mainnet transactions. "
}
} else {
pipelineCheckValidity.errorMessage = "Mainnet balance check failed! "
}
Expand Down

0 comments on commit 59bdfb7

Please sign in to comment.