diff --git a/cmd/rpcdaemon/README.md b/cmd/rpcdaemon/README.md index 22fa4f91db3..5ed0dbaae90 100644 --- a/cmd/rpcdaemon/README.md +++ b/cmd/rpcdaemon/README.md @@ -331,6 +331,8 @@ The following table shows the current implementation status of Erigon's RPC daem | engine_getPayloadBodiesByRangeV1 | Yes | | | engine_getClientVersionV1 | Yes | | | engine_getBlobsV1 | Yes | | +| engine_getBlobsV2 | Yes | Added in Fusaka | +| engine_getBlobsV3 | Yes | Added with BPO3 | | | | | | debug_getRawReceipts | Yes | `debug_` expected to be private | | debug_accountRange | Yes | | diff --git a/execution/engineapi/engine_api_methods.go b/execution/engineapi/engine_api_methods.go index df9e2b17fd8..838349e9d62 100644 --- a/execution/engineapi/engine_api_methods.go +++ b/execution/engineapi/engine_api_methods.go @@ -47,6 +47,7 @@ var ourCapabilities = []string{ "engine_getClientVersionV1", "engine_getBlobsV1", "engine_getBlobsV2", + "engine_getBlobsV3", } // Returns the most recent version of the payload(for the payloadID) at the time of receiving the call @@ -224,6 +225,20 @@ func (e *EngineServer) GetBlobsV1(ctx context.Context, blobHashes []common.Hash) func (e *EngineServer) GetBlobsV2(ctx context.Context, blobHashes []common.Hash) ([]*engine_types.BlobAndProofV2, error) { e.logger.Debug("[GetBlobsV2] Received Request", "hashes", len(blobHashes)) + // GetBlobsV2 was actually introduced in Fusaka, + // but here we're using the Pectra version to differentiate it from GetBlobsV3. + resp, err := e.getBlobs(ctx, blobHashes, clparams.ElectraVersion) + if err != nil { + return nil, err + } + if ret, ok := resp.([]*engine_types.BlobAndProofV2); ok { + return ret, err + } + return nil, err +} + +func (e *EngineServer) GetBlobsV3(ctx context.Context, blobHashes []common.Hash) ([]*engine_types.BlobAndProofV2, error) { + e.logger.Debug("[GetBlobsV3] Received Request", "hashes", len(blobHashes)) resp, err := e.getBlobs(ctx, blobHashes, clparams.FuluVersion) if err != nil { return nil, err diff --git a/execution/engineapi/engine_server.go b/execution/engineapi/engine_server.go index de50e6afe87..4974ff75bde 100644 --- a/execution/engineapi/engine_server.go +++ b/execution/engineapi/engine_server.go @@ -1029,17 +1029,36 @@ func (e *EngineServer) getBlobs(ctx context.Context, blobHashes []common.Hash, v return nil, nil } - if version == clparams.FuluVersion { + switch version { + case clparams.FuluVersion: // GetBlobsV3 ret := make([]*engine_types.BlobAndProofV2, len(blobHashes)) for i, bwp := range res.BlobsWithProofs { logHead := fmt.Sprintf("\n%x: ", blobHashes[i]) if len(bwp.Blob) == 0 { - // engine_getblobsv2 MUST return null in case of any missing or older version blobs + logLine = append(logLine, logHead, "nil") + } else if len(bwp.Proofs) != int(params.CellsPerExtBlob) { + logLine = append(logLine, logHead, fmt.Sprintf("pre-Fusaka proofs, len(proof)=%d", len(bwp.Proofs))) + } else { + ret[i] = &engine_types.BlobAndProofV2{Blob: bwp.Blob, CellProofs: make([]hexutil.Bytes, params.CellsPerExtBlob)} + for c := range params.CellsPerExtBlob { + ret[i].CellProofs[c] = bwp.Proofs[c] + } + logLine = append(logLine, logHead, fmt.Sprintf("OK, len(blob)=%d", len(bwp.Blob))) + } + } + e.logger.Debug("[GetBlobsV3]", "Responses", logLine) + return ret, nil + case clparams.ElectraVersion: // GetBlobsV2 + ret := make([]*engine_types.BlobAndProofV2, len(blobHashes)) + for i, bwp := range res.BlobsWithProofs { + logHead := fmt.Sprintf("\n%x: ", blobHashes[i]) + if len(bwp.Blob) == 0 { + // engine_getBlobsV2 MUST return null in case of any missing or older version blobs ret = nil logLine = append(logLine, logHead, "nil") break } else if len(bwp.Proofs) != int(params.CellsPerExtBlob) { - // engine_getblobsv2 MUST return null in case of any missing or older version blobs + // engine_getBlobsV2 MUST return null in case of any missing or older version blobs ret = nil logLine = append(logLine, logHead, fmt.Sprintf("pre-Fusaka proofs, len(proof)=%d", len(bwp.Proofs))) break @@ -1053,7 +1072,7 @@ func (e *EngineServer) getBlobs(ctx context.Context, blobHashes []common.Hash, v } e.logger.Debug("[GetBlobsV2]", "Responses", logLine) return ret, nil - } else if version == clparams.DenebVersion { + case clparams.DenebVersion: // GetBlobsV1 ret := make([]*engine_types.BlobAndProofV1, len(blobHashes)) for i, bwp := range res.BlobsWithProofs { logHead := fmt.Sprintf("\n%x: ", blobHashes[i]) @@ -1068,8 +1087,9 @@ func (e *EngineServer) getBlobs(ctx context.Context, blobHashes []common.Hash, v } e.logger.Debug("[GetBlobsV1]", "Responses", logLine) return ret, nil + default: + return nil, nil } - return nil, nil } func waitForResponse(maxWait time.Duration, waitCondnF func() (bool, error)) (bool, error) { diff --git a/execution/engineapi/engine_server_test.go b/execution/engineapi/engine_server_test.go index 76caef09573..0e772e01a45 100644 --- a/execution/engineapi/engine_server_test.go +++ b/execution/engineapi/engine_server_test.go @@ -201,6 +201,69 @@ func TestGetBlobsV2(t *testing.T) { blobHashes = blobHashes[1:] blobsResp, err = engineServer.GetBlobsV2(ctx, blobHashes) require.NoError(err) + require.Len(blobsResp, 2) + require.Equal(blobsResp[0].Blob, hexutil.Bytes(wrappedTxn.Blobs[0][:])) + require.Equal(blobsResp[1].Blob, hexutil.Bytes(wrappedTxn.Blobs[1][:])) + + for i := range 128 { + require.Equal(blobsResp[0].CellProofs[i], hexutil.Bytes(wrappedTxn.Proofs[i][:])) + require.Equal(blobsResp[1].CellProofs[i], hexutil.Bytes(wrappedTxn.Proofs[i+128][:])) + } +} + +func TestGetBlobsV3(t *testing.T) { + logger := log.New() + buf := bytes.NewBuffer(nil) + mockSentry, require := mock.MockWithTxPoolOsaka(t), require.New(t) + oneBlockStep(mockSentry, require, t) + + wrappedTxn := types.MakeV1WrappedBlobTxn(uint256.MustFromBig(mockSentry.ChainConfig.ChainID)) + txn, err := types.SignTx(wrappedTxn, *types.LatestSignerForChainID(mockSentry.ChainConfig.ChainID), mockSentry.Key) + require.NoError(err) + dt := &wrappedTxn.Tx.DynamicFeeTransaction + v, r, s := txn.RawSignatureValues() + dt.V.Set(v) + dt.R.Set(r) + dt.S.Set(s) + + ctx, conn := rpcdaemontest.CreateTestGrpcConn(t, mockSentry) + txPool := direct.NewTxPoolClient(mockSentry.TxPoolGrpcServer) + + ff := rpchelper.New(ctx, rpchelper.DefaultFiltersConfig, nil, txPool, txpoolproto.NewMiningClient(conn), func() {}, mockSentry.Log) + api := jsonrpc.NewEthAPI(newBaseApiForTest(mockSentry), mockSentry.DB, nil, txPool, nil, 5000000, ethconfig.Defaults.RPCTxFeeCap, 100_000, false, 100_000, 128, logger) + + executionRpc := direct.NewExecutionClientDirect(mockSentry.Eth1ExecutionService) + eth := rpcservices.NewRemoteBackend(nil, mockSentry.DB, mockSentry.BlockReader) + engineServer := NewEngineServer(mockSentry.Log, mockSentry.ChainConfig, executionRpc, nil, false, false, true, txPool, DefaultFcuTimeout) + ctx, cancel := context.WithCancel(ctx) + var eg errgroup.Group + t.Cleanup(func() { + err := eg.Wait() // wait for clean exit + require.ErrorIs(err, context.Canceled) + }) + t.Cleanup(cancel) + eg.Go(func() error { + return engineServer.Start(ctx, &httpcfg.HttpCfg{}, mockSentry.DB, mockSentry.BlockReader, ff, nil, mockSentry.Engine, eth, nil) + }) + + err = wrappedTxn.MarshalBinaryWrapped(buf) + require.NoError(err) + hh, err := api.SendRawTransaction(ctx, buf.Bytes()) + require.NoError(err) + require.NotEmpty(hh) + + blobHashes := append([]common.Hash{{}}, wrappedTxn.Tx.BlobVersionedHashes...) + blobsResp, err := engineServer.GetBlobsV3(ctx, blobHashes) + require.NoError(err) + require.Len(blobsResp, 3) // Unlike GetBlobsV2, only the missing blob should be nil + require.Nil(blobsResp[0]) + require.Equal(blobsResp[1].Blob, hexutil.Bytes(wrappedTxn.Blobs[0][:])) + require.Equal(blobsResp[2].Blob, hexutil.Bytes(wrappedTxn.Blobs[1][:])) + + blobHashes = blobHashes[1:] + blobsResp, err = engineServer.GetBlobsV3(ctx, blobHashes) + require.NoError(err) + require.Len(blobsResp, 2) require.Equal(blobsResp[0].Blob, hexutil.Bytes(wrappedTxn.Blobs[0][:])) require.Equal(blobsResp[1].Blob, hexutil.Bytes(wrappedTxn.Blobs[1][:])) diff --git a/execution/engineapi/engine_types/jsonrpc.go b/execution/engineapi/engine_types/jsonrpc.go index 5d79ec348fa..1954b4e6dae 100644 --- a/execution/engineapi/engine_types/jsonrpc.go +++ b/execution/engineapi/engine_types/jsonrpc.go @@ -90,7 +90,7 @@ type BlobAndProofV1 struct { Proof hexutil.Bytes `json:"proof" gencodec:"required"` } -// BlobAndProofV2 holds one item for engine_getBlobsV2 +// BlobAndProofV2 holds one item for engine_getBlobsV2/engine_getBlobsV3 type BlobAndProofV2 struct { Blob hexutil.Bytes `json:"blob" gencodec:"required"` CellProofs []hexutil.Bytes `json:"proofs" gencodec:"required"` diff --git a/execution/engineapi/interface.go b/execution/engineapi/interface.go index 740eda45f05..ded2bd7c8e3 100644 --- a/execution/engineapi/interface.go +++ b/execution/engineapi/interface.go @@ -42,4 +42,6 @@ type EngineAPI interface { GetPayloadBodiesByRangeV1(ctx context.Context, start, count hexutil.Uint64) ([]*engine_types.ExecutionPayloadBody, error) GetClientVersionV1(ctx context.Context, callerVersion *engine_types.ClientVersionV1) ([]engine_types.ClientVersionV1, error) GetBlobsV1(ctx context.Context, blobHashes []common.Hash) ([]*engine_types.BlobAndProofV1, error) + GetBlobsV2(ctx context.Context, blobHashes []common.Hash) ([]*engine_types.BlobAndProofV2, error) + GetBlobsV3(ctx context.Context, blobHashes []common.Hash) ([]*engine_types.BlobAndProofV2, error) } diff --git a/txnprovider/txpool/pool_test.go b/txnprovider/txpool/pool_test.go index 8d8c4ecbefe..861d96de0a6 100644 --- a/txnprovider/txpool/pool_test.go +++ b/txnprovider/txpool/pool_test.go @@ -1551,7 +1551,7 @@ func TestWrappedSixBlobTxnExceedsRlpLimit(t *testing.T) { require.NoError(err) } -func TestGetBlobsV1(t *testing.T) { +func TestGetBlobs(t *testing.T) { assert, require := assert.New(t), require.New(t) ch := make(chan Announcements, 5) coreDB := temporaltest.NewTestDB(t, datadir.New(t.TempDir()))