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-
2722type 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
301335func (h * Handler ) Call (funcCall * FunctionCall , id * BlockID ) ([]* felt.Felt , * jsonrpc.Error ) {
302336 state , closer , rpcErr := h .stateByBlockID (id )
0 commit comments