coordinator: adds config v2 (#853)

* coordinator: adds config v2 sample

* coordinator: update config V2

* coordinator: update config v2

* coordinator: update config v2

* coordinator: config v2 wip

* spotless fix

* coordinator: adds config v2 parsers

* coordinator: adds config v2 classes and parsing tests

* coordinator: adds config v2 classes and parsing tests

* remove end2end changes from staterecovery test trigger

* coordinator: config v2 - fix prover directory config

* coordinator: add getChaindId to EthApiClient.kt

* coordinator: improve EIP1559GasProvider validation

* coordinator: add createReadOnly to Web3JL2MessageServiceSmartContractClient

* coordinator: add validation to FeeHistoryFetcherImpl

* coordinator: add more configs to v2

* coordinator: extend Web3JFactory

* coordinator: addapt CoordinatorApp to new V2 configs

* coordinator: adapt local stack coordinator configs

* coordinator: log4j clients.l1 debug

* coordinator: revert attempt to use web3signer on CI ONly :(

* coordinator: fix test and configs

* coordinator: fix test and configs

* coordinator: fix traces node address

* coordinator: remove unnecessary file

* coordinator: hardcode tracesVersion to v2.1.0 to match prover regex

* Update config/coordinator/coordinator-config-v2.toml

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Fluent Crafter <205769460+fluentcrafter@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Roman Vaseev <4833306+Filter94@users.noreply.github.com>
Signed-off-by: Fluent Crafter <205769460+fluentcrafter@users.noreply.github.com>

* coordinator: default targetBlobsPerTransaction=7u

* coordinator: hardcode tracesVersion to 2.1.0 to match prover regex

* coordinator: fix agg configs

* coordinator: strict configs log warning when config is not used

* coordinator: add carved out config files

* coordinator: add opt-in to avoid annoying warning log

* feat: update Makefile for new coordinator config file and variable name

* localstack: remove coordinator forced platform

* coordinator: add missing config on gas-price-cap-calculation

* coordinator: remove old configs

---------

Signed-off-by: Fluent Crafter <205769460+fluentcrafter@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Roman Vaseev <4833306+Filter94@users.noreply.github.com>
Co-authored-by: jonesho <81145364+jonesho@users.noreply.github.com>
Co-authored-by: jonesho <jones.ho@consensys.net>
This commit is contained in:
Fluent Crafter
2025-06-06 11:15:01 +01:00
committed by GitHub
parent c6550573d3
commit 0c7c736830
97 changed files with 4511 additions and 3487 deletions

View File

@@ -65,7 +65,6 @@ jobs:
- '.github/workflows/staterecovery-*.yml'
- '.github/workflows/main.yml'
- '.github/workflows/reuse-*.yml'
- 'e2e/**'
postman:
- 'postman/**'
- 'sdk/**'

View File

@@ -78,12 +78,10 @@ jobs:
- name: Replace expected traces api version in coordinator config file
shell: bash
run: |
sed -i 's/^\(expected-traces-api-version-v2=\).*/\1"${{ env.EXPECTED_TRACES_API_VERSION }}"/' config/coordinator/coordinator-docker.config.toml
sed -i 's/^\(expected-traces-api-version-v2=\).*/\1"${{ env.EXPECTED_TRACES_API_VERSION }}"/' config/coordinator/coordinator-docker-traces-v2-override.config.toml
sed -i 's/^\(expected-traces-api-version[ ]*=[ ]*\).*/\1"${{ env.EXPECTED_TRACES_API_VERSION }}"/' config/coordinator/coordinator-config-v2.toml
echo "EXPECTED_TRACES_API_VERSION=${{ env.EXPECTED_TRACES_API_VERSION }}"
echo "BESU_PACKAGE_TAG=${{ env.BESU_PACKAGE_TAG }}"
echo "$(grep expected-traces-api-version-v2 config/coordinator/coordinator-docker.config.toml)"
echo "$(grep expected-traces-api-version-v2 config/coordinator/coordinator-docker-traces-v2-override.config.toml)"
echo "$(grep expected-traces-api-version config/coordinator/coordinator-config-v2.toml)"
- name: Spin up fresh environment with besu tracing with retry
uses: nick-fields/retry@v3
with:

View File

@@ -4,7 +4,7 @@
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
<option name="MAIN_CLASS_NAME" value="net.consensys.zkevm.coordinator.app.CoordinatorAppMain" />
<module name="zkevm.coordinator.app.main" />
<option name="PROGRAM_PARAMETERS" value="--traces-limits-v2=config/common/traces-limits-v2.toml --smart-contract-errors=config/common/smart-contract-errors.toml --gas-price-cap-time-of-day-multipliers=config/common/gas-price-cap-time-of-day-multipliers.toml config/coordinator/coordinator-docker.config.toml config/coordinator/coordinator-docker-traces-v2-override.config.toml config/coordinator/coordinator-local-dev.config.overrides.toml config/coordinator/coordinator-local-dev.config-traces-v2.overrides.toml" />
<option name="PROGRAM_PARAMETERS" value="--traces-limits-v2=config/common/traces-limits-v2.toml --smart-contract-errors=config/common/smart-contract-errors.toml --gas-price-cap-time-of-day-multipliers=config/common/gas-price-cap-time-of-day-multipliers.toml config/coordinator/coordinator-config-v2.toml config/coordinator/coordinator-config-v2-override-local-dev.toml" />
<option name="VM_PARAMETERS" value="-Dvertx.configurationFile=config/coordinator/vertx-options.json -Dlog4j2.configurationFile=config/coordinator/log4j2-dev.xml" />
<extension name="net.ashald.envfile">
<option name="IS_ENABLED" value="false" />

View File

@@ -67,7 +67,7 @@ start-env-with-tracing-v2:
## Enable L2 geth node
start-env-with-tracing-v2-extra:
make start-env COMPOSE_PROFILES:=l1,l2 COMPOSE_FILE:=docker/compose-tracing-v2-extra-extension.yml LINEA_PROTOCOL_CONTRACTS_ONLY=true DISABLE_JSON_RPC_PRICING_PROPAGATION=false DISABLE_TYPE2_STATE_PROOF_PROVIDER=false
make start-env COMPOSE_PROFILES:=l1,l2 COMPOSE_FILE:=docker/compose-tracing-v2-extra-extension.yml LINEA_PROTOCOL_CONTRACTS_ONLY=true DISABLE_TYPE2_STATE_PROOF_PROVIDER=false
start-env-with-tracing-v2-ci:
make start-env COMPOSE_FILE=docker/compose-tracing-v2-ci-extension.yml DISABLE_TYPE2_STATE_PROOF_PROVIDER=false

View File

@@ -0,0 +1,58 @@
[defaults]
l1-endpoint = "http://127.0.0.1:8445"
l2-endpoint = "http://127.0.0.1:8545"
[prover]
[prover.execution]
fs-requests-directory = "tmp/local/prover/v3/execution/requests"
fs-responses-directory = "tmp/local/prover/v3/execution/responses"
[prover.blob-compression]
fs-requests-directory = "tmp/local/prover/v3/compression/requests"
fs-responses-directory = "tmp/local/prover/v3/compression/responses"
[prover.proof-aggregation]
fs-requests-directory = "tmp/local/prover/v3/aggregation/requests"
fs-responses-directory = "tmp/local/prover/v3/aggregation/responses"
[traces]
[traces.counters]
endpoints = ["http://127.0.0.1:8745/"]
[traces.conflation]
endpoints = ["http://127.0.0.1:8745/"]
[state-manager]
endpoints = ["http://127.0.0.1:8998/"]
[type2-state-proof-provider]
disabled = true
[l1-finalization-monitor]
l1-query-block-tag="LATEST"
[l1-submission.blob.signer]
type = "Web3j"
[l1-submission.aggregation.signer]
type = "Web3j"
[message-anchoring]
disabled = false
l1-highest-block-tag="LATEST"
l2-highest-block-tag="LATEST"
anchoring-tick-interval = "PT1S"
[message-anchoring.l1-event-scraping]
polling-interval = "PT1S"
[message-anchoring.signer]
type = "Web3j"
[l2-network-gas-pricing]
disabled = false
extra-data-update-endpoint = "http://127.0.0.1:8545/"
[database]
hostname = "127.0.0.1"
port = "5432"
[api]
observability_port = 9545

View File

@@ -0,0 +1,287 @@
[defaults]
l1-endpoint = "http://l1-el-node:8545"
l2-endpoint = "http://sequencer:8545"
[protocol]
[protocol.genesis]
genesis-state-root-hash = "0x072ead6777750dc20232d1cee8dc9a395c2d350df4bbaa5096c6f59b214dcecd"
# shnarf for contract V5
# Keccak256(parentShnarf="0x00...00", snarkHash="0x00...00",
# parentStateRootHash="0x072ead6777750dc20232d1cee8dc9a395c2d350df4bbaa5096c6f59b214dcecd",
# evaludationClaim="0x00...00", evaludationPoint="0x00...00")
genesis-shnarf = "0x47452a1b9ebadfe02bdd02f580fa1eba17680d57eec968a591644d05d78ee84f"
[protocol.l1]
contract-address = "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9"
block-time = "PT1S"
[protocol.l2]
contract-address = "0xe537D669CA013d86EBeF1D64e40fC74CADC91987"
contract-deployment-block-number = 3
[conflation]
disabled = false
blocks-limit = 2
new-blocks-polling-interval="PT1S"
conflation-deadline = "PT6S" # =3*l2_block_time
conflation-deadline-check-interval = "PT3S"
conflation-deadline-last-block-confirmation-delay = "PT2S" # recommended: at least 2 * blockInterval
l2-fetch-blocks-limit = 4000
force-stop-conflation-at-block-inclusive=100_000_000
# This is to prevent inflight trasactions that may change Smart contract state while coordinator is restarted.
# Queries SMC for last finalised block, and keeps polling until this number of blocks observe the same state.
# If state is updated meanwhile, it resets counter and restarts the polling.
consistent-number-of-blocks-on-l1-to-wait = 1
l2-endpoint = "http://sequencer:8545"
l2-logs-endpoint = "http://sequencer:8545"
[conflation.l2-request-retries]
backoff-delay = "PT1S"
failures-warning-threshold = 3
[conflation.blob-compression]
blob-compressor-version="V1_2"
blob-size-limit = 102400 # 100KB
handler-polling-interval = "PT1S"
# default batches limit is aggregation-proofs-limit -1
# batches-limit must be less than or equal to aggregation-proofs-limit-1
batches-limit = 1
[conflation.proof-aggregation]
proofs-limit = 3
deadline = "PT1M"
coordinator-polling-interval = "PT2S"
deadline-check-interval = "PT8S"
target-end-blocks = []
[prover]
version = "v3.0.0"
[prover.execution]
fs-requests-directory = "/data/prover/v3/execution/requests"
fs-responses-directory = "/data/prover/v3/execution/responses"
[prover.blob-compression]
fs-requests-directory = "/data/prover/v3/compression/requests"
fs-responses-directory = "/data/prover/v3/compression/responses"
[prover.proof-aggregation]
fs-requests-directory = "/data/prover/v3/aggregation/requests"
fs-responses-directory = "/data/prover/v3/aggregation/responses"
#[prover.new]
#switch-block-number-inclusive=1000
#[prover.new.execution]
#fs-requests-directory = "/data/prover/v3/execution/requests"
#fs-responses-directory = "/data/prover/v3/execution/responses"
#[prover.new.blob-compression]
#fs-requests-directory = "/data/prover/v3/compression/requests"
#fs-responses-directory = "/data/prover/v3/compression/responses"
#[prover.new.proof-aggregation]
#fs-requests-directory = "/data/prover/v3/aggregation/requests"
#fs-responses-directory = "/data/prover/v3/aggregation/responses"
[traces]
expected-traces-api-version = "beta-v2.1-rc16.2"
[traces.counters]
endpoints = ["http://traces-node:8545/"]
request-limit-per-endpoint = 1
[traces.counters.request-retries]
backoff-delay = "PT1S"
failures-warning-threshold = 10
[traces.conflation]
endpoints = ["http://traces-node:8545/"]
request-limit-per-endpoint = 1
[traces.conflation.request-retries]
backoff-delay = "PT1S"
failures-warning-threshold = 10
[state-manager]
version = "2.3.0"
endpoints = ["http://shomei:8888/"]
request-limit-per-endpoint = 3
[state-manager.request-retries]
max-retries = 5
backoff-delay = "PT2S"
failures-warning-threshold = 2
[type2-state-proof-provider]
disabled = false
endpoints = ["http://shomei-frontend:8888/"]
l1-query-block-tag="LATEST"
l1-polling-interval="PT1S"
[type2-state-proof-provider.request-retries]
backoff-delay = "PT1S"
failures-warning-threshold = 2
[l1-finalization-monitor]
l1-polling-interval = "PT1S"
l1-query-block-tag="LATEST"
[l1-submission]
disabled = true
[l1-submission.dynamic-gas-price-cap]
disabled = false
[l1-submission.dynamic-gas-price-cap.gas-price-cap-calculation]
adjustment-constant = 25
blob-adjustment-constant = 25
finalization-target-max-delay = "PT32H"
base-fee-per-gas-percentile-window = "P7D"
base-fee-per-gas-percentile-window-leeway = "PT10M"
base-fee-per-gas-percentile = 10
gas-price-caps-check-coefficient = 0.9
# The lower bound of the "historic base fee per blob gas" used in
# the L1 dynamic gas price cap equation
historic-base-fee-per-blob-gas-lower-bound=100000000 # 0.1 GWEI
# An optional config to replace the "historic average reward" used in
# the L1 dynamic gas price cap equation
historic-avg-reward-constant=100000000 # 0.1 GWEI
[l1-submission.dynamic-gas-price-cap.fee-history-fetcher]
fetch-interval = "PT1S"
max-block-count = 1000
reward-percentiles = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
num-of-blocks-before-latest = 2
storage-period = "P10D"
[l1-submission.fallback-gas-price]
fee-history-block-count = 10
fee-history-reward-percentile = 15
[l1-submission.blob]
disabled = false
submission-delay = "PT1S"
submission-tick-interval = "PT1S"
max-submission-transactions-per-tick = 10
target-blobs-per-transaction=9
db-max-blobs-to-return = 100
[l1-submission.blob.gas]
gas-limit = 10000000
max-fee-per-gas-cap = 100000000000
max-fee-per-blob-gas-cap = 100000000000
max-priority-fee-per-gas-cap=20000000000
# Note: prefixed with "fallback-", used when dynamic gas price is disabled or DB is not populated yet
[l1-submission.blob.gas.fallback]
priority-fee-per-gas-upper-bound = 20000000000 # 20 GWEI
priority-fee-per-gas-lower-bound = 2000000000 # 2 GWEI
[l1-submission.blob.signer]
# Web3j/Web3signer
type = "Web3signer"
# The account with this private key is in genesis file
[l1-submission.blob.signer.web3j]
private-key = "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a"
[l1-submission.blob.signer.web3signer]
endpoint = "http://web3signer:9000"
max-pool-size = 10
keep-alive = true
public-key = "9d9031e97dd78ff8c15aa86939de9b1e791066a0224e331bc962a2099a7b1f0464b8bbafe1535f2301c72c2cb3535b172da30b02686ab0393d348614f157fbdb"
[l1-submission.aggregation]
disabled = false
l1-endpoint = "http://l1-el-node:8545"
submission-delay = "PT1S"
submission-tick-interval = "PT1S"
max-submissions-per-tick = 10
[l1-submission.aggregation.gas]
gas-limit = 10_000_000
max-fee-per-gas-cap = 200_000_000_000
max-priority-fee-per-gas-cap = 40_000_000_000
[l1-submission.aggregation.gas.fallback]
# Note: prefixed with "fallback-", used when dynamic gas price is disabled or DB is not populated yet
priority-fee-per-gas-upper-bound = 20000000000 # 20 GWEI
priority-fee-per-gas-lower-bound = 2000000000 # 2 GWEI
[l1-submission.aggregation.signer]
# Web3j/Web3signer
type = "Web3signer"
[l1-submission.aggregation.signer.web3j]
private-key = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
[l1-submission.aggregation.signer.web3signer]
endpoint = "http://web3signer:9000"
max-pool-size = 10
keep-alive = true
public-key = "ba5734d8f7091719471e7f7ed6b9df170dc70cc661ca05e688601ad984f068b0d67351e5f06073092499336ab0839ef8a521afd334e53807205fa2f08eec74f4"
[message-anchoring]
disabled = false
l1-highest-block-tag="LATEST"
l2-highest-block-tag="LATEST" # optional, default to LATEST it shall not be necessary as Linea has instant finality
anchoring-tick-interval = "PT2S"
[message-anchoring.l1-event-scraping]
polling-interval = "PT1S"
polling-timeout = "PT5S"
[message-anchoring.gas]
max-fee-per-gas-cap = 100000000000
gas-limit = 10000000
fee-history-block-count = 4
fee-history-reward-percentile = 15
[message-anchoring.signer]
# Web3j/Web3signer
type = "Web3signer"
[message-anchoring.signer.web3j]
private-key = "0x4d01ae6487860981699236a58b68f807ee5f17b12df5740b85cf4c4653be0f55"
[message-anchoring.signer.web3signer]
endpoint = "http://web3signer:9000"
max-pool-size = 10
keep-alive = true
public-key = "4a788ad6fa008beed58de6418369717d7492f37d173d70e2c26d9737e2c6eeae929452ef8602a19410844db3e200a0e73f5208fd76259a8766b73953fc3e7023"
[l2-network-gas-pricing] # old [dynamic-gas-price-service]
disabled = false
price-update-interval = "PT12S"
fee-history-block-count = 50
fee-history-reward-percentile = 15
gas-price-fixed-cost = 3000000
extra-data-update-endpoint = "http://sequencer:8545/"
[l2-network-gas-pricing.extra-data-update-request-retries]
max-retries = 4
timeout = "PT7S"
backoff-delay = "PT2S"
failures-warning-threshold = 3
[l2-network-gas-pricing.flat-rate-gas-pricing]
# Relate to legacy gas pricing, goes into extradata
# and is exposed on Bessu eth_gasPrice
gas-price-upper-bound = 10000000000 # 10 GWEI
gas-price-lower-bound = 90000000 # 0.09 GWEI
compressed-tx-size = 125
expected-gas = 21000
[l2-network-gas-pricing.dynamic-gas-pricing]
# Propagated to Sequencer and Besude through extraDataPricerService and besu
# uses it dynaically culcuale the profitability of each transaction on:
# eth_sendRawTransaction, linea_estimateGas, and block building
l1-blob-gas = 131072 # 2^17 # expected-l1-blob-gas previous name: expected-blob-gas
blob-submission-expected-execution-gas = 213000
variable-cost-upper-bound = 10000000001 # ~10 GWEI
variable-cost-lower-bound = 90000001 # ~0.09 GWEI
margin = 4.0
[database]
hostname = "postgres"
port=5432
username = "postgres"
password = "postgres"
schema = "linea_coordinator"
read-pool-size = 10
read-pipelining-limit = 10
transactional-pool-size = 10
[database.persistence-retries]
max-retries = 3
backoff-delay = "PT1S"
timeout = "PT10S"
failures-warning-threshold = 2
[api]
observability-port = 9545

View File

@@ -1,35 +0,0 @@
[prover]
[prover.execution]
fs-requests-directory = "/data/prover/v3/execution/requests"
fs-responses-directory = "/data/prover/v3/execution/responses"
[prover.blob-compression]
fs-requests-directory = "/data/prover/v3/compression/requests"
fs-responses-directory = "/data/prover/v3/compression/responses"
[prover.proof-aggregation]
fs-requests-directory = "/data/prover/v3/aggregation/requests"
fs-responses-directory = "/data/prover/v3/aggregation/responses"
[traces]
blob-compressor-version="V1_2"
[traces.counters-v2]
endpoints=["http://traces-node:8545/"]
request-limit-per-endpoint=1
request-retry.backoff-delay="PT1S"
request-retry.failures-warning-threshold=2
[traces.conflation-v2]
endpoints=["http://traces-node:8545/"]
request-limit-per-endpoint=1
request-retry.backoff-delay="PT1S"
request-retry.failures-warning-threshold=2
[l2-network-gas-pricing.json-rpc-pricing-propagation]
geth-gas-price-update-recipients=[
"http://l2-node:8545/"
]
[l2-network-gas-pricing.legacy.sample-transaction-gas-pricing]
plain-transfer-cost-multiplier=1.0
# Ratio of 350 / 29400 is based on data from Mainnet. Only 0.3% of transactions are less profitable than this
# Meaning 99.7% of transactions will be includable if priced using eth_gasPrice
compressed-tx-size=350
expected-gas=29400

View File

@@ -1,13 +0,0 @@
[finalization-signer]
# Web3j/Web3signer
type="Web3Signer"
[data-submission-signer]
# Web3j/Web3signer
type="Web3Signer"
[l2-signer]
# Web3j/Web3signer
type="Web3Signer"

View File

@@ -1,288 +0,0 @@
testL1Disabled=false
duplicated-logs-debounce-time="PT15S"
eip4844-switch-l2-block-number=0
[conflation]
blocks-limit=3
conflation-deadline="PT6S"
conflation-deadline-check-interval="PT3S"
conflation-deadline-last-block-confirmation-delay="PT2S" # recommended: at least 2 * blockInterval
# This is to prevent inflight trasactions that may change Smart contract state while coordinator is restarted.
# Queries SMC for last finalised block, and keeps polling until this number of blocks observe the same state.
# If state is updated meanwhile, it resets counter and restarts the polling.
consistent-number-of-blocks-on-l1-to-wait=1
fetch-blocks-limit=4000
[blob-compression]
blob-size-limit=102400 # 100KB
handler-polling-interval="PT1S"
# default batches limit is aggregation-proofs-limit -1
# batches-limit must be less than or equal to aggregation-proofs-limit-1
batches-limit=1
[proof-aggregation]
aggregation-proofs-limit=3
aggregation-deadline="PT10S"
aggregation-coordinator-polling-interval="PT2S"
deadline-check-interval="PT8S"
#target-end-blocks=[33, 90, 93]
[prover]
fs-inprogress-request-writing-suffix = ".inprogress_coordinator_writing"
fs-inprogress-proving-suffix-pattern = ".*\\.inprogress\\.prover.*"
fs-polling-interval = "PT1S"
fs-polling-timeout = "PT10M"
[prover.execution]
fs-requests-directory = "/data/prover/v2/execution/requests"
fs-responses-directory = "/data/prover/v2/execution/responses"
[prover.blob-compression]
fs-requests-directory = "/data/prover/v2/compression/requests"
fs-responses-directory = "/data/prover/v2/compression/responses"
[prover.proof-aggregation]
fs-requests-directory = "/data/prover/v2/aggregation/requests"
fs-responses-directory = "/data/prover/v2/aggregation/responses"
#[prover.new]
#switch-block-number-inclusive=1000
#[prover.new.execution]
#fs-requests-directory = "/data/prover/v3/execution/requests"
#fs-responses-directory = "/data/prover/v3/execution/responses"
#[prover.new.blob-compression]
#fs-requests-directory = "/data/prover/v3/compression/requests"
#fs-responses-directory = "/data/prover/v3/compression/responses"
#[prover.new.proof-aggregation]
#fs-requests-directory = "/data/prover/v3/aggregation/requests"
#fs-responses-directory = "/data/prover/v3/aggregation/responses"
[traces]
blob-compressor-version="V1_2"
raw-execution-traces-version="0.2.0"
expected-traces-api-version-v2="beta-v2.1-rc16.2"
[traces.counters-v2]
endpoints=["http://traces-node:8545/"]
request-limit-per-endpoint=1
request-retry.backoff-delay="PT1S"
request-retry.failures-warning-threshold=2
[traces.conflation-v2]
endpoints=["http://traces-node:8545/"]
request-limit-per-endpoint=1
request-retry.backoff-delay="PT1S"
request-retry.failures-warning-threshold=2
[state-manager]
version="2.3.0"
endpoints=["http://shomei:8888/"]
request-limit-per-endpoint=2
request-retry.backoff-delay="PT2S"
request-retry.failures-warning-threshold=2
[type2-state-proof-provider]
endpoints=["http://shomei-frontend:8888/"]
request-retry.backoff-delay="PT1S"
request-retry.failures-warning-threshold=2
l1-query-block-tag="LATEST"
l1-polling-interval="PT12S"
[api]
observability_port=9545
[l1]
rpc-endpoint="http://l1-el-node:8545"
zk-evm-contract-address="0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9"
finalization-polling-interval="PT6S"
l1-query-block-tag="latest"
gas-limit=10000000
fee-history-block-count=10
fee-history-reward-percentile=15
# Global caps of maxFeePerGas, maxFeePerBlobGas, and maxPriorityFeePerGas
# for L1 transactions regardless of L1 dynamic gas price cap is enabled or not
max-fee-per-gas-cap=100000000000
max-fee-per-blob-gas-cap=100000000000
max-priority-fee-per-gas-cap=20000000000
# The multiplier of global caps for L1 finalization transaction
# E.g. if set as 2.0, it means the global caps of finalization txn
# will always be 2 times higher than that of blob submission txn
gas-price-cap-multiplier-for-finalization=2.0
# blocks are 2s, this may catch in between blocks
send-message-event-polling-interval="PT1S"
# 10 blocks worth at 2s per block
max-event-scraping-time="PT5S" # use by message anchoring service
# An optional config to define the L1 block time with default as PT12S
block-time="PT1S" # set the same as local L1 block time
block-range-loop-limit=500
max-messages-to-collect=1000
finalized-block-tag="latest"
# reset this once we know what to do on dev/UAT
earliest-block=0
genesis-state-root-hash="0x072ead6777750dc20232d1cee8dc9a395c2d350df4bbaa5096c6f59b214dcecd"
# shnarf for contract V6
# Keccak256(parentShnarf="0x00...00", snarkHash="0x00...00",
# parentStateRootHash="0x072ead6777750dc20232d1cee8dc9a395c2d350df4bbaa5096c6f59b214dcecd",
# evaludationClaim="0x00...00", evaludationPoint="0x00...00")
genesis-shnarf-v6="0x47452a1b9ebadfe02bdd02f580fa1eba17680d57eec968a591644d05d78ee84f"
[l2]
rpc-endpoint="http://sequencer:8545"
message-service-address="0xe537D669CA013d86EBeF1D64e40fC74CADC91987"
gas-limit=10000000
max-fee-per-gas-cap=100000000000
fee-history-block-count=4
fee-history-reward-percentile=15
last-hash-search-window=25
anchoring-receipt-polling-interval="PT01S"
max-receipt-retries=120
# Number of children blocks to wait before considering they won't be reverted and elegible for conflation.
# this is a workaround to mitigate Geth fork issues with Clique PoA
# Coordinator will consider block as finalized after being included in the chain wtih children blocks-to-finalization
# Recommended: Geth sequencer minimum of 2, Besu sequencer minimum of 1, 0 is safe localy
blocks-to-finalization=0
new-block-polling-interval="PT1S"
[blob-submission]
disabled=false
use-eth-estimate-gas=false
db-polling-interval="PT1S"
max-blobs-to-return=100
proof-submission-delay="PT1S"
max-blobs-to-submit-per-tick=10
# These lower and upper bounds will be effective only if L1 dynamic
# gas price cap is disabled or during fallback when there's insufficient
# cached fee history data to compute dynamic gas price caps
priority-fee-per-gas-upper-bound=2000000000 # 2 GWEI
priority-fee-per-gas-lower-bound=200000000 # 0.2 GWEI
[aggregation-finalization]
disabled=false
use-eth-estimate-gas=true
db-polling-interval="PT1S"
max-aggregations-to-finalize-per-tick=1
proof-submission-delay="PT1S"
[finalization-signer]
# Web3j/Web3signer
type="Web3j"
[finalization-signer.web3j]
private-key="0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
[finalization-signer.web3signer]
endpoint="http://web3signer:9000"
max-pool-size=10
keep-alive=true
public-key="ba5734d8f7091719471e7f7ed6b9df170dc70cc661ca05e688601ad984f068b0d67351e5f06073092499336ab0839ef8a521afd334e53807205fa2f08eec74f4"
[data-submission-signer]
# Web3j/Web3signer
type="Web3j"
# The account with this private key is in genesis file
[data-submission-signer.web3j]
private-key="0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a"
[data-submission-signer.web3signer]
endpoint="http://web3signer:9000"
max-pool-size=10
keep-alive=true
public-key="9d9031e97dd78ff8c15aa86939de9b1e791066a0224e331bc962a2099a7b1f0464b8bbafe1535f2301c72c2cb3535b172da30b02686ab0393d348614f157fbdb"
[l2-signer]
# Web3j/Web3signer
type="Web3j"
[l2-signer.web3j]
private-key="0x4d01ae6487860981699236a58b68f807ee5f17b12df5740b85cf4c4653be0f55"
[l2-signer.web3signer]
endpoint="http://web3signer:9000"
max-pool-size=10
keep-alive=true
public-key="4a788ad6fa008beed58de6418369717d7492f37d173d70e2c26d9737e2c6eeae929452ef8602a19410844db3e200a0e73f5208fd76259a8766b73953fc3e7023"
[message-anchoring]
disabled = false
l1-highest-block-tag="LATEST"
l2-highest-block-tag="LATEST"
l1-event-polling-interval="PT1S"
anchoring-tick-interval = "PT1S"
[message-anchoring.l1-request-retries]
failures-warning-threshold = 1
[message-anchoring.l2-request-retries]
failures-warning-threshold = 1
[l2-network-gas-pricing]
disabled = false
price-update-interval = "PT12S"
fee-history-block-count = 50
fee-history-reward-percentile = 15
blob-submission-expected-execution-gas = 213000.0 # Lower to 120k as we improve efficiency
# Defaults to expected-blob-gas
#bytes-per-data-submission=131072.0 # 2^17
l1-blob-gas = 131072 # 2^17
[l2-network-gas-pricing.request-retry]
max-retries = 3
timeout = "PT6S"
backoff-delay = "PT1S"
failures-warning-threshold = 2
[l2-network-gas-pricing.variable-cost-pricing]
gas-price-fixed-cost = 3000000
legacy-fees-multiplier = 1.2
margin = 4.0
variable-cost-upper-bound = 10000000001 # ~10 GWEI
variable-cost-lower-bound = 90000001 # ~0.09 GWEI
[l2-network-gas-pricing.extra-data-pricing-propagation]
extra-data-update-recipient = "http://sequencer:8545/"
[l2-network-gas-pricing.legacy]
type="SampleTransaction"
gas-price-upper-bound = 10000000000 # 10 GWEI
gas-price-lower-bound = 90000000 # 0.09 GWEI
[l2-network-gas-pricing.json-rpc-pricing-propagation]
geth-gas-price-update-recipients = [
"http://l2-node:8545/"
]
besu-gas-price-update-recipients = []
[l1-dynamic-gas-price-cap-service]
disabled=false
[l1-dynamic-gas-price-cap-service.gas-price-cap-calculation]
adjustment-constant=25
blob-adjustment-constant=25
finalization-target-max-delay="PT30S"
gas-fee-percentile-window="PT1M"
gas-fee-percentile-window-leeway="PT10S"
gas-fee-percentile=10
gas-price-caps-check-coefficient=0.9
# The lower bound of the "historic base fee per blob gas" used in
# the L1 dynamic gas price cap equation
historic-base-fee-per-blob-gas-lower-bound=100000000 # 0.1 GWEI
# An optional config to replace the "historic average reward" used in
# the L1 dynamic gas price cap equation
historic-avg-reward-constant=100000000 # 0.1 GWEI
[l1-dynamic-gas-price-cap-service.fee-history-fetcher]
fetch-interval="PT1S"
max-block-count=1000
reward-percentiles=[10,20,30,40,50,60,70,80,90,100]
num-of-blocks-before-latest=4
[l1-dynamic-gas-price-cap-service.fee-history-storage]
storage-period="PT2M"
[database]
host="postgres"
port="5432"
username="postgres"
password="postgres"
schema="linea_coordinator"
read_pool_size=10
read_pipelining_limit=10
transactional_pool_size=10
[persistence-retry]
#max-retries = 10 commented as can be null
backoff-delay = "PT1S"

View File

@@ -1,28 +0,0 @@
[prover]
[prover.execution]
fs-requests-directory = "tmp/local/prover/v3/execution/requests"
fs-responses-directory = "tmp/local/prover/v3/execution/responses"
[prover.blob-compression]
fs-requests-directory = "tmp/local/prover/v3/compression/requests"
fs-responses-directory = "tmp/local/prover/v3/compression/responses"
[prover.proof-aggregation]
fs-requests-directory = "tmp/local/prover/v3/aggregation/requests"
fs-responses-directory = "tmp/local/prover/v3/aggregation/responses"
[l2]
rpc-endpoint="http://127.0.0.1:8745"
blocks-to-finalization=0
[traces.counters-v2]
endpoints=["http://127.0.0.1:8745"]
[traces.conflation-v2]
endpoints=["http://127.0.0.1:8745"]
[type2-state-proof-provider]
endpoints=[]
[l2-network-gas-pricing.json-rpc-pricing-propagation]
disabled=true
geth-gas-price-update-recipients=[]
besu-gas-price-update-recipients=[]

View File

@@ -1,66 +0,0 @@
# Can override any of this propeties in CLI as follows:
# -Dconfig.override.sequencer.engine-api=http://127.0.0.1:8650
[finalization-signer.web3signer]
endpoint="http://127.0.0.1:9000"
[data-submission-signer.web3signer]
endpoint="http://127.0.0.1:9000"
[l2-signer.web3signer]
endpoint="http://127.0.0.1:9000"
[prover]
[prover.execution]
fs-requests-directory="tmp/local/prover/v3/execution/requests"
fs-responses-directory="tmp/local/prover/v3/execution/responses"
[prover.blob-compression]
fs-requests-directory="tmp/local/prover/v3/compression/requests"
fs-responses-directory="tmp/local/prover/v3/compression/responses"
[prover.proof-aggregation]
fs-requests-directory="tmp/local/prover/v3/aggregation/requests"
fs-responses-directory="tmp/local/prover/v3/aggregation/responses"
# Config of Traces API Facade endpoint
[traces]
blob-compressor-version="V1_2"
[traces.counters-v2]
endpoints=["http://127.0.0.1:8745/"]
[traces.conflation-v2]
endpoints=["http://127.0.0.1:8745/"]
[state-manager]
endpoints=["http://127.0.0.1:8998/"]
[type2-state-proof-provider]
disabled=true
endpoints=["http://127.0.0.1:8889/"]
[l2-network-gas-pricing.extra-data-pricing-propagation]
extra-data-update-recipient="http://127.0.0.1:8545/"
[l2-network-gas-pricing.json-rpc-pricing-propagation]
disabled=true
geth-gas-price-update-recipients=["http://127.0.0.1:8845/"]
besu-gas-price-update-recipients=[]
[l1]
rpc-endpoint="http://127.0.0.1:8445"
blocks-to-finalization=2
# blocks are 2s, this may catch in between blocks
send-message-event-polling-interval="PT1S"
# 10 blocks worth at 2s per block
max-event-scraping-time="PT20S"
block-range-loop-limit=10000
finalized-block-tag="finalized"
earliestBlock=0
[l2]
rpc-endpoint="http://127.0.0.1:9045"
blocks-to-finalization=0
[database]
host="localhost"
[api]
observability_port=9546

View File

@@ -116,6 +116,10 @@
<DebouncingFilter/>
<appender-ref ref="console"/>
</Logger>
<Logger name="clients.l1" level="DEBUG" additivity="false">
<DebouncingFilter/>
<appender-ref ref="console"/>
</Logger>
<!-- <Logger name="clients.TracesCounters" level="TRACE" additivity="false">-->
<!-- <DebouncingFilter/>-->
<!-- <appender-ref ref="console"/>-->

View File

@@ -107,8 +107,8 @@ run {
"config/common/smart-contract-errors.toml",
"--gas-price-cap-time-of-day-multipliers",
"config/common/gas-price-cap-time-of-day-multipliers.toml",
"config/coordinator/coordinator-docker.config.toml",
"config/coordinator/coordinator-local-dev.config.overrides.toml"
"config/coordinator/coordinator-config-v2.toml",
"config/coordinator/coordinator-config-v2-override-local-dev.toml"
]
}

View File

@@ -1,131 +0,0 @@
package linea.coordinator.config
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.get
import com.github.michaelbull.result.getOrElse
import com.sksamuel.hoplite.ConfigLoaderBuilder
import com.sksamuel.hoplite.addPathSource
import net.consensys.linea.traces.TracesCountersV2
import net.consensys.zkevm.coordinator.app.config.BlockParameterDecoder
import net.consensys.zkevm.coordinator.app.config.CoordinatorConfig
import net.consensys.zkevm.coordinator.app.config.CoordinatorConfigTomlDto
import net.consensys.zkevm.coordinator.app.config.GasPriceCapTimeOfDayMultipliersConfig
import net.consensys.zkevm.coordinator.app.config.SmartContractErrorCodesConfig
import net.consensys.zkevm.coordinator.app.config.TracesLimitsV2ConfigFile
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
import java.nio.file.Path
inline fun <reified T : Any> loadConfigsOrError(
configFiles: List<Path>,
): Result<T, String> {
val confBuilder: ConfigLoaderBuilder = ConfigLoaderBuilder.Companion
.empty()
.addDefaults()
.addDecoder(BlockParameterDecoder())
for (configFile in configFiles.reversed()) {
// files must be added in reverse order for overriding
confBuilder.addPathSource(configFile, false)
}
return confBuilder.build().loadConfig<T>(emptyList()).let { config ->
if (config.isInvalid()) {
Err(config.getInvalidUnsafe().description())
} else {
Ok(config.getUnsafe())
}
}
}
fun logErrorIfPresent(
configName: String,
configFiles: List<Path>,
configLoadingResult: Result<Any?, String>,
logger: Logger,
) {
if (configLoadingResult is Err) {
logger.error("Failed to load $configName from files=$configFiles with error=${configLoadingResult.error}")
}
}
inline fun <reified T : Any> loadConfigsAndLogErrors(
configFiles: List<Path>,
configName: String,
logger: Logger = LogManager.getLogger("linea.coordinator.config"),
): Result<T, String> {
return loadConfigsOrError<T>(configFiles)
.also { logErrorIfPresent(configName, configFiles, it, logger) }
}
fun loadConfigsOrError(
coordinatorConfigFiles: List<Path>,
tracesLimitsFileV2: Path,
gasPriceCapTimeOfDayMultipliersFile: Path,
smartContractErrorsFile: Path,
logger: Logger = LogManager.getLogger("linea.coordinator.config"),
): Result<CoordinatorConfigTomlDto, String> {
val coordinatorBaseConfigs =
loadConfigsAndLogErrors<CoordinatorConfigTomlDto>(coordinatorConfigFiles, "coordinator", logger)
val tracesLimitsV2Configs =
loadConfigsAndLogErrors<TracesLimitsV2ConfigFile>(listOf(tracesLimitsFileV2), "traces limits v2", logger)
val gasPriceCapTimeOfDayMultipliersConfig =
loadConfigsAndLogErrors<GasPriceCapTimeOfDayMultipliersConfig>(
listOf(gasPriceCapTimeOfDayMultipliersFile),
"l1 submission gas prices caps",
logger,
)
val smartContractErrorsConfig = loadConfigsAndLogErrors<SmartContractErrorCodesConfig>(
listOf(smartContractErrorsFile),
"smart contract errors",
logger,
)
val configError = listOf(
coordinatorBaseConfigs,
tracesLimitsV2Configs,
gasPriceCapTimeOfDayMultipliersConfig,
smartContractErrorsConfig,
)
.find { it is Err }
if (configError != null) {
@Suppress("UNCHECKED_CAST")
return configError as Result<CoordinatorConfigTomlDto, String>
}
val baseConfig = coordinatorBaseConfigs.get()!!
val finalConfig = baseConfig.copy(
conflation = baseConfig.conflation.copy(
_tracesLimitsV2 = tracesLimitsV2Configs.get()?.tracesLimits?.let { TracesCountersV2(it) },
_smartContractErrors = smartContractErrorsConfig.get()!!.smartContractErrors,
),
l1DynamicGasPriceCapService = baseConfig.l1DynamicGasPriceCapService.copy(
gasPriceCapCalculation = baseConfig.l1DynamicGasPriceCapService.gasPriceCapCalculation.copy(
timeOfDayMultipliers = gasPriceCapTimeOfDayMultipliersConfig.get()?.gasPriceCapTimeOfDayMultipliers,
),
),
)
return Ok(finalConfig)
}
fun loadConfigs(
coordinatorConfigFiles: List<Path>,
tracesLimitsFileV2: Path,
gasPriceCapTimeOfDayMultipliersFile: Path,
smartContractErrorsFile: Path,
logger: Logger = LogManager.getLogger("linea.coordinator.config"),
): CoordinatorConfig {
loadConfigsOrError(
coordinatorConfigFiles,
tracesLimitsFileV2,
gasPriceCapTimeOfDayMultipliersFile,
smartContractErrorsFile,
logger,
).let {
return it
.getOrElse {
throw RuntimeException("Invalid configurations: $it")
}.reified()
}
}

View File

@@ -0,0 +1,13 @@
package linea.coordinator.config
import linea.domain.RetryConfig
import net.consensys.linea.jsonrpc.client.RequestRetryConfig
fun RetryConfig.toJsonRpcRetry(): RequestRetryConfig {
return RequestRetryConfig(
maxRetries = maxRetries,
timeout = timeout,
backoffDelay = backoffDelay,
failuresWarningThreshold = failuresWarningThreshold,
)
}

View File

@@ -0,0 +1,5 @@
package linea.coordinator.config.v2
data class ApiConfig(
val observabilityPort: UInt,
)

View File

@@ -0,0 +1,46 @@
package linea.coordinator.config.v2
import linea.blob.BlobCompressorVersion
import linea.domain.RetryConfig
import net.consensys.linea.traces.TracesCountersV2
import java.net.URL
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
data class ConflationConfig(
override val disabled: Boolean = false,
val blocksLimit: UInt? = null,
val forceStopConflationAtBlockInclusive: ULong? = null,
val blocksPollingInterval: Duration = 1.seconds,
val conflationDeadline: Duration? = null, // disabled by default
val conflationDeadlineCheckInterval: Duration = 10.seconds,
// 24 second without blocks must elapse before conflation deadline is considered expired
val conflationDeadlineLastBlockConfirmationDelay: Duration = 24.seconds,
val consistentNumberOfBlocksOnL1ToWait: UInt = 32u, // 1 epoch
val l2FetchBlocksLimit: UInt = UInt.MAX_VALUE,
val l2Endpoint: URL,
val l2RequestRetries: RetryConfig = RetryConfig.endlessRetry(
backoffDelay = 1.seconds,
failuresWarningThreshold = 3u,
),
val l2GetLogsEndpoint: URL,
val blobCompression: BlobCompression = BlobCompression(),
val proofAggregation: ProofAggregation = ProofAggregation(),
val tracesLimitsV2: TracesCountersV2,
) : FeatureToggle {
data class BlobCompression(
val blobSizeLimit: UInt = 102400u,
val handlerPollingInterval: Duration = 1.seconds,
val batchesLimit: UInt? = null,
val blobCompressorVersion: BlobCompressorVersion = BlobCompressorVersion.V1_2,
)
data class ProofAggregation(
val proofsLimit: UInt = 300u,
val deadline: Duration = Duration.INFINITE,
val deadlineCheckInterval: Duration = 30.seconds,
val coordinatorPollingInterval: Duration = 3.seconds,
val targetEndBlocks: List<ULong>? = null,
val aggregationSizeMultipleOf: UInt = 1u,
)
}

View File

@@ -0,0 +1,20 @@
package linea.coordinator.config.v2
import linea.web3j.SmartContractErrors
import net.consensys.zkevm.coordinator.clients.prover.ProversConfig
data class CoordinatorConfig(
val protocol: ProtocolConfig,
val conflation: ConflationConfig,
val proversConfig: ProversConfig,
val traces: TracesConfig,
val stateManager: StateManagerConfig,
val type2StateProofProvider: Type2StateProofManagerConfig,
val l1FinalizationMonitor: L1FinalizationMonitorConfig,
val l1Submission: L1SubmissionConfig? = null,
val messageAnchoring: MessageAnchoringConfig? = null,
val l2NetworkGasPricing: L2NetworkGasPricingConfig? = null,
val database: DatabaseConfig,
val api: ApiConfig,
val smartContractErrors: SmartContractErrors,
)

View File

@@ -0,0 +1,22 @@
package linea.coordinator.config.v2
import com.sksamuel.hoplite.Masked
import linea.domain.RetryConfig
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
data class DatabaseConfig(
val host: String,
val port: Int,
val username: String,
val password: Masked,
val schema: String,
val readPoolSize: Int = 10,
val readPipeliningLimit: Int = 10,
val transactionalPoolSize: Int = 10,
val persistenceRetries: RetryConfig = RetryConfig(
backoffDelay = 1.seconds,
timeout = 10.minutes,
failuresWarningThreshold = 3u,
),
)

View File

@@ -0,0 +1,8 @@
package linea.coordinator.config.v2
interface FeatureToggle {
val disabled: Boolean
}
fun FeatureToggle?.isDisabled(): Boolean = this?.disabled ?: true
fun FeatureToggle?.isEnabled(): Boolean = !isDisabled()

View File

@@ -0,0 +1,13 @@
package linea.coordinator.config.v2
import linea.domain.BlockParameter
import java.net.URL
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
data class L1FinalizationMonitorConfig(
val l1Endpoint: URL,
val l2Endpoint: URL,
val l1PollingInterval: Duration = 6.seconds,
val l1QueryBlockTag: BlockParameter.Tag = BlockParameter.Tag.FINALIZED,
)

View File

@@ -0,0 +1,84 @@
package linea.coordinator.config.v2
import net.consensys.linea.ethereum.gaspricing.dynamiccap.TimeOfDayMultipliers
import java.net.URL
import kotlin.time.Duration
data class L1SubmissionConfig(
val dynamicGasPriceCap: DynamicGasPriceCapConfig,
val fallbackGasPrice: FallbackGasPriceConfig,
val blob: BlobSubmissionConfig,
val aggregation: AggregationSubmissionConfig,
) : FeatureToggle {
override val disabled: Boolean
get() = blob.disabled && aggregation.disabled
data class DynamicGasPriceCapConfig(
override val disabled: Boolean,
val gasPriceCapCalculation: GasPriceCapCalculationConfig,
val feeHistoryFetcher: FeeHistoryFetcherConfig,
val timeOfDayMultipliers: TimeOfDayMultipliers,
) : FeatureToggle {
data class GasPriceCapCalculationConfig(
val adjustmentConstant: UInt,
val blobAdjustmentConstant: UInt,
val finalizationTargetMaxDelay: Duration,
val baseFeePerGasPercentileWindow: Duration,
val baseFeePerGasPercentileWindowLeeway: Duration,
val baseFeePerGasPercentile: UInt,
val gasPriceCapsCheckCoefficient: Double,
val historicBaseFeePerBlobGasLowerBound: ULong,
val historicAvgRewardConstant: ULong,
val timeOfTheDayMultipliers: Map<String, Double>,
)
data class FeeHistoryFetcherConfig(
val l1Endpoint: URL,
val fetchInterval: Duration,
val maxBlockCount: UInt,
val rewardPercentiles: List<UInt>,
val numOfBlocksBeforeLatest: UInt,
val storagePeriod: Duration,
)
}
data class FallbackGasPriceConfig(
val feeHistoryBlockCount: UInt,
val feeHistoryRewardPercentile: UInt,
)
data class GasConfig(
val gasLimit: ULong,
val maxFeePerGasCap: ULong,
val maxPriorityFeePerGasCap: ULong,
val maxFeePerBlobGasCap: ULong? = null,
val fallback: FallbackGasConfig,
) {
data class FallbackGasConfig(
val priorityFeePerGasUpperBound: ULong,
val priorityFeePerGasLowerBound: ULong,
)
}
data class BlobSubmissionConfig(
override val disabled: Boolean,
val l1Endpoint: URL,
val submissionDelay: Duration,
val submissionTickInterval: Duration,
val maxSubmissionTransactionsPerTick: UInt,
val targetBlobsPerTransaction: UInt,
val dbMaxBlobsToReturn: UInt,
val gas: GasConfig,
val signer: SignerConfig,
) : FeatureToggle
data class AggregationSubmissionConfig(
override val disabled: Boolean,
val l1Endpoint: URL,
val submissionDelay: Duration,
val submissionTickInterval: Duration,
val maxSubmissionsPerTick: UInt,
val gas: GasConfig,
val signer: SignerConfig,
) : FeatureToggle
}

View File

@@ -0,0 +1,34 @@
package linea.coordinator.config.v2
import linea.domain.RetryConfig
import java.net.URL
import kotlin.time.Duration
data class L2NetworkGasPricingConfig(
override val disabled: Boolean,
val priceUpdateInterval: Duration,
val feeHistoryBlockCount: UInt,
val feeHistoryRewardPercentile: UInt,
val gasPriceFixedCost: ULong,
val dynamicGasPricing: DynamicGasPricing,
val flatRateGasPricing: FlatRateGasPricing,
val extraDataUpdateEndpoint: URL,
val extraDataUpdateRequestRetries: RetryConfig,
val l1Endpoint: URL,
) : FeatureToggle {
data class DynamicGasPricing(
val l1BlobGas: ULong,
val blobSubmissionExpectedExecutionGas: ULong,
val variableCostUpperBound: ULong,
val variableCostLowerBound: ULong,
val margin: Double,
)
data class FlatRateGasPricing(
val gasPriceLowerBound: ULong,
val gasPriceUpperBound: ULong,
val plainTransferCostMultiplier: Double = 1.0,
val compressedTxSize: UInt = 125u,
val expectedGas: UInt = 21000u,
)
}

View File

@@ -0,0 +1,71 @@
package linea.coordinator.config.v2
import linea.domain.BlockParameter
import linea.domain.RetryConfig
import java.net.URL
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
data class MessageAnchoringConfig(
override val disabled: Boolean = false,
val l1Endpoint: URL,
val l1HighestBlockTag: BlockParameter = BlockParameter.Tag.FINALIZED,
val l1RequestRetries: RetryConfig = RetryConfig.endlessRetry(
backoffDelay = 1.seconds,
failuresWarningThreshold = 3u,
),
val l1EventScrapping: L1EventScrapping = L1EventScrapping(),
val l2Endpoint: URL,
val l2HighestBlockTag: BlockParameter = BlockParameter.Tag.LATEST,
val l2RequestRetries: RetryConfig = RetryConfig.endlessRetry(
backoffDelay = 1.seconds,
failuresWarningThreshold = 3u,
),
val anchoringTickInterval: Duration = 2.seconds,
val messageQueueCapacity: UInt = 10_000u,
val maxMessagesToAnchorPerL2Transaction: UInt = 100u,
val signer: SignerConfig,
val gas: GasConfig = GasConfig(),
) : FeatureToggle {
init {
require(messageQueueCapacity >= 1u) {
"messageQueueCapacity=$messageQueueCapacity must be equal or greater than 1"
}
require(maxMessagesToAnchorPerL2Transaction >= 1u) {
"maxMessagesToAnchorPerL2Transaction=$maxMessagesToAnchorPerL2Transaction be equal or greater than 1"
}
require(anchoringTickInterval >= 1.milliseconds) {
"anchoringTickInterval must be equal or greater than 1ms"
}
}
data class L1EventScrapping(
val pollingInterval: Duration = 2.seconds,
val pollingTimeout: Duration = 5.seconds,
val ethLogsSearchSuccessBackoffDelay: Duration = 1.milliseconds,
val ethLogsSearchBlockChunkSize: UInt = 1000u,
) {
init {
require(pollingInterval >= 1.milliseconds) {
"pollingInterval=$pollingInterval must be equal or greater than 1ms"
}
require(pollingTimeout >= 1.milliseconds) {
"pollingTimeout=$pollingTimeout must be equal or greater than 1ms"
}
require(ethLogsSearchSuccessBackoffDelay >= 1.milliseconds) {
"ethLogsSearchSuccessBackoffDelay=$ethLogsSearchSuccessBackoffDelay must be equal or greater than 1ms"
}
require(ethLogsSearchBlockChunkSize >= 1u) {
"ethLogsSearchBlockChunkSize=$ethLogsSearchBlockChunkSize must be equal or greater than 1"
}
}
}
data class GasConfig(
val maxFeePerGasCap: ULong = 100_000_000_000uL, // 100 gwei
val gasLimit: ULong = 2_500_000uL,
val feeHistoryBlockCount: UInt = 4u,
val feeHistoryRewardPercentile: UInt = 15u,
)
}

View File

@@ -0,0 +1,44 @@
package linea.coordinator.config.v2
import linea.domain.BlockParameter
import kotlin.time.Duration
data class ProtocolConfig(
val genesis: Genesis,
val l1: Layer1Config,
val l2: Layer2Config,
) {
data class Genesis(
val genesisStateRootHash: ByteArray,
val genesisShnarf: ByteArray,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Genesis
if (!genesisStateRootHash.contentEquals(other.genesisStateRootHash)) return false
if (!genesisShnarf.contentEquals(other.genesisShnarf)) return false
return true
}
override fun hashCode(): Int {
var result = genesisStateRootHash.contentHashCode()
result = 31 * result + genesisShnarf.contentHashCode()
return result
}
}
data class Layer1Config(
val contractAddress: String,
val blockTime: Duration,
)
data class Layer2Config(
val contractAddress: String,
val contractDeploymentBlockNumber: BlockParameter.BlockNumber?,
)
}

