Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e3fcfd2
feat(validation): add Validatable interface and accept it in the doRe…
thiagodeev Jun 19, 2026
6a48f72
feat(validation): implement validation for PreConfirmedDeltaUpdate type
thiagodeev Jun 19, 2026
654c858
feat(validation): enhance validation for PreConfirmedBlock and refact…
thiagodeev Jun 19, 2026
eb8d513
refactor(validation): remove redundant checks in AdaptPreConfirmedBlo…
thiagodeev Jun 19, 2026
f9e0c8c
refactor(sn2core): remove candidate transactions handling in AdaptPre…
thiagodeev Jun 19, 2026
6c4a05c
test(validation): add tests for feeder, PreConfirmedBlock and PreConf…
thiagodeev Jun 19, 2026
85c83ec
ci: fix linter
thiagodeev Jun 19, 2026
cfda3e8
chore: reorg doRequest params
thiagodeev Jun 19, 2026
a8f87f9
fix: PR comments from Claude
thiagodeev Jun 19, 2026
737dd18
style: move methods within the file
thiagodeev Jun 22, 2026
f723f54
reafactor(validation): move placeholder validation methods close to t…
thiagodeev Jun 22, 2026
a6b485f
refactor(feeder): move doRequest and Validatable to a new request.go …
thiagodeev Jun 22, 2026
94e7289
refactor(feeder): move get and buildQueryString methods to request.go…
thiagodeev Jun 22, 2026
f526296
refactor(feeder): replace custom publicKey type with starknet.PublicK…
thiagodeev Jun 22, 2026
4fceb15
refactor(validation): update Validate methods to return only an error…
thiagodeev Jun 22, 2026
d7bf4db
feat(feeder): new error message for invalid feeder responses
thiagodeev Jun 22, 2026
65f27f1
feat(sync): log error as error level for invalid feeder responses in …
thiagodeev Jun 22, 2026
319266e
docs(validation): update Validatable interface to clarify error handl…
thiagodeev Jun 22, 2026
54813e4
refactor(validation): improve error messages and formatting in valida…
thiagodeev Jun 22, 2026
e7b8ea0
chore; move .get() back to feeder.go file
thiagodeev Jun 22, 2026
fb44a2e
refactor(feeder): update Client to use *url.URL for URL handling and …
thiagodeev Jun 22, 2026
6f0583b
feat(validation): add URL validation for custom network feeder and ga…
thiagodeev Jun 23, 2026
38adaa0
refactor(feeder): update tests and client initialization to use *url.…
thiagodeev Jun 23, 2026
030c4cf
feat(validation): enhance error message in validateTxsLength for bett…
thiagodeev Jun 23, 2026
db31c27
fix(validation): improve error messages in validateHTTPURL for cleare…
thiagodeev Jun 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 1 addition & 38 deletions adapters/sn2core/sn2core.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)
}

Comment thread
thiagodeev marked this conversation as resolved.
candidateCount := 0
for i := range len(response.Transactions) {
if IsCandidateTx(response, i) {
Expand Down Expand Up @@ -626,22 +615,13 @@ 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,
) (pending.PreConfirmed, error) {
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
Expand All @@ -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])
Expand Down Expand Up @@ -720,7 +684,6 @@ func AdaptPreConfirmedWithDelta(
next.Block = &nextBlock
next.StateUpdate = &nextStateUpdate
next.TransactionStateDiffs = mergedStateDiffs
next.CandidateTxs = candidateTxs
return next, nil
}

Expand Down
42 changes: 0 additions & 42 deletions adapters/sn2core/sn2core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
58 changes: 23 additions & 35 deletions clients/feeder/feeder.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"errors"
"io"
"net/http"
"net/url"
"strconv"
"sync/atomic"
"time"
Expand All @@ -29,7 +30,7 @@
type Backoff func(wait time.Duration) time.Duration

type Client struct {
url string
url *url.URL
Comment thread
rodrodros marked this conversation as resolved.
client *http.Client
backoff Backoff
maxRetries int
Expand Down Expand Up @@ -143,8 +144,9 @@
return 0
}

func NewClient(clientURL string) *Client {
func NewClient(clientURL *url.URL) *Client {
defaultTimeouts := getDefaultFixedTimeouts()

client := &Client{
url: clientURL,
client: http.DefaultClient,
Expand All @@ -160,7 +162,7 @@
}

// 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) {
Comment thread
thiagodeev marked this conversation as resolved.
var res *http.Response
var err error
wait := time.Duration(0)
Expand All @@ -170,7 +172,7 @@
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
}
Expand Down Expand Up @@ -239,7 +241,7 @@
}

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,
})

Expand All @@ -249,7 +251,7 @@
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,
})
Expand All @@ -262,7 +264,7 @@
}

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,
})

Expand All @@ -273,7 +275,7 @@
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",
})
Expand Down Expand Up @@ -303,7 +305,7 @@
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",
})
Expand All @@ -312,7 +314,7 @@
}

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)

Check warning on line 317 in clients/feeder/feeder.go

View check run for this annotation

Codecov / codecov/patch

clients/feeder/feeder.go#L317

Added line #L317 was not covered by tests

addresses, err := doRequest[starknet.FeeTokenAddresses](ctx, c, queryURL)
if err != nil {
Expand All @@ -322,34 +324,34 @@
}

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,
})

return doRequest[starknet.Signature](ctx, c, queryURL)
}

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,
})

return doRequest[starknet.StateUpdate](ctx, c, queryURL)
}

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,
})
Expand All @@ -361,7 +363,7 @@
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,
Expand All @@ -379,7 +381,7 @@
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,
})

Expand All @@ -403,7 +405,7 @@
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),
Expand All @@ -421,7 +423,7 @@
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(),
})

Expand All @@ -434,23 +436,9 @@
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
}
Loading
Loading