Skip to content

Commit

Permalink
Adds L2EP Linea integration support (#3567)
Browse files Browse the repository at this point in the history
  • Loading branch information
mohamed-mehany authored Nov 18, 2024
1 parent bb2a56f commit 78cca56
Show file tree
Hide file tree
Showing 12 changed files with 216 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/healthy-windows-chew.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@chainlink/layer2-sequencer-health-adapter': minor
---

Adds L2EP support for Linea chain
10 changes: 7 additions & 3 deletions packages/sources/layer2-sequencer-health/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ Adapter that checks the Layer 2 Sequencer status
| | `BASE_HEALTH_ENDPOINT` | Base Health Endpoint | | |
| | `BASE_CHAIN_ID` | The chain id to connect to Base | | 8453 |
| | `BASE_DELTA` | Maximum time in milliseconds from last seen block to consider Base sequencer healthy | | 120000 (2 min) |
| | `LINEA_RPC_ENDPOINT` | Linea RPC Endpoint | | https://rpc.linea.build |
| | `LINEA_HEALTH_ENDPOINT` | Linea Health Endpoint | | |
| | `LINEA_CHAIN_ID` | The chain id to connect to Linea | | 59144 |
| | `LINEA_DELTA` | Maximum time in milliseconds from last seen block to consider Linea sequencer healthy | | 120000 (2 min) |
| | `METIS_RPC_ENDPOINT` | Metis RPC Endpoint | | https://andromeda.metis.io/?owner=1088 |
| | `METIS_HEALTH_ENDPOINT` | Metis Health Endpoint | | https://andromeda-healthy.metisdevops.link/health |
| | `METIS_CHAIN_ID` | The chain id to connect to Metis | | 1088 |
Expand All @@ -42,9 +46,9 @@ For the adapter to be useful on the desired network, at least one endpoint (RPC

### Input Parameters

| Required? | Name | Description | Options | Defaults to |
| :-------: | :-----: | :----------------------: | :----------------------------------------------------------------------: | :---------: |
|| network | Layer 2 Network to check | `arbitrum`, `optimism`, `base`, `metis`, `scroll`, `starkware`, `zksync` | |
| Required? | Name | Description | Options | Defaults to |
| :-------: | :-----: | :----------------------: | :-------------------------------------------------------------------------------: | :---------: |
|| network | Layer 2 Network to check | `arbitrum`, `optimism`, `base`, `linea`, `metis`, `scroll`, `starkware`, `zksync` | |

---

Expand Down
18 changes: 18 additions & 0 deletions packages/sources/layer2-sequencer-health/schemas/env.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
"type": "number",
"default": 120000
},
"LINEA_DELTA": {
"type": "number",
"default": 120000
},
"METIS_DELTA": {
"type": "number",
"default": 600000
Expand Down Expand Up @@ -74,6 +78,20 @@
"type": "string",
"default": "8453"
},
"LINEA_RPC_ENDPOINT": {
"type": "string",
"default": "https://rpc.linea.build"
},
"LINEA_HEALTH_ENDPOINT": {
"type": "string",
"default": ""
},
"LINEA_CHAIN_ID": {
"required": false,
"description": "The blockchain id to connect to",
"type": "string",
"default": "59144"
},
"METIS_RPC_ENDPOINT": {
"type": "string",
"default": "https://andromeda.metis.io/?owner=1088"
Expand Down
15 changes: 15 additions & 0 deletions packages/sources/layer2-sequencer-health/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,23 @@ export const DEFAULT_RETRY_INTERVAL = 5 * 100
export const ENV_ARBITRUM_RPC_ENDPOINT = 'ARBITRUM_RPC_ENDPOINT'
export const ENV_OPTIMISM_RPC_ENDPOINT = 'OPTIMISM_RPC_ENDPOINT'
export const ENV_BASE_RPC_ENDPOINT = 'BASE_RPC_ENDPOINT'
export const ENV_LINEA_RPC_ENDPOINT = 'LINEA_RPC_ENDPOINT'
export const ENV_METIS_RPC_ENDPOINT = 'METIS_RPC_ENDPOINT'
export const ENV_SCROLL_RPC_ENDPOINT = 'SCROLL_RPC_ENDPOINT'
export const ENV_ZKSYNC_RPC_ENDPOINT = 'ZKSYNC_RPC_ENDPOINT'

export const ENV_ARBITRUM_CHAIN_ID = 'ARBITRUM_CHAIN_ID'
export const ENV_OPTIMISM_CHAIN_ID = 'OPTIMISM_CHAIN_ID'
export const ENV_BASE_CHAIN_ID = 'BASE_CHAIN_ID'
export const ENV_LINEA_CHAIN_ID = 'BASE_CHAIN_ID'
export const ENV_METIS_CHAIN_ID = 'METIS_CHAIN_ID'
export const ENV_SCROLL_CHAIN_ID = 'SCROLL_CHAIN_ID'
export const ENV_ZKSYNC_CHAIN_ID = 'ZKSYNC_CHAIN_ID'

export const DEFAULT_ARBITRUM_CHAIN_ID = '42161'
export const DEFAULT_OPTIMISM_CHAIN_ID = '10'
export const DEFAULT_BASE_CHAIN_ID = '8453'
export const DEFAULT_LINEA_CHAIN_ID = '59144'
export const DEFAULT_METIS_CHAIN_ID = '1088'
export const DEFAULT_SCROLL_CHAIN_ID = '534352'
export const DEFAULT_ZKSYNC_CHAIN_ID = '324'
Expand All @@ -49,6 +52,7 @@ export enum Networks {
Arbitrum = 'arbitrum',
Optimism = 'optimism',
Base = 'base',
Linea = 'linea',
Metis = 'metis',
Scroll = 'scroll',
Starkware = 'starkware',
Expand All @@ -60,6 +64,7 @@ export type EVMNetworks = Exclude<Networks, Networks.Starkware>
const DEFAULT_ARBITRUM_RPC_ENDPOINT = 'https://arb1.arbitrum.io/rpc'
const DEFAULT_OPTIMISM_RPC_ENDPOINT = 'https://mainnet.optimism.io'
const DEFAULT_BASE_RPC_ENDPOINT = 'https://mainnet.base.org'
const DEFAULT_LINEA_RPC_ENDPOINT = 'https://rpc.linea.build'
const DEFAULT_METIS_RPC_ENDPOINT = 'https://andromeda.metis.io/?owner=1088'
const DEFAULT_SCROLL_RPC_ENDPOINT = 'https://rpc.scroll.io'
const DEFAULT_ZKSYNC_RPC_ENDPOINT = 'https://mainnet.era.zksync.io'
Expand All @@ -68,6 +73,7 @@ export const RPC_ENDPOINTS: Record<EVMNetworks, string | undefined> = {
[Networks.Arbitrum]: util.getEnv(ENV_ARBITRUM_RPC_ENDPOINT) || DEFAULT_ARBITRUM_RPC_ENDPOINT,
[Networks.Optimism]: util.getEnv(ENV_OPTIMISM_RPC_ENDPOINT) || DEFAULT_OPTIMISM_RPC_ENDPOINT,
[Networks.Base]: util.getEnv(ENV_BASE_RPC_ENDPOINT) || DEFAULT_BASE_RPC_ENDPOINT,
[Networks.Linea]: util.getEnv(ENV_LINEA_RPC_ENDPOINT) || DEFAULT_LINEA_RPC_ENDPOINT,
[Networks.Metis]: util.getEnv(ENV_METIS_RPC_ENDPOINT) || DEFAULT_METIS_RPC_ENDPOINT,
[Networks.Scroll]: util.getEnv(ENV_SCROLL_RPC_ENDPOINT) || DEFAULT_SCROLL_RPC_ENDPOINT,
[Networks.zkSync]: util.getEnv(ENV_ZKSYNC_RPC_ENDPOINT) || DEFAULT_ZKSYNC_RPC_ENDPOINT,
Expand All @@ -83,6 +89,9 @@ export const CHAIN_IDS: Record<EVMNetworks, number | undefined | string> = {
[Networks.Base]:
parseInt(util.getEnv(ENV_BASE_CHAIN_ID) || DEFAULT_BASE_CHAIN_ID) ||
util.getEnv(ENV_BASE_CHAIN_ID),
[Networks.Linea]:
parseInt(util.getEnv(ENV_LINEA_CHAIN_ID) || DEFAULT_LINEA_CHAIN_ID) ||
util.getEnv(ENV_LINEA_CHAIN_ID),
[Networks.Metis]:
parseInt(util.getEnv(ENV_METIS_CHAIN_ID) || DEFAULT_METIS_CHAIN_ID) ||
util.getEnv(ENV_METIS_CHAIN_ID),
Expand All @@ -98,6 +107,7 @@ export const CHAIN_DELTA: Record<Networks, number> = {
[Networks.Arbitrum]: Number(util.getEnv('ARBITRUM_DELTA')) || DEFAULT_DELTA_TIME,
[Networks.Optimism]: Number(util.getEnv('OPTIMISM_DELTA')) || DEFAULT_DELTA_TIME,
[Networks.Base]: Number(util.getEnv('BASE_DELTA')) || DEFAULT_DELTA_TIME,
[Networks.Linea]: Number(util.getEnv('LINEA_DELTA')) || DEFAULT_DELTA_TIME,
[Networks.Metis]: Number(util.getEnv('METIS_DELTA')) || DEFAULT_DELTA_TIME_METIS,
[Networks.Scroll]: Number(util.getEnv('SCROLL_DELTA')) || DEFAULT_DELTA_TIME,
[Networks.Starkware]: Number(util.getEnv('STARKWARE_DELTA')) || DEFAULT_DELTA_TIME,
Expand Down Expand Up @@ -132,6 +142,11 @@ export const HEALTH_ENDPOINTS: HeathEndpoints = {
responsePath: [],
processResponse: () => undefined,
},
[Networks.Linea]: {
endpoint: util.getEnv('LINEA_HEALTH_ENDPOINT'),
responsePath: [],
processResponse: () => undefined,
},
[Networks.Metis]: {
endpoint: util.getEnv('METIS_HEALTH_ENDPOINT') || DEFAULT_METIS_HEALTH_ENDPOINT,
responsePath: ['healthy'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const supportedEndpoints = ['health']
const defaultRequireTxFailure = {
[Networks.Arbitrum]: false,
[Networks.Base]: false,
[Networks.Linea]: false,
[Networks.Metis]: false,
[Networks.Optimism]: false,
[Networks.Scroll]: false,
Expand All @@ -36,6 +37,7 @@ export const inputParameters: InputParameters<TInputParameters> = {
options: [
Networks.Arbitrum,
Networks.Base,
Networks.Linea,
Networks.Metis,
Networks.Optimism,
Networks.Scroll,
Expand Down
10 changes: 10 additions & 0 deletions packages/sources/layer2-sequencer-health/src/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ export const sendEVMDummyTransaction = async (
gasPrice: 0,
to: wallet.address,
},
[Networks.Linea]: {
value: 0,
gasLimit: 0,
gasPrice: 0,
to: wallet.address,
},
[Networks.Metis]: {
value: 0,
gasLimit: 0,
Expand Down Expand Up @@ -84,6 +90,10 @@ const lastSeenBlock: Record<EVMNetworks, { block: number; timestamp: number }> =
block: 0,
timestamp: 0,
},
[Networks.Linea]: {
block: 0,
timestamp: 0,
},
[Networks.Metis]: {
block: 0,
timestamp: 0,
Expand Down
2 changes: 2 additions & 0 deletions packages/sources/layer2-sequencer-health/src/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const sequencerOnlineErrors: Record<Networks, string[]> = {
// TODO: Optimism error needs to be confirmed by their team
[Networks.Optimism]: ['cannot accept 0 gas price transaction'],
[Networks.Base]: ['transaction underpriced'],
[Networks.Linea]: ['Gas price below configured minimum gas price'],
[Networks.Metis]: ['cannot accept 0 gas price transaction'],
[Networks.Scroll]: ['invalid transaction: insufficient funds for l1fee + gas * price + value'],
// Sending an empty transaction to the dummy Starknet address should return one
Expand Down Expand Up @@ -88,6 +89,7 @@ const isExpectedErrorMessage = (network: Networks, error: Error) => {
[Networks.Arbitrum]: ['error', 'message'],
[Networks.Optimism]: ['error', 'message'],
[Networks.Base]: ['error', 'message'],
[Networks.Linea]: ['error', 'message'],
[Networks.Metis]: ['error', 'message'],
[Networks.Scroll]: ['error', 'error', 'message'],
[Networks.Starkware]: ['message'],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`execute Linea network should return failure if tx not required 1`] = `
{
"data": {
"result": 1,
},
"jobRunID": "1",
"result": 1,
"statusCode": 200,
}
`;

exports[`execute Linea network should return success when transaction submission is known 1`] = `
{
"data": {
"result": 0,
},
"jobRunID": "1",
"result": 0,
"statusCode": 200,
}
`;

exports[`execute arbitrum network should return failure if tx not required 1`] = `
{
"data": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,38 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`execute Linea network should return failure if tx not required even if it would be successful 1`] = `
{
"data": {
"result": 1,
},
"jobRunID": "1",
"result": 1,
"statusCode": 200,
}
`;

exports[`execute Linea network should return success when all methods succeed 1`] = `
{
"data": {
"result": 0,
},
"jobRunID": "1",
"result": 0,
"statusCode": 200,
}
`;

exports[`execute Linea network should return transaction submission is successful 1`] = `
{
"data": {
"result": 0,
},
"jobRunID": "1",
"result": 0,
"statusCode": 200,
}
`;

exports[`execute arbitrum network should return failure if tx not required even if it would be successful 1`] = `
{
"data": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,19 @@ export const mockResponseSuccessBlock = (): void => {
'Origin',
])

nock('https://rpc.linea.build')
.post('/', { jsonrpc: '2.0', method: 'eth_blockNumber', params: [], id: /^\d+$/ })
.reply(200, () => ({ jsonrpc: '2.0', id: 1, result: '0x42d293' }), [
'Content-Type',
'application/json',
'Connection',
'close',
'Vary',
'Accept-Encoding',
'Vary',
'Origin',
])

nock('https://andromeda.metis.io/')
.post('/', { jsonrpc: '2.0', method: 'eth_blockNumber', params: [], id: /^\d+$/ })
.query({ owner: 1088 })
Expand Down Expand Up @@ -190,6 +203,19 @@ export const mockResponseFailureBlock = (): void => {
'Origin',
])

nock('https://rpc.linea.build')
.post('/', { jsonrpc: '2.0', method: 'eth_blockNumber', params: [], id: /^\d+$/ })
.reply(200, () => ({ jsonrpc: '2.0', id: 1, result: '0x00' }), [
'Content-Type',
'application/json',
'Connection',
'close',
'Vary',
'Accept-Encoding',
'Vary',
'Origin',
])

nock('https://andromeda.metis.io')
.post('/', { jsonrpc: '2.0', method: 'eth_blockNumber', params: [], id: /^\d+$/ })
.query({ owner: 1088 })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const mockMessages = {
'https://arb1.arbitrum.io/rpc': 'gas price too low',
'https://mainnet.optimism.io': 'cannot accept 0 gas price transaction',
'https://mainnet.base.org': 'transaction underpriced',
'https://rpc.linea.build': 'Gas price below configured minimum gas price',
'https://andromeda.metis.io/?owner=1088': 'cannot accept 0 gas price transaction',
'https://rpc.scroll.io':
'invalid transaction: insufficient funds for l1fee + gas * price + value',
Expand Down Expand Up @@ -157,6 +158,35 @@ describe('execute', () => {
})
})

describe('Linea network', () => {
it('should return success when transaction submission is known', async () => {
mockResponseFailureBlock()

const data: AdapterRequest = {
id,
data: {
network: 'linea',
requireTxFailure: true,
},
}

await sendRequestAndExpectStatus(data, 0)
})

it('should return failure if tx not required', async () => {
mockResponseFailureBlock()

const data: AdapterRequest = {
id,
data: {
network: 'linea',
},
}

await sendRequestAndExpectStatus(data, 1)
})
})

describe('metis network', () => {
it('should return success when transaction submission is known', async () => {
mockResponseFailureHealth()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,52 @@ describe('execute', () => {
})
})

describe('Linea network', () => {
it('should return success when all methods succeed', async () => {
mockResponseSuccessBlock()
mockResponseSuccessHealth()

const data: AdapterRequest = {
id,
data: {
network: 'linea',
},
}

await sendRequestAndExpectStatus(data, 0)
})

it('should return transaction submission is successful', async () => {
mockResponseFailureBlock()
mockResponseSuccessHealth()

const data: AdapterRequest = {
id,
data: {
network: 'linea',
requireTxFailure: true,
},
}

await sendRequestAndExpectStatus(data, 0)
})

it('should return failure if tx not required even if it would be successful', async () => {
mockResponseFailureBlock()
mockResponseFailureHealth()

const data: AdapterRequest = {
id,
data: {
network: 'linea',
requireTxFailure: false,
},
}

await sendRequestAndExpectStatus(data, 1)
})
})

describe('metis network', () => {
it('should return success when all methods succeed', async () => {
mockResponseSuccessHealth()
Expand Down

0 comments on commit 78cca56

Please sign in to comment.