View File

@@ -0,0 +1,21 @@
package linea.coordinator.config.v2
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
data class ProverConfig(
val version: String,
val fsInprogressRequestWritingSuffix: String = ".inprogress_coordinator_writing",
val fsInprogressProvingSuffixPattern: String = "\\.inprogress\\.prover.*",
val fsPollingInterval: Duration = 15.seconds,
val fsPollingTimeout: Duration? = null,
val execution: ProverDirectoriesToml,
val blobCompression: ProverDirectoriesToml,
val proofAggregation: ProverDirectoriesToml,
val new: ProverConfig? = null,
) {
data class ProverDirectoriesToml(
val fsRequestsDirectory: String,
val fsResponsesDirectory: String,
)
}

View File

@@ -0,0 +1,76 @@
package linea.coordinator.config.v2
import java.net.URL
data class SignerConfig(
val type: SignerType,
val web3j: Web3jConfig?,
val web3signer: Web3SignerConfig?,
) {
init {
when {
type == SignerType.WEB3J && web3j == null -> {
throw IllegalArgumentException("signetType=$type requires web3j config")
}
type == SignerType.WEB3SIGNER && web3signer == null -> {
throw IllegalArgumentException("signetType=$type requires web3signer config")
}
}
}
enum class SignerType() {
WEB3J,
WEB3SIGNER,
}
data class Web3jConfig(
val privateKey: ByteArray,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Web3jConfig
return privateKey.contentEquals(other.privateKey)
}
override fun hashCode(): Int {
return privateKey.hashCode()
}
override fun toString(): String {
return "Web3jConfig(privateKey=***${privateKey.size}bytes***)"
}
}
data class Web3SignerConfig(
val endpoint: URL,
val publicKey: ByteArray,
val maxPoolSize: Int = 10,
val keepAlive: Boolean = true,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Web3SignerConfig
if (maxPoolSize != other.maxPoolSize) return false
if (keepAlive != other.keepAlive) return false
if (endpoint != other.endpoint) return false
if (!publicKey.contentEquals(other.publicKey)) return false
return true
}
override fun hashCode(): Int {
var result = maxPoolSize
result = 31 * result + keepAlive.hashCode()
result = 31 * result + endpoint.hashCode()
result = 31 * result + publicKey.contentHashCode()
return result
}
}
}

View File

@@ -0,0 +1,15 @@
package linea.coordinator.config.v2
import linea.domain.RetryConfig
import java.net.URL
import kotlin.time.Duration.Companion.seconds
data class StateManagerConfig(
val version: String,
val endpoints: List<URL>,
val requestLimitPerEndpoint: UInt = UInt.MAX_VALUE,
val requestRetries: RetryConfig = RetryConfig.endlessRetry(
backoffDelay = 1.seconds,
failuresWarningThreshold = 3u,
),
)

View File

@@ -0,0 +1,22 @@
package linea.coordinator.config.v2
import linea.domain.RetryConfig
import java.net.URL
import kotlin.time.Duration.Companion.seconds
data class TracesConfig(
val expectedTracesApiVersion: String,
val counters: ClientApiConfig,
val conflation: ClientApiConfig,
// val switchBlockNumberInclusive: UInt? = null,
// val new: TracesConfig? = null
) {
data class ClientApiConfig(
val endpoints: List<URL>,
val requestLimitPerEndpoint: UInt = 100u,
val requestRetries: RetryConfig = RetryConfig.endlessRetry(
backoffDelay = 1.seconds,
failuresWarningThreshold = 3u,
),
)
}

View File

@@ -0,0 +1,18 @@
package linea.coordinator.config.v2
import linea.domain.BlockParameter
import linea.domain.RetryConfig
import java.net.URL
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
data class Type2StateProofManagerConfig(
override val disabled: Boolean = false,
val endpoints: List<URL>,
val requestRetries: RetryConfig = RetryConfig.endlessRetry(
backoffDelay = 1.seconds,
failuresWarningThreshold = 3u,
),
val l1QueryBlockTag: BlockParameter.Tag = BlockParameter.Tag.FINALIZED,
val l1PollingInterval: Duration = 6.seconds,
) : FeatureToggle

View File

@@ -0,0 +1,11 @@
package linea.coordinator.config.v2.toml
import linea.coordinator.config.v2.ApiConfig
data class ApiConfigToml(
val observabilityPort: UInt = 9545u,
) {
fun reified(): ApiConfig {
return ApiConfig(observabilityPort)
}
}

View File

@@ -0,0 +1,169 @@
package linea.coordinator.config.v2.toml
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.get
import com.github.michaelbull.result.getOrElse
import com.github.michaelbull.result.recoverIf
import com.sksamuel.hoplite.ConfigLoaderBuilder
import com.sksamuel.hoplite.ConfigResult
import com.sksamuel.hoplite.ExperimentalHoplite
import com.sksamuel.hoplite.fp.Validated
import com.sksamuel.hoplite.toml.TomlPropertySource
import linea.coordinator.config.v2.CoordinatorConfig
import linea.coordinator.config.v2.toml.decoders.BlockParameterDecoder
import linea.coordinator.config.v2.toml.decoders.BlockParameterNumberDecoder
import linea.coordinator.config.v2.toml.decoders.BlockParameterTagDecoder
import linea.coordinator.config.v2.toml.decoders.TomlByteArrayHexDecoder
import linea.coordinator.config.v2.toml.decoders.TomlKotlinDurationDecoder
import linea.coordinator.config.v2.toml.decoders.TomlSignerTypeDecoder
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
import java.nio.file.Path
fun ConfigLoaderBuilder.addCoordinatorTomlDecoders(strict: Boolean): ConfigLoaderBuilder {
return this
.addDecoder(BlockParameterTagDecoder())
.addDecoder(BlockParameterNumberDecoder())
.addDecoder(BlockParameterDecoder())
.addDecoder(TomlByteArrayHexDecoder())
.addDecoder(TomlKotlinDurationDecoder())
.addDecoder(TomlSignerTypeDecoder())
.apply { if (strict) this.strict() }
}
@OptIn(ExperimentalHoplite::class)
inline fun <reified T : Any> parseConfig(toml: String, strict: Boolean = true): T {
return ConfigLoaderBuilder
.default()
.withExplicitSealedTypes()
.addCoordinatorTomlDecoders(strict)
.addSource(TomlPropertySource(toml))
.build()
.loadConfigOrThrow<T>()
}
@OptIn(ExperimentalHoplite::class)
inline fun <reified T : Any> loadConfigsOrError(
configFiles: List<Path>,
strict: Boolean,
): Result<T, String> {
val confLoader = ConfigLoaderBuilder
.empty()
.addDefaults()
.withExplicitSealedTypes()
.addCoordinatorTomlDecoders(strict)
.build()
return confLoader
.loadConfig<T>(configFiles.reversed().map { it.toAbsolutePath().toString() })
.let { configResult: ConfigResult<T> ->
when (configResult) {
is Validated.Valid -> Ok(configResult.value)
is Validated.Invalid -> Err(configResult.getInvalidUnsafe().description())
}
}
}
fun logErrorIfPresent(
configLoadingResult: Result<Any?, String>,
logger: Logger,
logLevel: Level = Level.ERROR,
) {
if (configLoadingResult is Err) {
logger.log(logLevel, configLoadingResult.error)
}
}
inline fun <reified T : Any> loadConfigsAndLogErrors(
configFiles: List<Path>,
logger: Logger = LogManager.getLogger("linea.coordinator.config"),
strict: Boolean,
): Result<T, String> {
return loadConfigsOrError<T>(configFiles, strict = strict)
.also {
val logLevel = if (strict) Level.WARN else Level.ERROR
logErrorIfPresent(it, logger, logLevel)
}
}
fun loadConfigsOrError(
coordinatorConfigFiles: List<Path>,
tracesLimitsFileV2: Path,
gasPriceCapTimeOfDayMultipliersFile: Path,
smartContractErrorsFile: Path,
logger: Logger = LogManager.getLogger("linea.coordinator.config"),
strict: Boolean = false,
): Result<CoordinatorConfigToml, String> {
val coordinatorBaseConfigs =
loadConfigsAndLogErrors<CoordinatorConfigFileToml>(coordinatorConfigFiles, logger, strict)
val tracesLimitsV2Configs =
loadConfigsAndLogErrors<TracesLimitsConfigFileToml>(listOf(tracesLimitsFileV2), logger, strict)
val gasPriceCapTimeOfDayMultipliersConfig =
loadConfigsAndLogErrors<GasPriceCapTimeOfDayMultipliersConfigFileToml>(
listOf(gasPriceCapTimeOfDayMultipliersFile),
logger,
strict,
)
val smartContractErrorsConfig = loadConfigsAndLogErrors<SmartContractErrorCodesConfigFileToml>(
listOf(smartContractErrorsFile),
logger,
strict,
)
val configError = listOf(
coordinatorBaseConfigs,
tracesLimitsV2Configs,
gasPriceCapTimeOfDayMultipliersConfig,
smartContractErrorsConfig,
)
.find { it is Err }
if (configError != null) {
@Suppress("UNCHECKED_CAST")
return configError as Result<CoordinatorConfigToml, String>
}
val finalConfig = CoordinatorConfigToml(
configs = coordinatorBaseConfigs.get()!!,
tracesLimitsV2 = tracesLimitsV2Configs.get()!!,
l1DynamicGasPriceCapTimeOfDayMultipliers = gasPriceCapTimeOfDayMultipliersConfig.get(),
smartContractErrors = smartContractErrorsConfig.get(),
)
return Ok(finalConfig)
}
fun loadConfigs(
coordinatorConfigFiles: List<Path>,
tracesLimitsFileV2: Path,
gasPriceCapTimeOfDayMultipliersFile: Path,
smartContractErrorsFile: Path,
logger: Logger = LogManager.getLogger("linea.coordinator.config"),
enforceStrict: Boolean = false,
): CoordinatorConfig {
return loadConfigsOrError(
coordinatorConfigFiles,
tracesLimitsFileV2,
gasPriceCapTimeOfDayMultipliersFile,
smartContractErrorsFile,
logger,
strict = true,
)
.recoverIf({ !enforceStrict }, {
loadConfigsOrError(
coordinatorConfigFiles,
tracesLimitsFileV2,
gasPriceCapTimeOfDayMultipliersFile,
smartContractErrorsFile,
logger,
strict = false,
).getOrElse {
throw RuntimeException("Invalid configurations: $it")
}
})
.getOrElse {
throw RuntimeException("Invalid configurations: $it")
}
.reified()
}

View File

@@ -0,0 +1,91 @@
package linea.coordinator.config.v2.toml
import linea.blob.BlobCompressorVersion
import linea.coordinator.config.v2.ConflationConfig
import net.consensys.linea.traces.TracesCountersV2
import java.net.URL
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
data class ConflationToml(
val disabled: Boolean = false,
val blocksLimit: UInt? = null,
val forceStopConflationAtBlockInclusive: ULong? = null,
val conflationDeadline: Duration? = null,
val conflationDeadlineCheckInterval: Duration = 30.seconds,
val conflationDeadlineLastBlockConfirmationDelay: Duration = 30.seconds,
val consistentNumberOfBlocksOnL1ToWait: UInt = 32u, // 1 epoch
val newBlocksPollingInterval: Duration = 1.seconds,
val l2FetchBlocksLimit: UInt? = null,
val l2Endpoint: URL? = null,
val l2RequestRetries: RequestRetriesToml? = null,
val l2LogsEndpoint: URL? = null,
val blobCompression: BlobCompressionToml = BlobCompressionToml(),
val proofAggregation: ProofAggregationToml = ProofAggregationToml(),
) {
data class BlobCompressionToml(
val blobSizeLimit: UInt = 102400u,
val handlerPollingInterval: Duration = 1.seconds,
val batchesLimit: UInt? = null,
val blobCompressorVersion: BlobCompressorVersion = BlobCompressorVersion.V1_2,
) {
fun reified(): ConflationConfig.BlobCompression {
return ConflationConfig.BlobCompression(
blobSizeLimit = this.blobSizeLimit,
handlerPollingInterval = this.handlerPollingInterval,
batchesLimit = this.batchesLimit,
blobCompressorVersion = this.blobCompressorVersion,
)
}
}
data class ProofAggregationToml(
val proofsLimit: UInt = 300u,
val deadline: Duration? = null,
val deadlineCheckInterval: Duration = 30.seconds,
val coordinatorPollingInterval: Duration = 3.seconds,
val targetEndBlocks: List<ULong>? = null,
val aggregationSizeMultipleOf: UInt = 1u,
) {
fun reified(): ConflationConfig.ProofAggregation {
return ConflationConfig.ProofAggregation(
proofsLimit = this.proofsLimit,
deadline = this.deadline ?: Duration.INFINITE,
deadlineCheckInterval = this.deadlineCheckInterval,
coordinatorPollingInterval = this.coordinatorPollingInterval,
targetEndBlocks = this.targetEndBlocks,
aggregationSizeMultipleOf = this.aggregationSizeMultipleOf,
)
}
}
fun reified(
defaults: DefaultsToml,
tracesCountersLimitsV2: TracesCountersV2,
): ConflationConfig {
return ConflationConfig(
disabled = this.disabled,
blocksLimit = this.blocksLimit,
forceStopConflationAtBlockInclusive = this.forceStopConflationAtBlockInclusive,
blocksPollingInterval = this.newBlocksPollingInterval,
conflationDeadline = this.conflationDeadline,
conflationDeadlineCheckInterval = this.conflationDeadlineCheckInterval,
conflationDeadlineLastBlockConfirmationDelay = this.conflationDeadlineLastBlockConfirmationDelay,
consistentNumberOfBlocksOnL1ToWait = this.consistentNumberOfBlocksOnL1ToWait,
l2FetchBlocksLimit = this.l2FetchBlocksLimit ?: UInt.MAX_VALUE,
l2Endpoint = this.l2Endpoint
?: defaults.l2Endpoint
?: throw AssertionError("l2Endpoint config missing"),
l2RequestRetries = this.l2RequestRetries?.asDomain
?: defaults.l2RequestRetries.asDomain,
l2GetLogsEndpoint = this.l2LogsEndpoint
?: this.l2Endpoint
?: defaults.l2Endpoint
?: throw AssertionError("please set l2GetLogsEndpoint or l2Endpoint config"),
blobCompression = this.blobCompression.reified(),
proofAggregation = this.proofAggregation.reified(),
tracesLimitsV2 = tracesCountersLimitsV2,
)
}
}

View File

@@ -0,0 +1,73 @@
package linea.coordinator.config.v2.toml
import linea.coordinator.config.v2.CoordinatorConfig
import linea.web3j.SmartContractErrors
import net.consensys.linea.ethereum.gaspricing.dynamiccap.TimeOfDayMultipliers
import net.consensys.linea.traces.TracesCountersV2
import net.consensys.linea.traces.TracingModuleV2
data class CoordinatorConfigFileToml(
val defaults: DefaultsToml = DefaultsToml(),
val protocol: ProtocolToml,
val conflation: ConflationToml = ConflationToml(),
val prover: ProverToml,
val traces: TracesToml,
val stateManager: StateManagerToml,
val type2StateProofProvider: Type2StateProofManagerToml,
val l1FinalizationMonitor: L1FinalizationMonitorConfigToml,
val l1Submission: L1SubmissionConfigToml,
val messageAnchoring: MessageAnchoringConfigToml,
val l2NetworkGasPricing: L2NetworkGasPricingConfigToml,
val database: DatabaseToml,
val api: ApiConfigToml = ApiConfigToml(),
)
data class TracesLimitsConfigFileToml(
val tracesLimits: Map<TracingModuleV2, UInt>,
)
data class GasPriceCapTimeOfDayMultipliersConfigFileToml(
val gasPriceCapTimeOfDayMultipliers: TimeOfDayMultipliers,
)
data class SmartContractErrorCodesConfigFileToml(val smartContractErrors: SmartContractErrors)
data class CoordinatorConfigToml(
val configs: CoordinatorConfigFileToml,
val tracesLimitsV2: TracesLimitsConfigFileToml,
val l1DynamicGasPriceCapTimeOfDayMultipliers: GasPriceCapTimeOfDayMultipliersConfigFileToml? = null,
val smartContractErrors: SmartContractErrorCodesConfigFileToml? = null,
) {
fun reified(): CoordinatorConfig {
return CoordinatorConfig(
protocol = configs.protocol.reified(),
conflation = configs.conflation.reified(
defaults = configs.defaults,
tracesCountersLimitsV2 = TracesCountersV2(tracesLimitsV2.tracesLimits),
),
proversConfig = this.configs.prover.reified(),
traces = this.configs.traces.reified(),
stateManager = this.configs.stateManager.reified(),
type2StateProofProvider = this.configs.type2StateProofProvider.reified(),
l1FinalizationMonitor = this.configs.l1FinalizationMonitor.reified(
defaults = this.configs.defaults,
),
l1Submission = this.configs.l1Submission.reified(
l1DefaultEndpoint = this.configs.defaults.l1Endpoint,
timeOfDayMultipliers = l1DynamicGasPriceCapTimeOfDayMultipliers
?.gasPriceCapTimeOfDayMultipliers
?: emptyMap(),
),
messageAnchoring = this.configs.messageAnchoring.reified(
l1DefaultEndpoint = this.configs.defaults.l1Endpoint,
l2DefaultEndpoint = this.configs.defaults.l2Endpoint,
),
l2NetworkGasPricing = this.configs.l2NetworkGasPricing.reified(
l1DefaultEndpoint = this.configs.defaults.l1Endpoint,
),
database = this.configs.database.reified(),
api = this.configs.api.reified(),
smartContractErrors = smartContractErrors?.smartContractErrors ?: emptyMap(),
)
}
}

View File

@@ -0,0 +1,36 @@
package linea.coordinator.config.v2.toml
import com.sksamuel.hoplite.Masked
import linea.coordinator.config.v2.DatabaseConfig
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
data class DatabaseToml(
val hostname: String,
val port: UInt = 5432u,
val username: String,
val password: Masked,
val schema: String = "linea_coordinator",
val readPoolSize: Int = 10,
val readPipeliningLimit: Int = 10,
val transactionalPoolSize: Int = 10,
val persistenceRetries: RequestRetriesToml = RequestRetriesToml(
backoffDelay = 1.seconds,
timeout = 10.minutes,
failuresWarningThreshold = 3u,
),
) {
fun reified(): DatabaseConfig {
return DatabaseConfig(
host = this.hostname,
port = this.port.toInt(),
username = this.username,
password = this.password,
schema = this.schema,
readPoolSize = this.readPoolSize,
readPipeliningLimit = this.readPipeliningLimit,
transactionalPoolSize = this.transactionalPoolSize,
persistenceRetries = this.persistenceRetries.asDomain,
)
}
}

View File

@@ -0,0 +1,17 @@
package linea.coordinator.config.v2.toml
import java.net.URL
import kotlin.time.Duration.Companion.seconds
data class DefaultsToml(
val l1Endpoint: URL? = null,
val l2Endpoint: URL? = null,
val l1RequestRetries: RequestRetriesToml = RequestRetriesToml.endlessRetry(
backoffDelay = 1.seconds,
failuresWarningThreshold = 3u,
),
val l2RequestRetries: RequestRetriesToml = RequestRetriesToml.endlessRetry(
backoffDelay = 1.seconds,
failuresWarningThreshold = 3u,
),
)

View File

@@ -0,0 +1,25 @@
package linea.coordinator.config.v2.toml
import linea.coordinator.config.v2.L1FinalizationMonitorConfig
import linea.domain.BlockParameter
import java.net.URL
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
data class L1FinalizationMonitorConfigToml(
val l1Endpoint: URL?,
val l2Endpoint: URL?,
val l1PollingInterval: Duration = 6.seconds,
val l1QueryBlockTag: BlockParameter.Tag = BlockParameter.Tag.FINALIZED,
) {
fun reified(
defaults: DefaultsToml,
): L1FinalizationMonitorConfig {
return L1FinalizationMonitorConfig(
l1Endpoint = this.l1Endpoint ?: defaults.l1Endpoint ?: throw AssertionError("l1Endpoint missing"),
l2Endpoint = this.l2Endpoint ?: defaults.l2Endpoint ?: throw AssertionError("l2Endpoint missing"),
l1PollingInterval = this.l1PollingInterval,
l1QueryBlockTag = this.l1QueryBlockTag,
)
}
}

View File

