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

Automated Orders: Scheduler #1069

Merged
merged 19 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from 12 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
30 changes: 30 additions & 0 deletions automated-orders/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# syntax=docker/dockerfile:1

FROM node:20-slim AS base
WORKDIR /app
COPY ./yarn.lock yarn.lock
COPY ./package.json package.json
COPY ./tsconfig.json tsconfig.json
artur-abliazimov marked this conversation as resolved.
Show resolved Hide resolved

FROM base AS libs
COPY ./packages/utils-library packages/utils-library
COPY ./packages/blockchain-library packages/blockchain-library
RUN yarn install --frozen-lockfile
RUN yarn build:libs
RUN rm -rf packages/*/src packages/*/node_modules packages/*/tsconfig*
artur-abliazimov marked this conversation as resolved.
Show resolved Hide resolved

FROM base AS scheduler
COPY ./packages/scheduler packages/scheduler
COPY --from=libs ["/app/packages", "./packages"]
RUN yarn install --frozen-lockfile
RUN yarn build:scheduler
RUN rm -rf packages/*/src packages/*/node_modules packages/*/tsconfig*
CMD ["yarn", "scheduler"]

FROM base AS relayer
COPY ./packages/relayer packages/relayer
COPY --from=libs ["/app/packages", "./packages"]
RUN yarn install --frozen-lockfile
RUN yarn build:relayer
RUN rm -rf packages/*/src packages/*/node_modules packages/*/tsconfig*
CMD ["yarn", "relayer"]
14 changes: 9 additions & 5 deletions automated-orders/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@
```bash
# example below

ETHEREUM_NODE_RPC=https://localhost:1122
ETHEREUM_REGISTRY_ADDRESS=0x
ETHEREUM_NODE_RPC="https://localhost:1133"
EVMOS_NODE_RPC="https://localhost:1122"
EVMOS_REGISTRY_ADDRESS="0x0000000000000000000000000000000000000000"
EVMOS_EVENTS_REGISTRY_START_POLLING_BLOCK=7055543
EVMOS_EVENTS_POLLING_INTERVAL_MSEC=100
EVMOS_EVENTS_POLLING_BLOCKS=1000
EVMOS_EVENTS_ORDER_RETRY_ATTEMPTS=3
EVMOS_EVENTS_CACHE_SIZE=1000000
EVMOS_CALLER_PRIVATE_KEY="CALLER_PRIVATE_KEY"
artur-abliazimov marked this conversation as resolved.
Show resolved Hide resolved
```

## @warden-automated-orders/relayer

```bash
# example below

ETHEREUM_NODE_RPC=https://localhost:1122
WARDEN_RPC_URL=https://localhost:1133
WARDEN_POLLING_INTERVAL_MSEC=100
```
9 changes: 8 additions & 1 deletion automated-orders/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@
],
"scripts": {
"build": "yarn lint && tsc --build --force --verbose",
"lint": "eslint --ext .ts --ignore-path .eslintignore ."
"lint": "eslint --ext .ts --ignore-path .eslintignore .",
"build:blockchain-lib": "yarn workspace @warden-automated-orders/blockchain build",
"build:utils": "yarn workspace @warden-automated-orders/utils build",
"build:libs": "yarn build:utils && yarn build:blockchain-lib",
"build:scheduler": "yarn workspace @warden-automated-orders/scheduler build",
"build:relayer": "yarn workspace @warden-automated-orders/relayer build",
"scheduler": "yarn workspace @warden-automated-orders/scheduler start",
"relayer": "yarn workspace @warden-automated-orders/relayer start"
},
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
Expand Down
1 change: 1 addition & 0 deletions automated-orders/packages/blockchain-library/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"dependencies": {
"@wardenprotocol/wardenjs": "0.0.11",
"@tanstack/react-query": "^4.36.1",
"lru-cache": "^11.0.2",
"web3": "^4.14.0"
},
"devDependencies": {
Expand Down
142 changes: 140 additions & 2 deletions automated-orders/packages/blockchain-library/src/clients/evm.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,153 @@
import { Web3 } from 'web3';
import { delay } from '@warden-automated-orders/utils';
import { LRUCache } from 'lru-cache';
import { AbiEventFragment, AbiFunctionFragment, Bytes, EventLog, FeeData, Transaction, Web3 } from 'web3';

import { IEvmConfiguration } from '../types/evm/configuration.js';
import { IEventPollingConfiguration } from '../types/evm/pollingConfiguration.js';

export class EvmClient {
web3: Web3;
signer: string;
eventsFromBlocks: Map<string, bigint>;
events: LRUCache<string, EventLog>;

artur-abliazimov marked this conversation as resolved.
Show resolved Hide resolved
constructor(private configuration: IEvmConfiguration) {
this.web3 = new Web3(this.configuration.rpcURL);
this.eventsFromBlocks = new Map<string, bigint>();

if (this.configuration.eventsCacheSize) {
this.events = new LRUCache<string, EventLog>({
max: this.configuration.eventsCacheSize,
});
}

if (this.configuration.callerPrivateKey) {
this.signer = this.web3.eth.accounts.privateKeyToAccount(this.configuration.callerPrivateKey!).address;
}
}

async broadcastTx(): Promise<void> {
public async broadcastTx(): Promise<void> {
// TODO: implementation
}

public async *pollEvents<T extends EventLog>(
contractAddress: string,
startBlock: bigint,
eventAbi: AbiEventFragment,
config: IEventPollingConfiguration,
): AsyncGenerator<T> {
while (true) {
await delay(config.pollingIntervalMsec);

for (const event of this.events) {
yield event[1] as T;
}

artur-abliazimov marked this conversation as resolved.
Show resolved Hide resolved
const contract = new this.web3.eth.Contract([eventAbi], contractAddress);
const endBlock = await this.web3.eth.getBlockNumber();

let fromBlock = BigInt(this.eventsFromBlocks.get(eventAbi.name) ?? startBlock);
let toBlock = endBlock;

do {
if (fromBlock + config.pollingBlocks > endBlock) {
toBlock = endBlock;
} else {
toBlock = fromBlock + config.pollingBlocks;
}

const logs = (await contract.getPastEvents({
fromBlock: fromBlock,
toBlock: toBlock,
})) as EventLog[];

artur-abliazimov marked this conversation as resolved.
Show resolved Hide resolved
for (const log of logs) {
if (log.event !== eventAbi.name) {
continue;
}

const event = log as T;

if (!event) {
continue;
}

this.events.set(this.getEventId(event), event);

yield event;
}

fromBlock = toBlock;

this.eventsFromBlocks.set(eventAbi.name, fromBlock);
} while (fromBlock < endBlock);
}
}

public async isContract(address: string): Promise<boolean> {
const code = await this.web3.eth.getCode(address);
return Buffer.from(code.replace('0x', ''), 'hex').length > 0;
}

public async callView<T>(contractAddress: string, functionAbi: AbiFunctionFragment, args: unknown[]): Promise<T> {
const contract = new this.web3.eth.Contract([functionAbi], contractAddress);
const result = await contract.methods[functionAbi.name](...args).call();

return result as T;
}

public getEventId(event: EventLog): string {
return `bk_${event.blockNumber}_ix_${event.logIndex}`;
}

public async getNextNonce(account: string): Promise<bigint> {
const transactionsCount = await this.web3.eth.getTransactionCount(account);

return transactionsCount;
}

public async getGasFees(
from: string,
to: string,
data: Bytes,
value: bigint,
): Promise<{ feeData: FeeData; gasLimit: bigint }> {
const feeData = await this.web3.eth.calculateFeeData();

const gasLimit = await this.web3.eth.estimateGas({
artur-abliazimov marked this conversation as resolved.
Show resolved Hide resolved
from: from,
to: to,
value: value,
data: data,
});

return { feeData, gasLimit };
}

public async sendTransaction(
contractAddress: string,
functionAbi: AbiFunctionFragment,
args: unknown[],
): Promise<boolean> {
const contract = new this.web3.eth.Contract([functionAbi], contractAddress);
const data = contract.methods[functionAbi.name](...args).encodeABI();

const nonce = await this.getNextNonce(this.signer);
const gas = await this.getGasFees(this.signer, contractAddress, data, 0n);

const tx: Transaction = {
from: this.signer,
to: contractAddress,
data: data,
gas: gas.gasLimit,
maxFeePerGas: gas.feeData.maxFeePerGas,
maxPriorityFeePerGas: gas.feeData.maxPriorityFeePerGas,
nonce: nonce,
};

const signedTx = await this.web3.eth.accounts.signTransaction(tx, this.configuration.callerPrivateKey!);
const receipt = await this.web3.eth.sendSignedTransaction(signedTx.rawTransaction!);

return receipt.status == 1;
}
}
artur-abliazimov marked this conversation as resolved.
Show resolved Hide resolved
46 changes: 0 additions & 46 deletions automated-orders/packages/blockchain-library/src/clients/warden.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,5 @@
import * as utils from '@warden-automated-orders/utils';
import { warden } from '@wardenprotocol/wardenjs';
import { SignRequest, SignRequestStatus } from '@wardenprotocol/wardenjs/codegen/warden/warden/v1beta3/signature';

import { IWardenConfiguration } from '../types/warden/configuration.js';
import { INewSignatureRequest } from '../types/warden/newSignatureRequest.js';

const { delay } = utils;
const { createRPCQueryClient } = warden.ClientFactory;

export class WardenClient {
constructor(private configuration: IWardenConfiguration) {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not used anywhere


async query() {
return await createRPCQueryClient({ rpcEndpoint: this.configuration.rpcURL });
}

async *pollSignatureRequests(): AsyncGenerator<INewSignatureRequest> {
while (true) {
await delay(this.configuration.pollingIntervalMsec);

// TODO: query paged fulfilled sign requests
// const query = (await this.query()).warden.warden.v1beta3;
const signRequests: SignRequest[] = [
{
id: 1n,
creator: 'string',
keyId: 1n,
dataForSigning: new Uint8Array(),
status: SignRequestStatus.SIGN_REQUEST_STATUS_FULFILLED,
signedData: new Uint8Array(),
rejectReason: 'string',
encryptionKey: new Uint8Array(),
deductedKeychainFees: [],
},
];

for (let i = 0; i < signRequests.length; i++) {
const request = signRequests[i];

if (!request.signedData) {
continue;
}

yield {
signedData: request.signedData!,
};
}
}
}
}
3 changes: 3 additions & 0 deletions automated-orders/packages/blockchain-library/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export { WardenClient } from './clients/warden.js';
export { EvmClient } from './clients/evm.js';
export { ChainIds } from './types/evm/constants.js';
export { NewSignatureProcessor } from './processors/newSignatureProcessor.js';
export { OrderProcessor } from './processors/orderProcessor.js';
export { INewSignatureRequest } from './types/warden/newSignatureRequest.js';
export { OrderRegistered, OrderRegisteredAbi } from './types/order/events.js';
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,14 @@ export class NewSignatureProcessor extends Processor<INewSignatureRequest> {
super(generator);
}

async handle(data: INewSignatureRequest): Promise<boolean> {
async handle(data: INewSignatureRequest): Promise<void> {
try {
logInfo(`New Signature request ${serialize(data)}`);

// TODO: implementation
await this.evm.broadcastTx();
artur-abliazimov marked this conversation as resolved.
Show resolved Hide resolved

return true;
} catch (error) {
logError(`New Signature error ${serialize(data)}. Error: ${error}, Stack trace: ${error.stack}`);

return false;
}
}
artur-abliazimov marked this conversation as resolved.
Show resolved Hide resolved
}
Loading
Loading