Skip to content

Commit 077cff4

Browse files
authored
fix(rpc): relay to sequencer tracing blocks from 1943705 to 1952704 (#3103)
* fix(rpc): relay to sequencer tracing blocks from 1943705 to 1952704 * chore: change cond logic * refactor: update v8 as well * chore: lint * fix: add 0.13.1.1 back in
1 parent b8a2459 commit 077cff4

File tree

4 files changed

+227
-153
lines changed

4 files changed

+227
-153
lines changed

core/block.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type Header struct {
3030
EventCount uint64
3131
// The time the sequencer created this block before executing transactions
3232
Timestamp uint64
33+
// Todo(rdr): It makes more sense for Protocol version to be stored in semver.Version instead
3334
// The version of the Starknet protocol used when creating this block
3435
ProtocolVersion string
3536
// Bloom filter on the events emitted this block

rpc/v8/trace.go

Lines changed: 105 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"slices"
99
"strconv"
1010

11-
"github.com/Masterminds/semver/v3"
1211
"github.com/NethermindEth/juno/blockchain"
1312
"github.com/NethermindEth/juno/core"
1413
"github.com/NethermindEth/juno/core/felt"
@@ -20,10 +19,6 @@ import (
2019
"github.com/NethermindEth/juno/vm"
2120
)
2221

23-
var traceFallbackVersion = semver.MustParse("0.13.1")
24-
25-
const excludedVersion = "0.13.1.1"
26-
2722
type TransactionTrace struct {
2823
Type TransactionType `json:"type"`
2924
ValidateInvocation *FunctionInvocation `json:"validate_invocation,omitempty"`
@@ -119,70 +114,60 @@ func (h *Handler) TraceBlockTransactions(
119114
) ([]TracedBlockTransaction, http.Header, *jsonrpc.Error) {
120115
block, rpcErr := h.blockByID(id)
121116
if rpcErr != nil {
122-
httpHeader := http.Header{}
123-
httpHeader.Set(ExecutionStepsHeader, "0")
124-
return nil, httpHeader, rpcErr
117+
return nil, defaultExecutionHeader(), rpcErr
125118
}
126119

127120
return h.traceBlockTransactions(ctx, block)
128121
}
129122

130-
//nolint:funlen,gocyclo
131-
func (h *Handler) traceBlockTransactions(ctx context.Context, block *core.Block) ([]TracedBlockTransaction, http.Header, *jsonrpc.Error) {
132-
httpHeader := http.Header{}
133-
httpHeader.Set(ExecutionStepsHeader, "0")
134-
123+
// traceBlockTransactions gets the trace for a block. The block will always be traced locally except
124+
// on specific case such as with Starknet version 0.13.2 or lower or when it is certain range
125+
func (h *Handler) traceBlockTransactions(
126+
ctx context.Context, block *core.Block,
127+
) ([]TracedBlockTransaction, http.Header, *jsonrpc.Error) {
135128
isPending := block.Hash == nil
136129
if !isPending {
137-
if blockVer, err := core.ParseBlockVersion(block.ProtocolVersion); err != nil {
138-
return nil, httpHeader, rpccore.ErrUnexpectedError.CloneWithData(err.Error())
139-
} else if blockVer.LessThanEqual(traceFallbackVersion) && block.ProtocolVersion != excludedVersion {
140-
// version <= 0.13.1 and not 0.13.1.1 fetch blocks from feeder gateway
141-
result, err := h.fetchTraces(ctx, block.Hash)
142-
if err != nil {
143-
return nil, httpHeader, err
144-
}
145-
146-
// fgw doesn't provide this data in traces endpoint. So, we get it from our block receipts
147-
txTotalGasConsumed := make(map[felt.Felt]core.GasConsumed, len(block.Receipts))
148-
for _, receipt := range block.Receipts {
149-
if receipt.ExecutionResources == nil {
150-
continue
151-
}
152-
153-
if receiptTGS := receipt.ExecutionResources.TotalGasConsumed; receiptTGS != nil {
154-
tgs := core.GasConsumed{
155-
L1Gas: receiptTGS.L1Gas,
156-
L1DataGas: receiptTGS.L1DataGas,
157-
L2Gas: receiptTGS.L2Gas,
158-
}
159-
txTotalGasConsumed[*receipt.TransactionHash] = tgs
160-
}
161-
}
162-
163-
// For every trace in block, add execution resources on root level
164-
for index, trace := range result {
165-
tgs := txTotalGasConsumed[*trace.TransactionHash]
166-
167-
result[index].TraceRoot.ExecutionResources = &ExecutionResources{
168-
InnerExecutionResources: InnerExecutionResources{
169-
L1Gas: tgs.L1Gas,
170-
L2Gas: tgs.L2Gas,
171-
},
172-
L1DataGas: tgs.L1DataGas,
173-
}
174-
}
175-
176-
return result, httpHeader, err
130+
// Check if it was already traced
131+
traces, hit := h.blockTraceCache.Get(rpccore.TraceCacheKey{BlockHash: *block.Hash})
132+
if hit {
133+
return traces, defaultExecutionHeader(), nil
177134
}
178135

179-
if trace, hit := h.blockTraceCache.Get(rpccore.TraceCacheKey{
180-
BlockHash: *block.Hash,
181-
}); hit {
182-
return trace, httpHeader, nil
136+
// Check if the trace should be provided by the feeder gateway
137+
blockVer, err := core.ParseBlockVersion(block.ProtocolVersion)
138+
if err != nil {
139+
return nil,
140+
defaultExecutionHeader(),
141+
rpccore.ErrUnexpectedError.CloneWithData(err.Error())
142+
}
143+
// We rely on the feeder gateway for Starknet version strictly older than "0.13.1.1"
144+
fetchFromFeederGW := blockVer.LessThan(core.Ver0_13_2) &&
145+
block.ProtocolVersion != "0.13.1.1"
146+
// This specific block range caused a re-org, also related with Cairo 0 and we have to
147+
// depend on the Sequencer to provide the correct traces
148+
fetchFromFeederGW = fetchFromFeederGW ||
149+
(block.Number >= 1943705 &&
150+
block.Number <= 1952704 &&
151+
*h.bcReader.Network() == utils.Mainnet)
152+
153+
if fetchFromFeederGW {
154+
traces, err := h.fetchTracesFromFeederGateway(ctx, block)
155+
if err != nil {
156+
return nil, defaultExecutionHeader(), err
157+
}
158+
h.blockTraceCache.Add(rpccore.TraceCacheKey{BlockHash: *block.Hash}, traces)
159+
return traces, defaultExecutionHeader(), nil
183160
}
184161
}
185162

163+
return h.traceBlockTransactionWithVM(block)
164+
}
165+
166+
// `traceBlockTransactionWithVM` traces a block and stores it in the block cache
167+
func (h *Handler) traceBlockTransactionWithVM(block *core.Block) (
168+
[]TracedBlockTransaction, http.Header, *jsonrpc.Error,
169+
) {
170+
httpHeader := defaultExecutionHeader()
186171
state, closer, err := h.bcReader.StateAtBlockHash(block.ParentHash)
187172
if err != nil {
188173
return nil, httpHeader, rpccore.ErrBlockNotFound
@@ -193,6 +178,8 @@ func (h *Handler) traceBlockTransactions(ctx context.Context, block *core.Block)
193178
headState core.StateReader
194179
headStateCloser blockchain.StateCloser
195180
)
181+
182+
isPending := block.Hash == nil
196183
if isPending {
197184
headState, headStateCloser, err = h.PendingState()
198185
} else {
@@ -265,38 +252,85 @@ func (h *Handler) traceBlockTransactions(ctx context.Context, block *core.Block)
265252
}
266253

267254
if !isPending {
268-
h.blockTraceCache.Add(rpccore.TraceCacheKey{
269-
BlockHash: *block.Hash,
270-
}, result)
255+
h.blockTraceCache.Add(rpccore.TraceCacheKey{BlockHash: *block.Hash}, result)
271256
}
272257

273258
return result, httpHeader, nil
274259
}
275260

276-
func (h *Handler) fetchTraces(ctx context.Context, blockHash *felt.Felt) ([]TracedBlockTransaction, *jsonrpc.Error) {
277-
blockID := BlockIDFromHash(blockHash)
278-
rpcBlock, err := h.BlockWithTxs(&blockID)
279-
if err != nil {
280-
return nil, err
261+
func (h *Handler) fetchTracesFromFeederGateway(
262+
ctx context.Context, block *core.Block,
263+
) ([]TracedBlockTransaction, *jsonrpc.Error) {
264+
// todo(rdr): this feels unnatural, why if I have the `core.Block` should I still
265+
// try to go for the rpcBlock? Ideally we extract all the info directly from `core.Block`
266+
blockID := BlockIDFromHash(block.Hash)
267+
rpcBlock, rpcErr := h.BlockWithTxs(&blockID)
268+
if rpcErr != nil {
269+
return nil, rpcErr
281270
}
282271

283272
if h.feederClient == nil {
284273
return nil, rpccore.ErrInternal.CloneWithData("no feeder client configured")
285274
}
286275

287-
blockTrace, fErr := h.feederClient.BlockTrace(ctx, blockHash.String())
288-
if fErr != nil {
289-
return nil, rpccore.ErrUnexpectedError.CloneWithData(fErr.Error())
276+
blockTrace, err := h.feederClient.BlockTrace(ctx, block.Hash.String())
277+
if err != nil {
278+
return nil, rpccore.ErrUnexpectedError.CloneWithData(err.Error())
290279
}
291280

292-
traces, aErr := AdaptFeederBlockTrace(rpcBlock, blockTrace)
293-
if aErr != nil {
294-
return nil, rpccore.ErrUnexpectedError.CloneWithData(aErr.Error())
281+
traces, err := AdaptFeederBlockTrace(rpcBlock, blockTrace)
282+
if err != nil {
283+
return nil, rpccore.ErrUnexpectedError.CloneWithData(err.Error())
295284
}
296285

286+
traces = fillFeederGatewayData(traces, block.Receipts)
287+
297288
return traces, nil
298289
}
299290

291+
// `fillFeederGatewayData` mutates the `traces` argument and fill it with the data from `receipts` which
292+
// the Feeder Gateway doesn't provide by default
293+
func fillFeederGatewayData(
294+
traces []TracedBlockTransaction, receipts []*core.TransactionReceipt,
295+
) []TracedBlockTransaction {
296+
totalGasConsumed := make(map[felt.Felt]core.GasConsumed, len(receipts))
297+
for _, re := range receipts {
298+
if re.ExecutionResources == nil {
299+
continue
300+
}
301+
302+
if reGasConsumed := re.ExecutionResources.TotalGasConsumed; reGasConsumed != nil {
303+
tgs := core.GasConsumed{
304+
L1Gas: reGasConsumed.L1Gas,
305+
L1DataGas: reGasConsumed.L1DataGas,
306+
L2Gas: reGasConsumed.L2Gas,
307+
}
308+
totalGasConsumed[*re.TransactionHash] = tgs
309+
}
310+
}
311+
312+
// For every trace in block, add execution resources on root level
313+
for index, trace := range traces {
314+
tgs := totalGasConsumed[*trace.TransactionHash]
315+
316+
traces[index].TraceRoot.ExecutionResources = &ExecutionResources{
317+
InnerExecutionResources: InnerExecutionResources{
318+
L1Gas: tgs.L1Gas,
319+
L2Gas: tgs.L2Gas,
320+
},
321+
L1DataGas: tgs.L1DataGas,
322+
}
323+
}
324+
325+
return traces
326+
}
327+
328+
func defaultExecutionHeader() http.Header {
329+
header := http.Header{}
330+
header.Set(ExecutionStepsHeader, "0")
331+
return header
332+
}
333+
300334
// https://github.com/starkware-libs/starknet-specs/blob/e0b76ed0d8d8eba405e182371f9edac8b2bcbc5a/api/starknet_api_openrpc.json#L401-L445
301335
func (h *Handler) Call(funcCall *FunctionCall, id *BlockID) ([]*felt.Felt, *jsonrpc.Error) {
302336
state, closer, rpcErr := h.stateByBlockID(id)

rpc/v9/handlers.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@ type Handler struct {
4343
idgen func() string
4444
subscriptions stdsync.Map // map[string]*subscription
4545

46-
blockTraceCache *lru.Cache[rpccore.TraceCacheKey, []TracedBlockTransaction]
46+
// todo(rdr): why do we have the `TraceCacheKey` type and why it feels uncomfortable
47+
// to use. It makes no sense, why not use `Felt` or `Hash` directly?
48+
blockTraceCache *lru.Cache[rpccore.TraceCacheKey, []TracedBlockTransaction]
49+
// todo(rdr): Can this cache be genericified and can it be applied to the `blockTraceCache`
4750
submittedTransactionsCache *rpccore.TransactionCache
4851

4952
filterLimit uint

0 commit comments

Comments
 (0)