Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds L2EP Linea integration support #3567

Merged
merged 1 commit into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading