-
Notifications
You must be signed in to change notification settings - Fork 839
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
implement engine_getBlobsV1 #7553
Changes from 5 commits
5fa9138
0a8de78
d9c272b
9c3f51f
c4324c3
e944868
5d51a16
8350a15
c1e1a46
300a035
110d6bd
4ca1dc1
cdec0d1
bdbd0cc
a066ed7
5797063
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
/* | ||
* Copyright contributors to Hyperledger Besu. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with | ||
* the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on | ||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the | ||
* specific language governing permissions and limitations under the License. | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; | ||
|
||
import org.hyperledger.besu.datatypes.BlobsWithCommitments; | ||
import org.hyperledger.besu.datatypes.Transaction; | ||
import org.hyperledger.besu.datatypes.VersionedHash; | ||
import org.hyperledger.besu.ethereum.ProtocolContext; | ||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; | ||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; | ||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod; | ||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter; | ||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; | ||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; | ||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; | ||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; | ||
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlobAndProofV1; | ||
import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; | ||
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; | ||
|
||
import java.util.Arrays; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import javax.annotation.Nonnull; | ||
import javax.annotation.Nullable; | ||
|
||
import io.vertx.core.Vertx; | ||
|
||
/** | ||
* #### Specification | ||
* | ||
* <p>1. Given an array of blob versioned hashes client software **MUST** respond with an array of | ||
* `BlobAndProofV1` objects with matching versioned hashes, respecting the order of versioned hashes | ||
* in the input array. | ||
* | ||
* <p>2. Client software **MUST** place responses in the order given in the request, using `null` | ||
* for any missing blobs. For instance, if the request is `[A_versioned_hash, B_versioned_hash, | ||
* C_versioned_hash]` and client software has data for blobs `A` and `C`, but doesn't have data for | ||
* `B`, the response **MUST** be `[A, null, C]`. | ||
* | ||
* <p>3. Client software **MUST** support request sizes of at least 128 blob versioned hashes. The | ||
* client **MUST** return `-38004: Too large request` error if the number of requested blobs is too | ||
* large. | ||
* | ||
* <p>4. Client software **MAY** return an array of all `null` entries if syncing or otherwise | ||
* unable to serve blob pool data. | ||
* | ||
* <p>5. Callers **MUST** consider that execution layer clients may prune old blobs from their pool, | ||
* and will respond with `null` if a blob has been pruned. | ||
*/ | ||
public class EngineGetBlobsV1 extends ExecutionEngineJsonRpcMethod { | ||
|
||
private final Map<VersionedHash, BlobsWithCommitments.BlobQuad> blobMap = new HashMap<>(); | ||
private final BlobCache blobCache; | ||
|
||
public EngineGetBlobsV1( | ||
final Vertx vertx, | ||
final ProtocolContext protocolContext, | ||
final EngineCallListener engineCallListener, | ||
final TransactionPool transactionPool) { | ||
super(vertx, protocolContext, engineCallListener); | ||
this.blobCache = transactionPool.getBlobCache(); | ||
transactionPool.subscribePendingTransactions(this::onTransactionAdded); | ||
transactionPool.subscribeDroppedTransactions(this::onTransactionDropped); | ||
} | ||
|
||
@Override | ||
public String getName() { | ||
return "engine_getBlobsV1"; | ||
} | ||
|
||
@Override | ||
public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) { | ||
final VersionedHash[] versionedHashes; | ||
try { | ||
versionedHashes = requestContext.getRequiredParameter(0, VersionedHash[].class); | ||
} catch (JsonRpcParameter.JsonRpcParameterException e) { | ||
throw new InvalidJsonRpcParameters( | ||
"Invalid versioned hashes parameter (index 0)", | ||
RpcErrorType.INVALID_VERSIONED_HASHES_PARAMS, | ||
e); | ||
} | ||
|
||
if (versionedHashes.length > 128) { | ||
return new JsonRpcErrorResponse( | ||
requestContext.getRequest().getId(), | ||
RpcErrorType.INVALID_ENGINE_GET_BLOBS_V1_TOO_LARGE_REQUEST); | ||
} | ||
|
||
final List<BlobAndProofV1> result = getResult(versionedHashes); | ||
|
||
return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), result); | ||
} | ||
|
||
private @Nonnull List<BlobAndProofV1> getResult(final VersionedHash[] versionedHashes) { | ||
return Arrays.stream(versionedHashes) | ||
.map(this::getBlobQuad) | ||
.map(this::getBlobAndProofV1) | ||
.toList(); | ||
} | ||
|
||
private @Nullable BlobAndProofV1 getBlobAndProofV1(final BlobsWithCommitments.BlobQuad bq) { | ||
if (bq == null) { | ||
return null; | ||
} | ||
return new BlobAndProofV1( | ||
bq.blob().getData().toHexString(), bq.kzgProof().getData().toHexString()); | ||
} | ||
|
||
private BlobsWithCommitments.BlobQuad getBlobQuad(final VersionedHash vh) { | ||
|
||
BlobsWithCommitments.BlobQuad blobQuad = blobMap.get(vh); | ||
if (blobQuad == null) { | ||
blobQuad = blobCache.get(vh); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in theory this fallback should not be necessary, and removing the need to have the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also don't understand. It seems to me that blobMap is redundant, and this RPC should just use the same instance of the blobCache that the transactionpool does. Management of the cache contents should be ignored by the RPC and treated as a "read only" concern. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The blob cache does contain all the blobs that have been put into a block recently. These blobs are used to re-add the blob transactions in case of a reorg, as the blobs are not part of the block. After 3 epochs these blobs are removed from the cache. Blobs that are in the cache are not part of Transactions that are in the pool. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. According to the specification of the new method, the CL should be interested in blobs that are in the txpool, and not blobs that have been already included in a block, so |
||
|
||
return blobQuad; | ||
} | ||
|
||
public void onTransactionAdded(final Transaction transaction) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. idk if the RPC should be itself managing the blob cache? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was wondering where that should live. The other option would be the TransactionPool. The BlobCache is taken care of in the TransactionPool, because these blobs are needed in case we have a reorg. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have moved that logic into the TransactionPool |
||
final Optional<BlobsWithCommitments> maybeBlobsWithCommitments = | ||
transaction.getBlobsWithCommitments(); | ||
if (maybeBlobsWithCommitments.isEmpty()) { | ||
return; | ||
} | ||
final List<BlobsWithCommitments.BlobQuad> blobQuads = | ||
maybeBlobsWithCommitments.get().getBlobQuads(); | ||
blobQuads.forEach(bq -> blobMap.put(bq.versionedHash(), bq)); | ||
} | ||
|
||
public void onTransactionDropped(final Transaction transaction) { | ||
final Optional<BlobsWithCommitments> maybeBlobsWithCommitments = | ||
transaction.getBlobsWithCommitments(); | ||
if (maybeBlobsWithCommitments.isEmpty()) { | ||
return; | ||
} | ||
final List<BlobsWithCommitments.BlobQuad> blobQuads = | ||
maybeBlobsWithCommitments.get().getBlobQuads(); | ||
blobQuads.forEach(bq -> blobMap.remove(bq.versionedHash())); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
/* | ||
* Copyright contributors to Hyperledger Besu. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with | ||
* the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on | ||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the | ||
* specific language governing permissions and limitations under the License. | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results; | ||
|
||
import com.fasterxml.jackson.annotation.JsonPropertyOrder; | ||
|
||
/** | ||
* The result of the eth_getBlobAndProofV1 JSON-RPC method contains an array of BlobAndProofV1. | ||
* BlobAndProofV1 contains the blob data and the kzg proof for the blob. | ||
*/ | ||
@JsonPropertyOrder({"blob", "proof"}) | ||
public class BlobAndProofV1 { | ||
|
||
private final String blob; | ||
|
||
private final String proof; | ||
|
||
public BlobAndProofV1(final String blob, final String proof) { | ||
this.blob = blob; | ||
this.proof = proof; | ||
} | ||
|
||
public String getProof() { | ||
return proof; | ||
} | ||
|
||
public String getBlob() { | ||
return blob; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think naming this something more specific would help me understand why it is necessary. As is, it reads like a synonym for blobCache, and seems like duplication.