chore: Add the feature for migration

This commit is contained in:
BluJ
2023-02-16 16:24:11 -07:00
parent df89119193
commit 6d1181eba3
12 changed files with 1150 additions and 531 deletions

View File

@@ -1 +1,7 @@
# embassy-sdk-ts
### Generate: Config class from legacy ConfigSpec
```sh
cat utils/test/config.json | deno run scripts/oldSpecToBuilder.ts "../../config/mod.ts" |deno fmt - > utils/test/output.ts
```

View File

@@ -1,43 +1,45 @@
import { Config } from "./config.ts";
import { Pointer } from "./pointer.ts";
import { Value } from "./value.ts";
import { expect } from "https://deno.land/x/expect@v0.2.9/mod.ts";
const { test } = Deno;
test("Pointer", () => {
test("String", () => {
const bitcoinPropertiesBuilt: {
"peer-tor-address": {
name: string;
description: string;
type: "pointer";
subtype: "package";
"package-id": string;
target: "tor-address";
interface: string;
description: string | null;
type: "string";
};
} = Config.withValue(
"peer-tor-address",
Value.pointer(
Pointer.packageTorAddress({
name: "Peer Tor Address",
} = Config.of(
{
"peer-tor-address": Value.string({
name: "Peer tor address",
default: "",
description: "The Tor address of the peer interface",
"package-id": "bitcoind",
interface: "peer",
warning: null,
nullable: false,
masked: true,
placeholder: null,
pattern: null,
"pattern-description": null,
textarea: null,
}),
),
},
).build();
expect(JSON.stringify(bitcoinPropertiesBuilt)).toEqual(
/*json*/ `{
"peer-tor-address": {
"type": "pointer",
"subtype": "package",
"target": "tor-address",
"name": "Peer Tor Address",
"type": "string",
"name": "Peer tor address",
"default": "",
"description": "The Tor address of the peer interface",
"package-id": "bitcoind",
"interface": "peer",
"warning": null
"warning": null,
"nullable": false,
"masked": true,
"placeholder": null,
"pattern": null,
"pattern-description": null,
"textarea": null
}}`
.replaceAll("\n", " ")
.replaceAll(/\s{2,}/g, "")

View File

@@ -1,8 +1,7 @@
import { BuilderExtract, IBuilder } from "./builder.ts";
import { Config } from "./config.ts";
import { Default, NullableDefault, NumberSpec, StringSpec } from "./value.ts";
import { Default, NumberSpec, StringSpec } from "./value.ts";
import { Description } from "./value.ts";
import * as T from "../types.ts";
import { Variants } from "./variants.ts";
import {
ConfigSpec,
@@ -65,7 +64,7 @@ export class List<A extends ValueSpecList> extends IBuilder<A> {
...a,
});
}
static objectV<
static obj<
A extends
& Description
& Default<Record<string, unknown>[]>
@@ -105,9 +104,10 @@ export class List<A extends ValueSpecList> extends IBuilder<A> {
range: string;
spec: {
tag: {
id: string;
id: B;
name: string;
description: null | string;
warning: null | string;
"variant-names": {
[key: string]: string;
};
@@ -118,6 +118,7 @@ export class List<A extends ValueSpecList> extends IBuilder<A> {
default: string;
};
},
B extends string,
>(a: A) {
const { spec: previousSpec, ...rest } = a;
const { variants: previousVariants, ...restSpec } = previousSpec;

View File

@@ -1,5 +1,4 @@
export { Config } from "./config.ts";
export { List } from "./list.ts";
export { Pointer } from "./pointer.ts";
export { Value } from "./value.ts";
export { Variants } from "./variants.ts";

View File

@@ -1,68 +0,0 @@
import { ValueSpec } from "../types/config-types.ts";
import { IBuilder } from "./builder.ts";
import { Description } from "./value.ts";
export class Pointer<A extends ValueSpec> extends IBuilder<A> {
static packageTorKey<
A extends Description & { "package-id": string; interface: string },
>(a: A) {
return new Pointer({
type: "pointer" as const,
subtype: "package" as const,
target: "tor-key" as const,
...a,
});
}
static packageTorAddress<
A extends Description & { "package-id": string; interface: string },
>(a: A) {
return new Pointer({
type: "pointer" as const,
subtype: "package" as const,
target: "tor-address" as const,
...a,
});
}
static packageLanAddress<
A extends Description & { "package-id": string; interface: string },
>(a: A) {
return new Pointer({
type: "pointer" as const,
subtype: "package" as const,
target: "lan-address" as const,
...a,
});
}
static packageConfig<
A extends Description & {
"package-id": string;
selector: string;
multi: boolean;
interface: string;
},
>(a: A) {
return new Pointer({
type: "pointer" as const,
subtype: "package" as const,
target: "config" as const,
...a,
});
}
static system<
A extends Description & {
"package-id": string;
selector: string;
multi: boolean;
interface: string;
},
>(
a: A,
) {
return new Pointer({
type: "pointer" as const,
subtype: "system" as const,
target: "system" as const,
...a,
});
}
}

View File

@@ -1,7 +1,6 @@
import { BuilderExtract, IBuilder } from "./builder.ts";
import { Config } from "./config.ts";
import { List } from "./list.ts";
import { Pointer } from "./pointer.ts";
import { Variants } from "./variants.ts";
import {
ConfigSpec,
@@ -27,23 +26,16 @@ export type Default<A> = {
default: A;
};
export type NullableDefault<A> = {
default?: A;
default: null | A;
};
export type StringSpec =
& {
copyable: boolean | null;
masked: boolean | null;
placeholder: string | null;
}
& (
| {
pattern: string;
"pattern-description": string;
}
// deno-lint-ignore ban-types
| {}
);
export type StringSpec = {
masked: boolean | null;
placeholder: string | null;
pattern: null | string;
"pattern-description": null | string;
textarea: boolean | null;
};
export type NumberSpec = {
range: string;
integral: boolean;
@@ -121,7 +113,7 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
& Default<string>
& {
tag: {
id: string;
id: B;
name: string;
description: string | null;
warning: string | null;
@@ -133,6 +125,7 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
"display-as": string | null;
"unique-by": UniqueBy;
},
B extends string,
>(a: A) {
const { variants: previousVariants, ...rest } = a;
const variants = previousVariants.build() as BuilderExtract<A["variants"]>;
@@ -143,9 +136,6 @@ export class Value<A extends ValueSpec> extends IBuilder<A> {
});
}
static pointer<A extends ValueSpec>(a: Pointer<A>) {
return new Value(a.build());
}
static list<A extends List<ValueSpecList>>(a: A) {
return new Value(a.build());
}

307
scripts/oldSpecToBuilder.ts Normal file
View File

@@ -0,0 +1,307 @@
// deno-lint-ignore-file no-explicit-any
import { readLines } from "https://deno.land/std@0.177.0/io/read_lines.ts";
import { camelCase } from "https://deno.land/x/case@2.1.1/mod.ts";
const list: string[] = [];
if (!Deno.isatty(Deno.stdin.rid)) {
for await (const line of readLines(Deno.stdin)) {
line && list.push(line);
}
}
console.log(`
import {Config, Value, List, Variants} from '${await Deno.args[0]}';
`);
const data = JSON.parse(list.join("\n"));
const namedConsts = new Set(["Config", "Value", "List"]);
newConst("config", convertConfigSpec(data));
function newConst(key: string, data: string) {
const variableName = getNextConstName(camelCase(key));
console.log(`export const ${variableName} = ${data};`);
return variableName;
}
function convertConfigSpec(data: any) {
let answer = "Config.of({";
for (const [key, value] of Object.entries(data)) {
const variableName = newConst(key, convertValueSpec(value));
answer += `"${key}": ${variableName},`;
}
return `${answer}});`;
}
function convertValueSpec(value: any): string {
switch (value.type) {
case "string": {
return `Value.string(${
JSON.stringify(
{
name: value.name || null,
default: value.default || null,
description: value.description || null,
warning: value.warning || null,
nullable: value.nullable || false,
masked: value.masked || null,
placeholder: value.placeholder || null,
pattern: value.pattern || null,
"pattern-description": value["pattern-description"] || null,
textarea: value.textarea || null,
},
null,
2,
)
})`;
}
case "number": {
return `Value.number(${
JSON.stringify(
{
name: value.name || null,
default: value.default || null,
description: value.description || null,
warning: value.warning || null,
nullable: value.nullable || false,
range: value.range || null,
integral: value.integral || false,
units: value.units || null,
placeholder: value.placeholder || null,
},
null,
2,
)
})`;
}
case "boolean": {
return `Value.boolean(${
JSON.stringify(
{
name: value.name || null,
default: value.default || false,
description: value.description || null,
warning: value.warning || null,
},
null,
2,
)
})`;
}
case "enum": {
return `Value.enum(${
JSON.stringify(
{
name: value.name || null,
description: value.description || null,
warning: value.warning || null,
default: value.default || null,
values: value.values || null,
"value-names": value["value-names"] || null,
},
null,
2,
)
})`;
}
case "object": {
const specName = newConst(
value.name + "_spec",
convertConfigSpec(value.spec),
);
return `Value.object({
name: ${JSON.stringify(value.name || null)},
description: ${JSON.stringify(value.description || null)},
warning: ${JSON.stringify(value.warning || null)},
default: ${JSON.stringify(value.default || null)},
"display-as": ${JSON.stringify(value["display-as"] || null)},
"unique-by": ${JSON.stringify(value["unique-by"] || null)},
spec: ${specName},
"value-names": ${JSON.stringify(value["value-names"] || {})},
})`;
}
case "union": {
const variants = newConst(
value.name + "_variants",
convertVariants(value.variants),
);
return `Value.union({
name: ${JSON.stringify(value.name || null)},
description: ${JSON.stringify(value.description || null)},
warning: ${JSON.stringify(value.warning || null)},
default: ${JSON.stringify(value.default || null)},
variants: ${variants},
tag: ${
JSON.stringify({
"id": value?.tag?.["id"] || null,
"name": value?.tag?.["name"] || null,
"description": value?.tag?.["description"] || null,
"warning": value?.tag?.["warning"] || null,
"variant-names": value?.tag?.["variant-names"] || {},
})
},
"display-as": ${JSON.stringify(value["display-as"] || null)},
"unique-by": ${JSON.stringify(value["unique-by"] || null)},
"variant-names": ${
JSON.stringify(value["variant-names"] as any || null)
},
})`;
}
case "list": {
const list = newConst(
value.name + "_list",
convertList(value),
);
return `Value.list(${list})`;
}
case "pointer": {
return "null as any";
}
}
throw Error(`Unknown type "${value.type}"`);
}
function convertList(value: any) {
switch (value.subtype) {
case "string": {
return `List.string(${
JSON.stringify(
{
name: value.name || null,
range: value.range || null,
spec: {
"masked": value?.spec?.["masked"] || null,
"placeholder": value?.spec?.["placeholder"] || null,
"pattern": value?.spec?.["pattern"] || null,
"pattern-description": value?.spec?.["pattern-description"] ||
null,
"textarea": value?.spec?.["textarea"] || false,
},
default: value.default || null,
description: value.description || null,
warning: value.warning || null,
},
null,
2,
)
})`;
}
case "number": {
return `List.number(${
JSON.stringify(
{
name: value.name || null,
range: value.range || null,
spec: {
range: value?.spec?.range || null,
integral: value?.spec?.integral || false,
units: value?.spec?.units || null,
placeholder: value?.spec?.placeholder || null,
},
default: value.default || null,
description: value.description || null,
warning: value.warning || null,
},
null,
2,
)
})`;
}
case "enum": {
return `List.enum(${
JSON.stringify(
{
name: value.name || null,
range: value.range || null,
spec: {
"values": value?.spec?.["values"] || null,
"value-names": value?.spec?.["value-names"] || {},
},
default: value.default || null,
description: value.description || null,
warning: value.warning || null,
},
null,
2,
)
})`;
}
case "object": {
const specName = newConst(
value.name + "_spec",
convertConfigSpec(value.spec.spec),
);
return `List.obj({
name: ${JSON.stringify(value.name || null)},
range: ${JSON.stringify(value.range || null)},
spec: {
spec: ${specName},
"display-as": ${
JSON.stringify(value?.spec?.["display-as"] || null)
},
"unique-by": ${JSON.stringify(value?.spec?.["unique-by"] || null)},
},
default: ${JSON.stringify(value.default || null)},
description: ${JSON.stringify(value.description || null)},
warning: ${JSON.stringify(value.warning || null)},
})`;
}
case "union": {
const variants = newConst(
value.name + "_variants",
convertConfigSpec(value.spec.variants),
);
return `List.union(
{
name:${JSON.stringify(value.name || null)},
range:${JSON.stringify(value.range || null)},
spec: {
tag: {
"id":${JSON.stringify(value?.spec?.tag?.["id"] || null)},
"name": ${
JSON.stringify(value?.spec?.tag?.["name"] || null)
},
"description": ${
JSON.stringify(value?.spec?.tag?.["description"] || null)
},
"warning": ${
JSON.stringify(value?.spec?.tag?.["warning"] || null)
},
"variant-names": ${
JSON.stringify(value?.spec?.tag?.["variant-names"] || {})
},
},
variants: ${variants},
"display-as": ${
JSON.stringify(value?.spec?.["display-as"] || null)
},
"unique-by": ${
JSON.stringify(value?.spec?.["unique-by"] || null)
},
default: ${JSON.stringify(value?.spec?.["default"] || null)},
},
default: ${JSON.stringify(value.default || null)},
description: ${JSON.stringify(value.description || null)},
warning: ${JSON.stringify(value.warning || null)},
}
)`;
}
}
throw new Error(`Unknown subtype "${value.subtype}"`);
}
function convertVariants(variants: any) {
let answer = "Variants.of({";
for (const [key, value] of Object.entries(variants)) {
const variableName = newConst(key, convertConfigSpec(value));
answer += `"${key}": ${variableName},`;
}
return `${answer}})`;
}
function getNextConstName(name: string, i = 0): string {
const newName = !i ? name : name + i;
if (namedConsts.has(newName)) {
return getNextConstName(name, i + 1);
}
namedConsts.add(newName);
return newName;
}

View File

@@ -8,7 +8,6 @@ export type ValueType =
| "enum"
| "list"
| "object"
| "pointer"
| "union";
export type ValueSpec = ValueSpecOf<ValueType>;
@@ -20,21 +19,20 @@ export type ValueSpecOf<T extends ValueType> = T extends "string"
: T extends "enum" ? ValueSpecEnum
: T extends "list" ? ValueSpecList
: T extends "object" ? ValueSpecObject
: T extends "pointer" ? ValueSpecPointer
: T extends "union" ? ValueSpecUnion
: never;
export interface ValueSpecString extends ListValueSpecString, WithStandalone {
type: "string";
default?: DefaultString;
default: null | DefaultString;
nullable: boolean;
textarea?: boolean;
textarea: null | boolean;
}
export interface ValueSpecNumber extends ListValueSpecNumber, WithStandalone {
type: "number";
nullable: boolean;
default?: number;
default: null | number;
}
export interface ValueSpecEnum extends ListValueSpecEnum, WithStandalone {
@@ -54,16 +52,6 @@ export interface ValueSpecUnion {
default: string;
}
export interface ValueSpecPointer extends WithStandalone {
type: "pointer";
subtype: "package" | "system";
"package-id": string;
target: "system" | "lan-address" | "tor-address" | "config" | "tor-key";
interface: string; // will only exist if target = tor-key || tor-address || lan-address
selector?: string; // will only exist if target = config
multi?: boolean; // will only exist if target = config
}
export interface ValueSpecObject extends WithStandalone {
type: "object";
spec: ConfigSpec;
@@ -71,11 +59,11 @@ export interface ValueSpecObject extends WithStandalone {
export interface WithStandalone {
name: string;
description?: null | string;
warning?: null | string;
description: null | string;
warning: null | string;
}
// no lists of booleans, lists, pointers
// no lists of booleans, lists
export type ListValueSpecType =
| "string"
| "number"
@@ -120,17 +108,17 @@ export function isValueSpecListOf<S extends ListValueSpecType>(
}
export interface ListValueSpecString {
pattern?: string;
"pattern-description"?: string;
pattern: null | string;
"pattern-description": null | string;
masked: boolean;
placeholder?: string;
placeholder: null | string;
}
export interface ListValueSpecNumber {
range: string;
integral: boolean;
units?: null | string;
placeholder?: null | string;
units: null | string;
placeholder: null | string;
}
export interface ListValueSpecEnum {
@@ -141,7 +129,7 @@ export interface ListValueSpecEnum {
export interface ListValueSpecObject {
spec: ConfigSpec; // this is a mapped type of the config object at this level, replacing the object's values with specs on those values
"unique-by": UniqueBy; // indicates whether duplicates can be permitted in the list
"display-as"?: null | string; // this should be a handlebars template which can make use of the entire config which corresponds to 'spec'
"display-as": null | string; // this should be a handlebars template which can make use of the entire config which corresponds to 'spec'
}
export type UniqueBy =
@@ -154,7 +142,7 @@ export type UniqueBy =
export interface ListValueSpecUnion {
tag: UnionTagSpec;
variants: { [key: string]: ConfigSpec };
"display-as"?: null | string; // this may be a handlebars template which can conditionally (on tag.id) make use of each union's entries, or if left blank will display as tag.id
"display-as": null | string; // this may be a handlebars template which can conditionally (on tag.id) make use of each union's entries, or if left blank will display as tag.id
"unique-by": UniqueBy;
default: string; // this should be the variantName which one prefers a user to start with by default when creating a new union instance in a list
}
@@ -166,8 +154,8 @@ export interface UnionTagSpec {
[variant: string]: string;
};
name: string;
description?: null | string;
warning?: null | string;
description: null | string;
warning: null | string;
}
export type DefaultString = string | { charset: string; len: number };

View File

@@ -1,6 +1,7 @@
import * as PM from "./propertiesMatcher.ts";
import { expect } from "https://deno.land/x/expect@v0.2.9/mod.ts";
import { matches } from "../dependencies.ts";
import { config as bitcoinPropertiesConfig } from "./test/output.ts";
const randWithSeed = (seed = 1) => {
return function random() {
@@ -8,377 +9,7 @@ const randWithSeed = (seed = 1) => {
return x - Math.floor(x);
};
};
const bitcoinProperties = {
"peer-tor-address": {
name: "Peer Tor Address",
description: "The Tor address of the peer interface",
type: "pointer",
subtype: "package",
"package-id": "bitcoind",
target: "tor-address",
interface: "peer",
},
"rpc-tor-address": {
name: "RPC Tor Address",
description: "The Tor address of the RPC interface",
type: "pointer",
subtype: "package",
"package-id": "bitcoind",
target: "tor-address",
interface: "rpc",
},
rpc: {
type: "object",
name: "RPC Settings",
description: "RPC configuration options.",
spec: {
enable: {
type: "boolean",
name: "Enable",
description: "Allow remote RPC requests.",
default: true,
},
username: {
type: "string",
nullable: false,
name: "Username",
description: "The username for connecting to Bitcoin over RPC.",
default: "bitcoin",
masked: true,
pattern: "^[a-zA-Z0-9_]+$",
"pattern-description": "Must be alphanumeric (can contain underscore).",
},
password: {
type: "string",
nullable: false,
name: "RPC Password",
description: "The password for connecting to Bitcoin over RPC.",
default: {
charset: "a-z,2-7",
len: 20,
},
pattern: '^[^\\n"]*$',
"pattern-description": "Must not contain newline or quote characters.",
copyable: true,
masked: true,
},
advanced: {
type: "object",
name: "Advanced",
description: "Advanced RPC Settings",
spec: {
auth: {
name: "Authorization",
description:
"Username and hashed password for JSON-RPC connections. RPC clients connect using the usual http basic authentication.",
type: "list",
subtype: "string",
default: Array<string>(),
spec: {
pattern: "^[a-zA-Z0-9_-]+:([0-9a-fA-F]{2})+\\$([0-9a-fA-F]{2})+$",
"pattern-description":
'Each item must be of the form "<USERNAME>:<SALT>$<HASH>".',
masked: false,
},
range: "[0,*)",
},
serialversion: {
name: "Serialization Version",
description:
"Return raw transaction or block hex with Segwit or non-SegWit serialization.",
type: "enum",
values: ["non-segwit", "segwit"],
"value-names": {},
default: "segwit",
},
servertimeout: {
name: "Rpc Server Timeout",
description:
"Number of seconds after which an uncompleted RPC call will time out.",
type: "number",
nullable: false,
range: "[5,300]",
integral: true,
units: "seconds",
default: 30,
},
threads: {
name: "Threads",
description:
"Set the number of threads for handling RPC calls. You may wish to increase this if you are making lots of calls via an integration.",
type: "number",
nullable: false,
default: 16,
range: "[1,64]",
integral: true,
units: undefined,
},
workqueue: {
name: "Work Queue",
description:
"Set the depth of the work queue to service RPC calls. Determines how long the backlog of RPC requests can get before it just rejects new ones.",
type: "number",
nullable: false,
default: 128,
range: "[8,256]",
integral: true,
units: "requests",
},
},
},
},
},
"zmq-enabled": {
type: "boolean",
name: "ZeroMQ Enabled",
description: "Enable the ZeroMQ interface",
default: true,
},
txindex: {
type: "boolean",
name: "Transaction Index",
description: "Enable the Transaction Index (txindex)",
default: true,
},
wallet: {
type: "object",
name: "Wallet",
description: "Wallet Settings",
spec: {
enable: {
name: "Enable Wallet",
description: "Load the wallet and enable wallet RPC calls.",
type: "boolean",
default: true,
},
avoidpartialspends: {
name: "Avoid Partial Spends",
description:
"Group outputs by address, selecting all or none, instead of selecting on a per-output basis. This improves privacy at the expense of higher transaction fees.",
type: "boolean",
default: true,
},
discardfee: {
name: "Discard Change Tolerance",
description:
"The fee rate (in BTC/kB) that indicates your tolerance for discarding change by adding it to the fee.",
type: "number",
nullable: false,
default: 0.0001,
range: "[0,.01]",
integral: false,
units: "BTC/kB",
},
},
},
advanced: {
type: "object",
name: "Advanced",
description: "Advanced Settings",
spec: {
mempool: {
type: "object",
name: "Mempool",
description: "Mempool Settings",
spec: {
mempoolfullrbf: {
name: "Enable Full RBF",
description:
"Policy for your node to use for relaying and mining unconfirmed transactions. For details, see https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-24.0.md#notice-of-new-option-for-transaction-replacement-policies",
type: "boolean",
default: false,
},
persistmempool: {
type: "boolean",
name: "Persist Mempool",
description: "Save the mempool on shutdown and load on restart.",
default: true,
},
maxmempool: {
type: "number",
nullable: false,
name: "Max Mempool Size",
description:
"Keep the transaction memory pool below <n> megabytes.",
range: "[1,*)",
integral: true,
units: "MiB",
default: 300,
},
mempoolexpiry: {
type: "number",
nullable: false,
name: "Mempool Expiration",
description:
"Do not keep transactions in the mempool longer than <n> hours.",
range: "[1,*)",
integral: true,
units: "Hr",
default: 336,
},
},
},
peers: {
type: "object",
name: "Peers",
description: "Peer Connection Settings",
spec: {
listen: {
type: "boolean",
name: "Make Public",
description:
"Allow other nodes to find your server on the network.",
default: true,
},
onlyconnect: {
type: "boolean",
name: "Disable Peer Discovery",
description: "Only connect to specified peers.",
default: false,
},
onlyonion: {
type: "boolean",
name: "Disable Clearnet",
description: "Only connect to peers over Tor.",
default: false,
},
addnode: {
name: "Add Nodes",
description: "Add addresses of nodes to connect to.",
type: "list",
subtype: "object",
range: "[0,*)",
default: Array<Record<string, unknown>>(),
spec: {
"unique-by": null,
spec: {
hostname: {
type: "string",
nullable: false,
name: "Hostname",
description: "Domain or IP address of bitcoin peer",
pattern:
"(^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$)|((^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$)|(^[a-z2-7]{16}\\.onion$)|(^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$))",
"pattern-description":
"Must be either a domain name, or an IPv4 or IPv6 address. Do not include protocol scheme (eg 'http://') or port.",
masked: false,
},
port: {
type: "number",
nullable: true,
name: "Port",
description:
"Port that peer is listening on for inbound p2p connections",
range: "[0,65535]",
integral: true,
},
},
},
},
},
},
dbcache: {
type: "number",
nullable: true,
name: "Database Cache",
description:
"How much RAM to allocate for caching the TXO set. Higher values improve syncing performance, but increase your chance of using up all your system's memory or corrupting your database in the event of an ungraceful shutdown. Set this high but comfortably below your system's total RAM during IBD, then turn down to 450 (or leave blank) once the sync completes.",
warning:
"WARNING: Increasing this value results in a higher chance of ungraceful shutdowns, which can leave your node unusable if it happens during the initial block download. Use this setting with caution. Be sure to set this back to the default (450 or leave blank) once your node is synced. DO NOT press the STOP button if your dbcache is large. Instead, set this number back to the default, hit save, and wait for bitcoind to restart on its own.",
range: "(0,*)",
integral: true,
units: "MiB",
},
pruning: {
type: "union",
name: "Pruning Settings",
description:
"Blockchain Pruning Options\nReduce the blockchain size on disk\n",
warning:
"If you set pruning to Manual and your disk is smaller than the total size of the blockchain, you MUST have something running that prunes these blocks or you may overfill your disk!\nDisabling pruning will convert your node into a full archival node. This requires a resync of the entire blockchain, a process that may take several days. Make sure you have enough free disk space or you may fill up your disk.\n",
tag: {
id: "mode",
name: "Pruning Mode",
description:
'- Disabled: Disable pruning\n- Automatic: Limit blockchain size on disk to a certain number of megabytes\n- Manual: Prune blockchain with the "pruneblockchain" RPC\n',
"variant-names": {
disabled: "Disabled",
automatic: "Automatic",
manual: "Manual",
},
},
variants: {
disabled: {},
automatic: {
size: {
type: "number",
nullable: false,
name: "Max Chain Size",
description: "Limit of blockchain size on disk.",
warning:
"Increasing this value will require re-syncing your node.",
default: 550,
range: "[550,1000000)",
integral: true,
units: "MiB",
},
},
manual: {
size: {
type: "number",
nullable: false,
name: "Failsafe Chain Size",
description: "Prune blockchain if size expands beyond this.",
default: 65536,
range: "[550,1000000)",
integral: true,
units: "MiB",
},
},
},
default: "disabled",
},
blockfilters: {
type: "object",
name: "Block Filters",
description: "Settings for storing and serving compact block filters",
spec: {
blockfilterindex: {
type: "boolean",
name: "Compute Compact Block Filters (BIP158)",
description:
"Generate Compact Block Filters during initial sync (IBD) to enable 'getblockfilter' RPC. This is useful if dependent services need block filters to efficiently scan for addresses/transactions etc.",
default: true,
},
peerblockfilters: {
type: "boolean",
name: "Serve Compact Block Filters to Peers (BIP157)",
description:
"Serve Compact Block Filters as a peer service to other nodes on the network. This is useful if you wish to connect an SPV client to your node to make it efficient to scan transactions without having to download all block data. 'Compute Compact Block Filters (BIP158)' is required.",
default: false,
},
},
},
bloomfilters: {
type: "object",
name: "Bloom Filters (BIP37)",
description: "Setting for serving Bloom Filters",
spec: {
peerbloomfilters: {
type: "boolean",
name: "Serve Bloom Filters to Peers",
description:
"Peers have the option of setting filters on each connection they make after the version handshake has completed. Bloom filters are for clients implementing SPV (Simplified Payment Verification) that want to check that block headers connect together correctly, without needing to verify the full blockchain. The client must trust that the transactions in the chain are in fact valid. It is highly recommended AGAINST using for anything except Bisq integration.",
warning:
"This is ONLY for use with Bisq integration, please use Block Filters for all other applications.",
default: false,
},
},
},
},
matches,
},
} as const;
const bitcoinProperties = bitcoinPropertiesConfig.build();
type BitcoinProperties = typeof bitcoinProperties;
const anyValue: unknown = "";
const _testBoolean: boolean = anyValue as PM.GuardAll<
@@ -443,6 +74,7 @@ const testUnionValue = anyValue as PM.GuardAll<{
tag: {
id: "mode";
name: "Pruning Mode";
warning: null;
description:
'- Disabled: Disable pruning\n- Automatic: Limit blockchain size on disk to a certain number of megabytes\n- Manual: Prune blockchain with the "pruneblockchain" RPC\n';
"variant-names": {
@@ -464,6 +96,7 @@ const testUnionValue = anyValue as PM.GuardAll<{
range: "[550,1000000)";
integral: true;
units: "MiB";
placeholder: null;
};
};
manual: {
@@ -497,10 +130,6 @@ const _testUnionBadUnion:
size: number;
} = testUnionValue;
const _testAll: PM.TypeFromProps<BitcoinProperties> = anyValue as {
// deno-lint-ignore no-explicit-any
"peer-tor-address": any;
// deno-lint-ignore no-explicit-any
"rpc-tor-address": any;
rpc: {
enable: boolean;
username: string;
@@ -647,6 +276,7 @@ const { test } = Deno;
"Must be a valid 64-digit hexadecimal value (ie a Nostr hex pubkey, not an npub). Go to https://damus.io/key/ to convert npub to hex.",
},
default: [] as string[], // [] as string []
warning: null,
},
} as const,
);
@@ -660,8 +290,6 @@ const { test } = Deno;
const checker = PM.typeFromProps(bitcoinProperties);
checker.unsafeCast({
"peer-tor-address": "",
"rpc-tor-address": "",
rpc: {
enable: true,
username: "asdf",
@@ -708,8 +336,6 @@ const { test } = Deno;
expect(() =>
checker.unsafeCast({
"peer-tor-address": "",
"rpc-tor-address": "",
rpc: {
enable: true,
username: "asdf",
@@ -757,8 +383,6 @@ const { test } = Deno;
expect(() =>
checker.unsafeCast({
"peer-tor-address": "",
"rpc-tor-address": "",
rpc: {
enable: true,
username: "asdf",
@@ -806,8 +430,6 @@ const { test } = Deno;
expect(() =>
checker.unsafeCast({
"peer-tor-address": "",
"rpc-tor-address": "",
rpc: {
enable: true,
username: "asdf",
@@ -853,8 +475,6 @@ const { test } = Deno;
})
).toThrow();
checker.unsafeCast({
"peer-tor-address": "",
"rpc-tor-address": null,
rpc: {
enable: true,
username: "asdf",

View File

@@ -276,9 +276,6 @@ export function guardAll<A extends ValueSpecAny>(
}
// deno-lint-ignore no-explicit-any
return matches.unknown as any;
case "pointer":
// deno-lint-ignore no-explicit-any
return matches.unknown as any;
case "union":
if (matchUnion.test(value)) {
return matches.some(

326
utils/test/config.json Normal file
View File

@@ -0,0 +1,326 @@
{
"rpc": {
"type": "object",
"name": "RPC Settings",
"description": "RPC configuration options.",
"spec": {
"enable": {
"type": "boolean",
"name": "Enable",
"description": "Allow remote RPC requests.",
"default": true
},
"username": {
"type": "string",
"nullable": false,
"name": "Username",
"description": "The username for connecting to Bitcoin over RPC.",
"default": "bitcoin",
"masked": true,
"pattern": "^[a-zA-Z0-9_]+$",
"pattern-description": "Must be alphanumeric (can contain underscore)."
},
"password": {
"type": "string",
"nullable": false,
"name": "RPC Password",
"description": "The password for connecting to Bitcoin over RPC.",
"default": {
"charset": "a-z,2-7",
"len": 20
},
"pattern": "^[^\\n\"]*$",
"pattern-description": "Must not contain newline or quote characters.",
"copyable": true,
"masked": true
},
"advanced": {
"type": "object",
"name": "Advanced",
"description": "Advanced RPC Settings",
"spec": {
"auth": {
"name": "Authorization",
"description": "Username and hashed password for JSON-RPC connections. RPC clients connect using the usual http basic authentication.",
"type": "list",
"subtype": "string",
"default": [],
"spec": {
"pattern": "^[a-zA-Z0-9_-]+:([0-9a-fA-F]{2})+\\$([0-9a-fA-F]{2})+$",
"pattern-description": "Each item must be of the form \"<USERNAME>:<SALT>$<HASH>\".",
"masked": false
},
"range": "[0,*)"
},
"serialversion": {
"name": "Serialization Version",
"description": "Return raw transaction or block hex with Segwit or non-SegWit serialization.",
"type": "enum",
"values": ["non-segwit", "segwit"],
"value-names": {},
"default": "segwit"
},
"servertimeout": {
"name": "Rpc Server Timeout",
"description": "Number of seconds after which an uncompleted RPC call will time out.",
"type": "number",
"nullable": false,
"range": "[5,300]",
"integral": true,
"units": "seconds",
"default": 30
},
"threads": {
"name": "Threads",
"description": "Set the number of threads for handling RPC calls. You may wish to increase this if you are making lots of calls via an integration.",
"type": "number",
"nullable": false,
"default": 16,
"range": "[1,64]",
"integral": true
},
"workqueue": {
"name": "Work Queue",
"description": "Set the depth of the work queue to service RPC calls. Determines how long the backlog of RPC requests can get before it just rejects new ones.",
"type": "number",
"nullable": false,
"default": 128,
"range": "[8,256]",
"integral": true,
"units": "requests"
}
}
}
}
},
"zmq-enabled": {
"type": "boolean",
"name": "ZeroMQ Enabled",
"description": "Enable the ZeroMQ interface",
"default": true
},
"txindex": {
"type": "boolean",
"name": "Transaction Index",
"description": "Enable the Transaction Index (txindex)",
"default": true
},
"wallet": {
"type": "object",
"name": "Wallet",
"description": "Wallet Settings",
"spec": {
"enable": {
"name": "Enable Wallet",
"description": "Load the wallet and enable wallet RPC calls.",
"type": "boolean",
"default": true
},
"avoidpartialspends": {
"name": "Avoid Partial Spends",
"description": "Group outputs by address, selecting all or none, instead of selecting on a per-output basis. This improves privacy at the expense of higher transaction fees.",
"type": "boolean",
"default": true
},
"discardfee": {
"name": "Discard Change Tolerance",
"description": "The fee rate (in BTC/kB) that indicates your tolerance for discarding change by adding it to the fee.",
"type": "number",
"nullable": false,
"default": 0.0001,
"range": "[0,.01]",
"integral": false,
"units": "BTC/kB"
}
}
},
"advanced": {
"type": "object",
"name": "Advanced",
"description": "Advanced Settings",
"spec": {
"mempool": {
"type": "object",
"name": "Mempool",
"description": "Mempool Settings",
"spec": {
"mempoolfullrbf": {
"name": "Enable Full RBF",
"description": "Policy for your node to use for relaying and mining unconfirmed transactions. For details, see https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-24.0.md#notice-of-new-option-for-transaction-replacement-policies",
"type": "boolean",
"default": false
},
"persistmempool": {
"type": "boolean",
"name": "Persist Mempool",
"description": "Save the mempool on shutdown and load on restart.",
"default": true
},
"maxmempool": {
"type": "number",
"nullable": false,
"name": "Max Mempool Size",
"description": "Keep the transaction memory pool below <n> megabytes.",
"range": "[1,*)",
"integral": true,
"units": "MiB",
"default": 300
},
"mempoolexpiry": {
"type": "number",
"nullable": false,
"name": "Mempool Expiration",
"description": "Do not keep transactions in the mempool longer than <n> hours.",
"range": "[1,*)",
"integral": true,
"units": "Hr",
"default": 336
}
}
},
"peers": {
"type": "object",
"name": "Peers",
"description": "Peer Connection Settings",
"spec": {
"listen": {
"type": "boolean",
"name": "Make Public",
"description": "Allow other nodes to find your server on the network.",
"default": true
},
"onlyconnect": {
"type": "boolean",
"name": "Disable Peer Discovery",
"description": "Only connect to specified peers.",
"default": false
},
"onlyonion": {
"type": "boolean",
"name": "Disable Clearnet",
"description": "Only connect to peers over Tor.",
"default": false
},
"addnode": {
"name": "Add Nodes",
"description": "Add addresses of nodes to connect to.",
"type": "list",
"subtype": "object",
"range": "[0,*)",
"default": [],
"spec": {
"unique-by": null,
"spec": {
"hostname": {
"type": "string",
"nullable": false,
"name": "Hostname",
"description": "Domain or IP address of bitcoin peer",
"pattern": "(^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$)|((^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$)|(^[a-z2-7]{16}\\.onion$)|(^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$))",
"pattern-description": "Must be either a domain name, or an IPv4 or IPv6 address. Do not include protocol scheme (eg 'http://') or port.",
"masked": false
},
"port": {
"type": "number",
"nullable": true,
"name": "Port",
"description": "Port that peer is listening on for inbound p2p connections",
"range": "[0,65535]",
"integral": true
}
}
}
}
}
},
"dbcache": {
"type": "number",
"nullable": true,
"name": "Database Cache",
"description": "How much RAM to allocate for caching the TXO set. Higher values improve syncing performance, but increase your chance of using up all your system's memory or corrupting your database in the event of an ungraceful shutdown. Set this high but comfortably below your system's total RAM during IBD, then turn down to 450 (or leave blank) once the sync completes.",
"warning": "WARNING: Increasing this value results in a higher chance of ungraceful shutdowns, which can leave your node unusable if it happens during the initial block download. Use this setting with caution. Be sure to set this back to the default (450 or leave blank) once your node is synced. DO NOT press the STOP button if your dbcache is large. Instead, set this number back to the default, hit save, and wait for bitcoind to restart on its own.",
"range": "(0,*)",
"integral": true,
"units": "MiB"
},
"pruning": {
"type": "union",
"name": "Pruning Settings",
"description": "Blockchain Pruning Options\nReduce the blockchain size on disk\n",
"warning": "If you set pruning to Manual and your disk is smaller than the total size of the blockchain, you MUST have something running that prunes these blocks or you may overfill your disk!\nDisabling pruning will convert your node into a full archival node. This requires a resync of the entire blockchain, a process that may take several days. Make sure you have enough free disk space or you may fill up your disk.\n",
"tag": {
"id": "mode",
"name": "Pruning Mode",
"description": "- Disabled: Disable pruning\n- Automatic: Limit blockchain size on disk to a certain number of megabytes\n- Manual: Prune blockchain with the \"pruneblockchain\" RPC\n",
"variant-names": {
"disabled": "Disabled",
"automatic": "Automatic",
"manual": "Manual"
}
},
"variants": {
"disabled": {},
"automatic": {
"size": {
"type": "number",
"nullable": false,
"name": "Max Chain Size",
"description": "Limit of blockchain size on disk.",
"warning": "Increasing this value will require re-syncing your node.",
"default": 550,
"range": "[550,1000000)",
"integral": true,
"units": "MiB"
}
},
"manual": {
"size": {
"type": "number",
"nullable": false,
"name": "Failsafe Chain Size",
"description": "Prune blockchain if size expands beyond this.",
"default": 65536,
"range": "[550,1000000)",
"integral": true,
"units": "MiB"
}
}
},
"default": "disabled"
},
"blockfilters": {
"type": "object",
"name": "Block Filters",
"description": "Settings for storing and serving compact block filters",
"spec": {
"blockfilterindex": {
"type": "boolean",
"name": "Compute Compact Block Filters (BIP158)",
"description": "Generate Compact Block Filters during initial sync (IBD) to enable 'getblockfilter' RPC. This is useful if dependent services need block filters to efficiently scan for addresses/transactions etc.",
"default": true
},
"peerblockfilters": {
"type": "boolean",
"name": "Serve Compact Block Filters to Peers (BIP157)",
"description": "Serve Compact Block Filters as a peer service to other nodes on the network. This is useful if you wish to connect an SPV client to your node to make it efficient to scan transactions without having to download all block data. 'Compute Compact Block Filters (BIP158)' is required.",
"default": false
}
}
},
"bloomfilters": {
"type": "object",
"name": "Bloom Filters (BIP37)",
"description": "Setting for serving Bloom Filters",
"spec": {
"peerbloomfilters": {
"type": "boolean",
"name": "Serve Bloom Filters to Peers",
"description": "Peers have the option of setting filters on each connection they make after the version handshake has completed. Bloom filters are for clients implementing SPV (Simplified Payment Verification) that want to check that block headers connect together correctly, without needing to verify the full blockchain. The client must trust that the transactions in the chain are in fact valid. It is highly recommended AGAINST using for anything except Bisq integration.",
"warning": "This is ONLY for use with Bisq integration, please use Block Filters for all other applications.",
"default": false
}
}
}
}
}
}

451
utils/test/output.ts Normal file
View File

@@ -0,0 +1,451 @@
import { Config, List, Value, Variants } from "../../config/mod.ts";
export const enable = Value.boolean({
"name": "Enable",
"default": true,
"description": "Allow remote RPC requests.",
"warning": null,
});
export const username = Value.string({
"name": "Username",
"default": "bitcoin",
"description": "The username for connecting to Bitcoin over RPC.",
"warning": null,
"nullable": false,
"masked": true,
"placeholder": null,
"pattern": "^[a-zA-Z0-9_]+$",
"pattern-description": "Must be alphanumeric (can contain underscore).",
"textarea": null,
});
export const password = Value.string({
"name": "RPC Password",
"default": {
"charset": "a-z,2-7",
"len": 20,
},
"description": "The password for connecting to Bitcoin over RPC.",
"warning": null,
"nullable": false,
"masked": true,
"placeholder": null,
"pattern": '^[^\\n"]*$',
"pattern-description": "Must not contain newline or quote characters.",
"textarea": null,
});
export const authorizationList = List.string({
"name": "Authorization",
"range": "[0,*)",
"spec": {
"masked": null,
"placeholder": null,
"pattern": "^[a-zA-Z0-9_-]+:([0-9a-fA-F]{2})+\\$([0-9a-fA-F]{2})+$",
"pattern-description":
'Each item must be of the form "<USERNAME>:<SALT>$<HASH>".',
"textarea": false,
},
"default": [],
"description":
"Username and hashed password for JSON-RPC connections. RPC clients connect using the usual http basic authentication.",
"warning": null,
});
export const auth = Value.list(authorizationList);
export const serialversion = Value.enum({
"name": "Serialization Version",
"description":
"Return raw transaction or block hex with Segwit or non-SegWit serialization.",
"warning": null,
"default": "segwit",
"values": [
"non-segwit",
"segwit",
],
"value-names": {},
});
export const servertimeout = Value.number({
"name": "Rpc Server Timeout",
"default": 30,
"description":
"Number of seconds after which an uncompleted RPC call will time out.",
"warning": null,
"nullable": false,
"range": "[5,300]",
"integral": true,
"units": "seconds",
"placeholder": null,
});
export const threads = Value.number({
"name": "Threads",
"default": 16,
"description":
"Set the number of threads for handling RPC calls. You may wish to increase this if you are making lots of calls via an integration.",
"warning": null,
"nullable": false,
"range": "[1,64]",
"integral": true,
"units": null,
"placeholder": null,
});
export const workqueue = Value.number({
"name": "Work Queue",
"default": 128,
"description":
"Set the depth of the work queue to service RPC calls. Determines how long the backlog of RPC requests can get before it just rejects new ones.",
"warning": null,
"nullable": false,
"range": "[8,256]",
"integral": true,
"units": "requests",
"placeholder": null,
});
export const advancedSpec = Config.of({
"auth": auth,
"serialversion": serialversion,
"servertimeout": servertimeout,
"threads": threads,
"workqueue": workqueue,
});
export const advanced = Value.object({
name: "Advanced",
description: "Advanced RPC Settings",
warning: null,
default: null,
"display-as": null,
"unique-by": null,
spec: advancedSpec,
"value-names": {},
});
export const rpcSettingsSpec = Config.of({
"enable": enable,
"username": username,
"password": password,
"advanced": advanced,
});
export const rpc = Value.object({
name: "RPC Settings",
description: "RPC configuration options.",
warning: null,
default: null,
"display-as": null,
"unique-by": null,
spec: rpcSettingsSpec,
"value-names": {},
});
export const zmqEnabled = Value.boolean({
"name": "ZeroMQ Enabled",
"default": true,
"description": "Enable the ZeroMQ interface",
"warning": null,
});
export const txindex = Value.boolean({
"name": "Transaction Index",
"default": true,
"description": "Enable the Transaction Index (txindex)",
"warning": null,
});
export const enable1 = Value.boolean({
"name": "Enable Wallet",
"default": true,
"description": "Load the wallet and enable wallet RPC calls.",
"warning": null,
});
export const avoidpartialspends = Value.boolean({
"name": "Avoid Partial Spends",
"default": true,
"description":
"Group outputs by address, selecting all or none, instead of selecting on a per-output basis. This improves privacy at the expense of higher transaction fees.",
"warning": null,
});
export const discardfee = Value.number({
"name": "Discard Change Tolerance",
"default": 0.0001,
"description":
"The fee rate (in BTC/kB) that indicates your tolerance for discarding change by adding it to the fee.",
"warning": null,
"nullable": false,
"range": "[0,.01]",
"integral": false,
"units": "BTC/kB",
"placeholder": null,
});
export const walletSpec = Config.of({
"enable": enable1,
"avoidpartialspends": avoidpartialspends,
"discardfee": discardfee,
});
export const wallet = Value.object({
name: "Wallet",
description: "Wallet Settings",
warning: null,
default: null,
"display-as": null,
"unique-by": null,
spec: walletSpec,
"value-names": {},
});
export const mempoolfullrbf = Value.boolean({
"name": "Enable Full RBF",
"default": false,
"description":
"Policy for your node to use for relaying and mining unconfirmed transactions. For details, see https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-24.0.md#notice-of-new-option-for-transaction-replacement-policies",
"warning": null,
});
export const persistmempool = Value.boolean({
"name": "Persist Mempool",
"default": true,
"description": "Save the mempool on shutdown and load on restart.",
"warning": null,
});
export const maxmempool = Value.number({
"name": "Max Mempool Size",
"default": 300,
"description": "Keep the transaction memory pool below <n> megabytes.",
"warning": null,
"nullable": false,
"range": "[1,*)",
"integral": true,
"units": "MiB",
"placeholder": null,
});
export const mempoolexpiry = Value.number({
"name": "Mempool Expiration",
"default": 336,
"description":
"Do not keep transactions in the mempool longer than <n> hours.",
"warning": null,
"nullable": false,
"range": "[1,*)",
"integral": true,
"units": "Hr",
"placeholder": null,
});
export const mempoolSpec = Config.of({
"mempoolfullrbf": mempoolfullrbf,
"persistmempool": persistmempool,
"maxmempool": maxmempool,
"mempoolexpiry": mempoolexpiry,
});
export const mempool = Value.object({
name: "Mempool",
description: "Mempool Settings",
warning: null,
default: null,
"display-as": null,
"unique-by": null,
spec: mempoolSpec,
"value-names": {},
});
export const listen = Value.boolean({
"name": "Make Public",
"default": true,
"description": "Allow other nodes to find your server on the network.",
"warning": null,
});
export const onlyconnect = Value.boolean({
"name": "Disable Peer Discovery",
"default": false,
"description": "Only connect to specified peers.",
"warning": null,
});
export const onlyonion = Value.boolean({
"name": "Disable Clearnet",
"default": false,
"description": "Only connect to peers over Tor.",
"warning": null,
});
export const hostname = Value.string({
"name": "Hostname",
"default": null,
"description": "Domain or IP address of bitcoin peer",
"warning": null,
"nullable": false,
"masked": null,
"placeholder": null,
"pattern":
"(^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$)|((^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$)|(^[a-z2-7]{16}\\.onion$)|(^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$))",
"pattern-description":
"Must be either a domain name, or an IPv4 or IPv6 address. Do not include protocol scheme (eg 'http://') or port.",
"textarea": null,
});
export const port = Value.number({
"name": "Port",
"default": null,
"description": "Port that peer is listening on for inbound p2p connections",
"warning": null,
"nullable": true,
"range": "[0,65535]",
"integral": true,
"units": null,
"placeholder": null,
});
export const addNodesSpec = Config.of({ "hostname": hostname, "port": port });
export const addNodesList = List.obj({
name: "Add Nodes",
range: "[0,*)",
spec: {
spec: addNodesSpec,
"display-as": null,
"unique-by": null,
},
default: [],
description: "Add addresses of nodes to connect to.",
warning: null,
});
export const addnode = Value.list(addNodesList);
export const peersSpec = Config.of({
"listen": listen,
"onlyconnect": onlyconnect,
"onlyonion": onlyonion,
"addnode": addnode,
});
export const peers = Value.object({
name: "Peers",
description: "Peer Connection Settings",
warning: null,
default: null,
"display-as": null,
"unique-by": null,
spec: peersSpec,
"value-names": {},
});
export const dbcache = Value.number({
"name": "Database Cache",
"default": null,
"description":
"How much RAM to allocate for caching the TXO set. Higher values improve syncing performance, but increase your chance of using up all your system's memory or corrupting your database in the event of an ungraceful shutdown. Set this high but comfortably below your system's total RAM during IBD, then turn down to 450 (or leave blank) once the sync completes.",
"warning":
"WARNING: Increasing this value results in a higher chance of ungraceful shutdowns, which can leave your node unusable if it happens during the initial block download. Use this setting with caution. Be sure to set this back to the default (450 or leave blank) once your node is synced. DO NOT press the STOP button if your dbcache is large. Instead, set this number back to the default, hit save, and wait for bitcoind to restart on its own.",
"nullable": true,
"range": "(0,*)",
"integral": true,
"units": "MiB",
"placeholder": null,
});
export const disabled = Config.of({});
export const size = Value.number({
"name": "Max Chain Size",
"default": 550,
"description": "Limit of blockchain size on disk.",
"warning": "Increasing this value will require re-syncing your node.",
"nullable": false,
"range": "[550,1000000)",
"integral": true,
"units": "MiB",
"placeholder": null,
});
export const automatic = Config.of({ "size": size });
export const size1 = Value.number({
"name": "Failsafe Chain Size",
"default": 65536,
"description": "Prune blockchain if size expands beyond this.",
"warning": null,
"nullable": false,
"range": "[550,1000000)",
"integral": true,
"units": "MiB",
"placeholder": null,
});
export const manual = Config.of({ "size": size1 });
export const pruningSettingsVariants = Variants.of({
"disabled": disabled,
"automatic": automatic,
"manual": manual,
});
export const pruning = Value.union({
name: "Pruning Settings",
description:
"Blockchain Pruning Options\nReduce the blockchain size on disk\n",
warning:
"If you set pruning to Manual and your disk is smaller than the total size of the blockchain, you MUST have something running that prunes these blocks or you may overfill your disk!\nDisabling pruning will convert your node into a full archival node. This requires a resync of the entire blockchain, a process that may take several days. Make sure you have enough free disk space or you may fill up your disk.\n",
default: "disabled",
variants: pruningSettingsVariants,
tag: {
"id": "mode",
"name": "Pruning Mode",
"description":
'- Disabled: Disable pruning\n- Automatic: Limit blockchain size on disk to a certain number of megabytes\n- Manual: Prune blockchain with the "pruneblockchain" RPC\n',
"warning": null,
"variant-names": {
"disabled": "Disabled",
"automatic": "Automatic",
"manual": "Manual",
},
},
"display-as": null,
"unique-by": null,
"variant-names": null,
});
export const blockfilterindex = Value.boolean({
"name": "Compute Compact Block Filters (BIP158)",
"default": true,
"description":
"Generate Compact Block Filters during initial sync (IBD) to enable 'getblockfilter' RPC. This is useful if dependent services need block filters to efficiently scan for addresses/transactions etc.",
"warning": null,
});
export const peerblockfilters = Value.boolean({
"name": "Serve Compact Block Filters to Peers (BIP157)",
"default": false,
"description":
"Serve Compact Block Filters as a peer service to other nodes on the network. This is useful if you wish to connect an SPV client to your node to make it efficient to scan transactions without having to download all block data. 'Compute Compact Block Filters (BIP158)' is required.",
"warning": null,
});
export const blockFiltersSpec = Config.of({
"blockfilterindex": blockfilterindex,
"peerblockfilters": peerblockfilters,
});
export const blockfilters = Value.object({
name: "Block Filters",
description: "Settings for storing and serving compact block filters",
warning: null,
default: null,
"display-as": null,
"unique-by": null,
spec: blockFiltersSpec,
"value-names": {},
});
export const peerbloomfilters = Value.boolean({
"name": "Serve Bloom Filters to Peers",
"default": false,
"description":
"Peers have the option of setting filters on each connection they make after the version handshake has completed. Bloom filters are for clients implementing SPV (Simplified Payment Verification) that want to check that block headers connect together correctly, without needing to verify the full blockchain. The client must trust that the transactions in the chain are in fact valid. It is highly recommended AGAINST using for anything except Bisq integration.",
"warning":
"This is ONLY for use with Bisq integration, please use Block Filters for all other applications.",
});
export const bloomFiltersBip37Spec = Config.of({
"peerbloomfilters": peerbloomfilters,
});
export const bloomfilters = Value.object({
name: "Bloom Filters (BIP37)",
description: "Setting for serving Bloom Filters",
warning: null,
default: null,
"display-as": null,
"unique-by": null,
spec: bloomFiltersBip37Spec,
"value-names": {},
});
export const advancedSpec1 = Config.of({
"mempool": mempool,
"peers": peers,
"dbcache": dbcache,
"pruning": pruning,
"blockfilters": blockfilters,
"bloomfilters": bloomfilters,
});
export const advanced1 = Value.object({
name: "Advanced",
description: "Advanced Settings",
warning: null,
default: null,
"display-as": null,
"unique-by": null,
spec: advancedSpec1,
"value-names": {},
});
export const config = Config.of({
"rpc": rpc,
"zmq-enabled": zmqEnabled,
"txindex": txindex,
"wallet": wallet,
"advanced": advanced1,
});