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

Mainnet address tracker service #137

Merged
merged 2 commits into from
Feb 9, 2024
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
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
Loading