Skip to content
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
4 changes: 4 additions & 0 deletions packages/dashmate/configs/defaults/getBaseConfigFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/
export default function getBaseConfigFactory() {
const prereleaseTag = semver.prerelease(version) === null ? '' : `-${semver.prerelease(version)[0]}`;
const dockerImageVersion = `${semver.major(version)}${prereleaseTag}`;

Check warning on line 19 in packages/dashmate/configs/defaults/getBaseConfigFactory.js

View workflow job for this annotation

GitHub Actions / JS packages (dashmate) / Linting

'dockerImageVersion' is assigned a value but never used

/**
* @typedef {function} getBaseConfig
Expand Down Expand Up @@ -190,6 +190,10 @@
port: 2444,
},
previousBlocksOffset: 8,
// How often the service reloads the quorum lists from Core. It answers
// requests from that copy only, so a quorum formed since the last reload
// is missing from its answers until the next one.
cacheTtlSeconds: 60,
versionCheckHost: '',
addressHostOverride: '',
},
Expand Down
5 changes: 5 additions & 0 deletions packages/dashmate/configs/defaults/getLocalConfigFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ export default function getLocalConfigFactory(getBaseConfig) {
api: {
port: 22444,
},
// Platform signs with a new quorum seconds after Core mines it. With the
// base 60 s reload, SDKs that take keys from this service (the test suite
// among them) fail to verify every proof signed in between. Releases of
// the service before 0.7.0 read Core on every request.
cacheTtlSeconds: 1,
},
gateway: {
ssl: {
Expand Down
15 changes: 15 additions & 0 deletions packages/dashmate/configs/getConfigFileMigrationsFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -1791,6 +1791,21 @@ export default function getConfigFileMigrationsFactory(homeDir, defaultConfigs)

return configFile;
},
'5.0.0-beta.4': (configFile) => {
Object.entries(configFile.configs)
.forEach(([, options]) => {
// The quorum list service reloads from Core on this interval and answers
// from that copy only. Local networks take the short interval of the local
// preset; every other network keeps the service's own 60 s.
const quorumList = options.platform?.quorumList;
if (quorumList && quorumList.cacheTtlSeconds === undefined) {
quorumList.cacheTtlSeconds = getDefaultConfigByNetwork(options.network)
.get('platform.quorumList.cacheTtlSeconds');
}
});

return configFile;
},
};
}

Expand Down
1 change: 1 addition & 0 deletions packages/dashmate/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ services:
- DASH_RPC_USER=quorum_list
- DASH_RPC_PASSWORD=${CORE_RPC_USERS_QUORUM_LIST_PASSWORD:?err}
- QUORUM_PREVIOUS_BLOCKS_OFFSET=${PLATFORM_QUORUM_LIST_PREVIOUS_BLOCKS_OFFSET:?err}
- QUORUM_CACHE_TTL_SECONDS=${PLATFORM_QUORUM_LIST_CACHE_TTL_SECONDS:?err}
- DASH_NETWORK=${DASH_CORE_NETWORK:?err}
- VERSION_CHECK_HOST=${PLATFORM_QUORUM_LIST_VERSION_CHECK_HOST:-}
- ADDRESS_HOST_OVERRIDE=${PLATFORM_QUORUM_LIST_ADDRESS_HOST_OVERRIDE:-}
Expand Down
1 change: 1 addition & 0 deletions packages/dashmate/docs/services/quorum_list.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The quorum list sidecar exposes the local LLMQ list over HTTP for SDKs and funct
| `platform.quorumList.api.host` | `127.0.0.1` | Host interface to bind the API |
| `platform.quorumList.api.port` | `2444` (mainnet), `12444` (testnet), `22444` (local) | Port for the HTTP API |
| `platform.quorumList.previousBlocksOffset` | `8` | Number of previous blocks to consider for quorum lookups |
| `platform.quorumList.cacheTtlSeconds` | `60`, `1` (local) | Seconds between reloads of the quorum lists from Core. The service answers from the last reload only, so a new quorum is missing from its answers until the next one |
| `platform.quorumList.versionCheckHost` | `""` | Host for version checking (set to `host.docker.internal` locally)|
| `platform.quorumList.addressHostOverride` | `""` | Override for address host (set to `127.0.0.1` locally) |

Expand Down
7 changes: 6 additions & 1 deletion packages/dashmate/src/config/configJsonSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,11 @@ export default {
type: 'integer',
minimum: 0,
},
cacheTtlSeconds: {
type: 'integer',
minimum: 1,
description: 'Seconds between reloads of the quorum lists from Core',
},
versionCheckHost: {
type: 'string',
description: 'Host to use for version checking (used in local networks)',
Expand All @@ -588,7 +593,7 @@ export default {
description: 'Override for address host (used in local networks)',
},
},
required: ['enabled', 'docker', 'api', 'previousBlocksOffset', 'versionCheckHost', 'addressHostOverride'],
required: ['enabled', 'docker', 'api', 'previousBlocksOffset', 'cacheTtlSeconds', 'versionCheckHost', 'addressHostOverride'],
additionalProperties: false,
},
gateway: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,36 @@ describe('migrateConfigFileFactory', () => {
});
});

it('should add the quorum list reload interval to a 5.0.0-beta.3 config, short only on local networks', async () => {
// Loading is what fails without it: the schema requires the option.
const fromVersion = '5.0.0-beta.3';
const { version } = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT_DIR, 'package.json'), 'utf8'));

const configFileData = createConfigFile().toObject();
configFileData.configFormatVersion = fromVersion;
// `dashmate setup local` runs the service on `local_seed`, a config of its
// own next to the `local` preset.
configFileData.configs.local_seed = structuredClone(configFileData.configs.local);
configFileData.configs.local_seed.group = 'local';
for (const options of Object.values(configFileData.configs)) {
delete options.platform.quorumList.cacheTtlSeconds;
}
configFileData.configs.testnet.platform.quorumList.cacheTtlSeconds = 5;

const migrated = migrateConfigFile(configFileData, fromVersion, version);

expect(migrated.configs.local.platform.quorumList.cacheTtlSeconds).to.equal(1);
expect(migrated.configs.local_seed.platform.quorumList.cacheTtlSeconds).to.equal(1);
expect(migrated.configs.base.platform.quorumList.cacheTtlSeconds).to.equal(60);
expect(migrated.configs.mainnet.platform.quorumList.cacheTtlSeconds).to.equal(60);
// An interval the operator chose is left alone.
expect(migrated.configs.testnet.platform.quorumList.cacheTtlSeconds).to.equal(5);

for (const [name, options] of Object.entries(migrated.configs)) {
expect(() => new Config(name, options), `migrated ${name} config does not load`).to.not.throw();
}
});

it('should keep an operator image that predates the 4.0.0 re-pin', async () => {
// Every config older than 4.0.0 crosses the unconditional re-pin in that
// migration, so it is the first place operator intent can be respected. It
Expand Down
Loading