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

More efficient interactionNonce -> ethTxHash #232

Merged
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
21 changes: 18 additions & 3 deletions src/client/element/element-bridge-data.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
IVault__factory,
} from "../../../typechain-types";
import { AztecAssetType } from "../bridge-data";
import { ChainProperties, ElementBridgeData } from "./element-bridge-data";
import { ElementBridgeData } from "./element-bridge-data";

jest.mock("../aztec/provider", () => ({
createWeb3Provider: jest.fn(),
Expand Down Expand Up @@ -139,6 +139,11 @@ describe("element bridge data", () => {
},
} as any;

const prepareGetTransactionReceiptMockForNonce = (nonce: number) => {
const event = defiEvents.find(x => x.nonce === nonce);
(elementBridge.provider as any).getTransactionReceipt = async () => ({ blockNumber: event?.blockNumber });
};

const rollupContract: Mockify<IRollupProcessor> = {
queryFilter: jest.fn().mockImplementation((filter: any, from: number, to: number) => {
const nonce = filter.interactionNonce;
Expand Down Expand Up @@ -172,12 +177,17 @@ describe("element bridge data", () => {
element: ElementBridge = elementBridge as any,
balancer: IVault = balancerContract as any,
rollup: IRollupProcessor = rollupContract as any,
chainProperties: ChainProperties = { eventBatchSize: 10 },
) => {
ElementBridge__factory.connect = () => element as any;
IVault__factory.connect = () => balancer as any;
IRollupProcessor__factory.connect = () => rollup as any;
return ElementBridgeData.create({} as any, EthAddress.ZERO, EthAddress.ZERO, EthAddress.ZERO, chainProperties); // can pass in dummy values here as the above factories do all of the work
return ElementBridgeData.create(
{} as any,
EthAddress.ZERO,
EthAddress.ZERO,
EthAddress.ZERO,
"https://api.aztec.network/aztec-connect-prod/falafel/graphql",
); // can pass in dummy values here as the above factories do all of the work
};

it("should return the correct amount of interest", async () => {
Expand All @@ -192,6 +202,7 @@ describe("element bridge data", () => {
const totalInput = defiEvents.find(x => x.nonce === 56)!.totalInputValue;
const userShareDivisor = 2n;
const defiEvent = getDefiEvent(56)!;
prepareGetTransactionReceiptMockForNonce(56);
const [daiValue] = await elementBridgeData.getInteractionPresentValue(56, totalInput / userShareDivisor);
const delta = outputValue - defiEvent.totalInputValue;
const timeElapsed = BigInt(now) - startDate;
Expand All @@ -217,6 +228,9 @@ describe("element bridge data", () => {
const totalInput = defiEvents.find(x => x.nonce === nonce)!.totalInputValue;
const userShareDivisor = 2n;

// Update mock return
(elementBridge.provider as any).getTransactionReceipt = async () => ({ blockNumber: 59 });
prepareGetTransactionReceiptMockForNonce(nonce);
const [daiValue] = await elementBridgeData.getInteractionPresentValue(nonce, totalInput / userShareDivisor);
const delta = interactions[nonce].quantityPT.toBigInt() - defiEvent.totalInputValue;
const timeElapsed = BigInt(now) - startDate;
Expand All @@ -237,6 +251,7 @@ describe("element bridge data", () => {

it("requesting the present value of an unknown interaction should return empty values", async () => {
const elementBridgeData = createElementBridgeData();
prepareGetTransactionReceiptMockForNonce(57);
const values = await elementBridgeData.getInteractionPresentValue(57, 0n);
expect(values).toStrictEqual([]);
});
Expand Down
91 changes: 43 additions & 48 deletions src/client/element/element-bridge-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { AsyncDefiBridgeProcessedEvent } from "../../../typechain-types/IRollupProcessor";
import { createWeb3Provider } from "../aztec/provider";
import { AuxDataConfig, AztecAsset, BridgeDataFieldGetters, SolidityType } from "../bridge-data";
import "isomorphic-fetch";

export type BatchSwapStep = {
poolId: string;
Expand All @@ -34,14 +35,6 @@ export type FundManagement = {
toInternalBalance: boolean;
};

// Some operations on this class require us to scan back over the blockchain
// to find published events. This is done in batches of blocks. The batch size in this set of properties
// determines how many blocks to request in each batch.
// Users of this class can customise this to their provider requirements
export type ChainProperties = {
eventBatchSize: number;
};

interface EventBlock {
nonce: number;
blockNumber: number;
Expand Down Expand Up @@ -77,21 +70,21 @@ export class ElementBridgeData implements BridgeDataFieldGetters {
private elementBridgeContract: ElementBridge,
private balancerContract: IVault,
private rollupContract: IRollupProcessor,
private chainProperties: ChainProperties,
private falafelGraphQlEndpoint: string,
) {}

static create(
provider: EthereumProvider,
elementBridgeAddress: EthAddress,
balancerAddress: EthAddress,
rollupContractAddress: EthAddress,
chainProperties: ChainProperties = { eventBatchSize: 10000 },
falafelGraphQlEndpoint: string,
) {
const ethersProvider = createWeb3Provider(provider);
const elementBridgeContract = ElementBridge__factory.connect(elementBridgeAddress.toString(), ethersProvider);
const rollupContract = IRollupProcessor__factory.connect(rollupContractAddress.toString(), ethersProvider);
const vaultContract = IVault__factory.connect(balancerAddress.toString(), ethersProvider);
return new ElementBridgeData(elementBridgeContract, vaultContract, rollupContract, chainProperties);
return new ElementBridgeData(elementBridgeContract, vaultContract, rollupContract, falafelGraphQlEndpoint);
}

private async storeEventBlocks(events: AsyncDefiBridgeProcessedEvent[]) {
Expand Down Expand Up @@ -123,55 +116,57 @@ export class ElementBridgeData implements BridgeDataFieldGetters {
return this.elementBridgeContract.provider.getBlock("latest");
}

private async getBlockNumber(interactionNonce: number) {
const id = Math.floor(interactionNonce / 32);
const query = `query Block($id: Int!) {
block: rollup(id: $id) {
ethTxHash
}
}`;

const response = await fetch(this.falafelGraphQlEndpoint, {
headers: { "Content-Type": "application/json" },
method: "POST",
body: JSON.stringify({
query,
operationName: "Block",
variables: { id },
}),
});

const data = await response.json();
const txhash = `0x${data["data"]["block"]["ethTxHash"]}`;
const tx = await this.elementBridgeContract.provider.getTransactionReceipt(txhash);
return tx.blockNumber;
}

private async findDefiEventForNonce(interactionNonce: number) {
// start off with the earliest possible block being the block in which the tranche was first deployed
let earliestBlockNumber = Number(
await this.elementBridgeContract.getTrancheDeploymentBlockNumber(interactionNonce),
);
// start with the last block being the current block
const lastBlock = await this.getCurrentBlock();
let latestBlockNumber = lastBlock.number;
// try and find previously stored events that encompass the nonce we are looking for
// also if we find the exact nonce then just return the stored data
for (let i = 0; i < this.interactionBlockNumbers.length; i++) {
const storedBlock = this.interactionBlockNumbers[i];
if (storedBlock.nonce == interactionNonce) {
return storedBlock;
}
if (storedBlock.nonce < interactionNonce) {
earliestBlockNumber = storedBlock.blockNumber;
}
if (storedBlock.nonce > interactionNonce) {
// this is the first block beyond the one we are looking for, we can break here
latestBlockNumber = storedBlock.blockNumber;
break;
}
}

let end = latestBlockNumber;
let start = end - (this.chainProperties.eventBatchSize - 1);
start = Math.max(start, earliestBlockNumber);

while (end > earliestBlockNumber) {
const events = await this.rollupContract.queryFilter(
this.rollupContract.filters.AsyncDefiBridgeProcessed(undefined, interactionNonce),
start,
end,
);
// capture these event markers
await this.storeEventBlocks(events);
// there should just be one event, the one we are searching for. but to be sure we will process everything received
for (const event of events) {
const newEventBlock = await decodeEvent(event);
if (newEventBlock.nonce == interactionNonce) {
return newEventBlock;
}
}
const start = await this.getBlockNumber(interactionNonce);
const end = start + 1;

// if we didn't find an event then go round again but search further back in time
end = start - 1;
start = end - (this.chainProperties.eventBatchSize - 1);
start = Math.max(start, earliestBlockNumber);
const events = await this.rollupContract.queryFilter(
this.rollupContract.filters.AsyncDefiBridgeProcessed(undefined, interactionNonce),
start,
end,
);
// capture these event markers
await this.storeEventBlocks(events);
// there should just be one event, the one we are searching for. but to be sure we will process everything received
for (const event of events) {
const newEventBlock = await decodeEvent(event);
if (newEventBlock.nonce == interactionNonce) {
return newEventBlock;
}
}
}

Expand Down