Skip to content

Commit 2d03bcd

Browse files
authored
fix: mergeBlockNumberRange function logic (#1734)
1 parent 9ef8ab7 commit 2d03bcd

6 files changed

Lines changed: 158 additions & 39 deletions

File tree

.changeset/purple-lamps-show.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@ensnode/ensnode-sdk": minor
3+
"@ensnode/ponder-sdk": minor
4+
---
5+
6+
Fixed logic applied while building indexed blockrange for a chain.

packages/ensnode-sdk/src/shared/blockrange.test.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,17 +168,39 @@ describe("Blockrange", () => {
168168
});
169169
});
170170

171-
it("ignores unbounded range when other bounds exist", () => {
171+
it("keeps start unbounded when any merged range has no start block", () => {
172+
const result = mergeBlockNumberRanges(
173+
buildBlockNumberRange(undefined, 200),
174+
buildBlockNumberRange(80, 250),
175+
);
176+
177+
expect(result).toStrictEqual({
178+
rangeType: RangeTypeIds.RightBounded,
179+
endBlock: 250,
180+
});
181+
});
182+
183+
it("keeps end unbounded when any merged range has no end block", () => {
184+
const result = mergeBlockNumberRanges(
185+
buildBlockNumberRange(100, 200),
186+
buildBlockNumberRange(80, undefined),
187+
);
188+
189+
expect(result).toStrictEqual({
190+
rangeType: RangeTypeIds.LeftBounded,
191+
startBlock: 80,
192+
});
193+
});
194+
195+
it("returns unbounded range when any merged range is fully unbounded", () => {
172196
const result = mergeBlockNumberRanges(
173197
buildBlockNumberRange(100, 200),
174198
buildBlockNumberRange(undefined, undefined),
175199
buildBlockNumberRange(undefined, 150),
176200
);
177201

178202
expect(result).toStrictEqual({
179-
rangeType: RangeTypeIds.Bounded,
180-
startBlock: 100,
181-
endBlock: 200,
203+
rangeType: RangeTypeIds.Unbounded,
182204
});
183205
});
184206
});

