The x/bridge module (dydx1zlefkpe3g0vvm9a4h0jf9000lmqutlh9jwjnsv) is responsible for receiving bridged tokens from the Ethereum blockchain. It has an associated daemon that knows about all “bridge events” that happened on Ethereum by making RPC calls to an Ethereum full node. Bridge events are logs emitted by the bridging smart-contract on Ethereum. Each event has a unique, sequentially-increasing id.
A bridge event can be considered to be in one of the following states:
State
Description
EthMined
Included in the Ethereum blockchain
EthFinalized
Included in the Ethereum blockchain in a block that has been finalized
Recognized
Understood to be finalized by an individual validator’s bridge daemon
Not in consensus
dYdX Chain validators should only propose Recognized events for inclusion in blocks after an additional waiting period (configured in the ProposeParams) in order to give enough time for other validators to recognize the event
Acknowledged
Included in a block on the open-source v4 chain developed by dYdX (”v4”) by a proposer and validated with +2⁄3 weight
In consensus
Completed
Was Acknowledged for a certain number of blocks
Has resulted in newly-minted tokens on v4
In consensus
The block-delay (configured in the SafetyParams) gives the validators the chance to halt or hard-fork the chain in the disaster-case of accidentally recognizing an invalid event
Messages
Injectable Transactions
MsgAcknowledgeBridges
This message is injected by the block proposer.
It should be rejected during ProcessProposal if any of below is true
event IDs are not sequential
first event’s id does not match NextAcknowledgeEventId in state.
last event’s id is not recognized yet.
any event’s content does not match what’s in state.
For each bridge event, it delays a MsgCompleteBridge (see below) using x/delaymsg module to be executed SafetyParams.delay_blocks blocks later.
This message should only be sent by the x/delaymsg module. It will mint tokens to the specified address by transferring from the bridge module account. The id does not have to be verified.
Let the wall-clock time of the proposer be wallClock. Let the block timestamp be blockTimestamp. In order to ensure an upper-bound on liveness issues in the case that +⅓ of validators cannot properly get logs from an Ethereum node, we will often skip proposing bridge events at all. Since we expect the rate of bridge events to be much lower than 1 per v4 block, we use a combination of probabilistic (based on pseudo-randomness) and deterministic (based on time) skipping. Therefore, skip this step altogether (by proposing a bridge tx with empty bridge events) if either of the following:
In PrepareProposal, the proposer will inject one MsgAcknowledgeBridges transaction that includes at most ProposeParams.max_bridges_per_block bridge events (in order of id), which need to satisfy following criteria
An id greater-than-or-equal-to the NextAcknowledgeEventId
A recognizedAt ≤ wallClock - ProposeParams.propose_delay_duration
Returns the RecognizedEventInfo from the BridgeEventManager. This has the next event id that has not yet been recognized by this node’s daemon. This also has the height of the highest Ethereum block from which a bridge event was recognized. These values are not in-consensus.
For each bridge event, sends a message to the x/delay-msg module, wrapping a MsgCompleteBridge to be executed SafetyParams.delay_blocks blocks in the future. Update AcknowledgedEventInfo in state.
Processes a bridge event by transfering the appropriate tokens to the given address from bridge module account. The id of the bridge is not validated as it should have already been validated by AcknowledgeBridges.
Stores information about the next event id to acknowledge (next_id). The bridge daemon should query for this event id next.
Also stores the Ethereum block height of the most recently acknowledged event. Ethereum block heights strictly lower than this number do not have to be checked by the daemon.
In practice, these value should be set to positive numbers at genesis since some of the bridging events should be included in genesis state.
message BridgeEventInfo {
// The next event id (the last processed id plus one) of the logs from the
// Ethereum contract.
uint32 next_id = 1;
// The Ethereum block height of the most recently acknowledged bridge event.
uint64 eth_block_height = 2;
}
Param Store
EventParams
EventParams
Stores parameters about which events to recognize and which tokens to mint. Used by the daemon to sanity-check information. Is not used directly by the module logic.
message EventParams {
// The denom of the token to mint.
string denom = 1;
// The numerical chain ID of the Ethereum chain to query.
uint64 eth_chain_id = 2;
// The address of the Ethereum contract to monitor for logs.
string eth_address = 3;
}
ProposeParams
ProposeParams
Holds the proposal parameters for the module.
message ProposeParams {
// The maximum number of bridge events to propose per block.
// Limits the number of events to propose in a single block
// in-order to smooth out the flow of events.
uint32 max_bridges_per_block = 1;
// The minimum amount of nanoseconds to wait between a finalized bridge and
// proposing it. This allows other validators to have enough time to
// also recognize its occurence. Therefore the bridge daemon should
// poll for new finalized events at least as often as this parameter.
google.protobuf.Duration propose_delay_duration = 2;
// Do not propose any events if a random number generator
// (between 0 and 1_000_000) generates a number smaller than this number.
uint32 skip_rate_ppm = 3;
// Do not propose any events if the timestamp of the proposal block is
// behind the proposers' wall-clock by at least this duration.
google.protobuf.Duration skip_if_block_delayed_by_duration = 4;
}
SafetyParams
SafetyParams
Holds the safety parameters for the module.
message SafetyParams {
// True if bridging is disabled.
bool is_disabled = 1;
// The number of blocks that bridges accepted in-consensus will be pending
// until the minted tokens are granted.
uint32 delay_blocks = 2;
}
In-Memory Data-Structures
The keeper stores the bridge events in a go-routine safe map which is keyed by id.
// BridgeEventManager maintains a map of "Recognized" Bridge Events.
// That is, events that have been finalized on Ethereum but are
// not yet in consensus on the dYdX Chain. Methods are goroutine safe.
type BridgeEventManager struct {
// Exclusive mutex taken when reading or writing
sync.Mutex
// Bridge events by ID
events map[uint32]types.BridgeEventWithTime
// Next unused key in the bridges map
nextRecognizedEventId uint32
// Time provider than can mocked out if necessary
timeProvider lib.TimeProvider
}
// BridgeEventWithTime is a type that wraps BridgeEvent but also
// holds an additional timestamp.
type BridgeEventWithTime struct {
event types.BridgeEvent
timestamp time.Time
}
Types
Protos
message BridgeEvent {
// The unique id of the Ethereum event log.
uint32 id = 1;
// The tokens bridged.
cosmos.base.v1beta1.Coin coin = 2
[ (gogoproto.nullable) = false ];
// The account address or module address to bridge to.
string address = 3
[ (cosmos_proto.scalar) = "cosmos.AddressString" ];
}