@@ -0,0 +1,172 @@
package linea.coordinator.config.v2.toml
import linea.coordinator.config.v2.L1SubmissionConfig
import linea.coordinator.config.v2.L1SubmissionConfig.DynamicGasPriceCapConfig.GasPriceCapCalculationConfig
import net.consensys.linea.ethereum.gaspricing.dynamiccap.TimeOfDayMultipliers
import java.net.URL
import kotlin.ULong
import kotlin.time.Duration
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.seconds
data class L1SubmissionConfigToml(
val disabled: Boolean = false,
val dynamicGasPriceCap: DynamicGasPriceCapToml,
val fallbackGasPrice: FallbackGasPriceToml,
val blob: BlobSubmissionConfigToml,
val aggregation: AggregationSubmissionToml,
) {
data class DynamicGasPriceCapToml(
val disabled: Boolean = false,
val gasPriceCapCalculation: GasPriceCapCalculationToml,
val feeHistoryFetcher: FeeHistoryFetcherConfig = FeeHistoryFetcherConfig(),
) {
data class GasPriceCapCalculationToml(
val adjustmentConstant: UInt,
val blobAdjustmentConstant: UInt,
val finalizationTargetMaxDelay: Duration,
val baseFeePerGasPercentileWindow: Duration,
val baseFeePerGasPercentileWindowLeeway: Duration,
val baseFeePerGasPercentile: UInt,
val gasPriceCapsCheckCoefficient: Double,
val historicBaseFeePerBlobGasLowerBound: ULong,
val historicAvgRewardConstant: ULong,
) {
fun reified(
timeOfTheDayMultipliers: TimeOfDayMultipliers,
): GasPriceCapCalculationConfig {
return GasPriceCapCalculationConfig(
adjustmentConstant = this.adjustmentConstant,
blobAdjustmentConstant = this.blobAdjustmentConstant,
finalizationTargetMaxDelay = this.finalizationTargetMaxDelay,
baseFeePerGasPercentileWindow = this.baseFeePerGasPercentileWindow,
baseFeePerGasPercentileWindowLeeway = this.baseFeePerGasPercentileWindowLeeway,
baseFeePerGasPercentile = this.baseFeePerGasPercentile,
gasPriceCapsCheckCoefficient = this.gasPriceCapsCheckCoefficient,
historicBaseFeePerBlobGasLowerBound = this.historicBaseFeePerBlobGasLowerBound,
historicAvgRewardConstant = this.historicAvgRewardConstant,
timeOfTheDayMultipliers = timeOfTheDayMultipliers,
)
}
}
data class FeeHistoryFetcherConfig(
val l1Endpoint: URL? = null,
val fetchInterval: Duration = 3.seconds,
val maxBlockCount: UInt = 1000u,
val rewardPercentiles: List<UInt> = listOf(10u, 20u, 30u, 40u, 50u, 60u, 70u, 80u, 90u, 100u),
val numOfBlocksBeforeLatest: UInt = 4u,
val storagePeriod: Duration = 10.days,
) {
fun reified(
defaultL1Endpoint: URL?,
): L1SubmissionConfig.DynamicGasPriceCapConfig.FeeHistoryFetcherConfig {
return L1SubmissionConfig.DynamicGasPriceCapConfig.FeeHistoryFetcherConfig(
l1Endpoint = this.l1Endpoint ?: defaultL1Endpoint
?: throw AssertionError("l1Endpoint config missing"),
fetchInterval = this.fetchInterval,
maxBlockCount = this.maxBlockCount,
rewardPercentiles = this.rewardPercentiles,
numOfBlocksBeforeLatest = this.numOfBlocksBeforeLatest,
storagePeriod = this.storagePeriod,
)
}
}
}
data class FallbackGasPriceToml(
val feeHistoryBlockCount: UInt,
val feeHistoryRewardPercentile: UInt,
)
data class GasConfigToml(
val gasLimit: ULong,
val maxFeePerGasCap: ULong,
val maxFeePerBlobGasCap: ULong? = null,
val maxPriorityFeePerGasCap: ULong,
val fallback: FallbackGasConfig,
) {
data class FallbackGasConfig(
val priorityFeePerGasUpperBound: ULong,
val priorityFeePerGasLowerBound: ULong,
)
fun reified(): L1SubmissionConfig.GasConfig {
return L1SubmissionConfig.GasConfig(
gasLimit = this.gasLimit,
maxFeePerGasCap = this.maxFeePerGasCap,
maxPriorityFeePerGasCap = this.maxPriorityFeePerGasCap,
maxFeePerBlobGasCap = this.maxFeePerBlobGasCap,
fallback = L1SubmissionConfig.GasConfig.FallbackGasConfig(
priorityFeePerGasLowerBound = this.fallback.priorityFeePerGasLowerBound,
priorityFeePerGasUpperBound = this.fallback.priorityFeePerGasUpperBound,
),
)
}
}
data class BlobSubmissionConfigToml(
val disabled: Boolean = false,
val l1Endpoint: URL? = null,
val submissionDelay: Duration = 0.seconds,
val submissionTickInterval: Duration = 12.seconds,
val maxSubmissionTransactionsPerTick: UInt = 2u,
// eip-7691 on Prague fork allows up to 9 blobs per transaction.
// however, Geth nodes fail with "transaction too large" error. only 7 blobs are accepted
val targetBlobsPerTransaction: UInt = 7u,
val dbMaxBlobsToReturn: UInt = 100u,
val gas: GasConfigToml,
val signer: SignerConfigToml,
)
data class AggregationSubmissionToml(
val disabled: Boolean = false,
val l1Endpoint: URL? = null,
val submissionDelay: Duration = 0.seconds,
val submissionTickInterval: Duration = 24.seconds,
val maxSubmissionsPerTick: UInt = 1u,
val gas: GasConfigToml,
val signer: SignerConfigToml,
)
fun reified(
l1DefaultEndpoint: URL?,
timeOfDayMultipliers: TimeOfDayMultipliers,
): L1SubmissionConfig {
return L1SubmissionConfig(
dynamicGasPriceCap = L1SubmissionConfig.DynamicGasPriceCapConfig(
disabled = this.dynamicGasPriceCap.disabled,
gasPriceCapCalculation = this.dynamicGasPriceCap.gasPriceCapCalculation.reified(timeOfDayMultipliers),
feeHistoryFetcher = this.dynamicGasPriceCap.feeHistoryFetcher.reified(l1DefaultEndpoint),
timeOfDayMultipliers = timeOfDayMultipliers,
),
fallbackGasPrice = L1SubmissionConfig.FallbackGasPriceConfig(
feeHistoryBlockCount = this.fallbackGasPrice.feeHistoryBlockCount,
feeHistoryRewardPercentile = this.fallbackGasPrice.feeHistoryRewardPercentile,
),
blob = L1SubmissionConfig.BlobSubmissionConfig(
disabled = this.blob.disabled,
l1Endpoint = this.blob.l1Endpoint ?: l1DefaultEndpoint
?: throw AssertionError("l1Endpoint config missing"),
submissionDelay = this.blob.submissionDelay,
submissionTickInterval = this.blob.submissionTickInterval,
maxSubmissionTransactionsPerTick = this.blob.maxSubmissionTransactionsPerTick,
targetBlobsPerTransaction = this.blob.targetBlobsPerTransaction,
dbMaxBlobsToReturn = this.blob.dbMaxBlobsToReturn,
gas = this.blob.gas.reified(),
signer = this.blob.signer.reified(),
),
aggregation = L1SubmissionConfig.AggregationSubmissionConfig(
disabled = this.aggregation.disabled,
l1Endpoint = this.aggregation.l1Endpoint ?: l1DefaultEndpoint
?: throw AssertionError("l1Endpoint config missing"),
submissionDelay = this.aggregation.submissionDelay,
submissionTickInterval = this.aggregation.submissionTickInterval,
maxSubmissionsPerTick = this.aggregation.maxSubmissionsPerTick,
gas = this.aggregation.gas.reified(),
signer = this.aggregation.signer.reified(),
),
)
}
}

View File

@@ -0,0 +1,83 @@
package linea.coordinator.config.v2.toml
import linea.coordinator.config.v2.L2NetworkGasPricingConfig
import java.net.URL
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
data class L2NetworkGasPricingConfigToml(
val disabled: Boolean = false,
val l1Endpoint: URL? = null,
val priceUpdateInterval: Duration = 12.seconds,
val feeHistoryBlockCount: UInt = 1000u,
val feeHistoryRewardPercentile: UInt = 15u,
val gasPriceFixedCost: ULong,
val extraDataUpdateEndpoint: URL,
val extraDataUpdateRequestRetries: RequestRetriesToml = RequestRetriesToml(
timeout = 8.seconds,
backoffDelay = 1.seconds,
failuresWarningThreshold = 3u,
),
val dynamicGasPricing: DynamicGasPricingToml,
val flatRateGasPricing: FlatRateGasPricingToml,
) {
init {
require(feeHistoryBlockCount > 0u) { "feeHistoryBlockCount=$feeHistoryBlockCount must be greater than 0" }
require(feeHistoryRewardPercentile in 1u..100u) {
"feeHistoryRewardPercentile=$feeHistoryRewardPercentile must be between 1..100"
}
}
data class DynamicGasPricingToml(
val l1BlobGas: ULong,
val blobSubmissionExpectedExecutionGas: ULong,
val variableCostUpperBound: ULong,
val variableCostLowerBound: ULong,
val margin: Double,
) {
fun reified(): L2NetworkGasPricingConfig.DynamicGasPricing {
return L2NetworkGasPricingConfig.DynamicGasPricing(
l1BlobGas = this.l1BlobGas,
blobSubmissionExpectedExecutionGas = this.blobSubmissionExpectedExecutionGas,
variableCostUpperBound = this.variableCostUpperBound,
variableCostLowerBound = this.variableCostLowerBound,
margin = this.margin,
)
}
}
data class FlatRateGasPricingToml(
val gasPriceLowerBound: ULong,
val gasPriceUpperBound: ULong,
val plainTransferCostMultiplier: Double = 1.0,
val compressedTxSize: UInt = 125u,
val expectedGas: UInt = 21000u,
) {
fun reified(): L2NetworkGasPricingConfig.FlatRateGasPricing {
return L2NetworkGasPricingConfig.FlatRateGasPricing(
gasPriceLowerBound = this.gasPriceLowerBound,
gasPriceUpperBound = this.gasPriceUpperBound,
plainTransferCostMultiplier = this.plainTransferCostMultiplier,
compressedTxSize = this.compressedTxSize,
expectedGas = this.expectedGas,
)
}
}
fun reified(
l1DefaultEndpoint: URL?,
): L2NetworkGasPricingConfig {
return L2NetworkGasPricingConfig(
disabled = disabled,
priceUpdateInterval = this.priceUpdateInterval,
feeHistoryBlockCount = this.feeHistoryBlockCount,
feeHistoryRewardPercentile = this.feeHistoryRewardPercentile,
gasPriceFixedCost = this.gasPriceFixedCost,
dynamicGasPricing = this.dynamicGasPricing.reified(),
flatRateGasPricing = this.flatRateGasPricing.reified(),
extraDataUpdateEndpoint = this.extraDataUpdateEndpoint,
extraDataUpdateRequestRetries = this.extraDataUpdateRequestRetries.asDomain,
l1Endpoint = this.l1Endpoint ?: l1DefaultEndpoint ?: throw AssertionError("l1Endpoint must be set"),
)
}
}

View File

@@ -0,0 +1,106 @@
package linea.coordinator.config.v2.toml
import linea.coordinator.config.v2.MessageAnchoringConfig
import linea.domain.BlockParameter
import java.net.URL
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
data class MessageAnchoringConfigToml(
var disabled: Boolean = false,
val anchoringTickInterval: Duration = 10.seconds,
val messageQueueCapacity: UInt = 10_000u,
val maxMessagesToAnchorPerL2Transaction: UInt = 100u,
val l1Endpoint: URL? = null, // shall default to L1 endpoint
val l1HighestBlockTag: BlockParameter = BlockParameter.Tag.FINALIZED,
val l1RequestRetries: RequestRetriesToml = RequestRetriesToml.endlessRetry(
backoffDelay = 1.seconds,
failuresWarningThreshold = 3u,
),
val l1EventScraping: L1EventScrapping = L1EventScrapping(),
val l2Endpoint: URL? = null,
val l2HighestBlockTag: BlockParameter = BlockParameter.Tag.LATEST,
val l2RequestRetries: RequestRetriesToml = RequestRetriesToml(
maxRetries = null,
backoffDelay = 1.seconds,
timeout = 8.seconds,
failuresWarningThreshold = 3u,
),
val signer: SignerConfigToml,
val gas: GasConfig = GasConfig(),
) {
init {
require(messageQueueCapacity >= 1u) {
"messageQueueCapacity=$messageQueueCapacity be equal or greater than 1"
}
require(maxMessagesToAnchorPerL2Transaction >= 1u) {
"maxMessagesToAnchorPerL2Transaction=$maxMessagesToAnchorPerL2Transaction be equal or greater than 1"
}
require(anchoringTickInterval >= 1.milliseconds) {
"anchoringTickInterval must be equal or greater than 1ms"
}
}
data class L1EventScrapping(
val pollingInterval: Duration = 6.seconds,
val pollingTimeout: Duration = 5.seconds,
val ethLogsSearchSuccessBackoffDelay: Duration = 1.milliseconds,
val ethLogsSearchBlockChunkSize: UInt = 1000u,
) {
init {
require(pollingInterval >= 1.milliseconds) {
"pollingInterval=$pollingInterval must be equal or greater than 1ms"
}
require(pollingTimeout >= 1.milliseconds) {
"pollingTimeout=$pollingTimeout must be equal or greater than 1ms"
}
require(ethLogsSearchSuccessBackoffDelay >= 1.milliseconds) {
"ethLogsSearchSuccessBackoffDelay=$ethLogsSearchSuccessBackoffDelay must be equal or greater than 1ms"
}
require(ethLogsSearchBlockChunkSize >= 1u) {
"ethLogsSearchBlockChunkSize=$ethLogsSearchBlockChunkSize must be equal or greater than 1"
}
}
}
data class GasConfig(
val maxFeePerGasCap: ULong = 100_000_000_000uL, // 100 gwei
val gasLimit: ULong = 2_500_000uL,
val feeHistoryBlockCount: UInt = 4u,
val feeHistoryRewardPercentile: UInt = 15u,
)
fun reified(
l1DefaultEndpoint: URL?,
l2DefaultEndpoint: URL?,
): MessageAnchoringConfig {
return MessageAnchoringConfig(
disabled = disabled,
l1Endpoint = l1Endpoint ?: l1DefaultEndpoint ?: throw AssertionError("l1Endpoint must be set"),
l2Endpoint = l2Endpoint ?: l2DefaultEndpoint ?: throw AssertionError("l2Endpoint must be set"),
l1HighestBlockTag = l1HighestBlockTag,
l2HighestBlockTag = l2HighestBlockTag,
l1RequestRetries = l1RequestRetries.asDomain,
l2RequestRetries = l2RequestRetries.asDomain,
l1EventScrapping = MessageAnchoringConfig.L1EventScrapping(
pollingInterval = l1EventScraping.pollingInterval,
pollingTimeout = l1EventScraping.pollingTimeout,
ethLogsSearchSuccessBackoffDelay = l1EventScraping.ethLogsSearchSuccessBackoffDelay,
ethLogsSearchBlockChunkSize = l1EventScraping.ethLogsSearchBlockChunkSize,
),
anchoringTickInterval = anchoringTickInterval,
messageQueueCapacity = messageQueueCapacity.toUInt(),
maxMessagesToAnchorPerL2Transaction = maxMessagesToAnchorPerL2Transaction.toUInt(),
signer = signer.reified(),
gas = MessageAnchoringConfig.GasConfig(
maxFeePerGasCap = gas.maxFeePerGasCap,
gasLimit = gas.gasLimit,
feeHistoryBlockCount = gas.feeHistoryBlockCount,
feeHistoryRewardPercentile = gas.feeHistoryRewardPercentile,
),
)
}
}

View File

@@ -0,0 +1,65 @@
package linea.coordinator.config.v2.toml
import linea.coordinator.config.v2.ProtocolConfig
import linea.domain.BlockParameter
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
data class ProtocolToml(
val genesis: Genesis,
val l1: Layer1Config,
val l2: Layer2Config,
) {
data class Genesis(
val genesisStateRootHash: ByteArray,
val genesisShnarf: ByteArray,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Genesis
if (!genesisStateRootHash.contentEquals(other.genesisStateRootHash)) return false
if (!genesisShnarf.contentEquals(other.genesisShnarf)) return false
return true
}
override fun hashCode(): Int {
var result = genesisStateRootHash.contentHashCode()
result = 31 * result + genesisShnarf.contentHashCode()
return result
}
}
data class Layer1Config(
val contractAddress: String,
val blockTime: Duration = 12.seconds,
)
data class Layer2Config(
val contractAddress: String,
// hoplite limitation: it does not work with nullable BlockParameter.BlockNumber?
val contractDeploymentBlockNumber: ULong?,
)
fun reified(): ProtocolConfig {
return ProtocolConfig(
genesis = ProtocolConfig.Genesis(
genesisStateRootHash = this.genesis.genesisStateRootHash,
genesisShnarf = this.genesis.genesisShnarf,
),
l1 = ProtocolConfig.Layer1Config(
contractAddress = this.l1.contractAddress,
blockTime = this.l1.blockTime,
),
l2 = ProtocolConfig.Layer2Config(
contractAddress = this.l2.contractAddress,
contractDeploymentBlockNumber = this.l2.contractDeploymentBlockNumber
?.let { BlockParameter.BlockNumber(it) },
),
)
}
}

View File

@@ -0,0 +1,88 @@
package linea.coordinator.config.v2.toml
import net.consensys.zkevm.coordinator.clients.prover.FileBasedProverConfig
import net.consensys.zkevm.coordinator.clients.prover.ProverConfig
import net.consensys.zkevm.coordinator.clients.prover.ProversConfig
import java.nio.file.Path
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
data class ProverToml(
val version: String,
val fsInprogressRequestWritingSuffix: String = ".inprogress_coordinator_writing",
val fsInprogressProvingSuffixPattern: String = "\\.inprogress\\.prover.*",
val fsPollingInterval: Duration = 15.seconds,
val fsPollingTimeout: Duration = Duration.INFINITE,
val execution: ProverDirectoriesToml,
val blobCompression: ProverDirectoriesToml,
val proofAggregation: ProverDirectoriesToml,
val switchBlockNumberInclusive: ULong? = null,
val new: ProverToml? = null,
) {
data class ProverDirectoriesToml(
val fsRequestsDirectory: String,
val fsResponsesDirectory: String,
)
fun reified(): ProversConfig {
return ProversConfig(
proverA = ProverConfig(
execution = FileBasedProverConfig(
requestsDirectory = Path.of(this.execution.fsRequestsDirectory),
responsesDirectory = Path.of(this.execution.fsResponsesDirectory),
inprogressProvingSuffixPattern = this.fsInprogressProvingSuffixPattern,
inprogressRequestWritingSuffix = this.fsInprogressRequestWritingSuffix,
pollingInterval = this.fsPollingInterval,
pollingTimeout = this.fsPollingTimeout,
),
blobCompression = FileBasedProverConfig(
requestsDirectory = Path.of(this.blobCompression.fsRequestsDirectory),
responsesDirectory = Path.of(this.blobCompression.fsResponsesDirectory),
inprogressProvingSuffixPattern = this.fsInprogressProvingSuffixPattern,
inprogressRequestWritingSuffix = this.fsInprogressRequestWritingSuffix,
pollingInterval = this.fsPollingInterval,
pollingTimeout = this.fsPollingTimeout,
),
proofAggregation = FileBasedProverConfig(
requestsDirectory = Path.of(this.proofAggregation.fsRequestsDirectory),
responsesDirectory = Path.of(this.proofAggregation.fsResponsesDirectory),
inprogressProvingSuffixPattern = this.fsInprogressProvingSuffixPattern,
inprogressRequestWritingSuffix = this.fsInprogressRequestWritingSuffix,
pollingInterval = this.fsPollingInterval,
pollingTimeout = this.fsPollingTimeout,
),
),
switchBlockNumberInclusive = this.switchBlockNumberInclusive ?: this.new?.switchBlockNumberInclusive,
proverB = this.new?.let { newProverConfig ->
ProverConfig(
execution = FileBasedProverConfig(
requestsDirectory = Path.of(newProverConfig.execution.fsRequestsDirectory),
responsesDirectory = Path.of(newProverConfig.execution.fsResponsesDirectory),
inprogressProvingSuffixPattern = newProverConfig.fsInprogressProvingSuffixPattern,
inprogressRequestWritingSuffix = newProverConfig.fsInprogressRequestWritingSuffix,
pollingInterval = newProverConfig.fsPollingInterval,
pollingTimeout = newProverConfig.fsPollingTimeout,
),
blobCompression = FileBasedProverConfig(
requestsDirectory = Path.of(newProverConfig.blobCompression.fsRequestsDirectory),
responsesDirectory = Path.of(newProverConfig.blobCompression.fsResponsesDirectory),
inprogressProvingSuffixPattern = newProverConfig.fsInprogressProvingSuffixPattern,
inprogressRequestWritingSuffix = newProverConfig.fsInprogressRequestWritingSuffix,
pollingInterval = newProverConfig.fsPollingInterval,
pollingTimeout = newProverConfig.fsPollingTimeout,
),
proofAggregation = FileBasedProverConfig(
requestsDirectory = Path.of(newProverConfig.proofAggregation.fsRequestsDirectory),
responsesDirectory = Path.of(newProverConfig.proofAggregation.fsResponsesDirectory),
inprogressProvingSuffixPattern = newProverConfig.fsInprogressProvingSuffixPattern,
inprogressRequestWritingSuffix = newProverConfig.fsInprogressRequestWritingSuffix,
pollingInterval = newProverConfig.fsPollingInterval,
pollingTimeout = newProverConfig.fsPollingTimeout,
),
)
},
)
}
}

View File

@@ -0,0 +1,54 @@
package linea.coordinator.config.v2.toml
import net.consensys.linea.jsonrpc.client.RequestRetryConfig
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
data class RequestRetriesToml(
val maxRetries: UInt? = null,
val timeout: Duration? = null,
val backoffDelay: Duration = 1.seconds,
val failuresWarningThreshold: UInt? = null,
) {
init {
maxRetries?.also {
require(maxRetries >= 1u) { "maxRetries must be >=1. value=$maxRetries" }
}
timeout?.also {
require(timeout >= 1.milliseconds) { "timeout must be >= 1ms. value=$timeout" }
}
require(backoffDelay >= 1.milliseconds) {
"backoffDelay must be >= 1ms. value=$backoffDelay"
}
require(failuresWarningThreshold == null || failuresWarningThreshold > 0u) {
"failuresWarningThreshold must be greater than or equal to 0. value=$failuresWarningThreshold"
}
}
internal val asJsonRpcRetryConfig = RequestRetryConfig(
maxRetries = maxRetries?.toUInt(),
timeout = timeout,
backoffDelay = backoffDelay,
failuresWarningThreshold = failuresWarningThreshold?.toUInt() ?: 0u,
)
internal val asDomain: linea.domain.RetryConfig = linea.domain.RetryConfig(
maxRetries = maxRetries?.toUInt(),
timeout = timeout,
backoffDelay = backoffDelay,
failuresWarningThreshold = failuresWarningThreshold?.toUInt() ?: 0u,
)
companion object {
fun endlessRetry(
backoffDelay: Duration,
failuresWarningThreshold: UInt,
) = RequestRetriesToml(
maxRetries = null,
timeout = null,
backoffDelay = backoffDelay,
failuresWarningThreshold = failuresWarningThreshold,
)
}
}

View File

@@ -0,0 +1,116 @@
package linea.coordinator.config.v2.toml
import com.sksamuel.hoplite.Masked
import linea.coordinator.config.v2.SignerConfig
import linea.kotlin.decodeHex
import java.net.URL
data class SignerConfigToml(
val type: SignerType,
val web3j: Web3jConfig?,
val web3signer: Web3SignerConfig?,
) {
init {
when {
type == SignerType.WEB3J && web3j == null -> {
throw IllegalArgumentException("signetType=$type requires web3j config")
}
type == SignerType.WEB3SIGNER && web3signer == null -> {
throw IllegalArgumentException("signetType=$type requires web3signer config")
}
}
}
enum class SignerType(val mame: String) {
WEB3J("web3j"),
WEB3SIGNER("web3signer"),
;
companion object {
fun valueOfIgnoreCase(name: String): SignerType {
return SignerType.entries.firstOrNull { it.mame.equals(name, ignoreCase = true) }
?: throw IllegalArgumentException("Unknown signer type: $name")
}
}
fun reified(): SignerConfig.SignerType {
return when (this) {
WEB3J -> SignerConfig.SignerType.WEB3J
WEB3SIGNER -> SignerConfig.SignerType.WEB3SIGNER
}
}
}
data class Web3jConfig(
val privateKey: Masked,
) {
init {
runCatching {
privateKey.value.decodeHex()
}.onFailure { throw IllegalArgumentException("Invalid hexadecimal encoding of privateKey") }
.onSuccess { require(it.size == 32) { "privateKey must be 32 bytes (64 hex characters)" } }
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Web3jConfig
return privateKey.value.decodeHex().contentEquals(other.privateKey.value.decodeHex())
}
override fun hashCode(): Int {
return privateKey.hashCode()
}
}
data class Web3SignerConfig(
val endpoint: URL,
val publicKey: ByteArray,
val maxPoolSize: Int = 10,
val keepAlive: Boolean = true,
) {
init {
require(publicKey.size == 64) { "publicKey must be 64 bytes (128 hex characters)" }
require(maxPoolSize > 0) { "maxPoolSize must be greater than 0" }
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Web3SignerConfig
if (maxPoolSize != other.maxPoolSize) return false
if (keepAlive != other.keepAlive) return false
if (endpoint != other.endpoint) return false
if (!publicKey.contentEquals(other.publicKey)) return false
return true
}
override fun hashCode(): Int {
var result = maxPoolSize
result = 31 * result + keepAlive.hashCode()
result = 31 * result + endpoint.hashCode()
result = 31 * result + publicKey.contentHashCode()
return result
}
}
fun reified(): SignerConfig {
return SignerConfig(
type = type.reified(),
web3j = web3j?.let { SignerConfig.Web3jConfig(it.privateKey.value.decodeHex()) },
web3signer = web3signer?.let {
SignerConfig.Web3SignerConfig(
endpoint = it.endpoint,
publicKey = it.publicKey,
maxPoolSize = it.maxPoolSize,
keepAlive = it.keepAlive,
)
},
)
}
}

View File

@@ -0,0 +1,24 @@
package linea.coordinator.config.v2.toml
import linea.coordinator.config.v2.StateManagerConfig
import java.net.URL
import kotlin.time.Duration.Companion.seconds
data class StateManagerToml(
val version: String,
val endpoints: List<URL>,
val requestLimitPerEndpoint: UInt = UInt.MAX_VALUE,
val requestRetries: RequestRetriesToml = RequestRetriesToml.endlessRetry(
backoffDelay = 1.seconds,
failuresWarningThreshold = 3u,
),
) {
fun reified(): StateManagerConfig {
return StateManagerConfig(
version = this.version,
endpoints = this.endpoints,
requestLimitPerEndpoint = this.requestLimitPerEndpoint,
requestRetries = this.requestRetries.asDomain,
)
}
}

View File

@@ -0,0 +1,66 @@
package linea.coordinator.config.v2.toml
import linea.coordinator.config.v2.TracesConfig
import java.net.URL
import kotlin.time.Duration.Companion.seconds
data class TracesToml(
val expectedTracesApiVersion: String,
val counters: ClientApiConfigToml,
val conflation: ClientApiConfigToml,
val switchBlockNumberInclusive: UInt? = null,
val new: TracesToml? = null,
) {
data class ClientApiConfigToml(
val endpoints: List<URL>,
val requestLimitPerEndpoint: UInt = UInt.MAX_VALUE,
val requestRetries: RequestRetriesToml = RequestRetriesToml.endlessRetry(
backoffDelay = 1.seconds,
failuresWarningThreshold = 3u,
),
) {
override fun toString(): String {
return "ClientApiConfigToml(" +
"endpoints=$endpoints, " +
"requestLimitPerEndpoint=$requestLimitPerEndpoint, " +
"requestRetries=$requestRetries" +
")"
}
}
fun reified(): TracesConfig {
return TracesConfig(
expectedTracesApiVersion = expectedTracesApiVersion,
counters = TracesConfig.ClientApiConfig(
endpoints = counters.endpoints,
requestLimitPerEndpoint = counters.requestLimitPerEndpoint,
requestRetries = counters.requestRetries.asDomain,
),
conflation = TracesConfig.ClientApiConfig(
endpoints = conflation.endpoints,
requestLimitPerEndpoint = conflation.requestLimitPerEndpoint,
requestRetries = conflation.requestRetries.asDomain,
),
/*
switchBlockNumberInclusive = switchBlockNumberInclusive,
new = new?.let { newTracesConfig ->
TracesConfig(
expectedTracesApiVersion = newTracesConfig.expectedTracesApiVersion,
counters = TracesConfig.ClientApiConfig(
endpoints = newTracesConfig.counters.endpoints,
requestLimitPerEndpoint = newTracesConfig.counters.requestLimitPerEndpoint,
requestRetries = newTracesConfig.counters.requestRetries.asDomain
),
conflation = TracesConfig.ClientApiConfig(
endpoints = newTracesConfig.conflation.endpoints,
requestLimitPerEndpoint = newTracesConfig.conflation.requestLimitPerEndpoint,
requestRetries = newTracesConfig.conflation.requestRetries.asDomain
),
switchBlockNumberInclusive = null,
new = null
)
}
*/
)
}
}

View File

@@ -0,0 +1,28 @@
package linea.coordinator.config.v2.toml
import linea.coordinator.config.v2.Type2StateProofManagerConfig
import linea.domain.BlockParameter
import java.net.URL
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
data class Type2StateProofManagerToml(
val disabled: Boolean = false,
val endpoints: List<URL>,
val requestRetries: RequestRetriesToml = RequestRetriesToml.endlessRetry(
backoffDelay = 1.seconds,
failuresWarningThreshold = 3u,
),
val l1QueryBlockTag: BlockParameter.Tag = BlockParameter.Tag.FINALIZED,
val l1PollingInterval: Duration = 6.seconds,
) {
fun reified(): Type2StateProofManagerConfig {
return Type2StateProofManagerConfig(
disabled = this.disabled,
endpoints = this.endpoints,
requestRetries = this.requestRetries.asDomain,
l1QueryBlockTag = this.l1QueryBlockTag,
l1PollingInterval = this.l1PollingInterval,
)
}
}

View File

