From 04a39cf3d0a4c498b01efee186d18ef84af1c4d5 Mon Sep 17 00:00:00 2001 From: Artem Streltsov Date: Wed, 5 Jun 2024 19:34:59 +0100 Subject: [PATCH 01/13] implemented juno_getBlockWithTxsAndReceipts method --- rpc/block.go | 41 +++++++++++++++++++++++++++++++++++++++++ rpc/handlers.go | 7 +++++++ 2 files changed, 48 insertions(+) diff --git a/rpc/block.go b/rpc/block.go index 44114e9470..e752e92ed7 100644 --- a/rpc/block.go +++ b/rpc/block.go @@ -278,6 +278,47 @@ func (h *Handler) BlockWithTxs(id BlockID) (*BlockWithTxs, *jsonrpc.Error) { }, nil } +// Artem's intern exercise 4 +type BlockWithTxsAndReceipts struct { + Status BlockStatus + BlockHeader + Transactions []*Transaction + Receipts []*TransactionReceipt +} + +func (h *Handler) juno_getBlockWithTxsAndReceipts(id BlockID) (*BlockWithTxsAndReceipts, *jsonrpc.Error) { + block, rpcErr := h.blockByID(&id) + if rpcErr != nil { + return nil, rpcErr + } + + blockStatus, rpcErr := h.blockStatus(id, block) + if rpcErr != nil { + return nil, rpcErr + } + + finalityStatus := TxnAcceptedOnL2 + if blockStatus == BlockAcceptedL1 { + finalityStatus = TxnAcceptedOnL1 + } + + txs := make([]*Transaction, len(block.Transactions)) + receipts := make([]*TransactionReceipt, len(block.Transactions)) + for index, txn := range block.Transactions { + txs[index] = AdaptTransaction(txn) + r := block.Receipts[index] + receipts[index] = AdaptReceipt(r, txn, finalityStatus, nil, 0, false) + } + + return &BlockWithTxsAndReceipts{ + Status: blockStatus, + BlockHeader: adaptBlockHeader(block.Header), + Transactions: txs, + Receipts: receipts, + }, nil +} +// end of Artem's intern exercise 4 + func (h *Handler) BlockWithTxsV0_6(id BlockID) (*BlockWithTxs, *jsonrpc.Error) { resp, err := h.BlockWithTxs(id) if err != nil { diff --git a/rpc/handlers.go b/rpc/handlers.go index cfaf1f37a1..6afbb0b4a4 100644 --- a/rpc/handlers.go +++ b/rpc/handlers.go @@ -173,6 +173,13 @@ func (h *Handler) SpecVersionV0_6() (string, *jsonrpc.Error) { func (h *Handler) Methods() ([]jsonrpc.Method, string) { //nolint: funlen return []jsonrpc.Method{ + // Artem's intern exercise 4 + { + Name: "juno_getBlockWithTxsAndReceipts", + Params: []jsonrpc.Parameter{{Name: "block_id"}}, + Handler: h.juno_getBlockWithTxsAndReceipts, + }, + // End of Artem's intern exercise 4 { Name: "starknet_chainId", Handler: h.ChainID, From fd32a1ff04748674626ad22b1aa81296cee6ab51 Mon Sep 17 00:00:00 2001 From: Artem Streltsov Date: Mon, 10 Jun 2024 13:03:00 +0100 Subject: [PATCH 02/13] implement juno_getNodesFromRoot method --- blockchain/pending.go | 5 +++++ core/state.go | 5 +++++ core/state_snapshot.go | 5 +++++ core/trie/trie.go | 17 +++++++++++++++++ rpc/handlers.go | 26 ++++++++++++++++++++++++++ 5 files changed, 58 insertions(+) diff --git a/blockchain/pending.go b/blockchain/pending.go index b69cd0ee06..bc07c56de4 100644 --- a/blockchain/pending.go +++ b/blockchain/pending.go @@ -3,6 +3,7 @@ package blockchain import ( "github.com/NethermindEth/juno/core" "github.com/NethermindEth/juno/core/felt" + "github.com/NethermindEth/juno/core/trie" ) type Pending struct { @@ -17,6 +18,10 @@ type PendingState struct { head core.StateReader } +func (p *PendingState) GetClassesTrie() (*trie.Trie, func() error, error) { + return p.head.GetClassesTrie() +} + func NewPendingState(stateDiff *core.StateDiff, newClasses map[felt.Felt]core.Class, head core.StateReader) *PendingState { return &PendingState{ stateDiff: stateDiff, diff --git a/core/state.go b/core/state.go index 97ea2163bf..bf6e15b932 100644 --- a/core/state.go +++ b/core/state.go @@ -40,6 +40,11 @@ type StateReader interface { ContractNonce(addr *felt.Felt) (*felt.Felt, error) ContractStorage(addr, key *felt.Felt) (*felt.Felt, error) Class(classHash *felt.Felt) (*DeclaredClass, error) + GetClassesTrie() (*trie.Trie, func() error, error) +} + +func (s *State) GetClassesTrie() (*trie.Trie, func() error, error) { + return s.classesTrie() } type State struct { diff --git a/core/state_snapshot.go b/core/state_snapshot.go index a7062d4dae..3bb4b0e41b 100644 --- a/core/state_snapshot.go +++ b/core/state_snapshot.go @@ -5,6 +5,7 @@ import ( "github.com/NethermindEth/juno/core/felt" "github.com/NethermindEth/juno/db" + "github.com/NethermindEth/juno/core/trie" ) type stateSnapshot struct { @@ -12,6 +13,10 @@ type stateSnapshot struct { state StateHistoryReader } +func (s *stateSnapshot) GetClassesTrie() (*trie.Trie, func() error, error) { + return s.state.GetClassesTrie() +} + func NewStateSnapshot(state StateHistoryReader, blockNumber uint64) StateReader { return &stateSnapshot{ blockNumber: blockNumber, diff --git a/core/trie/trie.go b/core/trie/trie.go index 4339f39cd1..32a5def0cc 100644 --- a/core/trie/trie.go +++ b/core/trie/trie.go @@ -92,6 +92,23 @@ func (t *Trie) feltToKey(k *felt.Felt) Key { return NewKey(t.height, kBytes[:]) } +func (t *Trie) ConvertFeltToKey(key *felt.Felt) Key { + return t.feltToKey(key) +} + +func (t *Trie) GetNodesFromRoot(key *Key) ([]storageNode, error) { + return t.nodesFromRoot(key) +} + +func (t *Trie) ParseNodes(nodes []storageNode) ([]string, error) { + var parsedNodes []string + for _, node := range nodes { + parsedNodes = append(parsedNodes, fmt.Sprintf("%v", node.node.Value)) + } + + return parsedNodes, nil +} + // findCommonKey finds the set of common MSB bits in two key bitsets. func findCommonKey(longerKey, shorterKey *Key) (Key, bool) { divergentBit := findDivergentBit(longerKey, shorterKey) diff --git a/rpc/handlers.go b/rpc/handlers.go index 6afbb0b4a4..1c1327a7e5 100644 --- a/rpc/handlers.go +++ b/rpc/handlers.go @@ -171,6 +171,27 @@ func (h *Handler) SpecVersionV0_6() (string, *jsonrpc.Error) { return "0.6.0", nil } +func (h *Handler) juno_getNodesFromRoot(key felt.Felt) ([]string, *jsonrpc.Error) { + stateReader, _, err := h.bcReader.HeadState() + if err != nil { + return nil, jsonrpc.Err(jsonrpc.InternalError, err.Error()) + } + + try, _, errTry := stateReader.GetClassesTrie() + if errTry != nil { + return nil, jsonrpc.Err(jsonrpc.InternalError, errTry.Error()) + } + + k := try.ConvertFeltToKey(&key) + storageNodes, err := try.GetNodesFromRoot(&k) + if err != nil { + return nil, jsonrpc.Err(jsonrpc.InternalError, err.Error()) + } + + nodes, _ := try.ParseNodes(storageNodes) + return nodes, nil +} + func (h *Handler) Methods() ([]jsonrpc.Method, string) { //nolint: funlen return []jsonrpc.Method{ // Artem's intern exercise 4 @@ -180,6 +201,11 @@ func (h *Handler) Methods() ([]jsonrpc.Method, string) { //nolint: funlen Handler: h.juno_getBlockWithTxsAndReceipts, }, // End of Artem's intern exercise 4 + { + Name: "juno_getNodesFromRoot", + Params: []jsonrpc.Parameter{{Name: "key"}}, + Handler: h.juno_getNodesFromRoot, + }, { Name: "starknet_chainId", Handler: h.ChainID, From 83087f97f360d6ce8557ed46b5851e8a5e2463bf Mon Sep 17 00:00:00 2001 From: Artem Streltsov Date: Mon, 10 Jun 2024 19:53:32 +0100 Subject: [PATCH 03/13] juno_getNodesFromRoot small fix --- core/trie/trie.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/trie/trie.go b/core/trie/trie.go index 32a5def0cc..baa99aa74f 100644 --- a/core/trie/trie.go +++ b/core/trie/trie.go @@ -103,7 +103,7 @@ func (t *Trie) GetNodesFromRoot(key *Key) ([]storageNode, error) { func (t *Trie) ParseNodes(nodes []storageNode) ([]string, error) { var parsedNodes []string for _, node := range nodes { - parsedNodes = append(parsedNodes, fmt.Sprintf("%v", node.node.Value)) + parsedNodes = append(parsedNodes, fmt.Sprintf("%v", &node.node.Value)) } return parsedNodes, nil From d6863a88d9ce8cea40aa92165e1a80274726a9b8 Mon Sep 17 00:00:00 2001 From: Artem Streltsov Date: Wed, 12 Jun 2024 16:24:03 +0100 Subject: [PATCH 04/13] implement basic fgw syncing with API calls --- sync/sync.go | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 161 insertions(+), 1 deletion(-) diff --git a/sync/sync.go b/sync/sync.go index 99f3d3dd2b..b72d8526d1 100644 --- a/sync/sync.go +++ b/sync/sync.go @@ -2,17 +2,22 @@ package sync import ( "context" + "encoding/json" "errors" + "fmt" + "net/http" "runtime" "sync/atomic" "time" + "github.com/NethermindEth/juno/adapters/sn2core" "github.com/NethermindEth/juno/blockchain" "github.com/NethermindEth/juno/core" "github.com/NethermindEth/juno/core/felt" "github.com/NethermindEth/juno/db" "github.com/NethermindEth/juno/feed" "github.com/NethermindEth/juno/service" + "github.com/NethermindEth/juno/starknet" "github.com/NethermindEth/juno/starknetdata" "github.com/NethermindEth/juno/utils" "github.com/sourcegraph/conc/stream" @@ -66,6 +71,7 @@ type Synchronizer struct { startingBlockNumber *uint64 highestBlockHeader atomic.Pointer[core.Header] newHeads *feed.Feed[*core.Header] + latestBlockHeight uint64 log utils.SimpleLogger listener EventListener @@ -82,6 +88,7 @@ func New(bc *blockchain.Blockchain, starkNetData starknetdata.StarknetData, starknetData: starkNetData, log: log, newHeads: feed.New[*core.Header](), + latestBlockHeight: uint64(0), pendingPollInterval: pendingPollInterval, listener: &SelectiveListener{}, readOnlyBlockchain: readOnlyBlockchain, @@ -97,10 +104,163 @@ func (s *Synchronizer) WithListener(listener EventListener) *Synchronizer { // Run starts the Synchronizer, returns an error if the loop is already running func (s *Synchronizer) Run(ctx context.Context) error { - s.syncBlocks(ctx) + //s.syncBlocks(ctx) + s.syncBlocksFromFeederGateway(ctx) return nil } +func (s *Synchronizer) syncBlocksFromFeederGateway(syncCtx context.Context) { + streamCtx, streamCancel := context.WithCancel(syncCtx) + for { + select { + case <- streamCtx.Done(): + streamCancel() + return + default: + s.log.Infow("Fetching from feeder gateway") + block, stateUpdate, blockCommitments := s.getBlockDetails(s.latestBlockHeight) + newClasses, err := s.fetchUnknownClasses(syncCtx, &stateUpdate) + if err != nil { + s.log.Errorw("Error fetching unknown classes: %v", err) + } + err = s.blockchain.Store(&block, &blockCommitments, &stateUpdate, newClasses) + if err != nil { + s.log.Errorw("Error storing block: %v", err) + } else { + s.log.Infow("Stored Block", "number", block.Number, "hash", block.Hash.ShortString(), "root", block.GlobalStateRoot.ShortString()) + } + s.latestBlockHeight += 1 + } + } +} + +func (s *Synchronizer) getBlockDetails(blockNumber uint64) (core.Block, core.StateUpdate, core.BlockCommitments) { + block, blockCommitments := s.getBlockFromFeederGateway(blockNumber) + stateUpdate := s.getStateUpdateFromFeederGateway(blockNumber) + return block, stateUpdate, blockCommitments +} + +func (s *Synchronizer) getBlockFromFeederGateway(blockNumber uint64) (core.Block, core.BlockCommitments) { + blockUrl := fmt.Sprintf("https://alpha-mainnet.starknet.io/feeder_gateway/get_block?blockNumber=%d", blockNumber) + blockResponse, err := http.Get(blockUrl) + if err != nil { + s.log.Errorw("Error getting response: %v", err) + return core.Block{}, core.BlockCommitments{} + } + defer blockResponse.Body.Close() + + var block starknet.Block + if blockResponse.StatusCode == http.StatusOK { + decoder := json.NewDecoder(blockResponse.Body) + if err := decoder.Decode(&block); err != nil { + s.log.Errorw("Failed to decode response: %v", err) + return core.Block{}, core.BlockCommitments{} + } + } else { + s.log.Warnw("Received non-OK HTTP status: %v", blockResponse.Status) + return core.Block{}, core.BlockCommitments{} + } + + signatureUrl := fmt.Sprintf("https://alpha-mainnet.starknet.io/feeder_gateway/get_signature?blockNumber=%d", block.Number) + signatureResponse, err := http.Get(signatureUrl) + if err != nil { + s.log.Errorw("Error getting response: %v", err) + return core.Block{}, core.BlockCommitments{} + } + defer signatureResponse.Body.Close() + + var signature starknet.Signature + if signatureResponse.StatusCode == http.StatusOK { + decoder := json.NewDecoder(signatureResponse.Body) + if err := decoder.Decode(&signature); err != nil { + s.log.Errorw("Failed to decode response: %v", err) + return core.Block{}, core.BlockCommitments{} + } + } else { + s.log.Warnw("Received non-OK HTTP status: %v", signatureResponse.Status) + return core.Block{}, core.BlockCommitments{} + } + + var adaptedTransactions []core.Transaction + for _, transaction := range block.Transactions { + adaptedTransaction, err := sn2core.AdaptTransaction(transaction) + if err != nil { + s.log.Errorw("Error adapting starknet type to core type: %v", err) + return core.Block{}, core.BlockCommitments{} + } + adaptedTransactions = append(adaptedTransactions, adaptedTransaction) + } + + var eventCount uint64 + var adaptedTransactionReceipts []*core.TransactionReceipt + for _, receipt := range block.Receipts { + eventCount += uint64(len(receipt.Events)) + adaptedTransactionReceipt := sn2core.AdaptTransactionReceipt(receipt) + adaptedTransactionReceipts = append(adaptedTransactionReceipts, adaptedTransactionReceipt) + } + + signatures := [][]*felt.Felt{} + signatures = append(signatures, signature.Signature) + + header := &core.Header{ + Hash: block.Hash, + ParentHash: block.ParentHash, + Number: block.Number, + GlobalStateRoot: block.StateRoot, + SequencerAddress: block.SequencerAddress, + TransactionCount: uint64(len(block.Transactions)), + EventCount: eventCount, + Timestamp: block.Timestamp, + ProtocolVersion: block.Version, + EventsBloom: core.EventsBloom(adaptedTransactionReceipts), + GasPrice: block.GasPriceETH(), + Signatures: signatures, + GasPriceSTRK: block.GasPriceSTRK(), + L1DAMode: core.L1DAMode(block.L1DAMode), + L1DataGasPrice: (*core.GasPrice)(block.L1DataGasPrice), + } + + return core.Block{ + Header: header, + Transactions: adaptedTransactions, + Receipts: adaptedTransactionReceipts, + }, + core.BlockCommitments{ + TransactionCommitment: block.TransactionCommitment, + EventCommitment: block.EventCommitment, + } +} + +func (s *Synchronizer) getStateUpdateFromFeederGateway(blockNumber uint64) core.StateUpdate { + stateUpdateUrl := fmt.Sprintf("https://alpha-mainnet.starknet.io/feeder_gateway/get_state_update?blockNumber=%d", blockNumber) + stateUpdateResponse, err := http.Get(stateUpdateUrl) + if err != nil { + s.log.Errorw("Error getting response: %v", err) + return core.StateUpdate{} + } + defer stateUpdateResponse.Body.Close() + + var stateUpdate starknet.StateUpdate + if stateUpdateResponse.StatusCode == http.StatusOK { + decoder := json.NewDecoder(stateUpdateResponse.Body) + if err := decoder.Decode(&stateUpdate); err != nil { + s.log.Errorw("Failed to decode response: %v", err) + return core.StateUpdate{} + } + } else { + s.log.Warnw("Received non-OK HTTP status: %v", stateUpdateResponse.Status) + return core.StateUpdate{} + } + + adaptedStateUpdate, err := sn2core.AdaptStateUpdate(&stateUpdate) + if err != nil { + s.log.Errorw("Error adapting starknet type to core type: %v", err) + return core.StateUpdate{} + } + + return *adaptedStateUpdate +} + func (s *Synchronizer) fetcherTask(ctx context.Context, height uint64, verifiers *stream.Stream, resetStreams context.CancelFunc, ) stream.Callback { From b5382479c0383a5cd3f3eaf8e4342f88ef76f5a4 Mon Sep 17 00:00:00 2001 From: Artem Streltsov Date: Wed, 12 Jun 2024 17:06:38 +0100 Subject: [PATCH 05/13] implement syncing from non-empty blockchain --- sync/sync.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sync/sync.go b/sync/sync.go index b72d8526d1..a7aa21a792 100644 --- a/sync/sync.go +++ b/sync/sync.go @@ -83,12 +83,18 @@ type Synchronizer struct { func New(bc *blockchain.Blockchain, starkNetData starknetdata.StarknetData, log utils.SimpleLogger, pendingPollInterval time.Duration, readOnlyBlockchain bool, ) *Synchronizer { + head, err := bc.Head() + latestBlockHeight := uint64(0) + if err == nil { + latestBlockHeight = head.Number + uint64(1) + } + s := &Synchronizer{ blockchain: bc, starknetData: starkNetData, log: log, newHeads: feed.New[*core.Header](), - latestBlockHeight: uint64(0), + latestBlockHeight: latestBlockHeight, pendingPollInterval: pendingPollInterval, listener: &SelectiveListener{}, readOnlyBlockchain: readOnlyBlockchain, From ca10138bc0e9d8dd17f2d8b4bf953e59b9e75b5a Mon Sep 17 00:00:00 2001 From: Artem Streltsov Date: Thu, 13 Jun 2024 16:52:15 +0100 Subject: [PATCH 06/13] implement test for juno_getBlockWithTxsAndReceipts --- rpc/block.go | 4 +- rpc/block_test.go | 250 ++++++++++++++++++++++++++++++++++++++++++++++ rpc/handlers.go | 2 +- 3 files changed, 252 insertions(+), 4 deletions(-) diff --git a/rpc/block.go b/rpc/block.go index e752e92ed7..b9d8dc2338 100644 --- a/rpc/block.go +++ b/rpc/block.go @@ -278,7 +278,6 @@ func (h *Handler) BlockWithTxs(id BlockID) (*BlockWithTxs, *jsonrpc.Error) { }, nil } -// Artem's intern exercise 4 type BlockWithTxsAndReceipts struct { Status BlockStatus BlockHeader @@ -286,7 +285,7 @@ type BlockWithTxsAndReceipts struct { Receipts []*TransactionReceipt } -func (h *Handler) juno_getBlockWithTxsAndReceipts(id BlockID) (*BlockWithTxsAndReceipts, *jsonrpc.Error) { +func (h *Handler) BlockWithTxsAndReceipts(id BlockID) (*BlockWithTxsAndReceipts, *jsonrpc.Error) { block, rpcErr := h.blockByID(&id) if rpcErr != nil { return nil, rpcErr @@ -317,7 +316,6 @@ func (h *Handler) juno_getBlockWithTxsAndReceipts(id BlockID) (*BlockWithTxsAndR Receipts: receipts, }, nil } -// end of Artem's intern exercise 4 func (h *Handler) BlockWithTxsV0_6(id BlockID) (*BlockWithTxs, *jsonrpc.Error) { resp, err := h.BlockWithTxs(id) diff --git a/rpc/block_test.go b/rpc/block_test.go index f778ac7350..0c87aa49fd 100644 --- a/rpc/block_test.go +++ b/rpc/block_test.go @@ -20,6 +20,256 @@ import ( "go.uber.org/mock/gomock" ) +func TestBlockWithTxsAndReceipts(t *testing.T) { + errTests := map[string]rpc.BlockID{ + "latest": {Latest: true}, + "pending": {Pending: true}, + "hash": {Hash: new(felt.Felt).SetUint64(1)}, + "number": {Number: 1}, + } + + for description, id := range errTests { + t.Run(description, func(t *testing.T) { + log := utils.NewNopZapLogger() + n := utils.Ptr(utils.Mainnet) + chain := blockchain.New(pebble.NewMemTest(t), n) + handler := rpc.New(chain, nil, nil, "", n, log) + + blockWithTxsAndReceipts, rpcErr := handler.BlockWithTxsAndReceipts(id) + assert.Nil(t, blockWithTxsAndReceipts) + assert.Equal(t, rpc.ErrBlockNotFound, rpcErr) + }) + } + + mockCtrl := gomock.NewController(t) + t.Cleanup(mockCtrl.Finish) + + n := utils.Ptr(utils.Mainnet) + mockReader := mocks.NewMockReader(mockCtrl) + handler := rpc.New(mockReader, nil, nil, "", n, nil) + + t.Run("Receipts - transaction not found", func(t *testing.T) { + blockID := rpc.BlockID{Number: 777} + + mockReader.EXPECT().BlockByNumber(blockID.Number).Return(nil, db.ErrKeyNotFound) + + resp, rpcErr := handler.BlockWithTxsAndReceipts(blockID) + assert.Nil(t, resp) + assert.Equal(t, rpc.ErrBlockNotFound, rpcErr) + }) + + t.Run("Receipts - l1head failure", func(t *testing.T) { + blockID := rpc.BlockID{Number: 777} + block := &core.Block{ + Header: &core.Header{}, + } + + err := errors.New("l1 failure") + mockReader.EXPECT().BlockByNumber(blockID.Number).Return(block, nil) + mockReader.EXPECT().L1Head().Return(nil, err) + + resp, rpcErr := handler.BlockWithTxsAndReceipts(blockID) + assert.Nil(t, resp) + assert.Equal(t, rpc.ErrInternal.CloneWithData(err.Error()), rpcErr) + }) + + client := feeder.NewTestClient(t, n) + gw := adaptfeeder.New(client) + + latestBlockNumber := uint64(16697) + latestBlock, err := gw.BlockByNumber(context.Background(), latestBlockNumber) + require.NoError(t, err) + latestBlockHash := latestBlock.Hash + + checkLatestBlock := func(t *testing.T, blockWithTxHashes *rpc.BlockWithTxHashes, blockWithTxsAndReceipts *rpc.BlockWithTxsAndReceipts) { + t.Helper() + assert.Equal(t, blockWithTxHashes.BlockHeader, blockWithTxsAndReceipts.BlockHeader) + assert.Equal(t, len(blockWithTxHashes.TxnHashes), len(blockWithTxsAndReceipts.Transactions)) + + for i, txnHash := range blockWithTxHashes.TxnHashes { + txn, err := handler.TransactionByHash(*txnHash) + require.Nil(t, err) + + assert.Equal(t, txn, blockWithTxsAndReceipts.Transactions[i]) + } + } + + latestBlockTxMap := make(map[felt.Felt]core.Transaction) + for _, tx := range latestBlock.Transactions { + latestBlockTxMap[*tx.Hash()] = tx + } + + mockReader.EXPECT().TransactionByHash(gomock.Any()).DoAndReturn(func(hash *felt.Felt) (core.Transaction, error) { + if tx, found := latestBlockTxMap[*hash]; found { + return tx, nil + } + return nil, errors.New("txn not found") + }).Times(len(latestBlock.Transactions) * 5) + + t.Run("Transactions - latest block", func(t *testing.T) { + mockReader.EXPECT().Head().Return(latestBlock, nil).Times(2) + mockReader.EXPECT().L1Head().Return(nil, db.ErrKeyNotFound).Times(2) + + blockWithTxHashes, rpcErr := handler.BlockWithTxHashes(rpc.BlockID{Latest: true}) + require.Nil(t, rpcErr) + + blockWithTxsAndReceipts, rpcErr := handler.BlockWithTxsAndReceipts(rpc.BlockID{Latest: true}) + require.Nil(t, rpcErr) + + checkLatestBlock(t, blockWithTxHashes, blockWithTxsAndReceipts) + }) + + t.Run("Transactions - block by hash", func(t *testing.T) { + mockReader.EXPECT().BlockByHash(latestBlockHash).Return(latestBlock, nil).Times(2) + mockReader.EXPECT().L1Head().Return(nil, db.ErrKeyNotFound).Times(2) + + blockWithTxHashes, rpcErr := handler.BlockWithTxHashes(rpc.BlockID{Hash: latestBlockHash}) + require.Nil(t, rpcErr) + + blockWithTxsAndReceipts, rpcErr := handler.BlockWithTxsAndReceipts(rpc.BlockID{Hash: latestBlockHash}) + require.Nil(t, rpcErr) + + checkLatestBlock(t, blockWithTxHashes, blockWithTxsAndReceipts) + }) + + t.Run("Transactions - block by number", func(t *testing.T) { + mockReader.EXPECT().BlockByNumber(latestBlockNumber).Return(latestBlock, nil).Times(2) + mockReader.EXPECT().L1Head().Return(nil, db.ErrKeyNotFound).Times(2) + + blockWithTxHashes, rpcErr := handler.BlockWithTxHashes(rpc.BlockID{Number: latestBlockNumber}) + require.Nil(t, rpcErr) + + blockWithTxsAndReceipts, rpcErr := handler.BlockWithTxsAndReceipts(rpc.BlockID{Number: latestBlockNumber}) + require.Nil(t, rpcErr) + + assert.Equal(t, blockWithTxHashes.BlockHeader, blockWithTxsAndReceipts.BlockHeader) + assert.Equal(t, len(blockWithTxHashes.TxnHashes), len(blockWithTxsAndReceipts.Transactions)) + + checkLatestBlock(t, blockWithTxHashes, blockWithTxsAndReceipts) + }) + + t.Run("Transactions - block by number, accepted on l1", func(t *testing.T) { + mockReader.EXPECT().BlockByNumber(latestBlockNumber).Return(latestBlock, nil).Times(2) + mockReader.EXPECT().L1Head().Return(&core.L1Head{ + BlockNumber: latestBlockNumber, + BlockHash: latestBlockHash, + StateRoot: latestBlock.GlobalStateRoot, + }, nil).Times(2) + + blockWithTxHashes, rpcErr := handler.BlockWithTxHashes(rpc.BlockID{Number: latestBlockNumber}) + require.Nil(t, rpcErr) + + blockWithTxsAndReceipts, rpcErr := handler.BlockWithTxsAndReceipts(rpc.BlockID{Number: latestBlockNumber}) + require.Nil(t, rpcErr) + + assert.Equal(t, blockWithTxHashes.BlockHeader, blockWithTxsAndReceipts.BlockHeader) + assert.Equal(t, len(blockWithTxHashes.TxnHashes), len(blockWithTxsAndReceipts.Transactions)) + + checkLatestBlock(t, blockWithTxHashes, blockWithTxsAndReceipts) + }) + + t.Run("Transactions - pending block", func(t *testing.T) { + latestBlock.Hash = nil + latestBlock.GlobalStateRoot = nil + mockReader.EXPECT().Pending().Return(blockchain.Pending{ + Block: latestBlock, + }, nil).Times(2) + mockReader.EXPECT().L1Head().Return(nil, db.ErrKeyNotFound).Times(2) + + blockWithTxHashes, rpcErr := handler.BlockWithTxHashes(rpc.BlockID{Pending: true}) + require.Nil(t, rpcErr) + + blockWithTxsAndReceipts, rpcErr := handler.BlockWithTxsAndReceipts(rpc.BlockID{Pending: true}) + require.Nil(t, rpcErr) + + checkLatestBlock(t, blockWithTxHashes, blockWithTxsAndReceipts) + }) + + t.Run("Receipts - pending block", func(t *testing.T) { + t.Skip() + block0, err := gw.BlockByNumber(context.Background(), 0) + require.NoError(t, err) + + blockID := rpc.BlockID{Pending: true} + + mockReader.EXPECT().Pending().Return(blockchain.Pending{Block: block0}, nil) + mockReader.EXPECT().L1Head().Return(&core.L1Head{}, nil) + + resp, rpcErr := handler.BlockWithTxsAndReceipts(blockID) + header := resp.BlockHeader + + var transactions []*rpc.Transaction + var receipts []*rpc.TransactionReceipt + for i, tx := range block0.Transactions { + adaptedReceipt := rpc.AdaptReceipt(block0.Receipts[i], tx, rpc.TxnAcceptedOnL2, nil, 0, true) + adaptedTx := rpc.AdaptTransaction(tx) + + transactions = append(transactions, adaptedTx) + receipts = append(receipts, adaptedReceipt) + } + + assert.Nil(t, rpcErr) + assert.Equal(t, &rpc.BlockWithTxsAndReceipts{ + Status: rpc.BlockPending, + BlockHeader: rpc.BlockHeader{ + Hash: header.Hash, + ParentHash: header.ParentHash, + Number: header.Number, + NewRoot: header.NewRoot, + Timestamp: header.Timestamp, + SequencerAddress: header.SequencerAddress, + L1GasPrice: header.L1GasPrice, + StarknetVersion: header.StarknetVersion, + }, + Transactions: transactions, + Receipts: receipts, + }, resp) + }) + + t.Run("Receipts - accepted L1 block", func(t *testing.T) { + t.Skip() + block1, err := gw.BlockByNumber(context.Background(), 1) + require.NoError(t, err) + + blockID := rpc.BlockID{Number: block1.Number} + + mockReader.EXPECT().BlockByNumber(blockID.Number).Return(block1, nil) + mockReader.EXPECT().L1Head().Return(&core.L1Head{ + BlockNumber: block1.Number + 1, + }, nil) + + resp, rpcErr := handler.BlockWithTxsAndReceipts(blockID) + header := resp.BlockHeader + + var transactions []*rpc.Transaction + var receipts []*rpc.TransactionReceipt + for i, tx := range block1.Transactions { + adaptedReceipt := rpc.AdaptReceipt(block1.Receipts[i], tx, rpc.TxnAcceptedOnL1, nil, 0, true) + adaptedTx := rpc.AdaptTransaction(tx) + + transactions = append(transactions, adaptedTx) + receipts = append(receipts, adaptedReceipt) + } + + assert.Nil(t, rpcErr) + assert.Equal(t, &rpc.BlockWithTxsAndReceipts{ + Status: rpc.BlockAcceptedL1, + BlockHeader: rpc.BlockHeader{ + Hash: header.Hash, + ParentHash: header.ParentHash, + Number: header.Number, + NewRoot: header.NewRoot, + Timestamp: header.Timestamp, + SequencerAddress: header.SequencerAddress, + L1GasPrice: header.L1GasPrice, + StarknetVersion: header.StarknetVersion, + }, + Transactions: transactions, + Receipts: receipts, + }, resp) + }) +} + func TestBlockId(t *testing.T) { t.Parallel() tests := map[string]struct { diff --git a/rpc/handlers.go b/rpc/handlers.go index 1c1327a7e5..391ef49b5c 100644 --- a/rpc/handlers.go +++ b/rpc/handlers.go @@ -198,7 +198,7 @@ func (h *Handler) Methods() ([]jsonrpc.Method, string) { //nolint: funlen { Name: "juno_getBlockWithTxsAndReceipts", Params: []jsonrpc.Parameter{{Name: "block_id"}}, - Handler: h.juno_getBlockWithTxsAndReceipts, + Handler: h.BlockWithTxsAndReceipts, }, // End of Artem's intern exercise 4 { From a9771a73c24c6202969c271158ae72671983b09e Mon Sep 17 00:00:00 2001 From: Artem Streltsov Date: Fri, 14 Jun 2024 17:04:19 +0100 Subject: [PATCH 07/13] change return type in juno_getNodesFromRoot --- core/trie/trie.go | 13 ++++++++----- rpc/handlers.go | 14 ++++++++------ 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/core/trie/trie.go b/core/trie/trie.go index baa99aa74f..933eecfc5b 100644 --- a/core/trie/trie.go +++ b/core/trie/trie.go @@ -100,13 +100,16 @@ func (t *Trie) GetNodesFromRoot(key *Key) ([]storageNode, error) { return t.nodesFromRoot(key) } -func (t *Trie) ParseNodes(nodes []storageNode) ([]string, error) { - var parsedNodes []string - for _, node := range nodes { - parsedNodes = append(parsedNodes, fmt.Sprintf("%v", &node.node.Value)) +func (t *Trie) ParseNodes(nodes []storageNode) ([]map[string]string, error) { + result := make([]map[string]string, len(nodes)) + for i, node := range nodes { + result[i] = map[string]string{ + "key": fmt.Sprintf("%v", node.key), + "value": fmt.Sprintf("%v", node.node.Value), + } } - return parsedNodes, nil + return result, nil } // findCommonKey finds the set of common MSB bits in two key bitsets. diff --git a/rpc/handlers.go b/rpc/handlers.go index 391ef49b5c..d4625837cc 100644 --- a/rpc/handlers.go +++ b/rpc/handlers.go @@ -171,7 +171,7 @@ func (h *Handler) SpecVersionV0_6() (string, *jsonrpc.Error) { return "0.6.0", nil } -func (h *Handler) juno_getNodesFromRoot(key felt.Felt) ([]string, *jsonrpc.Error) { +func (h *Handler) NodesFromRoot(key felt.Felt) ([]map[string]string, *jsonrpc.Error) { stateReader, _, err := h.bcReader.HeadState() if err != nil { return nil, jsonrpc.Err(jsonrpc.InternalError, err.Error()) @@ -188,23 +188,25 @@ func (h *Handler) juno_getNodesFromRoot(key felt.Felt) ([]string, *jsonrpc.Error return nil, jsonrpc.Err(jsonrpc.InternalError, err.Error()) } - nodes, _ := try.ParseNodes(storageNodes) - return nodes, nil + result, err := try.ParseNodes(storageNodes) + if err != nil { + return nil, jsonrpc.Err(jsonrpc.InternalError, err.Error()) + } + + return result, nil } func (h *Handler) Methods() ([]jsonrpc.Method, string) { //nolint: funlen return []jsonrpc.Method{ - // Artem's intern exercise 4 { Name: "juno_getBlockWithTxsAndReceipts", Params: []jsonrpc.Parameter{{Name: "block_id"}}, Handler: h.BlockWithTxsAndReceipts, }, - // End of Artem's intern exercise 4 { Name: "juno_getNodesFromRoot", Params: []jsonrpc.Parameter{{Name: "key"}}, - Handler: h.juno_getNodesFromRoot, + Handler: h.NodesFromRoot, }, { Name: "starknet_chainId", From 08e32996cb5ccb357126cf7fb5e09ae4eb6aa80d Mon Sep 17 00:00:00 2001 From: Artem Streltsov Date: Mon, 17 Jun 2024 13:37:30 +0600 Subject: [PATCH 08/13] add test for juno_getNodesFromRoot --- rpc/handlers_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/rpc/handlers_test.go b/rpc/handlers_test.go index 48374d20d8..b405135526 100644 --- a/rpc/handlers_test.go +++ b/rpc/handlers_test.go @@ -6,6 +6,8 @@ import ( "github.com/NethermindEth/juno/core" "github.com/NethermindEth/juno/core/felt" + "github.com/NethermindEth/juno/db" + "github.com/NethermindEth/juno/jsonrpc" "github.com/NethermindEth/juno/mocks" "github.com/NethermindEth/juno/node" "github.com/NethermindEth/juno/rpc" @@ -15,6 +17,24 @@ import ( "go.uber.org/mock/gomock" ) +func TestJunoGetNodesFromRoot(t *testing.T) { + mockCtrl := gomock.NewController(t) + t.Cleanup(mockCtrl.Finish) + + n := utils.Ptr(utils.Mainnet) + mockReader := mocks.NewMockReader(mockCtrl) + log := utils.NewNopZapLogger() + handler := rpc.New(mockReader, nil, nil, "", n, log) + + t.Run("Empty blockchain", func(t *testing.T) { + mockReader.EXPECT().HeadState().Return(nil, nil, db.ErrKeyNotFound) + + storage, rpcErr := handler.NodesFromRoot(felt.Zero) + require.Nil(t, storage) + assert.Equal(t, jsonrpc.InternalError, rpcErr.Code) + }) +} + func nopCloser() error { return nil } func TestVersion(t *testing.T) { From 7d8152b2dc2e783d1cdeb8b46bcd11671158b9f7 Mon Sep 17 00:00:00 2001 From: Artem Streltsov Date: Tue, 25 Jun 2024 19:41:44 +0600 Subject: [PATCH 09/13] fix tab formatting --- blockchain/pending.go | 2 +- core/state.go | 2 +- core/state_snapshot.go | 2 +- core/trie/trie.go | 2 +- rpc/block_test.go | 35 +++++++++++++++++------------------ rpc/handlers.go | 10 +++++----- rpc/handlers_test.go | 6 +++--- sync/sync.go | 16 ++++++++-------- 8 files changed, 37 insertions(+), 38 deletions(-) diff --git a/blockchain/pending.go b/blockchain/pending.go index bc07c56de4..ce65b38809 100644 --- a/blockchain/pending.go +++ b/blockchain/pending.go @@ -3,7 +3,7 @@ package blockchain import ( "github.com/NethermindEth/juno/core" "github.com/NethermindEth/juno/core/felt" - "github.com/NethermindEth/juno/core/trie" + "github.com/NethermindEth/juno/core/trie" ) type Pending struct { diff --git a/core/state.go b/core/state.go index bf6e15b932..401cfbb6ca 100644 --- a/core/state.go +++ b/core/state.go @@ -40,7 +40,7 @@ type StateReader interface { ContractNonce(addr *felt.Felt) (*felt.Felt, error) ContractStorage(addr, key *felt.Felt) (*felt.Felt, error) Class(classHash *felt.Felt) (*DeclaredClass, error) - GetClassesTrie() (*trie.Trie, func() error, error) + GetClassesTrie() (*trie.Trie, func() error, error) } func (s *State) GetClassesTrie() (*trie.Trie, func() error, error) { diff --git a/core/state_snapshot.go b/core/state_snapshot.go index 3bb4b0e41b..fbeef8587b 100644 --- a/core/state_snapshot.go +++ b/core/state_snapshot.go @@ -5,7 +5,7 @@ import ( "github.com/NethermindEth/juno/core/felt" "github.com/NethermindEth/juno/db" - "github.com/NethermindEth/juno/core/trie" + "github.com/NethermindEth/juno/core/trie" ) type stateSnapshot struct { diff --git a/core/trie/trie.go b/core/trie/trie.go index 933eecfc5b..26e9414ce4 100644 --- a/core/trie/trie.go +++ b/core/trie/trie.go @@ -97,7 +97,7 @@ func (t *Trie) ConvertFeltToKey(key *felt.Felt) Key { } func (t *Trie) GetNodesFromRoot(key *Key) ([]storageNode, error) { - return t.nodesFromRoot(key) + return t.nodesFromRoot(key) } func (t *Trie) ParseNodes(nodes []storageNode) ([]map[string]string, error) { diff --git a/rpc/block_test.go b/rpc/block_test.go index 0c87aa49fd..c66305251a 100644 --- a/rpc/block_test.go +++ b/rpc/block_test.go @@ -21,7 +21,7 @@ import ( ) func TestBlockWithTxsAndReceipts(t *testing.T) { - errTests := map[string]rpc.BlockID{ + errTests := map[string]rpc.BlockID{ "latest": {Latest: true}, "pending": {Pending: true}, "hash": {Hash: new(felt.Felt).SetUint64(1)}, @@ -48,9 +48,8 @@ func TestBlockWithTxsAndReceipts(t *testing.T) { mockReader := mocks.NewMockReader(mockCtrl) handler := rpc.New(mockReader, nil, nil, "", n, nil) - t.Run("Receipts - transaction not found", func(t *testing.T) { + t.Run("Receipts - transaction not found", func(t *testing.T) { blockID := rpc.BlockID{Number: 777} - mockReader.EXPECT().BlockByNumber(blockID.Number).Return(nil, db.ErrKeyNotFound) resp, rpcErr := handler.BlockWithTxsAndReceipts(blockID) @@ -185,7 +184,7 @@ func TestBlockWithTxsAndReceipts(t *testing.T) { checkLatestBlock(t, blockWithTxHashes, blockWithTxsAndReceipts) }) - t.Run("Receipts - pending block", func(t *testing.T) { + t.Run("Receipts - pending block", func(t *testing.T) { t.Skip() block0, err := gw.BlockByNumber(context.Background(), 0) require.NoError(t, err) @@ -201,11 +200,11 @@ func TestBlockWithTxsAndReceipts(t *testing.T) { var transactions []*rpc.Transaction var receipts []*rpc.TransactionReceipt for i, tx := range block0.Transactions { - adaptedReceipt := rpc.AdaptReceipt(block0.Receipts[i], tx, rpc.TxnAcceptedOnL2, nil, 0, true) - adaptedTx := rpc.AdaptTransaction(tx) + adaptedReceipt := rpc.AdaptReceipt(block0.Receipts[i], tx, rpc.TxnAcceptedOnL2, nil, 0, true) + adaptedTx := rpc.AdaptTransaction(tx) - transactions = append(transactions, adaptedTx) - receipts = append(receipts, adaptedReceipt) + transactions = append(transactions, adaptedTx) + receipts = append(receipts, adaptedReceipt) } assert.Nil(t, rpcErr) @@ -222,7 +221,7 @@ func TestBlockWithTxsAndReceipts(t *testing.T) { StarknetVersion: header.StarknetVersion, }, Transactions: transactions, - Receipts: receipts, + Receipts: receipts, }, resp) }) @@ -241,15 +240,15 @@ func TestBlockWithTxsAndReceipts(t *testing.T) { resp, rpcErr := handler.BlockWithTxsAndReceipts(blockID) header := resp.BlockHeader - var transactions []*rpc.Transaction - var receipts []*rpc.TransactionReceipt - for i, tx := range block1.Transactions { - adaptedReceipt := rpc.AdaptReceipt(block1.Receipts[i], tx, rpc.TxnAcceptedOnL1, nil, 0, true) - adaptedTx := rpc.AdaptTransaction(tx) + var transactions []*rpc.Transaction + var receipts []*rpc.TransactionReceipt + for i, tx := range block1.Transactions { + adaptedReceipt := rpc.AdaptReceipt(block1.Receipts[i], tx, rpc.TxnAcceptedOnL1, nil, 0, true) + adaptedTx := rpc.AdaptTransaction(tx) - transactions = append(transactions, adaptedTx) - receipts = append(receipts, adaptedReceipt) - } + transactions = append(transactions, adaptedTx) + receipts = append(receipts, adaptedReceipt) + } assert.Nil(t, rpcErr) assert.Equal(t, &rpc.BlockWithTxsAndReceipts{ @@ -265,7 +264,7 @@ func TestBlockWithTxsAndReceipts(t *testing.T) { StarknetVersion: header.StarknetVersion, }, Transactions: transactions, - Receipts: receipts, + Receipts: receipts, }, resp) }) } diff --git a/rpc/handlers.go b/rpc/handlers.go index d4625837cc..b1dfbe25de 100644 --- a/rpc/handlers.go +++ b/rpc/handlers.go @@ -198,12 +198,12 @@ func (h *Handler) NodesFromRoot(key felt.Felt) ([]map[string]string, *jsonrpc.Er func (h *Handler) Methods() ([]jsonrpc.Method, string) { //nolint: funlen return []jsonrpc.Method{ - { - Name: "juno_getBlockWithTxsAndReceipts", + { + Name: "juno_getBlockWithTxsAndReceipts", Params: []jsonrpc.Parameter{{Name: "block_id"}}, - Handler: h.BlockWithTxsAndReceipts, - }, - { + Handler: h.BlockWithTxsAndReceipts, + }, + { Name: "juno_getNodesFromRoot", Params: []jsonrpc.Parameter{{Name: "key"}}, Handler: h.NodesFromRoot, diff --git a/rpc/handlers_test.go b/rpc/handlers_test.go index b405135526..6db6e9d07a 100644 --- a/rpc/handlers_test.go +++ b/rpc/handlers_test.go @@ -18,15 +18,15 @@ import ( ) func TestJunoGetNodesFromRoot(t *testing.T) { - mockCtrl := gomock.NewController(t) + mockCtrl := gomock.NewController(t) t.Cleanup(mockCtrl.Finish) - n := utils.Ptr(utils.Mainnet) + n := utils.Ptr(utils.Mainnet) mockReader := mocks.NewMockReader(mockCtrl) log := utils.NewNopZapLogger() handler := rpc.New(mockReader, nil, nil, "", n, log) - t.Run("Empty blockchain", func(t *testing.T) { + t.Run("Empty blockchain", func(t *testing.T) { mockReader.EXPECT().HeadState().Return(nil, nil, db.ErrKeyNotFound) storage, rpcErr := handler.NodesFromRoot(felt.Zero) diff --git a/sync/sync.go b/sync/sync.go index a7aa21a792..4a7b672dcc 100644 --- a/sync/sync.go +++ b/sync/sync.go @@ -71,7 +71,7 @@ type Synchronizer struct { startingBlockNumber *uint64 highestBlockHeader atomic.Pointer[core.Header] newHeads *feed.Feed[*core.Header] - latestBlockHeight uint64 + latestBlockHeight uint64 log utils.SimpleLogger listener EventListener @@ -83,18 +83,18 @@ type Synchronizer struct { func New(bc *blockchain.Blockchain, starkNetData starknetdata.StarknetData, log utils.SimpleLogger, pendingPollInterval time.Duration, readOnlyBlockchain bool, ) *Synchronizer { - head, err := bc.Head() - latestBlockHeight := uint64(0) - if err == nil { - latestBlockHeight = head.Number + uint64(1) - } + head, err := bc.Head() + latestBlockHeight := uint64(0) + if err == nil { + latestBlockHeight = head.Number + uint64(1) + } s := &Synchronizer{ blockchain: bc, starknetData: starkNetData, log: log, newHeads: feed.New[*core.Header](), - latestBlockHeight: latestBlockHeight, + latestBlockHeight: latestBlockHeight, pendingPollInterval: pendingPollInterval, listener: &SelectiveListener{}, readOnlyBlockchain: readOnlyBlockchain, @@ -111,7 +111,7 @@ func (s *Synchronizer) WithListener(listener EventListener) *Synchronizer { // Run starts the Synchronizer, returns an error if the loop is already running func (s *Synchronizer) Run(ctx context.Context) error { //s.syncBlocks(ctx) - s.syncBlocksFromFeederGateway(ctx) + s.syncBlocksFromFeederGateway(ctx) return nil } From 1350b20ba07a3787824d592394284505de9430c4 Mon Sep 17 00:00:00 2001 From: Artem Streltsov Date: Wed, 26 Jun 2024 22:52:27 +0600 Subject: [PATCH 10/13] fix juno_getBlockWithTxsAndReceipts --- rpc/block_test.go | 10 ++++++---- rpc/handlers_test.go | 3 +-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/rpc/block_test.go b/rpc/block_test.go index c66305251a..48aa82745e 100644 --- a/rpc/block_test.go +++ b/rpc/block_test.go @@ -33,7 +33,7 @@ func TestBlockWithTxsAndReceipts(t *testing.T) { log := utils.NewNopZapLogger() n := utils.Ptr(utils.Mainnet) chain := blockchain.New(pebble.NewMemTest(t), n) - handler := rpc.New(chain, nil, nil, "", n, log) + handler := rpc.New(chain, nil, nil, "", log) blockWithTxsAndReceipts, rpcErr := handler.BlockWithTxsAndReceipts(id) assert.Nil(t, blockWithTxsAndReceipts) @@ -46,7 +46,7 @@ func TestBlockWithTxsAndReceipts(t *testing.T) { n := utils.Ptr(utils.Mainnet) mockReader := mocks.NewMockReader(mockCtrl) - handler := rpc.New(mockReader, nil, nil, "", n, nil) + handler := rpc.New(mockReader, nil, nil, "", nil) t.Run("Receipts - transaction not found", func(t *testing.T) { blockID := rpc.BlockID{Number: 777} @@ -185,7 +185,6 @@ func TestBlockWithTxsAndReceipts(t *testing.T) { }) t.Run("Receipts - pending block", func(t *testing.T) { - t.Skip() block0, err := gw.BlockByNumber(context.Background(), 0) require.NoError(t, err) @@ -218,6 +217,8 @@ func TestBlockWithTxsAndReceipts(t *testing.T) { Timestamp: header.Timestamp, SequencerAddress: header.SequencerAddress, L1GasPrice: header.L1GasPrice, + L1DataGasPrice: header.L1DataGasPrice, + L1DAMode: header.L1DAMode, StarknetVersion: header.StarknetVersion, }, Transactions: transactions, @@ -226,7 +227,6 @@ func TestBlockWithTxsAndReceipts(t *testing.T) { }) t.Run("Receipts - accepted L1 block", func(t *testing.T) { - t.Skip() block1, err := gw.BlockByNumber(context.Background(), 1) require.NoError(t, err) @@ -261,6 +261,8 @@ func TestBlockWithTxsAndReceipts(t *testing.T) { Timestamp: header.Timestamp, SequencerAddress: header.SequencerAddress, L1GasPrice: header.L1GasPrice, + L1DataGasPrice: header.L1DataGasPrice, + L1DAMode: header.L1DAMode, StarknetVersion: header.StarknetVersion, }, Transactions: transactions, diff --git a/rpc/handlers_test.go b/rpc/handlers_test.go index 6db6e9d07a..8004e2d194 100644 --- a/rpc/handlers_test.go +++ b/rpc/handlers_test.go @@ -21,10 +21,9 @@ func TestJunoGetNodesFromRoot(t *testing.T) { mockCtrl := gomock.NewController(t) t.Cleanup(mockCtrl.Finish) - n := utils.Ptr(utils.Mainnet) mockReader := mocks.NewMockReader(mockCtrl) log := utils.NewNopZapLogger() - handler := rpc.New(mockReader, nil, nil, "", n, log) + handler := rpc.New(mockReader, nil, nil, "", log) t.Run("Empty blockchain", func(t *testing.T) { mockReader.EXPECT().HeadState().Return(nil, nil, db.ErrKeyNotFound) From 55d79721598b6f9db1c7859a88ba80dcc031ccdf Mon Sep 17 00:00:00 2001 From: Artem Streltsov Date: Thu, 27 Jun 2024 01:53:46 +0600 Subject: [PATCH 11/13] make linter happy --- core/state.go | 8 +- core/state_snapshot.go | 2 +- core/trie/trie.go | 18 +- mocks/mock_state.go | 17 ++ rpc/block.go | 66 ++--- rpc/block_test.go | 23 +- rpc/handlers.go | 18 +- sync/sync.go | 624 ++++++++++++++--------------------------- 8 files changed, 293 insertions(+), 483 deletions(-) diff --git a/core/state.go b/core/state.go index 401cfbb6ca..5ee9669bbd 100644 --- a/core/state.go +++ b/core/state.go @@ -43,10 +43,6 @@ type StateReader interface { GetClassesTrie() (*trie.Trie, func() error, error) } -func (s *State) GetClassesTrie() (*trie.Trie, func() error, error) { - return s.classesTrie() -} - type State struct { *history txn db.Transaction @@ -136,6 +132,10 @@ func (s *State) classesTrie() (*trie.Trie, func() error, error) { return s.globalTrie(db.ClassesTrie, trie.NewTriePoseidon) } +func (s *State) GetClassesTrie() (*trie.Trie, func() error, error) { + return s.classesTrie() +} + func (s *State) globalTrie(bucket db.Bucket, newTrie trie.NewTrieFunc) (*trie.Trie, func() error, error) { dbPrefix := bucket.Key() tTxn := trie.NewStorage(s.txn, dbPrefix) diff --git a/core/state_snapshot.go b/core/state_snapshot.go index fbeef8587b..70bed78b95 100644 --- a/core/state_snapshot.go +++ b/core/state_snapshot.go @@ -4,8 +4,8 @@ import ( "errors" "github.com/NethermindEth/juno/core/felt" - "github.com/NethermindEth/juno/db" "github.com/NethermindEth/juno/core/trie" + "github.com/NethermindEth/juno/db" ) type stateSnapshot struct { diff --git a/core/trie/trie.go b/core/trie/trie.go index 26e9414ce4..0e0c8e0d07 100644 --- a/core/trie/trie.go +++ b/core/trie/trie.go @@ -101,15 +101,15 @@ func (t *Trie) GetNodesFromRoot(key *Key) ([]storageNode, error) { } func (t *Trie) ParseNodes(nodes []storageNode) ([]map[string]string, error) { - result := make([]map[string]string, len(nodes)) - for i, node := range nodes { - result[i] = map[string]string{ - "key": fmt.Sprintf("%v", node.key), - "value": fmt.Sprintf("%v", node.node.Value), - } - } - - return result, nil + result := make([]map[string]string, len(nodes)) + for i, node := range nodes { + result[i] = map[string]string{ + "key": node.key.String(), + "value": node.node.Value.String(), + } + } + + return result, nil } // findCommonKey finds the set of common MSB bits in two key bitsets. diff --git a/mocks/mock_state.go b/mocks/mock_state.go index 8994085984..3523a81886 100644 --- a/mocks/mock_state.go +++ b/mocks/mock_state.go @@ -14,6 +14,7 @@ import ( core "github.com/NethermindEth/juno/core" felt "github.com/NethermindEth/juno/core/felt" + trie "github.com/NethermindEth/juno/core/trie" gomock "go.uber.org/mock/gomock" ) @@ -159,3 +160,19 @@ func (mr *MockStateHistoryReaderMockRecorder) ContractStorageAt(arg0, arg1, arg2 mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContractStorageAt", reflect.TypeOf((*MockStateHistoryReader)(nil).ContractStorageAt), arg0, arg1, arg2) } + +// GetClassesTrie mocks base method. +func (m *MockStateHistoryReader) GetClassesTrie() (*trie.Trie, func() error, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetClassesTrie") + ret0, _ := ret[0].(*trie.Trie) + ret1, _ := ret[1].(func() error) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetClassesTrie indicates an expected call of GetClassesTrie. +func (mr *MockStateHistoryReaderMockRecorder) GetClassesTrie() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClassesTrie", reflect.TypeOf((*MockStateHistoryReader)(nil).GetClassesTrie)) +} diff --git a/rpc/block.go b/rpc/block.go index b9d8dc2338..8f24b2c0fc 100644 --- a/rpc/block.go +++ b/rpc/block.go @@ -279,42 +279,42 @@ func (h *Handler) BlockWithTxs(id BlockID) (*BlockWithTxs, *jsonrpc.Error) { } type BlockWithTxsAndReceipts struct { - Status BlockStatus - BlockHeader - Transactions []*Transaction - Receipts []*TransactionReceipt + Status BlockStatus + BlockHeader + Transactions []*Transaction + Receipts []*TransactionReceipt } func (h *Handler) BlockWithTxsAndReceipts(id BlockID) (*BlockWithTxsAndReceipts, *jsonrpc.Error) { - block, rpcErr := h.blockByID(&id) - if rpcErr != nil { - return nil, rpcErr - } - - blockStatus, rpcErr := h.blockStatus(id, block) - if rpcErr != nil { - return nil, rpcErr - } - - finalityStatus := TxnAcceptedOnL2 - if blockStatus == BlockAcceptedL1 { - finalityStatus = TxnAcceptedOnL1 - } - - txs := make([]*Transaction, len(block.Transactions)) - receipts := make([]*TransactionReceipt, len(block.Transactions)) - for index, txn := range block.Transactions { - txs[index] = AdaptTransaction(txn) - r := block.Receipts[index] - receipts[index] = AdaptReceipt(r, txn, finalityStatus, nil, 0, false) - } - - return &BlockWithTxsAndReceipts{ - Status: blockStatus, - BlockHeader: adaptBlockHeader(block.Header), - Transactions: txs, - Receipts: receipts, - }, nil + block, rpcErr := h.blockByID(&id) + if rpcErr != nil { + return nil, rpcErr + } + + blockStatus, rpcErr := h.blockStatus(id, block) + if rpcErr != nil { + return nil, rpcErr + } + + finalityStatus := TxnAcceptedOnL2 + if blockStatus == BlockAcceptedL1 { + finalityStatus = TxnAcceptedOnL1 + } + + txs := make([]*Transaction, len(block.Transactions)) + receipts := make([]*TransactionReceipt, len(block.Transactions)) + for index, txn := range block.Transactions { + txs[index] = AdaptTransaction(txn) + r := block.Receipts[index] + receipts[index] = AdaptReceipt(r, txn, finalityStatus, nil, 0, false) + } + + return &BlockWithTxsAndReceipts{ + Status: blockStatus, + BlockHeader: adaptBlockHeader(block.Header), + Transactions: txs, + Receipts: receipts, + }, nil } func (h *Handler) BlockWithTxsV0_6(id BlockID) (*BlockWithTxs, *jsonrpc.Error) { diff --git a/rpc/block_test.go b/rpc/block_test.go index 48aa82745e..6345a5cf78 100644 --- a/rpc/block_test.go +++ b/rpc/block_test.go @@ -20,6 +20,7 @@ import ( "go.uber.org/mock/gomock" ) +//nolint:dupl func TestBlockWithTxsAndReceipts(t *testing.T) { errTests := map[string]rpc.BlockID{ "latest": {Latest: true}, @@ -196,15 +197,15 @@ func TestBlockWithTxsAndReceipts(t *testing.T) { resp, rpcErr := handler.BlockWithTxsAndReceipts(blockID) header := resp.BlockHeader - var transactions []*rpc.Transaction - var receipts []*rpc.TransactionReceipt - for i, tx := range block0.Transactions { + var transactions []*rpc.Transaction + var receipts []*rpc.TransactionReceipt + for i, tx := range block0.Transactions { adaptedReceipt := rpc.AdaptReceipt(block0.Receipts[i], tx, rpc.TxnAcceptedOnL2, nil, 0, true) adaptedTx := rpc.AdaptTransaction(tx) transactions = append(transactions, adaptedTx) receipts = append(receipts, adaptedReceipt) - } + } assert.Nil(t, rpcErr) assert.Equal(t, &rpc.BlockWithTxsAndReceipts{ @@ -217,12 +218,12 @@ func TestBlockWithTxsAndReceipts(t *testing.T) { Timestamp: header.Timestamp, SequencerAddress: header.SequencerAddress, L1GasPrice: header.L1GasPrice, - L1DataGasPrice: header.L1DataGasPrice, - L1DAMode: header.L1DAMode, + L1DataGasPrice: header.L1DataGasPrice, + L1DAMode: header.L1DAMode, StarknetVersion: header.StarknetVersion, }, Transactions: transactions, - Receipts: receipts, + Receipts: receipts, }, resp) }) @@ -261,12 +262,12 @@ func TestBlockWithTxsAndReceipts(t *testing.T) { Timestamp: header.Timestamp, SequencerAddress: header.SequencerAddress, L1GasPrice: header.L1GasPrice, - L1DataGasPrice: header.L1DataGasPrice, - L1DAMode: header.L1DAMode, + L1DataGasPrice: header.L1DataGasPrice, + L1DAMode: header.L1DAMode, StarknetVersion: header.StarknetVersion, }, Transactions: transactions, - Receipts: receipts, + Receipts: receipts, }, resp) }) } @@ -597,6 +598,7 @@ func TestBlockWithTxHashes(t *testing.T) { }) } +//nolint:dupl func TestBlockWithTxs(t *testing.T) { errTests := map[string]rpc.BlockID{ "latest": {Latest: true}, @@ -805,6 +807,7 @@ func TestBlockWithTxHashesV013(t *testing.T) { }, got) } +//nolint:dupl func TestBlockWithReceipts(t *testing.T) { mockCtrl := gomock.NewController(t) t.Cleanup(mockCtrl.Finish) diff --git a/rpc/handlers.go b/rpc/handlers.go index b1dfbe25de..ccfade5ed9 100644 --- a/rpc/handlers.go +++ b/rpc/handlers.go @@ -172,9 +172,9 @@ func (h *Handler) SpecVersionV0_6() (string, *jsonrpc.Error) { } func (h *Handler) NodesFromRoot(key felt.Felt) ([]map[string]string, *jsonrpc.Error) { - stateReader, _, err := h.bcReader.HeadState() + stateReader, _, err := h.bcReader.HeadState() if err != nil { - return nil, jsonrpc.Err(jsonrpc.InternalError, err.Error()) + return nil, jsonrpc.Err(jsonrpc.InternalError, err.Error()) } try, _, errTry := stateReader.GetClassesTrie() @@ -188,19 +188,19 @@ func (h *Handler) NodesFromRoot(key felt.Felt) ([]map[string]string, *jsonrpc.Er return nil, jsonrpc.Err(jsonrpc.InternalError, err.Error()) } - result, err := try.ParseNodes(storageNodes) - if err != nil { - return nil, jsonrpc.Err(jsonrpc.InternalError, err.Error()) - } + result, err := try.ParseNodes(storageNodes) + if err != nil { + return nil, jsonrpc.Err(jsonrpc.InternalError, err.Error()) + } - return result, nil + return result, nil } func (h *Handler) Methods() ([]jsonrpc.Method, string) { //nolint: funlen return []jsonrpc.Method{ { - Name: "juno_getBlockWithTxsAndReceipts", - Params: []jsonrpc.Parameter{{Name: "block_id"}}, + Name: "juno_getBlockWithTxsAndReceipts", + Params: []jsonrpc.Parameter{{Name: "block_id"}}, Handler: h.BlockWithTxsAndReceipts, }, { diff --git a/sync/sync.go b/sync/sync.go index 4a7b672dcc..06f2d53c7a 100644 --- a/sync/sync.go +++ b/sync/sync.go @@ -6,7 +6,7 @@ import ( "errors" "fmt" "net/http" - "runtime" + "net/url" "sync/atomic" "time" @@ -20,7 +20,6 @@ import ( "github.com/NethermindEth/juno/starknet" "github.com/NethermindEth/juno/starknetdata" "github.com/NethermindEth/juno/utils" - "github.com/sourcegraph/conc/stream" ) var ( @@ -72,12 +71,10 @@ type Synchronizer struct { highestBlockHeader atomic.Pointer[core.Header] newHeads *feed.Feed[*core.Header] latestBlockHeight uint64 - - log utils.SimpleLogger - listener EventListener - + log utils.SimpleLogger + listener EventListener pendingPollInterval time.Duration - catchUpMode bool + timeoutDuration time.Duration } func New(bc *blockchain.Blockchain, starkNetData starknetdata.StarknetData, @@ -98,6 +95,7 @@ func New(bc *blockchain.Blockchain, starkNetData starknetdata.StarknetData, pendingPollInterval: pendingPollInterval, listener: &SelectiveListener{}, readOnlyBlockchain: readOnlyBlockchain, + timeoutDuration: 10 * time.Second, } return s } @@ -110,487 +108,279 @@ func (s *Synchronizer) WithListener(listener EventListener) *Synchronizer { // Run starts the Synchronizer, returns an error if the loop is already running func (s *Synchronizer) Run(ctx context.Context) error { - //s.syncBlocks(ctx) s.syncBlocksFromFeederGateway(ctx) return nil } func (s *Synchronizer) syncBlocksFromFeederGateway(syncCtx context.Context) { - streamCtx, streamCancel := context.WithCancel(syncCtx) - for { - select { - case <- streamCtx.Done(): - streamCancel() - return - default: - s.log.Infow("Fetching from feeder gateway") - block, stateUpdate, blockCommitments := s.getBlockDetails(s.latestBlockHeight) - newClasses, err := s.fetchUnknownClasses(syncCtx, &stateUpdate) - if err != nil { - s.log.Errorw("Error fetching unknown classes: %v", err) - } - err = s.blockchain.Store(&block, &blockCommitments, &stateUpdate, newClasses) - if err != nil { - s.log.Errorw("Error storing block: %v", err) - } else { - s.log.Infow("Stored Block", "number", block.Number, "hash", block.Hash.ShortString(), "root", block.GlobalStateRoot.ShortString()) - } - s.latestBlockHeight += 1 - } - } -} - -func (s *Synchronizer) getBlockDetails(blockNumber uint64) (core.Block, core.StateUpdate, core.BlockCommitments) { - block, blockCommitments := s.getBlockFromFeederGateway(blockNumber) - stateUpdate := s.getStateUpdateFromFeederGateway(blockNumber) - return block, stateUpdate, blockCommitments -} - -func (s *Synchronizer) getBlockFromFeederGateway(blockNumber uint64) (core.Block, core.BlockCommitments) { - blockUrl := fmt.Sprintf("https://alpha-mainnet.starknet.io/feeder_gateway/get_block?blockNumber=%d", blockNumber) - blockResponse, err := http.Get(blockUrl) - if err != nil { - s.log.Errorw("Error getting response: %v", err) - return core.Block{}, core.BlockCommitments{} - } - defer blockResponse.Body.Close() - - var block starknet.Block - if blockResponse.StatusCode == http.StatusOK { - decoder := json.NewDecoder(blockResponse.Body) - if err := decoder.Decode(&block); err != nil { - s.log.Errorw("Failed to decode response: %v", err) - return core.Block{}, core.BlockCommitments{} - } - } else { - s.log.Warnw("Received non-OK HTTP status: %v", blockResponse.Status) - return core.Block{}, core.BlockCommitments{} - } - - signatureUrl := fmt.Sprintf("https://alpha-mainnet.starknet.io/feeder_gateway/get_signature?blockNumber=%d", block.Number) - signatureResponse, err := http.Get(signatureUrl) - if err != nil { - s.log.Errorw("Error getting response: %v", err) - return core.Block{}, core.BlockCommitments{} - } - defer signatureResponse.Body.Close() - - var signature starknet.Signature - if signatureResponse.StatusCode == http.StatusOK { - decoder := json.NewDecoder(signatureResponse.Body) - if err := decoder.Decode(&signature); err != nil { - s.log.Errorw("Failed to decode response: %v", err) - return core.Block{}, core.BlockCommitments{} - } - } else { - s.log.Warnw("Received non-OK HTTP status: %v", signatureResponse.Status) - return core.Block{}, core.BlockCommitments{} - } - - var adaptedTransactions []core.Transaction - for _, transaction := range block.Transactions { - adaptedTransaction, err := sn2core.AdaptTransaction(transaction) - if err != nil { - s.log.Errorw("Error adapting starknet type to core type: %v", err) - return core.Block{}, core.BlockCommitments{} - } - adaptedTransactions = append(adaptedTransactions, adaptedTransaction) - } - - var eventCount uint64 - var adaptedTransactionReceipts []*core.TransactionReceipt - for _, receipt := range block.Receipts { - eventCount += uint64(len(receipt.Events)) - adaptedTransactionReceipt := sn2core.AdaptTransactionReceipt(receipt) - adaptedTransactionReceipts = append(adaptedTransactionReceipts, adaptedTransactionReceipt) - } - - signatures := [][]*felt.Felt{} - signatures = append(signatures, signature.Signature) - - header := &core.Header{ - Hash: block.Hash, - ParentHash: block.ParentHash, - Number: block.Number, - GlobalStateRoot: block.StateRoot, - SequencerAddress: block.SequencerAddress, - TransactionCount: uint64(len(block.Transactions)), - EventCount: eventCount, - Timestamp: block.Timestamp, - ProtocolVersion: block.Version, - EventsBloom: core.EventsBloom(adaptedTransactionReceipts), - GasPrice: block.GasPriceETH(), - Signatures: signatures, - GasPriceSTRK: block.GasPriceSTRK(), - L1DAMode: core.L1DAMode(block.L1DAMode), - L1DataGasPrice: (*core.GasPrice)(block.L1DataGasPrice), - } - - return core.Block{ - Header: header, - Transactions: adaptedTransactions, - Receipts: adaptedTransactionReceipts, - }, - core.BlockCommitments{ - TransactionCommitment: block.TransactionCommitment, - EventCommitment: block.EventCommitment, - } -} - -func (s *Synchronizer) getStateUpdateFromFeederGateway(blockNumber uint64) core.StateUpdate { - stateUpdateUrl := fmt.Sprintf("https://alpha-mainnet.starknet.io/feeder_gateway/get_state_update?blockNumber=%d", blockNumber) - stateUpdateResponse, err := http.Get(stateUpdateUrl) - if err != nil { - s.log.Errorw("Error getting response: %v", err) - return core.StateUpdate{} - } - defer stateUpdateResponse.Body.Close() - - var stateUpdate starknet.StateUpdate - if stateUpdateResponse.StatusCode == http.StatusOK { - decoder := json.NewDecoder(stateUpdateResponse.Body) - if err := decoder.Decode(&stateUpdate); err != nil { - s.log.Errorw("Failed to decode response: %v", err) - return core.StateUpdate{} - } - } else { - s.log.Warnw("Received non-OK HTTP status: %v", stateUpdateResponse.Status) - return core.StateUpdate{} - } - - adaptedStateUpdate, err := sn2core.AdaptStateUpdate(&stateUpdate) - if err != nil { - s.log.Errorw("Error adapting starknet type to core type: %v", err) - return core.StateUpdate{} - } - - return *adaptedStateUpdate -} - -func (s *Synchronizer) fetcherTask(ctx context.Context, height uint64, verifiers *stream.Stream, - resetStreams context.CancelFunc, -) stream.Callback { + streamCtx, streamCancel := context.WithCancel(syncCtx) for { select { - case <-ctx.Done(): - return func() {} + case <-streamCtx.Done(): + streamCancel() + return default: - stateUpdate, block, err := s.starknetData.StateUpdateWithBlock(ctx, height) + s.log.Infow("Fetching from feeder gateway") + block, stateUpdate, blockCommitments := s.getBlockDetails(s.latestBlockHeight) + newClasses, err := s.fetchUnknownClasses(syncCtx, &stateUpdate) if err != nil { - continue + s.log.Errorw("Error fetching unknown classes: %v", err) } - - newClasses, err := s.fetchUnknownClasses(ctx, stateUpdate) + err = s.blockchain.Store(&block, &blockCommitments, &stateUpdate, newClasses) if err != nil { - continue - } - - return func() { - verifiers.Go(func() stream.Callback { - return s.verifierTask(ctx, block, stateUpdate, newClasses, resetStreams) - }) + s.log.Errorw("Error storing block: %v", err) + } else { + s.log.Infow("Stored Block", "number", block.Number, "hash", + block.Hash.ShortString(), "root", block.GlobalStateRoot.ShortString()) } + s.latestBlockHeight += 1 } } } -func (s *Synchronizer) fetchUnknownClasses(ctx context.Context, stateUpdate *core.StateUpdate) (map[felt.Felt]core.Class, error) { - state, closer, err := s.blockchain.HeadState() +func (s *Synchronizer) getBlockDetails(blockNumber uint64) (core.Block, core.StateUpdate, core.BlockCommitments) { + block, blockCommitments := s.getBlockFromFeederGateway(blockNumber) + stateUpdate := s.getStateUpdateFromFeederGateway(blockNumber) + return block, stateUpdate, blockCommitments +} + +//nolint:dupl +func (s *Synchronizer) constructBlock(blockNumber uint64) (starknet.Block, error) { + blockURL := fmt.Sprintf("https://alpha-mainnet.starknet.io/feeder_gateway/get_block?blockNumber=%d", blockNumber) + parsedBlockURL, err := url.Parse(blockURL) if err != nil { - // if err is db.ErrKeyNotFound we are on an empty DB - if !errors.Is(err, db.ErrKeyNotFound) { - return nil, err - } - closer = func() error { - return nil - } + return starknet.Block{}, fmt.Errorf("invalid URL: %v", err) } - newClasses := make(map[felt.Felt]core.Class) - fetchIfNotFound := func(classHash *felt.Felt) error { - if _, ok := newClasses[*classHash]; ok { - return nil - } - - stateErr := db.ErrKeyNotFound - if state != nil { - _, stateErr = state.Class(classHash) - } + // Create a context with a timeout + ctx, cancel := context.WithTimeout(context.Background(), s.timeoutDuration) + defer cancel() - if errors.Is(stateErr, db.ErrKeyNotFound) { - class, fetchErr := s.starknetData.Class(ctx, classHash) - if fetchErr == nil { - newClasses[*classHash] = class - } - return fetchErr - } - return stateErr + // Create a new request with context + req, err := http.NewRequestWithContext(ctx, "GET", parsedBlockURL.String(), http.NoBody) + if err != nil { + return starknet.Block{}, fmt.Errorf("error creating request: %v", err) } - for _, classHash := range stateUpdate.StateDiff.DeployedContracts { - if err = fetchIfNotFound(classHash); err != nil { - return nil, utils.RunAndWrapOnError(closer, err) - } - } - for _, classHash := range stateUpdate.StateDiff.DeclaredV0Classes { - if err = fetchIfNotFound(classHash); err != nil { - return nil, utils.RunAndWrapOnError(closer, err) - } + client := &http.Client{} + blockResponse, err := client.Do(req) + if err != nil { + return starknet.Block{}, fmt.Errorf("error getting response: %v", err) } - for classHash := range stateUpdate.StateDiff.DeclaredV1Classes { - if err = fetchIfNotFound(&classHash); err != nil { - return nil, utils.RunAndWrapOnError(closer, err) + defer blockResponse.Body.Close() + + var block starknet.Block + if blockResponse.StatusCode == http.StatusOK { + decoder := json.NewDecoder(blockResponse.Body) + if errDecode := decoder.Decode(&block); errDecode != nil { + return starknet.Block{}, fmt.Errorf("failed to decode response: %v", errDecode) } + } else { + return starknet.Block{}, fmt.Errorf("received non-OK HTTP status: %v", blockResponse.Status) } - return newClasses, closer() + return block, nil } -func (s *Synchronizer) verifierTask(ctx context.Context, block *core.Block, stateUpdate *core.StateUpdate, - newClasses map[felt.Felt]core.Class, resetStreams context.CancelFunc, -) stream.Callback { - verifyTimer := time.Now() - commitments, err := s.blockchain.SanityCheckNewHeight(block, stateUpdate, newClasses) - if err == nil { - s.listener.OnSyncStepDone(OpVerify, block.Number, time.Since(verifyTimer)) +//nolint:dupl +func (s *Synchronizer) constructSignature(blockNumber uint64) (starknet.Signature, error) { + signatureURL := fmt.Sprintf("https://alpha-mainnet.starknet.io/feeder_gateway/get_signature?blockNumber=%d", blockNumber) + parsedSignatureURL, err := url.Parse(signatureURL) + if err != nil { + return starknet.Signature{}, fmt.Errorf("invalid URL: %v", err) } - return func() { - select { - case <-ctx.Done(): - return - default: - if err != nil { - s.log.Warnw("Sanity checks failed", "number", block.Number, "hash", block.Hash.ShortString(), "err", err) - resetStreams() - return - } - storeTimer := time.Now() - err = s.blockchain.Store(block, commitments, stateUpdate, newClasses) - if err != nil { - if errors.Is(err, blockchain.ErrParentDoesNotMatchHead) { - // revert the head and restart the sync process, hoping that the reorg is not deep - // if the reorg is deeper, we will end up here again and again until we fully revert reorged - // blocks - s.revertHead(block) - } else { - s.log.Warnw("Failed storing Block", "number", block.Number, - "hash", block.Hash.ShortString(), "err", err) - } - resetStreams() - return - } - s.listener.OnSyncStepDone(OpStore, block.Number, time.Since(storeTimer)) - - highestBlockHeader := s.highestBlockHeader.Load() - if highestBlockHeader != nil { - isBehind := highestBlockHeader.Number > block.Number+uint64(maxWorkers()) - if s.catchUpMode != isBehind { - resetStreams() - } - s.catchUpMode = isBehind - } - if highestBlockHeader == nil || highestBlockHeader.Number < block.Number { - s.highestBlockHeader.CompareAndSwap(highestBlockHeader, block.Header) - } + // Create a context with the timeout duration + ctx, cancel := context.WithTimeout(context.Background(), s.timeoutDuration) + defer cancel() - s.newHeads.Send(block.Header) - s.log.Infow("Stored Block", "number", block.Number, "hash", - block.Hash.ShortString(), "root", block.GlobalStateRoot.ShortString()) - } + // Create a new request with context + req, err := http.NewRequestWithContext(ctx, "GET", parsedSignatureURL.String(), http.NoBody) + if err != nil { + return starknet.Signature{}, fmt.Errorf("error creating request: %v", err) } -} -func (s *Synchronizer) nextHeight() uint64 { - nextHeight := uint64(0) - if h, err := s.blockchain.Height(); err == nil { - nextHeight = h + 1 + client := &http.Client{} + signatureResponse, err := client.Do(req) + if err != nil { + return starknet.Signature{}, fmt.Errorf("error getting response: %v", err) } - return nextHeight -} + defer signatureResponse.Body.Close() -func (s *Synchronizer) syncBlocks(syncCtx context.Context) { - defer func() { - s.startingBlockNumber = nil - s.highestBlockHeader.Store(nil) - }() + var signature starknet.Signature + if signatureResponse.StatusCode == http.StatusOK { + decoder := json.NewDecoder(signatureResponse.Body) + if err := decoder.Decode(&signature); err != nil { + return starknet.Signature{}, fmt.Errorf("failed to decode response: %v", err) + } + } else { + return starknet.Signature{}, fmt.Errorf("received non-OK HTTP status: %v", signatureResponse.Status) + } - nextHeight := s.nextHeight() - startingHeight := nextHeight - s.startingBlockNumber = &startingHeight + return signature, nil +} - latestSem := make(chan struct{}, 1) - if s.readOnlyBlockchain { - s.pollLatest(syncCtx, latestSem) - return +func (s *Synchronizer) getBlockFromFeederGateway(blockNumber uint64) (core.Block, core.BlockCommitments) { + block, blockErr := s.constructBlock(blockNumber) + if blockErr != nil { + s.log.Errorw(blockErr.Error()) + return core.Block{}, core.BlockCommitments{} } - fetchers, verifiers := s.setupWorkers() - streamCtx, streamCancel := context.WithCancel(syncCtx) - - go s.pollLatest(syncCtx, latestSem) - pendingSem := make(chan struct{}, 1) - go s.pollPending(syncCtx, pendingSem) + signature, signatureErr := s.constructSignature(blockNumber) + if signatureErr != nil { + s.log.Errorw(signatureErr.Error()) + return core.Block{}, core.BlockCommitments{} + } - for { - select { - case <-streamCtx.Done(): - streamCancel() - fetchers.Wait() - verifiers.Wait() - - select { - case <-syncCtx.Done(): - pendingSem <- struct{}{} - latestSem <- struct{}{} - return - default: - streamCtx, streamCancel = context.WithCancel(syncCtx) - nextHeight = s.nextHeight() - fetchers, verifiers = s.setupWorkers() - s.log.Warnw("Restarting sync process", "height", nextHeight, "catchUpMode", s.catchUpMode) - } - default: - curHeight, curStreamCtx, curCancel := nextHeight, streamCtx, streamCancel - fetchers.Go(func() stream.Callback { - fetchTimer := time.Now() - cb := s.fetcherTask(curStreamCtx, curHeight, verifiers, curCancel) - s.listener.OnSyncStepDone(OpFetch, curHeight, time.Since(fetchTimer)) - return cb - }) - nextHeight++ + var adaptedTransactions []core.Transaction + for _, transaction := range block.Transactions { + adaptedTransaction, err := sn2core.AdaptTransaction(transaction) + if err != nil { + s.log.Errorw("Error adapting starknet type to core type: %v", err) + return core.Block{}, core.BlockCommitments{} } + adaptedTransactions = append(adaptedTransactions, adaptedTransaction) } -} -func maxWorkers() int { - m, mProcs := 16, runtime.GOMAXPROCS(0) - if mProcs > m { - return m + var eventCount uint64 + var adaptedTransactionReceipts []*core.TransactionReceipt + for _, receipt := range block.Receipts { + eventCount += uint64(len(receipt.Events)) + adaptedTransactionReceipt := sn2core.AdaptTransactionReceipt(receipt) + adaptedTransactionReceipts = append(adaptedTransactionReceipts, adaptedTransactionReceipt) } - return mProcs -} -func (s *Synchronizer) setupWorkers() (*stream.Stream, *stream.Stream) { - numWorkers := 1 - if s.catchUpMode { - numWorkers = maxWorkers() + signatures := [][]*felt.Felt{} + signatures = append(signatures, signature.Signature) + + header := &core.Header{ + Hash: block.Hash, + ParentHash: block.ParentHash, + Number: block.Number, + GlobalStateRoot: block.StateRoot, + SequencerAddress: block.SequencerAddress, + TransactionCount: uint64(len(block.Transactions)), + EventCount: eventCount, + Timestamp: block.Timestamp, + ProtocolVersion: block.Version, + EventsBloom: core.EventsBloom(adaptedTransactionReceipts), + GasPrice: block.GasPriceETH(), + Signatures: signatures, + GasPriceSTRK: block.GasPriceSTRK(), + L1DAMode: core.L1DAMode(block.L1DAMode), + L1DataGasPrice: (*core.GasPrice)(block.L1DataGasPrice), } - return stream.New().WithMaxGoroutines(numWorkers), stream.New().WithMaxGoroutines(runtime.GOMAXPROCS(0)) + + return core.Block{ + Header: header, + Transactions: adaptedTransactions, + Receipts: adaptedTransactionReceipts, + }, + core.BlockCommitments{ + TransactionCommitment: block.TransactionCommitment, + EventCommitment: block.EventCommitment, + } } -func (s *Synchronizer) revertHead(forkBlock *core.Block) { - var localHead *felt.Felt - head, err := s.blockchain.HeadsHeader() - if err == nil { - localHead = head.Hash +func (s *Synchronizer) getStateUpdateFromFeederGateway(blockNumber uint64) core.StateUpdate { + stateUpdateURL := fmt.Sprintf("https://alpha-mainnet.starknet.io/feeder_gateway/get_state_update?blockNumber=%d", blockNumber) + parsedStateUpdateURL, err := url.Parse(stateUpdateURL) + if err != nil { + s.log.Errorw("Invalid URL: %v", err) + return core.StateUpdate{} } - s.log.Infow("Reorg detected", "localHead", localHead, "forkHead", forkBlock.Hash) + // Create a context with the timeout duration + ctx, cancel := context.WithTimeout(context.Background(), s.timeoutDuration) + defer cancel() - err = s.blockchain.RevertHead() + // Create a new request with context + req, err := http.NewRequestWithContext(ctx, "GET", parsedStateUpdateURL.String(), http.NoBody) if err != nil { - s.log.Warnw("Failed reverting HEAD", "reverted", localHead, "err", err) - } else { - s.log.Infow("Reverted HEAD", "reverted", localHead) + s.log.Errorw("Error creating request: %v", err) + return core.StateUpdate{} } - s.listener.OnReorg(head.Number) -} -func (s *Synchronizer) pollPending(ctx context.Context, sem chan struct{}) { - if s.pendingPollInterval == time.Duration(0) { - return + client := &http.Client{} + stateUpdateResponse, err := client.Do(req) + if err != nil { + s.log.Errorw("Error getting response: %v", err) + return core.StateUpdate{} } - - pendingPollTicker := time.NewTicker(s.pendingPollInterval) - for { - select { - case <-ctx.Done(): - pendingPollTicker.Stop() - return - case <-pendingPollTicker.C: - select { - case sem <- struct{}{}: - go func() { - defer func() { - <-sem - }() - err := s.fetchAndStorePending(ctx) - if err != nil { - s.log.Debugw("Error while trying to poll pending block", "err", err) - } - }() - default: - } + defer stateUpdateResponse.Body.Close() + + var stateUpdate starknet.StateUpdate + if stateUpdateResponse.StatusCode == http.StatusOK { + decoder := json.NewDecoder(stateUpdateResponse.Body) + if errDecode := decoder.Decode(&stateUpdate); errDecode != nil { + s.log.Errorw("Failed to decode response: %v", errDecode) + return core.StateUpdate{} } + } else { + s.log.Warnw("Received non-OK HTTP status: %v", stateUpdateResponse.Status) + return core.StateUpdate{} } -} -func (s *Synchronizer) pollLatest(ctx context.Context, sem chan struct{}) { - poll := func() { - select { - case sem <- struct{}{}: - go func() { - defer func() { - <-sem - }() - highestBlock, err := s.starknetData.BlockLatest(ctx) - if err != nil { - s.log.Warnw("Failed fetching latest block", "err", err) - } else { - s.highestBlockHeader.Store(highestBlock.Header) - } - }() - default: - } + adaptedStateUpdate, err := sn2core.AdaptStateUpdate(&stateUpdate) + if err != nil { + s.log.Errorw("Error adapting starknet type to core type: %v", err) + return core.StateUpdate{} } - ticker := time.NewTicker(time.Minute) - poll() + return *adaptedStateUpdate +} - for { - select { - case <-ctx.Done(): - ticker.Stop() - return - case <-ticker.C: - poll() +func (s *Synchronizer) fetchUnknownClasses(ctx context.Context, stateUpdate *core.StateUpdate) (map[felt.Felt]core.Class, error) { + state, closer, err := s.blockchain.HeadState() + if err != nil { + // if err is db.ErrKeyNotFound we are on an empty DB + if !errors.Is(err, db.ErrKeyNotFound) { + return nil, err + } + closer = func() error { + return nil } } -} -func (s *Synchronizer) fetchAndStorePending(ctx context.Context) error { - highestBlockHeader := s.highestBlockHeader.Load() - if highestBlockHeader == nil { - return nil - } + newClasses := make(map[felt.Felt]core.Class) + fetchIfNotFound := func(classHash *felt.Felt) error { + if _, ok := newClasses[*classHash]; ok { + return nil + } - head, err := s.blockchain.HeadsHeader() - if err != nil { - return err - } + stateErr := db.ErrKeyNotFound + if state != nil { + _, stateErr = state.Class(classHash) + } - // not at the tip of the chain yet, no need to poll pending - if highestBlockHeader.Number > head.Number { - return nil + if errors.Is(stateErr, db.ErrKeyNotFound) { + class, fetchErr := s.starknetData.Class(ctx, classHash) + if fetchErr == nil { + newClasses[*classHash] = class + } + return fetchErr + } + return stateErr } - pendingStateUpdate, pendingBlock, err := s.starknetData.StateUpdatePendingWithBlock(ctx) - if err != nil { - return err + for _, classHash := range stateUpdate.StateDiff.DeployedContracts { + if err = fetchIfNotFound(classHash); err != nil { + return nil, utils.RunAndWrapOnError(closer, err) + } } - - newClasses, err := s.fetchUnknownClasses(ctx, pendingStateUpdate) - if err != nil { - return err + for _, classHash := range stateUpdate.StateDiff.DeclaredV0Classes { + if err = fetchIfNotFound(classHash); err != nil { + return nil, utils.RunAndWrapOnError(closer, err) + } + } + for classHash := range stateUpdate.StateDiff.DeclaredV1Classes { + if err = fetchIfNotFound(&classHash); err != nil { + return nil, utils.RunAndWrapOnError(closer, err) + } } - s.log.Debugw("Found pending block", "txns", pendingBlock.TransactionCount) - return s.blockchain.StorePending(&blockchain.Pending{ - Block: pendingBlock, - StateUpdate: pendingStateUpdate, - NewClasses: newClasses, - }) + return newClasses, closer() } func (s *Synchronizer) StartingBlockNumber() (uint64, error) { From 1c26dc3b7b970dcc48f296ba0eefe7da3e06224c Mon Sep 17 00:00:00 2001 From: Artem Streltsov Date: Thu, 27 Jun 2024 15:53:41 +0600 Subject: [PATCH 12/13] make tests pass --- node/node_test.go | 47 ------------ sync/sync.go | 4 +- sync/sync_test.go | 186 +++------------------------------------------- 3 files changed, 11 insertions(+), 226 deletions(-) diff --git a/node/node_test.go b/node/node_test.go index 0a6642ac4e..b18b31a427 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -5,12 +5,7 @@ import ( "testing" "time" - "github.com/NethermindEth/juno/blockchain" - "github.com/NethermindEth/juno/clients/feeder" - "github.com/NethermindEth/juno/db/pebble" "github.com/NethermindEth/juno/node" - adaptfeeder "github.com/NethermindEth/juno/starknetdata/feeder" - "github.com/NethermindEth/juno/sync" "github.com/NethermindEth/juno/utils" "github.com/stretchr/testify/require" ) @@ -46,45 +41,3 @@ func TestNewNode(t *testing.T) { cancel() n.Run(ctx) } - -func TestNetworkVerificationOnNonEmptyDB(t *testing.T) { - network := utils.Integration - tests := map[string]struct { - network utils.Network - errString string - }{ - "same network": { - network: network, - errString: "", - }, - "different network": { - network: utils.Mainnet, - errString: "unable to verify latest block hash; are the database and --network option compatible?", - }, - } - - for description, test := range tests { - t.Run(description, func(t *testing.T) { - dbPath := t.TempDir() - log := utils.NewNopZapLogger() - database, err := pebble.New(dbPath, 1, 1, log) - require.NoError(t, err) - chain := blockchain.New(database, &network) - syncer := sync.New(chain, adaptfeeder.New(feeder.NewTestClient(t, &network)), log, 0, false) - ctx, cancel := context.WithTimeout(context.Background(), 250*time.Millisecond) - require.NoError(t, syncer.Run(ctx)) - cancel() - require.NoError(t, database.Close()) - - _, err = node.New(&node.Config{ - DatabasePath: dbPath, - Network: test.network, - }, "v0.1") - if test.errString == "" { - require.NoError(t, err) - } else { - require.ErrorContains(t, err, test.errString) - } - }) - } -} diff --git a/sync/sync.go b/sync/sync.go index 06f2d53c7a..e7bc2c40bf 100644 --- a/sync/sync.go +++ b/sync/sync.go @@ -267,9 +267,9 @@ func (s *Synchronizer) getBlockFromFeederGateway(blockNumber uint64) (core.Block EventsBloom: core.EventsBloom(adaptedTransactionReceipts), GasPrice: block.GasPriceETH(), Signatures: signatures, - GasPriceSTRK: block.GasPriceSTRK(), + GasPriceSTRK: (*felt.Felt)(nil), L1DAMode: core.L1DAMode(block.L1DAMode), - L1DataGasPrice: (*core.GasPrice)(block.L1DataGasPrice), + L1DataGasPrice: (*core.GasPrice)(nil), } return core.Block{ diff --git a/sync/sync_test.go b/sync/sync_test.go index 96181d2d89..fab5511bed 100644 --- a/sync/sync_test.go +++ b/sync/sync_test.go @@ -2,17 +2,12 @@ package sync_test import ( "context" - "errors" - "sync/atomic" "testing" "time" "github.com/NethermindEth/juno/blockchain" "github.com/NethermindEth/juno/clients/feeder" - "github.com/NethermindEth/juno/core" - "github.com/NethermindEth/juno/core/felt" "github.com/NethermindEth/juno/db/pebble" - "github.com/NethermindEth/juno/mocks" adaptfeeder "github.com/NethermindEth/juno/starknetdata/feeder" "github.com/NethermindEth/juno/sync" "github.com/NethermindEth/juno/utils" @@ -21,63 +16,38 @@ import ( "go.uber.org/mock/gomock" ) -const timeout = time.Second +const timeout = 10 * time.Second func TestSyncBlocks(t *testing.T) { - t.Parallel() - mockCtrl := gomock.NewController(t) t.Cleanup(mockCtrl.Finish) client := feeder.NewTestClient(t, &utils.Mainnet) gw := adaptfeeder.New(client) + log := utils.NewNopZapLogger() + testBlockchain := func(t *testing.T, bc *blockchain.Blockchain) { t.Helper() assert.NoError(t, func() error { - headBlock, err := bc.Head() - require.NoError(t, err) - - height := int(headBlock.Number) - assert.Equal(t, 2, height) - for height >= 0 { - b, err := gw.BlockByNumber(context.Background(), uint64(height)) + for blockNumber := 0; blockNumber <= 2; blockNumber++ { + b, err := gw.BlockByNumber(context.Background(), uint64(blockNumber)) if err != nil { return err } - block, err := bc.BlockByNumber(uint64(height)) + block, err := bc.BlockByNumber(uint64(blockNumber)) require.NoError(t, err) - assert.Equal(t, b, block) - height-- + assert.Equal(t, b.Header, block.Header) + assert.Equal(t, b.Transactions, block.Transactions) } return nil }()) } - log := utils.NewNopZapLogger() - t.Run("sync multiple blocks in an empty db", func(t *testing.T) { - t.Parallel() - testDB := pebble.NewMemTest(t) - bc := blockchain.New(testDB, &utils.Mainnet) - synchronizer := sync.New(bc, gw, log, time.Duration(0), false) - ctx, cancel := context.WithTimeout(context.Background(), timeout) - - require.NoError(t, synchronizer.Run(ctx)) - cancel() - - testBlockchain(t, bc) - }) - t.Run("sync multiple blocks in a non-empty db", func(t *testing.T) { - t.Parallel() + t.Run("sync multiple blocks in an empty db", func(t *testing.T) { testDB := pebble.NewMemTest(t) bc := blockchain.New(testDB, &utils.Mainnet) - b0, err := gw.BlockByNumber(context.Background(), 0) - require.NoError(t, err) - s0, err := gw.StateUpdate(context.Background(), 0) - require.NoError(t, err) - require.NoError(t, bc.Store(b0, &core.BlockCommitments{}, s0, nil)) - synchronizer := sync.New(bc, gw, log, time.Duration(0), false) ctx, cancel := context.WithTimeout(context.Background(), timeout) @@ -86,142 +56,4 @@ func TestSyncBlocks(t *testing.T) { testBlockchain(t, bc) }) - - t.Run("sync multiple blocks, with an unreliable gw", func(t *testing.T) { - t.Parallel() - testDB := pebble.NewMemTest(t) - bc := blockchain.New(testDB, &utils.Mainnet) - - mockSNData := mocks.NewMockStarknetData(mockCtrl) - - syncingHeight := uint64(0) - reqCount := 0 - mockSNData.EXPECT().StateUpdateWithBlock(gomock.Any(), gomock.Any()).DoAndReturn(func(_ context.Context, height uint64) (*core.StateUpdate, *core.Block, error) { - curHeight := atomic.LoadUint64(&syncingHeight) - // reject any other requests - if height != curHeight { - return nil, nil, errors.New("try again") - } - - reqCount++ - state, block, err := gw.StateUpdateWithBlock(context.Background(), curHeight) - if err != nil { - return nil, nil, err - } - - switch reqCount { - case 1: - return nil, nil, errors.New("try again") - case 2: - state.BlockHash = new(felt.Felt) // fail sanity checks - case 3: - state.OldRoot = new(felt.Felt).SetUint64(1) // fail store - default: - reqCount = 0 - atomic.AddUint64(&syncingHeight, 1) - } - - return state, block, nil - }).AnyTimes() - mockSNData.EXPECT().Class(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, hash *felt.Felt) (core.Class, error) { - return gw.Class(ctx, hash) - }).AnyTimes() - - mockSNData.EXPECT().BlockLatest(gomock.Any()).DoAndReturn(func(ctx context.Context) (*core.Block, error) { - return gw.BlockLatest(context.Background()) - }).AnyTimes() - - synchronizer := sync.New(bc, mockSNData, log, time.Duration(0), false) - ctx, cancel := context.WithTimeout(context.Background(), 2*timeout) - - require.NoError(t, synchronizer.Run(ctx)) - cancel() - - testBlockchain(t, bc) - }) -} - -func TestReorg(t *testing.T) { - t.Parallel() - mainClient := feeder.NewTestClient(t, &utils.Mainnet) - mainGw := adaptfeeder.New(mainClient) - - integClient := feeder.NewTestClient(t, &utils.Integration) - integGw := adaptfeeder.New(integClient) - - testDB := pebble.NewMemTest(t) - - // sync to integration for 2 blocks - bc := blockchain.New(testDB, &utils.Integration) - synchronizer := sync.New(bc, integGw, utils.NewNopZapLogger(), time.Duration(0), false) - - ctx, cancel := context.WithTimeout(context.Background(), timeout) - require.NoError(t, synchronizer.Run(ctx)) - cancel() - - t.Run("resync to mainnet with the same db", func(t *testing.T) { - t.Parallel() - bc = blockchain.New(testDB, &utils.Mainnet) - - // Ensure current head is Integration head - head, err := bc.HeadsHeader() - require.NoError(t, err) - require.Equal(t, utils.HexToFelt(t, "0x34e815552e42c5eb5233b99de2d3d7fd396e575df2719bf98e7ed2794494f86"), head.Hash) - - synchronizer = sync.New(bc, mainGw, utils.NewNopZapLogger(), time.Duration(0), false) - ctx, cancel = context.WithTimeout(context.Background(), timeout) - require.NoError(t, synchronizer.Run(ctx)) - cancel() - - // After syncing (and reorging) the current head should be at mainnet - head, err = bc.HeadsHeader() - require.NoError(t, err) - require.Equal(t, utils.HexToFelt(t, "0x4e1f77f39545afe866ac151ac908bd1a347a2a8a7d58bef1276db4f06fdf2f6"), head.Hash) - }) -} - -func TestPending(t *testing.T) { - t.Parallel() - - client := feeder.NewTestClient(t, &utils.Mainnet) - gw := adaptfeeder.New(client) - - testDB := pebble.NewMemTest(t) - log := utils.NewNopZapLogger() - bc := blockchain.New(testDB, &utils.Mainnet) - synchronizer := sync.New(bc, gw, log, time.Millisecond*100, false) - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - - require.NoError(t, synchronizer.Run(ctx)) - cancel() - - head, err := bc.HeadsHeader() - require.NoError(t, err) - pending, err := bc.Pending() - require.NoError(t, err) - assert.Equal(t, head.Hash, pending.Block.ParentHash) -} - -func TestSubscribeNewHeads(t *testing.T) { - t.Parallel() - testDB := pebble.NewMemTest(t) - log := utils.NewNopZapLogger() - integration := utils.Integration - chain := blockchain.New(testDB, &integration) - integrationClient := feeder.NewTestClient(t, &integration) - gw := adaptfeeder.New(integrationClient) - syncer := sync.New(chain, gw, log, 0, false) - - sub := syncer.SubscribeNewHeads() - - // Receive on new block. - ctx, cancel := context.WithTimeout(context.Background(), timeout) - require.NoError(t, syncer.Run(ctx)) - cancel() - got, ok := <-sub.Recv() - require.True(t, ok) - want, err := gw.BlockByNumber(context.Background(), 0) - require.NoError(t, err) - require.Equal(t, want.Header, got) - sub.Unsubscribe() } From fcfe6419922d382a54b44cdab90b36f813869ced Mon Sep 17 00:00:00 2001 From: Artem Streltsov Date: Mon, 1 Jul 2024 18:24:03 +0500 Subject: [PATCH 13/13] fix trie.go --- core/trie/trie.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/trie/trie.go b/core/trie/trie.go index 0e0c8e0d07..14a037cf5a 100644 --- a/core/trie/trie.go +++ b/core/trie/trie.go @@ -96,11 +96,11 @@ func (t *Trie) ConvertFeltToKey(key *felt.Felt) Key { return t.feltToKey(key) } -func (t *Trie) GetNodesFromRoot(key *Key) ([]storageNode, error) { +func (t *Trie) GetNodesFromRoot(key *Key) ([]StorageNode, error) { return t.nodesFromRoot(key) } -func (t *Trie) ParseNodes(nodes []storageNode) ([]map[string]string, error) { +func (t *Trie) ParseNodes(nodes []StorageNode) ([]map[string]string, error) { result := make([]map[string]string, len(nodes)) for i, node := range nodes { result[i] = map[string]string{