Skip to content

Commit 6ad9419

Browse files
authored
Merge pull request #480 from dappnode/fix/detect-unregistered-package-with-current-ethers
Detect an unregistered package with current ethers
2 parents 072d289 + a604f85 commit 6ad9419

4 files changed

Lines changed: 85 additions & 2 deletions

File tree

‎src/tasks/generatePublishTxs/index.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import repoAbi from "../../contracts/RepoAbi.json" with { type: "json" };
1212
import { VerbosityOptions } from "../../commands/build/types.js";
1313
import { getRegistryAddressFromDnpName } from "./getRegistryAddressFromEns.js";
1414
import { Repo } from "./types.js";
15+
import { isPackageNotRegisteredError } from "../../utils/isPackageNotRegisteredError.js";
1516

1617
const isZeroAddress = (address: string): boolean => parseInt(address) === 0;
1718

@@ -145,7 +146,8 @@ async function getRepoContractIfExists({
145146
try {
146147
return await apm.getRepoContract(ensName);
147148
} catch (e) {
148-
if (e.message.includes("Could not resolve name")) return null;
149+
// No repository yet: the caller builds a newRepoWithVersion transaction.
150+
if (isPackageNotRegisteredError(e)) return null;
149151
else throw e;
150152
}
151153
}

‎src/tasks/publish/subtasks/getFetchApmVersionsTask.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ListrTask } from "listr";
22
import { PackageToBuildProps, ListrContextPublish, ReleaseType } from "../../../types.js";
33
import { getNextVersionFromApm } from "../../../utils/versions/getNextVersionFromApm.js";
44
import { Manifest } from "@dappnode/types";
5+
import { isPackageNotRegisteredError } from "../../../utils/isPackageNotRegisteredError.js";
56

67
export function getFetchNextVersionsFromApmTask({
78
releaseType,
@@ -53,7 +54,9 @@ export async function getNextPackageVersion({
5354
ensName: name
5455
});
5556
} catch (e) {
56-
if (e.message.includes("NOREPO")) return version;
57+
// Publishing this package for the first time: there is no repository to
58+
// read a current version from, so start from the manifest version.
59+
if (isPackageNotRegisteredError(e)) return version;
5760
else throw e;
5861
}
5962
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* True when an APM lookup failed because the package has no repository on the
3+
* registry yet, rather than because something actually went wrong.
4+
*
5+
* This is the normal state when publishing a package for the first time, and
6+
* callers use it to fall back to the version declared in the manifest.
7+
*
8+
* The condition has to be recognised from the error message because it surfaces
9+
* differently depending on the ethers version resolving the ENS name:
10+
*
11+
* - `NOREPO` is raised by @dappnode/toolkit when the name resolves but carries
12+
* no repository.
13+
* - `Could not resolve name` is what ethers v6 reported for an unresolvable
14+
* name in earlier releases.
15+
* - `ResolverNotFound` is the ENS custom error current ethers decodes and
16+
* raises instead. Without it, a first publish fails with
17+
* `execution reverted: ResolverNotFound(bytes)`.
18+
*
19+
* Errors that merely reverted for some other reason are deliberately not
20+
* matched, so a genuine registry failure is never mistaken for an unpublished
21+
* package.
22+
*/
23+
export function isPackageNotRegisteredError(error: unknown): boolean {
24+
const message =
25+
error instanceof Error ? error.message : typeof error === "string" ? error : "";
26+
27+
return (
28+
message.includes("NOREPO") ||
29+
message.includes("Could not resolve name") ||
30+
message.includes("ResolverNotFound")
31+
);
32+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { expect } from "chai";
2+
import { isPackageNotRegisteredError } from "../../src/utils/isPackageNotRegisteredError.js";
3+
4+
describe("isPackageNotRegisteredError", () => {
5+
// Each string is what an APM lookup for an unpublished package actually
6+
// produced, so the first publish must keep working across ethers versions.
7+
const notRegistered: { name: string; message: string }[] = [
8+
{
9+
name: "toolkit reports no repository",
10+
message: "Could not resolve name foo.dnp.dappnode.eth: NOREPO"
11+
},
12+
{
13+
name: "ethers v6 before the custom-error decoding",
14+
message: "Could not resolve name foo.dnp.dappnode.eth"
15+
},
16+
{
17+
name: "current ethers decodes the ENS custom error",
18+
message: "execution reverted: ResolverNotFound(bytes)"
19+
}
20+
];
21+
22+
for (const { name, message } of notRegistered) {
23+
it(`detects an unregistered package: ${name}`, () => {
24+
expect(isPackageNotRegisteredError(new Error(message))).to.equal(true);
25+
});
26+
}
27+
28+
// A registry problem must never be mistaken for an unpublished package, or
29+
// publish would silently restart versioning from the manifest.
30+
const realFailures: { name: string; message: string }[] = [
31+
{ name: "unrelated revert", message: "execution reverted: Unauthorized()" },
32+
{ name: "network failure", message: "could not detect network" },
33+
{ name: "bad response", message: "missing response for request" }
34+
];
35+
36+
for (const { name, message } of realFailures) {
37+
it(`does not swallow a real failure: ${name}`, () => {
38+
expect(isPackageNotRegisteredError(new Error(message))).to.equal(false);
39+
});
40+
}
41+
42+
it("tolerates a non-Error value", () => {
43+
expect(isPackageNotRegisteredError(undefined)).to.equal(false);
44+
expect(isPackageNotRegisteredError("execution reverted: ResolverNotFound(bytes)")).to.equal(true);
45+
});
46+
});

0 commit comments

Comments
 (0)