Skip to content

Commit

Permalink
More efficient interactionNonce -> ethTxHash (#232)
Browse files Browse the repository at this point in the history
  • Loading branch information
joss-aztec authored Sep 8, 2022
1 parent 42b6703 commit 2e5ce82
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 51 deletions.
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

0 comments on commit 2e5ce82

Please sign in to comment.