diff --git a/adapters/sn2core/sn2core.go b/adapters/sn2core/sn2core.go index 65468d1fa5..ce6e566f64 100644 --- a/adapters/sn2core/sn2core.go +++ b/adapters/sn2core/sn2core.go @@ -513,22 +513,11 @@ func IsCandidateTx(response *starknet.PreConfirmedBlock, id int) bool { return response.TransactionStateDiffs[id] == nil || response.Receipts[id] == nil } +// AdaptPreConfirmedBlock adapts a pre_confirmed block into a pending.PreConfirmed. func AdaptPreConfirmedBlock( response *starknet.PreConfirmedBlock, number uint64, ) (pending.PreConfirmed, error) { - if response.Status != "PRE_CONFIRMED" { - return pending.PreConfirmed{}, errors.New("invalid status for pre_confirmed block") - } - - isInvalidPayloadSizes := len(response.Transactions) != len(response.TransactionStateDiffs) || - len(response.Transactions) != len(response.Receipts) - if isInvalidPayloadSizes { - return pending.PreConfirmed{}, errors.New( - "invalid sizes of transactions, state diffs and receipts", - ) - } - candidateCount := 0 for i := range len(response.Transactions) { if IsCandidateTx(response, i) { @@ -626,8 +615,6 @@ func AdaptPreConfirmedBlock( // // Returns [ErrPreConfirmedIdentifierMismatch] if current.BlockIdentifier // differs from delta.BlockIdentifier. -// -//nolint:funlen // Let's simplify it later. func AdaptPreConfirmedWithDelta( current *pending.PreConfirmed, delta *starknet.PreConfirmedDeltaUpdate, @@ -635,13 +622,6 @@ func AdaptPreConfirmedWithDelta( if current.BlockIdentifier != delta.BlockIdentifier { return pending.PreConfirmed{}, ErrPreConfirmedIdentifierMismatch } - if len(delta.Transactions) != len(delta.Receipts) || - len(delta.Transactions) != len(delta.TransactionStateDiffs) { - return pending.PreConfirmed{}, fmt.Errorf( - "mismatched lengths: transactions (%d), state diffs (%d), receipts (%d) must be equal", - len(delta.Transactions), len(delta.TransactionStateDiffs), len(delta.Receipts), - ) - } existingTxs := current.Block.Transactions existingReceipts := current.Block.Receipts @@ -661,27 +641,11 @@ func AdaptPreConfirmedWithDelta( nextStateDiff.Merge(current.StateUpdate.StateDiff) addedEventCount := uint64(0) - var candidateTxs []core.Transaction for i := range delta.Transactions { tx, err := AdaptTransaction(&delta.Transactions[i]) if err != nil { return pending.PreConfirmed{}, err } - - // Bug workaround: the delta endpoint may still return txs that carry a null - // state diff / receipt, similar to candidate txs. Store them as candidate txs - // instead of dereferencing nil and panicking, and reducing the slice lengths - // to match the number of txs that actually have state diff / receipt. - // Ref: https://demerzelsolutions.slack.com/archives/C02UDC3AZHQ/p1781793289647619 - if delta.TransactionStateDiffs[i] == nil || delta.Receipts[i] == nil { - candidateTxs = append(candidateTxs, tx) - mergedTxs = mergedTxs[:len(mergedTxs)-1] - mergedReceipts = mergedReceipts[:len(mergedReceipts)-1] - mergedStateDiffs = mergedStateDiffs[:len(mergedStateDiffs)-1] - n-- - addedCount-- - continue - } mergedTxs[n+i] = tx sd, err := AdaptStateDiff(delta.TransactionStateDiffs[i]) @@ -720,7 +684,6 @@ func AdaptPreConfirmedWithDelta( next.Block = &nextBlock next.StateUpdate = &nextStateUpdate next.TransactionStateDiffs = mergedStateDiffs - next.CandidateTxs = candidateTxs return next, nil } diff --git a/adapters/sn2core/sn2core_test.go b/adapters/sn2core/sn2core_test.go index 3e13efca73..31bc029cef 100644 --- a/adapters/sn2core/sn2core_test.go +++ b/adapters/sn2core/sn2core_test.go @@ -957,48 +957,6 @@ func TestAdaptPreConfirmedWithDelta(t *testing.T) { _, err := sn2core.AdaptPreConfirmedWithDelta(current, delta) require.Error(t, err) }) - - t.Run("handles tx carrying nil state diff/receipt in delta", func(t *testing.T) { - tx := starknet.Transaction{ - Type: starknet.TxnInvoke, CallData: &[]*felt.Felt{}, Signature: &[]*felt.Felt{}, - } - receipt := starknet.TransactionReceipt{} - stateDiff := starknet.StateDiff{} - - // block with 1 "normal" tx - current := &pending.PreConfirmed{ - BlockIdentifier: "round-a", - Block: &core.Block{ - Header: &core.Header{ - TransactionCount: 1, - EventCount: 0, - EventsBloom: core.EventsBloom(nil), - }, - Transactions: []core.Transaction{&core.InvokeTransaction{}}, - Receipts: []*core.TransactionReceipt{{}}, - }, - StateUpdate: &core.StateUpdate{StateDiff: &core.StateDiff{}}, - TransactionStateDiffs: []*core.StateDiff{{}}, - } - // Delta with 2 valid txs and 1 tx without state diff and receipt. - // Must not panic; the "buggy" tx should be stored in the CandidateTxs slice, - // and the tx count should not include it. - delta := &starknet.PreConfirmedDeltaUpdate{ - BlockIdentifier: "round-a", - Transactions: []starknet.Transaction{tx, tx, tx}, - Receipts: []*starknet.TransactionReceipt{&receipt, nil, &receipt}, - TransactionStateDiffs: []*starknet.StateDiff{&stateDiff, nil, &stateDiff}, - } - - next, err := sn2core.AdaptPreConfirmedWithDelta(current, delta) - require.NoError(t, err) - newLen := len(current.Block.Transactions) + 2 // 2 valid txs - assert.Equal(t, newLen, len(next.Block.Transactions)) - assert.Equal(t, newLen, len(next.Block.Receipts)) - assert.Equal(t, newLen, len(next.TransactionStateDiffs)) - assert.Equal(t, uint64(newLen), next.Block.TransactionCount) - assert.Equal(t, 1, len(next.CandidateTxs)) - }) } // mustFetchPreConfirmedBlock fetches a real pre_confirmed Full from the test client. diff --git a/clients/feeder/feeder.go b/clients/feeder/feeder.go index 4488e2d4c2..03338a6e42 100644 --- a/clients/feeder/feeder.go +++ b/clients/feeder/feeder.go @@ -6,6 +6,7 @@ import ( "errors" "io" "net/http" + "net/url" "strconv" "sync/atomic" "time" @@ -29,7 +30,7 @@ var ErrDeprecatedCompiledClass = errors.New("deprecated compiled class") type Backoff func(wait time.Duration) time.Duration type Client struct { - url string + url *url.URL client *http.Client backoff Backoff maxRetries int @@ -143,8 +144,9 @@ func NopBackoff(d time.Duration) time.Duration { return 0 } -func NewClient(clientURL string) *Client { +func NewClient(clientURL *url.URL) *Client { defaultTimeouts := getDefaultFixedTimeouts() + client := &Client{ url: clientURL, client: http.DefaultClient, @@ -160,7 +162,7 @@ func NewClient(clientURL string) *Client { } // get performs a "GET" http request with the given URL and returns the response body -func (c *Client) get(ctx context.Context, queryURL string) (io.ReadCloser, error) { +func (c *Client) get(ctx context.Context, queryURL *url.URL) (io.ReadCloser, error) { var res *http.Response var err error wait := time.Duration(0) @@ -170,7 +172,7 @@ func (c *Client) get(ctx context.Context, queryURL string) (io.ReadCloser, error return nil, ctx.Err() case <-time.After(wait): var req *http.Request - req, err = http.NewRequestWithContext(ctx, http.MethodGet, queryURL, http.NoBody) + req, err = http.NewRequestWithContext(ctx, http.MethodGet, queryURL.String(), http.NoBody) if err != nil { return nil, err } @@ -239,7 +241,7 @@ func (c *Client) get(ctx context.Context, queryURL string) (io.ReadCloser, error } func (c *Client) Block(ctx context.Context, blockID string) (*starknet.Block, error) { - queryURL := c.buildQueryString("get_block", map[string]string{ + queryURL := buildQueryString(c.url, "get_block", map[string]string{ blockNumberArg: blockID, }) @@ -249,7 +251,7 @@ func (c *Client) Block(ctx context.Context, blockID string) (*starknet.Block, er func (c *Client) BlockHeader( ctx context.Context, blockID string, ) (starknet.BlockHeader, error) { - queryURL := c.buildQueryString("get_block", map[string]string{ + queryURL := buildQueryString(c.url, "get_block", map[string]string{ blockNumberArg: blockID, "headerOnly": trueStr, }) @@ -262,7 +264,7 @@ func (c *Client) BlockHeader( } func (c *Client) BlockTrace(ctx context.Context, blockHash string) (*starknet.BlockTrace, error) { - queryURL := c.buildQueryString("get_block_traces", map[string]string{ + queryURL := buildQueryString(c.url, "get_block_traces", map[string]string{ "blockHash": blockHash, }) @@ -273,7 +275,7 @@ func (c *Client) CasmClassDefinition( ctx context.Context, classHash *felt.Felt, ) (*starknet.CasmClass, error) { - queryURL := c.buildQueryString("get_compiled_class_by_class_hash", map[string]string{ + queryURL := buildQueryString(c.url, "get_compiled_class_by_class_hash", map[string]string{ classHashArg: classHash.String(), blockNumberArg: "latest", }) @@ -303,7 +305,7 @@ func (c *Client) CasmClassDefinition( func (c *Client) ClassDefinition( ctx context.Context, classHash *felt.Felt, ) (*starknet.ClassDefinition, error) { - queryURL := c.buildQueryString("get_class_by_hash", map[string]string{ + queryURL := buildQueryString(c.url, "get_class_by_hash", map[string]string{ classHashArg: classHash.String(), blockNumberArg: "latest", }) @@ -312,7 +314,7 @@ func (c *Client) ClassDefinition( } func (c *Client) FeeTokenAddresses(ctx context.Context) (starknet.FeeTokenAddresses, error) { - queryURL := c.buildQueryString("get_contract_addresses", nil) + queryURL := buildQueryString(c.url, "get_contract_addresses", nil) addresses, err := doRequest[starknet.FeeTokenAddresses](ctx, c, queryURL) if err != nil { @@ -322,18 +324,18 @@ func (c *Client) FeeTokenAddresses(ctx context.Context) (starknet.FeeTokenAddres } func (c *Client) PublicKey(ctx context.Context) (*felt.Felt, error) { - queryURL := c.buildQueryString("get_public_key", nil) + queryURL := buildQueryString(c.url, "get_public_key", nil) // public key is a hex string - publicKey, err := doRequest[string](ctx, c, queryURL) + publicKey, err := doRequest[starknet.PublicKey](ctx, c, queryURL) if err != nil { return nil, err } - return felt.NewFromString[felt.Felt](*publicKey) + return felt.NewFromString[felt.Felt](string(*publicKey)) } func (c *Client) Signature(ctx context.Context, blockID string) (*starknet.Signature, error) { - queryURL := c.buildQueryString("get_signature", map[string]string{ + queryURL := buildQueryString(c.url, "get_signature", map[string]string{ blockNumberArg: blockID, }) @@ -341,7 +343,7 @@ func (c *Client) Signature(ctx context.Context, blockID string) (*starknet.Signa } func (c *Client) StateUpdate(ctx context.Context, blockID string) (*starknet.StateUpdate, error) { - queryURL := c.buildQueryString("get_state_update", map[string]string{ + queryURL := buildQueryString(c.url, "get_state_update", map[string]string{ blockNumberArg: blockID, }) @@ -349,7 +351,7 @@ func (c *Client) StateUpdate(ctx context.Context, blockID string) (*starknet.Sta } func (c *Client) StateUpdateWithBlock(ctx context.Context, blockID string) (*starknet.StateUpdateWithBlock, error) { - queryURL := c.buildQueryString("get_state_update", map[string]string{ + queryURL := buildQueryString(c.url, "get_state_update", map[string]string{ blockNumberArg: blockID, "includeBlock": trueStr, }) @@ -361,7 +363,7 @@ func (c *Client) StateUpdateWithBlockAndSignature( ctx context.Context, blockID string, ) (*starknet.StateUpdateWithBlockAndSignature, error) { - queryURL := c.buildQueryString("get_state_update", map[string]string{ + queryURL := buildQueryString(c.url, "get_state_update", map[string]string{ blockNumberArg: blockID, "includeBlock": trueStr, "includeSignature": trueStr, @@ -379,7 +381,7 @@ func (c *Client) DeprecatedPreConfirmedBlock( ctx context.Context, blockNumber string, ) (*starknet.DeprecatedPreConfirmedBlock, error) { - queryURL := c.buildQueryString("get_preconfirmed_block", map[string]string{ + queryURL := buildQueryString(c.url, "get_preconfirmed_block", map[string]string{ blockNumberArg: blockNumber, }) @@ -403,7 +405,7 @@ func (c *Client) PreConfirmedBlockWithIdentifier( if blockIdentifier == "" { blockIdentifier = PreConfirmedBlankIdentifier } - queryURL := c.buildQueryString("get_preconfirmed_block", map[string]string{ + queryURL := buildQueryString(c.url, "get_preconfirmed_block", map[string]string{ blockNumberArg: blockNumber, "blockIdentifier": blockIdentifier, "knownTransactionCount": strconv.FormatUint(knownTransactionCount, 10), @@ -421,7 +423,7 @@ func (c *Client) PreConfirmedBlockWithIdentifier( func (c *Client) Transaction( ctx context.Context, transactionHash *felt.Felt, ) (*starknet.DeprecatedTransactionStatus, error) { - queryURL := c.buildQueryString("get_transaction", map[string]string{ + queryURL := buildQueryString(c.url, "get_transaction", map[string]string{ "transactionHash": transactionHash.String(), }) @@ -434,23 +436,9 @@ func (c *Client) TransactionStatus( ctx context.Context, transactionHash *felt.Felt, ) (*starknet.TransactionStatus, error) { - queryURL := c.buildQueryString("get_transaction_status", map[string]string{ + queryURL := buildQueryString(c.url, "get_transaction_status", map[string]string{ "transactionHash": transactionHash.String(), }) return doRequest[starknet.TransactionStatus](ctx, c, queryURL) } - -func doRequest[T any](ctx context.Context, client *Client, queryURL string) (*T, error) { - result := new(T) - body, err := client.get(ctx, queryURL) - if err != nil { - return nil, err - } - defer body.Close() - - if err = json.NewDecoder(body).Decode(result); err != nil { - return nil, err - } - return result, nil -} diff --git a/clients/feeder/feeder_test.go b/clients/feeder/feeder_test.go index 3567c88da1..645fb016aa 100644 --- a/clients/feeder/feeder_test.go +++ b/clients/feeder/feeder_test.go @@ -3,6 +3,7 @@ package feeder_test import ( "net/http" "net/http/httptest" + "net/url" "strconv" "testing" "time" @@ -708,17 +709,6 @@ func TestClassV1Unmarshal(t *testing.T) { } } -func TestBuildQueryString_WithErrorUrl(t *testing.T) { - defer func() { - if r := recover(); r == nil { - require.Fail(t, "The code did not panic") - } - }() - baseURL := "https\t://mock_feeder.io" - client := feeder.NewClient(baseURL).WithUserAgent(ua) - _, _ = client.Block(t.Context(), strconv.Itoa(0)) -} - func TestStateUpdate(t *testing.T) { client := feeder.NewTestClient(t, &networks.Mainnet) @@ -856,8 +846,10 @@ func TestHttpError(t *testing.T) { w.WriteHeader(http.StatusInternalServerError) })) t.Cleanup(srv.Close) + feederURL, err := url.Parse(srv.URL) + require.NoError(t, err) client := feeder. - NewClient(srv.URL). + NewClient(feederURL). WithBackoff(feeder.NopBackoff). WithMaxRetries(maxRetries). WithUserAgent(ua) @@ -896,13 +888,15 @@ func TestBackoffFailure(t *testing.T) { })) t.Cleanup(srv.Close) + feederURL, err := url.Parse(srv.URL) + require.NoError(t, err) c := feeder. - NewClient(srv.URL). + NewClient(feederURL). WithBackoff(feeder.NopBackoff). WithMaxRetries(maxRetries). WithUserAgent(ua) - _, err := c.Block(t.Context(), strconv.Itoa(0)) + _, err = c.Block(t.Context(), strconv.Itoa(0)) assert.EqualError(t, err, "500 Internal Server Error") assert.Equal(t, maxRetries, try-1) // we have retried `maxRetries` times } @@ -1186,7 +1180,9 @@ func TestClientRetryBehavior(t *testing.T) { })) defer srv.Close() - client := feeder.NewClient(srv.URL). + feederURL, err := url.Parse(srv.URL) + require.NoError(t, err) + client := feeder.NewClient(feederURL). WithTimeouts( []time.Duration{250 * time.Millisecond, 750 * time.Millisecond, 2 * time.Second}, false, @@ -1209,12 +1205,14 @@ func TestClientRetryBehavior(t *testing.T) { })) defer srv.Close() - client := feeder.NewClient(srv.URL). + feederURL, err := url.Parse(srv.URL) + require.NoError(t, err) + client := feeder.NewClient(feederURL). WithTimeouts([]time.Duration{250 * time.Millisecond}, false). WithMaxRetries(2). WithBackoff(feeder.NopBackoff) - _, err := client.Block(t.Context(), "1") + _, err = client.Block(t.Context(), "1") require.Error(t, err) require.Equal(t, 3, requestCount) }) @@ -1235,7 +1233,9 @@ func TestClientRetryBehavior(t *testing.T) { })) defer srv.Close() - client := feeder.NewClient(srv.URL). + feederURL, err := url.Parse(srv.URL) + require.NoError(t, err) + client := feeder.NewClient(feederURL). WithTimeouts([]time.Duration{250 * time.Millisecond, 750 * time.Millisecond}, false). WithMaxRetries(1). WithBackoff(feeder.NopBackoff) @@ -1288,3 +1288,57 @@ func TestBlockHeader(t *testing.T) { assert.Zero(t, header) }) } + +// clientServingBody spins up a test feeder server that answers every request +// with the given body, and returns a client wired to it. +func clientServingBody(t *testing.T, body string) *feeder.Client { + t.Helper() + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write([]byte(body)) + })) + t.Cleanup(srv.Close) + feederURL, err := url.Parse(srv.URL) + require.NoError(t, err) + return feeder.NewClient(feederURL).WithBackoff(feeder.NopBackoff).WithMaxRetries(0) +} + +// TestFeederValidation checks that the response +// validation wired into [doRequest] works as expected. +func TestFeederValidation(t *testing.T) { + t.Run("valid response is returned", func(t *testing.T) { + body := `{ + "changed": true, + "block_identifier": "abc123", + "transactions": [{"version": "0x3"}], + "transaction_receipts": [{"transaction_hash": "0x123"}], + "transaction_state_diffs": [{"storage_diffs": {}}] + }` + client := clientServingBody(t, body) + + update, err := client.PreConfirmedBlockWithIdentifier(t.Context(), "", "", 0) + require.NoError(t, err) + _, ok := update.(starknet.PreConfirmedDeltaUpdate) + require.True(t, ok) + }) + + t.Run("invalid response fails validation", func(t *testing.T) { + // invalid response for the preconfirmed endpoint: delta cannot have zero transactions + body := `{ + "changed": true, + "block_identifier": "abc123", + "transactions": [], + "transaction_receipts": [], + "transaction_state_diffs": [] + }` + client := clientServingBody(t, body) + + update, err := client.PreConfirmedBlockWithIdentifier(t.Context(), "", "", 0) + require.Error(t, err) + require.Nil(t, update) + assert.ErrorIs(t, err, feeder.ErrInvalidFeederResponse) + assert.ErrorContains(t, err, + "get_preconfirmed_block?blockIdentifier=0x0&blockNumber=&knownTransactionCount=0", + "error must contain the query URL that returned the invalid response", + ) + }) +} diff --git a/clients/feeder/request.go b/clients/feeder/request.go new file mode 100644 index 0000000000..3d42a6a256 --- /dev/null +++ b/clients/feeder/request.go @@ -0,0 +1,60 @@ +package feeder + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/url" +) + +var ErrInvalidFeederResponse = errors.New("invalid feeder response") + +// Validatable is a generic constraint satisfied by a pointer type *T whose +// underlying value type T can validate itself. Implementers provide a +// Validate method with a pointer receiver that checks the receiver's fields +// and returns an error if validation fails. +type Validatable[T any] interface { + *T + Validate() error +} + +func doRequest[T any, V Validatable[T]]( + ctx context.Context, + client *Client, + fullURL *url.URL, +) (*T, error) { + var result T + body, err := client.get(ctx, fullURL) + if err != nil { + return nil, err + } + defer body.Close() + + if err = json.NewDecoder(body).Decode(&result); err != nil { + return nil, err + } + + err = V(&result).Validate() + if err != nil { + return nil, errors.Join( + ErrInvalidFeederResponse, + fmt.Errorf("querying %s: %w", fullURL, err), + ) + } + + return &result, nil +} + +// buildQueryString builds the full URL with encoded parameters +func buildQueryString(baseURL *url.URL, endpoint string, args map[string]string) *url.URL { + base := baseURL.JoinPath(endpoint) + + params := url.Values{} + for k, v := range args { + params.Add(k, v) + } + base.RawQuery = params.Encode() + + return base +} diff --git a/clients/feeder/test_feeder.go b/clients/feeder/test_feeder.go index 5c03872a0d..67171368ae 100644 --- a/clients/feeder/test_feeder.go +++ b/clients/feeder/test_feeder.go @@ -24,7 +24,13 @@ func NewTestClient(t testing.TB, network *networks.Network) *Client { ua := "Juno/v0.0.1-test Starknet Implementation" apiKey := "API_KEY" - c := NewClient(srv.URL).WithBackoff(NopBackoff).WithMaxRetries(0).WithUserAgent(ua).WithAPIKey(apiKey) + feederURL, err := url.Parse(srv.URL) + require.NoError(t, err) + c := NewClient(feederURL). + WithBackoff(NopBackoff). + WithMaxRetries(0). + WithUserAgent(ua). + WithAPIKey(apiKey) c.client = &http.Client{ Transport: &http.Transport{ // On macOS tests often fail with the following error: @@ -177,24 +183,6 @@ func handleNotFound(dir, queryArg string, w http.ResponseWriter) { } } -// buildQueryString builds the query url with encoded parameters -func (c *Client) buildQueryString(endpoint string, args map[string]string) string { - base, err := url.Parse(c.url) - if err != nil { - panic("Malformed feeder base URL") - } - - base.Path += endpoint - - params := url.Values{} - for k, v := range args { - params.Add(k, v) - } - base.RawQuery = params.Encode() - - return base.String() -} - func findTargetDirectory(targetRelPath string) (string, error) { root, err := os.Getwd() if err != nil { diff --git a/cmd/juno/juno.go b/cmd/juno/juno.go index 5fec243f6a..6a60c5ed6f 100644 --- a/cmd/juno/juno.go +++ b/cmd/juno/juno.go @@ -2,9 +2,11 @@ package main import ( "context" + "errors" "fmt" "math" "math/big" + "net/url" "os" "os/signal" "path/filepath" @@ -430,10 +432,20 @@ func NewCmd(config *node.Config, run func(*cobra.Command, []string) error) *cobr return fmt.Errorf("invalid %s:%v, must be uint array of length 2 (e.g. `0,100`)", cnUnverifiableRangeF, unverifRange) } + feederURL := v.GetString(cnFeederURLF) + gatewayURL := v.GetString(cnGatewayURLF) + + if err := validateHTTPURL(feederURL); err != nil { + return fmt.Errorf("invalid feeder URL %s: %w", feederURL, err) + } + if err := validateHTTPURL(gatewayURL); err != nil { + return fmt.Errorf("invalid gateway URL %s: %w", gatewayURL, err) + } + config.Network = networks.Network{ Name: v.GetString(cnNameF), - FeederURL: v.GetString(cnFeederURLF), - GatewayURL: v.GetString(cnGatewayURLF), + FeederURL: feederURL, + GatewayURL: gatewayURL, L1ChainID: l1ChainID, L2ChainID: v.GetString(cnL2ChainIDF), CoreContractAddress: eth.AddressFromString(v.GetString(cnCoreContractAddressF)), @@ -665,3 +677,18 @@ func NewCmd(config *node.Config, run func(*cobra.Command, []string) error) *cobr return junoCmd } + +func validateHTTPURL(rawURL string) error { + // rejects relative / scheme-less strings + u, err := url.ParseRequestURI(rawURL) + if err != nil { + return err + } + if u.Scheme != "http" && u.Scheme != "https" { + return fmt.Errorf("URL must use http or https scheme, got %s", u.Scheme) + } + if u.Host == "" { + return errors.New("URL must have a host") + } + return nil +} diff --git a/cmd/juno/juno_test.go b/cmd/juno/juno_test.go index c451a61cee..1fc56c04df 100644 --- a/cmd/juno/juno_test.go +++ b/cmd/juno/juno_test.go @@ -39,8 +39,8 @@ func TestConfigPrecedence(t *testing.T) { defaultNetwork := networks.Mainnet defaultCustomNetwork := networks.Network{ Name: "custom", - FeederURL: "awesome_feeder_url", - GatewayURL: "awesome_gateway_url", + FeederURL: "http://awesome.feeder", + GatewayURL: "http://awesome.gateway", L2ChainID: "SN_AWESOME", L1ChainID: new(big.Int).SetUint64(1), CoreContractAddress: defaultCoreContractAddress, @@ -198,7 +198,7 @@ func TestConfigPrecedence(t *testing.T) { inputArgs: []string{ "--log-level", "debug", "--http-port", "4576", "--http-host", "0.0.0.0", "--db-path", "/home/.juno", "--pprof", "--db-cache-size", "1024", - "--cn-name", "custom", "--cn-feeder-url", "awesome_feeder_url", "--cn-gateway-url", "awesome_gateway_url", + "--cn-name", "custom", "--cn-feeder-url", "http://awesome.feeder", "--cn-gateway-url", "http://awesome.gateway", "--cn-l1-chain-id", "0x1", "--cn-l2-chain-id", "SN_AWESOME", "--cn-unverifiable-range", "0,10", "--cn-core-contract-address", "0xc662c410C0ECf747543f5bA90660f6ABeBD9C8c4", @@ -213,8 +213,8 @@ http-port: 4576 db-path: /home/.juno pprof: true cn-name: custom -cn-feeder-url: awesome_feeder_url -cn-gateway-url: awesome_gateway_url +cn-feeder-url: http://awesome.feeder +cn-gateway-url: http://awesome.gateway cn-l2-chain-id: SN_AWESOME cn-l1-chain-id: 0x1 cn-core-contract-address: 0xc662c410C0ECf747543f5bA90660f6ABeBD9C8c4 @@ -909,6 +909,55 @@ func TestUnknownFlagDoesNotPrintUsage(t *testing.T) { assert.NotContains(t, output, "Global Flags:") } +// TestCustomNetworkURLValidation drives URL validation through NewCmd's PreRunE, +// which validates the custom-network feeder/gateway URLs. +func TestCustomNetworkURLValidation(t *testing.T) { + baseArgs := func(feeder, gateway string) []string { + return []string{ + "--cn-name", "custom", + "--cn-feeder-url", feeder, + "--cn-gateway-url", gateway, + "--cn-l1-chain-id", "0x1", "--cn-l2-chain-id", "SN_AWESOME", + "--cn-unverifiable-range", "0,10", + "--cn-core-contract-address", "0xc662c410C0ECf747543f5bA90660f6ABeBD9C8c4", + } + } + + feederErr := "invalid feeder URL" + gatewayErr := "invalid gateway URL" + + tests := map[string]struct { + feeder string + gateway string + errSubstr string + }{ + "both valid http": {feeder: "http://feeder.host", gateway: "http://gateway.host"}, + "both valid https": {feeder: "https://feeder.host", gateway: "https://gateway.host"}, + "invalid feeder": {feeder: "feeder_host", gateway: "http://gateway.host", errSubstr: feederErr}, + "invalid gateway": {feeder: "http://feeder.host", gateway: "gateway_host", errSubstr: gatewayErr}, + "feeder bad scheme": {feeder: "ftp://feeder.host", gateway: "http://gateway.host", errSubstr: feederErr}, + "feeder missing host": {feeder: "http:///feeder", gateway: "http://gateway.host", errSubstr: feederErr}, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + config := new(node.Config) + cmd := juno.NewCmd(config, func(_ *cobra.Command, _ []string) error { return nil }) + cmd.SetArgs(baseArgs(tc.feeder, tc.gateway)) + + err := cmd.ExecuteContext(t.Context()) + if tc.errSubstr != "" { + require.Error(t, err) + assert.ErrorContains(t, err, tc.errSubstr) + return + } + require.NoError(t, err) + assert.Equal(t, tc.feeder, config.Network.FeederURL) + assert.Equal(t, tc.gateway, config.Network.GatewayURL) + }) + } +} + func tempCfgFile(t *testing.T, cfg string) string { t.Helper() diff --git a/node/node.go b/node/node.go index 07faab89a5..1e555f10c8 100644 --- a/node/node.go +++ b/node/node.go @@ -358,7 +358,12 @@ func New(cfg *Config, version string, logLevel *log.Level) (*Node, error) { if err != nil { return nil, fmt.Errorf("invalid gateway timeouts: %w", err) } - client = feeder.NewClient(cfg.Network.FeederURL). + + feederURL, err := url.Parse(cfg.Network.FeederURL) + if err != nil { + return nil, fmt.Errorf("invalid feeder URL %s: %w", cfg.Network.FeederURL, err) + } + client = feeder.NewClient(feederURL). WithUserAgent(ua). WithLogger(logger). WithTimeouts(timeouts, fixed). diff --git a/starknet/block.go b/starknet/block.go index 2f80d32618..67a879a116 100644 --- a/starknet/block.go +++ b/starknet/block.go @@ -14,6 +14,12 @@ type BlockHeader struct { Number uint64 `json:"block_number"` } +// TODO: placeholder for now to avoid compiler errors. A proper validation +// should be implemented in a follow-up PR. +func (val *BlockHeader) Validate() error { + return nil +} + // Block object returned by the feeder in JSON format for "get_block" endpoint type Block struct { Hash *felt.Felt `json:"block_hash"` @@ -61,6 +67,12 @@ func (b *Block) L1GasPriceSTRK() *felt.Felt { return b.GasPriceFRI } +// TODO: placeholder for now to avoid compiler errors. A proper validation +// should be implemented in a follow-up PR. +func (b *Block) Validate() error { + return nil +} + type L1DAMode uint const ( diff --git a/starknet/class.go b/starknet/class.go index 67d738d631..4121415d99 100644 --- a/starknet/class.go +++ b/starknet/class.go @@ -73,6 +73,12 @@ type ClassDefinition struct { Sierra *SierraClass } +// TODO: placeholder for now to avoid compiler errors. A proper validation +// should be implemented in a follow-up PR. +func (c *ClassDefinition) Validate() error { + return nil +} + func (c *ClassDefinition) UnmarshalJSON(data []byte) error { jsonMap := make(map[string]any) if err := json.Unmarshal(data, &jsonMap); err != nil { diff --git a/starknet/deprecated_pre_confirmed_block.go b/starknet/deprecated_pre_confirmed_block.go index d586b212a8..fbad199a19 100644 --- a/starknet/deprecated_pre_confirmed_block.go +++ b/starknet/deprecated_pre_confirmed_block.go @@ -43,3 +43,9 @@ func (b *DeprecatedPreConfirmedBlock) AsUpdate() PreConfirmedUpdate { L1DataGasPrice: b.L1DataGasPrice, } } + +// TODO: placeholder for now to avoid compiler errors. A proper validation +// should be implemented in a follow-up PR. +func (b *DeprecatedPreConfirmedBlock) Validate() error { + return nil +} diff --git a/starknet/fee_token_addresses.go b/starknet/fee_token_addresses.go index ec75823af9..5def780f7b 100644 --- a/starknet/fee_token_addresses.go +++ b/starknet/fee_token_addresses.go @@ -9,3 +9,9 @@ type FeeTokenAddresses struct { StrkL2TokenAddress felt.Felt `json:"strk_l2_token_address"` EthL2TokenAddress felt.Felt `json:"eth_l2_token_address"` } + +// TODO: placeholder for now to avoid compiler errors. A proper validation +// should be implemented in a follow-up PR. +func (val *FeeTokenAddresses) Validate() error { + return nil +} diff --git a/starknet/pre_confirmed_update.go b/starknet/pre_confirmed_update.go index 664015cdb2..af293eab50 100644 --- a/starknet/pre_confirmed_update.go +++ b/starknet/pre_confirmed_update.go @@ -3,6 +3,7 @@ package starknet import ( "encoding/json" "errors" + "fmt" "github.com/NethermindEth/juno/core/felt" ) @@ -20,6 +21,8 @@ type PreConfirmedUpdate interface { // variant discrimination and discards it before decoding into this struct. type PreConfirmedNoChange struct{} +func (PreConfirmedNoChange) isPreConfirmedUpdate() {} + // PreConfirmedDeltaUpdate carries transactions/receipts/state diffs appended since the // caller's known transaction count for the same block_identifier. // @@ -33,6 +36,21 @@ type PreConfirmedDeltaUpdate struct { TransactionStateDiffs []*StateDiff `json:"transaction_state_diffs"` } +func (PreConfirmedDeltaUpdate) isPreConfirmedUpdate() {} + +func (val *PreConfirmedDeltaUpdate) validate() error { + if val.BlockIdentifier == "" { + return errors.New("block_identifier is required") + } + if len(val.Transactions) == 0 { + return errors.New("delta requires at least one transaction") + } + + return validateTxsLength( + val.Transactions, val.Receipts, val.TransactionStateDiffs, + ) +} + // PreConfirmedBlock carries a full pre_confirmed block for a new round. // // Note: the wire JSON also carries a top-level `"changed"` boolean which is not @@ -53,9 +71,63 @@ type PreConfirmedBlock struct { L1DataGasPrice *GasPrice `json:"l1_data_gas_price"` } -func (PreConfirmedNoChange) isPreConfirmedUpdate() {} -func (PreConfirmedDeltaUpdate) isPreConfirmedUpdate() {} -func (PreConfirmedBlock) isPreConfirmedUpdate() {} +func (PreConfirmedBlock) isPreConfirmedUpdate() {} + +func (pb *PreConfirmedBlock) validate() error { + if pb.BlockIdentifier == "" { + return errors.New("block_identifier is required") + } + if pb.Status != "PRE_CONFIRMED" { + return fmt.Errorf("invalid status: %s", pb.Status) + } + if pb.Version == "" { + return errors.New("version is required") + } + if pb.SequencerAddress == nil { + return errors.New("sequencer_address is required") + } + if pb.L1GasPrice == nil { + return errors.New("l1_gas_price is required") + } + if pb.L2GasPrice == nil { + return errors.New("l2_gas_price is required") + } + if pb.L1DataGasPrice == nil { + return errors.New("l1_data_gas_price is required") + } + return validateTxsLength( + pb.Transactions, pb.Receipts, pb.TransactionStateDiffs, + ) +} + +func validateTxsLength( + txs []Transaction, + receipts []*TransactionReceipt, + stateDiffs []*StateDiff, +) error { + if len(txs) != len(receipts) || len(txs) != len(stateDiffs) { + return fmt.Errorf( + "transactions, receipts, and tx_state_diffs must have the same length: "+ + "txs: %d, receipts: %d, stateDiffs: %d", + len(txs), + len(receipts), + len(stateDiffs), + ) + } + + for i := range txs { + if txs[i] == (Transaction{}) { + return fmt.Errorf("transaction at index %d is empty", i) + } + if receipts[i] == nil { + return fmt.Errorf("receipt at index %d is nil", i) + } + if stateDiffs[i] == nil { + return fmt.Errorf("transaction state diff at index %d is nil", i) + } + } + return nil +} var ( _ PreConfirmedUpdate = PreConfirmedNoChange{} @@ -102,3 +174,20 @@ func (e *PreConfirmedUpdateEnvelope) UnmarshalJSON(data []byte) error { } return nil } + +func (e *PreConfirmedUpdateEnvelope) Validate() error { + switch update := e.Update.(type) { + case PreConfirmedNoChange: + case PreConfirmedDeltaUpdate: + if err := update.validate(); err != nil { + return fmt.Errorf("invalid pre_confirmed delta update: %w", err) + } + case PreConfirmedBlock: + if err := update.validate(); err != nil { + return fmt.Errorf("invalid pre_confirmed full block: %w", err) + } + default: + return fmt.Errorf("invalid pre_confirmed update type %T", e.Update) + } + return nil +} diff --git a/starknet/pre_confirmed_update_test.go b/starknet/pre_confirmed_update_test.go index 10b21e9227..ab4d4f1125 100644 --- a/starknet/pre_confirmed_update_test.go +++ b/starknet/pre_confirmed_update_test.go @@ -165,6 +165,235 @@ func TestPreConfirmedUpdateEnvelope_UnmarshalJSON(t *testing.T) { }) } +func nonEmptyTx() starknet.Transaction { + return starknet.Transaction{Hash: felt.NewRandom[felt.Felt]()} +} + +// validBlock returns a PreConfirmedBlock that passes validate(), so individual +// tests can flip a single field to exercise one failure path at a time. +func validBlock() starknet.PreConfirmedBlock { + return starknet.PreConfirmedBlock{ + BlockIdentifier: "abc123", + Transactions: []starknet.Transaction{nonEmptyTx()}, + Receipts: []*starknet.TransactionReceipt{{}}, + TransactionStateDiffs: []*starknet.StateDiff{{}}, + Status: "PRE_CONFIRMED", + Version: "0.14.0", + SequencerAddress: felt.NewFromUint64[felt.Felt](0xaa), + L1GasPrice: &starknet.GasPrice{}, + L2GasPrice: &starknet.GasPrice{}, + L1DataGasPrice: &starknet.GasPrice{}, + } +} + +// validDelta returns a PreConfirmedDeltaUpdate that passes validate(). +func validDelta() starknet.PreConfirmedDeltaUpdate { + return starknet.PreConfirmedDeltaUpdate{ + BlockIdentifier: "abc123", + Transactions: []starknet.Transaction{nonEmptyTx()}, + Receipts: []*starknet.TransactionReceipt{{}}, + TransactionStateDiffs: []*starknet.StateDiff{{}}, + } +} + +func TestPreConfirmedUpdateEnvelope_Validate(t *testing.T) { + t.Run("NoChange is always valid", func(t *testing.T) { + env := &starknet.PreConfirmedUpdateEnvelope{Update: starknet.PreConfirmedNoChange{}} + err := env.Validate() + require.NoError(t, err) + }) + + t.Run("valid Block passes", func(t *testing.T) { + env := &starknet.PreConfirmedUpdateEnvelope{Update: validBlock()} + err := env.Validate() + require.NoError(t, err) + }) + + t.Run("valid Delta passes", func(t *testing.T) { + env := &starknet.PreConfirmedUpdateEnvelope{Update: validDelta()} + err := env.Validate() + require.NoError(t, err) + }) + + t.Run("invalid Block propagates validate error", func(t *testing.T) { + b := validBlock() + b.Status = "ACCEPTED_ON_L2" + env := &starknet.PreConfirmedUpdateEnvelope{Update: b} + err := env.Validate() + require.Error(t, err) + }) + + t.Run("invalid Delta propagates validate error", func(t *testing.T) { + d := validDelta() + d.Transactions = nil + env := &starknet.PreConfirmedUpdateEnvelope{Update: d} + err := env.Validate() + require.Error(t, err) + }) + + t.Run("nil Update hits the default branch", func(t *testing.T) { + // A zero-value envelope has a nil Update interface, which falls through + // the type switch to the default error branch. + env := &starknet.PreConfirmedUpdateEnvelope{} + err := env.Validate() + require.ErrorContains(t, err, "invalid pre_confirmed update type") + }) +} + +func TestPreConfirmedBlock_validate(t *testing.T) { + tests := []struct { + name string + mutate func(*starknet.PreConfirmedBlock) + wantErr bool + }{ + { + name: "valid", + mutate: func(*starknet.PreConfirmedBlock) {}, + }, + { + name: "missing block_identifier", + mutate: func(b *starknet.PreConfirmedBlock) { b.BlockIdentifier = "" }, + wantErr: true, + }, + { + name: "wrong status", + mutate: func(b *starknet.PreConfirmedBlock) { b.Status = "ACCEPTED_ON_L2" }, + wantErr: true, + }, + { + name: "missing version", + mutate: func(b *starknet.PreConfirmedBlock) { b.Version = "" }, + wantErr: true, + }, + { + name: "missing sequencer_address", + mutate: func(b *starknet.PreConfirmedBlock) { b.SequencerAddress = nil }, + wantErr: true, + }, + { + name: "missing l1_gas_price", + mutate: func(b *starknet.PreConfirmedBlock) { b.L1GasPrice = nil }, + wantErr: true, + }, + { + name: "missing l2_gas_price", + mutate: func(b *starknet.PreConfirmedBlock) { b.L2GasPrice = nil }, + wantErr: true, + }, + { + name: "missing l1_data_gas_price", + mutate: func(b *starknet.PreConfirmedBlock) { b.L1DataGasPrice = nil }, + wantErr: true, + }, + { + name: "mismatched lengths", + mutate: func(b *starknet.PreConfirmedBlock) { + b.Receipts = nil + }, + wantErr: true, + }, + { + name: "empty transaction", + mutate: func(b *starknet.PreConfirmedBlock) { + b.Transactions = []starknet.Transaction{{}} + }, + wantErr: true, + }, + { + name: "nil receipt", + mutate: func(b *starknet.PreConfirmedBlock) { + b.Receipts = []*starknet.TransactionReceipt{nil} + }, + wantErr: true, + }, + { + name: "nil state diff", + mutate: func(b *starknet.PreConfirmedBlock) { + b.TransactionStateDiffs = []*starknet.StateDiff{nil} + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := validBlock() + tt.mutate(&b) + // validate() is unexported; drive it through the exported Validate(). + err := (&starknet.PreConfirmedUpdateEnvelope{Update: b}).Validate() + if !tt.wantErr { + require.NoError(t, err) + return + } + require.Error(t, err) + }) + } +} + +func TestPreConfirmedDeltaUpdate_validate(t *testing.T) { + tests := []struct { + name string + mutate func(*starknet.PreConfirmedDeltaUpdate) + wantErr bool + }{ + { + name: "valid", + mutate: func(*starknet.PreConfirmedDeltaUpdate) {}, + }, + { + name: "missing block_identifier", + mutate: func(d *starknet.PreConfirmedDeltaUpdate) { d.BlockIdentifier = "" }, + wantErr: true, + }, + { + name: "zero transactions", + mutate: func(d *starknet.PreConfirmedDeltaUpdate) { d.Transactions = nil }, + wantErr: true, + }, + { + name: "mismatched lengths", + mutate: func(d *starknet.PreConfirmedDeltaUpdate) { + d.Transactions = []starknet.Transaction{nonEmptyTx(), nonEmptyTx()} + }, + wantErr: true, + }, + { + name: "empty transaction", + mutate: func(d *starknet.PreConfirmedDeltaUpdate) { + d.Transactions = []starknet.Transaction{{}} + }, + wantErr: true, + }, + { + name: "nil receipt", + mutate: func(d *starknet.PreConfirmedDeltaUpdate) { + d.Receipts = []*starknet.TransactionReceipt{nil} + }, + wantErr: true, + }, + { + name: "nil state diff", + mutate: func(d *starknet.PreConfirmedDeltaUpdate) { + d.TransactionStateDiffs = []*starknet.StateDiff{nil} + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := validDelta() + tt.mutate(&d) + err := (&starknet.PreConfirmedUpdateEnvelope{Update: d}).Validate() + if !tt.wantErr { + require.NoError(t, err) + return + } + require.Error(t, err) + }) + } +} + // AsUpdate produces a PreConfirmedBlock that shares the legacy block's data // and carries the synthetic "LegacyAPI" identifier downstream code uses to // detect a legacy source. diff --git a/starknet/public_key.go b/starknet/public_key.go new file mode 100644 index 0000000000..b2ba689807 --- /dev/null +++ b/starknet/public_key.go @@ -0,0 +1,10 @@ +package starknet + +// PublicKey is the sequencer's constant STARK curve public key in hex format. +type PublicKey string + +// TODO: placeholder for now to avoid compiler errors. A proper validation +// should be implemented in a follow-up PR. +func (val *PublicKey) Validate() error { + return nil +} diff --git a/starknet/signature.go b/starknet/signature.go index 4c1eaf6c84..84135555e1 100644 --- a/starknet/signature.go +++ b/starknet/signature.go @@ -11,3 +11,9 @@ type Signature struct { StateDiffCommitment *felt.Felt `json:"state_diff_commitment"` } `json:"signature_input"` } + +// TODO: placeholder for now to avoid compiler errors. A proper validation +// should be implemented in a follow-up PR. +func (val *Signature) Validate() error { + return nil +} diff --git a/starknet/state_update.go b/starknet/state_update.go index 6773aed564..4732503a97 100644 --- a/starknet/state_update.go +++ b/starknet/state_update.go @@ -10,6 +10,12 @@ type StateUpdate struct { StateDiff StateDiff `json:"state_diff"` } +// TODO: placeholder for now to avoid compiler errors. A proper validation +// should be implemented in a follow-up PR. +func (val *StateUpdate) Validate() error { + return nil +} + type StateDiff struct { // todo(rdr): What is key and value, I think it should go with `felt.StorageKey` and // `felt.StorageValue`. Also, why pointers to values and not values directly @@ -60,6 +66,12 @@ type StateUpdateWithBlock struct { StateUpdate *StateUpdate `json:"state_update"` } +// TODO: placeholder for now to avoid compiler errors. A proper validation +// should be implemented in a follow-up PR. +func (val *StateUpdateWithBlock) Validate() error { + return nil +} + // StateUpdateWithBlock represents the object response by the feeder for "get_state_update" // endpoint with `includeBlock` and `includeSignature` arguments type StateUpdateWithBlockAndSignature struct { @@ -67,3 +79,9 @@ type StateUpdateWithBlockAndSignature struct { Block *Block `json:"block"` Signature []*felt.Felt `json:"signature"` } + +// TODO: placeholder for now to avoid compiler errors. A proper validation +// should be implemented in a follow-up PR. +func (val *StateUpdateWithBlockAndSignature) Validate() error { + return nil +} diff --git a/starknet/trace.go b/starknet/trace.go index d20de08e25..9ed970cb31 100644 --- a/starknet/trace.go +++ b/starknet/trace.go @@ -6,6 +6,12 @@ type BlockTrace struct { Traces []TransactionTrace `json:"traces"` } +// TODO: placeholder for now to avoid compiler errors. A proper validation +// should be implemented in a follow-up PR. +func (val *BlockTrace) Validate() error { + return nil +} + type TransactionTrace struct { TransactionHash felt.Felt `json:"transaction_hash"` RevertError string `json:"revert_error"` diff --git a/starknet/transaction.go b/starknet/transaction.go index 8a4d9146e6..f1f3d69f10 100644 --- a/starknet/transaction.go +++ b/starknet/transaction.go @@ -200,6 +200,12 @@ type DeprecatedTransactionStatus struct { FailureReason *TransactionFailureReason `json:"transaction_failure_reason,omitempty"` } +// TODO: placeholder for now to avoid compiler errors. A proper validation +// should be implemented in a follow-up PR. +func (val *DeprecatedTransactionStatus) Validate() error { + return nil +} + // TransactionStatus represents the response from the get_transaction_status endpoint. type TransactionStatus struct { TxStatus string `json:"tx_status"` @@ -210,6 +216,12 @@ type TransactionStatus struct { TxFailureReason TransactionFailureReason `json:"tx_failure_reason,omitzero"` } +// TODO: placeholder for now to avoid compiler errors. A proper validation +// should be implemented in a follow-up PR. +func (val *TransactionStatus) Validate() error { + return nil +} + // TransactionFailureReason represents the failure reason of a transaction // returned by the feeder. type TransactionFailureReason struct { diff --git a/starknetdata/feeder/feeder_test.go b/starknetdata/feeder/feeder_test.go index 103933c6fd..a49761e94e 100644 --- a/starknetdata/feeder/feeder_test.go +++ b/starknetdata/feeder/feeder_test.go @@ -3,6 +3,7 @@ package feeder_test import ( "net/http" "net/http/httptest" + "net/url" "strconv" "testing" @@ -344,7 +345,9 @@ func TestAdapterErrorPaths(t *testing.T) { w.WriteHeader(http.StatusInternalServerError) })) t.Cleanup(srv.Close) - errClient := feeder.NewClient(srv.URL).WithBackoff(feeder.NopBackoff).WithMaxRetries(0) + feederURL, err := url.Parse(srv.URL) + require.NoError(t, err) + errClient := feeder.NewClient(feederURL).WithBackoff(feeder.NopBackoff).WithMaxRetries(0) errAdapter := adaptfeeder.New(errClient) hdr, err := errAdapter.BlockHeaderLatest(ctx) assert.Error(t, err) diff --git a/sync/pending_polling.go b/sync/pending_polling.go index 882481a0fb..d59f14e266 100644 --- a/sync/pending_polling.go +++ b/sync/pending_polling.go @@ -5,6 +5,7 @@ import ( "errors" "time" + "github.com/NethermindEth/juno/clients/feeder" "github.com/NethermindEth/juno/core" "github.com/NethermindEth/juno/core/felt" "github.com/NethermindEth/juno/core/pending" @@ -211,7 +212,12 @@ func (s *Synchronizer) pollPreConfirmed( ctx, current.Block.Number, current.BlockIdentifier, txCount, ) if err != nil { - s.logger.Debug("Error while trying to poll pre_confirmed block", zap.Error(err)) + const msg = "polling pre-confirmed block" + if errors.Is(err, feeder.ErrInvalidFeederResponse) { + s.logger.Error(msg, zap.Error(err), zap.Uint64("block_number", current.Block.Number)) + continue + } + s.logger.Debug(msg, zap.Error(err), zap.Uint64("block_number", current.Block.Number)) continue }