Skip to content

Commit

Permalink
docs: bridge service (#223)
Browse files Browse the repository at this point in the history
Co-authored-by: Arpit Temani <[email protected]>
Co-authored-by: Rachit Sonthalia <[email protected]>
  • Loading branch information
3 people committed Mar 3, 2025
1 parent 0840885 commit 4193500
Show file tree
Hide file tree
Showing 6 changed files with 298 additions and 17 deletions.
5 changes: 3 additions & 2 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Summary

- [Getting Started](./getting_started.md)
- [Local Debug](./local_debug.md)
- [Local Debug](./local_debug.md)
- [AggOracle](./aggoracle.md)
- [Aggsender](./aggsender.md)
- [Release lifecycle](./release_lifecycle.md)
- [Bridge service](./bridge_service.md)
- [Release lifecycle](./release_lifecycle.md)
278 changes: 278 additions & 0 deletions docs/bridge_service.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
# Bridge service component

The bridge service abstracts interaction with the unified LxLy bridge. It represents decentralized indexer, that sequences the bridge data. Each bridge service sequences L1 network and a dedicated L2 one (which is uniquely defined by the network id parameter). Therefore, each agglayer connected chain runs its own bridge service. It is implemented as a JSON RPC service.

## Bridge flow

The diagram below describes the basic L2 -> L2 bridge workflow. Note that L2 networks consist of the `aggkit` node and execution client.

```mermaid
sequenceDiagram
User->>L2 (A): Bridge assets to L2 (B)
L2 (A)->>L2 (A): Index bridge tx
L2 (A)->>AggLayer: Send certificate
AggLayer->>L1: Settle batch
AggLayer-->>L2 (A): L1 tx hash
L2 (A)->>L2 (A): Add relation bridge : included in L1InfoTree index X
User->>L2 (A): Poll is bridge ready for claim
L2 (A)-->>User: L1InfoTree index X
User->>L2 (B): Get first GER injected that happened at or after L1InfoTree index X
L2 (B)-->>User: GER Y
User->>L2 (A): Build proof for bridge using GER Y
L2 (A)-->>User: Proof
User->>L2 (B): Claim (proof)
L2 (B)->>L2 (B): Send claim tx<br/>(bridge is settled on the L2 (B))
L2 (B)-->>User: Tx hash
```

## Indexers

The bridge service relies on specific data located on different chains (such as `bridge`, `claim`, and `token mapping` events, as well as the L1 info tree). This data is retrieved using indexer components. In this paragraph, we will list and briefly describe each of them.

### L1 Info Tree Sync

It interacts with L1 execution layer (via RPC) in order to:

- Sync the L1 info tree,
- Generate merkle proofs,
- Build the relation `bridge <-> L1InfoTree index` for bridges originated on L1
- Sync the rollup exit tree (namely a tree consisted of all local exit trees, that tracks exits per rollup network), persist, generate proofs

### Bridge Sync

It interacts with the L2 or L1 execution layer (via RPC) in order to:

- Sync bridges, claims and token mappings. Needs to be modular as it's execution client specific.
- Build the local exit tree
- Generate merkle proofs

## Bridging custom ERC20 token

When a non-native ERC20 token, not yet mapped on a destination network, is bridged, its representation is deployed on the destination network using the `CREATE2` opcode. The mapping process emits the `NewWrappedToken` [event](https://github.com/0xPolygonHermez/zkevm-contracts/blob/21d3fd6ec0881731de49f1a6133fb97ed863a7ab/contracts/v2/PolygonZkEVMBridgeV2.sol#L561-L566) on the destination network.

Mapped token details are available via the `bridge_getTokenMappings` endpoint.

The following diagram depicts the basic flow of bridging the custom ERC20 token.

```mermaid
sequenceDiagram
participant User
participant OriginERC20 as Origin ERC20 Token
participant OriginBridge as Origin Bridge Contract
participant DestIndexer as Destination Bridge Indexer
participant DestBridge as Destination Bridge Contract
%% Step 1: Approve Transaction
User->>OriginERC20: approve(amount)
Note right of OriginERC20: User authorizes bridge to transfer tokens
%% Step 2: Call Bridge Asset
User->>OriginBridge: bridgeAsset(amount, destinationNetwork)
OriginBridge-->>User: Transaction receipt (bridge asset event emitted)
%% Step 3: Indexing on Destination
DestIndexer-->>OriginBridge: Polls for bridge asset event
OriginBridge-->>DestIndexer: Emits bridge asset event
Note right of DestIndexer: Indexes bridge asset transaction
%% Step 4: Polling for Claim Readiness
loop Poll until ready for claim
User->>DestIndexer: Is bridge ready for claim?
DestIndexer-->>User: Not ready yet / Ready signal
end
%% Step 5: Claim Bridge on Destination
User->>DestBridge: claimBridge(leafValue, proofLocalExitRoot, proofRollupExitRoot)
Note right of DestBridge: `leafValue` consists of bridge data <br/> (e.g. globalIndex, originNetwork, originTokenAddress, <br/>destinationNetwork, destinationAddress etc.)
DestBridge-->>DestBridge: Deploys wrapped token
DestBridge-->>DestBridge: Performs token mapping
%% Step 6: Final Transaction Hash to User
DestBridge-->>User: Transaction hash (wrapped token deployed)
Note right of User: Bridge process completed successfully
```

## API

This paragraph explains a set of endpoints, that are necessary in order to conduct the bridge flow described above.

### Get bridges

Retrieves the bridge(s) for a specified network, supporting pagination. The bridges represent the `BridgeEvent` events emitted by the bridge contract.

#### Parameters

| **Name** | **Type** | **Description** | **Required** | **Notes** |
|----------------|-----------|---------------------------------------------------------------- |--------------|---------------------------|
| `networkID` | `uint32` | ID of the network to fetch bridges from. | Yes | `0` for L1 (otherwise L2) |
| `pageNumber` | `*uint32` | Page number for pagination (pointer to `uint32`). | No | Defaults if `nil`. |
| `pageSize` | `*uint32` | Number of items per page (pointer to `uint32`). | No | Defaults if `nil`. |
| `depositCount` | `*uint32` | Query param for a specific Deposit Count (pointer to `uint32`). | No | Defaults if `nil`. |


#### Return value

Successful response (`BridgesResult`)

- `bridges`: Array of bridges (Bridge) with details:
- `block_num`: Block number where the event was recorded.
- `block_pos`: Position of the log within the block.
- `block_timestamp`: Timestamp of the block.
- `leaf_type`: Type of bridge (unspecified, asset, message).
- `origin_network`: Network ID where the original token resides.
- `origin_address`: Address of the original token on the origin network.
- `destination_network`: Destination network where we are bridging to
- `destination_address`: Destination address where we are bridging to
- `amount`: Amount of the bridge
- `metadata`: Additional encoded information.
- `deposit_count`: Deposit count for the bridge
- `tx_hash`: Hash of the transaction that triggered the event.
- `from_address`: Address which initiated the event.
- `bridge_hash`: Hash of the bridge
- `count`: Total number of bridges available.

```json
{
"bridges": [
{
"block_num": 11952,
"block_pos": 1,
"block_timestamp": 1739563963,
"leaf_type": 1,
"origin_network": 0,
"origin_address": "0xc67dc429d7bde82abf29ae609c9213276d803acf",
"destination_network": 0,
"destination_address": "0xa67dc429d7bde82abf29ae609c9213276d803acf",
"amount": 1,
"metadata": "Base64 or Hex-encoded data",
"deposit_count": 1,
"tx_hash": "0xd4c6e67a65e6cc35965d692cfc7c176d954a660bc7bef34dd5dd3491a53352b5",
"from_address": "0xb67dc429d7bde82abf29ae609c9213276d803acf",
}
],
"count": 1
}
```

Failed response (`rpc.Error`)

- `code` - error code
- `message` - error message

### Get claims

Retrieves the claim(s) for a specified network with support for pagination returning results in descending order of `GlobalIndex`. The claims represent the `ClaimEvent` events emitted by the bridge contract.

#### Parameters

| **Name** | **Type** | **Description** | **Required** | **Notes** |
|----------------|-----------|---------------------------------------------------------------- |--------------|---------------------------|
| `networkID` | `uint32` | ID of the network to fetch claims from. | Yes | `0` for L1 (otherwise L2) |
| `pageNumber` | `*uint32` | Page number for pagination (pointer to `uint32`). | No | Defaults if `nil`. |
| `pageSize` | `*uint32` | Number of items per page (pointer to `uint32`). | No | Defaults if `nil`. |

#### Return value

Successful response (`ClaimsResult`)

- `claims`: Array of claims (Claim) with details:
- `block_num`: Block number where the event was recorded.
- `block_timestamp`: Timestamp of the block.
- `tx_hash`: Hash of the transaction that triggered the `ClaimEvent`.
- `global_index`: Global index of the claim.
- `origin_address`: Address of the original token on the origin network.
- `origin_network`: Network ID where the original token resides.
- `destination_address`: Destination address where we are claiming.
- `destination_network`: Destination network where we are claiming.
- `amount`: Amount of the claim.
- `from_address`: Address which initiated the event.
- `count`: Total number of claims available.

```json
{
"claims": [
{
"block_num": 11952,
"block_timestamp": 1739563963,
"tx_hash": "0x02152c9059502b05d5fa68cb796be72eba32c9b2504c004a775a80008cbc0766",
"global_index": 18446744073709551610,
"origin_address": "0xb26f25ed6bcb8dd7c1c7266e12fa4aaff48fc892",
"origin_network": 0,
"destination_address": "0x85da99c8a7c2c95964c8efd687e95e632fc533d6",
"destination_network": 1,
"amount": 100000000000000000,
"from_address": "0xb67dc429d7bde82abf29ae609c9213276d803acf",
}
],
"count": 1
}
```

Failed response (`rpc.Error`)

- `code` - error code
- `message` - error message

### Get token mappings

Retrieves the token mappings for a specified network, supporting pagination. The token mappings represent the `NewWrappedToken` events emitted by the bridge contract.

#### Parameters

| **Name** | **Type** | **Description** | **Required** | **Notes** |
|--------------------|-------------|-------------------------------------------------------------|--------------|---------------------------------|
| `networkID` | `uint32` | ID of the network to fetch token mappings from. | Yes | `0` for L1(otherwise L2) |
| `pageNumber` | `*uint32` | Page number for pagination (pointer to `uint32`). | No | Defaults if `nil`. |
| `pageSize` | `*uint32` | Number of items per page (pointer to `uint32`). | No | Defaults if `nil`. |

---

#### Return value

Successful response (`TokenMappingResult`)

- `tokenMappings`: Array of token mappings (TokenMapping) with details:
- `block_num`: Block number where the event was recorded.
- `block_pos`: Position of the log within the block.
- `block_timestamp`: Timestamp of the block.
- `tx_hash`: Hash of the transaction that triggered the event.
- `origin_network`: Network ID where the original token resides.
- `origin_token_address`: Address of the original token on the origin network.
- `wrapped_token_address`: Address of the deployed wrapped token on the destination network.
- `metadata`: Additional encoded information.
- `count`: Total number of token mappings available.

```json
{
"tokenMappings": [
{
"block_num": 11952,
"block_pos": 1,
"block_timestamp": 1739563963,
"tx_hash": "0xd4c6e67a65e6cc35965d692cfc7c176d954a660bc7bef34dd5dd3491a53352b5",
"origin_network": 0,
"origin_token_address": "0xc67dc429d7bde82abf29ae609c9213276d803acf",
"wrapped_token_address": "0xa10efd92865e759b699ba2d96047459d89ca4844",
"metadata": "Base64 or Hex-encoded data"
}
],
"count": 1
}
```

Failed response (`rpc.Error`)

- `code` - error code
- `message` - error message

### L1 info tree index for bridge

TBD

### Injected L1 tree info after index

TBD

### Get proof

TBD
26 changes: 15 additions & 11 deletions rpc/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ type TokenMappingsResult struct {
Count int `json:"count"`
}

// GetTokenMappings returns the token mappings for the given network
// GetTokenMappings returns the token mappings for the given network.
// If networkID is 0, it returns the token mappings for the L1 network.
// If networkID is the same as the client, it returns the token mappings for the L2 network.
// The result is paginated.
func (b *BridgeEndpoints) GetTokenMappings(networkID uint32, pageNumber, pageSize *uint32) (interface{}, rpc.Error) {
b.logger.Debugf("GetTokenMappings request received (network id=%d)", networkID)

Expand Down Expand Up @@ -229,13 +232,13 @@ type BridgesResult struct {
Count int `json:"count"`
}

func (b *BridgeEndpoints) GetBridges(
networkID uint32,
pageNumber, pageSize *uint32,
depositCount *uint64,
) (interface{}, rpc.Error) {
// GetBridges returns the bridges for the given network and the total count of bridges.
// If networkID is 0, it returns the bridges for the L1 network.
// If networkID is the same as the client, it returns the bridges for the L2 network.
// The result is paginated.
func (b *BridgeEndpoints) GetBridges(networkID uint32, pageNumber, pageSize *uint32,
depositCount *uint64) (interface{}, rpc.Error) {
b.logger.Debugf("GetBridges request received (network id=%d)", networkID)

ctx, cancel, pageNumberU32, pageSizeU32, setupErr := b.setupRequest(pageNumber, pageSize, "get_bridges")
if setupErr != nil {
return nil, setupErr
Expand Down Expand Up @@ -280,11 +283,12 @@ type ClaimsResult struct {
Count int `json:"count"`
}

func (b *BridgeEndpoints) GetClaims(networkID uint32,
pageNumber, pageSize *uint32,
) (interface{}, rpc.Error) {
// GetClaims returns the claims for the given network.
// If networkID is 0, it returns the claims for the L1 network.
// If networkID is the same as the client, it returns the claims for the L2 network.
// The result is paginated.
func (b *BridgeEndpoints) GetClaims(networkID uint32, pageNumber, pageSize *uint32) (interface{}, rpc.Error) {
b.logger.Debugf("GetClaims request received (network id=%d)", networkID)

ctx, cancel, pageNumberU32, pageSizeU32, setupErr := b.setupRequest(pageNumber, pageSize, "get_claims")
if setupErr != nil {
return nil, setupErr
Expand Down
3 changes: 1 addition & 2 deletions test/bats/pp/bridge-e2e-msg.bats
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ setup() {
readonly weth_token_addr=$(cast call --rpc-url $l2_rpc_url $bridge_addr 'WETHToken() (address)')
}


@test "Transfer message" {
echo "====== bridgeMessage L1 -> L2" >&3
destination_addr=$sender_addr
Expand All @@ -66,4 +65,4 @@ setup() {
destination_net=0
run bridge_message "$destination_addr" "$l2_rpc_url"
assert_success
}
}
1 change: 0 additions & 1 deletion test/bats/pp/bridge-e2e.bats
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,6 @@ setup() {

@test "ERC20 token deposit L1 -> L2" {
echo "Retrieving ERC20 contract artifact from $erc20_artifact_path" >&3
# local erc20_bytecode=$(cat "$erc20_artifact_path" | jq -r '.bytecode')

run jq -r '.bytecode' "$erc20_artifact_path"
assert_success
Expand Down
2 changes: 1 addition & 1 deletion test/bats/pp/e2e-pp.bats
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
setup() {
load '../helpers/common-setup'

_common_setup

if [ -z "$BRIDGE_ADDRESS" ]; then
Expand Down

0 comments on commit 4193500

Please sign in to comment.