Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions coordinator/clients/execution-witness-client/README.md
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't much value/motivation for the existence of this file. It feels generated by AI with obvious info.

The non-obvious part is Node prerequisites section, which can be added as comment ExecutionWitnessClient.kt directly.

@Filter94 @jonesho Any take on this?

Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Execution witness JSON-RPC client

Kotlin client for Besu's `debug_executionWitness` RPC (provided by [besu-zkevm-plugin](https://github.com/Consensys/besu-zkevm-plugin)).

## Interface

[`ExecutionWitnessClient`](../../../jvm-libs/linea/clients/interfaces/src/main/kotlin/linea/executionwitness/ExecutionWitnessClient.kt) in `jvm-libs:linea:clients:interfaces`.

Implementation: [`ExecutionWitnessJsonRpcClient`](src/main/kotlin/linea/coordinator/clients/executionwitness/ExecutionWitnessJsonRpcClient.kt).

## RPC

**Request**

```json
{
"jsonrpc": "2.0",
"method": "debug_executionWitness",
"params": ["<block>"],
"id": 1
}
```

**Response** (`result` object, or `null` if witness unavailable):

```json
{
"state": ["..."],
"keys": ["..."],
"codes": ["..."],
"headers": ["..."]
}
```

Hex strings may be with or without a `0x` prefix.

## Block parameter mapping

| `BlockParameter` | RPC `params[0]` |
|------------------|-----------------|
| `Tag.LATEST` (etc.) | tag string |
| `BlockNumber(n)` | decimal `n.toString()` |
| `BlockHash(h)` | `0x` + hex (32 bytes) |

## Node prerequisites

- `besu-zkevm-plugin` loaded
- Trie-log plugin enabled
- `--rpc-http-api=DEBUG`

## Tests

```bash
./gradlew :coordinator:clients:execution-witness-client:test
```
16 changes: 16 additions & 0 deletions coordinator/clients/execution-witness-client/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
plugins {
id 'net.consensys.zkevm.kotlin-library-conventions'
}

dependencies {
implementation project(':jvm-libs:linea:clients:interfaces')
implementation project(':jvm-libs:generic:extensions:futures')
implementation project(':jvm-libs:generic:extensions:kotlin')
implementation project(':jvm-libs:generic:json-rpc')
implementation project(':jvm-libs:generic:errors')
api "io.vertx:vertx-core"

testImplementation "io.vertx:vertx-junit5"
testImplementation project(':jvm-libs:linea:metrics:micrometer')
testImplementation "org.wiremock:wiremock:${libs.versions.wiremock.get()}"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package linea.coordinator.clients.executionwitness

import linea.domain.BlockParameter
import linea.kotlin.encodeHex

internal fun BlockParameter.toDebugExecutionWitnessRpcParam(): String =
when (this) {
is BlockParameter.Tag -> getTag()
is BlockParameter.BlockNumber -> getNumber().toString()
is BlockParameter.BlockHash -> getHash().encodeHex(prefix = true)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package linea.coordinator.clients.executionwitness

import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.fold
import io.vertx.core.Vertx
import linea.domain.BlockParameter
import linea.error.ErrorResponse
import linea.executionwitness.ExecutionWitness
import linea.executionwitness.ExecutionWitnessClient
import linea.executionwitness.ExecutionWitnessError
import net.consensys.linea.async.toSafeFuture
import net.consensys.linea.jsonrpc.JsonRpcRequestListParams
import net.consensys.linea.jsonrpc.client.JsonRpcClient
import net.consensys.linea.jsonrpc.client.JsonRpcRequestRetryer
import net.consensys.linea.jsonrpc.client.RequestRetryConfig
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
import tech.pegasys.teku.infrastructure.async.SafeFuture
import java.util.concurrent.atomic.AtomicInteger

class ExecutionWitnessJsonRpcClient(
private val rpcClient: JsonRpcClient,
) : ExecutionWitnessClient {

constructor(
vertx: Vertx,
rpcClient: JsonRpcClient,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use JsonRpcV2Client instead. V1 is meant to be deprecated/removed once Shomei and Traces API get removed.

retryConfig: RequestRetryConfig,
log: Logger = LogManager.getLogger(ExecutionWitnessJsonRpcClient::class.java),
) : this(
JsonRpcRequestRetryer(
vertx,
rpcClient,
config = JsonRpcRequestRetryer.Config(
methodsToRetry = retryableMethods,
requestRetry = retryConfig,
),
log = log,
),
)

private val requestId = AtomicInteger(0)

override fun getExecutionWitness(
block: BlockParameter,
): SafeFuture<Result<ExecutionWitness, ErrorResponse<ExecutionWitnessError>>> {
val jsonRequest = JsonRpcRequestListParams(
jsonrpc = "2.0",
id = requestId.incrementAndGet(),
method = "debug_executionWitness",
params = listOf(block.toDebugExecutionWitnessRpcParam()),
)

return rpcClient
.makeRequest(jsonRequest)
.toSafeFuture()
.thenApply { responseResult ->
responseResult.fold(
{ success -> ExecutionWitnessResponseParser.parseExecutionWitness(success) },
{ error -> Err(ExecutionWitnessResponseParser.mapRpcError(error)) },
)
}
}

companion object {
internal val retryableMethods = setOf("debug_executionWitness")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package linea.coordinator.clients.executionwitness

import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import io.vertx.core.json.JsonArray
import io.vertx.core.json.JsonObject
import linea.error.ErrorResponse
import linea.executionwitness.ExecutionWitness
import linea.executionwitness.ExecutionWitnessError
import linea.kotlin.decodeHex
import net.consensys.linea.jsonrpc.JsonRpcErrorResponse
import net.consensys.linea.jsonrpc.JsonRpcSuccessResponse

object ExecutionWitnessResponseParser {

fun mapRpcError(jsonRpcErrorResponse: JsonRpcErrorResponse): ErrorResponse<ExecutionWitnessError> {
return ErrorResponse(
ExecutionWitnessError.RPC_ERROR,
"${jsonRpcErrorResponse.error.code}: ${jsonRpcErrorResponse.error.message}",
)
}

fun parseExecutionWitness(
jsonRpcResponse: JsonRpcSuccessResponse,
): Result<ExecutionWitness, ErrorResponse<ExecutionWitnessError>> {
if (jsonRpcResponse.result == null) {
return Err(
ErrorResponse(
ExecutionWitnessError.NULL_RESULT,
"debug_executionWitness returned null (witness unavailable for block)",
),
)
}

return try {
val json = jsonRpcResponse.result as JsonObject
Ok(
ExecutionWitness(
state = parseHexList(json, "state"),
keys = parseHexList(json, "keys"),
codes = parseHexList(json, "codes"),
headers = parseHexList(json, "headers"),
),
)
} catch (throwable: Throwable) {
Err(
ErrorResponse(
ExecutionWitnessError.PARSE_ERROR,
throwable.message ?: "failed to parse execution witness",
),
)
}
}

private fun parseHexList(json: JsonObject, field: String): List<ByteArray> {
val array = json.getValue(field) as? JsonArray
?: throw IllegalArgumentException("missing or invalid field: $field")
return array.map { element ->
(element as String).decodeHex()
}
}
}
Loading
Loading