DSM data bus

dsm-data-bus-min

The DSM data bus is a separate transport layer for communication between council-daemon and depositor-bot. This works using a smart contract and any evm compatible network.

Selecting a network to deploy the smart contract

Two networks were chosen for deployment of the smart contract: Gnosis and Polygon. The logic of network selection is described in detail in the reserch: https://hackmd.io/Fh_83JZaTyu6kIcK9zlbpg

This reserch was created before the Dencun update. At the moment, according to the information from https://l2fees.info/, the prices of L2 solutions are still much more expensive than Gnosis and Polygon.

Requirements

Smart Contract:

  • It should be capable of sending arbitrary events with any data
  • It needs a minimalistic implementation
  • It should consume minimal gas
  • It requires no ongoing support or administration

DSM Data Bus Messages (ABI):

  • The message interface should be backward compatible with other transport modules
  • Data structures should be as lightweight as possible
  • There should be a messaging strategy for specific message types that reduces gas costs

Monitoring:

  • It should monitor the gas price and the network's operational status to allow timely responses and subsequent switching between networks.
  • It should track the balances of the Guardians to ensure timely replenishment.

Council-daemon:

  • A new transport module needs to be implemented while maintaining backward compatibility with other transport modules.
  • It must send the minimum necessary messages, but still provide visibility into the guardians' status even in the absence of deposits/pauses/unvesting.
  • Funds should be allocated to the Guardians' addresses across all networks where the DSM data bus is deployed.

Depositor-bot:

  • A new transport module needs to be implemented while maintaining backward compatibility with other transport modules.
  • During the message reception phase, it is necessary to verify the Guardians' addresses with the DSM contract from the main network.

Smart contract implementation

The prototype is already available to view: https://github.com/lidofinance/data-bus

Technology selection

To implement Data Bus, Solidity and Hardhat was chosen as language and the development platform.

Implementation of the smart contract

The smart contract uses a special kind of event, referred to as an "abstract event," which can be customized to carry different types of data under various event identifiers. This flexibility allows the system to handle multiple event types through a single unified mechanism.

Abstract Event Design

The event structure in the contract is defined as follows:

event Message(
    bytes32 indexed eventId,
    address indexed sender,
    bytes data
) anonymous;

The anonymous attribute in this event means it does not use the standard topic for the event signature as is typical in Ethereum events. Instead, it utilizes a manually specified eventId as the primary identifier. This design allows the system to abstract away from specific event types, enabling users to define and emit any event type based on this generic template. For more details on anonymous events, you can refer to the Solidity documentation.

Emitting Events

To emit an event, you first calculate the hash of your event signature (e.g., keccak256(bytes('SomeEvent(address,bytes)'))) which becomes the eventId. This identifier along with the data is then passed to the following function:

function sendMessage(bytes32 _eventId, bytes calldata _data)

This function takes the eventId and the data you want to transmit, and it logs the event on the blockchain. This method allows any user-defined event to be emitted using the Message event template, offering a flexible and powerful way to handle custom events.

DSM Data Bus Messages

Since the smart contract is abstract and allows you to send any type of message, I propose to discuss message types separately. These messages will be implemented on top of the abstract Message event and will be used as an ABI on the Council-Daemon and Depositor-Bot side.

Each message interface for the Dsm Data Bus will be represented as a Human Readable ABI

AppMetaData

Each message will contain an app structure. This structure contains meta information about the Guardian, the application name and its version. This data is needed to monitor the system consistency.

DepositData

There are some minor differences in the implementation of this structure:

  • guardianAddress - passed directly to the event, as it should be indexed
  • it was decided to refuse the type field, because it is a keyword in solidity and its function in onchain implementation can be easily replaced by event.name.

It is also important to take into account that it is necessary to resend the message every 150 blocks, because the signature becomes invalid after this interval.

Current interface in the offchain module:

export interface MessageDeposit {
  type: MessageType;
  guardianAddress: string;
  depositRoot: string;
  nonce: number;
  blockNumber: number;
  blockHash: string;
  signature: Signature;
  stakingModuleId: number;
  app: {
    version: string;
    name: string;
  };
}

New interface for DSM Data Bus:

event MessageDepositV1(address indexed guardianAddress, (bytes32 depositRoot, uint256 nonce, uint256 blockNumber, bytes32 blockHash, bytes signature, uint256 stakingModuleId, (bytes32 version) app) data)

PauseV2Data

This structure and event is necessary to handle pauses in the DSM 2 version. It was decided to use two different structures for backward compatibility with other transport modules.

Current interface in the offchain module:

export interface MessagePauseV2 {
  type: MessageType;
  guardianAddress: string;
  depositRoot: string;
  nonce: number;
  blockNumber: number;
  blockHash: string;
  signature: Signature;
  stakingModuleId: number;
  app: {
    version: string;
    name: string;
  };
}

New interface for DSM Data Bus:

event MessagePauseV2(address indexed guardianAddress, (bytes32 depositRoot, uint256 nonce, uint256 blockNumber, bytes32 blockHash, bytes signature, uint256 stakingModuleId, (bytes32 version) app) data)

PauseV3Data

This structure and event is required to handle pauses in DSM version 3.

Current interface in the offchain module:

export interface MessagePauseV3 {
  type: MessageType;
  guardianAddress: string;
  blockNumber: number;
  signature: Signature;
  app: {
    version: string;
    name: string;
  };
}

New interface for DSM Data Bus:

event MessagePauseV3(address indexed guardianAddress, (uint256 blockNumber, bytes signature, (bytes32 version) app) data)

UnvetData

Current interface in the offchain module:

export interface MessageUnvet {
  type: MessageType;
  guardianAddress: string;
  nonce: number;
  blockNumber: number;
  blockHash: string;
  stakingModuleId: number;
  signature: Signature;
  operatorIds: string;
  vettedKeysByOperator: string;
  app: {
    version: string;
    name: string;
  };
}

New interface for DSM Data Bus:

event MessageUnvetV1(address indexed guardianAddress, (uint256 nonce, uint256 blockNumber, bytes32 blockHash, uint256 stakingModuleId, bytes signature, bytes32 operatorIds, bytes32 vettedKeysByOperator, (string bytes32) app) data)

PingData

In the ofchain implementation, these messages are sent once every 12 seconds. In order to optimise gas costs in onchain implementation it is necessary to implement logic of smart sending of Ping messages on Council-Daemon side: send this message only if there were no other messages for 150 blocks or if data from AppMetaData has changed.

150 blocks is the interval for sending deposit messages.

Current interface in the offchain module:

export interface MessagePing {
  type: MessageType;
  guardianAddress: string;
  blockNumber: number;
  stakingModuleIds: number[];
  app: {
    version: string;
    name: string;
  };
}

New interface for DSM Data Bus:

event MessagePingV1(address indexed guardianAddress, (uint256 blockNumber, (bytes32 version) app) data)