packages/ensnode-sdk/src/shared/blockrange.ts

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,9 @@ export function buildBlockNumberRange(
148148
* Merge multiple block number ranges into a single range.
149149
*
150150
* The resulting range is a union that covers all input ranges:
151-
* - Uses the minimum defined start block (undefined if no ranges define a start block)
152-
* - Uses the maximum defined end block (undefined if no ranges define an end block)
151+
* - Uses the minimum start block when every input range has a start block
152+
* - Uses the maximum end block when every input range has an end block
153+
* - Leaves a side unbounded when any input range is unbounded on that side
153154
*
154155
* Returns an unbounded range if no ranges are provided.
155156
*
@@ -163,21 +164,38 @@ export function mergeBlockNumberRanges(...ranges: BlockNumberRange[]): BlockNumb
163164

164165
let minStartBlock: BlockNumber | undefined;
165166
let maxEndBlock: BlockNumber | undefined;
167+
let hasUnboundedStart = false;
168+
let hasUnboundedEnd = false;
166169

167170
for (const range of ranges) {
168-
// Update min start block (lower values win, undefined is ignored)
169-
if (range.startBlock !== undefined) {
170-
if (minStartBlock === undefined || range.startBlock < minStartBlock) {
171-
minStartBlock = range.startBlock;
172-
}
171+
if (range.startBlock === undefined) {
172+
hasUnboundedStart = true;
173+
} else if (minStartBlock === undefined || range.startBlock < minStartBlock) {
174+
minStartBlock = range.startBlock;
173175
}
174176

175-
// Update max end block (higher values win, undefined is ignored)
176-
if (range.endBlock !== undefined) {
177-
if (maxEndBlock === undefined || range.endBlock > maxEndBlock) {
178-
maxEndBlock = range.endBlock;
179-
}
177+
if (range.endBlock === undefined) {
178+
hasUnboundedEnd = true;
179+
} else if (maxEndBlock === undefined || range.endBlock > maxEndBlock) {
180+
maxEndBlock = range.endBlock;
180181
}
182+
183+
// Early return if the merged range is already unbounded
184+
if (hasUnboundedStart && hasUnboundedEnd) {
185+
return buildBlockNumberRange(undefined, undefined);
186+
}
187+
}
188+
189+
// The merged range has an unbounded start if any input range has
190+
// an unbounded start
191+
if (hasUnboundedStart) {
192+
minStartBlock = undefined;
193+
}
194+
195+
// The merged range has an unbounded end if any input range has
196+
// an unbounded end
197+
if (hasUnboundedEnd) {
198+
maxEndBlock = undefined;
181199
}
182200

183201
return buildBlockNumberRange(minStartBlock, maxEndBlock);

packages/ensnode-sdk/src/shared/config/indexed-blockranges.test.ts

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import * as datasources from "@ensnode/datasources";
44
import { type DatasourceName, DatasourceNames, ENSNamespaceIds } from "@ensnode/datasources";
55

66
import { PluginName } from "../../ensindexer/config/types";
7-
import { buildBlockNumberRange } from "../blockrange";
7+
import { type BlockNumberRangeWithStartBlock, buildBlockNumberRange } from "../blockrange";
8+
import type { ChainId } from "../types";
89
import { buildIndexedBlockranges } from "./indexed-blockranges";
910

1011
vi.mock("@ensnode/datasources", async () => {
@@ -69,13 +70,13 @@ describe("buildIndexedBlockranges()", () => {
6970
// Act
7071
const result = buildIndexedBlockranges(ENSNamespaceIds.Mainnet, pluginsRequiredDatasourceNames);
7172

73+
const expectedEntries = new Map<ChainId, BlockNumberRangeWithStartBlock>([
74+
[1, buildBlockNumberRange(80, undefined)],
75+
[8453, buildBlockNumberRange(5, 260)],
76+
]);
77+
7278
// Assert
73-
expect(result).toStrictEqual(
74-
new Map([
75-
[1, buildBlockNumberRange(80, 200)],
76-
[8453, buildBlockNumberRange(5, 260)],
77-
]),
78-
);
79+
expect(result).toStrictEqual(expectedEntries);
7980
});
8081

8182
it("keeps endBlock undefined when no contracts define it", () => {
@@ -110,6 +111,38 @@ describe("buildIndexedBlockranges()", () => {
110111
expect(result).toStrictEqual(new Map([[1, buildBlockNumberRange(90, undefined)]]));
111112
});
112113

114+
it("keeps endBlock undefined when only some contracts define it", () => {
115+
// Arrange
116+
const basenamesDatasourceConfig: unknown = {
117+
chain: { id: 8453 },
118+
contracts: {
119+
registry: { startBlock: 17571480 },
120+
reverseRegistrar: { startBlock: 18619035, endBlock: 35936564 },
121+
registrarController: { startBlock: 17575714 },
122+
},
123+
};
124+
125+
const datasourcesByName: Partial<
126+
Record<DatasourceName, ReturnType<typeof datasources.maybeGetDatasource>>
127+
> = {
128+
[DatasourceNames.Basenames]: datasourceMock(basenamesDatasourceConfig),
129+
};
130+
131+
maybeGetDatasourceMock.mockImplementation(
132+
(_namespace, datasourceName) => datasourcesByName[datasourceName as DatasourceName],
133+
);
134+
135+
const pluginsRequiredDatasourceNames = new Map([
136+
[PluginName.Basenames, [DatasourceNames.Basenames]],
137+
]);
138+
139+
// Act
140+
const result = buildIndexedBlockranges(ENSNamespaceIds.Mainnet, pluginsRequiredDatasourceNames);
141+
142+
// Assert
143+
expect(result).toStrictEqual(new Map([[8453, buildBlockNumberRange(17571480, undefined)]]));
144+
});
145+
113146
it("throws when a required datasource is missing", () => {
114147
// Arrange
115148
maybeGetDatasourceMock.mockReturnValue(undefined);

packages/ponder-sdk/src/blockrange.test.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,17 +168,39 @@ describe("Blockrange", () => {
168168
});
169169
});
170170

171-
it("ignores unbounded range when other bounds exist", () => {
171+
it("keeps start unbounded when any merged range has no start block", () => {
172+
const result = mergeBlockNumberRanges(
173+
buildBlockNumberRange(undefined, 200),
174+
buildBlockNumberRange(80, 250),
175+
);
176+
177+
expect(result).toStrictEqual({
178+
rangeType: RangeTypeIds.RightBounded,
179+
endBlock: 250,
180+
});
181+
});
182+
183+
it("keeps end unbounded when any merged range has no end block", () => {
184+
const result = mergeBlockNumberRanges(
185+
buildBlockNumberRange(100, 200),
186+
buildBlockNumberRange(80, undefined),
187+
);
188+
189+
expect(result).toStrictEqual({
190+
rangeType: RangeTypeIds.LeftBounded,
191+
startBlock: 80,
192+
});
193+
});
194+
195+
it("returns unbounded range when any merged range is fully unbounded", () => {
172196
const result = mergeBlockNumberRanges(
173197
buildBlockNumberRange(100, 200),
174198
buildBlockNumberRange(undefined, undefined),
175199
buildBlockNumberRange(undefined, 150),
176200
);
177201

178202
expect(result).toStrictEqual({
179-
rangeType: RangeTypeIds.Bounded,
180-
startBlock: 100,
181-
endBlock: 200,
203+
rangeType: RangeTypeIds.Unbounded,
182204
});
183205
});
184206
});

packages/ponder-sdk/src/blockrange.ts

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,9 @@ export function buildBlockNumberRange(
152152
* Merge multiple block number ranges into a single range.
153153
*
154154
* The resulting range is a union that covers all input ranges:
155-
* - Uses the minimum defined start block (undefined if no ranges define a start block)
156-
* - Uses the maximum defined end block (undefined if no ranges define an end block)
155+
* - Uses the minimum start block when every input range has a start block
156+
* - Uses the maximum end block when every input range has an end block
157+
* - Leaves a side unbounded when any input range is unbounded on that side
157158
*
158159
* Returns an unbounded range if no ranges are provided.
159160
*
@@ -167,21 +168,38 @@ export function mergeBlockNumberRanges(...ranges: BlockNumberRange[]): BlockNumb
167168

168169
let minStartBlock: BlockNumber | undefined;
169170
let maxEndBlock: BlockNumber | undefined;
171+
let hasUnboundedStart = false;
172+
let hasUnboundedEnd = false;
170173

171174
for (const range of ranges) {
172-
// Update min start block (lower values win, undefined is ignored)
173-
if (range.startBlock !== undefined) {
174-
if (minStartBlock === undefined || range.startBlock < minStartBlock) {
175-
minStartBlock = range.startBlock;
176-
}
175+
if (range.startBlock === undefined) {
176+
hasUnboundedStart = true;
177+
} else if (minStartBlock === undefined || range.startBlock < minStartBlock) {
178+
minStartBlock = range.startBlock;
177179
}
178180

179-
// Update max end block (higher values win, undefined is ignored)
180-
if (range.endBlock !== undefined) {
181-
if (maxEndBlock === undefined || range.endBlock > maxEndBlock) {
182-
maxEndBlock = range.endBlock;
183-
}
181+
if (range.endBlock === undefined) {
182+
hasUnboundedEnd = true;
183+
} else if (maxEndBlock === undefined || range.endBlock > maxEndBlock) {
184+
maxEndBlock = range.endBlock;
184185
}
186+
187+
// Early return if the merged range is already unbounded
188+
if (hasUnboundedStart && hasUnboundedEnd) {
189+
return buildBlockNumberRange(undefined, undefined);
190+
}
191+
}
192+
193+
// The merged range has an unbounded start if any input range has
194+
// an unbounded start
195+
if (hasUnboundedStart) {
196+
minStartBlock = undefined;
197+
}
198+
199+
// The merged range has an unbounded end if any input range has
200+
// an unbounded end
201+
if (hasUnboundedEnd) {
202+
maxEndBlock = undefined;
185203
}
186204

187205
return buildBlockNumberRange(minStartBlock, maxEndBlock);

0 commit comments

Comments
 (0)