@@ -1,4 +1,4 @@
package net.consensys.zkevm.coordinator.app.config
package linea.coordinator.config.v2.toml.decoders
import com.sksamuel.hoplite.ConfigFailure
import com.sksamuel.hoplite.ConfigResult
@@ -12,21 +12,27 @@ import com.sksamuel.hoplite.fp.valid
import linea.domain.BlockParameter
import kotlin.reflect.KType
class BlockParameterDecoder : Decoder<BlockParameter> {
override fun supports(type: KType): Boolean = type.classifier == BlockParameter::class
override fun decode(node: Node, type: KType, context: DecoderContext): ConfigResult<BlockParameter> {
@Suppress("UNCHECKED_CAST")
open class AbstractBlockParameterDecoder<T : BlockParameter> : Decoder<T> {
override fun supports(type: KType): Boolean = type.classifier in listOf(
BlockParameter::class,
BlockParameter.Tag::class,
BlockParameter.BlockNumber::class,
)
override fun decode(node: Node, type: KType, context: DecoderContext): ConfigResult<T> {
return when (node) {
is StringNode -> runCatching {
BlockParameter.parse(node.value)
}.fold(
{ it.valid() },
{ (it as T).valid() },
{ ConfigFailure.DecodeError(node, type).invalid() },
)
is LongNode -> runCatching {
BlockParameter.fromNumber(node.value)
}.fold(
{ it.valid() },
{ (it as T).valid() },
{ ConfigFailure.DecodeError(node, type).invalid() },
)
@@ -34,3 +40,15 @@ class BlockParameterDecoder : Decoder<BlockParameter> {
}
}
}
class BlockParameterTagDecoder : AbstractBlockParameterDecoder<BlockParameter.Tag>() {
override fun supports(type: KType): Boolean = type.classifier == BlockParameter.Tag::class
}
class BlockParameterNumberDecoder : AbstractBlockParameterDecoder<BlockParameter.BlockNumber>() {
override fun supports(type: KType): Boolean = type.classifier == BlockParameter.BlockNumber::class
}
class BlockParameterDecoder : AbstractBlockParameterDecoder<BlockParameter>() {
override fun supports(type: KType): Boolean = type.classifier == BlockParameter::class
}

View File

@@ -0,0 +1,83 @@
package linea.coordinator.config.v2.toml.decoders
import com.sksamuel.hoplite.ConfigFailure
import com.sksamuel.hoplite.ConfigResult
import com.sksamuel.hoplite.DecoderContext
import com.sksamuel.hoplite.Node
import com.sksamuel.hoplite.StringNode
import com.sksamuel.hoplite.decoder.Decoder
import com.sksamuel.hoplite.fp.invalid
import com.sksamuel.hoplite.fp.valid
import linea.coordinator.config.v2.toml.SignerConfigToml
import linea.kotlin.decodeHex
import kotlin.reflect.KType
import kotlin.time.Duration
class TomlByteArrayHexDecoder : Decoder<ByteArray> {
override fun decode(
node: Node,
type: KType,
context: DecoderContext,
): ConfigResult<ByteArray> {
return when (node) {
is StringNode -> runCatching {
node.value.decodeHex()
}.fold(
{ it.valid() },
{ ConfigFailure.DecodeError(node, type).invalid() },
)
else -> { ConfigFailure.DecodeError(node, type).invalid() }
}
}
override fun supports(type: KType): Boolean {
return type.classifier == ByteArray::class
}
}
class TomlKotlinDurationDecoder : Decoder<Duration> {
override fun decode(
node: Node,
type: KType,
context: DecoderContext,
): ConfigResult<Duration> {
return when (node) {
is StringNode -> runCatching {
Duration.parse(node.value)
}.fold(
{ it.valid() },
{ ConfigFailure.DecodeError(node, type).invalid() },
)
else -> { ConfigFailure.DecodeError(node, type).invalid() }
}
}
override fun supports(type: KType): Boolean {
return type.classifier == Duration::class
}
}
class TomlSignerTypeDecoder : Decoder<SignerConfigToml.SignerType> {
override fun decode(
node: Node,
type: KType,
context: DecoderContext,
): ConfigResult<SignerConfigToml.SignerType> {
return when (node) {
is StringNode -> runCatching {
SignerConfigToml.SignerType.valueOfIgnoreCase(node.value.lowercase())
}.fold(
{ it.valid() },
{ ConfigFailure.DecodeError(node, type).invalid() },
)
else -> { ConfigFailure.DecodeError(node, type).invalid() }
}
}
override fun supports(type: KType): Boolean {
return type.classifier == SignerConfigToml.SignerType::class
}
}

View File

@@ -3,12 +3,11 @@ package net.consensys.zkevm.coordinator.app
import io.vertx.core.Vertx
import io.vertx.core.http.HttpVersion
import io.vertx.ext.web.client.WebClientOptions
import linea.kotlin.encodeHex
import linea.web3j.SmartContractErrors
import linea.web3j.transactionmanager.AsyncFriendlyTransactionManager
import net.consensys.linea.contract.l1.Web3JLineaRollupSmartContractClient
import net.consensys.linea.httprest.client.VertxHttpRestClient
import net.consensys.zkevm.coordinator.app.config.L1Config
import net.consensys.zkevm.coordinator.app.config.SignerConfig
import net.consensys.zkevm.coordinator.clients.smartcontract.LineaRollupSmartContractClient
import net.consensys.zkevm.ethereum.crypto.Web3SignerRestClient
import net.consensys.zkevm.ethereum.crypto.Web3SignerTxSignService
@@ -18,30 +17,29 @@ import org.web3j.protocol.Web3j
import org.web3j.service.TxSignServiceImpl
import org.web3j.tx.gas.ContractGasProvider
import org.web3j.utils.Numeric
import java.net.URI
fun createTransactionManager(
vertx: Vertx,
signerConfig: SignerConfig,
signerConfig: linea.coordinator.config.v2.SignerConfig,
client: Web3j,
): AsyncFriendlyTransactionManager {
val transactionSignService = when (signerConfig.type) {
SignerConfig.Type.Web3j -> {
TxSignServiceImpl(Credentials.create(signerConfig.web3j!!.privateKey.value))
linea.coordinator.config.v2.SignerConfig.SignerType.WEB3J -> {
TxSignServiceImpl(Credentials.create(signerConfig.web3j!!.privateKey.encodeHex()))
}
SignerConfig.Type.Web3Signer -> {
linea.coordinator.config.v2.SignerConfig.SignerType.WEB3SIGNER -> {
val web3SignerConfig = signerConfig.web3signer!!
val endpoint = URI(web3SignerConfig.endpoint)
val endpoint = web3SignerConfig.endpoint
val webClientOptions: WebClientOptions =
WebClientOptions()
.setKeepAlive(web3SignerConfig.keepAlive)
.setProtocolVersion(HttpVersion.HTTP_1_1)
.setMaxPoolSize(web3SignerConfig.maxPoolSize.toInt())
.setMaxPoolSize(web3SignerConfig.maxPoolSize)
.setDefaultHost(endpoint.host)
.setDefaultPort(endpoint.port)
val httpRestClient = VertxHttpRestClient(webClientOptions, vertx)
val signer = Web3SignerRestClient(httpRestClient, signerConfig.web3signer.publicKey)
val signer = Web3SignerRestClient(httpRestClient, signerConfig.web3signer.publicKey.encodeHex())
val signerAdapter = ECKeypairSignerAdapter(signer, Numeric.toBigInt(signerConfig.web3signer.publicKey))
val web3SignerCredentials = Credentials.create(signerAdapter)
Web3SignerTxSignService(web3SignerCredentials)
@@ -52,7 +50,7 @@ fun createTransactionManager(
}
fun createLineaRollupContractClient(
l1Config: L1Config,
contractAddress: String,
transactionManager: AsyncFriendlyTransactionManager,
contractGasProvider: ContractGasProvider,
web3jClient: Web3j,
@@ -60,7 +58,7 @@ fun createLineaRollupContractClient(
useEthEstimateGas: Boolean,
): LineaRollupSmartContractClient {
return Web3JLineaRollupSmartContractClient.load(
contractAddress = l1Config.zkEvmContractAddress,
contractAddress = contractAddress,
web3j = web3jClient,
transactionManager = transactionManager,
contractGasProvider = contractGasProvider,

View File

@@ -4,15 +4,14 @@ import io.micrometer.core.instrument.MeterRegistry
import io.vertx.core.Vertx
import io.vertx.micrometer.backends.BackendRegistries
import io.vertx.sqlclient.SqlClient
import linea.web3j.createWeb3jHttpClient
import linea.coordinator.config.v2.CoordinatorConfig
import linea.coordinator.config.v2.DatabaseConfig
import net.consensys.linea.async.toSafeFuture
import net.consensys.linea.jsonrpc.client.LoadBalancingJsonRpcClient
import net.consensys.linea.jsonrpc.client.VertxHttpJsonRpcClientFactory
import net.consensys.linea.metrics.micrometer.MicrometerMetricsFacade
import net.consensys.linea.vertx.loadVertxConfig
import net.consensys.zkevm.coordinator.api.Api
import net.consensys.zkevm.coordinator.app.config.CoordinatorConfig
import net.consensys.zkevm.coordinator.app.config.DatabaseConfig
import net.consensys.zkevm.fileio.DirectoryCleaner
import net.consensys.zkevm.persistence.dao.aggregation.AggregationsRepositoryImpl
import net.consensys.zkevm.persistence.dao.aggregation.PostgresAggregationsDao
@@ -23,17 +22,12 @@ import net.consensys.zkevm.persistence.dao.batch.persistence.RetryingBatchesPost
import net.consensys.zkevm.persistence.dao.blob.BlobsPostgresDao
import net.consensys.zkevm.persistence.dao.blob.BlobsRepositoryImpl
import net.consensys.zkevm.persistence.dao.blob.RetryingBlobsPostgresDao
import net.consensys.zkevm.persistence.dao.feehistory.FeeHistoriesPostgresDao
import net.consensys.zkevm.persistence.dao.feehistory.FeeHistoriesRepositoryImpl
import net.consensys.zkevm.persistence.db.Db
import net.consensys.zkevm.persistence.db.PersistenceRetryer
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
import org.web3j.protocol.Web3j
import tech.pegasys.teku.infrastructure.async.SafeFuture
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toKotlinDuration
class CoordinatorApp(private val configs: CoordinatorConfig) {
private val log: Logger = LogManager.getLogger(this::class.java)
@@ -59,20 +53,13 @@ class CoordinatorApp(private val configs: CoordinatorConfig) {
),
vertx,
)
private val l2Web3jClient: Web3j = createWeb3jHttpClient(
rpcUrl = configs.l2.rpcEndpoint.toString(),
log = LogManager.getLogger("clients.l2.eth-api.rpc-node"),
pollingInterval = 1.seconds,
requestResponseLogLevel = Level.TRACE,
failuresLogLevel = Level.DEBUG,
)
private val persistenceRetryer = PersistenceRetryer(
vertx = vertx,
config = PersistenceRetryer.Config(
backoffDelay = configs.persistenceRetry.backoffDelay.toKotlinDuration(),
maxRetries = configs.persistenceRetry.maxRetries,
timeout = configs.persistenceRetry.timeout?.toKotlinDuration(),
backoffDelay = configs.database.persistenceRetries.backoffDelay,
maxRetries = configs.database.persistenceRetries.maxRetries?.toInt(),
timeout = configs.database.persistenceRetries.timeout,
),
)
@@ -92,7 +79,7 @@ class CoordinatorApp(private val configs: CoordinatorConfig) {
blobsDao = RetryingBlobsPostgresDao(
delegate = BlobsPostgresDao(
config = BlobsPostgresDao.Config(
maxBlobsToReturn = configs.blobSubmission.maxBlobsToReturn.toUInt(),
maxBlobsToReturn = configs.l1Submission?.blob?.dbMaxBlobsToReturn ?: 50u,
),
connection = sqlClient,
),
@@ -109,30 +96,15 @@ class CoordinatorApp(private val configs: CoordinatorConfig) {
),
)
private val l1FeeHistoriesRepository =
FeeHistoriesRepositoryImpl(
FeeHistoriesRepositoryImpl.Config(
rewardPercentiles = configs.l1DynamicGasPriceCapService.feeHistoryFetcher.rewardPercentiles,
minBaseFeePerBlobGasToCache =
configs.l1DynamicGasPriceCapService.gasPriceCapCalculation.historicBaseFeePerBlobGasLowerBound,
fixedAverageRewardToCache =
configs.l1DynamicGasPriceCapService.gasPriceCapCalculation.historicAvgRewardConstant,
),
FeeHistoriesPostgresDao(
sqlClient,
),
)
private val l1App = L1DependentApp(
configs = configs,
vertx = vertx,
l2Web3jClient = l2Web3jClient,
httpJsonRpcClientFactory = httpJsonRpcClientFactory,
batchesRepository = batchesRepository,
blobsRepository = blobsRepository,
aggregationsRepository = aggregationsRepository,
l1FeeHistoriesRepository = l1FeeHistoriesRepository,
smartContractErrors = configs.conflation.smartContractErrors,
sqlClient = sqlClient,
smartContractErrors = configs.smartContractErrors,
metricsFacade = micrometerMetricsFacade,
)
@@ -175,7 +147,6 @@ class CoordinatorApp(private val configs: CoordinatorConfig) {
return kotlin.runCatching {
SafeFuture.allOf(
l1App.stop(),
SafeFuture.fromRunnable { l2Web3jClient.shutdown() },
api.stop().toSafeFuture(),
).thenApply {
LoadBalancingJsonRpcClient.stop()

View File

@@ -1,6 +1,6 @@
package net.consensys.zkevm.coordinator.app
import net.consensys.zkevm.coordinator.app.config.CoordinatorConfig
import linea.coordinator.config.v2.CoordinatorConfig
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
import picocli.CommandLine
@@ -89,7 +89,7 @@ internal constructor(private val errorWriter: PrintWriter, private val startActi
}
}
val configs = linea.coordinator.config.loadConfigs(
val configs = linea.coordinator.config.v2.toml.loadConfigs(
coordinatorConfigFiles = configFiles.map { it.toPath() },
tracesLimitsFileV2 = tracesLimitsV2File.toPath(),
smartContractErrorsFile = smartContractErrorsFile.toPath(),

View File

@@ -1,6 +1,6 @@
package net.consensys.zkevm.coordinator.app
import net.consensys.zkevm.coordinator.app.config.CoordinatorConfig
import linea.coordinator.config.v2.CoordinatorConfig
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.core.LoggerContext
import org.apache.logging.log4j.core.config.Configurator

View File

@@ -0,0 +1,15 @@
package net.consensys.zkevm.coordinator.app
import net.consensys.zkevm.LongRunningService
import tech.pegasys.teku.infrastructure.async.SafeFuture
import java.util.concurrent.CompletableFuture
object DisabledLongRunningService : LongRunningService {
override fun start(): CompletableFuture<Unit> {
return SafeFuture.completedFuture(Unit)
}
override fun stop(): CompletableFuture<Unit> {
return SafeFuture.completedFuture(Unit)
}
}

View File

@@ -1,597 +0,0 @@
package net.consensys.zkevm.coordinator.app.config
import com.sksamuel.hoplite.ConfigAlias
import com.sksamuel.hoplite.Masked
import linea.blob.BlobCompressorVersion
import linea.domain.BlockParameter
import linea.domain.assertIsValidAddress
import linea.kotlin.assertIs32Bytes
import linea.kotlin.decodeHex
import linea.web3j.SmartContractErrors
import net.consensys.linea.ethereum.gaspricing.dynamiccap.MAX_FEE_HISTORIES_STORAGE_PERIOD
import net.consensys.linea.ethereum.gaspricing.dynamiccap.MAX_FEE_HISTORY_BLOCK_COUNT
import net.consensys.linea.ethereum.gaspricing.dynamiccap.MAX_REWARD_PERCENTILES_SIZE
import net.consensys.linea.ethereum.gaspricing.dynamiccap.TimeOfDayMultipliers
import net.consensys.linea.ethereum.gaspricing.dynamiccap.getAllTimeOfDayKeys
import net.consensys.linea.jsonrpc.client.RequestRetryConfig
import net.consensys.linea.traces.TracesCounters
import net.consensys.linea.traces.TracesCountersV2
import net.consensys.linea.traces.TracingModuleV2
import net.consensys.zkevm.coordinator.app.L2NetworkGasPricingService
import net.consensys.zkevm.coordinator.clients.prover.ProversConfig
import java.math.BigInteger
import java.net.URL
import java.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toJavaDuration
import kotlin.time.toKotlinDuration
data class ApiConfig(
val observabilityPort: UInt,
)
data class ConflationConfig(
val consistentNumberOfBlocksOnL1ToWait: Int,
val conflationDeadline: Duration,
val conflationDeadlineCheckInterval: Duration,
val conflationDeadlineLastBlockConfirmationDelay: Duration,
val blocksLimit: Long? = null,
private var _tracesLimitsV2: TracesCountersV2?,
private var _smartContractErrors: SmartContractErrors?,
val fetchBlocksLimit: Int,
@ConfigAlias("conflation-target-end-block-numbers")
private val _conflationTargetEndBlockNumbers: List<Long> = emptyList(),
) {
init {
require(conflationDeadlineCheckInterval <= conflationDeadline) {
"Clock ticker interval must be smaller than conflation deadline"
}
consistentNumberOfBlocksOnL1ToWait.let {
require(it > 0) { "consistentNumberOfBlocksOnL1ToWait must be grater than 0" }
}
blocksLimit?.let { require(it > 0) { "blocksLimit must be greater than 0" } }
_smartContractErrors = _smartContractErrors
?.let { it.mapKeys { it.key.lowercase() } }
?: emptyMap()
}
val tracesLimitsV2: TracesCounters
get() = _tracesLimitsV2 ?: throw IllegalStateException("Traces limits not defined!")
val smartContractErrors: SmartContractErrors = _smartContractErrors!!
val conflationTargetEndBlockNumbers: Set<ULong> = _conflationTargetEndBlockNumbers.map { it.toULong() }.toSet()
}
interface RetryConfig {
val maxRetries: Int?
val timeout: Duration?
val backoffDelay: Duration
}
/**
* This class is used to parse the requestRetry config from the toml file.
* If we use UInt toml parser throws an exception because it does not support SOMETIMES UInt values: ¯\_(ツ)_/¯
*
* kotlin.reflect.jvm.internal.KotlinReflectionInternalError:
* This callable does not support a default call: public constructor
* RequestRetryConfigTomlFriendly(maxRetries: kotlin.UInt? = ...
*/
data class RequestRetryConfigTomlFriendly(
override val maxRetries: Int? = null,
override val timeout: Duration? = null,
override val backoffDelay: Duration = 1.milliseconds.toJavaDuration(),
val failuresWarningThreshold: Int = 0,
) : RetryConfig {
init {
maxRetries?.also {
require(maxRetries >= 1) { "maxRetries must be >=1. value=$maxRetries" }
}
timeout?.also {
require(timeout.toKotlinDuration() >= 1.milliseconds) { "timeout must be >= 1ms. value=$timeout" }
}
require(backoffDelay.toKotlinDuration() >= 1.milliseconds) {
"backoffDelay must be >= 1ms. value=$backoffDelay"
}
require(failuresWarningThreshold >= 0) {
"failuresWarningThreshold must be greater than or equal to 0. value=$failuresWarningThreshold"
}
}
internal val asJsonRpcRetryConfig = RequestRetryConfig(
maxRetries = maxRetries?.toUInt(),
timeout = timeout?.toKotlinDuration(),
backoffDelay = backoffDelay.toKotlinDuration(),
failuresWarningThreshold = failuresWarningThreshold.toUInt(),
)
internal val asDomain: linea.domain.RetryConfig = linea.domain.RetryConfig(
maxRetries = maxRetries?.toUInt(),
timeout = timeout?.toKotlinDuration(),
backoffDelay = backoffDelay.toKotlinDuration(),
failuresWarningThreshold = failuresWarningThreshold.toUInt(),
)
companion object {
fun endlessRetry(
backoffDelay: Duration,
failuresWarningThreshold: Int,
) = RequestRetryConfigTomlFriendly(
maxRetries = null,
timeout = null,
backoffDelay = backoffDelay,
failuresWarningThreshold = failuresWarningThreshold,
)
}
}
data class PersistenceRetryConfig(
override val maxRetries: Int? = null,
override val backoffDelay: Duration = 1.seconds.toJavaDuration(),
override val timeout: Duration? = 10.minutes.toJavaDuration(),
) : RetryConfig
internal interface RequestRetryConfigurable {
val requestRetry: RequestRetryConfigTomlFriendly
val requestRetryConfig: RequestRetryConfig
get() = requestRetry.asJsonRpcRetryConfig
}
data class BlobCompressionConfig(
val blobSizeLimit: Int,
@ConfigAlias("batches-limit")
private val _batchesLimit: Int? = null,
val handlerPollingInterval: Duration,
) {
init {
_batchesLimit?.also {
require(it > 0) { "batchesLimit=$_batchesLimit must be greater than 0" }
}
}
val batchesLimit: UInt?
get() = _batchesLimit?.toUInt()
}
data class AggregationConfig(
val aggregationProofsLimit: Int,
val aggregationDeadline: Duration,
val aggregationCoordinatorPollingInterval: Duration,
val deadlineCheckInterval: Duration,
val aggregationSizeMultipleOf: Int = 1,
@ConfigAlias("target-end-blocks")
private val _targetEndBlocks: List<Long> = emptyList(),
) {
val targetEndBlocks: List<ULong> = _targetEndBlocks.map { it.toULong() }
init {
require(aggregationSizeMultipleOf > 0) { "aggregationSizeMultipleOf should be greater than 0" }
}
}
data class TracesConfig(
val rawExecutionTracesVersion: String,
val blobCompressorVersion: BlobCompressorVersion,
val expectedTracesApiVersionV2: String,
val countersV2: FunctionalityEndpoint,
val conflationV2: FunctionalityEndpoint,
) {
data class FunctionalityEndpoint(
val endpoints: List<URL>,
val requestLimitPerEndpoint: UInt,
override val requestRetry: RequestRetryConfigTomlFriendly,
) : RequestRetryConfigurable {
init {
require(requestLimitPerEndpoint > 0u) { "requestLimitPerEndpoint must be greater than 0" }
}
}
}
data class StateManagerClientConfig(
val version: String,
val endpoints: List<URL>,
val requestLimitPerEndpoint: UInt,
override val requestRetry: RequestRetryConfigTomlFriendly,
) : RequestRetryConfigurable {
init {
require(requestLimitPerEndpoint > 0u) { "requestLimitPerEndpoint must be greater than 0" }
}
}
data class BlobSubmissionConfig(
val dbPollingInterval: Duration,
val maxBlobsToReturn: Int,
val proofSubmissionDelay: Duration,
val priorityFeePerGasUpperBound: ULong,
val priorityFeePerGasLowerBound: ULong,
val maxBlobsToSubmitPerTick: Int = maxBlobsToReturn,
val targetBlobsToSendPerTransaction: Int = 9,
val useEthEstimateGas: Boolean = false,
override var disabled: Boolean = false,
) : FeatureToggleable {
init {
require(maxBlobsToReturn > 0) { "maxBlobsToReturn must be greater than 0" }
require(maxBlobsToSubmitPerTick >= 0) { "submissionLimit must be greater or equal to 0" }
require(targetBlobsToSendPerTransaction in 1..9) {
"targetBlobsToSendPerTransaction must be between 1 and 9, value=$targetBlobsToSendPerTransaction"
}
}
}
data class AggregationFinalizationConfig(
val dbPollingInterval: Duration,
val maxAggregationsToFinalizePerTick: Int,
val proofSubmissionDelay: Duration,
val useEthEstimateGas: Boolean = false,
override var disabled: Boolean = false,
) : FeatureToggleable {
init {
require(maxAggregationsToFinalizePerTick > 0) {
"maxAggregationsToFinalizePerIteration must be greater than 0"
}
}
}
data class DatabaseConfig(
val host: String,
val port: Int,
val username: String,
val password: Masked,
val schema: String,
val readPoolSize: Int,
val readPipeliningLimit: Int,
val transactionalPoolSize: Int,
)
data class L1Config(
val zkEvmContractAddress: String,
val rpcEndpoint: URL,
val finalizationPollingInterval: Duration,
@ConfigAlias("l1-query-block-tag")
private val _l1QueryBlockTag: String = BlockParameter.Tag.FINALIZED.name,
val gasLimit: ULong,
val feeHistoryBlockCount: Int,
val feeHistoryRewardPercentile: Double,
val maxFeePerGasCap: ULong,
val maxFeePerBlobGasCap: ULong,
val maxPriorityFeePerGasCap: ULong,
val gasPriceCapMultiplierForFinalization: Double,
val earliestBlock: BigInteger,
val sendMessageEventPollingInterval: Duration,
val maxEventScrapingTime: Duration,
val maxMessagesToCollect: UInt,
val finalizedBlockTag: String,
val blockRangeLoopLimit: UInt = 0U,
val blockTime: Duration = Duration.parse("PT12S"),
@ConfigAlias("eth-fee-history-endpoint") private val _ethFeeHistoryEndpoint: URL?,
@ConfigAlias("genesis-state-root-hash") private val _genesisStateRootHash: String,
@ConfigAlias("genesis-shnarf-v6") private val _genesisShnarfV6: String,
) {
val ethFeeHistoryEndpoint: URL
get() = _ethFeeHistoryEndpoint ?: rpcEndpoint
val genesisStateRootHash: ByteArray
get() = _genesisStateRootHash.decodeHex().assertIs32Bytes("genesisStateRootHash")
val genesisShnarfV6: ByteArray
get() = _genesisShnarfV6.decodeHex().assertIs32Bytes("genesisShnarfV6")
val l1QueryBlockTag: BlockParameter.Tag
get() = BlockParameter.Tag.fromString(_l1QueryBlockTag)
init {
require(gasPriceCapMultiplierForFinalization > 0.0) {
"gas price cap multiplier for finalization must be greater than 0.0." +
" Value=$gasPriceCapMultiplierForFinalization"
}
// just to ensure that the tag is valid right at config parsing time
BlockParameter.Tag.fromString(_l1QueryBlockTag)
}
}
data class L2Config(
val messageServiceAddress: String,
val messageServiceDeploymentBlockNumber: ULong? = null,
val rpcEndpoint: URL,
val gasLimit: ULong,
val maxFeePerGasCap: ULong,
val feeHistoryBlockCount: UInt,
val feeHistoryRewardPercentile: Double,
val blocksToFinalization: UInt,
val lastHashSearchWindow: UInt,
val anchoringReceiptPollingInterval: Duration,
val maxReceiptRetries: UInt,
val newBlockPollingInterval: Duration,
) {
init {
messageServiceAddress.assertIsValidAddress("messageServiceAddress")
}
}
data class SignerConfig(
val type: Type,
val web3signer: Web3SignerConfig?,
val web3j: Web3jConfig?,
) {
enum class Type {
Web3j,
Web3Signer,
}
init {
when (type) {
Type.Web3Signer -> {
if (web3signer == null) throw IllegalStateException("Signer $type configuration is null.")
}
Type.Web3j -> {
if (web3j == null) throw IllegalStateException("Signer $type configuration is null.")
}
}
}
}
data class Web3jConfig(
val privateKey: Masked,
)
data class Web3SignerConfig(
val endpoint: String,
val maxPoolSize: UInt,
val keepAlive: Boolean,
val publicKey: String,
)
interface FeatureToggleable {
val disabled: Boolean
val enabled: Boolean
get() = !disabled
}
data class L1DynamicGasPriceCapServiceConfig(
val gasPriceCapCalculation: GasPriceCapCalculation,
val feeHistoryFetcher: FeeHistoryFetcher,
val feeHistoryStorage: FeeHistoryStorage,
override var disabled: Boolean = false,
) : FeatureToggleable {
data class GasPriceCapCalculation(
val adjustmentConstant: UInt,
val blobAdjustmentConstant: UInt,
val finalizationTargetMaxDelay: Duration,
val gasFeePercentileWindow: Duration,
val gasFeePercentileWindowLeeway: Duration,
val gasFeePercentile: Double,
val gasPriceCapsCheckCoefficient: Double,
val historicBaseFeePerBlobGasLowerBound: ULong,
val historicAvgRewardConstant: ULong?,
val timeOfDayMultipliers: TimeOfDayMultipliers?,
) {
init {
timeOfDayMultipliers?.also {
val allTimeOfDayKeys = getAllTimeOfDayKeys()
if (allTimeOfDayKeys != it.keys) {
val missingKeys = allTimeOfDayKeys - it.keys
val extraKeys = it.keys - allTimeOfDayKeys
val errorMessage =
"Invalid time of day multipliers: missing keys: " +
"${missingKeys.joinToString(",", "[", "]")}, " +
"unsupported keys=${extraKeys.joinToString(",", "[", "]")}"
throw IllegalStateException(errorMessage)
}
it.entries.forEach { timeOfDayMultiplier ->
require(timeOfDayMultiplier.value > 0.0) {
throw IllegalStateException(
"Each multiplier in timeOfDayMultipliers must be greater than 0.0." +
" Key=${timeOfDayMultiplier.key} Value=${timeOfDayMultiplier.value}",
)
}
}
require(gasFeePercentile in 0.0..100.0) {
"gasFeePercentile must be within 0.0 and 100.0." +
" Value=$gasFeePercentile"
}
require(gasPriceCapsCheckCoefficient > 0.0) {
"gasPriceCapsCheckCoefficient must be greater than 0.0." +
" Value=$gasPriceCapsCheckCoefficient"
}
}
}
}
data class FeeHistoryStorage(
val storagePeriod: Duration,
) {
init {
require(storagePeriod <= MAX_FEE_HISTORIES_STORAGE_PERIOD.toJavaDuration()) {
"storagePeriod must be at most $MAX_FEE_HISTORIES_STORAGE_PERIOD days"
}
}
}
data class FeeHistoryFetcher(
val fetchInterval: Duration,
val maxBlockCount: UInt,
val rewardPercentiles: List<Double>,
val numOfBlocksBeforeLatest: UInt = 4U,
val endpoint: URL?,
) {
init {
require(
maxBlockCount > 0U &&
maxBlockCount <= MAX_FEE_HISTORY_BLOCK_COUNT,
) {
"maxBlockCount must be greater than 0 and " +
"less than or equal to $MAX_FEE_HISTORY_BLOCK_COUNT"
}
require(
rewardPercentiles.isNotEmpty() &&
rewardPercentiles.size <= MAX_REWARD_PERCENTILES_SIZE,
) {
"rewardPercentiles must be a non-empty list with " +
"maximum length as $MAX_REWARD_PERCENTILES_SIZE."
}
require(rewardPercentiles.zipWithNext().all { it.first < it.second }) {
"rewardPercentiles must contain monotonically-increasing values"
}
rewardPercentiles.forEach { percentile ->
require(percentile in 0.0..100.0) {
"Each percentile in rewardPercentiles must be within 0.0 and 100.0." +
" Value=$percentile"
}
}
}
}
init {
require(feeHistoryStorage.storagePeriod >= gasPriceCapCalculation.gasFeePercentileWindow) {
"storagePeriod must be at least same length as" +
" gasFeePercentileWindow=${gasPriceCapCalculation.gasFeePercentileWindow}." +
" Value=${feeHistoryStorage.storagePeriod}"
}
require(
gasPriceCapCalculation.gasFeePercentileWindow
>= gasPriceCapCalculation.gasFeePercentileWindowLeeway,
) {
"gasFeePercentileWindow must be at least same length as" +
" gasFeePercentileWindowLeeway=${gasPriceCapCalculation.gasFeePercentileWindowLeeway}." +
" Value=${gasPriceCapCalculation.gasFeePercentileWindow}"
}
require(feeHistoryFetcher.rewardPercentiles.contains(gasPriceCapCalculation.gasFeePercentile)) {
"rewardPercentiles must contain the given" +
" gasFeePercentile=${gasPriceCapCalculation.gasFeePercentile}." +
" Value=${feeHistoryFetcher.rewardPercentiles}"
}
}
}
data class SmartContractErrorCodesConfig(val smartContractErrors: SmartContractErrors)
data class GasPriceCapTimeOfDayMultipliersConfig(val gasPriceCapTimeOfDayMultipliers: TimeOfDayMultipliers)
data class Type2StateProofProviderConfig(
override var disabled: Boolean = false,
val endpoints: List<URL>,
val l1QueryBlockTag: BlockParameter.Tag = BlockParameter.Tag.LATEST,
val l1PollingInterval: Duration = Duration.ofSeconds(12),
override val requestRetry: RequestRetryConfigTomlFriendly,
) : FeatureToggleable, RequestRetryConfigurable
data class TracesLimitsV2ConfigFile(val tracesLimits: Map<TracingModuleV2, UInt>)
//
// CoordinatorConfigTomlDto class to parse from toml
// CoordinatorConfig class with reified configs
// separation between Toml representation and domain representation
// otherwise it's hard to test the configuration is loaded properly
data class CoordinatorConfigTomlDto(
val l2InclusiveBlockNumberToStopAndFlushAggregation: ULong? = null,
val blobCompression: BlobCompressionConfig,
val proofAggregation: AggregationConfig,
val traces: TracesConfig,
val type2StateProofProvider: Type2StateProofProviderConfig,
val l1: L1Config,
val l2: L2Config,
val finalizationSigner: SignerConfig,
val dataSubmissionSigner: SignerConfig,
val blobSubmission: BlobSubmissionConfig,
val aggregationFinalization: AggregationFinalizationConfig,
val database: DatabaseConfig,
val persistenceRetry: PersistenceRetryConfig,
val stateManager: StateManagerClientConfig,
val conflation: ConflationConfig,
val api: ApiConfig,
val l2Signer: SignerConfig,
val messageAnchoring: MessageAnchoringConfigTomlDto = MessageAnchoringConfigTomlDto(),
val l2NetworkGasPricing: L2NetworkGasPricingTomlDto,
val l1DynamicGasPriceCapService: L1DynamicGasPriceCapServiceConfig,
val testL1Disabled: Boolean = false,
val prover: ProverConfigTomlDto,
) {
fun reified(): CoordinatorConfig = CoordinatorConfig(
l2InclusiveBlockNumberToStopAndFlushAggregation = l2InclusiveBlockNumberToStopAndFlushAggregation,
blobCompression = blobCompression,
proofAggregation = proofAggregation,
traces = traces,
type2StateProofProvider = type2StateProofProvider,
l1 = l1,
l2 = l2,
finalizationSigner = finalizationSigner,
dataSubmissionSigner = dataSubmissionSigner,
blobSubmission = blobSubmission,
aggregationFinalization = aggregationFinalization,
database = database,
persistenceRetry = persistenceRetry,
stateManager = stateManager,
conflation = conflation,
api = api,
l2Signer = l2Signer,
messageAnchoring = messageAnchoring.reified(
l1DefaultEndpoint = l1.rpcEndpoint,
l2DefaultEndpoint = l2.rpcEndpoint,
),
l2NetworkGasPricingService =
if (testL1Disabled || l2NetworkGasPricing.disabled) null else l2NetworkGasPricing.reified(),
l1DynamicGasPriceCapService = l1DynamicGasPriceCapService,
testL1Disabled = testL1Disabled,
proversConfig = prover.reified(),
)
}
data class CoordinatorConfig(
val l2InclusiveBlockNumberToStopAndFlushAggregation: ULong? = null,
val blobCompression: BlobCompressionConfig,
val proofAggregation: AggregationConfig,
val traces: TracesConfig,
val type2StateProofProvider: Type2StateProofProviderConfig,
val l1: L1Config,
val l2: L2Config,
val finalizationSigner: SignerConfig,
val dataSubmissionSigner: SignerConfig,
val blobSubmission: BlobSubmissionConfig,
val aggregationFinalization: AggregationFinalizationConfig,
val database: DatabaseConfig,
val persistenceRetry: PersistenceRetryConfig,
val stateManager: StateManagerClientConfig,
val conflation: ConflationConfig,
val api: ApiConfig,
val l2Signer: SignerConfig,
val messageAnchoring: MessageAnchoringConfig,
val l2NetworkGasPricingService: L2NetworkGasPricingService.Config?,
val l1DynamicGasPriceCapService: L1DynamicGasPriceCapServiceConfig,
val testL1Disabled: Boolean = false,
val proversConfig: ProversConfig,
) {
init {
if (l2InclusiveBlockNumberToStopAndFlushAggregation != null) {
require(proofAggregation.targetEndBlocks.contains(l2InclusiveBlockNumberToStopAndFlushAggregation)) {
"proofAggregation.targetEndBlocks should contain the l2InclusiveBlockNumberToStopAndFlushAggregation"
}
require(conflation.conflationTargetEndBlockNumbers.contains(l2InclusiveBlockNumberToStopAndFlushAggregation)) {
"conflation.conflationTargetEndBlockNumbers should contain the l2InclusiveBlockNumberToStopAndFlushAggregation"
}
}
require(
blobCompression.batchesLimit == null ||
blobCompression.batchesLimit!! < proofAggregation.aggregationProofsLimit.toUInt(),
) {
"[blob-compression].batchesLimit=${blobCompression.batchesLimit} must be less than " +
"[proof-aggregation].aggregationProofsLimit=${proofAggregation.aggregationProofsLimit}"
}
if (testL1Disabled) {
messageAnchoring.disabled = true
l1DynamicGasPriceCapService.disabled = true
}
}
}

View File

@@ -1,194 +0,0 @@
package net.consensys.zkevm.coordinator.app.config
import com.sksamuel.hoplite.ConfigAlias
import linea.kotlin.toKWeiUInt
import net.consensys.linea.ethereum.gaspricing.BoundableFeeCalculator
import net.consensys.linea.ethereum.gaspricing.staticcap.ExtraDataV1UpdaterImpl
import net.consensys.linea.ethereum.gaspricing.staticcap.FeeHistoryFetcherImpl
import net.consensys.linea.ethereum.gaspricing.staticcap.GasPriceUpdaterImpl
import net.consensys.linea.ethereum.gaspricing.staticcap.GasUsageRatioWeightedAverageFeesCalculator
import net.consensys.linea.ethereum.gaspricing.staticcap.MinerExtraDataV1CalculatorImpl
import net.consensys.linea.ethereum.gaspricing.staticcap.TransactionCostCalculator
import net.consensys.linea.ethereum.gaspricing.staticcap.VariableFeesCalculator
import net.consensys.zkevm.coordinator.app.L2NetworkGasPricingService
import java.net.URL
import java.time.Duration
import kotlin.time.toKotlinDuration
// Defaults to a compressed plain transfer transaction
data class SampleTransactionGasPricingTomlDto(
val plainTransferCostMultiplier: Double = 1.0,
val compressedTxSize: Int = 125,
val expectedGas: Int = 21000,
)
data class LegacyGasPricingTomlDto(
val type: Type,
val naiveGasPricing: NaiveGasPricingTomlDto?,
val sampleTransactionGasPricing: SampleTransactionGasPricingTomlDto = SampleTransactionGasPricingTomlDto(),
val gasPriceUpperBound: ULong,
val gasPriceLowerBound: ULong,
) {
enum class Type {
Naive,
SampleTransaction,
}
init {
if (type == Type.Naive && naiveGasPricing == null) {
throw IllegalStateException("LegacyGasPricing $type configuration is null.")
}
require(gasPriceUpperBound >= gasPriceLowerBound) {
"gasPriceUpperBound must be greater than or equal to gasPriceLowerBound"
}
}
}
data class NaiveGasPricingTomlDto(
val baseFeeCoefficient: Double,
val priorityFeeCoefficient: Double,
val baseFeeBlobCoefficient: Double,
)
data class VariableCostPricingTomlDto(
val gasPriceFixedCost: ULong,
val legacyFeesMultiplier: Double,
val margin: Double,
val variableCostUpperBound: ULong,
val variableCostLowerBound: ULong,
) {
init {
require(variableCostUpperBound >= variableCostLowerBound) {
"variableCostUpperBound must be greater than or equal to variableCostLowerBound"
}
}
}
data class JsonRpcPricingPropagationTomlDto(
override var disabled: Boolean = false,
val gethGasPriceUpdateRecipients: List<URL>,
val besuGasPriceUpdateRecipients: List<URL>,
) : FeatureToggleable {
init {
require(disabled || (gethGasPriceUpdateRecipients.isNotEmpty() || besuGasPriceUpdateRecipients.isNotEmpty())) {
"There is no point of enabling JSON RPC pricing propagation if there are no " +
"gethGasPriceUpdateRecipients or besuGasPriceUpdateRecipients defined"
}
}
}
data class ExtraDataPricingPropagationTomlDto(
override var disabled: Boolean = false,
val extraDataUpdateRecipient: URL,
) : FeatureToggleable
data class L2NetworkGasPricingTomlDto(
override var disabled: Boolean = false,
override val requestRetry: RequestRetryConfigTomlFriendly,
val priceUpdateInterval: Duration,
val feeHistoryBlockCount: Int,
val feeHistoryRewardPercentile: Double,
val blobSubmissionExpectedExecutionGas: Int,
@ConfigAlias("bytesPerDataSubmission") val _bytesPerDataSubmission: Int?,
val l1BlobGas: Int,
val legacy: LegacyGasPricingTomlDto,
val variableCostPricing: VariableCostPricingTomlDto,
val jsonRpcPricingPropagation: JsonRpcPricingPropagationTomlDto?,
val extraDataPricingPropagation: ExtraDataPricingPropagationTomlDto,
) : FeatureToggleable, RequestRetryConfigurable {
init {
require(feeHistoryBlockCount > 0) { "feeHistoryBlockCount must be greater than 0" }
require(blobSubmissionExpectedExecutionGas > 0) { "blobSubmissionExpectedExecutionGas must be greater than 0" }
require(l1BlobGas > 0) { "l1BlobGas must be greater than 0" }
require(disabled || (jsonRpcPricingPropagation?.enabled == true || extraDataPricingPropagation.enabled)) {
"There is no point of enabling L2 network gas pricing if " +
"both jsonRpcPricingPropagation and extraDataPricingPropagation are disabled"
}
}
private val bytesPerDataSubmission = _bytesPerDataSubmission ?: l1BlobGas
fun reified(): L2NetworkGasPricingService.Config {
val legacyGasPricingConfig = when (legacy.type) {
LegacyGasPricingTomlDto.Type.Naive -> {
L2NetworkGasPricingService.LegacyGasPricingCalculatorConfig(
transactionCostCalculatorConfig = null,
naiveGasPricingCalculatorConfig = GasUsageRatioWeightedAverageFeesCalculator.Config(
baseFeeCoefficient = legacy.naiveGasPricing!!.baseFeeCoefficient,
priorityFeeCoefficient = legacy.naiveGasPricing.priorityFeeCoefficient,
baseFeeBlobCoefficient = legacy.naiveGasPricing.baseFeeBlobCoefficient,
blobSubmissionExpectedExecutionGas = blobSubmissionExpectedExecutionGas,
expectedBlobGas = l1BlobGas,
),
legacyGasPricingCalculatorBounds = BoundableFeeCalculator.Config(
legacy.gasPriceUpperBound.toDouble(),
legacy.gasPriceLowerBound.toDouble(),
0.0,
),
)
}
LegacyGasPricingTomlDto.Type.SampleTransaction -> {
L2NetworkGasPricingService.LegacyGasPricingCalculatorConfig(
transactionCostCalculatorConfig = TransactionCostCalculator.Config(
sampleTransactionCostMultiplier = legacy.sampleTransactionGasPricing.plainTransferCostMultiplier,
fixedCostWei = variableCostPricing.gasPriceFixedCost,
compressedTxSize = legacy.sampleTransactionGasPricing.compressedTxSize,
expectedGas = legacy.sampleTransactionGasPricing.expectedGas,
),
naiveGasPricingCalculatorConfig = null,
legacyGasPricingCalculatorBounds = BoundableFeeCalculator.Config(
legacy.gasPriceUpperBound.toDouble(),
legacy.gasPriceLowerBound.toDouble(),
0.0,
),
)
}
}
val gasPriceUpdaterConfig = if (jsonRpcPricingPropagation?.enabled == true) {
GasPriceUpdaterImpl.Config(
gethEndpoints = jsonRpcPricingPropagation.gethGasPriceUpdateRecipients,
besuEndPoints = jsonRpcPricingPropagation.besuGasPriceUpdateRecipients,
retryConfig = requestRetryConfig,
)
} else {
null
}
return L2NetworkGasPricingService.Config(
feeHistoryFetcherConfig = FeeHistoryFetcherImpl.Config(
feeHistoryBlockCount = feeHistoryBlockCount.toUInt(),
feeHistoryRewardPercentile = feeHistoryRewardPercentile,
),
legacy = legacyGasPricingConfig,
jsonRpcGasPriceUpdaterConfig = gasPriceUpdaterConfig,
jsonRpcPriceUpdateInterval = priceUpdateInterval.toKotlinDuration(),
extraDataPricingPropagationEnabled = extraDataPricingPropagation.enabled,
extraDataUpdateInterval = priceUpdateInterval.toKotlinDuration(),
variableFeesCalculatorConfig = VariableFeesCalculator.Config(
blobSubmissionExpectedExecutionGas = blobSubmissionExpectedExecutionGas.toUInt(),
bytesPerDataSubmission = l1BlobGas.toUInt(),
expectedBlobGas = bytesPerDataSubmission.toUInt(),
margin = variableCostPricing.margin,
),
variableFeesCalculatorBounds = BoundableFeeCalculator.Config(
feeUpperBound = variableCostPricing.variableCostUpperBound.toDouble(),
feeLowerBound = variableCostPricing.variableCostLowerBound.toDouble(),
feeMargin = 0.0,
),
extraDataCalculatorConfig = MinerExtraDataV1CalculatorImpl.Config(
fixedCostInKWei = variableCostPricing.gasPriceFixedCost.toKWeiUInt(),
ethGasPriceMultiplier = variableCostPricing.legacyFeesMultiplier,
),
extraDataUpdaterConfig = ExtraDataV1UpdaterImpl.Config(
extraDataPricingPropagation.extraDataUpdateRecipient,
requestRetryConfig,
),
)
}
}

View File

@@ -1,95 +0,0 @@
package net.consensys.zkevm.coordinator.app.config
import linea.domain.BlockParameter
import java.net.URL
import java.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toJavaDuration
import kotlin.time.toKotlinDuration
data class MessageAnchoringConfigTomlDto(
var disabled: Boolean = false,
val l1Endpoint: URL? = null, // shall default to L1 endpoint
val l1HighestBlockTag: BlockParameter = BlockParameter.Tag.FINALIZED,
val l1RequestRetries: RequestRetryConfigTomlFriendly = RequestRetryConfigTomlFriendly.endlessRetry(
backoffDelay = 1.seconds.toJavaDuration(),
failuresWarningThreshold = 3,
),
val l1EventPollingInterval: Duration = 12.seconds.toJavaDuration(),
val l1EventPollingTimeout: Duration = 6.seconds.toJavaDuration(),
val l1SuccessBackoffDelay: Duration = 1.milliseconds.toJavaDuration(), // is configurable mostly for testing purposes
val l1EventSearchBlockChunk: Int = 1000,
val l2Endpoint: URL? = null,
val l2HighestBlockTag: BlockParameter = BlockParameter.Tag.LATEST,
val l2RequestRetries: RequestRetryConfigTomlFriendly = RequestRetryConfigTomlFriendly.endlessRetry(
backoffDelay = 1.seconds.toJavaDuration(),
failuresWarningThreshold = 3,
),
val anchoringTickInterval: Duration = 2.seconds.toJavaDuration(),
val messageQueueCapacity: Int = 10_000,
val maxMessagesToAnchorPerL2Transaction: Int = 100,
) {
init {
require(messageQueueCapacity > 0) {
"messageQueueCapacity must be greater than 0"
}
require(maxMessagesToAnchorPerL2Transaction >= 1) {
"maxMessagesToAnchorPerL2Transaction=$maxMessagesToAnchorPerL2Transaction be equal or greater than 1"
}
require(l1EventPollingInterval.toMillis() >= 1) {
"l1EventPollingInterval=$l1EventPollingInterval must be equal or greater than 1ms"
}
require(l1EventPollingTimeout.toMillis() >= 1) {
"l1EventPollingTimeout=$l1EventPollingTimeout must be equal or greater than 1ms"
}
require(l1SuccessBackoffDelay.toMillis() >= 1) {
"l1SuccessBackoffDelay=$l1SuccessBackoffDelay must be equal or greater than 1ms"
}
require(l1EventSearchBlockChunk >= 1) {
"l1EventSearchBlockChunk=$l1EventSearchBlockChunk must be equal or greater than 1"
}
require(anchoringTickInterval.toMillis() >= 1) {
"anchoringTickInterval must be equal or greater than 1ms"
}
}
fun reified(
l1DefaultEndpoint: URL,
l2DefaultEndpoint: URL,
): MessageAnchoringConfig {
return MessageAnchoringConfig(
disabled = disabled,
l1Endpoint = l1Endpoint ?: l1DefaultEndpoint,
l2Endpoint = l2Endpoint ?: l2DefaultEndpoint,
l1HighestBlockTag = l1HighestBlockTag,
l2HighestBlockTag = l2HighestBlockTag,
l1RequestRetryConfig = l1RequestRetries.asDomain,
l2RequestRetryConfig = l2RequestRetries.asDomain,
l1EventPollingInterval = l1EventPollingInterval.toKotlinDuration(),
l1EventPollingTimeout = l1EventPollingTimeout.toKotlinDuration(),
l1SuccessBackoffDelay = l1SuccessBackoffDelay.toKotlinDuration(),
l1EventSearchBlockChunk = l1EventSearchBlockChunk.toUInt(),
anchoringTickInterval = anchoringTickInterval.toKotlinDuration(),
messageQueueCapacity = messageQueueCapacity.toUInt(),
maxMessagesToAnchorPerL2Transaction = maxMessagesToAnchorPerL2Transaction.toUInt(),
)
}
}
data class MessageAnchoringConfig(
override var disabled: Boolean,
val l1Endpoint: URL,
val l2Endpoint: URL,
val l1HighestBlockTag: BlockParameter,
val l2HighestBlockTag: BlockParameter,
val l1RequestRetryConfig: linea.domain.RetryConfig,
val l2RequestRetryConfig: linea.domain.RetryConfig,
val l1EventPollingInterval: kotlin.time.Duration,
val l1EventPollingTimeout: kotlin.time.Duration,
val l1SuccessBackoffDelay: kotlin.time.Duration,
val l1EventSearchBlockChunk: UInt,
val anchoringTickInterval: kotlin.time.Duration,
val messageQueueCapacity: UInt,
val maxMessagesToAnchorPerL2Transaction: UInt,
) : FeatureToggleable

View File

@@ -1,91 +0,0 @@
package net.consensys.zkevm.coordinator.app.config
import net.consensys.zkevm.coordinator.clients.prover.FileBasedProverConfig
import net.consensys.zkevm.coordinator.clients.prover.ProverConfig
import net.consensys.zkevm.coordinator.clients.prover.ProversConfig
import java.nio.file.Path
import java.time.Duration
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toJavaDuration
import kotlin.time.toKotlinDuration
data class ProverConfigTomlDto(
val switchBlockNumberInclusive: Long? = null,
var fsInprogressRequestWritingSuffix: String? = null,
var fsInprogressProvingSuffixPattern: String? = null,
var fsPollingInterval: Duration? = null,
var fsPollingTimeout: Duration? = null,
val execution: FileSystemTomlDto,
val blobCompression: FileSystemTomlDto,
val proofAggregation: FileSystemTomlDto,
val new: ProverConfigTomlDto? = null,
) {
private fun asProverConfig(): ProverConfig {
return ProverConfig(
execution = execution.toDomain(),
blobCompression = blobCompression.toDomain(),
proofAggregation = proofAggregation.toDomain(),
)
}
fun reified(): ProversConfig {
fsInprogressRequestWritingSuffix = fsInprogressRequestWritingSuffix ?: ".inprogress_coordinator_writing"
fsInprogressProvingSuffixPattern = fsInprogressProvingSuffixPattern ?: "\\.inprogress\\.prover.*"
fsPollingInterval = fsPollingInterval ?: 1.seconds.toJavaDuration()
fsPollingTimeout = fsPollingTimeout ?: 3.hours.toJavaDuration()
execution.reifyWithRootDefaults(this)
blobCompression.reifyWithRootDefaults(this)
proofAggregation.reifyWithRootDefaults(this)
if (new != null) {
if (new.switchBlockNumberInclusive == null) {
throw IllegalArgumentException("switchBlockNumberInclusive must be set when new prover is configured")
}
new.fsInprogressProvingSuffixPattern = new.fsInprogressProvingSuffixPattern
?: fsInprogressProvingSuffixPattern
new.fsInprogressRequestWritingSuffix = new.fsInprogressRequestWritingSuffix
?: fsInprogressRequestWritingSuffix
new.fsPollingInterval = new.fsPollingInterval ?: fsPollingInterval
new.fsPollingTimeout = new.fsPollingTimeout ?: fsPollingTimeout
new.execution.reifyWithRootDefaults(new)
new.blobCompression.reifyWithRootDefaults(new)
new.proofAggregation.reifyWithRootDefaults(new)
}
return ProversConfig(
proverA = this.asProverConfig(),
switchBlockNumberInclusive = new?.switchBlockNumberInclusive?.toULong(),
proverB = new?.asProverConfig(),
)
}
}
data class FileSystemTomlDto(
internal val fsRequestsDirectory: Path,
internal val fsResponsesDirectory: Path,
internal var fsInprogressRequestWritingSuffix: String?,
internal var fsInprogressProvingSuffixPattern: String?,
internal var fsPollingInterval: Duration?,
internal var fsPollingTimeout: Duration?,
) {
internal fun reifyWithRootDefaults(rootConfig: ProverConfigTomlDto) {
fsInprogressRequestWritingSuffix = fsInprogressRequestWritingSuffix
?: rootConfig.fsInprogressRequestWritingSuffix
fsInprogressProvingSuffixPattern = fsInprogressProvingSuffixPattern
?: rootConfig.fsInprogressProvingSuffixPattern
fsPollingInterval = fsPollingInterval ?: rootConfig.fsPollingInterval
fsPollingTimeout = fsPollingTimeout ?: rootConfig.fsPollingTimeout
}
fun toDomain(): FileBasedProverConfig {
return FileBasedProverConfig(
requestsDirectory = fsRequestsDirectory,
responsesDirectory = fsResponsesDirectory,
inprogressRequestWritingSuffix = fsInprogressRequestWritingSuffix!!,
inprogressProvingSuffixPattern = fsInprogressProvingSuffixPattern!!,
pollingInterval = fsPollingInterval!!.toKotlinDuration(),
pollingTimeout = fsPollingTimeout!!.toKotlinDuration(),
)
}
}

View File

@@ -0,0 +1,39 @@
package linea.coordinator.config.v2
import linea.coordinator.config.v2.toml.ApiConfigToml
import linea.coordinator.config.v2.toml.parseConfig
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
class ApiConfigParsingTest {
companion object {
val toml = """
[api]
observability-port = 9546
""".trimIndent()
val config = ApiConfigToml(
observabilityPort = 9546u,
)
val tomlMinimal = ""
val configMinimal = ApiConfigToml(
observabilityPort = 9545u,
)
}
data class WrapperConfig(
val api: ApiConfigToml = ApiConfigToml(),
)
@Test
fun `should parse api full config`() {
assertThat(parseConfig<WrapperConfig>(toml).api).isEqualTo(config)
}
@Test
fun `should parse api minimal config`() {
assertThat(parseConfig<WrapperConfig>(tomlMinimal).api).isEqualTo(configMinimal)
}
}

View File

@@ -0,0 +1,107 @@
package linea.coordinator.config.v2
import linea.coordinator.config.v2.toml.ConflationToml
import linea.coordinator.config.v2.toml.parseConfig
import linea.kotlin.toURL
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import kotlin.time.Duration.Companion.seconds
class ConflationParsingTest {
companion object {
val toml = """
[conflation]
disabled = true
blocks-limit = 2
conflation-deadline = "PT6S"
conflation-deadline-check-interval = "PT3S"
conflation-deadline-last-block-confirmation-delay = "PT2S" # recommended: at least 2 * blockInterval
l2-fetch-blocks-limit = 4_000
l2-endpoint = "http://l2-node-1:8545"
l2-logs-endpoint = "http://l2-node-2:8545"
consistent-number-of-blocks-on-l1-to-wait = 1
[conflation.blob-compression]
blob-size-limit = 102_400 # 100KB
handler-polling-interval = "PT1S"
# default batches limit is aggregation-proofs-limit -1
# batches-limit must be less than or equal to aggregation-proofs-limit-1
batches-limit = 1
[conflation.proof-aggregation]
proofs-limit = 3
deadline = "PT1M"
coordinator-polling-interval = "PT2S"
deadline-check-interval = "PT8S"
target-end-blocks = [10, 20, 30_000]
""".trimIndent()
val config = ConflationToml(
disabled = true,
blocksLimit = 2u,
conflationDeadline = 6.seconds,
conflationDeadlineCheckInterval = 3.seconds,
conflationDeadlineLastBlockConfirmationDelay = 2.seconds,
l2FetchBlocksLimit = 4000u,
l2Endpoint = "http://l2-node-1:8545".toURL(),
l2LogsEndpoint = "http://l2-node-2:8545".toURL(),
consistentNumberOfBlocksOnL1ToWait = 1u,
blobCompression = ConflationToml.BlobCompressionToml(
blobSizeLimit = 102_400U,
handlerPollingInterval = 1.seconds,
batchesLimit = 1u,
),
proofAggregation = ConflationToml.ProofAggregationToml(
proofsLimit = 3u,
deadline = 60.seconds,
coordinatorPollingInterval = 2.seconds,
deadlineCheckInterval = 8.seconds,
targetEndBlocks = listOf(10uL, 20uL, 30_000uL),
),
)
val tomlMinimal = """
# all fields are optional, defaults will be used if not specified
""".trimIndent()
val configMinimal = ConflationToml(
disabled = false,
blocksLimit = null,
conflationDeadline = null,
l2FetchBlocksLimit = null,
l2Endpoint = null,
l2LogsEndpoint = null,
consistentNumberOfBlocksOnL1ToWait = 32u,
blobCompression = ConflationToml.BlobCompressionToml(
blobSizeLimit = 102_400U,
handlerPollingInterval = 1.seconds,
batchesLimit = null,
),
proofAggregation = ConflationToml.ProofAggregationToml(
proofsLimit = 300u,
deadline = null,
deadlineCheckInterval = 30.seconds,
coordinatorPollingInterval = 3.seconds,
targetEndBlocks = null,
),
)
}
data class WrapperConfig(
val conflation: ConflationToml = ConflationToml(),
)
@Test
fun `should parse conflation toml configs - full`() {
assertThat(
parseConfig<WrapperConfig>(toml).conflation,
)
.isEqualTo(config)
}
@Test
fun `should parse conflation toml configs - minimal`() {
assertThat(
parseConfig<WrapperConfig>(tomlMinimal).conflation,
)
.isEqualTo(configMinimal)
}
}

View File

@@ -0,0 +1,79 @@
package linea.coordinator.config.v2
import linea.coordinator.config.v2.toml.CoordinatorConfigFileToml
import linea.coordinator.config.v2.toml.parseConfig
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
class CoordinatorConfigTest {
@Test
fun `should parse full configs`() {
val toml = """
${DefaultsParsingTest.toml}
${ProtocolParsingTest.toml}
${ConflationParsingTest.toml}
${ProverParsingTest.toml}
${TracesParsingTest.toml}
${StateManagerParsingTest.toml}
${Type2StateProofProviderParsingTest.toml}
${L1FinalizationMonitorParsingTest.toml}
${L1SubmissionConfigParsingTest.toml}
${MessageAnchoringConfigParsingTest.toml}
${L2NetWorkingGasPricingConfigParsingTest.toml}
${DataBaseConfigParsingTest.toml}
${ApiConfigParsingTest.toml}
""".trimIndent()
val config = CoordinatorConfigFileToml(
defaults = DefaultsParsingTest.config,
protocol = ProtocolParsingTest.config,
conflation = ConflationParsingTest.config,
prover = ProverParsingTest.config,
traces = TracesParsingTest.config,
stateManager = StateManagerParsingTest.config,
type2StateProofProvider = Type2StateProofProviderParsingTest.config,
l1FinalizationMonitor = L1FinalizationMonitorParsingTest.config,
l1Submission = L1SubmissionConfigParsingTest.config,
messageAnchoring = MessageAnchoringConfigParsingTest.config,
l2NetworkGasPricing = L2NetWorkingGasPricingConfigParsingTest.config,
database = DataBaseConfigParsingTest.config,
api = ApiConfigParsingTest.config,
)
assertThat(parseConfig<CoordinatorConfigFileToml>(toml)).isEqualTo(config)
}
@Test
fun `should parse minimal configs`() {
val toml = """
${DefaultsParsingTest.tomlMinimal}
${ProtocolParsingTest.tomlMinimal}
${ConflationParsingTest.tomlMinimal}
${ProverParsingTest.tomlMinimal}
${TracesParsingTest.tomlMinimal}
${StateManagerParsingTest.tomlMinimal}
${Type2StateProofProviderParsingTest.tomlMinimal}
${L1FinalizationMonitorParsingTest.tomlMinimal}
${L1SubmissionConfigParsingTest.tomlMinimal}
${MessageAnchoringConfigParsingTest.tomlMinimal}
${L2NetWorkingGasPricingConfigParsingTest.tomlMinimal}
${DataBaseConfigParsingTest.tomlMinimal}
${ApiConfigParsingTest.tomlMinimal}
""".trimIndent()
val config = CoordinatorConfigFileToml(
defaults = DefaultsParsingTest.configMinimal,
protocol = ProtocolParsingTest.configMinimal,
conflation = ConflationParsingTest.configMinimal,
prover = ProverParsingTest.configMinimal,
traces = TracesParsingTest.configMinimal,
stateManager = StateManagerParsingTest.configMinimal,
type2StateProofProvider = Type2StateProofProviderParsingTest.configMinimal,
l1FinalizationMonitor = L1FinalizationMonitorParsingTest.configMinimal,
l1Submission = L1SubmissionConfigParsingTest.configMinimal,
messageAnchoring = MessageAnchoringConfigParsingTest.configMinimal,
l2NetworkGasPricing = L2NetWorkingGasPricingConfigParsingTest.configMinimal,
database = DataBaseConfigParsingTest.configMinimal,
api = ApiConfigParsingTest.configMinimal,
)
assertThat(parseConfig<CoordinatorConfigFileToml>(toml)).isEqualTo(config)
}
}

View File

@@ -0,0 +1,86 @@
package linea.coordinator.config.v2
import com.sksamuel.hoplite.Masked
import linea.coordinator.config.v2.toml.DatabaseToml
import linea.coordinator.config.v2.toml.RequestRetriesToml
import linea.coordinator.config.v2.toml.parseConfig
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
class DataBaseConfigParsingTest {
companion object {
val toml = """
[database]
hostname = "localhost"
port = "5432"
username = "someuser"
password = "somepassword"
schema = "linea_coordinator"
read_pool_size = 10
read_pipelining_limit = 11
transactional_pool_size = 12
[database.persistence-retries]
max-retries = 3
backoff-delay = "PT1S"
timeout = "PT40S"
failures-warning-threshold = 2
""".trimIndent()
val config = DatabaseToml(
hostname = "localhost",
username = "someuser",
password = Masked("somepassword"),
schema = "linea_coordinator",
readPoolSize = 10,
readPipeliningLimit = 11,
transactionalPoolSize = 12,
port = 5432u,
persistenceRetries = RequestRetriesToml(
maxRetries = 3u,
backoffDelay = 1.seconds,
timeout = 40.seconds,
failuresWarningThreshold = 2u,
),
)
val tomlMinimal = """
[database]
hostname = "localhost"
username = "someuser"
password = "somepassword"
""".trimIndent()
val configMinimal = DatabaseToml(
hostname = "localhost",
username = "someuser",
password = Masked("somepassword"),
schema = "linea_coordinator",
readPoolSize = 10,
readPipeliningLimit = 10,
transactionalPoolSize = 10,
port = 5432u,
persistenceRetries = RequestRetriesToml(
maxRetries = null,
backoffDelay = 1.seconds,
timeout = 10.minutes,
failuresWarningThreshold = 3u,
),
)
}
data class WrapperConfig(
val database: DatabaseToml,
)
@Test
fun `should parse database full config`() {
assertThat(parseConfig<WrapperConfig>(toml).database).isEqualTo(config)
}
@Test
fun `should parse database minimal config`() {
assertThat(parseConfig<WrapperConfig>(tomlMinimal).database).isEqualTo(configMinimal)
}
}

View File

@@ -0,0 +1,69 @@
package linea.coordinator.config.v2
import linea.coordinator.config.v2.toml.DefaultsToml
import linea.coordinator.config.v2.toml.RequestRetriesToml
import linea.coordinator.config.v2.toml.parseConfig
import linea.kotlin.toURL
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import kotlin.time.Duration.Companion.seconds
class DefaultsParsingTest {
companion object {
val toml = """
[defaults]
l1-endpoint = "http://l1-el-node:8545"
l2-endpoint = "http://sequencer:8545"
[defaults.l1-request-retries]
backoff-delay = "PT2S"
failures-warning-threshold = 2
timeout = "PT20S"
[defaults.l2-request-retries]
backoff-delay = "PT3S"
failures-warning-threshold = 3
timeout = "PT30S"
""".trimIndent()
val config = DefaultsToml(
l1Endpoint = "http://l1-el-node:8545".toURL(),
l2Endpoint = "http://sequencer:8545".toURL(),
l1RequestRetries = RequestRetriesToml(
backoffDelay = 2.seconds,
failuresWarningThreshold = 2u,
timeout = 20.seconds,
),
l2RequestRetries = RequestRetriesToml(
backoffDelay = 3.seconds,
failuresWarningThreshold = 3u,
timeout = 30.seconds,
),
)
val tomlMinimal = """
""".trimIndent()
val configMinimal = DefaultsToml(
l1Endpoint = null,
l2Endpoint = null,
l1RequestRetries = RequestRetriesToml.endlessRetry(
backoffDelay = 1.seconds,
failuresWarningThreshold = 3u,
),
l2RequestRetries = RequestRetriesToml.endlessRetry(
backoffDelay = 1.seconds,
failuresWarningThreshold = 3u,
),
)
}
internal data class WrapperConfig(val defaults: DefaultsToml = DefaultsToml())
@Test
fun `should parse defaults full configs`() {
assertThat(parseConfig<WrapperConfig>(toml).defaults).isEqualTo(config)
}
@Test
fun `should parse defaults minimal configs`() {
assertThat(parseConfig<WrapperConfig>(tomlMinimal).defaults).isEqualTo(configMinimal)
}
}

View File

@@ -0,0 +1,57 @@
package linea.coordinator.config.v2
import linea.coordinator.config.v2.toml.L1FinalizationMonitorConfigToml
import linea.coordinator.config.v2.toml.parseConfig
import linea.domain.BlockParameter
import linea.kotlin.toURL
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import kotlin.time.Duration.Companion.seconds
class L1FinalizationMonitorParsingTest {
companion object {
val toml = """
[l1-finalization-monitor]
l1-endpoint = "http://l1-el-node:8545"
l2-endpoint = "http://sequencer:8545"
l1-polling-interval = "PT1S"
l1-query-block-tag="FINALIZED"
""".trimIndent()
val config = L1FinalizationMonitorConfigToml(
l1Endpoint = "http://l1-el-node:8545".toURL(),
l2Endpoint = "http://sequencer:8545".toURL(),
l1PollingInterval = 1.seconds,
l1QueryBlockTag = BlockParameter.Tag.FINALIZED,
)
val tomlMinimal = """
[l1-finalization-monitor]
l1-endpoint = "http://l1-el-node:8545"
l2-endpoint = "http://sequencer:8545"
""".trimIndent()
val configMinimal = L1FinalizationMonitorConfigToml(
l1Endpoint = "http://l1-el-node:8545".toURL(),
l2Endpoint = "http://sequencer:8545".toURL(),
l1PollingInterval = 6.seconds,
l1QueryBlockTag = BlockParameter.Tag.FINALIZED,
)
}
data class WrapperConfig(
val l1FinalizationMonitor: L1FinalizationMonitorConfigToml,
)
@Test
fun `should parse finalization monitor full config`() {
assertThat(parseConfig<WrapperConfig>(toml).l1FinalizationMonitor)
.isEqualTo(config)
}
@Test
fun `should parse finalization monitor minimal config`() {
assertThat(parseConfig<WrapperConfig>(tomlMinimal).l1FinalizationMonitor)
.isEqualTo(configMinimal)
}
}

View File

@@ -0,0 +1,358 @@
package linea.coordinator.config.v2
import com.sksamuel.hoplite.Masked
import linea.coordinator.config.v2.toml.L1SubmissionConfigToml
import linea.coordinator.config.v2.toml.SignerConfigToml
import linea.coordinator.config.v2.toml.parseConfig
import linea.kotlin.decodeHex
import linea.kotlin.toURL
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
class L1SubmissionConfigParsingTest {
companion object {
val toml = """
[l1-submission]
disabled = true
[l1-submission.dynamic-gas-price-cap]
disabled = true
[l1-submission.dynamic-gas-price-cap.gas-price-cap-calculation]
adjustment-constant = 25
blob-adjustment-constant = 25
finalization-target-max-delay = "PT32H"
base-fee-per-gas-percentile-window = "P7D"
base-fee-per-gas-percentile-window-leeway = "PT10M"
base-fee-per-gas-percentile = 10
gas-price-caps-check-coefficient = 0.9
historic-base-fee-per-blob-gas-lower-bound=100000011 # 0.1 GWEI
historic-avg-reward-constant=100000012 # 0.1 GWEI
[l1-submission.dynamic-gas-price-cap.fee-history-fetcher]
l1-endpoint = "http://l1-node:8545"
fetch-interval = "PT1S"
max-block-count = 1000
reward-percentiles = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
num-of-blocks-before-latest = 2
storage-period = "P10D"
[l1-submission.fallback-gas-price]
fee-history-block-count = 10
fee-history-reward-percentile = 15
[l1-submission.blob]
disabled = false
l1-endpoint = "http://l1-el-node:8545"
submission-delay = "PT1S"
submission-tick-interval = "PT10S"
max-submission-transactions-per-tick = 10
target-blobs-per-transaction=9
db-max-blobs-to-return = 100
[l1-submission.blob.gas]
gas-limit = 10_000_000
max-fee-per-gas-cap = 100_000_000_000
max-fee-per-blob-gas-cap = 100_000_000
max-priority-fee-per-gas-cap=10_000_000_000
# Note: prefixed with "fallback-", used when dynamic gas price is disabled or DB is not populated yet
[l1-submission.blob.gas.fallback]
priority-fee-per-gas-upper-bound = 20_000_000_000 # 20 GWEI
priority-fee-per-gas-lower-bound = 2_000_000_000 # 2 GWEI
[l1-submission.blob.signer]
# Web3j/Web3signer
type = "Web3j"
# The account with this private key is in genesis file
[l1-submission.blob.signer.web3j]
private-key = "0x0000000000000000000000000000000000000000000000000000000000000001"
[l1-submission.blob.signer.web3signer]
endpoint = "http://web3signer:9000"
max-pool-size = 10
keep-alive = true
public-key = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"
[l1-submission.aggregation]
disabled = false
l1-endpoint = "http://l1-el-node:8545"
submission-delay = "PT2S"
submission-tick-interval = "PT12S"
max-submissions-per-tick = 10
[l1-submission.aggregation.gas]
gas-limit = 10_000_001
max-fee-per-gas-cap = 100_000_000_001
max-priority-fee-per-gas-cap = 10_000_000_001
[l1-submission.aggregation.gas.fallback]
# Note: prefixed with "fallback-", used when dynamic gas price is disabled or DB is not populated yet
priority-fee-per-gas-upper-bound = 20_000_000_001 # 20 GWEI
priority-fee-per-gas-lower-bound = 2_000_000_001 # 2 GWEI
[l1-submission.aggregation.signer]
# Web3j/Web3signer
type = "Web3signer"
[l1-submission.aggregation.signer.web3j]
private-key = "0x0000000000000000000000000000000000000000000000000000000000000002"
[l1-submission.aggregation.signer.web3signer]
endpoint = "http://web3signer:9000"
max-pool-size = 10
keep-alive = true
public-key = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002"
""".trimIndent()
val config =
L1SubmissionConfigToml(
disabled = true,
dynamicGasPriceCap = L1SubmissionConfigToml.DynamicGasPriceCapToml(
disabled = true,
gasPriceCapCalculation = L1SubmissionConfigToml.DynamicGasPriceCapToml.GasPriceCapCalculationToml(
adjustmentConstant = 25u,
blobAdjustmentConstant = 25u,
finalizationTargetMaxDelay = 32.hours,
baseFeePerGasPercentileWindow = 7.days,
baseFeePerGasPercentileWindowLeeway = 10.minutes,
baseFeePerGasPercentile = 10u,
gasPriceCapsCheckCoefficient = 0.9,
historicBaseFeePerBlobGasLowerBound = 100000011UL,
historicAvgRewardConstant = 100000012UL,
),
feeHistoryFetcher = L1SubmissionConfigToml.DynamicGasPriceCapToml.FeeHistoryFetcherConfig(
l1Endpoint = "http://l1-node:8545".toURL(),
fetchInterval = 1.seconds,
maxBlockCount = 1000u,
rewardPercentiles = listOf(10, 20, 30, 40, 50, 60, 70, 80, 90, 100).map { it.toUInt() },
numOfBlocksBeforeLatest = 2u,
storagePeriod = 10.days,
),
),
fallbackGasPrice = L1SubmissionConfigToml.FallbackGasPriceToml(
feeHistoryBlockCount = 10u,
feeHistoryRewardPercentile = 15u,
),
blob = L1SubmissionConfigToml.BlobSubmissionConfigToml(
disabled = false,
l1Endpoint = "http://l1-el-node:8545".toURL(),
submissionDelay = 1.seconds,
submissionTickInterval = 10.seconds,
maxSubmissionTransactionsPerTick = 10u,
targetBlobsPerTransaction = 9u,
dbMaxBlobsToReturn = 100u,
gas = L1SubmissionConfigToml.GasConfigToml(
gasLimit = 10_000_000u,
maxFeePerGasCap = 100_000_000_000u,
maxFeePerBlobGasCap = 100_000_000u,
maxPriorityFeePerGasCap = 10_000_000_000u,
fallback = L1SubmissionConfigToml.GasConfigToml.FallbackGasConfig(
priorityFeePerGasUpperBound = 20_000_000_000u,
priorityFeePerGasLowerBound = 2_000_000_000u,
),
),
signer = SignerConfigToml(
type = SignerConfigToml.SignerType.WEB3J,
web3j = SignerConfigToml.Web3jConfig(
privateKey = Masked("0x0000000000000000000000000000000000000000000000000000000000000001"),
),
web3signer = SignerConfigToml.Web3SignerConfig(
endpoint = "http://web3signer:9000".toURL(),
publicKey = (
"0000000000000000000000000000000000000000000000000000000000000000" +
"0000000000000000000000000000000000000000000000000000000000000001"
).decodeHex(),
maxPoolSize = 10,
keepAlive = true,
),
),
),
aggregation = L1SubmissionConfigToml.AggregationSubmissionToml(
disabled = false,
l1Endpoint = "http://l1-el-node:8545".toURL(),
submissionDelay = 2.seconds,
submissionTickInterval = 12.seconds,
maxSubmissionsPerTick = 10u,
gas = L1SubmissionConfigToml.GasConfigToml(
gasLimit = 10_000_001u,
maxFeePerGasCap = 100_000_000_001u,
maxFeePerBlobGasCap = null,
maxPriorityFeePerGasCap = 10_000_000_001u,
fallback = L1SubmissionConfigToml.GasConfigToml.FallbackGasConfig(
priorityFeePerGasUpperBound = 20_000_000_001u,
priorityFeePerGasLowerBound = 2_000_000_001u,
),
),
signer = SignerConfigToml(
type = SignerConfigToml.SignerType.WEB3SIGNER,
web3j = SignerConfigToml.Web3jConfig(
privateKey = Masked("0x0000000000000000000000000000000000000000000000000000000000000002"),
),
web3signer = SignerConfigToml.Web3SignerConfig(
endpoint = "http://web3signer:9000".toURL(),
publicKey = (
"0000000000000000000000000000000000000000000000000000000000000000" +
"0000000000000000000000000000000000000000000000000000000000000002"
).decodeHex(),
maxPoolSize = 10,
keepAlive = true,
),
),
),
)
val tomlMinimal = """
[l1-submission]
[l1-submission.dynamic-gas-price-cap]
[l1-submission.dynamic-gas-price-cap.gas-price-cap-calculation]
adjustment-constant = 25
blob-adjustment-constant = 25
finalization-target-max-delay = "PT32H"
base-fee-per-gas-percentile-window = "P7D"
base-fee-per-gas-percentile-window-leeway = "PT10M"
base-fee-per-gas-percentile = 10
gas-price-caps-check-coefficient = 0.9
historic-base-fee-per-blob-gas-lower-bound=100000011 # 0.1 GWEI
historic-avg-reward-constant=100000012 # 0.1 GWEI
[l1-submission.dynamic-gas-price-cap.gas-price-cap-calculation]
[l1-submission.fallback-gas-price]
fee-history-block-count = 10
fee-history-reward-percentile = 15
[l1-submission.blob]
[l1-submission.blob.gas]
gas-limit = 10_000_000
max-fee-per-gas-cap = 100_000_000_001
max-fee-per-blob-gas-cap = 100_000_000
max-priority-fee-per-gas-cap=10_000_000_000
[l1-submission.blob.gas.fallback]
priority-fee-per-gas-upper-bound = 20_000_000_000 # 20 GWEI
priority-fee-per-gas-lower-bound = 2_000_000_000 # 2 GWEI
[l1-submission.blob.signer]
type = "Web3j"
[l1-submission.blob.signer.web3j]
private-key = "0x0000000000000000000000000000000000000000000000000000000000000001"
[l1-submission.aggregation]
[l1-submission.aggregation.gas]
gas-limit = 10_000_001
max-fee-per-gas-cap = 100_000_000_011
max-priority-fee-per-gas-cap = 10_000_000_010
[l1-submission.aggregation.gas.fallback]
priority-fee-per-gas-upper-bound = 20_000_000_001 # 20 GWEI
priority-fee-per-gas-lower-bound = 2_000_000_001 # 2 GWEI
[l1-submission.aggregation.signer]
type = "Web3signer"
[l1-submission.aggregation.signer.web3signer]
endpoint = "http://web3signer:9000"
max-pool-size = 10
keep-alive = true
public-key = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002"
""".trimIndent()
val configMinimal =
L1SubmissionConfigToml(
disabled = false,
dynamicGasPriceCap = L1SubmissionConfigToml.DynamicGasPriceCapToml(
disabled = false,
gasPriceCapCalculation = L1SubmissionConfigToml.DynamicGasPriceCapToml.GasPriceCapCalculationToml(
adjustmentConstant = 25u,
blobAdjustmentConstant = 25u,
finalizationTargetMaxDelay = 32.hours,
baseFeePerGasPercentileWindow = 7.days,
baseFeePerGasPercentileWindowLeeway = 10.minutes,
baseFeePerGasPercentile = 10u,
gasPriceCapsCheckCoefficient = 0.9,
historicBaseFeePerBlobGasLowerBound = 100000011UL,
historicAvgRewardConstant = 100000012UL,
),
feeHistoryFetcher = L1SubmissionConfigToml.DynamicGasPriceCapToml.FeeHistoryFetcherConfig(
fetchInterval = 3.seconds,
maxBlockCount = 1000u,
rewardPercentiles = listOf(10, 20, 30, 40, 50, 60, 70, 80, 90, 100).map { it.toUInt() },
numOfBlocksBeforeLatest = 4u,
storagePeriod = 10.days,
),
),
fallbackGasPrice = L1SubmissionConfigToml.FallbackGasPriceToml(
feeHistoryBlockCount = 10u,
feeHistoryRewardPercentile = 15u,
),
blob = L1SubmissionConfigToml.BlobSubmissionConfigToml(
disabled = false,
l1Endpoint = null,
submissionDelay = 0.seconds,
submissionTickInterval = 12.seconds,
maxSubmissionTransactionsPerTick = 2u,
targetBlobsPerTransaction = 7u,
dbMaxBlobsToReturn = 100u,
gas = L1SubmissionConfigToml.GasConfigToml(
gasLimit = 10_000_000u,
maxFeePerGasCap = 100_000_000_001u,
maxFeePerBlobGasCap = 100_000_000u,
maxPriorityFeePerGasCap = 10_000_000_000u,
fallback = L1SubmissionConfigToml.GasConfigToml.FallbackGasConfig(
priorityFeePerGasUpperBound = 20_000_000_000u,
priorityFeePerGasLowerBound = 2_000_000_000u,
),
),
signer = SignerConfigToml(
type = SignerConfigToml.SignerType.WEB3J,
web3j = SignerConfigToml.Web3jConfig(
privateKey = Masked("0x0000000000000000000000000000000000000000000000000000000000000001"),
),
web3signer = null,
),
),
aggregation = L1SubmissionConfigToml.AggregationSubmissionToml(
disabled = false,
l1Endpoint = null,
submissionDelay = 0.seconds,
submissionTickInterval = 24.seconds,
maxSubmissionsPerTick = 1u,
gas = L1SubmissionConfigToml.GasConfigToml(
gasLimit = 10_000_001u,
maxFeePerGasCap = 100_000_000_011u,
maxFeePerBlobGasCap = null,
maxPriorityFeePerGasCap = 10_000_000_010u,
fallback = L1SubmissionConfigToml.GasConfigToml.FallbackGasConfig(
priorityFeePerGasUpperBound = 20_000_000_001u,
priorityFeePerGasLowerBound = 2_000_000_001u,
),
),
signer = SignerConfigToml(
type = SignerConfigToml.SignerType.WEB3SIGNER,
web3j = null,
web3signer = SignerConfigToml.Web3SignerConfig(
endpoint = "http://web3signer:9000".toURL(),
publicKey = (
"0000000000000000000000000000000000000000000000000000000000000000" +
"0000000000000000000000000000000000000000000000000000000000000002"
).decodeHex(),
maxPoolSize = 10,
keepAlive = true,
),
),
),
)
}
data class WrapperConfig(
val l1Submission: L1SubmissionConfigToml,
)
@Test
fun `should parse l1 submission full config`() {
assertThat(parseConfig<WrapperConfig>(toml).l1Submission)
.isEqualTo(config)
}
@Test
fun `should parse l1 submission minimal config`() {
assertThat(parseConfig<WrapperConfig>(tomlMinimal).l1Submission).isEqualTo(configMinimal)
}
}

View File

@@ -0,0 +1,135 @@
package linea.coordinator.config.v2
import linea.coordinator.config.v2.toml.L2NetworkGasPricingConfigToml
import linea.coordinator.config.v2.toml.RequestRetriesToml
import linea.coordinator.config.v2.toml.parseConfig
import linea.kotlin.toURL
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import kotlin.time.Duration.Companion.seconds
class L2NetWorkingGasPricingConfigParsingTest {
companion object {
val toml = """
[l2-network-gas-pricing]
disabled = false
price-update-interval = "PT12S"
fee-history-block-count = 50
fee-history-reward-percentile = 15
gas-price-fixed-cost = 3000000
l1-endpoint="http://l1-el-node:8545/"
extra-data-update-endpoint = "http://sequencer:8545/"
[l2-network-gas-pricing.extra-data-update-request-retries]
max-retries = 3
timeout = "PT6S"
backoff-delay = "PT1S"
failures-warning-threshold = 2
[l2-network-gas-pricing.flat-rate-gas-pricing]
gas-price-upper-bound = 10000000000 # 10 GWEI
gas-price-lower-bound = 90000000 # 0.09 GWEI
compressed-tx-size = 125
expected-gas = 21000
[l2-network-gas-pricing.dynamic-gas-pricing]
l1-blob-gas = 131072 # 2^17
blob-submission-expected-execution-gas = 213000 # Lower to 120k as we improve efficiency
variable-cost-upper-bound = 10000000001 # ~10 GWEI
variable-cost-lower-bound = 90000001 # ~0.09 GWEI
margin = 4.0
""".trimIndent()
val config = L2NetworkGasPricingConfigToml(
disabled = false,
priceUpdateInterval = 12.seconds,
feeHistoryBlockCount = 50u,
feeHistoryRewardPercentile = 15u,
gasPriceFixedCost = 3_000_000UL,
l1Endpoint = "http://l1-el-node:8545/".toURL(),
extraDataUpdateEndpoint = "http://sequencer:8545/".toURL(),
extraDataUpdateRequestRetries = RequestRetriesToml(
maxRetries = 3u,
timeout = 6.seconds,
backoffDelay = 1.seconds,
failuresWarningThreshold = 2u,
),
dynamicGasPricing = L2NetworkGasPricingConfigToml.DynamicGasPricingToml(
l1BlobGas = 131072UL, // 2^17
blobSubmissionExpectedExecutionGas = 213000UL, // Lower to 120k as we improve efficiency
variableCostUpperBound = 10_000_000_001UL, // ~10 GWEI
variableCostLowerBound = 90_000_001UL, // ~0.09 GWEI
margin = 4.0,
),
flatRateGasPricing = L2NetworkGasPricingConfigToml.FlatRateGasPricingToml(
gasPriceUpperBound = 10_000_000_000UL, // 10 GWEI
gasPriceLowerBound = 90_000_000UL, // 0.09 GWEI
compressedTxSize = 125u,
expectedGas = 21000u,
),
)
val tomlMinimal = """
[l2-network-gas-pricing]
gas-price-fixed-cost = 3000000
extra-data-update-endpoint = "http://sequencer:8545/"
[l2-network-gas-pricing.flat-rate-gas-pricing]
gas-price-upper-bound = 10000000000 # 10 GWEI
gas-price-lower-bound = 90000000 # 0.09 GWEI
compressed-tx-size = 125
expected-gas = 21000
[l2-network-gas-pricing.dynamic-gas-pricing]
l1-blob-gas = 131072 # 2^17
blob-submission-expected-execution-gas = 213000 # Lower to 120k as we improve efficiency
variable-cost-upper-bound = 10000000001 # ~10 GWEI
variable-cost-lower-bound = 90000001 # ~0.09 GWEI
margin = 4.0
""".trimIndent()
val configMinimal = L2NetworkGasPricingConfigToml(
disabled = false,
priceUpdateInterval = 12.seconds,
feeHistoryBlockCount = 1000u,
feeHistoryRewardPercentile = 15u,
gasPriceFixedCost = 3_000_000UL,
l1Endpoint = null,
extraDataUpdateEndpoint = "http://sequencer:8545/".toURL(),
extraDataUpdateRequestRetries = RequestRetriesToml(
maxRetries = null,
timeout = 8.seconds,
backoffDelay = 1.seconds,
failuresWarningThreshold = 3u,
),
dynamicGasPricing = L2NetworkGasPricingConfigToml.DynamicGasPricingToml(
l1BlobGas = 131072UL, // 2^17
blobSubmissionExpectedExecutionGas = 213000UL, // Lower to 120k as we improve efficiency
variableCostUpperBound = 10_000_000_001UL, // ~10 GWEI
variableCostLowerBound = 90_000_001UL, // ~0.09 GWEI
margin = 4.0,
),
flatRateGasPricing = L2NetworkGasPricingConfigToml.FlatRateGasPricingToml(
gasPriceUpperBound = 10_000_000_000UL, // 10 GWEI
gasPriceLowerBound = 90_000_000UL, // 0.09 GWEI
compressedTxSize = 125u,
expectedGas = 21000u,
),
)
}
data class WrapperConfig(
val l2NetworkGasPricing: L2NetworkGasPricingConfigToml,
)
@Test
fun `should parse l2 network gaspricing full config`() {
assertThat(parseConfig<WrapperConfig>(toml).l2NetworkGasPricing)
.isEqualTo(config)
}
@Test
fun `should parse l2 network gaspricing minimal config`() {
assertThat(parseConfig<WrapperConfig>(tomlMinimal).l2NetworkGasPricing)
.isEqualTo(configMinimal)
}
}

View File

@@ -0,0 +1,27 @@
package linea.coordinator.config.v2
import linea.coordinator.config.v2.toml.loadConfigs
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import java.nio.file.Path
class LocalStackConfigsParsingTest {
@Test
fun `should keep local stack testing configs updated with the code`() {
// Just assert that Files have been loaded and parsed correctly
// This is to prevent Code changes in coordinator and forgetting to update config files used in the local stack
loadConfigs(
coordinatorConfigFiles = listOf(
Path.of("../../config/coordinator/coordinator-config-v2.toml"),
Path.of("../../config/coordinator/coordinator-config-v2-override-local-dev.toml"),
),
tracesLimitsFileV2 = Path.of("../../config/common/traces-limits-v2.toml"),
gasPriceCapTimeOfDayMultipliersFile = Path.of("../../config/common/gas-price-cap-time-of-day-multipliers.toml"),
smartContractErrorsFile = Path.of("../../config/common/smart-contract-errors.toml"),
enforceStrict = true,
).also { configs ->
// just small assertion to ensure that the configs are loaded and overridden correctly
assertThat(configs.database.host).isEqualTo("127.0.0.1")
}
}
}

View File

@@ -0,0 +1,185 @@
package linea.coordinator.config.v2
import com.sksamuel.hoplite.Masked
import linea.coordinator.config.v2.toml.MessageAnchoringConfigToml
import linea.coordinator.config.v2.toml.RequestRetriesToml
import linea.coordinator.config.v2.toml.SignerConfigToml
import linea.coordinator.config.v2.toml.parseConfig
import linea.domain.BlockParameter
import linea.kotlin.decodeHex
import linea.kotlin.toURL
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
class MessageAnchoringConfigParsingTest {
companion object {
val toml = """
[message-anchoring]
disabled = false
anchoring-tick-interval = "PT13S"
message-queue-capacity = 12_300
max-messages-to-anchor-per-l2-transaction = 86
l1-endpoint = "http://l1-el-node:8545"
l2-endpoint = "http://sequencer:8545"
l1-highest-block-tag="FINALIZED"
l2-highest-block-tag="LATEST" # optional, default to LATEST it shall not be necessary as Linea has instant finality
[message-anchoring.l1-request-retries]
max-retries = 4
backoff-delay = "PT1S"
timeout = "PT6S"
failures-warning-threshold = 2
[message-anchoring.l2-request-retries]
max-retries = 5
backoff-delay = "PT0.1S"
timeout = "PT10S"
failures-warning-threshold = 3
[message-anchoring.l1-event-scraping]
polling-interval = "PT1S"
polling-timeout = "PT50S"
eth-logs-search-success-backoff-delay = "PT0.1S"
eth-logs-search-block-chunk-size = 123
[message-anchoring.gas]
max-fee-per-gas-cap = 100000000000
gas-limit = 10000000
fee-history-block-count = 4
fee-history-reward-percentile = 15
[message-anchoring.signer]
# Web3j/Web3signer
type = "Web3j"
[message-anchoring.signer.web3j]
private-key = "0x0000000000000000000000000000000000000000000000000000000000000001"
[message-anchoring.signer.web3signer]
endpoint = "http://web3signer:9000"
max-pool-size = 11
keep-alive = true
public-key = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"
""".trimIndent()
val config =
MessageAnchoringConfigToml(
disabled = false,
l1Endpoint = "http://l1-el-node:8545".toURL(),
l2Endpoint = "http://sequencer:8545".toURL(),
l1HighestBlockTag = BlockParameter.Tag.FINALIZED,
l2HighestBlockTag = BlockParameter.Tag.LATEST,
anchoringTickInterval = 13.seconds,
messageQueueCapacity = 12_300u,
maxMessagesToAnchorPerL2Transaction = 86u,
l1EventScraping = MessageAnchoringConfigToml.L1EventScrapping(
pollingInterval = 1.seconds,
pollingTimeout = 50.seconds,
ethLogsSearchSuccessBackoffDelay = 100.milliseconds,
ethLogsSearchBlockChunkSize = 123u,
),
l1RequestRetries = RequestRetriesToml(
maxRetries = 4u,
backoffDelay = 1.seconds,
timeout = 6.seconds,
failuresWarningThreshold = 2u,
),
l2RequestRetries = RequestRetriesToml(
maxRetries = 5u,
backoffDelay = 100.milliseconds,
timeout = 10.seconds,
failuresWarningThreshold = 3u,
),
gas = MessageAnchoringConfigToml.GasConfig(
maxFeePerGasCap = 100_000_000_000u,
gasLimit = 10_000_000u,
feeHistoryBlockCount = 4u,
feeHistoryRewardPercentile = 15u,
),
signer = SignerConfigToml(
type = SignerConfigToml.SignerType.WEB3J,
web3j = SignerConfigToml.Web3jConfig(
privateKey = Masked("0x0000000000000000000000000000000000000000000000000000000000000001"),
),
web3signer = SignerConfigToml.Web3SignerConfig(
endpoint = "http://web3signer:9000".toURL(),
maxPoolSize = 11,
keepAlive = true,
publicKey = (
"0000000000000000000000000000000000000000000000000000000000000000" +
"0000000000000000000000000000000000000000000000000000000000000001"
).decodeHex(),
),
),
)
val tomlMinimal = """
[message-anchoring]
[message-anchoring.signer]
type = "Web3j"
[message-anchoring.signer.web3j]
private-key = "0x0000000000000000000000000000000000000000000000000000000000000001"
""".trimIndent()
val configMinimal =
MessageAnchoringConfigToml(
disabled = false,
anchoringTickInterval = 10.seconds,
messageQueueCapacity = 10_000u,
maxMessagesToAnchorPerL2Transaction = 100u,
l1HighestBlockTag = BlockParameter.Tag.FINALIZED,
l2HighestBlockTag = BlockParameter.Tag.LATEST,
l1Endpoint = null,
l2Endpoint = null,
l1EventScraping = MessageAnchoringConfigToml.L1EventScrapping(
pollingInterval = 6.seconds,
pollingTimeout = 5.seconds,
ethLogsSearchSuccessBackoffDelay = 1.milliseconds,
ethLogsSearchBlockChunkSize = 1000u,
),
l1RequestRetries = RequestRetriesToml(
maxRetries = null,
backoffDelay = 1.seconds,
timeout = null,
failuresWarningThreshold = 3u,
),
l2RequestRetries = RequestRetriesToml(
maxRetries = null,
backoffDelay = 1.seconds,
timeout = 8.seconds,
failuresWarningThreshold = 3u,
),
gas = MessageAnchoringConfigToml.GasConfig(
maxFeePerGasCap = 100_000_000_000u,
gasLimit = 2_500_000u,
feeHistoryBlockCount = 4u,
feeHistoryRewardPercentile = 15u,
),
signer = SignerConfigToml(
type = SignerConfigToml.SignerType.WEB3J,
web3j = SignerConfigToml.Web3jConfig(
privateKey = Masked("0x0000000000000000000000000000000000000000000000000000000000000001"),
),
web3signer = null,
),
)
}
data class WrapperConfig(
val messageAnchoring: MessageAnchoringConfigToml,
)
@Test
fun `should parse message anchoring full config`() {
assertThat(parseConfig<WrapperConfig>(toml).messageAnchoring)
.isEqualTo(config)
}
@Test
fun `should parse message anchoring minimal config`() {
assertThat(parseConfig<WrapperConfig>(tomlMinimal).messageAnchoring)
.isEqualTo(configMinimal)
}
}

View File

@@ -0,0 +1,78 @@
package linea.coordinator.config.v2
import linea.coordinator.config.v2.toml.ProtocolToml
import linea.coordinator.config.v2.toml.parseConfig
import linea.kotlin.decodeHex
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import kotlin.time.Duration.Companion.seconds
class ProtocolParsingTest {
companion object {
val toml = """
[protocol.genesis]
genesis-state-root-hash = "0x0000000000000000000000000000000000000000000000000000000000000001"
genesis-shnarf = "0x0000000000000000000000000000000000000000000000000000000000000002"
[protocol.l1]
contract-address = "0x0000000000000000000000000000000000000001"
block-time = "PT2S"
[protocol.l2]
contract-address = "0x0000000000000000000000000000000000000002"
contract-deployment-block-number = 1
""".trimIndent()
val config = ProtocolToml(
genesis = ProtocolToml.Genesis(
genesisStateRootHash = "0x0000000000000000000000000000000000000000000000000000000000000001".decodeHex(),
genesisShnarf = "0x0000000000000000000000000000000000000000000000000000000000000002".decodeHex(),
),
l1 = ProtocolToml.Layer1Config(
contractAddress = "0x0000000000000000000000000000000000000001",
blockTime = 2.seconds,
),
l2 = ProtocolToml.Layer2Config(
contractAddress = "0x0000000000000000000000000000000000000002",
contractDeploymentBlockNumber = 1UL,
),
)
val tomlMinimal = """
[protocol.genesis]
genesis-state-root-hash = "0x0000000000000000000000000000000000000000000000000000000000000001"
genesis-shnarf = "0x0000000000000000000000000000000000000000000000000000000000000002"
[protocol.l1]
contract-address = "0x0000000000000000000000000000000000000001"
[protocol.l2]
contract-address = "0x0000000000000000000000000000000000000002"
""".trimIndent()
val configMinimal = ProtocolToml(
genesis = ProtocolToml.Genesis(
genesisStateRootHash = "0x0000000000000000000000000000000000000000000000000000000000000001".decodeHex(),
genesisShnarf = "0x0000000000000000000000000000000000000000000000000000000000000002".decodeHex(),
),
l1 = ProtocolToml.Layer1Config(
contractAddress = "0x0000000000000000000000000000000000000001",
blockTime = 12.seconds,
),
l2 = ProtocolToml.Layer2Config(
contractAddress = "0x0000000000000000000000000000000000000002",
contractDeploymentBlockNumber = null,
),
)
}
internal data class WrapperConfig(val protocol: ProtocolToml)
@Test
fun `should parse protocol full configs`() {
assertThat(parseConfig<WrapperConfig>(toml).protocol)
.isEqualTo(config)
}
@Test
fun `should parse protocol minimal configs`() {
assertThat(parseConfig<WrapperConfig>(tomlMinimal).protocol)
.isEqualTo(configMinimal)
}
}

View File

@@ -0,0 +1,129 @@
package linea.coordinator.config.v2
import linea.coordinator.config.v2.toml.ProverToml
import linea.coordinator.config.v2.toml.parseConfig
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
class ProverParsingTest {
companion object {
val toml = """
[prover]
version = "v2.0.0"
fs-inprogress-request-writing-suffix = ".coordinator_writing_request"
fs-inprogress-proving-suffix-pattern = "\\.inprogress\\.prover_is_proving.*"
fs-polling-interval = "PT1S"
fs-polling-timeout = "PT10M"
[prover.execution]
fs-requests-directory = "/data/prover/v2/execution/requests"
fs-responses-directory = "/data/prover/v2/execution/responses"
[prover.blob-compression]
fs-requests-directory = "/data/prover/v2/compression/requests"
fs-responses-directory = "/data/prover/v2/compression/responses"
[prover.proof-aggregation]
fs-requests-directory = "/data/prover/v2/aggregation/requests"
fs-responses-directory = "/data/prover/v2/aggregation/responses"
[prover.new]
version = "v3.0.0"
switch-block-number-inclusive=1000
[prover.new.execution]
fs-requests-directory = "/data/prover/v3/execution/requests"
fs-responses-directory = "/data/prover/v3/execution/responses"
[prover.new.blob-compression]
fs-requests-directory = "/data/prover/v3/compression/requests"
fs-responses-directory = "/data/prover/v3/compression/responses"
[prover.new.proof-aggregation]
fs-requests-directory = "/data/prover/v3/aggregation/requests"
fs-responses-directory = "/data/prover/v3/aggregation/responses"
""".trimIndent()
val config = ProverToml(
version = "v2.0.0",
fsInprogressRequestWritingSuffix = ".coordinator_writing_request",
fsInprogressProvingSuffixPattern = "\\.inprogress\\.prover_is_proving.*",
fsPollingInterval = 1.seconds,
fsPollingTimeout = 10.minutes,
execution = ProverToml.ProverDirectoriesToml(
fsRequestsDirectory = "/data/prover/v2/execution/requests",
fsResponsesDirectory = "/data/prover/v2/execution/responses",
),
blobCompression = ProverToml.ProverDirectoriesToml(
fsRequestsDirectory = "/data/prover/v2/compression/requests",
fsResponsesDirectory = "/data/prover/v2/compression/responses",
),
proofAggregation = ProverToml.ProverDirectoriesToml(
fsRequestsDirectory = "/data/prover/v2/aggregation/requests",
fsResponsesDirectory = "/data/prover/v2/aggregation/responses",
),
new = ProverToml(
switchBlockNumberInclusive = 1_000u,
version = "v3.0.0",
execution = ProverToml.ProverDirectoriesToml(
fsRequestsDirectory = "/data/prover/v3/execution/requests",
fsResponsesDirectory = "/data/prover/v3/execution/responses",
),
blobCompression = ProverToml.ProverDirectoriesToml(
fsRequestsDirectory = "/data/prover/v3/compression/requests",
fsResponsesDirectory = "/data/prover/v3/compression/responses",
),
proofAggregation = ProverToml.ProverDirectoriesToml(
fsRequestsDirectory = "/data/prover/v3/aggregation/requests",
fsResponsesDirectory = "/data/prover/v3/aggregation/responses",
),
),
)
val tomlMinimal = """
[prover]
version = "v2.0.0"
[prover.execution]
fs-requests-directory = "/data/prover/v2/execution/requests"
fs-responses-directory = "/data/prover/v2/execution/responses"
[prover.blob-compression]
fs-requests-directory = "/data/prover/v2/compression/requests"
fs-responses-directory = "/data/prover/v2/compression/responses"
[prover.proof-aggregation]
fs-requests-directory = "/data/prover/v2/aggregation/requests"
fs-responses-directory = "/data/prover/v2/aggregation/responses"
""".trimIndent()
val configMinimal = ProverToml(
version = "v2.0.0",
fsInprogressRequestWritingSuffix = ".inprogress_coordinator_writing",
fsInprogressProvingSuffixPattern = "\\.inprogress\\.prover.*",
execution = ProverToml.ProverDirectoriesToml(
fsRequestsDirectory = "/data/prover/v2/execution/requests",
fsResponsesDirectory = "/data/prover/v2/execution/responses",
),
blobCompression = ProverToml.ProverDirectoriesToml(
fsRequestsDirectory = "/data/prover/v2/compression/requests",
fsResponsesDirectory = "/data/prover/v2/compression/responses",
),
proofAggregation = ProverToml.ProverDirectoriesToml(
fsRequestsDirectory = "/data/prover/v2/aggregation/requests",
fsResponsesDirectory = "/data/prover/v2/aggregation/responses",
),
switchBlockNumberInclusive = null,
new = null,
)
}
data class WrapperConfig(
val prover: ProverToml,
)
@Test
fun `should parse prover toml configs - full`() {
assertThat(
parseConfig<WrapperConfig>(toml).prover,
).isEqualTo(config)
}
@Test
fun `should parse conflation toml configs and provide defaults`() {
assertThat(
parseConfig<WrapperConfig>(tomlMinimal).prover,
).isEqualTo(configMinimal)
}
}

View File

@@ -0,0 +1,61 @@
package linea.coordinator.config.v2
import com.sksamuel.hoplite.Masked
import linea.coordinator.config.v2.toml.SignerConfigToml
import linea.coordinator.config.v2.toml.parseConfig
import linea.kotlin.decodeHex
import linea.kotlin.toURL
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
class SignerConfigParsingTest {
companion object {
val toml = """
[web3jExample]
type = "wEb3j" # Shall be case insensitive
[web3jExample.web3j]
private-key = "0x0000000000000000000000000000000000000000000000000000000000000001"
[web3signerExample]
type = "Web3SiGner" # Shall be case insensitive
[web3signerExample.web3signer]
endpoint = "http://web3signer:9000"
max-pool-size = 10
keep-alive = true
public-key = "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"
""".trimIndent()
val config = WrapperConfig(
web3jExample = SignerConfigToml(
type = SignerConfigToml.SignerType.WEB3J,
web3j = SignerConfigToml.Web3jConfig(
privateKey = Masked("0x0000000000000000000000000000000000000000000000000000000000000001"),
),
web3signer = null,
),
web3SignerExample = SignerConfigToml(
type = SignerConfigToml.SignerType.WEB3SIGNER,
web3j = null,
web3signer = SignerConfigToml.Web3SignerConfig(
endpoint = "http://web3signer:9000".toURL(),
publicKey = (
"0000000000000000000000000000000000000000000000000000000000000000" +
"0000000000000000000000000000000000000000000000000000000000000001"
).decodeHex(),
maxPoolSize = 10,
keepAlive = true,
),
),
)
}
data class WrapperConfig(
val web3jExample: SignerConfigToml,
val web3SignerExample: SignerConfigToml,
)
@Test
fun `should parse full state manager config`() {
assertThat(parseConfig<WrapperConfig>(toml)).isEqualTo(config)
}
}

View File

@@ -0,0 +1,68 @@
package linea.coordinator.config.v2
import linea.coordinator.config.v2.toml.RequestRetriesToml
import linea.coordinator.config.v2.toml.StateManagerToml
import linea.coordinator.config.v2.toml.parseConfig
import linea.kotlin.toURL
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import kotlin.time.Duration.Companion.seconds
class StateManagerParsingTest {
companion object {
val toml = """
[state-manager]
version = "2.2.0"
endpoints = ["http://shomei:8888/"]
request-limit-per-endpoint = 3
[state-manager.request-retries]
max-retries = 5
backoff-delay = "PT2S"
failures-warning-threshold = 2
""".trimIndent()
val config = StateManagerToml(
version = "2.2.0",
endpoints = listOf("http://shomei:8888/".toURL()),
requestLimitPerEndpoint = 3u,
requestRetries = RequestRetriesToml(
maxRetries = 5u,
backoffDelay = 2.seconds,
failuresWarningThreshold = 2u,
),
)
val tomlMinimal = """
[state-manager]
version = "2.2.0"
endpoints = ["http://shomei:8888/"]
""".trimIndent()
val configMinimal = StateManagerToml(
version = "2.2.0",
endpoints = listOf("http://shomei:8888/".toURL()),
requestLimitPerEndpoint = UInt.MAX_VALUE,
requestRetries = RequestRetriesToml(
maxRetries = null,
backoffDelay = 1.seconds,
failuresWarningThreshold = 3u,
),
)
}
data class WrapperConfig(
val stateManager: StateManagerToml,
)
@Test
fun `should parse state manager full config`() {
assertThat(parseConfig<WrapperConfig>(toml).stateManager)
.isEqualTo(config)
}
@Test
fun `should parse state manager minimal config`() {
assertThat(parseConfig<WrapperConfig>(tomlMinimal).stateManager)
.isEqualTo(configMinimal)
}
}

View File

@@ -0,0 +1,15 @@
package linea.coordinator.config.v2
import java.nio.file.Path
import java.nio.file.Paths
class TestHelper {
companion object {
fun pathToResource(resource: String): Path {
return Paths.get(
this::class.java.classLoader.getResource(resource)?.toURI()
?: error("Resource not found: $resource"),
)
}
}
}

View File

@@ -0,0 +1,174 @@
package linea.coordinator.config.v2
import linea.coordinator.config.v2.toml.RequestRetriesToml
import linea.coordinator.config.v2.toml.TracesToml
import linea.coordinator.config.v2.toml.parseConfig
import linea.kotlin.toURL
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import java.net.URI
import kotlin.time.Duration.Companion.seconds
class TracesParsingTest {
companion object {
val toml = """
[traces]
expected-traces-api-version = "1.2.0"
[traces.counters]
endpoints = ["http://traces-api-1:8080/"]
request-limit-per-endpoint = 20
[traces.counters.request-retries]
max-retries = 40
backoff-delay = "PT1S"
failures-warning-threshold = 2
[traces.conflation]
endpoints = ["http://traces-api-2:8080/"]
request-limit-per-endpoint = 2
[traces.conflation.request-retries]
max-retries = 30
backoff-delay = "PT3S"
failures-warning-threshold = 4
[traces.new]
switch-block-number-inclusive=1_000
expected-traces-api-version = "2.0.0"
[traces.new.counters]
endpoints = ["http://traces-api-v2-11:8080/", "http://traces-api-v2-12:8080/"]
request-limit-per-endpoint = 200
[traces.new.counters.request-retries]
max-retries = 4
backoff-delay = "PT1S"
failures-warning-threshold = 2
[traces.new.conflation]
endpoints = ["http://traces-api-v2-21:8080/", "http://traces-api-v2-22:8080/"]
request-limit-per-endpoint = 5
[traces.new.conflation.request-retries]
max-retries = 55
backoff-delay = "PT50S"
failures-warning-threshold = 50
""".trimIndent()
val config = TracesToml(
expectedTracesApiVersion = "1.2.0",
counters = TracesToml.ClientApiConfigToml(
endpoints = listOf(URI.create("http://traces-api-1:8080/").toURL()),
requestLimitPerEndpoint = 20u,
requestRetries = RequestRetriesToml(
maxRetries = 40u,
backoffDelay = 1.seconds,
failuresWarningThreshold = 2u,
),
),
conflation = TracesToml.ClientApiConfigToml(
endpoints = listOf(URI.create("http://traces-api-2:8080/").toURL()),
requestLimitPerEndpoint = 2u,
requestRetries = RequestRetriesToml(
maxRetries = 30u,
backoffDelay = 3.seconds,
failuresWarningThreshold = 4u,
),
),
new = TracesToml(
expectedTracesApiVersion = "2.0.0",
switchBlockNumberInclusive = 1_000u,
counters = TracesToml.ClientApiConfigToml(
endpoints = listOf("http://traces-api-v2-11:8080/", "http://traces-api-v2-12:8080/").map { it.toURL() },
requestLimitPerEndpoint = 200u,
requestRetries = RequestRetriesToml(
maxRetries = 4u,
backoffDelay = 1.seconds,
failuresWarningThreshold = 2u,
),
),
conflation = TracesToml.ClientApiConfigToml(
endpoints = listOf("http://traces-api-v2-21:8080/", "http://traces-api-v2-22:8080/").map { it.toURL() },
requestLimitPerEndpoint = 5u,
requestRetries = RequestRetriesToml(
maxRetries = 55u,
backoffDelay = 50.seconds,
failuresWarningThreshold = 50u,
),
),
),
)
val tomlMinimal = """
[traces]
expected-traces-api-version = "1.2.0"
[traces.counters]
endpoints = ["http://traces-api-1:8080/"]
[traces.conflation]
endpoints = ["http://traces-api-2:8080/"]
""".trimIndent()
val configMinimal = TracesToml(
expectedTracesApiVersion = "1.2.0",
counters = TracesToml.ClientApiConfigToml(
endpoints = listOf(URI.create("http://traces-api-1:8080/").toURL()),
requestLimitPerEndpoint = UInt.MAX_VALUE,
requestRetries = RequestRetriesToml(
maxRetries = null,
backoffDelay = 1.seconds,
failuresWarningThreshold = 3u,
),
),
conflation = TracesToml.ClientApiConfigToml(
endpoints = listOf(URI.create("http://traces-api-2:8080/").toURL()),
requestLimitPerEndpoint = UInt.MAX_VALUE,
requestRetries = RequestRetriesToml(
maxRetries = null,
backoffDelay = 1.seconds,
failuresWarningThreshold = 3u,
),
),
)
}
data class WrapperConfig(
val traces: TracesToml,
)
@Test
fun `should parse ClientApiConfigToml config`() {
assertThat(
parseConfig<TracesToml.ClientApiConfigToml>(
"""
endpoints = ["http://traces-api-1:8080/", "http://traces-api-2:8080/"]
request-limit-per-endpoint = 2
[request-retries]
max-retries = 6
backoff-delay = "PT3S"
failures-warning-threshold = 4
""".trimIndent(),
),
)
.isEqualTo(
TracesToml.ClientApiConfigToml(
endpoints = listOf(
"http://traces-api-1:8080/".toURL(),
"http://traces-api-2:8080/".toURL(),
),
requestLimitPerEndpoint = 2u,
requestRetries = RequestRetriesToml(
maxRetries = 6u,
backoffDelay = 3.seconds,
failuresWarningThreshold = 4u,
),
),
)
}
@Test
fun `should parse traces full config`() {
assertThat(parseConfig<WrapperConfig>(toml).traces)
.isEqualTo(config)
}
@Test
fun `should parse traces minimal config`() {
assertThat(parseConfig<WrapperConfig>(tomlMinimal).traces)
.isEqualTo(configMinimal)
}
}

View File

@@ -0,0 +1,71 @@
package linea.coordinator.config.v2
import linea.coordinator.config.v2.toml.RequestRetriesToml
import linea.coordinator.config.v2.toml.Type2StateProofManagerToml
import linea.coordinator.config.v2.toml.parseConfig
import linea.domain.BlockParameter
import linea.kotlin.toURL
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import kotlin.time.Duration.Companion.seconds
class Type2StateProofProviderParsingTest {
companion object {
val toml = """
[type2-state-proof-provider]
disabled = false
endpoints = ["http://shomei-frontend-i1:8888/", "http://shomei-frontend-i2:8888/"]
l1-query-block-tag="SAFE"
l1-polling-interval="PT12S"
[type2-state-proof-provider.request-retries]
max-retries = 3
backoff-delay = "PT1S"
failures-warning-threshold = 2
""".trimIndent()
val config = Type2StateProofManagerToml(
disabled = false,
endpoints = listOf("http://shomei-frontend-i1:8888/".toURL(), "http://shomei-frontend-i2:8888/".toURL()),
l1QueryBlockTag = BlockParameter.Tag.SAFE,
l1PollingInterval = 12.seconds,
requestRetries = RequestRetriesToml(
maxRetries = 3u,
backoffDelay = 1.seconds,
failuresWarningThreshold = 2u,
),
)
val tomlMinimal = """
[type2-state-proof-provider]
endpoints = ["http://shomei-frontend-i1:8888/", "http://shomei-frontend-i2:8888/"]
""".trimIndent()
val configMinimal = Type2StateProofManagerToml(
disabled = false,
endpoints = listOf("http://shomei-frontend-i1:8888/".toURL(), "http://shomei-frontend-i2:8888/".toURL()),
l1QueryBlockTag = BlockParameter.Tag.FINALIZED,
l1PollingInterval = 6.seconds,
requestRetries = RequestRetriesToml(
maxRetries = null,
backoffDelay = 1.seconds,
failuresWarningThreshold = 3u,
),
)
}
data class WrapperConfig(
val type2StateProofProvider: Type2StateProofManagerToml,
)
@Test
fun `should parse type2-state-proof-provider full config`() {
assertThat(parseConfig<WrapperConfig>(toml).type2StateProofProvider)
.isEqualTo(config)
}
@Test
fun `should parse type2-state-proof-provider minimal config`() {
assertThat(parseConfig<WrapperConfig>(tomlMinimal).type2StateProofProvider)
.isEqualTo(configMinimal)
}
}

View File

@@ -0,0 +1,63 @@
package linea.coordinator.config.v2.toml.decoder
import linea.coordinator.config.v2.toml.parseConfig
import linea.domain.BlockParameter
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
class BlockParameterDecoderTest {
@Test
fun `should decode block parameter tag`() {
data class ConfigTomTag(val blockParameter: BlockParameter.Tag)
assertThat(parseConfig<ConfigTomTag>("""block-parameter = "latest" """))
.isEqualTo(ConfigTomTag(BlockParameter.Tag.LATEST))
}
@Test
fun `should decode block parameter tag nullable`() {
data class ConfigTomTag(val blockParameter: BlockParameter.Tag? = null)
assertThat(parseConfig<ConfigTomTag>("""block-parameter = "latest" """))
.isEqualTo(ConfigTomTag(BlockParameter.Tag.LATEST))
assertThat(parseConfig<ConfigTomTag>(""" """))
.isEqualTo(ConfigTomTag(null))
}
@Test
fun `should decode block parameter number`() {
data class ConfigTomTag(val blockParameter: BlockParameter.BlockNumber)
assertThat(parseConfig<ConfigTomTag>("""block-parameter = 2_000 """))
.isEqualTo(ConfigTomTag(BlockParameter.BlockNumber(2_000UL)))
}
@Disabled("fails with Cannot cast java.lang.Long to linea.domain.BlockParameter.BlockNumber")
fun `should decode block parameter number nullable`() {
data class ConfigTomTag(val blockParameter: BlockParameter.BlockNumber? = null)
assertThat(parseConfig<ConfigTomTag>("""block-parameter = 2_000 """))
.isEqualTo(ConfigTomTag(BlockParameter.BlockNumber(2_000UL)))
assertThat(parseConfig<ConfigTomTag>(""" """))
.isEqualTo(ConfigTomTag(null))
}
@Test
fun `should decode block parameter generic type with tag set`() {
data class ConfigTomTag(val blockParameter: BlockParameter)
assertThat(parseConfig<ConfigTomTag>("""block-parameter = "latest" """))
.isEqualTo(ConfigTomTag(BlockParameter.Tag.LATEST))
}
@Test
fun `should decode block parameter generic type with number set`() {
data class ConfigTomTag(val blockParameter: BlockParameter)
assertThat(parseConfig<ConfigTomTag>("""block-parameter = 2_000 """))
.isEqualTo(ConfigTomTag(BlockParameter.BlockNumber(2_000UL)))
}
}

View File

@@ -1,558 +0,0 @@
package net.consensys.zkevm.coordinator.app.config
import com.github.michaelbull.result.getError
import com.sksamuel.hoplite.Masked
import linea.blob.BlobCompressorVersion
import linea.coordinator.config.loadConfigs
import linea.coordinator.config.loadConfigsOrError
import linea.domain.BlockParameter
import net.consensys.linea.ethereum.gaspricing.BoundableFeeCalculator
import net.consensys.linea.ethereum.gaspricing.staticcap.ExtraDataV1UpdaterImpl
import net.consensys.linea.ethereum.gaspricing.staticcap.FeeHistoryFetcherImpl
import net.consensys.linea.ethereum.gaspricing.staticcap.GasPriceUpdaterImpl
import net.consensys.linea.ethereum.gaspricing.staticcap.MinerExtraDataV1CalculatorImpl
import net.consensys.linea.ethereum.gaspricing.staticcap.TransactionCostCalculator
import net.consensys.linea.ethereum.gaspricing.staticcap.VariableFeesCalculator
import net.consensys.linea.jsonrpc.client.RequestRetryConfig
import net.consensys.zkevm.coordinator.app.L2NetworkGasPricingService
import net.consensys.zkevm.coordinator.clients.prover.FileBasedProverConfig
import net.consensys.zkevm.coordinator.clients.prover.ProverConfig
import net.consensys.zkevm.coordinator.clients.prover.ProversConfig
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertThrows
import java.math.BigInteger
import java.net.URI
import java.nio.file.Path
import java.nio.file.Paths
import java.time.Duration
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toJavaDuration
class CoordinatorConfigTest {
companion object {
private val apiConfig = ApiConfig(9545U)
private val conflationConfig = ConflationConfig(
consistentNumberOfBlocksOnL1ToWait = 1,
conflationDeadline = Duration.parse("PT6S"),
conflationDeadlineCheckInterval = Duration.parse("PT3S"),
conflationDeadlineLastBlockConfirmationDelay = Duration.parse("PT2S"),
blocksLimit = 2,
_tracesLimitsV2 = expectedTracesLimitsV2,
_smartContractErrors = mapOf(
// L1 Linea Rollup
"0f06cd15" to "DataAlreadySubmitted",
"c01eab56" to "EmptySubmissionData",
),
fetchBlocksLimit = 4000,
)
private val proversConfig = ProversConfig(
proverA = ProverConfig(
execution = FileBasedProverConfig(
requestsDirectory = Path.of("/data/prover/v2/execution/requests"),
responsesDirectory = Path.of("/data/prover/v2/execution/responses"),
pollingInterval = 1.seconds,
pollingTimeout = 10.minutes,
inprogressProvingSuffixPattern = ".*\\.inprogress\\.prover.*",
inprogressRequestWritingSuffix = ".inprogress_coordinator_writing",
),
blobCompression = FileBasedProverConfig(
requestsDirectory = Path.of("/data/prover/v2/compression/requests"),
responsesDirectory = Path.of("/data/prover/v2/compression/responses"),
pollingInterval = 1.seconds,
pollingTimeout = 10.minutes,
inprogressProvingSuffixPattern = ".*\\.inprogress\\.prover.*",
inprogressRequestWritingSuffix = ".inprogress_coordinator_writing",
),
proofAggregation = FileBasedProverConfig(
requestsDirectory = Path.of("/data/prover/v2/aggregation/requests"),
responsesDirectory = Path.of("/data/prover/v2/aggregation/responses"),
pollingInterval = 1.seconds,
pollingTimeout = 10.minutes,
inprogressProvingSuffixPattern = ".*\\.inprogress\\.prover.*",
inprogressRequestWritingSuffix = ".inprogress_coordinator_writing",
),
),
switchBlockNumberInclusive = null,
proverB = null,
)
private val blobCompressionConfig = BlobCompressionConfig(
blobSizeLimit = 100 * 1024,
handlerPollingInterval = Duration.parse("PT1S"),
_batchesLimit = 1,
)
private val aggregationConfig = AggregationConfig(
aggregationProofsLimit = 3,
aggregationDeadline = Duration.parse("PT10S"),
aggregationCoordinatorPollingInterval = Duration.parse("PT2S"),
deadlineCheckInterval = Duration.parse("PT8S"),
)
private val tracesConfig = TracesConfig(
blobCompressorVersion = BlobCompressorVersion.V1_2,
rawExecutionTracesVersion = "0.2.0",
expectedTracesApiVersionV2 = "v0.8.0-rc8",
conflationV2 = TracesConfig.FunctionalityEndpoint(
endpoints = listOf(URI("http://traces-node:8545/").toURL()),
requestLimitPerEndpoint = 1U,
requestRetry = RequestRetryConfigTomlFriendly(
backoffDelay = Duration.parse("PT1S"),
failuresWarningThreshold = 2,
),
),
countersV2 = TracesConfig.FunctionalityEndpoint(
endpoints = listOf(URI("http://traces-node:8545/").toURL()),
requestLimitPerEndpoint = 1U,
requestRetry = RequestRetryConfigTomlFriendly(
backoffDelay = Duration.parse("PT1S"),
failuresWarningThreshold = 2,
),
),
)
private val type2StateProofProviderConfig = Type2StateProofProviderConfig(
endpoints = listOf(URI("http://shomei-frontend:8888/").toURL()),
requestRetry = RequestRetryConfigTomlFriendly(
backoffDelay = Duration.parse("PT1S"),
failuresWarningThreshold = 2,
),
l1QueryBlockTag = BlockParameter.Tag.SAFE,
l1PollingInterval = Duration.parse("PT6S"),
)
private val stateManagerConfig = StateManagerClientConfig(
version = "2.3.0",
endpoints = listOf(
URI("http://shomei:8888/").toURL(),
),
requestLimitPerEndpoint = 2U,
requestRetry = RequestRetryConfigTomlFriendly(
backoffDelay = Duration.parse("PT2S"),
failuresWarningThreshold = 2,
),
)
private val blobSubmissionConfig = BlobSubmissionConfig(
dbPollingInterval = Duration.parse("PT1S"),
maxBlobsToReturn = 100,
maxBlobsToSubmitPerTick = 10,
priorityFeePerGasUpperBound = 2000000000UL,
priorityFeePerGasLowerBound = 200000000UL,
proofSubmissionDelay = Duration.parse("PT1S"),
targetBlobsToSendPerTransaction = 9,
useEthEstimateGas = false,
disabled = false,
)
private val aggregationFinalizationConfig = AggregationFinalizationConfig(
dbPollingInterval = Duration.parse("PT1S"),
maxAggregationsToFinalizePerTick = 1,
proofSubmissionDelay = Duration.parse("PT1S"),
useEthEstimateGas = true,
disabled = false,
)
private val databaseConfig = DatabaseConfig(
host = "postgres",
port = 5432,
username = "postgres",
password = Masked("postgres"),
schema = "linea_coordinator",
readPoolSize = 10,
readPipeliningLimit = 10,
transactionalPoolSize = 10,
)
private val persistenceRetryConfig = PersistenceRetryConfig(
maxRetries = null,
backoffDelay = Duration.parse("PT1S"),
)
private val l1Config = L1Config(
zkEvmContractAddress = "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",
rpcEndpoint = URI("http://l1-el-node:8545").toURL(),
finalizationPollingInterval = Duration.parse("PT6S"),
_l1QueryBlockTag = BlockParameter.Tag.LATEST.getTag(),
gasLimit = 10000000UL,
feeHistoryBlockCount = 10,
feeHistoryRewardPercentile = 15.0,
maxFeePerGasCap = 100000000000UL,
maxFeePerBlobGasCap = 100000000000UL,
maxPriorityFeePerGasCap = 20000000000UL,
gasPriceCapMultiplierForFinalization = 2.0,
earliestBlock = BigInteger.ZERO,
sendMessageEventPollingInterval = Duration.parse("PT1S"),
maxEventScrapingTime = Duration.parse("PT5S"),
maxMessagesToCollect = 1000U,
finalizedBlockTag = "latest",
blockTime = Duration.parse("PT1S"),
blockRangeLoopLimit = 500U,
_ethFeeHistoryEndpoint = null,
_genesisStateRootHash = "0x072ead6777750dc20232d1cee8dc9a395c2d350df4bbaa5096c6f59b214dcecd",
_genesisShnarfV6 = "0x47452a1b9ebadfe02bdd02f580fa1eba17680d57eec968a591644d05d78ee84f",
)
private val l2Config = L2Config(
messageServiceAddress = "0xe537D669CA013d86EBeF1D64e40fC74CADC91987",
rpcEndpoint = URI("http://sequencer:8545").toURL(),
gasLimit = 10000000u,
maxFeePerGasCap = 100000000000u,
feeHistoryBlockCount = 4U,
feeHistoryRewardPercentile = 15.0,
blocksToFinalization = 0U,
lastHashSearchWindow = 25U,
anchoringReceiptPollingInterval = Duration.parse("PT01S"),
maxReceiptRetries = 120U,
newBlockPollingInterval = Duration.parse("PT1S"),
)
private val finalizationSigner = SignerConfig(
type = SignerConfig.Type.Web3j,
web3signer = Web3SignerConfig(
endpoint = "http://web3signer:9000",
maxPoolSize = 10U,
keepAlive = true,
publicKey =
"ba5734d8f7091719471e7f7ed6b9df170dc70cc661ca05e688601ad984f068b0d67351e5f06073092499336ab0839ef8a521afd334e5" +
"3807205fa2f08eec74f4",
),
web3j = Web3jConfig(Masked("0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d")),
)
private val dataSubmissionSigner = SignerConfig(
type = SignerConfig.Type.Web3j,
web3signer = Web3SignerConfig(
endpoint = "http://web3signer:9000",
maxPoolSize = 10U,
keepAlive = true,
publicKey =
"9d9031e97dd78ff8c15aa86939de9b1e791066a0224e331bc962a2099a7b1f0464b8bbafe1535f2301c72c2cb3535b172da30b02686a" +
"b0393d348614f157fbdb",
),
web3j = Web3jConfig(Masked("0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a")),
)
private val l2SignerConfig = SignerConfig(
type = SignerConfig.Type.Web3j,
web3signer = Web3SignerConfig(
endpoint = "http://web3signer:9000",
maxPoolSize = 10U,
keepAlive = true,
publicKey =
"4a788ad6fa008beed58de6418369717d7492f37d173d70e2c26d9737e2c6eeae929452ef8602a19410844db3e200a0e73f5208fd7625" +
"9a8766b73953fc3e7023",
),
web3j = Web3jConfig(Masked("0x4d01ae6487860981699236a58b68f807ee5f17b12df5740b85cf4c4653be0f55")),
)
private val l2NetworkGasPricingRequestRetryConfig = RequestRetryConfig(
maxRetries = 3u,
timeout = 6.seconds,
backoffDelay = 1.seconds,
failuresWarningThreshold = 2u,
)
private val l2NetworkGasPricingServiceConfig = L2NetworkGasPricingService.Config(
feeHistoryFetcherConfig = FeeHistoryFetcherImpl.Config(
feeHistoryBlockCount = 50U,
feeHistoryRewardPercentile = 15.0,
),
legacy = L2NetworkGasPricingService.LegacyGasPricingCalculatorConfig(
legacyGasPricingCalculatorBounds = BoundableFeeCalculator.Config(
feeUpperBound = 10_000_000_000.0,
feeLowerBound = 90_000_000.0,
feeMargin = 0.0,
),
transactionCostCalculatorConfig = TransactionCostCalculator.Config(
sampleTransactionCostMultiplier = 1.0,
fixedCostWei = 3000000u,
compressedTxSize = 125,
expectedGas = 21000,
),
naiveGasPricingCalculatorConfig = null,
),
jsonRpcGasPriceUpdaterConfig = GasPriceUpdaterImpl.Config(
gethEndpoints = listOf(
URI("http://l2-node:8545/").toURL(),
),
besuEndPoints = listOf(),
retryConfig = l2NetworkGasPricingRequestRetryConfig,
),
jsonRpcPriceUpdateInterval = 12.seconds,
extraDataPricingPropagationEnabled = true,
extraDataUpdateInterval = 12.seconds,
variableFeesCalculatorConfig = VariableFeesCalculator.Config(
blobSubmissionExpectedExecutionGas = 213_000u,
bytesPerDataSubmission = 131072u,
expectedBlobGas = 131072u,
margin = 4.0,
),
variableFeesCalculatorBounds = BoundableFeeCalculator.Config(
feeUpperBound = 10_000_000_001.0,
feeLowerBound = 90_000_001.0,
feeMargin = 0.0,
),
extraDataCalculatorConfig = MinerExtraDataV1CalculatorImpl.Config(
fixedCostInKWei = 3000u,
ethGasPriceMultiplier = 1.2,
),
extraDataUpdaterConfig = ExtraDataV1UpdaterImpl.Config(
sequencerEndpoint = URI("http://sequencer:8545/").toURL(),
retryConfig = l2NetworkGasPricingRequestRetryConfig,
),
)
private val l1DynamicGasPriceCapServiceConfig = L1DynamicGasPriceCapServiceConfig(
disabled = false,
gasPriceCapCalculation = L1DynamicGasPriceCapServiceConfig.GasPriceCapCalculation(
adjustmentConstant = 25U,
blobAdjustmentConstant = 25U,
finalizationTargetMaxDelay = Duration.parse("PT30S"),
gasFeePercentileWindow = Duration.parse("PT1M"),
gasFeePercentileWindowLeeway = Duration.parse("PT10S"),
gasFeePercentile = 10.0,
gasPriceCapsCheckCoefficient = 0.9,
historicBaseFeePerBlobGasLowerBound = 100_000_000u,
historicAvgRewardConstant = 100_000_000u,
timeOfDayMultipliers = expectedTimeOfDayMultipliers,
),
feeHistoryFetcher = L1DynamicGasPriceCapServiceConfig.FeeHistoryFetcher(
fetchInterval = Duration.parse("PT1S"),
maxBlockCount = 1000U,
rewardPercentiles = listOf(10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0),
numOfBlocksBeforeLatest = 4U,
endpoint = null,
),
feeHistoryStorage = L1DynamicGasPriceCapServiceConfig.FeeHistoryStorage(
storagePeriod = Duration.parse("PT2M"),
),
)
private val coordinatorConfig = CoordinatorConfig(
blobCompression = blobCompressionConfig,
proofAggregation = aggregationConfig,
traces = tracesConfig,
type2StateProofProvider = type2StateProofProviderConfig,
l1 = l1Config,
l2 = l2Config,
finalizationSigner = finalizationSigner,
dataSubmissionSigner = dataSubmissionSigner,
blobSubmission = blobSubmissionConfig,
aggregationFinalization = aggregationFinalizationConfig,
database = databaseConfig,
persistenceRetry = persistenceRetryConfig,
stateManager = stateManagerConfig,
conflation = conflationConfig,
api = apiConfig,
l2Signer = l2SignerConfig,
messageAnchoring = MessageAnchoringConfigTomlDto().reified(
l1DefaultEndpoint = l1Config.rpcEndpoint,
l2DefaultEndpoint = l2Config.rpcEndpoint,
),
l2NetworkGasPricingService = l2NetworkGasPricingServiceConfig,
l1DynamicGasPriceCapService = l1DynamicGasPriceCapServiceConfig,
proversConfig = proversConfig,
)
}
private data class TestConfig(val extraField: String)
@Test
fun `should keep local stack testing configs uptodate with the code`() {
// Just assert that Files have been loaded and parsed correctly
// This is to prevent Code changes in coordinator and forgetting to update config files used in the local stack
loadConfigs(
coordinatorConfigFiles = listOf(
Path.of("../../config/coordinator/coordinator-docker.config.toml"),
Path.of("../../config/coordinator/coordinator-docker-traces-v2-override.config.toml"),
Path.of("../../config/coordinator/coordinator-docker-web3signer-override.config.toml"),
Path.of("../../config/coordinator/coordinator-local-dev.config.overrides.toml"),
Path.of("../../config/coordinator/coordinator-local-dev.config-traces-v2.overrides.toml"),
),
tracesLimitsFileV2 = Path.of("../../config/common/traces-limits-v2.toml"),
gasPriceCapTimeOfDayMultipliersFile = Path.of("../../config/common/gas-price-cap-time-of-day-multipliers.toml"),
smartContractErrorsFile = Path.of("../../config/common/smart-contract-errors.toml"),
)
}
private fun pathToResource(resource: String): Path {
return Paths.get(
this::class.java.classLoader.getResource(resource)?.toURI()
?: error("Resource not found: $resource"),
)
}
@Test
fun `should parse and consolidate configs`() {
val configs = loadConfigs(
coordinatorConfigFiles = listOf(pathToResource("configs/coordinator.config.toml")),
tracesLimitsFileV2 = pathToResource("configs/traces-limits-v2.toml"),
gasPriceCapTimeOfDayMultipliersFile = pathToResource("configs/gas-price-cap-time-of-day-multipliers.toml"),
smartContractErrorsFile = pathToResource("configs/smart-contract-errors.toml"),
)
assertEquals(coordinatorConfig, configs)
assertEquals(coordinatorConfig.l1.rpcEndpoint, coordinatorConfig.l1.ethFeeHistoryEndpoint)
}
@Test
fun parsesValidWeb3SignerConfigOverride() {
val config = loadConfigs(
coordinatorConfigFiles = listOf(
pathToResource("configs/coordinator.config.toml"),
pathToResource("configs/coordinator-web3signer-override.config.toml"),
),
tracesLimitsFileV2 = pathToResource("configs/traces-limits-v2.toml"),
gasPriceCapTimeOfDayMultipliersFile = pathToResource("configs/gas-price-cap-time-of-day-multipliers.toml"),
smartContractErrorsFile = pathToResource("configs/smart-contract-errors.toml"),
)
val expectedConfig =
coordinatorConfig.copy(
finalizationSigner = finalizationSigner.copy(type = SignerConfig.Type.Web3Signer),
dataSubmissionSigner = dataSubmissionSigner.copy(type = SignerConfig.Type.Web3Signer),
l2Signer = l2SignerConfig.copy(type = SignerConfig.Type.Web3Signer),
)
assertThat(config).isEqualTo(expectedConfig)
}
@Test
fun parsesValidTracesV2ConfigOverride() {
val config = loadConfigs(
coordinatorConfigFiles = listOf(
pathToResource("configs/coordinator.config.toml"),
pathToResource("configs/coordinator-traces-v2-override.config.toml"),
),
tracesLimitsFileV2 = pathToResource("configs/traces-limits-v2.toml"),
gasPriceCapTimeOfDayMultipliersFile = pathToResource("configs/gas-price-cap-time-of-day-multipliers.toml"),
smartContractErrorsFile = pathToResource("configs/smart-contract-errors.toml"),
)
val expectedConfig =
coordinatorConfig.copy(
l2NetworkGasPricingService = l2NetworkGasPricingServiceConfig.copy(
legacy =
l2NetworkGasPricingServiceConfig.legacy.copy(
transactionCostCalculatorConfig =
l2NetworkGasPricingServiceConfig.legacy.transactionCostCalculatorConfig?.copy(
compressedTxSize = 350,
expectedGas = 29400,
),
),
),
traces = tracesConfig.copy(
blobCompressorVersion = BlobCompressorVersion.V1_2,
expectedTracesApiVersionV2 = "v0.8.0-rc8",
conflationV2 = tracesConfig.conflationV2,
countersV2 = tracesConfig.countersV2,
),
proversConfig = proversConfig.copy(
proverA = proversConfig.proverA.copy(
execution = proversConfig.proverA.execution.copy(
requestsDirectory = Path.of("/data/prover/v3/execution/requests"),
responsesDirectory = Path.of("/data/prover/v3/execution/responses"),
),
blobCompression = proversConfig.proverA.blobCompression.copy(
requestsDirectory = Path.of("/data/prover/v3/compression/requests"),
responsesDirectory = Path.of("/data/prover/v3/compression/responses"),
),
proofAggregation = proversConfig.proverA.proofAggregation.copy(
requestsDirectory = Path.of("/data/prover/v3/aggregation/requests"),
responsesDirectory = Path.of("/data/prover/v3/aggregation/responses"),
),
),
),
messageAnchoring = MessageAnchoringConfigTomlDto().copy(
l1Endpoint = URI("http://l1-endpoint-for-anchoring:8545").toURL(),
l2Endpoint = URI("http://l2-endpoint-for-anchoring:8545").toURL(),
l1HighestBlockTag = BlockParameter.Tag.LATEST,
l1EventPollingInterval = 1.seconds.toJavaDuration(),
anchoringTickInterval = 1.seconds.toJavaDuration(),
l1RequestRetries = RequestRetryConfigTomlFriendly(
maxRetries = 10,
failuresWarningThreshold = 1,
),
).reified(
l1DefaultEndpoint = l1Config.rpcEndpoint,
l2DefaultEndpoint = l2Config.rpcEndpoint,
),
)
assertThat(config).isEqualTo(expectedConfig)
}
@Test
fun invalidConfigReturnsErrorResult() {
val configsResult = loadConfigsOrError<TestConfig>(
configFiles = listOf(pathToResource("configs/coordinator.config.toml")),
)
assertThat(configsResult.getError()).contains("'extraField': Missing String from config")
}
@Test
fun testInvalidAggregationByTargetBlockNumberWhenL2InclusiveBlockNumberToStopAndFlushAggregationSpecified() {
val aggregationConfigWithoutTargetBlockNumber = aggregationConfig.copy(
_targetEndBlocks = emptyList(),
)
val conflationConfigWithTargetBlockNumber = conflationConfig.copy(
_conflationTargetEndBlockNumbers = listOf(100L),
)
val exception = assertThrows<IllegalArgumentException> {
coordinatorConfig.copy(
l2InclusiveBlockNumberToStopAndFlushAggregation = 100uL,
proofAggregation = aggregationConfigWithoutTargetBlockNumber,
conflation = conflationConfigWithTargetBlockNumber,
)
}
assertThat(exception.message)
.isEqualTo("proofAggregation.targetEndBlocks should contain the l2InclusiveBlockNumberToStopAndFlushAggregation")
}
@Test
fun testInvalidConflationByTargetBlockNumberWhenL2InclusiveBlockNumberToStopAndFlushAggregationSpecified() {
val aggregationConfigWithTargetBlockNumber = aggregationConfig.copy(
_targetEndBlocks = listOf(100L),
)
val conflationConfigWithoutTargetBlockNumber = conflationConfig.copy(
_conflationTargetEndBlockNumbers = emptyList(),
)
val exception = assertThrows<IllegalArgumentException> {
coordinatorConfig.copy(
l2InclusiveBlockNumberToStopAndFlushAggregation = 100uL,
proofAggregation = aggregationConfigWithTargetBlockNumber,
conflation = conflationConfigWithoutTargetBlockNumber,
)
}
assertThat(exception.message)
.isEqualTo(
"conflation.conflationTargetEndBlockNumbers should contain the " +
"l2InclusiveBlockNumberToStopAndFlushAggregation",
)
}
@Test
fun testValidAggrAndConflationByTargetBlockNumberWhenL2InclusiveBlockNumberToStopAndFlushAggregationSpecified() {
val aggregationConfigWithoutSwithBlockNumber = aggregationConfig.copy(
_targetEndBlocks = listOf(10L, 100L),
)
val conflationConfigWithTargetBlockNumber = conflationConfig.copy(
_conflationTargetEndBlockNumbers = listOf(100L),
)
assertDoesNotThrow {
coordinatorConfig.copy(
l2InclusiveBlockNumberToStopAndFlushAggregation = 100uL,
proofAggregation = aggregationConfigWithoutSwithBlockNumber,
conflation = conflationConfigWithTargetBlockNumber,
)
}
}
}

View File

@@ -1,172 +0,0 @@
package net.consensys.zkevm.coordinator.app.config
val expectedTimeOfDayMultipliers = mapOf(
"SUNDAY_0" to 1.7489178377946066,
"SUNDAY_1" to 1.7494632175198737,
"SUNDAY_2" to 1.75,
"SUNDAY_3" to 1.733166295438555,
"SUNDAY_4" to 1.6993775444542885,
"SUNDAY_5" to 1.6350086618091364,
"SUNDAY_6" to 1.5627740860151331,
"SUNDAY_7" to 1.4831149222064164,
"SUNDAY_8" to 1.4101476768256929,
"SUNDAY_9" to 1.370085278922007,
"SUNDAY_10" to 1.3516015544068651,
"SUNDAY_11" to 1.3482404546676368,
"SUNDAY_12" to 1.3580905751578942,
"SUNDAY_13" to 1.3775497419563296,
"SUNDAY_14" to 1.3700255667542938,
"SUNDAY_15" to 1.2642948506461285,
"SUNDAY_16" to 1.2794806131912935,
"SUNDAY_17" to 1.2750892256476676,
"SUNDAY_18" to 1.2919720208955585,
"SUNDAY_19" to 1.317984990098603,
"SUNDAY_20" to 1.4433501639513178,
"SUNDAY_21" to 1.4705921238901998,
"SUNDAY_22" to 1.515043370430801,
"SUNDAY_23" to 1.5556742617266397,
"MONDAY_0" to 1.5381562278760164,
"MONDAY_1" to 1.5423761828433993,
"MONDAY_2" to 1.539015963719092,
"MONDAY_3" to 1.487676153648977,
"MONDAY_4" to 1.430973985132037,
"MONDAY_5" to 1.4656765439056292,
"MONDAY_6" to 1.4484298622828233,
"MONDAY_7" to 1.4459076216659752,
"MONDAY_8" to 1.4899061835032241,
"MONDAY_9" to 1.5249733712852067,
"MONDAY_10" to 1.511367489481033,
"MONDAY_11" to 1.4225695658047797,
"MONDAY_12" to 1.2887291896624584,
"MONDAY_13" to 1.1460926897291355,
"MONDAY_14" to 1.0004897955233254,
"MONDAY_15" to 0.8694664537368378,
"MONDAY_16" to 0.8270273375962802,
"MONDAY_17" to 0.7868289022833883,
"MONDAY_18" to 0.7780303121746551,
"MONDAY_19" to 0.7756215256634205,
"MONDAY_20" to 0.7984895728860915,
"MONDAY_21" to 0.8918589268832423,
"MONDAY_22" to 0.9967716668541272,
"MONDAY_23" to 1.0973334887144106,
"TUESDAY_0" to 1.2233064209957951,
"TUESDAY_1" to 1.3238883432855082,
"TUESDAY_2" to 1.3874518307497257,
"TUESDAY_3" to 1.463621147171298,
"TUESDAY_4" to 1.4975989065490154,
"TUESDAY_5" to 1.481679186141442,
"TUESDAY_6" to 1.452778387763161,
"TUESDAY_7" to 1.3414858185569951,
"TUESDAY_8" to 1.2869454637983988,
"TUESDAY_9" to 1.249347290389873,
"TUESDAY_10" to 1.196488297386161,
"TUESDAY_11" to 1.1136140507034202,
"TUESDAY_12" to 0.9867528660797885,
"TUESDAY_13" to 0.8018989158195754,
"TUESDAY_14" to 0.6173048748109258,
"TUESDAY_15" to 0.46718586671750373,
"TUESDAY_16" to 0.4103633833041902,
"TUESDAY_17" to 0.4871260756989506,
"TUESDAY_18" to 0.5667378483016126,
"TUESDAY_19" to 0.6464203510900723,
"TUESDAY_20" to 0.7780268325299871,
"TUESDAY_21" to 0.8995921101255763,
"TUESDAY_22" to 1.0077600114996088,
"TUESDAY_23" to 1.1109769960680498,
"WEDNESDAY_0" to 1.2097668746150059,
"WEDNESDAY_1" to 1.2631002319009361,
"WEDNESDAY_2" to 1.2912775191940549,
"WEDNESDAY_3" to 1.3229785939630059,
"WEDNESDAY_4" to 1.3428607301494424,
"WEDNESDAY_5" to 1.3750788517823973,
"WEDNESDAY_6" to 1.3752344527256497,
"WEDNESDAY_7" to 1.3505490078766218,
"WEDNESDAY_8" to 1.2598503219367945,
"WEDNESDAY_9" to 1.2051668977452374,
"WEDNESDAY_10" to 1.0320896222195326,
"WEDNESDAY_11" to 0.8900138031631949,
"WEDNESDAY_12" to 0.6341155208698448,
"WEDNESDAY_13" to 0.48337590254714624,
"WEDNESDAY_14" to 0.2903189399226416,
"WEDNESDAY_15" to 0.25,
"WEDNESDAY_16" to 0.25711039485046006,
"WEDNESDAY_17" to 0.37307641907591793,
"WEDNESDAY_18" to 0.45280799454961196,
"WEDNESDAY_19" to 0.5631397823847637,
"WEDNESDAY_20" to 0.6285005244224133,
"WEDNESDAY_21" to 0.6671897537279405,
"WEDNESDAY_22" to 0.7268406397452634,
"WEDNESDAY_23" to 0.8068904097486369,
"THURSDAY_0" to 0.9021601102971811,
"THURSDAY_1" to 1.023741688964238,
"THURSDAY_2" to 1.1340689935096755,
"THURSDAY_3" to 1.2530130345819006,
"THURSDAY_4" to 1.3163421664973542,
"THURSDAY_5" to 1.3536343767230727,
"THURSDAY_6" to 1.3432290485306728,
"THURSDAY_7" to 1.2864983218982178,
"THURSDAY_8" to 1.2320488534113174,
"THURSDAY_9" to 1.1984530721079034,
"THURSDAY_10" to 1.0877338251341975,
"THURSDAY_11" to 0.9999324929016475,
"THURSDAY_12" to 0.87536726762619,
"THURSDAY_13" to 0.6560822412167919,
"THURSDAY_14" to 0.44836474861432074,
"THURSDAY_15" to 0.36145134935025247,
"THURSDAY_16" to 0.2695997829759713,
"THURSDAY_17" to 0.2898426312618241,
"THURSDAY_18" to 0.3970093434340387,
"THURSDAY_19" to 0.5193273246848977,
"THURSDAY_20" to 0.6426415257034419,
"THURSDAY_21" to 0.800685718218497,
"THURSDAY_22" to 0.9215516833839711,
"THURSDAY_23" to 1.053701659160912,
"FRIDAY_0" to 1.149649788723893,
"FRIDAY_1" to 1.2046315447861193,
"FRIDAY_2" to 1.2724031281576726,
"FRIDAY_3" to 1.3525693456352732,
"FRIDAY_4" to 1.3746126314960814,
"FRIDAY_5" to 1.3744591862592468,
"FRIDAY_6" to 1.3297812543035683,
"FRIDAY_7" to 1.2762064429631657,
"FRIDAY_8" to 1.235662409263294,
"FRIDAY_9" to 1.2171558028785991,
"FRIDAY_10" to 1.182722399785398,
"FRIDAY_11" to 1.137345538963285,
"FRIDAY_12" to 0.9999308422620752,
"FRIDAY_13" to 0.8055000309055653,
"FRIDAY_14" to 0.5667135273493851,
"FRIDAY_15" to 0.4081529603000651,
"FRIDAY_16" to 0.3987031354907009,
"FRIDAY_17" to 0.5030075499003412,
"FRIDAY_18" to 0.6518159532641841,
"FRIDAY_19" to 0.8733483414970974,
"FRIDAY_20" to 1.0496224913080463,
"FRIDAY_21" to 1.1820684558591705,
"FRIDAY_22" to 1.2561688567574458,
"FRIDAY_23" to 1.3204704912328773,
"SATURDAY_0" to 1.3832230236620218,
"SATURDAY_1" to 1.4632908341022142,
"SATURDAY_2" to 1.5019230781315296,
"SATURDAY_3" to 1.5437332506007084,
"SATURDAY_4" to 1.5934153179751855,
"SATURDAY_5" to 1.6245578072557723,
"SATURDAY_6" to 1.6294919789890665,
"SATURDAY_7" to 1.6027665451672717,
"SATURDAY_8" to 1.6068061069158674,
"SATURDAY_9" to 1.624257927970777,
"SATURDAY_10" to 1.5996112411089,
"SATURDAY_11" to 1.5659672993092648,
"SATURDAY_12" to 1.5333537902522736,
"SATURDAY_13" to 1.445292929996356,
"SATURDAY_14" to 1.2966021477035259,
"SATURDAY_15" to 1.250999408961155,
"SATURDAY_16" to 1.2535364828163025,
"SATURDAY_17" to 1.2736456128871074,
"SATURDAY_18" to 1.3348268054897328,
"SATURDAY_19" to 1.4571388900094875,
"SATURDAY_20" to 1.5073787902995706,
"SATURDAY_21" to 1.5605139580010123,
"SATURDAY_22" to 1.5885303316932382,
"SATURDAY_23" to 1.6169891066719597,
)

View File

@@ -1,60 +0,0 @@
package net.consensys.zkevm.coordinator.app.config
import net.consensys.linea.traces.TracesCountersV2
import net.consensys.linea.traces.TracingModuleV2
val expectedTracesLimitsV2 = TracesCountersV2(
mapOf(
TracingModuleV2.ADD to 1u,
TracingModuleV2.BIN to 2u,
TracingModuleV2.BLAKE_MODEXP_DATA to 3u,
TracingModuleV2.BLOCK_DATA to 4u,
TracingModuleV2.BLOCK_HASH to 5u,
TracingModuleV2.EC_DATA to 6u,
TracingModuleV2.EUC to 7u,
TracingModuleV2.EXP to 8u,
TracingModuleV2.EXT to 9u,
TracingModuleV2.GAS to 10u,
TracingModuleV2.HUB to 11u,
TracingModuleV2.LOG_DATA to 12u,
TracingModuleV2.LOG_INFO to 13u,
TracingModuleV2.MMIO to 14u,
TracingModuleV2.MMU to 15u,
TracingModuleV2.MOD to 16u,
TracingModuleV2.MUL to 18u,
TracingModuleV2.MXP to 19u,
TracingModuleV2.OOB to 20u,
TracingModuleV2.RLP_ADDR to 21u,
TracingModuleV2.RLP_TXN to 22u,
TracingModuleV2.RLP_TXN_RCPT to 23u,
TracingModuleV2.ROM to 24u,
TracingModuleV2.ROM_LEX to 25u,
TracingModuleV2.SHAKIRA_DATA to 26u,
TracingModuleV2.SHF to 27u,
TracingModuleV2.STP to 28u,
TracingModuleV2.TRM to 29u,
TracingModuleV2.TXN_DATA to 30u,
TracingModuleV2.WCP to 31u,
// Reference table limits, set to UInt.MAX_VALUE
TracingModuleV2.BIN_REFERENCE_TABLE to 32u,
TracingModuleV2.INSTRUCTION_DECODER to 33u,
TracingModuleV2.SHF_REFERENCE_TABLE to 34u,
// Precompiles limits
TracingModuleV2.PRECOMPILE_BLAKE_EFFECTIVE_CALLS to 35u,
TracingModuleV2.PRECOMPILE_BLAKE_ROUNDS to 36u,
TracingModuleV2.PRECOMPILE_ECADD_EFFECTIVE_CALLS to 37u,
TracingModuleV2.PRECOMPILE_ECMUL_EFFECTIVE_CALLS to 38u,
TracingModuleV2.PRECOMPILE_ECPAIRING_FINAL_EXPONENTIATIONS to 39u,
TracingModuleV2.PRECOMPILE_ECPAIRING_G2_MEMBERSHIP_CALLS to 40u,
TracingModuleV2.PRECOMPILE_ECPAIRING_MILLER_LOOPS to 41u,
TracingModuleV2.PRECOMPILE_ECRECOVER_EFFECTIVE_CALLS to 42u,
TracingModuleV2.PRECOMPILE_MODEXP_EFFECTIVE_CALLS to 43u,
TracingModuleV2.PRECOMPILE_RIPEMD_BLOCKS to 44u,
TracingModuleV2.PRECOMPILE_SHA2_BLOCKS to 45u,
// Block limits
TracingModuleV2.BLOCK_KECCAK to 46u,
TracingModuleV2.BLOCK_L1_SIZE to 47u,
TracingModuleV2.BLOCK_L2_L1_LOGS to 48u,
TracingModuleV2.BLOCK_TRANSACTIONS to 49u,
),
)

View File

@@ -1,548 +0,0 @@
package net.consensys.zkevm.coordinator.app.config
import com.sksamuel.hoplite.ConfigLoaderBuilder
import com.sksamuel.hoplite.toml.TomlPropertySource
import net.consensys.linea.ethereum.gaspricing.BoundableFeeCalculator
import net.consensys.linea.ethereum.gaspricing.staticcap.ExtraDataV1UpdaterImpl
import net.consensys.linea.ethereum.gaspricing.staticcap.FeeHistoryFetcherImpl
import net.consensys.linea.ethereum.gaspricing.staticcap.GasPriceUpdaterImpl
import net.consensys.linea.ethereum.gaspricing.staticcap.GasUsageRatioWeightedAverageFeesCalculator
import net.consensys.linea.ethereum.gaspricing.staticcap.MinerExtraDataV1CalculatorImpl
import net.consensys.linea.ethereum.gaspricing.staticcap.TransactionCostCalculator
import net.consensys.linea.ethereum.gaspricing.staticcap.VariableFeesCalculator
import net.consensys.linea.jsonrpc.client.RequestRetryConfig
import net.consensys.zkevm.coordinator.app.L2NetworkGasPricingService
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import java.net.URI
import java.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toJavaDuration
class L2NetworkGasPricingConfigTest {
data class Config(
val l2NetworkGasPricing: L2NetworkGasPricingTomlDto,
)
private fun parseConfig(toml: String): L2NetworkGasPricingTomlDto {
return ConfigLoaderBuilder
.default()
.addSource(TomlPropertySource(toml))
.build()
.loadConfigOrThrow<Config>().l2NetworkGasPricing
}
private val naiveL2NetworkGasPricingServiceConfigToml = """
[l2-network-gas-pricing]
disabled = false
price-update-interval = "PT12S"
fee-history-block-count = 50
fee-history-reward-percentile = 15
blob-submission-expected-execution-gas = 213000.0 # Lower to 120k as we improve efficiency
# Defaults to expected-blob-gas
#bytes-per-data-submission=131072.0 # 2^17
l1-blob-gas = 131072 # 2^17
[l2-network-gas-pricing.request-retry]
max-retries = 3
timeout = "PT6S"
backoff-delay = "PT1S"
failures-warning-threshold = 2
[l2-network-gas-pricing.variable-cost-pricing]
gas-price-fixed-cost = 3000000
legacy-fees-multiplier = 1.2
margin = 4.0
variable-cost-upper-bound = 10000000001 # ~10 GWEI
variable-cost-lower-bound = 90000001 # ~0.09 GWEI
[l2-network-gas-pricing.extra-data-pricing-propagation]
extra-data-update-recipient = "http://sequencer:8545/"
[l2-network-gas-pricing.legacy]
type="Naive"
gas-price-upper-bound = 10000000000 # 10 GWEI
gas-price-lower-bound = 90000000 # 0.09 GWEI
[l2-network-gas-pricing.legacy.naive-gas-pricing]
base-fee-coefficient = 0.1
priority-fee-coefficient = 1.0
base-fee-blob-coefficient = 0.1
[l2-network-gas-pricing.json-rpc-pricing-propagation]
geth-gas-price-update-recipients = [
"http://traces-node:8545/",
"http://l2-node:8545/"
]
besu-gas-price-update-recipients = [
"http://sequencer:8545/"
]
""".trimIndent()
private val sampleTransactionL2NetworkGasPricingServiceConfigToml = """
[l2-network-gas-pricing]
disabled = false
price-update-interval = "PT12S"
fee-history-block-count = 50
fee-history-reward-percentile = 15
blob-submission-expected-execution-gas = 213000.0 # Lower to 120k as we improve efficiency
# Defaults to expected-blob-gas
#bytes-per-data-submission=131072.0 # 2^17
l1-blob-gas = 131072 # 2^17
[l2-network-gas-pricing.request-retry]
max-retries = 3
timeout = "PT6S"
backoff-delay = "PT1S"
failures-warning-threshold = 2
[l2-network-gas-pricing.variable-cost-pricing]
gas-price-fixed-cost = 3000000
legacy-fees-multiplier = 1.2
margin = 4.0
variable-cost-upper-bound = 10000000001 # ~10 GWEI
variable-cost-lower-bound = 90000001 # ~0.09 GWEI
[l2-network-gas-pricing.extra-data-pricing-propagation]
extra-data-update-recipient = "http://sequencer:8545/"
[l2-network-gas-pricing.legacy]
type="SampleTransaction"
gas-price-upper-bound = 10000000000 # 10 GWEI
gas-price-lower-bound = 90000000 # 0.09 GWEI
[l2-network-gas-pricing.json-rpc-pricing-propagation]
geth-gas-price-update-recipients = [
"http://traces-node:8545/",
"http://l2-node:8545/"
]
besu-gas-price-update-recipients = [
"http://sequencer:8545/"
]
""".trimIndent()
@Test
fun `dto with naive legacy gas calculator is parseable`() {
val config = parseConfig(naiveL2NetworkGasPricingServiceConfigToml)
assertThat(config).isEqualTo(
L2NetworkGasPricingTomlDto(
requestRetry = RequestRetryConfigTomlFriendly(
maxRetries = 3,
timeout = 6.seconds.toJavaDuration(),
backoffDelay = 1.seconds.toJavaDuration(),
failuresWarningThreshold = 2,
),
priceUpdateInterval = Duration.parse("PT12S"),
feeHistoryBlockCount = 50,
feeHistoryRewardPercentile = 15.0,
blobSubmissionExpectedExecutionGas = 213_000,
_bytesPerDataSubmission = null,
l1BlobGas = 131072,
legacy = LegacyGasPricingTomlDto(
type = LegacyGasPricingTomlDto.Type.Naive,
gasPriceUpperBound = 10_000_000_000u,
gasPriceLowerBound = 90_000_000u,
naiveGasPricing = NaiveGasPricingTomlDto(
baseFeeCoefficient = 0.1,
priorityFeeCoefficient = 1.0,
baseFeeBlobCoefficient = 0.1,
),
),
variableCostPricing = VariableCostPricingTomlDto(
gasPriceFixedCost = 3000000u,
legacyFeesMultiplier = 1.2,
margin = 4.0,
variableCostUpperBound = 10_000_000_001u,
variableCostLowerBound = 90_000_001u,
),
jsonRpcPricingPropagation = JsonRpcPricingPropagationTomlDto(
gethGasPriceUpdateRecipients = listOf(
URI("http://traces-node:8545/").toURL(),
URI("http://l2-node:8545/").toURL(),
),
besuGasPriceUpdateRecipients = listOf(
URI("http://sequencer:8545/").toURL(),
),
),
extraDataPricingPropagation = ExtraDataPricingPropagationTomlDto(
extraDataUpdateRecipient = URI("http://sequencer:8545/").toURL(),
),
),
)
}
@Test
fun `dto with sample transaction legacy gas calculator is parseable`() {
val config = parseConfig(sampleTransactionL2NetworkGasPricingServiceConfigToml)
assertThat(config).isEqualTo(
L2NetworkGasPricingTomlDto(
requestRetry = RequestRetryConfigTomlFriendly(
maxRetries = 3,
timeout = 6.seconds.toJavaDuration(),
backoffDelay = 1.seconds.toJavaDuration(),
failuresWarningThreshold = 2,
),
priceUpdateInterval = Duration.parse("PT12S"),
feeHistoryBlockCount = 50,
feeHistoryRewardPercentile = 15.0,
blobSubmissionExpectedExecutionGas = 213_000,
_bytesPerDataSubmission = null,
l1BlobGas = 131072,
legacy = LegacyGasPricingTomlDto(
type = LegacyGasPricingTomlDto.Type.SampleTransaction,
gasPriceUpperBound = 10_000_000_000u,
gasPriceLowerBound = 90_000_000u,
naiveGasPricing = null,
),
variableCostPricing = VariableCostPricingTomlDto(
gasPriceFixedCost = 3000000u,
legacyFeesMultiplier = 1.2,
margin = 4.0,
variableCostUpperBound = 10_000_000_001u,
variableCostLowerBound = 90_000_001u,
),
jsonRpcPricingPropagation = JsonRpcPricingPropagationTomlDto(
gethGasPriceUpdateRecipients = listOf(
URI("http://traces-node:8545/").toURL(),
URI("http://l2-node:8545/").toURL(),
),
besuGasPriceUpdateRecipients = listOf(
URI("http://sequencer:8545/").toURL(),
),
),
extraDataPricingPropagation = ExtraDataPricingPropagationTomlDto(
extraDataUpdateRecipient = URI("http://sequencer:8545/").toURL(),
),
),
)
}
@Test
fun `reification is correct`() {
val config = parseConfig(naiveL2NetworkGasPricingServiceConfigToml).reified()
val l2NetworkGasPricingRequestretryConfig = RequestRetryConfig(
maxRetries = 3u,
timeout = 6.seconds,
backoffDelay = 1.seconds,
failuresWarningThreshold = 2u,
)
assertThat(config).isEqualTo(
L2NetworkGasPricingService.Config(
feeHistoryFetcherConfig = FeeHistoryFetcherImpl.Config(
feeHistoryBlockCount = 50U,
feeHistoryRewardPercentile = 15.0,
),
legacy = L2NetworkGasPricingService.LegacyGasPricingCalculatorConfig(
naiveGasPricingCalculatorConfig = GasUsageRatioWeightedAverageFeesCalculator.Config(
baseFeeCoefficient = 0.1,
priorityFeeCoefficient = 1.0,
baseFeeBlobCoefficient = 0.1,
blobSubmissionExpectedExecutionGas = 213_000,
expectedBlobGas = 131072,
),
legacyGasPricingCalculatorBounds = BoundableFeeCalculator.Config(
10_000_000_000.0,
90_000_000.0,
0.0,
),
transactionCostCalculatorConfig = null,
),
jsonRpcGasPriceUpdaterConfig = GasPriceUpdaterImpl.Config(
gethEndpoints = listOf(
URI("http://traces-node:8545/").toURL(),
URI("http://l2-node:8545/").toURL(),
),
besuEndPoints = listOf(
URI("http://sequencer:8545/").toURL(),
),
retryConfig = l2NetworkGasPricingRequestretryConfig,
),
jsonRpcPriceUpdateInterval = 12.seconds,
extraDataPricingPropagationEnabled = true,
extraDataUpdateInterval = 12.seconds,
variableFeesCalculatorConfig = VariableFeesCalculator.Config(
blobSubmissionExpectedExecutionGas = 213_000u,
bytesPerDataSubmission = 131072u,
expectedBlobGas = 131072u,
margin = 4.0,
),
variableFeesCalculatorBounds = BoundableFeeCalculator.Config(
feeUpperBound = 10_000_000_001.0,
feeLowerBound = 90_000_001.0,
feeMargin = 0.0,
),
extraDataCalculatorConfig = MinerExtraDataV1CalculatorImpl.Config(
fixedCostInKWei = 3000u,
ethGasPriceMultiplier = 1.2,
),
extraDataUpdaterConfig = ExtraDataV1UpdaterImpl.Config(
sequencerEndpoint = URI("http://sequencer:8545/").toURL(),
retryConfig = l2NetworkGasPricingRequestretryConfig,
),
),
)
}
@Test
fun `Sample transaction reification is correct`() {
val config = parseConfig(sampleTransactionL2NetworkGasPricingServiceConfigToml).reified()
val l2NetworkGasPricingRequestretryConfig = RequestRetryConfig(
maxRetries = 3u,
timeout = 6.seconds,
backoffDelay = 1.seconds,
failuresWarningThreshold = 2u,
)
assertThat(config).isEqualTo(
L2NetworkGasPricingService.Config(
feeHistoryFetcherConfig = FeeHistoryFetcherImpl.Config(
feeHistoryBlockCount = 50U,
feeHistoryRewardPercentile = 15.0,
),
legacy = L2NetworkGasPricingService.LegacyGasPricingCalculatorConfig(
naiveGasPricingCalculatorConfig = null,
legacyGasPricingCalculatorBounds = BoundableFeeCalculator.Config(
10_000_000_000.0,
90_000_000.0,
0.0,
),
transactionCostCalculatorConfig = TransactionCostCalculator.Config(
sampleTransactionCostMultiplier = 1.0,
fixedCostWei = 3000000u,
compressedTxSize = 125,
expectedGas = 21000,
),
),
jsonRpcGasPriceUpdaterConfig = GasPriceUpdaterImpl.Config(
gethEndpoints = listOf(
URI("http://traces-node:8545/").toURL(),
URI("http://l2-node:8545/").toURL(),
),
besuEndPoints = listOf(
URI("http://sequencer:8545/").toURL(),
),
retryConfig = l2NetworkGasPricingRequestretryConfig,
),
jsonRpcPriceUpdateInterval = 12.seconds,
extraDataPricingPropagationEnabled = true,
extraDataUpdateInterval = 12.seconds,
variableFeesCalculatorConfig = VariableFeesCalculator.Config(
blobSubmissionExpectedExecutionGas = 213_000u,
bytesPerDataSubmission = 131072u,
expectedBlobGas = 131072u,
margin = 4.0,
),
variableFeesCalculatorBounds = BoundableFeeCalculator.Config(
feeUpperBound = 10_000_000_001.0,
feeLowerBound = 90_000_001.0,
feeMargin = 0.0,
),
extraDataCalculatorConfig = MinerExtraDataV1CalculatorImpl.Config(
fixedCostInKWei = 3000u,
ethGasPriceMultiplier = 1.2,
),
extraDataUpdaterConfig = ExtraDataV1UpdaterImpl.Config(
sequencerEndpoint = URI("http://sequencer:8545/").toURL(),
retryConfig = l2NetworkGasPricingRequestretryConfig,
),
),
)
}
@Test
fun `Undefined json rpc pricing propagation reification is correct`() {
val undefinedJsonRpcPropagation = """
[l2-network-gas-pricing]
disabled = false
price-update-interval = "PT12S"
fee-history-block-count = 50
fee-history-reward-percentile = 15
blob-submission-expected-execution-gas = 213000.0 # Lower to 120k as we improve efficiency
# Defaults to expected-blob-gas
#bytes-per-data-submission=131072.0 # 2^17
l1-blob-gas = 131072 # 2^17
[l2-network-gas-pricing.request-retry]
max-retries = 3
timeout = "PT6S"
backoff-delay = "PT1S"
failures-warning-threshold = 2
[l2-network-gas-pricing.variable-cost-pricing]
gas-price-fixed-cost = 3000000
legacy-fees-multiplier = 1.2
margin = 4.0
variable-cost-upper-bound = 10000000001 # ~10 GWEI
variable-cost-lower-bound = 90000001 # ~0.09 GWEI
[l2-network-gas-pricing.extra-data-pricing-propagation]
extra-data-update-recipient = "http://sequencer:8545/"
[l2-network-gas-pricing.legacy]
type="SampleTransaction"
gas-price-upper-bound = 10000000000 # 10 GWEI
gas-price-lower-bound = 90000000 # 0.09 GWEI
""".trimIndent()
val config = parseConfig(undefinedJsonRpcPropagation).reified()
val l2NetworkGasPricingRequestretryConfig = RequestRetryConfig(
maxRetries = 3u,
timeout = 6.seconds,
backoffDelay = 1.seconds,
failuresWarningThreshold = 2u,
)
assertThat(config).isEqualTo(
L2NetworkGasPricingService.Config(
feeHistoryFetcherConfig = FeeHistoryFetcherImpl.Config(
feeHistoryBlockCount = 50U,
feeHistoryRewardPercentile = 15.0,
),
legacy = L2NetworkGasPricingService.LegacyGasPricingCalculatorConfig(
naiveGasPricingCalculatorConfig = null,
legacyGasPricingCalculatorBounds = BoundableFeeCalculator.Config(
10_000_000_000.0,
90_000_000.0,
0.0,
),
transactionCostCalculatorConfig = TransactionCostCalculator.Config(
sampleTransactionCostMultiplier = 1.0,
fixedCostWei = 3000000u,
compressedTxSize = 125,
expectedGas = 21000,
),
),
jsonRpcGasPriceUpdaterConfig = null,
jsonRpcPriceUpdateInterval = 12.seconds,
extraDataPricingPropagationEnabled = true,
extraDataUpdateInterval = 12.seconds,
variableFeesCalculatorConfig = VariableFeesCalculator.Config(
blobSubmissionExpectedExecutionGas = 213_000u,
bytesPerDataSubmission = 131072u,
expectedBlobGas = 131072u,
margin = 4.0,
),
variableFeesCalculatorBounds = BoundableFeeCalculator.Config(
feeUpperBound = 10_000_000_001.0,
feeLowerBound = 90_000_001.0,
feeMargin = 0.0,
),
extraDataCalculatorConfig = MinerExtraDataV1CalculatorImpl.Config(
fixedCostInKWei = 3000u,
ethGasPriceMultiplier = 1.2,
),
extraDataUpdaterConfig = ExtraDataV1UpdaterImpl.Config(
sequencerEndpoint = URI("http://sequencer:8545/").toURL(),
retryConfig = l2NetworkGasPricingRequestretryConfig,
),
),
)
}
@Test
fun `Json rpc pricing propagation can be disabled without complete removal`() {
val disabledJsonRpcPricingPropagation = """
[l2-network-gas-pricing]
disabled = false
price-update-interval = "PT12S"
fee-history-block-count = 50
fee-history-reward-percentile = 15
blob-submission-expected-execution-gas = 213000.0 # Lower to 120k as we improve efficiency
# Defaults to expected-blob-gas
#bytes-per-data-submission=131072.0 # 2^17
l1-blob-gas = 131072 # 2^17
[l2-network-gas-pricing.request-retry]
max-retries = 3
timeout = "PT6S"
backoff-delay = "PT1S"
failures-warning-threshold = 2
[l2-network-gas-pricing.variable-cost-pricing]
gas-price-fixed-cost = 3000000
legacy-fees-multiplier = 1.2
margin = 4.0
variable-cost-upper-bound = 10000000001 # ~10 GWEI
variable-cost-lower-bound = 90000001 # ~0.09 GWEI
[l2-network-gas-pricing.extra-data-pricing-propagation]
extra-data-update-recipient = "http://sequencer:8545/"
[l2-network-gas-pricing.legacy]
type="SampleTransaction"
gas-price-upper-bound = 10000000000 # 10 GWEI
gas-price-lower-bound = 90000000 # 0.09 GWEI
[l2-network-gas-pricing.json-rpc-pricing-propagation]
disabled = true
geth-gas-price-update-recipients = []
besu-gas-price-update-recipients = []
""".trimIndent()
val config = parseConfig(disabledJsonRpcPricingPropagation).reified()
val l2NetworkGasPricingRequestretryConfig = RequestRetryConfig(
maxRetries = 3u,
timeout = 6.seconds,
backoffDelay = 1.seconds,
failuresWarningThreshold = 2u,
)
assertThat(config).isEqualTo(
L2NetworkGasPricingService.Config(
feeHistoryFetcherConfig = FeeHistoryFetcherImpl.Config(
feeHistoryBlockCount = 50U,
feeHistoryRewardPercentile = 15.0,
),
legacy = L2NetworkGasPricingService.LegacyGasPricingCalculatorConfig(
naiveGasPricingCalculatorConfig = null,
legacyGasPricingCalculatorBounds = BoundableFeeCalculator.Config(
10_000_000_000.0,
90_000_000.0,
0.0,
),
transactionCostCalculatorConfig = TransactionCostCalculator.Config(
sampleTransactionCostMultiplier = 1.0,
fixedCostWei = 3000000u,
compressedTxSize = 125,
expectedGas = 21000,
),
),
jsonRpcGasPriceUpdaterConfig = null,
jsonRpcPriceUpdateInterval = 12.seconds,
extraDataPricingPropagationEnabled = true,
extraDataUpdateInterval = 12.seconds,
variableFeesCalculatorConfig = VariableFeesCalculator.Config(
blobSubmissionExpectedExecutionGas = 213_000u,
bytesPerDataSubmission = 131072u,
expectedBlobGas = 131072u,
margin = 4.0,
),
variableFeesCalculatorBounds = BoundableFeeCalculator.Config(
feeUpperBound = 10_000_000_001.0,
feeLowerBound = 90_000_001.0,
feeMargin = 0.0,
),
extraDataCalculatorConfig = MinerExtraDataV1CalculatorImpl.Config(
fixedCostInKWei = 3000u,
ethGasPriceMultiplier = 1.2,
),
extraDataUpdaterConfig = ExtraDataV1UpdaterImpl.Config(
sequencerEndpoint = URI("http://sequencer:8545/").toURL(),
retryConfig = l2NetworkGasPricingRequestretryConfig,
),
),
)
}
}

View File

@@ -1,132 +0,0 @@
package net.consensys.zkevm.coordinator.app.config
import com.sksamuel.hoplite.ConfigLoaderBuilder
import com.sksamuel.hoplite.toml.TomlPropertySource
import linea.domain.BlockParameter
import linea.domain.RetryConfig
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import java.net.URI
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
class MessageAnchoringConfigTest {
private val l1DefaultEndpoint = URI("http://l1-default-rpc-endpoint:8545").toURL()
private val l2DefaultEndpoint = URI("http://l2-default-rpc-endpoint:8545").toURL()
data class Config(
val messageAnchoring: MessageAnchoringConfigTomlDto = MessageAnchoringConfigTomlDto(),
)
private fun parseConfig(toml: String): MessageAnchoringConfig {
return ConfigLoaderBuilder
.default()
.addDecoder(BlockParameterDecoder())
.addSource(TomlPropertySource(toml))
.build()
.loadConfigOrThrow<Config>()
.let {
it.messageAnchoring.reified(
l1DefaultEndpoint = l1DefaultEndpoint,
l2DefaultEndpoint = l2DefaultEndpoint,
)
}
}
@Test
fun `should parse message anchoroing full config`() {
val toml = """
[message-anchoring]
disabled = true
l1-endpoint = "http://l1-rpc-endpoint:8545"
l2-endpoint = "http://l2-rpc-endpoint:8545"
l1-highest-block-tag="FINALIZED"
l2-highest-block-tag="LATEST"
l1-event-polling-interval="PT30S"
l1-event-polling-timeout="PT6S"
l1-success-backoff-delay="PT0.1s"
l1-event-search-block-chunk=123
message-anchoring-chunck-size=123
anchoring-tick-interval="PT3S"
message-queue-capacity=321
maxMessagesToAnchorPerL2Transaction=54
[message-anchoring.l1-request-retries]
max-retries = 10
timeout = "PT100S"
backoff-delay = "PT11S"
failures-warning-threshold = 1
[message-anchoring.l2-request-retries]
max-retries = 20
timeout = "PT200S"
backoff-delay = "PT21S"
failures-warning-threshold = 2
""".trimIndent()
assertThat(parseConfig(toml))
.isEqualTo(
MessageAnchoringConfig(
disabled = true,
l1Endpoint = URI("http://l1-rpc-endpoint:8545").toURL(),
l2Endpoint = URI("http://l2-rpc-endpoint:8545").toURL(),
l1HighestBlockTag = BlockParameter.Tag.FINALIZED,
l2HighestBlockTag = BlockParameter.Tag.LATEST,
l1RequestRetryConfig = RetryConfig(
maxRetries = 10u,
timeout = 100.seconds,
backoffDelay = 11.seconds,
failuresWarningThreshold = 1u,
),
l2RequestRetryConfig = RetryConfig(
maxRetries = 20u,
timeout = 200.seconds,
backoffDelay = 21.seconds,
failuresWarningThreshold = 2u,
),
l1EventPollingInterval = 30.seconds,
l1EventPollingTimeout = 6.seconds,
l1SuccessBackoffDelay = 100.milliseconds,
l1EventSearchBlockChunk = 123u,
anchoringTickInterval = 3.seconds,
messageQueueCapacity = 321u,
maxMessagesToAnchorPerL2Transaction = 54u,
),
)
}
@Test
fun `should parse message anchoroing with defaults`() {
val toml = """
# Nothing configured to return defaults
""".trimIndent()
assertThat(parseConfig(toml))
.isEqualTo(
MessageAnchoringConfig(
disabled = false,
l1Endpoint = URI("http://l1-default-rpc-endpoint:8545").toURL(),
l2Endpoint = URI("http://l2-default-rpc-endpoint:8545").toURL(),
l1HighestBlockTag = BlockParameter.Tag.FINALIZED,
l2HighestBlockTag = BlockParameter.Tag.LATEST,
l1RequestRetryConfig = RetryConfig(
maxRetries = null,
timeout = null,
backoffDelay = 1.seconds,
failuresWarningThreshold = 3u,
),
l2RequestRetryConfig = RetryConfig(
maxRetries = null,
timeout = null,
backoffDelay = 1.seconds,
failuresWarningThreshold = 3u,
),
l1EventPollingInterval = 12.seconds,
l1EventPollingTimeout = 6.seconds,
l1SuccessBackoffDelay = 1.milliseconds,
l1EventSearchBlockChunk = 1000u,
anchoringTickInterval = 2.seconds,
messageQueueCapacity = 10_000u,
maxMessagesToAnchorPerL2Transaction = 100u,
),
)
}
}

View File

@@ -1,149 +0,0 @@
package net.consensys.zkevm.coordinator.app.config
import com.sksamuel.hoplite.ConfigLoaderBuilder
import com.sksamuel.hoplite.toml.TomlPropertySource
import net.consensys.zkevm.coordinator.clients.prover.FileBasedProverConfig
import net.consensys.zkevm.coordinator.clients.prover.ProverConfig
import net.consensys.zkevm.coordinator.clients.prover.ProversConfig
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import java.nio.file.Path
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
class ProverConfigTest {
data class Config(
val prover: ProverConfigTomlDto,
)
private fun parseConfig(toml: String): ProversConfig {
return ConfigLoaderBuilder
.default()
.addSource(TomlPropertySource(toml))
.build()
.loadConfigOrThrow<Config>()
.let { it.prover.reified() }
}
val proverAConfigToml = """
[prover]
fs-inprogress-request-writing-suffix = ".inprogress_coordinator_writing"
fs-inprogress-proving-suffix-pattern = "\\.inprogress\\.prover.*"
fs-polling-interval = "PT10S"
fs-polling-timeout = "PT10M"
[prover.execution]
fs-requests-directory = "/data/prover/execution/requests"
fs-responses-directory = "/data/prover/execution/responses"
fs-inprogress-request-writing-suffix = ".OVERRIDE_inprogress_coordinator_writing"
fs-inprogress-proving-suffix-pattern = "OVERRIDE_\\.inprogress\\.prover.*"
[prover.blob-compression]
fs-requests-directory = "/data/prover/compression/requests"
fs-responses-directory = "/data/prover/compression/responses"
fs-polling-interval = "PT20S"
fs-polling-timeout = "PT20M"
[prover.proof-aggregation]
fs-requests-directory = "/data/prover/aggregation/requests"
fs-responses-directory = "/data/prover/aggregation/responses"
""".trimIndent()
@Test
fun `should load configs with single prover and overrids`() {
val config = parseConfig(proverAConfigToml)
assertThat(config.switchBlockNumberInclusive).isNull()
assertProverAConfig(config.proverA)
assertThat(config.proverB).isNull()
}
fun assertProverAConfig(config: ProverConfig) {
assertThat(config.execution).isEqualTo(
FileBasedProverConfig(
requestsDirectory = Path.of("/data/prover/execution/requests"),
responsesDirectory = Path.of("/data/prover/execution/responses"),
inprogressRequestWritingSuffix = ".OVERRIDE_inprogress_coordinator_writing",
inprogressProvingSuffixPattern = "OVERRIDE_\\.inprogress\\.prover.*",
pollingInterval = 10.seconds,
pollingTimeout = 10.minutes,
),
)
assertThat(config.blobCompression).isEqualTo(
FileBasedProverConfig(
requestsDirectory = Path.of("/data/prover/compression/requests"),
responsesDirectory = Path.of("/data/prover/compression/responses"),
inprogressRequestWritingSuffix = ".inprogress_coordinator_writing",
inprogressProvingSuffixPattern = "\\.inprogress\\.prover.*",
pollingInterval = 20.seconds,
pollingTimeout = 20.minutes,
),
)
assertThat(config.proofAggregation).isEqualTo(
FileBasedProverConfig(
requestsDirectory = Path.of("/data/prover/aggregation/requests"),
responsesDirectory = Path.of("/data/prover/aggregation/responses"),
inprogressRequestWritingSuffix = ".inprogress_coordinator_writing",
inprogressProvingSuffixPattern = "\\.inprogress\\.prover.*",
pollingInterval = 10.seconds,
pollingTimeout = 10.minutes,
),
)
}
@Test
fun `should load configs with 2 provers and overrides`() {
val toml = """
$proverAConfigToml
[prover.new]
switch-block-number-inclusive=200
fs-inprogress-request-writing-suffix = ".NEW_OVERRIDE_inprogress_coordinator_writing"
fs-polling-timeout = "PT5M"
[prover.new.execution]
fs-requests-directory = "/data/prover-new/execution/requests"
fs-responses-directory = "/data/prover-new/execution/responses"
fs-inprogress-request-writing-suffix = ".NEW_OVERRIDE_2_inprogress_coordinator_writing"
fs-inprogress-proving-suffix-pattern = "NEW_OVERRIDE_2\\.inprogress\\.prover.*"
[prover.new.blob-compression]
fs-requests-directory = "/data/prover-new/compression/requests"
fs-responses-directory = "/data/prover-new/compression/responses"
fs-polling-interval = "PT12S"
fs-polling-timeout = "PT12M"
[prover.new.proof-aggregation]
fs-requests-directory = "/data/prover-new/aggregation/requests"
fs-responses-directory = "/data/prover-new/aggregation/responses
"
""".trimIndent()
val config = parseConfig(toml)
assertProverAConfig(config.proverA)
assertThat(config.switchBlockNumberInclusive).isEqualTo(200uL)
assertThat(config.proverB).isNotNull
assertThat(config.proverB!!.execution).isEqualTo(
FileBasedProverConfig(
requestsDirectory = Path.of("/data/prover-new/execution/requests"),
responsesDirectory = Path.of("/data/prover-new/execution/responses"),
inprogressRequestWritingSuffix = ".NEW_OVERRIDE_2_inprogress_coordinator_writing",
inprogressProvingSuffixPattern = "NEW_OVERRIDE_2\\.inprogress\\.prover.*",
pollingInterval = 10.seconds,
pollingTimeout = 5.minutes,
),
)
assertThat(config.proverB!!.blobCompression).isEqualTo(
FileBasedProverConfig(
requestsDirectory = Path.of("/data/prover-new/compression/requests"),
responsesDirectory = Path.of("/data/prover-new/compression/responses"),
inprogressRequestWritingSuffix = ".NEW_OVERRIDE_inprogress_coordinator_writing",
inprogressProvingSuffixPattern = "\\.inprogress\\.prover.*",
pollingInterval = 12.seconds,
pollingTimeout = 12.minutes,
),
)
assertThat(config.proverB!!.proofAggregation).isEqualTo(
FileBasedProverConfig(
requestsDirectory = Path.of("/data/prover-new/aggregation/requests"),
responsesDirectory = Path.of("/data/prover-new/aggregation/responses"),
inprogressRequestWritingSuffix = ".NEW_OVERRIDE_inprogress_coordinator_writing",
inprogressProvingSuffixPattern = "\\.inprogress\\.prover.*",
pollingInterval = 10.seconds,
pollingTimeout = 5.minutes,
),
)
}
}

View File

@@ -23,6 +23,9 @@ class FeeHistoryFetcherImpl(
val feeHistoryRewardPercentile: Double,
) {
init {
require(feeHistoryBlockCount > 0u) {
"feeHistoryBlockCount=$feeHistoryBlockCount must be greater than 0."
}
require(feeHistoryRewardPercentile in 0.0..100.0) {
"feeHistoryRewardPercentile must be within 0.0 and 100.0." +
" Value=$feeHistoryRewardPercentile"

View File

@@ -1,6 +1,7 @@
package net.consensys.zkevm.ethereum
import com.sksamuel.hoplite.ConfigLoaderBuilder
import com.sksamuel.hoplite.ExperimentalHoplite
import com.sksamuel.hoplite.addFileSource
import linea.contract.l1.LineaContractVersion
import linea.kotlin.gwei
@@ -74,11 +75,15 @@ interface ContractsManager {
object MakeFileDelegatedContractsManager : ContractsManager {
val log = LoggerFactory.getLogger(MakeFileDelegatedContractsManager::class.java)
@OptIn(ExperimentalHoplite::class)
val lineaRollupContractErrors = findPathTo("config")!!
.resolve("common/smart-contract-errors.toml")
.let { filePath ->
data class ErrorsFile(val smartContractErrors: Map<String, String>)
ConfigLoaderBuilder.default()
ConfigLoaderBuilder
.default()
.withExplicitSealedTypes()
.addFileSource(filePath.toAbsolutePath().toString())
.build()
.loadConfigOrThrow<ErrorsFile>()

View File

@@ -202,7 +202,6 @@ services:
hostname: coordinator
container_name: coordinator
image: consensys/linea-coordinator:${COORDINATOR_TAG:-7e306e2}
platform: linux/amd64
profiles: [ "l2", "debug" ]
depends_on:
postgres:
@@ -219,13 +218,11 @@ services:
- "9545:9545"
restart: on-failure
environment:
config__override__l2-network-gas-pricing__json-rpc-pricing-propagation__disabled: ${DISABLE_JSON_RPC_PRICING_PROPAGATION:-true}
config__override__type2-state-proof-provider__disabled: ${DISABLE_TYPE2_STATE_PROOF_PROVIDER:-true}
command: [ 'java', '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005','-Dvertx.configurationFile=/var/lib/coordinator/vertx-options.json', '-Dlog4j2.configurationFile=/var/lib/coordinator/log4j2-dev.xml', '-jar', 'libs/coordinator.jar', '--traces-limits-v2', 'config/traces-limits-v2.toml', '--smart-contract-errors', 'config/smart-contract-errors.toml', '--gas-price-cap-time-of-day-multipliers', 'config/gas-price-cap-time-of-day-multipliers.toml', 'config/coordinator-docker.config.toml', 'config/coordinator-docker-traces-v2-override.config.toml' ]
command: [ 'java', '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005','-Dvertx.configurationFile=/var/lib/coordinator/vertx-options.json', '-Dlog4j2.configurationFile=/var/lib/coordinator/log4j2-dev.xml', '-jar', 'libs/coordinator.jar', '--traces-limits-v2', 'config/traces-limits-v2.toml', '--smart-contract-errors', 'config/smart-contract-errors.toml', '--gas-price-cap-time-of-day-multipliers', 'config/gas-price-cap-time-of-day-multipliers.toml', 'config/coordinator-config.toml']
#command: [ 'echo', 'forced exit' ]
volumes:
- ../config/coordinator/coordinator-docker.config.toml:/opt/consensys/linea/coordinator/config/coordinator-docker.config.toml:ro
- ../config/coordinator/coordinator-docker-web3signer-override.config.toml:/opt/consensys/linea/coordinator/config/coordinator-docker-web3signer-override.config.toml:ro
- ../config/coordinator/coordinator-docker-traces-v2-override.config.toml:/opt/consensys/linea/coordinator/config/coordinator-docker-traces-v2-override.config.toml:ro
- ../config/coordinator/coordinator-config-v2.toml:/opt/consensys/linea/coordinator/config/coordinator-config.toml:ro
- ../config/common/traces-limits-v2.toml:/opt/consensys/linea/coordinator/config/traces-limits-v2.toml:ro
- ../config/common/smart-contract-errors.toml:/opt/consensys/linea/coordinator/config/smart-contract-errors.toml:ro
- ../config/common/gas-price-cap-time-of-day-multipliers.toml:/opt/consensys/linea/coordinator/config/gas-price-cap-time-of-day-multipliers.toml:ro

View File

@@ -11,7 +11,7 @@ services:
extends:
file: compose-spec-l2-services.yml
service: shomei-frontend
postman:
extends:
file: compose-spec-l2-services.yml

View File

@@ -16,3 +16,4 @@ fun String.toIntFromHex(): Int = removePrefix("0x").toInt(16)
fun String.toLongFromHex(): Long = removePrefix("0x").toLong(16)
fun String.toULongFromHex(): ULong = BigInteger(removePrefix("0x"), 16).toULong()
fun String.toBigIntegerFromHex(): BigInteger = BigInteger(removePrefix("0x"), 16)
fun String.toURL(): java.net.URL = java.net.URI.create(this).toURL()

View File

@@ -6,6 +6,8 @@ import linea.domain.BlockWithTxHashes
import tech.pegasys.teku.infrastructure.async.SafeFuture
interface EthApiClient : EthLogsClient {
fun getChainId(): SafeFuture<ULong>
fun findBlockByNumber(
blockParameter: BlockParameter,
): SafeFuture<Block?>

View File

@@ -17,6 +17,7 @@ import kotlin.time.Duration.Companion.seconds
class FakeEthApiClient(
initialLogsDb: Set<EthLog> = emptySet(),
val chainId: ULong = 101UL,
val genesisTimestamp: Instant = Instant.parse("2025-04-01T00:00:00Z"),
val blockTime: Duration = 1.seconds,
initialTagsBlocks: Map<BlockParameter.Tag, ULong> = mapOf(
@@ -112,6 +113,10 @@ class FakeEthApiClient(
}
}
override fun getChainId(): SafeFuture<ULong> {
return SafeFuture.completedFuture(chainId)
}
override fun findBlockByNumber(blockParameter: BlockParameter): SafeFuture<Block?> {
val blockNumber = blockParameterToBlockNumber(blockParameter)
if (isAfterHead(blockNumber)) {

View File

@@ -25,6 +25,7 @@ import org.web3j.tx.gas.StaticGasProvider
import tech.pegasys.teku.infrastructure.async.SafeFuture
import java.math.BigInteger
import java.util.concurrent.atomic.AtomicReference
import kotlin.random.Random
class Web3JL2MessageServiceSmartContractClient(
private val web3j: Web3j,
@@ -77,6 +78,30 @@ class Web3JL2MessageServiceSmartContractClient(
deploymentBlockNumberProvider = deploymentBlockNumberProvider,
)
}
fun createReadOnly(
web3jClient: Web3j,
contractAddress: String,
smartContractErrors: SmartContractErrors,
smartContractDeploymentBlockNumber: ULong?,
): L2MessageServiceSmartContractClientReadOnly {
val unUsedTxManager = AsyncFriendlyTransactionManager(
web3j = web3jClient,
credentials = Credentials.create(Random.nextBytes(64).encodeHex()),
chainId = 1L,
)
return create(
web3jClient = web3jClient,
contractAddress = contractAddress,
gasLimit = 0UL,
maxFeePerGasCap = 0UL,
feeHistoryBlockCount = 1u,
feeHistoryRewardPercentile = 100.0,
transactionManager = unUsedTxManager,
smartContractErrors = smartContractErrors,
smartContractDeploymentBlockNumber = smartContractDeploymentBlockNumber,
)
}
}
private val fakeCredentials = Credentials.create(ByteArray(32).encodeHex())

View File

@@ -8,26 +8,54 @@ import org.web3j.protocol.http.HttpService
import org.web3j.utils.Async
import java.util.concurrent.ScheduledExecutorService
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
fun createWeb3jHttpClient(
rpcUrl: String,
log: Logger = org.apache.logging.log4j.LogManager.getLogger(Web3j::class.java),
pollingInterval: Duration = 500.milliseconds,
pollingInterval: Duration = 1.seconds,
executorService: ScheduledExecutorService = Async.defaultExecutorService(),
requestResponseLogLevel: Level = Level.TRACE,
failuresLogLevel: Level = Level.DEBUG,
): Web3j {
return Web3j.build(
HttpService(
rpcUrl,
okHttpClientBuilder(
logger = log,
requestResponseLogLevel = requestResponseLogLevel,
failuresLogLevel = failuresLogLevel,
).build(),
),
pollingInterval.inWholeMilliseconds,
val httpService = createWeb3jHttpService(rpcUrl, log, requestResponseLogLevel, failuresLogLevel)
return createWeb3jHttpClient(
httpService,
pollingInterval,
executorService,
)
}
fun createWeb3jHttpClient(
httpService: HttpService,
pollingInterval: Duration = 1.seconds,
executorService: ScheduledExecutorService = Async.defaultExecutorService(),
): Web3j {
return Web3j.build(
/* web3jService = */
httpService,
// used for Web3jRx to poll for new Blocks and TransactionsReceipts
/* pollingInterval = */
pollingInterval.inWholeMilliseconds,
/* scheduledExecutorService = */
executorService,
)
}
fun createWeb3jHttpService(
rpcUrl: String,
log: Logger = org.apache.logging.log4j.LogManager.getLogger(Web3j::class.java),
requestResponseLogLevel: Level = Level.TRACE,
failuresLogLevel: Level = Level.DEBUG,
): HttpService {
return HttpService(
/* url = */
rpcUrl,
/* httpClient = */
okHttpClientBuilder(
logger = log,
requestResponseLogLevel = requestResponseLogLevel,
failuresLogLevel = failuresLogLevel,
).build(),
)
}

View File

@@ -5,6 +5,7 @@ import linea.domain.BlockParameter
import linea.domain.BlockWithTxHashes
import linea.domain.EthLog
import linea.ethapi.EthApiClient
import linea.kotlin.toULong
import linea.web3j.domain.toDomain
import linea.web3j.domain.toWeb3j
import linea.web3j.mapToDomainWithTxHashes
@@ -23,6 +24,14 @@ class Web3jEthApiClient(
val web3jClient: Web3j,
) : EthApiClient {
override fun getChainId(): SafeFuture<ULong> {
return web3jClient
.ethChainId()
.requestAsync { resp ->
resp.chainId?.toULong() ?: throw IllegalStateException("Chain ID not found in response")
}
}
override fun findBlockByNumber(blockParameter: BlockParameter): SafeFuture<Block?> {
return web3jClient
.ethGetBlockByNumber(blockParameter.toWeb3j(), true)

View File

@@ -65,7 +65,14 @@ fun createEthApiClient(
vertx: Vertx?,
): EthApiClient {
val web3jClient =
createWeb3jHttpClient(rpcUrl, log, pollingInterval, executorService, requestResponseLogLevel, failuresLogLevel)
createWeb3jHttpClient(
rpcUrl,
log,
pollingInterval,
executorService,
requestResponseLogLevel,
failuresLogLevel,
)
return createEthApiClient(web3jClient, requestRetryConfig, vertx)
}

View File

@@ -28,6 +28,10 @@ class Web3jEthApiClientWithRetries(
)
}
override fun getChainId(): SafeFuture<ULong> {
return retry { ethApiClient.getChainId() }
}
override fun findBlockByNumber(blockParameter: BlockParameter): SafeFuture<Block?> {
return retry { ethApiClient.findBlockByNumber(blockParameter) }
}

View File

@@ -4,6 +4,7 @@ import linea.kotlin.toBigInteger
import linea.kotlin.toIntervalString
import linea.web3j.domain.blocksRange
import linea.web3j.domain.toLineaDomain
import linea.web3j.requestAsync
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
import org.web3j.protocol.Web3j
@@ -20,7 +21,13 @@ class EIP1559GasProvider(private val web3jClient: Web3j, private val config: Con
val maxFeePerGasCap: ULong,
val feeHistoryBlockCount: UInt,
val feeHistoryRewardPercentile: Double,
)
) {
init {
require(feeHistoryBlockCount > 0u) {
"feeHistoryBlockCount=$feeHistoryBlockCount must be greater than 0."
}
}
}
private val chainId: Long = web3jClient.ethChainId().send().chainId.toLong()
private var cacheIsValidForBlockNumber: BigInteger = BigInteger.ZERO
@@ -36,9 +43,7 @@ class EIP1559GasProvider(private val web3jClient: Web3j, private val config: Con
DefaultBlockParameterName.LATEST,
listOf(config.feeHistoryRewardPercentile),
)
.sendAsync()
.thenApply {
feeHistoryResponse ->
.requestAsync { feeHistoryResponse ->
val feeHistory = feeHistoryResponse.feeHistory.toLineaDomain()
var maxPriorityFeePerGas = feeHistory.reward.sumOf { it[0] } / feeHistory.reward.size.toUInt()

View File

@@ -21,11 +21,9 @@ build:
run-e2e-test:
echo "EXPECTED_TRACES_API_VERSION=${LINEA_TRACER_PLUGIN_VERSION}"
if [ "$(shell uname)" = "Linux" ]; then \
sed -i'' 's/^\(expected-traces-api-version-v2=\).*/\1"${LINEA_TRACER_PLUGIN_VERSION}"/' ../config/coordinator/coordinator-docker.config.toml; \
sed -i'' 's/^\(expected-traces-api-version-v2=\).*/\1"${LINEA_TRACER_PLUGIN_VERSION}"/' ../config/coordinator/coordinator-docker-traces-v2-override.config.toml; \
sed -i'' 's/^\(expected-traces-api-version[ ]*=[ ]*\).*/\1"${LINEA_TRACER_PLUGIN_VERSION}"/' ../config/coordinator/coordinator-config-v2.toml; \
elif [ "$(shell uname)" = "Darwin" ]; then \
sed -i '' 's/^\(expected-traces-api-version-v2=\).*/\1"${LINEA_TRACER_PLUGIN_VERSION}"/' ../config/coordinator/coordinator-docker.config.toml; \
sed -i '' 's/^\(expected-traces-api-version-v2=\).*/\1"${LINEA_TRACER_PLUGIN_VERSION}"/' ../config/coordinator/coordinator-docker-traces-v2-override.config.toml; \
sed -i '' 's/^\(expected-traces-api-version[ ]*=[ ]*\).*/\1"${LINEA_TRACER_PLUGIN_VERSION}"/' ../config/coordinator/coordinator-config-v2.toml; \
fi
cd .. && BESU_PACKAGE_TAG=$(TAG) make start-env-with-tracing-v2-ci && pnpm run -F e2e test:e2e:local