Skip to content

Dashmate read-only commands rewrite config and can clobber concurrent updates #4242

Description

@infraclaw-dash

Expected Behavior

Read-only Dashmate commands such as dashmate config get and dashmate core cli ... should not rewrite the Dashmate configuration file.

Configuration updates should remain durable after dashmate config set exits successfully, including when another read-only Dashmate command is running concurrently.

Current Behavior

Dashmate hydrates each Config by calling setOptions(), which marks the config as changed. BaseCommand.finally() then persists the complete config file after every successful command, including read-only commands.

The persistence path rewrites the whole config.json without inter-process locking or stale-write detection. Consequently, two overlapping Dashmate processes can perform a lost update:

  1. A read-only command loads the old full configuration.
  2. dashmate config set loads the same configuration, changes one value, writes it, prints success, and exits successfully.
  3. The read-only command exits later and writes its stale full configuration over the updated file.

The setting silently reverts even though config set reported success. This can cause subsequent render/update/restart operations to use an older component image or other stale configuration.

The relevant code path appears to be:

  • Config constructor calls setOptions(), which sets this.changed = true.
  • BaseCommand.finally() calls configFileRepository.write(configFile) whenever configFile.isChanged() is true.
  • ConfigFileJsonRepository.write() replaces the complete JSON file without coordinating with other Dashmate processes.

Possible Solution

  • Mark configurations loaded from disk as clean after hydration, unless a migration or actual mutation changed them.
  • Ensure read-only commands never persist configuration as a side effect.
  • Serialize config read-modify-write operations across Dashmate processes with an inter-process lock, or add equivalent stale-write detection/retry. Atomic rename alone would prevent partial writes but would not prevent this lost-update case.
  • Add concurrency tests covering a slow read-only command overlapping config set.

Steps to Reproduce (for bugs)

  1. Configure a Dashmate node and record the config.json hash and modification time.

  2. Run a read-only command such as:

    dashmate core cli getblockchaininfo
    

    The configuration file is rewritten despite no requested configuration change.

  3. To reproduce the lost update, start a Dashmate command that remains in flight after loading the configuration, then concurrently run:

    dashmate config set platform.drive.abci.docker.image <new-image> --config=testnet
    
  4. Allow the first command to exit after config set. Inspect config.json directly. With the stale writer finishing last, the image setting has reverted to its previous value even though config set exited successfully.

A loop of concurrent read-only CLI calls while repeatedly setting and reading back a value also reproduces the issue nondeterministically.

Context

This was observed during a rolling component update. Concurrent status queries overlapped image-pin updates. Some nodes retained one old image digest while others completed correctly, depending on process completion order. The same failure pattern was observed on separate runs.

Immediate readback/retry reduces the likelihood but does not close the race, because another stale Dashmate process can still write after the readback.

Your Environment

  • Version used: Dashmate 4.1.0; the same behavior was also observed with 4.1.0-rc.2
  • Environment name and version: Node.js v24.14.1
  • Operating System and version: Linux, arm64
  • Link to your project: Dash Platform

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions