From 35f6d09388e1ce7fafe5e38a2a1c0cdca6f3224b Mon Sep 17 00:00:00 2001 From: Amirul Ashraf Date: Thu, 23 May 2024 13:23:55 +0800 Subject: [PATCH 01/23] Ashraf Basic snap algo .. Did something + #1925 --- blockchain/blockchain.go | 67 +- blockchain/blockchain_test.go | 19 + blockchain/snap_server_interface.go | 164 +++ core/contract.go | 31 +- core/state.go | 292 ++++- core/trie/key.go | 58 + core/trie/key_test.go | 118 ++ core/trie/proof.go | 8 + core/trie/snap_support.go | 162 +++ core/trie/snap_support_test.go | 278 +++++ core/trie/trie.go | 69 ++ core/trie/trie_test.go | 152 +++ db/db.go | 2 + db/pebble/db.go | 12 + db/remote/db.go | 5 + migration/migration_pkg_test.go | 2 + node/node_test.go | 1 + p2p/starknet/handlers.go | 3 +- p2p/starknet/p2p/proto/class.proto | 7 +- p2p/starknet/p2p/proto/snapshot.proto | 112 ++ p2p/starknet/p2p/proto/state.proto | 2 +- p2p/starknet/spec/class.pb.go | 118 +- p2p/starknet/spec/common.pb.go | 2 +- p2p/starknet/spec/event.pb.go | 2 +- p2p/starknet/spec/header.pb.go | 2 +- p2p/starknet/spec/receipt.pb.go | 2 +- p2p/starknet/spec/snapshot.pb.go | 1530 +++++++++++++++++++++++++ p2p/starknet/spec/state.pb.go | 2 +- p2p/starknet/spec/transaction.pb.go | 2 +- rpc/events_test.go | 3 + sync/snap_server.go | 447 ++++++++ sync/snap_server_test.go | 206 ++++ sync/snapsyncer.go | 1107 ++++++++++++++++++ sync/snapsyncer_test.go | 384 +++++++ sync/sync_test.go | 5 + 35 files changed, 5337 insertions(+), 39 deletions(-) create mode 100644 blockchain/snap_server_interface.go create mode 100644 core/trie/snap_support.go create mode 100644 core/trie/snap_support_test.go create mode 100644 p2p/starknet/p2p/proto/snapshot.proto create mode 100644 p2p/starknet/spec/snapshot.pb.go create mode 100644 sync/snap_server.go create mode 100644 sync/snap_server_test.go create mode 100644 sync/snapsyncer.go create mode 100644 sync/snapsyncer_test.go diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 4aa6659b98..2101f0c3e5 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -74,6 +74,8 @@ type Blockchain struct { network *utils.Network database db.DB + snapshots []*snapshotRecord + listener EventListener cachedPending atomic.Pointer[Pending] @@ -81,11 +83,19 @@ type Blockchain struct { func New(database db.DB, network *utils.Network) *Blockchain { RegisterCoreTypesToEncoder() - return &Blockchain{ + bc := &Blockchain{ database: database, network: network, listener: &SelectiveListener{}, } + + // TODO: Used only for testing though... + err := bc.seedSnapshot() + if err != nil { + fmt.Printf("Error seeding snapshot %s", err) + } + + return bc } func (b *Blockchain) WithListener(listener EventListener) *Blockchain { @@ -336,7 +346,7 @@ func (b *Blockchain) SetL1Head(update *core.L1Head) error { func (b *Blockchain) Store(block *core.Block, blockCommitments *core.BlockCommitments, stateUpdate *core.StateUpdate, newClasses map[felt.Felt]core.Class, ) error { - return b.database.Update(func(txn db.Transaction) error { + err := b.database.Update(func(txn db.Transaction) error { if err := verifyBlock(txn, block); err != nil { return err } @@ -372,6 +382,49 @@ func (b *Blockchain) Store(block *core.Block, blockCommitments *core.BlockCommit heightBin := core.MarshalBlockNumber(block.Number) return txn.Set(db.ChainHeight.Key(), heightBin) }) + if err != nil { + return err + } + + err = b.seedSnapshot() + if err != nil { + return err + } + + return nil +} + +func (b *Blockchain) StoreRaw(blockNumber uint64, stateDiff *core.StateDiff) error { + return b.database.Update(func(txn db.Transaction) error { + return core.NewState(txn).UpdateNoVerify(blockNumber, stateDiff, make(map[felt.Felt]core.Class)) + }) +} + +func (b *Blockchain) PutClasses(blockNumber uint64, classHashes map[felt.Felt]*felt.Felt, newClasses map[felt.Felt]core.Class) error { + return b.database.Update(func(txn db.Transaction) error { + v1ClassHashes := map[felt.Felt]*felt.Felt{} + for ch, class := range newClasses { + if class.Version() == 1 { + v1ClassHashes[ch] = classHashes[ch] + } + } + + return core.NewState(txn).UpdateNoVerify(blockNumber, &core.StateDiff{ + DeclaredV1Classes: v1ClassHashes, + }, newClasses) + }) +} + +func (b *Blockchain) PutContracts(address, nonces, classHash []*felt.Felt) error { + return b.database.Update(func(txn db.Transaction) error { + return core.NewState(txn).UpdateContractNoLog(address, nonces, classHash) + }) +} + +func (b *Blockchain) PutStorage(storage map[felt.Felt]map[felt.Felt]*felt.Felt) error { + return b.database.Update(func(txn db.Transaction) error { + return core.NewState(txn).UpdateContractStorages(storage) + }) } // VerifyBlock assumes the block has already been sanity-checked. @@ -769,6 +822,16 @@ func (b *Blockchain) HeadState() (core.StateReader, StateCloser, error) { return core.NewState(txn), txn.Discard, nil } +func (b *Blockchain) HeadStateFreakingState() (*core.State, StateCloser, error) { + b.listener.OnRead("HeadState") + txn, err := b.database.NewTransaction(false) + if err != nil { + return nil, nil, err + } + + return core.NewState(txn), txn.Discard, nil +} + // StateAtBlockNumber returns a StateReader that provides a stable view to the state at the given block number func (b *Blockchain) StateAtBlockNumber(blockNumber uint64) (core.StateReader, StateCloser, error) { b.listener.OnRead("StateAtBlockNumber") diff --git a/blockchain/blockchain_test.go b/blockchain/blockchain_test.go index c0c3666a91..abb70e01b7 100644 --- a/blockchain/blockchain_test.go +++ b/blockchain/blockchain_test.go @@ -27,6 +27,7 @@ func TestNew(t *testing.T) { gw := adaptfeeder.New(client) t.Run("empty blockchain's head is nil", func(t *testing.T) { chain := blockchain.New(pebble.NewMemTest(t), &utils.Mainnet) + defer chain.Close() assert.Equal(t, &utils.Mainnet, chain.Network()) b, err := chain.Head() assert.Nil(t, b) @@ -42,8 +43,10 @@ func TestNew(t *testing.T) { testDB := pebble.NewMemTest(t) chain := blockchain.New(testDB, &utils.Mainnet) assert.NoError(t, chain.Store(block0, &emptyCommitments, stateUpdate0, nil)) + chain.Close() chain = blockchain.New(testDB, &utils.Mainnet) + defer chain.Close() b, err := chain.Head() require.NoError(t, err) assert.Equal(t, block0, b) @@ -55,6 +58,7 @@ func TestHeight(t *testing.T) { gw := adaptfeeder.New(client) t.Run("return nil if blockchain is empty", func(t *testing.T) { chain := blockchain.New(pebble.NewMemTest(t), &utils.Sepolia) + defer chain.Close() _, err := chain.Height() assert.Error(t, err) }) @@ -68,16 +72,19 @@ func TestHeight(t *testing.T) { testDB := pebble.NewMemTest(t) chain := blockchain.New(testDB, &utils.Mainnet) assert.NoError(t, chain.Store(block0, &emptyCommitments, stateUpdate0, nil)) + chain.Close() chain = blockchain.New(testDB, &utils.Mainnet) height, err := chain.Height() require.NoError(t, err) assert.Equal(t, block0.Number, height) + chain.Close() }) } func TestBlockByNumberAndHash(t *testing.T) { chain := blockchain.New(pebble.NewMemTest(t), &utils.Sepolia) + defer chain.Close() t.Run("same block is returned for both GetBlockByNumber and GetBlockByHash", func(t *testing.T) { client := feeder.NewTestClient(t, &utils.Mainnet) gw := adaptfeeder.New(client) @@ -114,6 +121,7 @@ func TestVerifyBlock(t *testing.T) { require.NoError(t, err) chain := blockchain.New(pebble.NewMemTest(t), &utils.Mainnet) + defer chain.Close() t.Run("error if chain is empty and incoming block number is not 0", func(t *testing.T) { block := &core.Block{Header: &core.Header{Number: 10}} @@ -176,6 +184,7 @@ func TestSanityCheckNewHeight(t *testing.T) { require.NoError(t, err) chain := blockchain.New(pebble.NewMemTest(t), &utils.Mainnet) + defer chain.Close() client := feeder.NewTestClient(t, &utils.Mainnet) @@ -221,6 +230,7 @@ func TestStore(t *testing.T) { t.Run("add block to empty blockchain", func(t *testing.T) { chain := blockchain.New(pebble.NewMemTest(t), &utils.Mainnet) + defer chain.Close() require.NoError(t, chain.Store(block0, &emptyCommitments, stateUpdate0, nil)) headBlock, err := chain.Head() @@ -247,6 +257,7 @@ func TestStore(t *testing.T) { require.NoError(t, err) chain := blockchain.New(pebble.NewMemTest(t), &utils.Mainnet) + defer chain.Close() require.NoError(t, chain.Store(block0, &emptyCommitments, stateUpdate0, nil)) require.NoError(t, chain.Store(block1, &emptyCommitments, stateUpdate1, nil)) @@ -270,6 +281,7 @@ func TestStore(t *testing.T) { func TestBlockCommitments(t *testing.T) { chain := blockchain.New(pebble.NewMemTest(t), &utils.Mainnet) + defer chain.Close() client := feeder.NewTestClient(t, &utils.Mainnet) gw := adaptfeeder.New(client) @@ -295,6 +307,7 @@ func TestBlockCommitments(t *testing.T) { func TestTransactionAndReceipt(t *testing.T) { chain := blockchain.New(pebble.NewMemTest(t), &utils.Mainnet) + defer chain.Close() client := feeder.NewTestClient(t, &utils.Mainnet) gw := adaptfeeder.New(client) @@ -383,6 +396,7 @@ func TestTransactionAndReceipt(t *testing.T) { func TestState(t *testing.T) { testDB := pebble.NewMemTest(t) chain := blockchain.New(testDB, &utils.Mainnet) + defer chain.Close() client := feeder.NewTestClient(t, &utils.Mainnet) gw := adaptfeeder.New(client) @@ -446,6 +460,7 @@ func TestState(t *testing.T) { func TestEvents(t *testing.T) { testDB := pebble.NewMemTest(t) chain := blockchain.New(testDB, &utils.Goerli2) + defer chain.Close() client := feeder.NewTestClient(t, &utils.Goerli2) gw := adaptfeeder.New(client) @@ -565,6 +580,7 @@ func TestEvents(t *testing.T) { func TestRevert(t *testing.T) { testdb := pebble.NewMemTest(t) chain := blockchain.New(testdb, &utils.Mainnet) + defer chain.Close() client := feeder.NewTestClient(t, &utils.Mainnet) gw := adaptfeeder.New(client) @@ -653,6 +669,7 @@ func TestL1Update(t *testing.T) { got, err := chain.L1Head() require.NoError(t, err) assert.Equal(t, head, got) + chain.Close() }) } } @@ -660,6 +677,7 @@ func TestL1Update(t *testing.T) { func TestPending(t *testing.T) { testDB := pebble.NewMemTest(t) chain := blockchain.New(testDB, &utils.Mainnet) + defer chain.Close() client := feeder.NewTestClient(t, &utils.Mainnet) gw := adaptfeeder.New(client) @@ -801,6 +819,7 @@ func TestPending(t *testing.T) { func TestStorePendingIncludesNumber(t *testing.T) { network := utils.Mainnet chain := blockchain.New(pebble.NewMemTest(t), &network) + defer chain.Close() // Store pending genesis. require.NoError(t, chain.StorePending(&blockchain.Pending{ diff --git a/blockchain/snap_server_interface.go b/blockchain/snap_server_interface.go new file mode 100644 index 0000000000..e06e3b6c7a --- /dev/null +++ b/blockchain/snap_server_interface.go @@ -0,0 +1,164 @@ +package blockchain + +import ( + "errors" + "fmt" + + "github.com/NethermindEth/juno/core" + "github.com/NethermindEth/juno/core/felt" + "github.com/NethermindEth/juno/db" +) + +const MaxSnapshots = 128 + +type snapshotRecord struct { + stateRoot *felt.Felt + contractsRoot *felt.Felt + classRoot *felt.Felt + blockHash *felt.Felt + txn db.Transaction + closer func() error +} + +var ErrMissingSnapshot = errors.New("missing snapshot") + +func (b *Blockchain) GetStateForStateRoot(stateRoot *felt.Felt) (*core.State, error) { + snapshot, err := b.findSnapshotMatching(func(record *snapshotRecord) bool { + return record.stateRoot.Equal(stateRoot) + }) + if err != nil { + return nil, err + } + + s := core.NewState(snapshot.txn) + + return s, nil +} + +func (b *Blockchain) findSnapshotMatching(filter func(record *snapshotRecord) bool) (*snapshotRecord, error) { + var snapshot *snapshotRecord + for _, record := range b.snapshots { + if filter(record) { + snapshot = record + break + } + } + + if snapshot == nil { + return nil, ErrMissingSnapshot + } + + return snapshot, nil +} + +func (b *Blockchain) GetClasses(felts []*felt.Felt) ([]core.Class, error) { + classes := make([]core.Class, len(felts)) + err := b.database.View(func(txn db.Transaction) error { + state := core.NewState(txn) + for i, f := range felts { + d, err := state.Class(f) + if err != nil && !errors.Is(err, db.ErrKeyNotFound) { + return err + } else if errors.Is(err, db.ErrKeyNotFound) { + classes[i] = nil + } else { + classes[i] = d.Class + } + } + + return nil + }) + if err != nil { + return nil, err + } + + return classes, nil +} + +func (b *Blockchain) GetDClasses(felts []*felt.Felt) ([]*core.DeclaredClass, error) { + classes := make([]*core.DeclaredClass, len(felts)) + err := b.database.View(func(txn db.Transaction) error { + state := core.NewState(txn) + for i, f := range felts { + d, err := state.Class(f) + if err != nil && !errors.Is(err, db.ErrKeyNotFound) { + return err + } else if errors.Is(err, db.ErrKeyNotFound) { + classes[i] = nil + } else { + classes[i] = d + } + } + + return nil + }) + if err != nil { + return nil, err + } + + return classes, nil +} + +func (b *Blockchain) seedSnapshot() error { + headheader, err := b.HeadsHeader() + if err != nil { + return err + } + + stateR, srCloser, err := b.HeadState() + if err != nil { + return err + } + + defer func() { _ = srCloser() }() + + state := stateR.(*core.State) + contractsRoot, classRoot, err := state.StateAndClassRoot() + if err != nil { + return err + } + + stateRoot, err := state.Root() + if err != nil { + return err + } + + txn, closer, err := b.database.PersistedView() + if err != nil { + return err + } + + dbsnap := snapshotRecord{ + stateRoot: stateRoot, + contractsRoot: contractsRoot, + classRoot: classRoot, + blockHash: headheader.Hash, + txn: txn, + closer: closer, + } + + fmt.Printf("Snapshot %d %s %s\n", headheader.Number, headheader.GlobalStateRoot, stateRoot) + + // TODO: Reorgs + b.snapshots = append(b.snapshots, &dbsnap) + if len(b.snapshots) > MaxSnapshots { + toremove := b.snapshots[0] + err = toremove.closer() + if err != nil { + return err + } + + // TODO: I think internally, it keep the old array. + // maybe the append copy it to a new array, who knows... + b.snapshots = b.snapshots[1:] + } + + return nil +} + +func (b *Blockchain) Close() { + for _, snapshot := range b.snapshots { + // ignore the errors here as it's most likely called on shutdown + _ = snapshot.closer() + } +} diff --git a/core/contract.go b/core/contract.go index 2af1fd8c4c..69f3e23e5f 100644 --- a/core/contract.go +++ b/core/contract.go @@ -10,7 +10,10 @@ import ( ) // contract storage has fixed height at 251 -const ContractStorageTrieHeight = 251 +const ( + GlobalTrieHeight = 251 + ContractStorageTrieHeight = 251 +) var ( ErrContractNotDeployed = errors.New("contract not deployed") @@ -168,6 +171,32 @@ func (c *ContractUpdater) UpdateStorage(diff map[felt.Felt]*felt.Felt, cb OnValu return cStorage.Commit() } +// UpdateStorage applies a change-set to the contract storage. +func (c *ContractUpdater) UpdateStorageKV(diff []FeltKV, cb OnValueChanged) error { + cStorage, err := storage(c.Address, c.txn) + if err != nil { + return err + } + + // apply the diff + for _, kv := range diff { + key := kv.Key + value := kv.Value + oldValue, pErr := cStorage.Put(key, value) + if pErr != nil { + return pErr + } + + if oldValue != nil { + if err = cb(key, oldValue); err != nil { + return err + } + } + } + + return cStorage.Commit() +} + func ContractStorage(addr, key *felt.Felt, txn db.Transaction) (*felt.Felt, error) { cStorage, err := storage(addr, txn) if err != nil { diff --git a/core/state.go b/core/state.go index effde8b518..fb7d8e8c19 100644 --- a/core/state.go +++ b/core/state.go @@ -124,6 +124,38 @@ func (s *State) Root() (*felt.Felt, error) { return crypto.PoseidonArray(stateVersion, storageRoot, classesRoot), nil } +func (s *State) StateAndClassRoot() (*felt.Felt, *felt.Felt, error) { + var storageRoot, classesRoot *felt.Felt + + sStorage, closer, err := s.storage() + if err != nil { + return nil, nil, err + } + + if storageRoot, err = sStorage.Root(); err != nil { + return nil, nil, err + } + + if err = closer(); err != nil { + return nil, nil, err + } + + classes, closer, err := s.classesTrie() + if err != nil { + return nil, nil, err + } + + if classesRoot, err = classes.Root(); err != nil { + return nil, nil, err + } + + if err = closer(); err != nil { + return nil, nil, err + } + + return storageRoot, classesRoot, nil +} + // storage returns a [core.Trie] that represents the Starknet global state in the given Txn context. func (s *State) storage() (*trie.Trie, func() error, error) { return s.globalTrie(db.StateTrie, trie.NewTriePedersen) @@ -133,6 +165,19 @@ func (s *State) classesTrie() (*trie.Trie, func() error, error) { return s.globalTrie(db.ClassesTrie, trie.NewTriePoseidon) } +// storage returns a [core.Trie] that represents the Starknet global state in the given Txn context. +func (s *State) StorageTrie() (*trie.Trie, func() error, error) { + return s.storage() +} + +func (s *State) ClassTrie() (*trie.Trie, func() error, error) { + return s.classesTrie() +} + +func (s *State) StorageTrieForAddr(addr *felt.Felt) (*trie.Trie, error) { + return storage(addr, s.txn) +} + func (s *State) globalTrie(bucket db.Bucket, newTrie trie.NewTrieFunc) (*trie.Trie, func() error, error) { dbPrefix := bucket.Key() tTxn := trie.NewStorage(s.txn, dbPrefix) @@ -204,14 +249,22 @@ func (s *State) Update(blockNumber uint64, update *StateUpdate, declaredClasses return err } + if err = s.UpdateNoVerify(blockNumber, update.StateDiff, declaredClasses); err != nil { + return err + } + + return s.verifyStateUpdateRoot(update.NewRoot) +} + +func (s *State) UpdateNoVerify(blockNumber uint64, update *StateDiff, declaredClasses map[felt.Felt]Class) error { // register declared classes mentioned in stateDiff.deployedContracts and stateDiff.declaredClasses for cHash, class := range declaredClasses { - if err = s.putClass(&cHash, class, blockNumber); err != nil { + if err := s.putClass(&cHash, class, blockNumber); err != nil { return err } } - if err = s.updateDeclaredClassesTrie(update.StateDiff.DeclaredV1Classes, declaredClasses); err != nil { + if err := s.updateDeclaredClassesTrie(update.DeclaredV1Classes, declaredClasses); err != nil { return err } @@ -221,13 +274,13 @@ func (s *State) Update(blockNumber uint64, update *StateUpdate, declaredClasses } // register deployed contracts - for addr, classHash := range update.StateDiff.DeployedContracts { + for addr, classHash := range update.DeployedContracts { if err = s.putNewContract(stateTrie, &addr, classHash, blockNumber); err != nil { return err } } - if err = s.updateContracts(stateTrie, blockNumber, update.StateDiff, true); err != nil { + if err = s.updateContracts(stateTrie, blockNumber, update, true); err != nil { return err } @@ -235,7 +288,7 @@ func (s *State) Update(blockNumber uint64, update *StateUpdate, declaredClasses return err } - return s.verifyStateUpdateRoot(update.NewRoot) + return nil } var ( @@ -279,6 +332,77 @@ func (s *State) updateContracts(stateTrie *trie.Trie, blockNumber uint64, diff * return s.updateContractStorages(stateTrie, diff.StorageDiffs, blockNumber, logChanges) } +func (s *State) UpdateContractNoLog(paths, nonces, classes []*felt.Felt) error { + stateTrie, storageCloser, err := s.storage() + if err != nil { + return err + } + + for i, path := range paths { + nonce := nonces[i] + class := classes[i] + + contract, err := NewContractUpdater(path, s.txn) + if err != nil && !errors.Is(err, ErrContractNotDeployed) { + return err + } + if errors.Is(err, ErrContractNotDeployed) { + contract, err = DeployContract(path, class, s.txn) + if err != nil { + return err + } + } + + err = contract.Replace(class) + if err != nil { + return err + } + + err = contract.UpdateNonce(nonce) + if err != nil { + return err + } + + err = s.updateContractCommitment(stateTrie, contract) + if err != nil { + return err + } + } + + if err = storageCloser(); err != nil { + return err + } + return nil +} + +func (s *State) UpdateContractStorages(storages map[felt.Felt]map[felt.Felt]*felt.Felt) error { + stateTrie, storageCloser, err := s.storage() + if err != nil { + return err + } + + err = s.updateContractStorages(stateTrie, storages, 0, false) + if err != nil { + return err + } + + return storageCloser() +} + +func (s *State) UpdateContractStoragesKV(storages map[felt.Felt][]FeltKV) error { + stateTrie, storageCloser, err := s.storage() + if err != nil { + return err + } + + err = s.updateContractStoragesKV(stateTrie, storages, 0, false) + if err != nil { + return err + } + + return storageCloser() +} + // replaceContract replaces the class that a contract at a given address instantiates func (s *State) replaceContract(stateTrie *trie.Trie, addr, classHash *felt.Felt) (*felt.Felt, error) { contract, err := NewContractUpdater(addr, s.txn) @@ -342,6 +466,64 @@ func (s *State) Class(classHash *felt.Felt) (*DeclaredClass, error) { return &class, nil } +// StartsWith checks if the byte array 'a' starts with the byte array 'b' +func StartsWith(a, b []byte) bool { + // If b is longer than a, it can't be a prefix + if len(b) > len(a) { + return false + } + + // Compare the elements of a and b + for i := 0; i < len(b); i++ { + if a[i] != b[i] { + return false + } + } + + return true +} + +func (s *State) PrintIt() (*DeclaredClass, error) { + classKey := db.Class.Key(nil) + + it, err := s.txn.NewIterator() + if err != nil { + return nil, err + } + + it.Seek(classKey) + idx := 0 + printed := 0 + for it.Valid() && StartsWith(it.Key(), classKey) { + value, err := it.Value() + if err != nil { + return nil, err + } + + var class DeclaredClass + err = encoder.Unmarshal(value, &class) + if err != nil { + return nil, err + } + + if class.Class.Version() == 0 { + fmt.Printf("%d %x %d %d\n", idx, it.Key(), class.Class.Version(), len(value)) + if class.Class.Version() == 0 && len(value) < 20000 { + fmt.Printf("%x\n", value) + printed++ + if printed >= 2 { + return nil, nil + } + } + } + + idx++ + it.Next() + } + + return nil, nil +} + func (s *State) updateStorageBuffered(contractAddr *felt.Felt, updateDiff map[felt.Felt]*felt.Felt, blockNumber uint64, logChanges bool) ( *db.BufferedTransaction, error, ) { @@ -367,6 +549,106 @@ func (s *State) updateStorageBuffered(contractAddr *felt.Felt, updateDiff map[fe return bufferedTxn, nil } +func (s *State) updateStorageBufferedKV(contractAddr *felt.Felt, updateDiff []FeltKV, blockNumber uint64, logChanges bool) ( + *db.BufferedTransaction, error, +) { + // to avoid multiple transactions writing to s.txn, create a buffered transaction and use that in the worker goroutine + bufferedTxn := db.NewBufferedTransaction(s.txn) + bufferedState := NewState(bufferedTxn) + bufferedContract, err := NewContractUpdater(contractAddr, bufferedTxn) + if err != nil { + return nil, err + } + + onValueChanged := func(location, oldValue *felt.Felt) error { + if logChanges { + return bufferedState.LogContractStorage(contractAddr, location, oldValue, blockNumber) + } + return nil + } + + if err = bufferedContract.UpdateStorageKV(updateDiff, onValueChanged); err != nil { + return nil, err + } + + return bufferedTxn, nil +} + +type FeltKV struct { + Key *felt.Felt + Value *felt.Felt +} + +// updateContractStorage applies the diff set to the Trie of the +// contract at the given address in the given Txn context. +func (s *State) updateContractStoragesKV(stateTrie *trie.Trie, diffs map[felt.Felt][]FeltKV, + blockNumber uint64, logChanges bool, +) error { + // make sure all noClassContracts are deployed + for addr := range diffs { + if _, ok := noClassContracts[addr]; !ok { + continue + } + + _, err := NewContractUpdater(&addr, s.txn) + if err != nil { + if !errors.Is(err, ErrContractNotDeployed) { + return err + } + // Deploy noClassContract + err = s.putNewContract(stateTrie, &addr, noClassContractsClassHash, blockNumber) + if err != nil { + return err + } + } + } + + // sort the contracts in decending diff size order + // so we start with the heaviest update first + keys := make([]felt.Felt, 0, len(diffs)) + for key := range diffs { + keys = append(keys, key) + } + sort.SliceStable(keys, func(i, j int) bool { + return len(diffs[keys[i]]) > len(diffs[keys[j]]) + }) + + // update per-contract storage Tries concurrently + contractUpdaters := pool.NewWithResults[*db.BufferedTransaction]().WithErrors().WithMaxGoroutines(runtime.GOMAXPROCS(0)) + for _, key := range keys { + conractAddr := key + updateDiff := diffs[conractAddr] + contractUpdaters.Go(func() (*db.BufferedTransaction, error) { + return s.updateStorageBufferedKV(&conractAddr, updateDiff, blockNumber, logChanges) + }) + } + + bufferedTxns, err := contractUpdaters.Wait() + if err != nil { + return err + } + + // flush buffered txns + for _, bufferedTxn := range bufferedTxns { + if err = bufferedTxn.Flush(); err != nil { + return err + } + } + + for addr := range diffs { + contract, err := NewContractUpdater(&addr, s.txn) + if err != nil { + return err + } + + if err = s.updateContractCommitment(stateTrie, contract); err != nil { + return err + } + } + + return nil +} + // updateContractStorage applies the diff set to the Trie of the // contract at the given address in the given Txn context. func (s *State) updateContractStorages(stateTrie *trie.Trie, diffs map[felt.Felt]map[felt.Felt]*felt.Felt, diff --git a/core/trie/key.go b/core/trie/key.go index 7f0e6af609..a451cb25ee 100644 --- a/core/trie/key.go +++ b/core/trie/key.go @@ -153,3 +153,61 @@ func (k *Key) RemoveLastBit() { inUseBytes[0] = (inUseBytes[0] << unusedBitsCount) >> unusedBitsCount } } + +// CmpAligned is Cmp as if the value is bigendian bytes of key of the same length +func (k Key) CmpAligned(other *Key) int { + // No its not aligned, so need to convert to bigint then left shift it so that the MSB is of the same index + height := k.len + if other.len > height { + height = other.len + } + + b1i := k.alignedBitInt(height) + b2i := other.alignedBitInt(height) + return b1i.Cmp(b2i) +} + +func (k Key) alignedBitInt(height uint8) *big.Int { + theint := &big.Int{} + theint = theint.SetBytes(k.bitset[:]) + if k.len < height { + theint = theint.Lsh(theint, uint(height-k.len)) + } + + return theint +} + +func (k *Key) AppendBitMut(flag bool) { + const LSB = uint8(0x1) + bit := k.len + byteIdx := bit / 8 + byteAtIdx := k.bitset[len(k.bitset)-int(byteIdx)-1] + bitIdx := bit % 8 + + // I'm sure someone will make this nicer + if flag { + byteAtIdx |= LSB << bitIdx + } else { + byteAtIdx &= ^(LSB << bitIdx) + } + + k.len++ + k.bitset[len(k.bitset)-int(byteIdx)-1] = byteAtIdx +} + +func (k Key) Append(otherKey *Key) Key { + result := NewKey(otherKey.len, otherKey.bitset[:]) + + // I'm sure someone will make this faster + for i := uint8(0); i < k.len; i++ { + result.AppendBitMut(k.Test(i)) + } + + return result +} + +func (k Key) AppendBit(flag bool) Key { + result := NewKey(0, []byte{}) + result.AppendBitMut(flag) + return k.Append(&result) +} diff --git a/core/trie/key_test.go b/core/trie/key_test.go index 8d56a31e0c..a16299a019 100644 --- a/core/trie/key_test.go +++ b/core/trie/key_test.go @@ -2,6 +2,7 @@ package trie_test import ( "bytes" + "fmt" "testing" "github.com/NethermindEth/juno/core/felt" @@ -153,3 +154,120 @@ func TestTruncate(t *testing.T) { }) } } + +func Test_cmp(t *testing.T) { + tests := []struct { + n1 int + n2 int + isHigher bool + }{ + { + n1: 10, + n2: 0, + isHigher: true, + }, + { + n1: 5, + n2: 0, + isHigher: true, + }, + { + n1: 5, + n2: 4, + isHigher: true, + }, + { + n1: 5, + n2: 5, + isHigher: false, + }, + { + n1: 4, + n2: 5, + isHigher: false, + }, + { + n1: 0, + n2: 5, + isHigher: false, + }, + { + n1: 300, + n2: 1, + isHigher: true, + }, + { + n1: 1, + n2: 300, + isHigher: false, + }, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("%d %d %v", test.n1, test.n2, test.isHigher), func(t *testing.T) { + k1 := numToKey(test.n1) + k2 := numToKey(test.n2) + + assert.Equal(t, + k1.CmpAligned(&k2) > 0, + test.isHigher) + }) + } +} + +func numToKey(num int) trie.Key { + return trie.NewKey(8, []byte{byte(num)}) +} + +func TestKeyAppend(t *testing.T) { + tests := map[string]struct { + Key1 trie.Key + Key2 trie.Key + ExpectedKey trie.Key + }{ + "no append": { + Key1: trie.NewKey(1, []byte{0x01}), + Key2: trie.NewKey(0, []byte{0x00}), + ExpectedKey: trie.NewKey(1, []byte{0x01}), + }, + "from zero append": { + Key1: trie.NewKey(0, []byte{0x00}), + Key2: trie.NewKey(1, []byte{0x01}), + ExpectedKey: trie.NewKey(1, []byte{0x01}), + }, + "append shift": { + Key1: trie.NewKey(1, []byte{0x01}), + Key2: trie.NewKey(7, []byte{0x00}), + ExpectedKey: trie.NewKey(8, []byte{0x80}), + }, + "append to a new byte": { + Key1: trie.NewKey(8, []byte{0xff}), + Key2: trie.NewKey(1, []byte{0x01}), + ExpectedKey: trie.NewKey(9, []byte{0x01, 0xff}), + }, + "append multi byte": { + Key1: trie.NewKey(11, []byte{0x00, 0xff}), // 000 1111 1111 + Key2: trie.NewKey(12, []byte{0x00, 0xff}), // 0000 1111 1111 + ExpectedKey: trie.NewKey(23, []byte{0x0f, 0xf0, 0xff}), // 000 1111 1111 0000 1111 1111 + }, + } + + for desc, test := range tests { + t.Run(desc, func(t *testing.T) { + appended := test.Key1.Append(&test.Key2) + assert.Equal(t, test.ExpectedKey, appended) + }) + } +} + +func TestKeyAppendBit(t *testing.T) { + k1 := trie.NewKey(1, []byte{0x01}) + k2 := k1.AppendBit(true) + expected := trie.NewKey(2, []byte{0x03}) + assert.Equal(t, k2, expected) + + k1 = trie.NewKey(1, []byte{0x00}) + k2 = k1.AppendBit(true) + expected = trie.NewKey(2, []byte{0x01}) + assert.Equal(t, k2, expected) +} diff --git a/core/trie/proof.go b/core/trie/proof.go index 517ae60764..0744a9a119 100644 --- a/core/trie/proof.go +++ b/core/trie/proof.go @@ -674,3 +674,11 @@ func BuildTrie(leftProofPath, rightProofPath []StorageNode, keys, values []*felt } return tempTrie, nil } + +func (t *Trie) RangeProof(startPath, endPath *felt.Felt) ([]ProofNode, error) { + // TODO: Do this properly + const trieHeight = 251 + bts := startPath.Bytes() + k := NewKey(trieHeight, bts[:]) + return GetProof(&k, t) +} diff --git a/core/trie/snap_support.go b/core/trie/snap_support.go new file mode 100644 index 0000000000..84f3f1e2c4 --- /dev/null +++ b/core/trie/snap_support.go @@ -0,0 +1,162 @@ +package trie + +import ( + "github.com/NethermindEth/juno/core/felt" +) + +func (t *Trie) IterateAndGenerateProof(startValue *felt.Felt, consumer func(key, value *felt.Felt) (bool, error), +) ([]ProofNode, bool, error) { + var lastKey *felt.Felt + + finished, err := t.Iterate(startValue, func(key, value *felt.Felt) (bool, error) { + lastKey = key + + return consumer(key, value) + }) + if err != nil { + return nil, finished, err + } + + proofset := map[felt.Felt]ProofNode{} + + // If start value is null && finished, you dont need to provide any proof at all + if !finished || startValue != nil { + feltBts := startValue.Bytes() + startKey := NewKey(t.height, feltBts[:]) + // Yes, the left proof is actually for the start query, not the actual leaf. Very confusing, yea I know. Need to + // actually check that the server did not skip leafs. + leftProof, err := GetProof(&startKey, t) + if err != nil { + return nil, finished, err + } + for _, proof := range leftProof { + // Well.. using the trie hash here is kinda slow... but I just need it to work right now. + proofset[*proof.Hash(t.hash)] = proof + } + } + + if !finished && lastKey != nil { + feltBts := lastKey.Bytes() + lastKey := NewKey(t.height, feltBts[:]) + rightProof, err := GetProof(&lastKey, t) + if err != nil { + return nil, finished, err + } + + for _, proof := range rightProof { + proofset[*proof.Hash(t.hash)] = proof + } + } + + proofs := make([]ProofNode, 0, len(proofset)) + for _, node := range proofset { + proofs = append(proofs, node) + } + + return proofs, finished, nil +} + +func VerifyRange(root, startKey *felt.Felt, keys, values []*felt.Felt, proofs []ProofNode, hash hashFunc, + treeHeight uint8, +) (hasMore, valid bool, oerr error) { + proofMap := map[felt.Felt]ProofNode{} + for _, proof := range proofs { + proofHash := proof.Hash(hash) + proofMap[*proofHash] = proof + } + + if len(proofMap) == 0 && startKey == nil { + // Special case where the whole trie is sent in one go. + // We just need to completely reconstruct the trie. + + tempTrie, err := newTrie(newMemStorage(), treeHeight, hash) + if err != nil { + return false, false, err + } + + for i, key := range keys { + _, err = tempTrie.Put(key, values[i]) + if err != nil { + return false, false, err + } + } + + recalculatedRoot, err := tempTrie.Root() + if err != nil { + return false, false, err + } + + if !root.Equal(recalculatedRoot) { + return false, false, nil + } + + return false, true, nil + } + + if _, ok := proofMap[*root]; !ok { + // Verification failure, root not included in proof. + return false, false, nil + } + + proofKeys := map[felt.Felt]Key{} + err := buildKeys(NewKey(0, []byte{}), root, proofMap, proofKeys, 0) + if err != nil { + return false, false, err + } + + // TODO: Verify here proof here + + hasMoreKeyCheck := startKey + if len(keys) > 0 { + hasMoreKeyCheck = keys[len(keys)-1] + } + + feltBytes := hasMoreKeyCheck.Bytes() + hasMoreKeyCheckKey := NewKey(treeHeight, feltBytes[:]) + + // does this actually work on all case? + hasMore = false + for _, key := range proofKeys { + comparison := key.CmpAligned(&hasMoreKeyCheckKey) + if comparison > 0 { + hasMore = true + } + } + + return hasMore, true, nil +} + +func buildKeys(currentKey Key, currentNode *felt.Felt, proofMap map[felt.Felt]ProofNode, keys map[felt.Felt]Key, depth int) error { + keys[*currentNode] = currentKey + proofNode, ok := proofMap[*currentNode] + if !ok { + return nil + } + + if proofNode.Edge != nil { + chKey := currentKey.Append(proofNode.Edge.Path) + ch := proofNode.Edge.Child + err := buildKeys(chKey, ch, proofMap, keys, depth+1) + if err != nil { + return err + } + } else { + binary := proofNode.Binary + + chKey := currentKey.AppendBit(false) + ch := binary.LeftHash + err := buildKeys(chKey, ch, proofMap, keys, depth+1) + if err != nil { + return err + } + + chKey = currentKey.AppendBit(true) + ch = binary.RightHash + err = buildKeys(chKey, ch, proofMap, keys, depth+1) + if err != nil { + return err + } + } + + return nil +} diff --git a/core/trie/snap_support_test.go b/core/trie/snap_support_test.go new file mode 100644 index 0000000000..9a3501bb6d --- /dev/null +++ b/core/trie/snap_support_test.go @@ -0,0 +1,278 @@ +package trie_test + +import ( + "testing" + + "github.com/NethermindEth/juno/core/crypto" + "github.com/NethermindEth/juno/core/felt" + "github.com/NethermindEth/juno/core/trie" + "github.com/NethermindEth/juno/db" + "github.com/stretchr/testify/assert" +) + +const trieHeight = 251 + +func TestRangeAndVerify(t *testing.T) { + scenarios := []struct { + name string + startQuery *felt.Felt + limitQuery *felt.Felt + maxNode int + expectedKeyCount int + hasMore bool + noProof bool + }{ + { + name: "all", + startQuery: numToFelt(0), + expectedKeyCount: 10, + hasMore: false, + }, + { + name: "all without start query", + expectedKeyCount: 10, + hasMore: false, + noProof: true, + }, + { + name: "start in the middle", + startQuery: numToFelt(500), + expectedKeyCount: 5, + hasMore: false, + }, + { + name: "start with limit query", + startQuery: numToFelt(100), + limitQuery: numToFelt(500), + expectedKeyCount: 5, + hasMore: true, + }, + { + name: "start with limit query and node count limit", + startQuery: numToFelt(100), + limitQuery: numToFelt(500), + maxNode: 3, + expectedKeyCount: 3, + hasMore: true, + }, + { + name: "finished before limit query", + startQuery: numToFelt(100), + limitQuery: numToFelt(20000), + expectedKeyCount: 9, + hasMore: false, + }, + { + name: "last one right after limit query", + startQuery: numToFelt(100), + limitQuery: numToFelt(900), + expectedKeyCount: 9, + hasMore: false, + }, + { + name: "two leaf after limit query, last leaf skipped", + startQuery: numToFelt(100), + limitQuery: numToFelt(800), + expectedKeyCount: 8, + hasMore: true, + }, + { + name: "no node between start and limit", + startQuery: numToFelt(450), + limitQuery: numToFelt(451), + expectedKeyCount: 1, + hasMore: true, + }, + { + name: "start query after last node", + startQuery: numToFelt(10000), + expectedKeyCount: 0, + hasMore: false, + }, + } + + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + storage := trie.NewStorage(db.NewMemTransaction(), []byte{}) + testTrie, err := trie.NewTriePedersen(storage, 251) + assert.NoError(t, err) + + for i := 0; i < 10; i++ { + _, err = testTrie.Put(numToFelt(i*100+1), numToFelt(i*100+2)) + assert.NoError(t, err) + } + + expectedRoot, err := testTrie.Root() + assert.NoError(t, err) + + startQuery := scenario.startQuery + var keys []*felt.Felt + var values []*felt.Felt + + proofs, _, err := testTrie.IterateAndGenerateProof(startQuery, func(key, value *felt.Felt) (bool, error) { + keys = append(keys, key) + values = append(values, value) + if scenario.maxNode > 0 && len(keys) >= scenario.maxNode { + return false, nil + } + if scenario.limitQuery != nil && key.Cmp(scenario.limitQuery) > 0 { + // Last (one after limit) is included. + return false, nil + } + return true, nil + }) + assert.NoError(t, err) + + assert.Equal(t, scenario.expectedKeyCount, len(keys)) + if scenario.noProof { + assert.Empty(t, proofs) + } + + hasMore, valid, err := trie.VerifyRange(expectedRoot, startQuery, keys, values, proofs, crypto.Pedersen, trieHeight) + assert.NoError(t, err) + assert.True(t, valid) + + assert.Equal(t, scenario.hasMore, hasMore) + }) + } +} + +func TestRangeAndVerifyReject(t *testing.T) { + scenarios := []struct { + name string + startQuery *felt.Felt + skip bool + maxNode int + mutator func(keys, values []*felt.Felt, proofs []trie.ProofNode) ([]*felt.Felt, []*felt.Felt, []trie.ProofNode) + }{ + { + name: "missing proofs", + startQuery: numToFelt(500), + mutator: func(keys, values []*felt.Felt, proofs []trie.ProofNode) ([]*felt.Felt, []*felt.Felt, []trie.ProofNode) { + return keys, values, nil + }, + }, + { + name: "missing leaf when all node requested", + mutator: func(keys, values []*felt.Felt, proofs []trie.ProofNode) ([]*felt.Felt, []*felt.Felt, []trie.ProofNode) { + return keys[1:], values[1:], nil + }, + }, + { + skip: true, + name: "missing part of keys at start", + startQuery: numToFelt(500), + mutator: func(keys, values []*felt.Felt, proofs []trie.ProofNode) ([]*felt.Felt, []*felt.Felt, []trie.ProofNode) { + return keys[1:], values[1:], proofs + }, + }, + { + skip: true, + name: "missing part of keys at end", + startQuery: numToFelt(500), + mutator: func(keys, values []*felt.Felt, proofs []trie.ProofNode) ([]*felt.Felt, []*felt.Felt, []trie.ProofNode) { + return keys[:len(keys)-1], values[:len(keys)-1], proofs + }, + }, + { + skip: true, + name: "missing part of keys in the middle", + startQuery: numToFelt(500), + mutator: func(keys, values []*felt.Felt, proofs []trie.ProofNode) ([]*felt.Felt, []*felt.Felt, []trie.ProofNode) { + newkeys := []*felt.Felt{} + newvalues := []*felt.Felt{} + newkeys = append(newkeys, keys[:2]...) + newvalues = append(newvalues, values[:2]...) + newkeys = append(newkeys, keys[3:]...) + newvalues = append(newvalues, values[3:]...) + + return newkeys, newvalues, proofs + }, + }, + { + name: "missing part of keys in the middle when whole trie is sent", + mutator: func(keys, values []*felt.Felt, proofs []trie.ProofNode) ([]*felt.Felt, []*felt.Felt, []trie.ProofNode) { + newkeys := []*felt.Felt{} + newvalues := []*felt.Felt{} + newkeys = append(newkeys, keys[:2]...) + newvalues = append(newvalues, values[:2]...) + newkeys = append(newkeys, keys[3:]...) + newvalues = append(newvalues, values[3:]...) + + return newkeys, newvalues, proofs + }, + }, + { + name: "value changed", + mutator: func(keys, values []*felt.Felt, proofs []trie.ProofNode) ([]*felt.Felt, []*felt.Felt, []trie.ProofNode) { + values[3] = numToFelt(10000) + return keys, values, proofs + }, + }, + { + skip: true, + startQuery: numToFelt(500), + name: "value changed when whole trie is sent", + mutator: func(keys, values []*felt.Felt, proofs []trie.ProofNode) ([]*felt.Felt, []*felt.Felt, []trie.ProofNode) { + values[3] = numToFelt(10000) + return keys, values, proofs + }, + }, + { + name: "key changed", + mutator: func(keys, values []*felt.Felt, proofs []trie.ProofNode) ([]*felt.Felt, []*felt.Felt, []trie.ProofNode) { + keys[3] = numToFelt(10000) + return keys, values, proofs + }, + }, + { + skip: true, + startQuery: numToFelt(500), + name: "key changed when whole trie is sent", + mutator: func(keys, values []*felt.Felt, proofs []trie.ProofNode) ([]*felt.Felt, []*felt.Felt, []trie.ProofNode) { + keys[3] = numToFelt(10000) + return keys, values, proofs + }, + }, + } + + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + if scenario.skip { + t.Skip() + } + + storage := trie.NewStorage(db.NewMemTransaction(), []byte{}) + testTrie, err := trie.NewTriePedersen(storage, 251) + assert.NoError(t, err) + + for i := 0; i < 10; i++ { + _, err = testTrie.Put(numToFelt(i*100+1), numToFelt(i*100+2)) + assert.NoError(t, err) + } + + expectedRoot, err := testTrie.Root() + assert.NoError(t, err) + + startQuery := scenario.startQuery + var keys []*felt.Felt + var values []*felt.Felt + + proofs, _, err := testTrie.IterateAndGenerateProof(startQuery, func(key, value *felt.Felt) (bool, error) { + keys = append(keys, key) + values = append(values, value) + if scenario.maxNode > 0 && len(keys) >= scenario.maxNode { + return false, nil + } + return true, nil + }) + assert.NoError(t, err) + + keys, values, proofs = scenario.mutator(keys, values, proofs) + + _, valid, err := trie.VerifyRange(expectedRoot, startQuery, keys, values, proofs, crypto.Pedersen, trieHeight) + assert.NoError(t, err) + assert.False(t, valid) + }) + } +} diff --git a/core/trie/trie.go b/core/trie/trie.go index c03357d3af..5ab0f2c481 100644 --- a/core/trie/trie.go +++ b/core/trie/trie.go @@ -11,10 +11,18 @@ import ( "github.com/NethermindEth/juno/core/crypto" "github.com/NethermindEth/juno/core/felt" "github.com/NethermindEth/juno/db" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" ) type hashFunc func(*felt.Felt, *felt.Felt) *felt.Felt +type IterableStorage interface { + IterateLeaf(startKey *Key, consumer func(key, value *felt.Felt) (bool, error)) (bool, error) +} + +type HashFunc func(*felt.Felt, *felt.Felt) *felt.Felt + // Trie is a dense Merkle Patricia Trie (i.e., all internal nodes have two children). // // This implementation allows for a "flat" storage by keying nodes on their path rather than @@ -43,6 +51,10 @@ type Trie struct { rootKeyIsDirty bool } +func (t *Trie) GetRootKey() *Key { + return t.rootKey +} + type NewTrieFunc func(*Storage, uint8) (*Trie, error) func NewTriePedersen(storage *Storage, height uint8) (*Trie, error) { @@ -53,6 +65,10 @@ func NewTriePoseidon(storage *Storage, height uint8) (*Trie, error) { return newTrie(storage, height, crypto.Poseidon) } +func NewTrie(storage *Storage, height uint8, hash HashFunc) (*Trie, error) { + return newTrie(storage, height, hashFunc(hash)) +} + func newTrie(storage *Storage, height uint8, hash hashFunc) (*Trie, error) { if height > felt.Bits { return nil, fmt.Errorf("max trie height is %d, got: %d", felt.Bits, height) @@ -321,8 +337,14 @@ func (t *Trie) insertOrUpdateValue(nodeKey *Key, node *Node, nodes []StorageNode return nil } +var triePut = promauto.NewCounter(prometheus.CounterOpts{ + Name: "juno_trie_put", + Help: "trie put", +}) + // Put updates the corresponding `value` for a `key` func (t *Trie) Put(key, value *felt.Felt) (*felt.Felt, error) { + triePut.Inc() if key.Cmp(t.maxKey) > 0 { return nil, fmt.Errorf("key %s exceeds trie height %d", key, t.height) } @@ -715,3 +737,50 @@ func (t *Trie) dump(level int, parentP *Key) { storage: t.storage, }).dump(level+1, t.rootKey) } + +// Iterate the trie from startValue in ascending order until the consumer returned false or an error occur or end of +// trie was reached. Return true if end of trie is reached. +// TODO: its much more efficient to iterate from the txn level. But even without that, if the leaf are ordered correctly, +// block cache should have a pretty good hit rate. +func (t *Trie) Iterate(startValue *felt.Felt, consumer func(key, value *felt.Felt) (bool, error)) (bool, error) { + if startValue == nil { + startValue = &felt.Zero + } + startKey := t.feltToKey(startValue) + + return t.doIterate(&startKey, t.rootKey, consumer) +} + +// doIterate returns false if the end of the trie is reached, true otherwise +func (t *Trie) doIterate(startKey, key *Key, consumer func(key, value *felt.Felt) (bool, error)) (bool, error) { + if key == nil { + return false, nil + } + + node, err := t.storage.Get(key) + if err != nil { + return false, err + } + + if key.Len() == t.height { + if startKey.CmpAligned(key) > 0 { + return true, nil + } + keyAsFelt := key.Felt() + return consumer(&keyAsFelt, node.Value) + } + + // If the startKey is higher than the right node, no point in going to left at all + if startKey.CmpAligned(node.Right) < 0 { + next, err := t.doIterate(startKey, node.Left, consumer) + if err != nil { + return false, err + } + + if !next { + return false, nil + } + } + + return t.doIterate(startKey, node.Right, consumer) +} diff --git a/core/trie/trie_test.go b/core/trie/trie_test.go index fb5460739d..6ed50be639 100644 --- a/core/trie/trie_test.go +++ b/core/trie/trie_test.go @@ -1,6 +1,7 @@ package trie_test import ( + "math/big" "strconv" "testing" @@ -375,3 +376,154 @@ func BenchmarkTriePut(b *testing.B) { return t.Commit() })) } + +func TestTrieIterate(t *testing.T) { + t.Run("iterate standard", func(t *testing.T) { + require.NoError(t, trie.RunOnTempTriePedersen(251, func(tempTrie *trie.Trie) error { + expectedKeys := []*felt.Felt{} + expectedValues := []*felt.Felt{} + for i := 0; i < 2; i++ { + key := new(felt.Felt).SetUint64(uint64(i*10 + 1)) + val := new(felt.Felt).SetUint64(uint64(i + 1)) + + expectedKeys = append(expectedKeys, key) + expectedValues = append(expectedValues, val) + + _, err := tempTrie.Put(key, val) + require.NoError(t, err) + } + + startAddr := new(felt.Felt).SetUint64(0) + keys := []*felt.Felt{} + values := []*felt.Felt{} + finished, err := tempTrie.Iterate(startAddr, func(key, value *felt.Felt) (bool, error) { + keys = append(keys, key) + values = append(values, value) + return true, nil + }) + + assert.Nil(t, err) + assert.True(t, finished) + + assert.Equal(t, expectedKeys, keys) + assert.Equal(t, expectedValues, values) + + return nil + })) + }) +} + +func numToFelt(num int) *felt.Felt { + return numToFeltBigInt(big.NewInt(int64(num))) +} + +func numToFeltBigInt(num *big.Int) *felt.Felt { + f := felt.Zero + return f.SetBigInt(num) +} + +func TestTrie_Iterate(t *testing.T) { + tr, err := trie.NewTriePedersen(trie.NewStorage(db.NewMemTransaction(), []byte{1}), 251) + assert.Nil(t, err) + + for i := 0; i < 10; i++ { + _, err = tr.Put(numToFelt(i*10), numToFelt(i+10)) + assert.Nil(t, err) + } + err = tr.Commit() + assert.Nil(t, err) + + tests := []struct { + name string + startKey *felt.Felt + count int + expectedKeys []*felt.Felt + expectedValues []*felt.Felt + }{ + { + name: "all", + startKey: numToFelt(0), + count: 10, + expectedKeys: []*felt.Felt{ + numToFelt(0), + numToFelt(10), + numToFelt(20), + numToFelt(30), + numToFelt(40), + numToFelt(50), + numToFelt(60), + numToFelt(70), + numToFelt(80), + numToFelt(90), + }, + expectedValues: []*felt.Felt{ + numToFelt(10), + numToFelt(11), + numToFelt(12), + numToFelt(13), + numToFelt(14), + numToFelt(15), + numToFelt(16), + numToFelt(17), + numToFelt(18), + numToFelt(19), + }, + }, + { + name: "limited", + startKey: numToFelt(0), + count: 2, + expectedKeys: []*felt.Felt{ + numToFelt(0), + numToFelt(10), + }, + expectedValues: []*felt.Felt{ + numToFelt(10), + numToFelt(11), + }, + }, + { + name: "limited with offset", + startKey: numToFelt(30), + count: 2, + expectedKeys: []*felt.Felt{ + numToFelt(30), + numToFelt(40), + }, + expectedValues: []*felt.Felt{ + numToFelt(13), + numToFelt(14), + }, + }, + { + name: "limited with offset that does not match a leaf", + startKey: numToFelt(25), + count: 2, + expectedKeys: []*felt.Felt{ + numToFelt(30), + numToFelt(40), + }, + expectedValues: []*felt.Felt{ + numToFelt(13), + numToFelt(14), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + keys := make([]*felt.Felt, 0) + values := make([]*felt.Felt, 0) + + _, err := tr.Iterate(test.startKey, func(key *felt.Felt, value *felt.Felt) (bool, error) { + keys = append(keys, key) + values = append(values, value) + return len(keys) < test.count, nil + }) + assert.Nil(t, err) + + assert.Equal(t, test.expectedKeys, keys) + assert.Equal(t, test.expectedValues, values) + }) + } +} diff --git a/db/db.go b/db/db.go index 6475dc5e19..1ff7b101f7 100644 --- a/db/db.go +++ b/db/db.go @@ -33,6 +33,8 @@ type DB interface { // WithListener registers an EventListener WithListener(listener EventListener) DB + + PersistedView() (Transaction, func() error, error) } // Iterator is an iterator over a DB's key/value pairs. diff --git a/db/pebble/db.go b/db/pebble/db.go index 5974edf720..d3b930335c 100644 --- a/db/pebble/db.go +++ b/db/pebble/db.go @@ -94,6 +94,7 @@ func (d *DB) NewTransaction(update bool) (db.Transaction, error) { d.wMutex.Lock() return NewBatch(d.pebble.NewIndexedBatch(), d.wMutex, d.listener), nil } + txn.rwlock = &sync.RWMutex{} return NewSnapshot(d.pebble.NewSnapshot(), d.listener), nil } @@ -158,3 +159,14 @@ func CalculatePrefixSize(ctx context.Context, pDB *DB, prefix []byte) (*Item, er return item, utils.RunAndWrapOnError(it.Close, err) } + +// View : see db.DB.View +func (d *DB) PersistedView() (db.Transaction, func() error, error) { + txn, err := d.NewTransaction(false) + if err != nil { + return nil, nil, err + } + return txn, func() error { + return txn.Discard() + }, nil +} diff --git a/db/remote/db.go b/db/remote/db.go index 80f084edbe..45b1768a2c 100644 --- a/db/remote/db.go +++ b/db/remote/db.go @@ -2,6 +2,7 @@ package remote import ( "context" + "errors" "math" "time" @@ -80,3 +81,7 @@ func (d *DB) Close() error { func (d *DB) Impl() any { return d.kvClient } + +func (d *DB) PersistedView() (db.Transaction, func() error, error) { + return nil, nil, errors.New("persisted view not supported") +} diff --git a/migration/migration_pkg_test.go b/migration/migration_pkg_test.go index fbcc168a90..960541fc44 100644 --- a/migration/migration_pkg_test.go +++ b/migration/migration_pkg_test.go @@ -81,6 +81,7 @@ func TestRelocateContractStorageRootKeys(t *testing.T) { func TestRecalculateBloomFilters(t *testing.T) { testdb := pebble.NewMemTest(t) chain := blockchain.New(testdb, &utils.Mainnet) + defer chain.Close() client := feeder.NewTestClient(t, &utils.Mainnet) gw := adaptfeeder.New(client) @@ -165,6 +166,7 @@ func TestChangeTrieNodeEncoding(t *testing.T) { func TestCalculateBlockCommitments(t *testing.T) { testdb := pebble.NewMemTest(t) chain := blockchain.New(testdb, &utils.Mainnet) + defer chain.Close() client := feeder.NewTestClient(t, &utils.Mainnet) gw := adaptfeeder.New(client) diff --git a/node/node_test.go b/node/node_test.go index a9287e0e4e..e9f718cba6 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -74,6 +74,7 @@ func TestNetworkVerificationOnNonEmptyDB(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 250*time.Millisecond) require.NoError(t, syncer.Run(ctx)) cancel() + chain.Close() require.NoError(t, database.Close()) _, err = node.New(&node.Config{ diff --git a/p2p/starknet/handlers.go b/p2p/starknet/handlers.go index 28c4eee5f3..77b69349d2 100644 --- a/p2p/starknet/handlers.go +++ b/p2p/starknet/handlers.go @@ -1,4 +1,5 @@ -//go:generate protoc --go_out=./ --proto_path=./ --go_opt=Mp2p/proto/transaction.proto=./spec --go_opt=Mp2p/proto/state.proto=./spec --go_opt=Mp2p/proto/snapshot.proto=./spec --go_opt=Mp2p/proto/receipt.proto=./spec --go_opt=Mp2p/proto/mempool.proto=./spec --go_opt=Mp2p/proto/event.proto=./spec --go_opt=Mp2p/proto/block.proto=./spec --go_opt=Mp2p/proto/common.proto=./spec p2p/proto/transaction.proto p2p/proto/state.proto p2p/proto/snapshot.proto p2p/proto/common.proto p2p/proto/block.proto p2p/proto/event.proto p2p/proto/receipt.proto +//go:generate protoc --go_out=./ --proto_path=./ --go_opt=Mp2p/proto/common.proto=./spec --go_opt=Mp2p/proto/header.proto=./spec --go_opt=Mp2p/proto/event.proto=./spec --go_opt=Mp2p/proto/receipt.proto=./spec --go_opt=Mp2p/proto/state.proto=./spec --go_opt=Mp2p/proto/transaction.proto=./spec --go_opt=Mp2p/proto/class.proto=./spec --go_opt=Mp2p/proto/snapshot.proto=./spec p2p/proto/common.proto p2p/proto/event.proto p2p/proto/header.proto p2p/proto/receipt.proto p2p/proto/state.proto p2p/proto/transaction.proto p2p/proto/class.proto p2p/proto/snapshot.proto + package starknet import ( diff --git a/p2p/starknet/p2p/proto/class.proto b/p2p/starknet/p2p/proto/class.proto index 2cd3ed5265..f68235350c 100644 --- a/p2p/starknet/p2p/proto/class.proto +++ b/p2p/starknet/p2p/proto/class.proto @@ -55,4 +55,9 @@ message ClassesResponse { Class class = 1; Fin fin = 2; // Fin is sent after the peer sent all the data or when it encountered a block that it doesn't have its classes. } -} \ No newline at end of file +} + +message Classes { + uint32 domain = 1; + repeated Class classes = 2; +} diff --git a/p2p/starknet/p2p/proto/snapshot.proto b/p2p/starknet/p2p/proto/snapshot.proto new file mode 100644 index 0000000000..47b5ae12e0 --- /dev/null +++ b/p2p/starknet/p2p/proto/snapshot.proto @@ -0,0 +1,112 @@ +syntax = "proto3"; + +import "p2p/proto/common.proto"; +import "p2p/proto/state.proto"; +import "p2p/proto/class.proto"; + +message PatriciaNode { + message Edge { + uint32 length = 1; + Felt252 path = 2; // as bits of left/right + Felt252 value = 3; + } + message Binary { + Felt252 left = 1; + Felt252 right = 2; + } + + oneof node { + Edge edge = 1; + Binary binary = 2; + } +} + +// non leaf nodes required to build the trie given the range (leaves) +message PatriciaRangeProof { + repeated PatriciaNode nodes = 1; +} + +// leafs of the contract state tree +message ContractState { + Address address = 1; // the key + Hash class = 2; + Hash storage = 3; // patricia + uint64 nonce = 4; +} + +// request a range from the contract state tree that matches the given root (block) +// starts at 'start' and ends no more than 'end'. +// the result is (ContractRange+, PatriciaRangeProof)* +message ContractRangeRequest { + uint32 domain = 1; // volition + Hash state_root = 2; + Address start = 3; + Address end = 4; + uint32 chunks_per_proof = 5; // how many ContractRange items to send before sending a proof +} + +// stream of leaves in the contracts tree +message ContractRange { + repeated ContractState state = 1; +} + +message ContractRangeResponse { + optional Hash root = 1; // may not appear if Fin is sent to end the whole response + optional Hash contracts_root = 2;// may not appear if Fin is sent to end the whole response + optional Hash classes_root = 3;// may not appear if Fin is sent to end the whole response + oneof responses { + ContractRange range = 4; + Fin fin = 5; + } +} + +// duplicate of GetContractRange. Can introduce a 'type' instead. +// result is (Classes+, PatriciaRangeProof)* +message ClassRangeRequest { + Hash root = 1; + Hash start = 2; + Hash end = 3; + uint32 chunks_per_proof = 4; +} + +message ClassRangeResponse { + optional Hash root = 1; // may not appear if Fin is sent to end the whole response + optional Hash contracts_root = 2;// may not appear if Fin is sent to end the whole response + optional Hash classes_root = 3;// may not appear if Fin is sent to end the whole response + oneof responses { + Classes classes = 4; + Fin fin = 5; + } +} + +// A position in some contract's state tree is identified by the state tree's root and the key in it +message StorageLeafQuery { + Hash contract_storage_root = 1; + Felt252 key = 2; +} + +message StorageRangeQuery { + Address address = 3; + StorageLeafQuery start = 1; + StorageLeafQuery end = 2; +} + +// result is (ContractStorageRange+, PatriciaRangeProof)* +message ContractStorageRequest { + uint32 domain = 1; // volition + Hash state_root = 2; + uint32 chunks_per_proof = 5; // how many ContractRange items to send before sending a proof + repeated StorageRangeQuery query = 3; +} + +message ContractStorage { + repeated ContractStoredValue keyValue = 2; +} + +message ContractStorageResponse { + optional Hash state_root = 1; // may not appear if Fin is sent to end the whole response + oneof responses { + ContractStorage storage = 2; + Fin fin = 3; + } +} diff --git a/p2p/starknet/p2p/proto/state.proto b/p2p/starknet/p2p/proto/state.proto index 79f33fcafe..228897c242 100644 --- a/p2p/starknet/p2p/proto/state.proto +++ b/p2p/starknet/p2p/proto/state.proto @@ -34,4 +34,4 @@ message StateDiffsResponse { DeclaredClass declared_class = 2; Fin fin = 3; // Fin is sent after the peer sent all the data or when it encountered a block that it doesn't have its state diff. } -} \ No newline at end of file +} diff --git a/p2p/starknet/spec/class.pb.go b/p2p/starknet/spec/class.pb.go index c0d591d4ca..53389258a0 100644 --- a/p2p/starknet/spec/class.pb.go +++ b/p2p/starknet/spec/class.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.34.2 -// protoc v5.27.1 +// protoc v3.17.3 // source: p2p/proto/class.proto package spec @@ -570,6 +570,61 @@ func (*ClassesResponse_Class) isClassesResponse_ClassMessage() {} func (*ClassesResponse_Fin) isClassesResponse_ClassMessage() {} +type Classes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Domain uint32 `protobuf:"varint,1,opt,name=domain,proto3" json:"domain,omitempty"` + Classes []*Class `protobuf:"bytes,2,rep,name=classes,proto3" json:"classes,omitempty"` +} + +func (x *Classes) Reset() { + *x = Classes{} + if protoimpl.UnsafeEnabled { + mi := &file_p2p_proto_class_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Classes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Classes) ProtoMessage() {} + +func (x *Classes) ProtoReflect() protoreflect.Message { + mi := &file_p2p_proto_class_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Classes.ProtoReflect.Descriptor instead. +func (*Classes) Descriptor() ([]byte, []int) { + return file_p2p_proto_class_proto_rawDescGZIP(), []int{8} +} + +func (x *Classes) GetDomain() uint32 { + if x != nil { + return x.Domain + } + return 0 +} + +func (x *Classes) GetClasses() []*Class { + if x != nil { + return x.Classes + } + return nil +} + var File_p2p_proto_class_proto protoreflect.FileDescriptor var file_p2p_proto_class_proto_rawDesc = []byte{ @@ -640,10 +695,15 @@ var file_p2p_proto_class_proto_rawDesc = []byte{ 0x05, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x12, 0x18, 0x0a, 0x03, 0x66, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x04, 0x2e, 0x46, 0x69, 0x6e, 0x48, 0x00, 0x52, 0x03, 0x66, 0x69, 0x6e, 0x42, 0x0f, 0x0a, 0x0d, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x4e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x64, 0x45, 0x74, 0x68, 0x2f, 0x6a, 0x75, - 0x6e, 0x6f, 0x2f, 0x70, 0x32, 0x70, 0x2f, 0x73, 0x74, 0x61, 0x72, 0x6b, 0x6e, 0x65, 0x74, 0x2f, - 0x73, 0x70, 0x65, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x22, 0x43, 0x0a, 0x07, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x64, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x20, 0x0a, 0x07, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x06, 0x2e, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x52, 0x07, 0x63, + 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x64, 0x45, + 0x74, 0x68, 0x2f, 0x6a, 0x75, 0x6e, 0x6f, 0x2f, 0x70, 0x32, 0x70, 0x2f, 0x73, 0x74, 0x61, 0x72, + 0x6b, 0x6e, 0x65, 0x74, 0x2f, 0x73, 0x70, 0x65, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( @@ -658,7 +718,7 @@ func file_p2p_proto_class_proto_rawDescGZIP() []byte { return file_p2p_proto_class_proto_rawDescData } -var file_p2p_proto_class_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_p2p_proto_class_proto_msgTypes = make([]protoimpl.MessageInfo, 9) var file_p2p_proto_class_proto_goTypes = []any{ (*EntryPoint)(nil), // 0: EntryPoint (*Cairo0Class)(nil), // 1: Cairo0Class @@ -668,33 +728,35 @@ var file_p2p_proto_class_proto_goTypes = []any{ (*Class)(nil), // 5: Class (*ClassesRequest)(nil), // 6: ClassesRequest (*ClassesResponse)(nil), // 7: ClassesResponse - (*Felt252)(nil), // 8: Felt252 - (*Hash)(nil), // 9: Hash - (*Iteration)(nil), // 10: Iteration - (*Fin)(nil), // 11: Fin + (*Classes)(nil), // 8: Classes + (*Felt252)(nil), // 9: Felt252 + (*Hash)(nil), // 10: Hash + (*Iteration)(nil), // 11: Iteration + (*Fin)(nil), // 12: Fin } var file_p2p_proto_class_proto_depIdxs = []int32{ - 8, // 0: EntryPoint.selector:type_name -> Felt252 + 9, // 0: EntryPoint.selector:type_name -> Felt252 0, // 1: Cairo0Class.externals:type_name -> EntryPoint 0, // 2: Cairo0Class.l1_handlers:type_name -> EntryPoint 0, // 3: Cairo0Class.constructors:type_name -> EntryPoint - 8, // 4: SierraEntryPoint.selector:type_name -> Felt252 + 9, // 4: SierraEntryPoint.selector:type_name -> Felt252 2, // 5: Cairo1EntryPoints.externals:type_name -> SierraEntryPoint 2, // 6: Cairo1EntryPoints.l1_handlers:type_name -> SierraEntryPoint 2, // 7: Cairo1EntryPoints.constructors:type_name -> SierraEntryPoint 3, // 8: Cairo1Class.entry_points:type_name -> Cairo1EntryPoints - 8, // 9: Cairo1Class.program:type_name -> Felt252 + 9, // 9: Cairo1Class.program:type_name -> Felt252 1, // 10: Class.cairo0:type_name -> Cairo0Class 4, // 11: Class.cairo1:type_name -> Cairo1Class - 9, // 12: Class.class_hash:type_name -> Hash - 10, // 13: ClassesRequest.iteration:type_name -> Iteration + 10, // 12: Class.class_hash:type_name -> Hash + 11, // 13: ClassesRequest.iteration:type_name -> Iteration 5, // 14: ClassesResponse.class:type_name -> Class - 11, // 15: ClassesResponse.fin:type_name -> Fin - 16, // [16:16] is the sub-list for method output_type - 16, // [16:16] is the sub-list for method input_type - 16, // [16:16] is the sub-list for extension type_name - 16, // [16:16] is the sub-list for extension extendee - 0, // [0:16] is the sub-list for field type_name + 12, // 15: ClassesResponse.fin:type_name -> Fin + 5, // 16: Classes.classes:type_name -> Class + 17, // [17:17] is the sub-list for method output_type + 17, // [17:17] is the sub-list for method input_type + 17, // [17:17] is the sub-list for extension type_name + 17, // [17:17] is the sub-list for extension extendee + 0, // [0:17] is the sub-list for field type_name } func init() { file_p2p_proto_class_proto_init() } @@ -800,6 +862,18 @@ func file_p2p_proto_class_proto_init() { return nil } } + file_p2p_proto_class_proto_msgTypes[8].Exporter = func(v any, i int) any { + switch v := v.(*Classes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_p2p_proto_class_proto_msgTypes[5].OneofWrappers = []any{ (*Class_Cairo0)(nil), @@ -815,7 +889,7 @@ func file_p2p_proto_class_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_p2p_proto_class_proto_rawDesc, NumEnums: 0, - NumMessages: 8, + NumMessages: 9, NumExtensions: 0, NumServices: 0, }, diff --git a/p2p/starknet/spec/common.pb.go b/p2p/starknet/spec/common.pb.go index 25aedb5463..1b18c7c9c7 100644 --- a/p2p/starknet/spec/common.pb.go +++ b/p2p/starknet/spec/common.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.34.2 -// protoc v5.27.1 +// protoc v3.17.3 // source: p2p/proto/common.proto package spec diff --git a/p2p/starknet/spec/event.pb.go b/p2p/starknet/spec/event.pb.go index 8543465c35..5b559e259c 100644 --- a/p2p/starknet/spec/event.pb.go +++ b/p2p/starknet/spec/event.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.34.2 -// protoc v5.27.1 +// protoc v3.17.3 // source: p2p/proto/event.proto package spec diff --git a/p2p/starknet/spec/header.pb.go b/p2p/starknet/spec/header.pb.go index 5d94c6211b..49a8280b36 100644 --- a/p2p/starknet/spec/header.pb.go +++ b/p2p/starknet/spec/header.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.34.2 -// protoc v5.27.1 +// protoc v3.17.3 // source: p2p/proto/header.proto package spec diff --git a/p2p/starknet/spec/receipt.pb.go b/p2p/starknet/spec/receipt.pb.go index d3c83c97ff..5e29680442 100644 --- a/p2p/starknet/spec/receipt.pb.go +++ b/p2p/starknet/spec/receipt.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.34.2 -// protoc v5.27.1 +// protoc v3.17.3 // source: p2p/proto/receipt.proto package spec diff --git a/p2p/starknet/spec/snapshot.pb.go b/p2p/starknet/spec/snapshot.pb.go new file mode 100644 index 0000000000..8c89f0bdc2 --- /dev/null +++ b/p2p/starknet/spec/snapshot.pb.go @@ -0,0 +1,1530 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v3.17.3 +// source: p2p/proto/snapshot.proto + +package spec + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type PatriciaNode struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Node: + // + // *PatriciaNode_Edge_ + // *PatriciaNode_Binary_ + Node isPatriciaNode_Node `protobuf_oneof:"node"` +} + +func (x *PatriciaNode) Reset() { + *x = PatriciaNode{} + if protoimpl.UnsafeEnabled { + mi := &file_p2p_proto_snapshot_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PatriciaNode) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PatriciaNode) ProtoMessage() {} + +func (x *PatriciaNode) ProtoReflect() protoreflect.Message { + mi := &file_p2p_proto_snapshot_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PatriciaNode.ProtoReflect.Descriptor instead. +func (*PatriciaNode) Descriptor() ([]byte, []int) { + return file_p2p_proto_snapshot_proto_rawDescGZIP(), []int{0} +} + +func (m *PatriciaNode) GetNode() isPatriciaNode_Node { + if m != nil { + return m.Node + } + return nil +} + +func (x *PatriciaNode) GetEdge() *PatriciaNode_Edge { + if x, ok := x.GetNode().(*PatriciaNode_Edge_); ok { + return x.Edge + } + return nil +} + +func (x *PatriciaNode) GetBinary() *PatriciaNode_Binary { + if x, ok := x.GetNode().(*PatriciaNode_Binary_); ok { + return x.Binary + } + return nil +} + +type isPatriciaNode_Node interface { + isPatriciaNode_Node() +} + +type PatriciaNode_Edge_ struct { + Edge *PatriciaNode_Edge `protobuf:"bytes,1,opt,name=edge,proto3,oneof"` +} + +type PatriciaNode_Binary_ struct { + Binary *PatriciaNode_Binary `protobuf:"bytes,2,opt,name=binary,proto3,oneof"` +} + +func (*PatriciaNode_Edge_) isPatriciaNode_Node() {} + +func (*PatriciaNode_Binary_) isPatriciaNode_Node() {} + +// non leaf nodes required to build the trie given the range (leaves) +type PatriciaRangeProof struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Nodes []*PatriciaNode `protobuf:"bytes,1,rep,name=nodes,proto3" json:"nodes,omitempty"` +} + +func (x *PatriciaRangeProof) Reset() { + *x = PatriciaRangeProof{} + if protoimpl.UnsafeEnabled { + mi := &file_p2p_proto_snapshot_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PatriciaRangeProof) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PatriciaRangeProof) ProtoMessage() {} + +func (x *PatriciaRangeProof) ProtoReflect() protoreflect.Message { + mi := &file_p2p_proto_snapshot_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PatriciaRangeProof.ProtoReflect.Descriptor instead. +func (*PatriciaRangeProof) Descriptor() ([]byte, []int) { + return file_p2p_proto_snapshot_proto_rawDescGZIP(), []int{1} +} + +func (x *PatriciaRangeProof) GetNodes() []*PatriciaNode { + if x != nil { + return x.Nodes + } + return nil +} + +// leafs of the contract state tree +type ContractState struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address *Address `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` // the key + Class *Hash `protobuf:"bytes,2,opt,name=class,proto3" json:"class,omitempty"` + Storage *Hash `protobuf:"bytes,3,opt,name=storage,proto3" json:"storage,omitempty"` // patricia + Nonce uint64 `protobuf:"varint,4,opt,name=nonce,proto3" json:"nonce,omitempty"` +} + +func (x *ContractState) Reset() { + *x = ContractState{} + if protoimpl.UnsafeEnabled { + mi := &file_p2p_proto_snapshot_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ContractState) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ContractState) ProtoMessage() {} + +func (x *ContractState) ProtoReflect() protoreflect.Message { + mi := &file_p2p_proto_snapshot_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ContractState.ProtoReflect.Descriptor instead. +func (*ContractState) Descriptor() ([]byte, []int) { + return file_p2p_proto_snapshot_proto_rawDescGZIP(), []int{2} +} + +func (x *ContractState) GetAddress() *Address { + if x != nil { + return x.Address + } + return nil +} + +func (x *ContractState) GetClass() *Hash { + if x != nil { + return x.Class + } + return nil +} + +func (x *ContractState) GetStorage() *Hash { + if x != nil { + return x.Storage + } + return nil +} + +func (x *ContractState) GetNonce() uint64 { + if x != nil { + return x.Nonce + } + return 0 +} + +// request a range from the contract state tree that matches the given root (block) +// starts at 'start' and ends no more than 'end'. +// the result is (ContractRange+, PatriciaRangeProof)* +type ContractRangeRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Domain uint32 `protobuf:"varint,1,opt,name=domain,proto3" json:"domain,omitempty"` // volition + StateRoot *Hash `protobuf:"bytes,2,opt,name=state_root,json=stateRoot,proto3" json:"state_root,omitempty"` + Start *Address `protobuf:"bytes,3,opt,name=start,proto3" json:"start,omitempty"` + End *Address `protobuf:"bytes,4,opt,name=end,proto3" json:"end,omitempty"` + ChunksPerProof uint32 `protobuf:"varint,5,opt,name=chunks_per_proof,json=chunksPerProof,proto3" json:"chunks_per_proof,omitempty"` // how many ContractRange items to send before sending a proof +} + +func (x *ContractRangeRequest) Reset() { + *x = ContractRangeRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_p2p_proto_snapshot_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ContractRangeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ContractRangeRequest) ProtoMessage() {} + +func (x *ContractRangeRequest) ProtoReflect() protoreflect.Message { + mi := &file_p2p_proto_snapshot_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ContractRangeRequest.ProtoReflect.Descriptor instead. +func (*ContractRangeRequest) Descriptor() ([]byte, []int) { + return file_p2p_proto_snapshot_proto_rawDescGZIP(), []int{3} +} + +func (x *ContractRangeRequest) GetDomain() uint32 { + if x != nil { + return x.Domain + } + return 0 +} + +func (x *ContractRangeRequest) GetStateRoot() *Hash { + if x != nil { + return x.StateRoot + } + return nil +} + +func (x *ContractRangeRequest) GetStart() *Address { + if x != nil { + return x.Start + } + return nil +} + +func (x *ContractRangeRequest) GetEnd() *Address { + if x != nil { + return x.End + } + return nil +} + +func (x *ContractRangeRequest) GetChunksPerProof() uint32 { + if x != nil { + return x.ChunksPerProof + } + return 0 +} + +// stream of leaves in the contracts tree +type ContractRange struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + State []*ContractState `protobuf:"bytes,1,rep,name=state,proto3" json:"state,omitempty"` +} + +func (x *ContractRange) Reset() { + *x = ContractRange{} + if protoimpl.UnsafeEnabled { + mi := &file_p2p_proto_snapshot_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ContractRange) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ContractRange) ProtoMessage() {} + +func (x *ContractRange) ProtoReflect() protoreflect.Message { + mi := &file_p2p_proto_snapshot_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ContractRange.ProtoReflect.Descriptor instead. +func (*ContractRange) Descriptor() ([]byte, []int) { + return file_p2p_proto_snapshot_proto_rawDescGZIP(), []int{4} +} + +func (x *ContractRange) GetState() []*ContractState { + if x != nil { + return x.State + } + return nil +} + +type ContractRangeResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Root *Hash `protobuf:"bytes,1,opt,name=root,proto3,oneof" json:"root,omitempty"` // may not appear if Fin is sent to end the whole response + ContractsRoot *Hash `protobuf:"bytes,2,opt,name=contracts_root,json=contractsRoot,proto3,oneof" json:"contracts_root,omitempty"` // may not appear if Fin is sent to end the whole response + ClassesRoot *Hash `protobuf:"bytes,3,opt,name=classes_root,json=classesRoot,proto3,oneof" json:"classes_root,omitempty"` // may not appear if Fin is sent to end the whole response + // Types that are assignable to Responses: + // + // *ContractRangeResponse_Range + // *ContractRangeResponse_Fin + Responses isContractRangeResponse_Responses `protobuf_oneof:"responses"` +} + +func (x *ContractRangeResponse) Reset() { + *x = ContractRangeResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_p2p_proto_snapshot_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ContractRangeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ContractRangeResponse) ProtoMessage() {} + +func (x *ContractRangeResponse) ProtoReflect() protoreflect.Message { + mi := &file_p2p_proto_snapshot_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ContractRangeResponse.ProtoReflect.Descriptor instead. +func (*ContractRangeResponse) Descriptor() ([]byte, []int) { + return file_p2p_proto_snapshot_proto_rawDescGZIP(), []int{5} +} + +func (x *ContractRangeResponse) GetRoot() *Hash { + if x != nil { + return x.Root + } + return nil +} + +func (x *ContractRangeResponse) GetContractsRoot() *Hash { + if x != nil { + return x.ContractsRoot + } + return nil +} + +func (x *ContractRangeResponse) GetClassesRoot() *Hash { + if x != nil { + return x.ClassesRoot + } + return nil +} + +func (m *ContractRangeResponse) GetResponses() isContractRangeResponse_Responses { + if m != nil { + return m.Responses + } + return nil +} + +func (x *ContractRangeResponse) GetRange() *ContractRange { + if x, ok := x.GetResponses().(*ContractRangeResponse_Range); ok { + return x.Range + } + return nil +} + +func (x *ContractRangeResponse) GetFin() *Fin { + if x, ok := x.GetResponses().(*ContractRangeResponse_Fin); ok { + return x.Fin + } + return nil +} + +type isContractRangeResponse_Responses interface { + isContractRangeResponse_Responses() +} + +type ContractRangeResponse_Range struct { + Range *ContractRange `protobuf:"bytes,4,opt,name=range,proto3,oneof"` +} + +type ContractRangeResponse_Fin struct { + Fin *Fin `protobuf:"bytes,5,opt,name=fin,proto3,oneof"` +} + +func (*ContractRangeResponse_Range) isContractRangeResponse_Responses() {} + +func (*ContractRangeResponse_Fin) isContractRangeResponse_Responses() {} + +// duplicate of GetContractRange. Can introduce a 'type' instead. +// result is (Classes+, PatriciaRangeProof)* +type ClassRangeRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Root *Hash `protobuf:"bytes,1,opt,name=root,proto3" json:"root,omitempty"` + Start *Hash `protobuf:"bytes,2,opt,name=start,proto3" json:"start,omitempty"` + End *Hash `protobuf:"bytes,3,opt,name=end,proto3" json:"end,omitempty"` + ChunksPerProof uint32 `protobuf:"varint,4,opt,name=chunks_per_proof,json=chunksPerProof,proto3" json:"chunks_per_proof,omitempty"` +} + +func (x *ClassRangeRequest) Reset() { + *x = ClassRangeRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_p2p_proto_snapshot_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ClassRangeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClassRangeRequest) ProtoMessage() {} + +func (x *ClassRangeRequest) ProtoReflect() protoreflect.Message { + mi := &file_p2p_proto_snapshot_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClassRangeRequest.ProtoReflect.Descriptor instead. +func (*ClassRangeRequest) Descriptor() ([]byte, []int) { + return file_p2p_proto_snapshot_proto_rawDescGZIP(), []int{6} +} + +func (x *ClassRangeRequest) GetRoot() *Hash { + if x != nil { + return x.Root + } + return nil +} + +func (x *ClassRangeRequest) GetStart() *Hash { + if x != nil { + return x.Start + } + return nil +} + +func (x *ClassRangeRequest) GetEnd() *Hash { + if x != nil { + return x.End + } + return nil +} + +func (x *ClassRangeRequest) GetChunksPerProof() uint32 { + if x != nil { + return x.ChunksPerProof + } + return 0 +} + +type ClassRangeResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Root *Hash `protobuf:"bytes,1,opt,name=root,proto3,oneof" json:"root,omitempty"` // may not appear if Fin is sent to end the whole response + ContractsRoot *Hash `protobuf:"bytes,2,opt,name=contracts_root,json=contractsRoot,proto3,oneof" json:"contracts_root,omitempty"` // may not appear if Fin is sent to end the whole response + ClassesRoot *Hash `protobuf:"bytes,3,opt,name=classes_root,json=classesRoot,proto3,oneof" json:"classes_root,omitempty"` // may not appear if Fin is sent to end the whole response + // Types that are assignable to Responses: + // + // *ClassRangeResponse_Classes + // *ClassRangeResponse_Fin + Responses isClassRangeResponse_Responses `protobuf_oneof:"responses"` +} + +func (x *ClassRangeResponse) Reset() { + *x = ClassRangeResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_p2p_proto_snapshot_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ClassRangeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClassRangeResponse) ProtoMessage() {} + +func (x *ClassRangeResponse) ProtoReflect() protoreflect.Message { + mi := &file_p2p_proto_snapshot_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClassRangeResponse.ProtoReflect.Descriptor instead. +func (*ClassRangeResponse) Descriptor() ([]byte, []int) { + return file_p2p_proto_snapshot_proto_rawDescGZIP(), []int{7} +} + +func (x *ClassRangeResponse) GetRoot() *Hash { + if x != nil { + return x.Root + } + return nil +} + +func (x *ClassRangeResponse) GetContractsRoot() *Hash { + if x != nil { + return x.ContractsRoot + } + return nil +} + +func (x *ClassRangeResponse) GetClassesRoot() *Hash { + if x != nil { + return x.ClassesRoot + } + return nil +} + +func (m *ClassRangeResponse) GetResponses() isClassRangeResponse_Responses { + if m != nil { + return m.Responses + } + return nil +} + +func (x *ClassRangeResponse) GetClasses() *Classes { + if x, ok := x.GetResponses().(*ClassRangeResponse_Classes); ok { + return x.Classes + } + return nil +} + +func (x *ClassRangeResponse) GetFin() *Fin { + if x, ok := x.GetResponses().(*ClassRangeResponse_Fin); ok { + return x.Fin + } + return nil +} + +type isClassRangeResponse_Responses interface { + isClassRangeResponse_Responses() +} + +type ClassRangeResponse_Classes struct { + Classes *Classes `protobuf:"bytes,4,opt,name=classes,proto3,oneof"` +} + +type ClassRangeResponse_Fin struct { + Fin *Fin `protobuf:"bytes,5,opt,name=fin,proto3,oneof"` +} + +func (*ClassRangeResponse_Classes) isClassRangeResponse_Responses() {} + +func (*ClassRangeResponse_Fin) isClassRangeResponse_Responses() {} + +// A position in some contract's state tree is identified by the state tree's root and the key in it +type StorageLeafQuery struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ContractStorageRoot *Hash `protobuf:"bytes,1,opt,name=contract_storage_root,json=contractStorageRoot,proto3" json:"contract_storage_root,omitempty"` + Key *Felt252 `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` +} + +func (x *StorageLeafQuery) Reset() { + *x = StorageLeafQuery{} + if protoimpl.UnsafeEnabled { + mi := &file_p2p_proto_snapshot_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StorageLeafQuery) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StorageLeafQuery) ProtoMessage() {} + +func (x *StorageLeafQuery) ProtoReflect() protoreflect.Message { + mi := &file_p2p_proto_snapshot_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StorageLeafQuery.ProtoReflect.Descriptor instead. +func (*StorageLeafQuery) Descriptor() ([]byte, []int) { + return file_p2p_proto_snapshot_proto_rawDescGZIP(), []int{8} +} + +func (x *StorageLeafQuery) GetContractStorageRoot() *Hash { + if x != nil { + return x.ContractStorageRoot + } + return nil +} + +func (x *StorageLeafQuery) GetKey() *Felt252 { + if x != nil { + return x.Key + } + return nil +} + +type StorageRangeQuery struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address *Address `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` + Start *StorageLeafQuery `protobuf:"bytes,1,opt,name=start,proto3" json:"start,omitempty"` + End *StorageLeafQuery `protobuf:"bytes,2,opt,name=end,proto3" json:"end,omitempty"` +} + +func (x *StorageRangeQuery) Reset() { + *x = StorageRangeQuery{} + if protoimpl.UnsafeEnabled { + mi := &file_p2p_proto_snapshot_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StorageRangeQuery) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StorageRangeQuery) ProtoMessage() {} + +func (x *StorageRangeQuery) ProtoReflect() protoreflect.Message { + mi := &file_p2p_proto_snapshot_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StorageRangeQuery.ProtoReflect.Descriptor instead. +func (*StorageRangeQuery) Descriptor() ([]byte, []int) { + return file_p2p_proto_snapshot_proto_rawDescGZIP(), []int{9} +} + +func (x *StorageRangeQuery) GetAddress() *Address { + if x != nil { + return x.Address + } + return nil +} + +func (x *StorageRangeQuery) GetStart() *StorageLeafQuery { + if x != nil { + return x.Start + } + return nil +} + +func (x *StorageRangeQuery) GetEnd() *StorageLeafQuery { + if x != nil { + return x.End + } + return nil +} + +// result is (ContractStorageRange+, PatriciaRangeProof)* +type ContractStorageRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Domain uint32 `protobuf:"varint,1,opt,name=domain,proto3" json:"domain,omitempty"` // volition + StateRoot *Hash `protobuf:"bytes,2,opt,name=state_root,json=stateRoot,proto3" json:"state_root,omitempty"` + ChunksPerProof uint32 `protobuf:"varint,5,opt,name=chunks_per_proof,json=chunksPerProof,proto3" json:"chunks_per_proof,omitempty"` // how many ContractRange items to send before sending a proof + Query []*StorageRangeQuery `protobuf:"bytes,3,rep,name=query,proto3" json:"query,omitempty"` +} + +func (x *ContractStorageRequest) Reset() { + *x = ContractStorageRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_p2p_proto_snapshot_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ContractStorageRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ContractStorageRequest) ProtoMessage() {} + +func (x *ContractStorageRequest) ProtoReflect() protoreflect.Message { + mi := &file_p2p_proto_snapshot_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ContractStorageRequest.ProtoReflect.Descriptor instead. +func (*ContractStorageRequest) Descriptor() ([]byte, []int) { + return file_p2p_proto_snapshot_proto_rawDescGZIP(), []int{10} +} + +func (x *ContractStorageRequest) GetDomain() uint32 { + if x != nil { + return x.Domain + } + return 0 +} + +func (x *ContractStorageRequest) GetStateRoot() *Hash { + if x != nil { + return x.StateRoot + } + return nil +} + +func (x *ContractStorageRequest) GetChunksPerProof() uint32 { + if x != nil { + return x.ChunksPerProof + } + return 0 +} + +func (x *ContractStorageRequest) GetQuery() []*StorageRangeQuery { + if x != nil { + return x.Query + } + return nil +} + +type ContractStorage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + KeyValue []*ContractStoredValue `protobuf:"bytes,2,rep,name=keyValue,proto3" json:"keyValue,omitempty"` +} + +func (x *ContractStorage) Reset() { + *x = ContractStorage{} + if protoimpl.UnsafeEnabled { + mi := &file_p2p_proto_snapshot_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ContractStorage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ContractStorage) ProtoMessage() {} + +func (x *ContractStorage) ProtoReflect() protoreflect.Message { + mi := &file_p2p_proto_snapshot_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ContractStorage.ProtoReflect.Descriptor instead. +func (*ContractStorage) Descriptor() ([]byte, []int) { + return file_p2p_proto_snapshot_proto_rawDescGZIP(), []int{11} +} + +func (x *ContractStorage) GetKeyValue() []*ContractStoredValue { + if x != nil { + return x.KeyValue + } + return nil +} + +type ContractStorageResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + StateRoot *Hash `protobuf:"bytes,1,opt,name=state_root,json=stateRoot,proto3,oneof" json:"state_root,omitempty"` // may not appear if Fin is sent to end the whole response + // Types that are assignable to Responses: + // + // *ContractStorageResponse_Storage + // *ContractStorageResponse_Fin + Responses isContractStorageResponse_Responses `protobuf_oneof:"responses"` +} + +func (x *ContractStorageResponse) Reset() { + *x = ContractStorageResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_p2p_proto_snapshot_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ContractStorageResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ContractStorageResponse) ProtoMessage() {} + +func (x *ContractStorageResponse) ProtoReflect() protoreflect.Message { + mi := &file_p2p_proto_snapshot_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ContractStorageResponse.ProtoReflect.Descriptor instead. +func (*ContractStorageResponse) Descriptor() ([]byte, []int) { + return file_p2p_proto_snapshot_proto_rawDescGZIP(), []int{12} +} + +func (x *ContractStorageResponse) GetStateRoot() *Hash { + if x != nil { + return x.StateRoot + } + return nil +} + +func (m *ContractStorageResponse) GetResponses() isContractStorageResponse_Responses { + if m != nil { + return m.Responses + } + return nil +} + +func (x *ContractStorageResponse) GetStorage() *ContractStorage { + if x, ok := x.GetResponses().(*ContractStorageResponse_Storage); ok { + return x.Storage + } + return nil +} + +func (x *ContractStorageResponse) GetFin() *Fin { + if x, ok := x.GetResponses().(*ContractStorageResponse_Fin); ok { + return x.Fin + } + return nil +} + +type isContractStorageResponse_Responses interface { + isContractStorageResponse_Responses() +} + +type ContractStorageResponse_Storage struct { + Storage *ContractStorage `protobuf:"bytes,2,opt,name=storage,proto3,oneof"` +} + +type ContractStorageResponse_Fin struct { + Fin *Fin `protobuf:"bytes,3,opt,name=fin,proto3,oneof"` +} + +func (*ContractStorageResponse_Storage) isContractStorageResponse_Responses() {} + +func (*ContractStorageResponse_Fin) isContractStorageResponse_Responses() {} + +type PatriciaNode_Edge struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Length uint32 `protobuf:"varint,1,opt,name=length,proto3" json:"length,omitempty"` + Path *Felt252 `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` // as bits of left/right + Value *Felt252 `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *PatriciaNode_Edge) Reset() { + *x = PatriciaNode_Edge{} + if protoimpl.UnsafeEnabled { + mi := &file_p2p_proto_snapshot_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PatriciaNode_Edge) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PatriciaNode_Edge) ProtoMessage() {} + +func (x *PatriciaNode_Edge) ProtoReflect() protoreflect.Message { + mi := &file_p2p_proto_snapshot_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PatriciaNode_Edge.ProtoReflect.Descriptor instead. +func (*PatriciaNode_Edge) Descriptor() ([]byte, []int) { + return file_p2p_proto_snapshot_proto_rawDescGZIP(), []int{0, 0} +} + +func (x *PatriciaNode_Edge) GetLength() uint32 { + if x != nil { + return x.Length + } + return 0 +} + +func (x *PatriciaNode_Edge) GetPath() *Felt252 { + if x != nil { + return x.Path + } + return nil +} + +func (x *PatriciaNode_Edge) GetValue() *Felt252 { + if x != nil { + return x.Value + } + return nil +} + +type PatriciaNode_Binary struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Left *Felt252 `protobuf:"bytes,1,opt,name=left,proto3" json:"left,omitempty"` + Right *Felt252 `protobuf:"bytes,2,opt,name=right,proto3" json:"right,omitempty"` +} + +func (x *PatriciaNode_Binary) Reset() { + *x = PatriciaNode_Binary{} + if protoimpl.UnsafeEnabled { + mi := &file_p2p_proto_snapshot_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PatriciaNode_Binary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PatriciaNode_Binary) ProtoMessage() {} + +func (x *PatriciaNode_Binary) ProtoReflect() protoreflect.Message { + mi := &file_p2p_proto_snapshot_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PatriciaNode_Binary.ProtoReflect.Descriptor instead. +func (*PatriciaNode_Binary) Descriptor() ([]byte, []int) { + return file_p2p_proto_snapshot_proto_rawDescGZIP(), []int{0, 1} +} + +func (x *PatriciaNode_Binary) GetLeft() *Felt252 { + if x != nil { + return x.Left + } + return nil +} + +func (x *PatriciaNode_Binary) GetRight() *Felt252 { + if x != nil { + return x.Right + } + return nil +} + +var File_p2p_proto_snapshot_proto protoreflect.FileDescriptor + +var file_p2p_proto_snapshot_proto_rawDesc = []byte{ + 0x0a, 0x18, 0x70, 0x32, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x6e, 0x61, 0x70, + 0x73, 0x68, 0x6f, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x16, 0x70, 0x32, 0x70, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x1a, 0x15, 0x70, 0x32, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x74, + 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x15, 0x70, 0x32, 0x70, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x22, 0x96, 0x02, 0x0a, 0x0c, 0x50, 0x61, 0x74, 0x72, 0x69, 0x63, 0x69, 0x61, 0x4e, 0x6f, 0x64, + 0x65, 0x12, 0x28, 0x0a, 0x04, 0x65, 0x64, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x12, 0x2e, 0x50, 0x61, 0x74, 0x72, 0x69, 0x63, 0x69, 0x61, 0x4e, 0x6f, 0x64, 0x65, 0x2e, 0x45, + 0x64, 0x67, 0x65, 0x48, 0x00, 0x52, 0x04, 0x65, 0x64, 0x67, 0x65, 0x12, 0x2e, 0x0a, 0x06, 0x62, + 0x69, 0x6e, 0x61, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x50, 0x61, + 0x74, 0x72, 0x69, 0x63, 0x69, 0x61, 0x4e, 0x6f, 0x64, 0x65, 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x72, + 0x79, 0x48, 0x00, 0x52, 0x06, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x1a, 0x5c, 0x0a, 0x04, 0x45, + 0x64, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x1c, 0x0a, 0x04, 0x70, + 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x46, 0x65, 0x6c, 0x74, + 0x32, 0x35, 0x32, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1e, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x46, 0x65, 0x6c, 0x74, 0x32, + 0x35, 0x32, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x46, 0x0a, 0x06, 0x42, 0x69, 0x6e, + 0x61, 0x72, 0x79, 0x12, 0x1c, 0x0a, 0x04, 0x6c, 0x65, 0x66, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x08, 0x2e, 0x46, 0x65, 0x6c, 0x74, 0x32, 0x35, 0x32, 0x52, 0x04, 0x6c, 0x65, 0x66, + 0x74, 0x12, 0x1e, 0x0a, 0x05, 0x72, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x08, 0x2e, 0x46, 0x65, 0x6c, 0x74, 0x32, 0x35, 0x32, 0x52, 0x05, 0x72, 0x69, 0x67, 0x68, + 0x74, 0x42, 0x06, 0x0a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x22, 0x39, 0x0a, 0x12, 0x50, 0x61, 0x74, + 0x72, 0x69, 0x63, 0x69, 0x61, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x12, + 0x23, 0x0a, 0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, + 0x2e, 0x50, 0x61, 0x74, 0x72, 0x69, 0x63, 0x69, 0x61, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x05, 0x6e, + 0x6f, 0x64, 0x65, 0x73, 0x22, 0x87, 0x01, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, + 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x05, 0x63, 0x6c, + 0x61, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, 0x68, + 0x52, 0x05, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x12, 0x1f, 0x0a, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x61, + 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x52, + 0x07, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x22, 0xba, + 0x01, 0x0a, 0x14, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, + 0x24, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x52, 0x09, 0x73, 0x74, 0x61, 0x74, + 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x1e, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x05, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x1a, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x03, 0x65, 0x6e, + 0x64, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, + 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x63, 0x68, 0x75, + 0x6e, 0x6b, 0x73, 0x50, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x22, 0x35, 0x0a, 0x0d, 0x43, + 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x24, 0x0a, 0x05, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x43, 0x6f, + 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, + 0x74, 0x65, 0x22, 0x95, 0x02, 0x0a, 0x15, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x52, + 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x04, + 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, + 0x68, 0x48, 0x01, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, 0x0e, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x73, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x48, 0x02, 0x52, 0x0d, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x73, 0x52, 0x6f, 0x6f, 0x74, 0x88, 0x01, 0x01, 0x12, + 0x2d, 0x0a, 0x0c, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x48, 0x03, 0x52, 0x0b, + 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x52, 0x6f, 0x6f, 0x74, 0x88, 0x01, 0x01, 0x12, 0x26, + 0x0a, 0x05, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, + 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x48, 0x00, 0x52, + 0x05, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x03, 0x66, 0x69, 0x6e, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x04, 0x2e, 0x46, 0x69, 0x6e, 0x48, 0x00, 0x52, 0x03, 0x66, 0x69, 0x6e, + 0x42, 0x0b, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x42, 0x07, 0x0a, + 0x05, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x61, 0x63, 0x74, 0x73, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x63, 0x6c, + 0x61, 0x73, 0x73, 0x65, 0x73, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x22, 0x8e, 0x01, 0x0a, 0x11, 0x43, + 0x6c, 0x61, 0x73, 0x73, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x19, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, + 0x2e, 0x48, 0x61, 0x73, 0x68, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x12, 0x1b, 0x0a, 0x05, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, + 0x68, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x52, 0x03, 0x65, 0x6e, + 0x64, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, + 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x63, 0x68, 0x75, + 0x6e, 0x6b, 0x73, 0x50, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x22, 0x90, 0x02, 0x0a, 0x12, + 0x43, 0x6c, 0x61, 0x73, 0x73, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x48, 0x01, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x88, + 0x01, 0x01, 0x12, 0x31, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x73, 0x5f, + 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, + 0x68, 0x48, 0x02, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x73, 0x52, 0x6f, + 0x6f, 0x74, 0x88, 0x01, 0x01, 0x12, 0x2d, 0x0a, 0x0c, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, + 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, + 0x73, 0x68, 0x48, 0x03, 0x52, 0x0b, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x52, 0x6f, 0x6f, + 0x74, 0x88, 0x01, 0x01, 0x12, 0x24, 0x0a, 0x07, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x48, + 0x00, 0x52, 0x07, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x03, 0x66, 0x69, + 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x04, 0x2e, 0x46, 0x69, 0x6e, 0x48, 0x00, 0x52, + 0x03, 0x66, 0x69, 0x6e, 0x42, 0x0b, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x73, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x73, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x42, 0x0f, 0x0a, + 0x0d, 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x22, 0x69, + 0x0a, 0x10, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4c, 0x65, 0x61, 0x66, 0x51, 0x75, 0x65, + 0x72, 0x79, 0x12, 0x39, 0x0a, 0x15, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x5f, 0x73, + 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x52, 0x13, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, + 0x63, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x1a, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x46, 0x65, 0x6c, + 0x74, 0x32, 0x35, 0x32, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x85, 0x01, 0x0a, 0x11, 0x53, 0x74, + 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, + 0x22, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x08, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x12, 0x27, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4c, 0x65, 0x61, 0x66, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x23, 0x0a, 0x03, + 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x53, 0x74, 0x6f, 0x72, + 0x61, 0x67, 0x65, 0x4c, 0x65, 0x61, 0x66, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x03, 0x65, 0x6e, + 0x64, 0x22, 0xaa, 0x01, 0x0a, 0x16, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x53, 0x74, + 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x64, 0x6f, + 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x24, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x6f, + 0x6f, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x52, + 0x09, 0x73, 0x74, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x68, + 0x75, 0x6e, 0x6b, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x50, 0x65, 0x72, 0x50, + 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x28, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x61, 0x6e, + 0x67, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x43, + 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, + 0x65, 0x12, 0x30, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x53, 0x74, + 0x6f, 0x72, 0x65, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x22, 0xa8, 0x01, 0x0a, 0x17, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, + 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x29, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x48, 0x01, 0x52, 0x09, 0x73, 0x74, + 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x88, 0x01, 0x01, 0x12, 0x2c, 0x0a, 0x07, 0x73, 0x74, + 0x6f, 0x72, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x43, 0x6f, + 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, + 0x07, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x03, 0x66, 0x69, 0x6e, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x04, 0x2e, 0x46, 0x69, 0x6e, 0x48, 0x00, 0x52, 0x03, 0x66, + 0x69, 0x6e, 0x42, 0x0b, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x42, + 0x0d, 0x0a, 0x0b, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_p2p_proto_snapshot_proto_rawDescOnce sync.Once + file_p2p_proto_snapshot_proto_rawDescData = file_p2p_proto_snapshot_proto_rawDesc +) + +func file_p2p_proto_snapshot_proto_rawDescGZIP() []byte { + file_p2p_proto_snapshot_proto_rawDescOnce.Do(func() { + file_p2p_proto_snapshot_proto_rawDescData = protoimpl.X.CompressGZIP(file_p2p_proto_snapshot_proto_rawDescData) + }) + return file_p2p_proto_snapshot_proto_rawDescData +} + +var file_p2p_proto_snapshot_proto_msgTypes = make([]protoimpl.MessageInfo, 15) +var file_p2p_proto_snapshot_proto_goTypes = []any{ + (*PatriciaNode)(nil), // 0: PatriciaNode + (*PatriciaRangeProof)(nil), // 1: PatriciaRangeProof + (*ContractState)(nil), // 2: ContractState + (*ContractRangeRequest)(nil), // 3: ContractRangeRequest + (*ContractRange)(nil), // 4: ContractRange + (*ContractRangeResponse)(nil), // 5: ContractRangeResponse + (*ClassRangeRequest)(nil), // 6: ClassRangeRequest + (*ClassRangeResponse)(nil), // 7: ClassRangeResponse + (*StorageLeafQuery)(nil), // 8: StorageLeafQuery + (*StorageRangeQuery)(nil), // 9: StorageRangeQuery + (*ContractStorageRequest)(nil), // 10: ContractStorageRequest + (*ContractStorage)(nil), // 11: ContractStorage + (*ContractStorageResponse)(nil), // 12: ContractStorageResponse + (*PatriciaNode_Edge)(nil), // 13: PatriciaNode.Edge + (*PatriciaNode_Binary)(nil), // 14: PatriciaNode.Binary + (*Address)(nil), // 15: Address + (*Hash)(nil), // 16: Hash + (*Fin)(nil), // 17: Fin + (*Classes)(nil), // 18: Classes + (*Felt252)(nil), // 19: Felt252 + (*ContractStoredValue)(nil), // 20: ContractStoredValue +} +var file_p2p_proto_snapshot_proto_depIdxs = []int32{ + 13, // 0: PatriciaNode.edge:type_name -> PatriciaNode.Edge + 14, // 1: PatriciaNode.binary:type_name -> PatriciaNode.Binary + 0, // 2: PatriciaRangeProof.nodes:type_name -> PatriciaNode + 15, // 3: ContractState.address:type_name -> Address + 16, // 4: ContractState.class:type_name -> Hash + 16, // 5: ContractState.storage:type_name -> Hash + 16, // 6: ContractRangeRequest.state_root:type_name -> Hash + 15, // 7: ContractRangeRequest.start:type_name -> Address + 15, // 8: ContractRangeRequest.end:type_name -> Address + 2, // 9: ContractRange.state:type_name -> ContractState + 16, // 10: ContractRangeResponse.root:type_name -> Hash + 16, // 11: ContractRangeResponse.contracts_root:type_name -> Hash + 16, // 12: ContractRangeResponse.classes_root:type_name -> Hash + 4, // 13: ContractRangeResponse.range:type_name -> ContractRange + 17, // 14: ContractRangeResponse.fin:type_name -> Fin + 16, // 15: ClassRangeRequest.root:type_name -> Hash + 16, // 16: ClassRangeRequest.start:type_name -> Hash + 16, // 17: ClassRangeRequest.end:type_name -> Hash + 16, // 18: ClassRangeResponse.root:type_name -> Hash + 16, // 19: ClassRangeResponse.contracts_root:type_name -> Hash + 16, // 20: ClassRangeResponse.classes_root:type_name -> Hash + 18, // 21: ClassRangeResponse.classes:type_name -> Classes + 17, // 22: ClassRangeResponse.fin:type_name -> Fin + 16, // 23: StorageLeafQuery.contract_storage_root:type_name -> Hash + 19, // 24: StorageLeafQuery.key:type_name -> Felt252 + 15, // 25: StorageRangeQuery.address:type_name -> Address + 8, // 26: StorageRangeQuery.start:type_name -> StorageLeafQuery + 8, // 27: StorageRangeQuery.end:type_name -> StorageLeafQuery + 16, // 28: ContractStorageRequest.state_root:type_name -> Hash + 9, // 29: ContractStorageRequest.query:type_name -> StorageRangeQuery + 20, // 30: ContractStorage.keyValue:type_name -> ContractStoredValue + 16, // 31: ContractStorageResponse.state_root:type_name -> Hash + 11, // 32: ContractStorageResponse.storage:type_name -> ContractStorage + 17, // 33: ContractStorageResponse.fin:type_name -> Fin + 19, // 34: PatriciaNode.Edge.path:type_name -> Felt252 + 19, // 35: PatriciaNode.Edge.value:type_name -> Felt252 + 19, // 36: PatriciaNode.Binary.left:type_name -> Felt252 + 19, // 37: PatriciaNode.Binary.right:type_name -> Felt252 + 38, // [38:38] is the sub-list for method output_type + 38, // [38:38] is the sub-list for method input_type + 38, // [38:38] is the sub-list for extension type_name + 38, // [38:38] is the sub-list for extension extendee + 0, // [0:38] is the sub-list for field type_name +} + +func init() { file_p2p_proto_snapshot_proto_init() } +func file_p2p_proto_snapshot_proto_init() { + if File_p2p_proto_snapshot_proto != nil { + return + } + file_p2p_proto_common_proto_init() + file_p2p_proto_state_proto_init() + file_p2p_proto_class_proto_init() + if !protoimpl.UnsafeEnabled { + file_p2p_proto_snapshot_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*PatriciaNode); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_p2p_proto_snapshot_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*PatriciaRangeProof); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_p2p_proto_snapshot_proto_msgTypes[2].Exporter = func(v any, i int) any { + switch v := v.(*ContractState); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_p2p_proto_snapshot_proto_msgTypes[3].Exporter = func(v any, i int) any { + switch v := v.(*ContractRangeRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_p2p_proto_snapshot_proto_msgTypes[4].Exporter = func(v any, i int) any { + switch v := v.(*ContractRange); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_p2p_proto_snapshot_proto_msgTypes[5].Exporter = func(v any, i int) any { + switch v := v.(*ContractRangeResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_p2p_proto_snapshot_proto_msgTypes[6].Exporter = func(v any, i int) any { + switch v := v.(*ClassRangeRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_p2p_proto_snapshot_proto_msgTypes[7].Exporter = func(v any, i int) any { + switch v := v.(*ClassRangeResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_p2p_proto_snapshot_proto_msgTypes[8].Exporter = func(v any, i int) any { + switch v := v.(*StorageLeafQuery); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_p2p_proto_snapshot_proto_msgTypes[9].Exporter = func(v any, i int) any { + switch v := v.(*StorageRangeQuery); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_p2p_proto_snapshot_proto_msgTypes[10].Exporter = func(v any, i int) any { + switch v := v.(*ContractStorageRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_p2p_proto_snapshot_proto_msgTypes[11].Exporter = func(v any, i int) any { + switch v := v.(*ContractStorage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_p2p_proto_snapshot_proto_msgTypes[12].Exporter = func(v any, i int) any { + switch v := v.(*ContractStorageResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_p2p_proto_snapshot_proto_msgTypes[13].Exporter = func(v any, i int) any { + switch v := v.(*PatriciaNode_Edge); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_p2p_proto_snapshot_proto_msgTypes[14].Exporter = func(v any, i int) any { + switch v := v.(*PatriciaNode_Binary); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_p2p_proto_snapshot_proto_msgTypes[0].OneofWrappers = []any{ + (*PatriciaNode_Edge_)(nil), + (*PatriciaNode_Binary_)(nil), + } + file_p2p_proto_snapshot_proto_msgTypes[5].OneofWrappers = []any{ + (*ContractRangeResponse_Range)(nil), + (*ContractRangeResponse_Fin)(nil), + } + file_p2p_proto_snapshot_proto_msgTypes[7].OneofWrappers = []any{ + (*ClassRangeResponse_Classes)(nil), + (*ClassRangeResponse_Fin)(nil), + } + file_p2p_proto_snapshot_proto_msgTypes[12].OneofWrappers = []any{ + (*ContractStorageResponse_Storage)(nil), + (*ContractStorageResponse_Fin)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_p2p_proto_snapshot_proto_rawDesc, + NumEnums: 0, + NumMessages: 15, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_p2p_proto_snapshot_proto_goTypes, + DependencyIndexes: file_p2p_proto_snapshot_proto_depIdxs, + MessageInfos: file_p2p_proto_snapshot_proto_msgTypes, + }.Build() + File_p2p_proto_snapshot_proto = out.File + file_p2p_proto_snapshot_proto_rawDesc = nil + file_p2p_proto_snapshot_proto_goTypes = nil + file_p2p_proto_snapshot_proto_depIdxs = nil +} diff --git a/p2p/starknet/spec/state.pb.go b/p2p/starknet/spec/state.pb.go index c230a042b3..a165d3d680 100644 --- a/p2p/starknet/spec/state.pb.go +++ b/p2p/starknet/spec/state.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.34.2 -// protoc v5.27.1 +// protoc v3.17.3 // source: p2p/proto/state.proto package spec diff --git a/p2p/starknet/spec/transaction.pb.go b/p2p/starknet/spec/transaction.pb.go index ecd18338b3..3aa5519f2a 100644 --- a/p2p/starknet/spec/transaction.pb.go +++ b/p2p/starknet/spec/transaction.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.34.2 -// protoc v5.27.1 +// protoc v3.17.3 // source: p2p/proto/transaction.proto package spec diff --git a/rpc/events_test.go b/rpc/events_test.go index 7f8b483987..8eb54a3f4e 100644 --- a/rpc/events_test.go +++ b/rpc/events_test.go @@ -28,6 +28,7 @@ func TestEvents(t *testing.T) { testDB := pebble.NewMemTest(t) n := utils.Ptr(utils.Sepolia) chain := blockchain.New(testDB, n) + defer chain.Close() client := feeder.NewTestClient(t, n) gw := adaptfeeder.New(client) @@ -238,6 +239,7 @@ func TestSubscribeNewHeadsAndUnsubscribe(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) chain := blockchain.New(pebble.NewMemTest(t), n) + defer chain.Close() syncer := sync.New(chain, gw, log, 0, false) handler := rpc.New(chain, syncer, nil, "", log) @@ -319,6 +321,7 @@ func TestMultipleSubscribeNewHeadsAndUnsubscribe(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) chain := blockchain.New(pebble.NewMemTest(t), n) + defer chain.Close() syncer := sync.New(chain, gw, log, 0, false) handler := rpc.New(chain, syncer, nil, "", log) go func() { diff --git a/sync/snap_server.go b/sync/snap_server.go new file mode 100644 index 0000000000..b1f3e03c7d --- /dev/null +++ b/sync/snap_server.go @@ -0,0 +1,447 @@ +package sync + +import ( + "context" + "errors" + "math/big" + + "github.com/NethermindEth/juno/adapters/core2p2p" + "github.com/NethermindEth/juno/adapters/p2p2core" + "github.com/NethermindEth/juno/blockchain" + "github.com/NethermindEth/juno/core" + "github.com/NethermindEth/juno/core/felt" + "github.com/NethermindEth/juno/core/trie" + "github.com/NethermindEth/juno/p2p/starknet/spec" + "github.com/NethermindEth/juno/utils/iter" +) + +type ContractRangeStreamingResult struct { + ContractsRoot *felt.Felt + ClassesRoot *felt.Felt + Range []*spec.ContractState + RangeProof *spec.PatriciaRangeProof +} + +type StorageRangeRequest struct { + StateRoot *felt.Felt + ChunkPerProof uint64 // Missing in spec + Queries []*spec.StorageRangeQuery +} + +type StorageRangeStreamingResult struct { + ContractsRoot *felt.Felt + ClassesRoot *felt.Felt + StorageAddr *felt.Felt + Range []*spec.ContractStoredValue + RangeProof *spec.PatriciaRangeProof +} + +type ClassRangeStreamingResult struct { + ContractsRoot *felt.Felt + ClassesRoot *felt.Felt + Range *spec.Classes + RangeProof *spec.PatriciaRangeProof +} + +type SnapServer interface { + GetContractRange(ctx context.Context, request *spec.ContractRangeRequest) iter.Seq2[*ContractRangeStreamingResult, error] + GetStorageRange(ctx context.Context, request *StorageRangeRequest) iter.Seq2[*StorageRangeStreamingResult, error] + GetClassRange(ctx context.Context, request *spec.ClassRangeRequest) iter.Seq2[*ClassRangeStreamingResult, error] + GetClasses(ctx context.Context, classHashes []*felt.Felt) ([]*spec.Class, error) +} + +type SnapServerBlockchain interface { + GetStateForStateRoot(stateRoot *felt.Felt) (*core.State, error) + GetClasses(felts []*felt.Felt) ([]core.Class, error) +} + +type snapServer struct { + blockchain SnapServerBlockchain +} + +var _ SnapServerBlockchain = &blockchain.Blockchain{} + +func determineMaxNodes(specifiedMaxNodes uint64) uint64 { + const ( + defaultMaxNodes = 1024 * 16 + maxNodePerRequest = 1024 * 1024 // I just want it to process faster + ) + + if specifiedMaxNodes == 0 { + return defaultMaxNodes + } + + if specifiedMaxNodes < maxNodePerRequest { + return specifiedMaxNodes + } + + return maxNodePerRequest +} + +func (b *snapServer) GetClassRange(ctx context.Context, request *spec.ClassRangeRequest) iter.Seq2[*ClassRangeStreamingResult, error] { + return func(yield func(*ClassRangeStreamingResult, error) bool) { + stateRoot := p2p2core.AdaptHash(request.Root) + + s, err := b.blockchain.GetStateForStateRoot(stateRoot) + if err != nil { + yield(nil, err) + return + } + + contractRoot, classRoot, err := s.StateAndClassRoot() + if err != nil { + yield(nil, err) + return + } + + ctrie, classCloser, err := s.ClassTrie() + if err != nil { + yield(nil, err) + return + } + defer func() { _ = classCloser() }() + + startAddr := p2p2core.AdaptHash(request.Start) + limitAddr := p2p2core.AdaptHash(request.End) + if limitAddr.IsZero() { + limitAddr = nil + } + + for { + response := &spec.Classes{ + Classes: make([]*spec.Class, 0), + } + + classkeys := []*felt.Felt{} + proofs, finished, err := iterateWithLimit(ctrie, startAddr, limitAddr, determineMaxNodes(uint64(request.ChunksPerProof)), + func(key, value *felt.Felt) error { + classkeys = append(classkeys, key) + return nil + }) + if err != nil { + yield(nil, err) + return + } + + coreClasses, err := b.blockchain.GetClasses(classkeys) + if err != nil { + yield(nil, err) + return + } + + for _, coreclass := range coreClasses { + if coreclass == nil { + yield(nil, errors.New("class is nil")) + return + } + response.Classes = append(response.Classes, core2p2p.AdaptClass(coreclass)) + } + + if err != nil { + yield(nil, err) + return + } + + shouldContinue := yield(&ClassRangeStreamingResult{ + ContractsRoot: contractRoot, + ClassesRoot: classRoot, + Range: response, + RangeProof: Core2P2pProof(proofs), + }, err) + + if finished || !shouldContinue { + break + } + startAddr = classkeys[len(classkeys)-1] + } + + // TODO: not needed? - just stop the loop + yield(nil, nil) + } +} + +func (b *snapServer) GetContractRange( + ctx context.Context, + request *spec.ContractRangeRequest, +) iter.Seq2[*ContractRangeStreamingResult, error] { + return func(yield func(*ContractRangeStreamingResult, error) bool) { + stateRoot := p2p2core.AdaptHash(request.StateRoot) + + s, err := b.blockchain.GetStateForStateRoot(stateRoot) + if err != nil { + yield(nil, err) + return + } + + contractRoot, classRoot, err := s.StateAndClassRoot() + if err != nil { + yield(nil, err) + return + } + + strie, scloser, err := s.StorageTrie() + if err != nil { + yield(nil, err) + return + } + defer func() { _ = scloser() }() + + startAddr := p2p2core.AdaptAddress(request.Start) + limitAddr := p2p2core.AdaptAddress(request.End) + states := []*spec.ContractState{} + + for { + proofs, finished, err := iterateWithLimit(strie, startAddr, limitAddr, determineMaxNodes(uint64(request.ChunksPerProof)), + func(key, value *felt.Felt) error { + classHash, err := s.ContractClassHash(key) + if err != nil { + return err + } + + nonce, err := s.ContractNonce(key) + if err != nil { + return err + } + + ctr, err := s.StorageTrieForAddr(key) + if err != nil { + return err + } + + croot, err := ctr.Root() + if err != nil { + return err + } + + startAddr = key + states = append(states, &spec.ContractState{ + Address: core2p2p.AdaptAddress(key), + Class: core2p2p.AdaptHash(classHash), + Storage: core2p2p.AdaptHash(croot), + Nonce: nonce.Uint64(), + }) + return nil + }) + if err != nil { + yield(nil, err) + return + } + + shouldContinue := yield(&ContractRangeStreamingResult{ + ContractsRoot: contractRoot, + ClassesRoot: classRoot, + Range: states, + RangeProof: Core2P2pProof(proofs), + }, nil) + + if finished || !shouldContinue { + break + } + } + + yield(nil, nil) + } +} + +func (b *snapServer) GetStorageRange(ctx context.Context, request *StorageRangeRequest) iter.Seq2[*StorageRangeStreamingResult, error] { + return func(yield func(*StorageRangeStreamingResult, error) bool) { + stateRoot := request.StateRoot + + s, err := b.blockchain.GetStateForStateRoot(stateRoot) + if err != nil { + yield(nil, err) + return + } + + contractRoot, classRoot, err := s.StateAndClassRoot() + if err != nil { + yield(nil, err) + return + } + + var curNodeLimit int64 = 1000000 + + for _, query := range request.Queries { + if ctxerr := ctx.Err(); ctxerr != nil { + break + } + + contractLimit := uint64(curNodeLimit) + + strie, err := s.StorageTrieForAddr(p2p2core.AdaptAddress(query.Address)) + if err != nil { + yield(nil, err) + return + } + + handled, err := b.handleStorageRangeRequest(ctx, strie, query, request.ChunkPerProof, contractLimit, + func(values []*spec.ContractStoredValue, proofs []trie.ProofNode) { + yield(&StorageRangeStreamingResult{ + ContractsRoot: contractRoot, + ClassesRoot: classRoot, + StorageAddr: p2p2core.AdaptAddress(query.Address), + Range: values, + RangeProof: Core2P2pProof(proofs), + }, nil) + }) + if err != nil { + yield(nil, err) + return + } + + curNodeLimit -= handled + + if curNodeLimit <= 0 { + break + } + } + } +} + +func (b *snapServer) GetClasses(ctx context.Context, felts []*felt.Felt) ([]*spec.Class, error) { + classes := make([]*spec.Class, len(felts)) + coreClasses, err := b.blockchain.GetClasses(felts) + if err != nil { + return nil, err + } + + for i, class := range coreClasses { + classes[i] = core2p2p.AdaptClass(class) + } + + return classes, nil +} + +func (b *snapServer) handleStorageRangeRequest( + ctx context.Context, + stTrie *trie.Trie, + request *spec.StorageRangeQuery, + maxChunkPerProof uint64, + nodeLimit uint64, + yield func([]*spec.ContractStoredValue, []trie.ProofNode), +) (int64, error) { + totalSent := int64(0) + finished := false + startAddr := p2p2core.AdaptFelt(request.Start.Key) + var endAddr *felt.Felt = nil + if request.End != nil { + endAddr = p2p2core.AdaptFelt(request.End.Key) + } + + for !finished { + if ctxErr := ctx.Err(); ctxErr != nil { + return totalSent, ctxErr + } + + response := []*spec.ContractStoredValue{} + + limit := maxChunkPerProof + if nodeLimit < limit { + limit = nodeLimit + } + + proofs, finish, err := iterateWithLimit(stTrie, startAddr, endAddr, limit, func(key, value *felt.Felt) error { + response = append(response, &spec.ContractStoredValue{ + Key: core2p2p.AdaptFelt(key), + Value: core2p2p.AdaptFelt(value), + }) + + startAddr = key + return nil + }) + finished = finish + + if err != nil { + return 0, err + } + + if len(response) == 0 { + finished = true + } + + yield(response, proofs) + + totalSent += int64(len(response)) + nodeLimit -= limit + + asBint := startAddr.BigInt(big.NewInt(0)) + asBint = asBint.Add(asBint, big.NewInt(1)) + startAddr = startAddr.SetBigInt(asBint) + } + + return totalSent, nil +} + +func iterateWithLimit( + srcTrie *trie.Trie, + startAddr *felt.Felt, + limitAddr *felt.Felt, + maxNodes uint64, + consumer func(key, value *felt.Felt) error, +) ([]trie.ProofNode, bool, error) { + pathes := make([]*felt.Felt, 0) + hashes := make([]*felt.Felt, 0) + + // TODO: Verify class trie + count := uint64(0) + proof, finished, err := srcTrie.IterateAndGenerateProof(startAddr, func(key *felt.Felt, value *felt.Felt) (bool, error) { + // Need at least one. + if limitAddr != nil && key.Cmp(limitAddr) > 1 && count > 0 { + return false, nil + } + + pathes = append(pathes, key) + hashes = append(hashes, value) + + err := consumer(key, value) + if err != nil { + return false, err + } + + count++ + if count >= maxNodes { + return false, nil + } + return true, nil + }) + if err != nil { + return nil, finished, err + } + + return proof, finished, err +} + +func Core2P2pProof(proofs []trie.ProofNode) *spec.PatriciaRangeProof { + nodes := make([]*spec.PatriciaNode, len(proofs)) + + for i := range proofs { + if proofs[i].Binary != nil { + binary := proofs[i].Binary + nodes[i] = &spec.PatriciaNode{ + Node: &spec.PatriciaNode_Binary_{ + Binary: &spec.PatriciaNode_Binary{ + Left: core2p2p.AdaptFelt(binary.LeftHash), + Right: core2p2p.AdaptFelt(binary.RightHash), + }, + }, + } + } + if proofs[i].Edge != nil { + edge := proofs[i].Edge + pathfeld := edge.Path.Felt() + nodes[i] = &spec.PatriciaNode{ + Node: &spec.PatriciaNode_Edge_{ + Edge: &spec.PatriciaNode_Edge{ + Length: uint32(edge.Path.Len()), + Path: core2p2p.AdaptFelt(&pathfeld), + Value: core2p2p.AdaptFelt(edge.Child), + }, + }, + } + } + } + + return &spec.PatriciaRangeProof{ + Nodes: nodes, + } +} diff --git a/sync/snap_server_test.go b/sync/snap_server_test.go new file mode 100644 index 0000000000..bd0bf74683 --- /dev/null +++ b/sync/snap_server_test.go @@ -0,0 +1,206 @@ +package sync + +import ( + "context" + "fmt" + "testing" + + "github.com/NethermindEth/juno/adapters/core2p2p" + "github.com/NethermindEth/juno/adapters/p2p2core" + "github.com/NethermindEth/juno/blockchain" + "github.com/NethermindEth/juno/core" + "github.com/NethermindEth/juno/core/crypto" + "github.com/NethermindEth/juno/core/felt" + "github.com/NethermindEth/juno/db" + "github.com/NethermindEth/juno/db/pebble" + "github.com/NethermindEth/juno/p2p/starknet/spec" + "github.com/NethermindEth/juno/utils" + "github.com/stretchr/testify/assert" +) + +func TestClassRange(t *testing.T) { + var d db.DB + t.Skip("DB snapshot is needed for this test") + d, _ = pebble.NewWithOptions("/Users/pnowosie/juno/snapshots/juno-sepolia", 128000000, 128, false) + defer func() { _ = d.Close() }() + bc := blockchain.New(d, &utils.Sepolia) // Needed because class loader need encoder to be registered + + b, err := bc.Head() + assert.NoError(t, err) + + fmt.Printf("headblock %d\n", b.Number) + + stateRoot := b.GlobalStateRoot + + server := &snapServer{ + blockchain: bc, + } + + startRange := (&felt.Felt{}).SetUint64(0) + + chunksPerProof := 150 + var classResult *ClassRangeStreamingResult + server.GetClassRange(context.Background(), + &spec.ClassRangeRequest{ + Root: core2p2p.AdaptHash(stateRoot), + Start: core2p2p.AdaptHash(startRange), + ChunksPerProof: uint32(chunksPerProof), + })(func(result *ClassRangeStreamingResult, err error) bool { + if err != nil { + fmt.Printf("err %s\n", err) + t.Fatal(err) + } + + if result != nil { + classResult = result + } + + return false + }) + + assert.NotNil(t, classResult) + assert.Equal(t, chunksPerProof, len(classResult.Range.Classes)) + verifyErr := VerifyGlobalStateRoot(stateRoot, classResult.ClassesRoot, classResult.ContractsRoot) + assert.NoError(t, verifyErr) +} + +func TestContractRange(t *testing.T) { + var d db.DB + t.Skip("DB snapshot is needed for this test") + d, _ = pebble.NewWithOptions("/Users/pnowosie/juno/snapshots/juno-sepolia", 128000000, 128, false) + defer func() { _ = d.Close() }() + bc := blockchain.New(d, &utils.Sepolia) // Needed because class loader need encoder to be registered + + b, err := bc.Head() + assert.NoError(t, err) + + fmt.Printf("headblock %d\n", b.Number) + + stateRoot := b.GlobalStateRoot + + server := &snapServer{ + blockchain: bc, + } + + startRange := (&felt.Felt{}).SetUint64(0) + + chunksPerProof := 150 + var contractResult *ContractRangeStreamingResult + server.GetContractRange(context.Background(), + &spec.ContractRangeRequest{ + StateRoot: core2p2p.AdaptHash(stateRoot), + Start: core2p2p.AdaptAddress(startRange), + ChunksPerProof: uint32(chunksPerProof), + })(func(result *ContractRangeStreamingResult, err error) bool { + if err != nil { + fmt.Printf("err %s\n", err) + t.Fatal(err) + } + + if result != nil { + contractResult = result + } + + return false + }) + + assert.NotNil(t, contractResult) + assert.Equal(t, chunksPerProof, len(contractResult.Range)) + verifyErr := VerifyGlobalStateRoot(stateRoot, contractResult.ClassesRoot, contractResult.ContractsRoot) + assert.NoError(t, verifyErr) +} + +func TestContractStorageRange(t *testing.T) { + var d db.DB + t.Skip("DB snapshot is needed for this test") + d, _ = pebble.NewWithOptions("/Users/pnowosie/juno/snapshots/juno-sepolia", 128000000, 128, false) + defer func() { _ = d.Close() }() + bc := blockchain.New(d, &utils.Sepolia) // Needed because class loader need encoder to be registered + + b, err := bc.Head() + assert.NoError(t, err) + + fmt.Printf("headblock %d\n", b.Number) + + stateRoot := b.GlobalStateRoot + + server := &snapServer{ + blockchain: bc, + } + + startRange := (&felt.Felt{}).SetUint64(0) + + tests := []struct { + address *felt.Felt + storageRoot *felt.Felt + expectedLeaves int + }{ + { + address: feltFromString("0x3deecdb26a60e4c062d5bd98ab37f72ea2acc37f28dae6923359627ebde9"), + storageRoot: feltFromString("0x276edbc91a945d11645ba0b8298c7d657e554d06ab2bb765cbc44d61fa01fd5"), + expectedLeaves: 1, + }, + { + address: feltFromString("0x5de00d3720421ab00fdbc47d33d253605c1ac226ab1a0d267f7d57e23305"), + storageRoot: feltFromString("0x5eebb2c6722d321469cb662260c5171c9f6f67b9a625c9c9ab56b0a4631b0fe"), + expectedLeaves: 2, + }, + { + address: feltFromString("0x1ee60ed3c5abd9a08c61de5e8cbcf32b49646e681ee6e84da9d52f5c3099"), + storageRoot: feltFromString("0x60dccd54f4956147c6a499b71579820d181e22d5e9c430fd5953f861ca7727e"), + expectedLeaves: 4, + }, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("%.7s...", test.address), func(t *testing.T) { + request := &StorageRangeRequest{ + StateRoot: stateRoot, + ChunkPerProof: 100, + Queries: []*spec.StorageRangeQuery{ + { + Address: core2p2p.AdaptAddress(test.address), + Start: &spec.StorageLeafQuery{ + ContractStorageRoot: core2p2p.AdaptHash(test.storageRoot), + Key: core2p2p.AdaptFelt(startRange), + }, + End: nil, + }, + }, + } + + keys := make([]*felt.Felt, 0, test.expectedLeaves) + vals := make([]*felt.Felt, 0, test.expectedLeaves) + server.GetStorageRange(context.Background(), request)(func(result *StorageRangeStreamingResult, err error) bool { + if err != nil { + fmt.Printf("err %s\n", err) + t.Fatal(err) + } + + if result != nil { + for _, r := range result.Range { + keys = append(keys, p2p2core.AdaptFelt(r.Key)) + vals = append(vals, p2p2core.AdaptFelt(r.Value)) + } + } + + return true + }) + + fmt.Println("Address:", test.address, "storage length:", len(keys)) + assert.Equal(t, test.expectedLeaves, len(keys)) + + hasMore, err := VerifyTrie(test.storageRoot, keys, vals, nil, core.ContractStorageTrieHeight, crypto.Pedersen) + assert.NoError(t, err) + assert.False(t, hasMore) + }) + } +} + +func feltFromString(str string) *felt.Felt { + f, err := (&felt.Felt{}).SetString(str) + if err != nil { + panic(err) + } + return f +} diff --git a/sync/snapsyncer.go b/sync/snapsyncer.go new file mode 100644 index 0000000000..d7bad6f7cf --- /dev/null +++ b/sync/snapsyncer.go @@ -0,0 +1,1107 @@ +package sync + +import ( + "context" + "errors" + "fmt" + big "math/big" + "sync" + "sync/atomic" + "time" + + "github.com/NethermindEth/juno/adapters/core2p2p" + "github.com/NethermindEth/juno/adapters/p2p2core" + "github.com/NethermindEth/juno/blockchain" + "github.com/NethermindEth/juno/core" + "github.com/NethermindEth/juno/core/crypto" + "github.com/NethermindEth/juno/core/felt" + "github.com/NethermindEth/juno/core/trie" + "github.com/NethermindEth/juno/p2p/starknet/spec" + "github.com/NethermindEth/juno/service" + "github.com/NethermindEth/juno/starknetdata" + "github.com/NethermindEth/juno/utils" + "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "golang.org/x/sync/errgroup" +) + +const JobDuration = time.Second * 10 + +type Blockchain interface { + GetClasses(felts []*felt.Felt) ([]core.Class, error) + PutClasses(blockNumber uint64, v1CompiledHashes map[felt.Felt]*felt.Felt, newClasses map[felt.Felt]core.Class) error + PutContracts(address, nonces, classHash []*felt.Felt) error + PutStorage(storage map[felt.Felt]map[felt.Felt]*felt.Felt) error +} + +type SnapSyncher struct { + baseSync service.Service + starknetData starknetdata.StarknetData + snapServer SnapServer + blockchain Blockchain + log utils.Logger + + startingBlock *core.Header + lastBlock *core.Header + currentGlobalStateRoot *felt.Felt + + contractRangeDone chan interface{} + storageRangeDone chan interface{} + + storageRangeJobCount int32 + storageRangeJobQueue chan *storageRangeJob + storageRefreshJob chan *storageRangeJob + + classesJob chan *felt.Felt + + // Three lock priority lock + mtxM *sync.Mutex + mtxN *sync.Mutex + mtxL *sync.Mutex +} + +type storageRangeJob struct { + path *felt.Felt + storageRoot *felt.Felt + startAddress *felt.Felt + classHash *felt.Felt + nonce uint64 +} + +func NewSnapSyncer( + baseSyncher service.Service, + consensus starknetdata.StarknetData, + server SnapServer, + bc *blockchain.Blockchain, + log utils.Logger, +) *SnapSyncher { + return &SnapSyncher{ + baseSync: baseSyncher, + starknetData: consensus, + snapServer: server, + blockchain: bc, + log: log, + } +} + +var ( + // magic values linter does not like + start = 1.0 + factor = 1.5 + count = 30 + + rangeProgress = promauto.NewGauge(prometheus.GaugeOpts{ + Name: "juno_range_progress", + Help: "Time in address get", + }) + + pivotUpdates = promauto.NewCounter(prometheus.CounterOpts{ + Name: "juno_pivot_update", + Help: "Time in address get", + }) + + storageAddressCount = promauto.NewHistogram(prometheus.HistogramOpts{ + Name: "juno_storage_address_count", + Help: "Time in address get", + Buckets: prometheus.ExponentialBuckets(start, factor, count), + }) +) + +var ( + storageJobWorker = 8 + storageBatchSize = 500 + storageJobQueueSize = storageJobWorker * storageBatchSize // Too high and the progress from address range would be inaccurate. + + // For some reason, the trie throughput is higher if the batch size is small. + classRangeChunksPerProof = 500 + contractRangeChunkPerProof = 500 + storageRangeChunkPerProof = 500 + maxStorageBatchSize = 500 + maxMaxPerStorageSize = 500 + + fetchClassWorkerCount = 8 // Fairly parallelizable. But this is brute force... + classesJobQueueSize = 128 + + maxPivotDistance = 32 // Set to 1 to test updated storage. + newPivotHeadDistance = uint64(0) // This should be the reorg depth + +) + +func (s *SnapSyncher) Run(ctx context.Context) error { + s.log.Infow("starting snap sync") + // 1. Get the current head + // 2. Start the snap sync with pivot set to that head + // 3. If at any moment, if: + // a. The current head is too new (more than 64 block let say) + // b. Too many missing node + // then reset the pivot. + // 4. Once finished, replay state update from starting pivot to the latest pivot. + // 5. Then do some cleanup, mark things and complete and such. + // 6. Probably download old state updato/bodies too + // 7. Send back control to base sync. + + err := s.runPhase1(ctx) + if err != nil { + return err + } + + s.log.Infow("delegating to standard synchronizer") + return s.baseSync.Run(ctx) +} + +func VerifyTrie( + expectedRoot *felt.Felt, + paths, hashes []*felt.Felt, + proofs []trie.ProofNode, + height uint8, + hash func(*felt.Felt, *felt.Felt) *felt.Felt, +) (bool, error) { + hasMore, valid, err := trie.VerifyRange(expectedRoot, nil, paths, hashes, proofs, hash, height) + if err != nil { + return false, err + } + if !valid { + return false, errors.New("invalid proof") + } + + return hasMore, nil +} + +//nolint:gocyclo,nolintlint +func (s *SnapSyncher) runPhase1(ctx context.Context) error { + starttime := time.Now() + + err := s.initState(ctx) + if err != nil { + return errors.Join(err, errors.New("error initialising snap syncer state")) + } + + eg, ectx := errgroup.WithContext(ctx) + + eg.Go(func() error { + defer func() { + s.log.Infow("pool latest block done") + if err := recover(); err != nil { + s.log.Errorw("latest block pool panicked", "err", err) + } + }() + + return s.poolLatestBlock(ectx) + }) + + eg.Go(func() error { + defer func() { + if err := recover(); err != nil { + s.log.Errorw("class range panicked", "err", err) + } + }() + + err := s.runClassRangeWorker(ectx) + if err != nil { + s.log.Errorw("error in class range worker", "err", err) + } + return err + }) + + eg.Go(func() error { + defer func() { + if err := recover(); err != nil { + s.log.Errorw("address range paniced", "err", err) + } + }() + + err := s.runContractRangeWorker(ectx) + if err != nil { + s.log.Errorw("error in address range worker", "err", err) + } + + s.log.Infow("contract range done") + close(s.contractRangeDone) + close(s.classesJob) + + return err + }) + + storageEg, sctx := errgroup.WithContext(ectx) + for i := 0; i < storageJobWorker; i++ { + i := i + storageEg.Go(func() error { + defer func() { + if err := recover(); err != nil { + s.log.Errorw("storage worker paniced", "err", err) + } + }() + + err := s.runStorageRangeWorker(sctx, i) + if err != nil { + s.log.Errorw("error in storage range worker", "err", err) + } + s.log.Infow("Storage worker completed", "workerId", i) + + return err + }) + } + + // For notifying that storage range is done + eg.Go(func() error { + err := storageEg.Wait() + if err != nil { + return err + } + + s.log.Infow("Storage range range completed") + close(s.storageRangeDone) + return nil + }) + + eg.Go(func() error { + defer func() { + if err := recover(); err != nil { + s.log.Errorw("storage refresh paniced", "err", err) + } + }() + + err := s.runStorageRefreshWorker(ectx) + if err != nil { + s.log.Errorw("error in storage refresh worker", "err", err) + } + + return err + }) + + for i := 0; i < fetchClassWorkerCount; i++ { + i := i + eg.Go(func() error { + err := s.runFetchClassJob(ectx) + if err != nil { + s.log.Errorw("fetch class failed", "err", err) + } + s.log.Infow("fetch class completed", "workerId", i) + return err + }) + } + + err = eg.Wait() + if err != nil { + return err + } + + s.log.Infow("first phase completed", "duration", time.Since(starttime)) + + return nil +} + +func (s *SnapSyncher) getNextStartingBlock(ctx context.Context) (*core.Block, error) { + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + head, err := s.starknetData.BlockLatest(ctx) + if err != nil { + s.log.Warnw("error getting current head", "error", err) + continue + } + startingBlock, err := s.starknetData.BlockByNumber(ctx, head.Number-newPivotHeadDistance) + if err != nil { + s.log.Warnw("error getting starting block", "error", err) + continue + } + + return startingBlock, nil + } +} + +func (s *SnapSyncher) initState(ctx context.Context) error { + startingBlock, err := s.getNextStartingBlock(ctx) + if err != nil { + return errors.Join(err, errors.New("error getting current head")) + } + + s.startingBlock = startingBlock.Header + s.lastBlock = startingBlock.Header + + fmt.Printf("Start state root is %s\n", s.startingBlock.GlobalStateRoot) + s.currentGlobalStateRoot = s.startingBlock.GlobalStateRoot.Clone() + s.storageRangeJobCount = 0 + s.storageRangeJobQueue = make(chan *storageRangeJob, storageJobQueueSize) + s.classesJob = make(chan *felt.Felt, classesJobQueueSize) + + s.contractRangeDone = make(chan interface{}) + s.storageRangeDone = make(chan interface{}) + + s.mtxM = &sync.Mutex{} + s.mtxN = &sync.Mutex{} + s.mtxL = &sync.Mutex{} + + return nil +} + +func calculatePercentage(f *felt.Felt) uint64 { + const maxPercent = 100 + maxint := big.NewInt(1) + maxint.Lsh(maxint, core.GlobalTrieHeight) + + percent := f.BigInt(big.NewInt(0)) + percent.Mul(percent, big.NewInt(maxPercent)) + percent.Div(percent, maxint) + + return percent.Uint64() +} + +//nolint:gocyclo,nolintlint +func (s *SnapSyncher) runClassRangeWorker(ctx context.Context) error { + totaladded := 0 + completed := false + startAddr := &felt.Zero + for !completed { + s.log.Infow("class range progress", "progress", calculatePercentage(startAddr)) + + stateRoot := s.currentGlobalStateRoot + + var err error + + s.log.Infow("class range state root", "stateroot", stateRoot) + + // TODO: Maybe timeout + s.snapServer.GetClassRange(ctx, &spec.ClassRangeRequest{ + Root: core2p2p.AdaptHash(stateRoot), + Start: core2p2p.AdaptHash(startAddr), + ChunksPerProof: uint32(classRangeChunksPerProof), + })(func(response *ClassRangeStreamingResult, reqErr error) bool { + if reqErr != nil { + fmt.Printf("%s\n", errors.Join(reqErr, errors.New("error get address range"))) + return false + } + + if response == nil || (response.Range == nil && response.RangeProof == nil) { + // State root missing. + return false + } + s.log.Infow("got", "res", len(response.Range.Classes), "err", reqErr, "startAdr", startAddr) + + err = VerifyGlobalStateRoot(stateRoot, response.ClassesRoot, response.ContractsRoot) + if err != nil { + s.log.Infow("global state root verification failure") + // Root verification failed + // TODO: Ban peer + return false + } + + if response.ClassesRoot.Equal(&felt.Zero) { + // Special case, no V1 at all + completed = true + return false + } + + paths := make([]*felt.Felt, len(response.Range.Classes)) + values := make([]*felt.Felt, len(response.Range.Classes)) + coreClasses := make([]core.Class, len(response.Range.Classes)) + + egrp := errgroup.Group{} + + for i, cls := range response.Range.Classes { + coreClass := p2p2core.AdaptClass(cls) + i := i + egrp.Go(func() error { + coreClasses[i] = coreClass + paths[i] = CalculateClassHash(coreClass) + values[i] = CalculateCompiledClassHash(coreClass) + + // For verification, should be + // leafValue := crypto.Poseidon(leafVersion, compiledClassHash) + return nil + }) + } + + err = egrp.Wait() + if err != nil { + return false + } + + proofs := P2pProofToTrieProofs(response.RangeProof) + hasNext, err := VerifyTrie(response.ClassesRoot, paths, values, proofs, core.GlobalTrieHeight, crypto.Poseidon) + if err != nil { + // Root verification failed + // TODO: Ban peer + return false + } + + // Ingest + coreClassesMap := map[felt.Felt]core.Class{} + coreClassesHashMap := map[felt.Felt]*felt.Felt{} + for i, coreClass := range coreClasses { + coreClassesMap[*paths[i]] = coreClass + coreClassesHashMap[*paths[i]] = values[i] + } + + err = s.blockchain.PutClasses(s.lastBlock.Number, coreClassesHashMap, coreClassesMap) + if err != nil { + return false + } + + if !hasNext { + s.log.Infow("class range completed", "totalClass", totaladded) + completed = true + return false + } + + // Increment addr, start loop again + startAddr = paths[len(paths)-1] + + return true + }) + + if err != nil { + return err + } + } + + return nil +} + +func CalculateCompiledClassHash(cls core.Class) *felt.Felt { + return cls.(*core.Cairo1Class).Compiled.Hash() +} + +func P2pProofToTrieProofs(proof *spec.PatriciaRangeProof) []trie.ProofNode { + // TODO: Move to adapter + + proofs := make([]trie.ProofNode, len(proof.Nodes)) + for i, node := range proof.Nodes { + if node.GetBinary() != nil { + binary := node.GetBinary() + proofs[i] = trie.ProofNode{ + Binary: &trie.Binary{ + LeftHash: p2p2core.AdaptFelt(binary.Left), + RightHash: p2p2core.AdaptFelt(binary.Right), + }, + } + } else { + edge := node.GetEdge() + // TODO. What if edge is nil too? + key := trie.NewKey(uint8(edge.Length), edge.Path.Elements) + proofs[i] = trie.ProofNode{ + Edge: &trie.Edge{ + Child: p2p2core.AdaptFelt(edge.Value), + Path: &key, + }, + } + } + } + + return proofs +} + +var stateVersion = new(felt.Felt).SetBytes([]byte(`STARKNET_STATE_V0`)) + +func VerifyGlobalStateRoot(globalStateRoot, classRoot, storageRoot *felt.Felt) error { + if classRoot.IsZero() { + if globalStateRoot.Equal(storageRoot) { + return nil + } else { + return errors.New("invalid global state root") + } + } + + if !crypto.PoseidonArray(stateVersion, storageRoot, classRoot).Equal(globalStateRoot) { + return errors.New("invalid global state root") + } + return nil +} + +func CalculateClassHash(cls core.Class) *felt.Felt { + hash, err := cls.Hash() + if err != nil { + panic(err) + } + + return hash +} + +//nolint:gocyclo +func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { + startAddr := &felt.Zero + completed := false + + for !completed { + var outherErr error + + stateRoot := s.currentGlobalStateRoot + s.snapServer.GetContractRange(ctx, &spec.ContractRangeRequest{ + Domain: 0, // What do this do? + StateRoot: core2p2p.AdaptHash(stateRoot), + Start: core2p2p.AdaptAddress(startAddr), + End: nil, // No need for now. + ChunksPerProof: uint32(contractRangeChunkPerProof), + })(func(response *ContractRangeStreamingResult, _err error) bool { + s.log.Infow("snap range progress", "progress", calculatePercentage(startAddr), "addr", startAddr) + rangeProgress.Set(float64(calculatePercentage(startAddr))) + + if response == nil || (response.Range == nil && response.RangeProof == nil) { + // State root missing. + return false + } + + err := VerifyGlobalStateRoot(stateRoot, response.ClassesRoot, response.ContractsRoot) + outherErr = err + if err != nil { + // Root verification failed + // TODO: Ban peer + return false + } + + paths := make([]*felt.Felt, len(response.Range)) + values := make([]*felt.Felt, len(response.Range)) + + for i, rangeValue := range response.Range { + paths[i] = p2p2core.AdaptAddress(rangeValue.Address) + values[i] = CalculateRangeValueHash(rangeValue) + } + + proofs := P2pProofToTrieProofs(response.RangeProof) + hasNext, err := VerifyTrie(response.ContractsRoot, paths, values, proofs, core.GlobalTrieHeight, crypto.Pedersen) + outherErr = err + if err != nil { + // The peer should get penalised in this case + return false + } + + classes := []*felt.Felt{} + nonces := []*felt.Felt{} + for _, r := range response.Range { + classHash := p2p2core.AdaptHash(r.Class) + classes = append(classes, classHash) + nonces = append(nonces, (&felt.Felt{}).SetUint64(r.Nonce)) + } + + err = s.blockchain.PutContracts(paths, nonces, classes) + outherErr = err + if err != nil { + fmt.Printf("%s\n", err) + panic(err) + } + + // We don't actually store it directly here... only put it as part of job. + // Can't remember why. Could be because it would be some wasted work. + for _, r := range response.Range { + path := p2p2core.AdaptAddress(r.Address) + storageRoot := p2p2core.AdaptHash(r.Storage) + classHash := p2p2core.AdaptHash(r.Class) + nonce := r.Nonce + + err = s.queueClassJob(ctx, classHash) + outherErr = err + if err != nil { + return false + } + + err = s.queueStorageRangeJob(ctx, path, storageRoot, classHash, nonce) + outherErr = err + if err != nil { + return false + } + } + + if !hasNext { + s.log.Infow("address range completed") + completed = true + return false + } + + if len(paths) == 0 { + return false + } + + startAddr = paths[len(paths)-1] + return true + }) + + if outherErr != nil { + s.log.Errorw("Error with contract range", "err", outherErr) + // TODO: address the error properly + // Well... need to figure out how to determine if its a temporary error or not. + // For sure, the state root can be outdated, so this need to restart + return outherErr + } + } + + return nil +} + +func CalculateRangeValueHash(value *spec.ContractState) *felt.Felt { + nonce := fp.NewElement(value.Nonce) + return calculateContractCommitment( + p2p2core.AdaptHash(value.Storage), + p2p2core.AdaptHash(value.Class), + felt.NewFelt(&nonce), + ) +} + +func calculateContractCommitment(storageRoot, classHash, nonce *felt.Felt) *felt.Felt { + return crypto.Pedersen(crypto.Pedersen(crypto.Pedersen(classHash, storageRoot), nonce), &felt.Zero) +} + +func (s *SnapSyncher) queueClassJob(ctx context.Context, classHash *felt.Felt) error { + queued := false + for !queued { + select { + case s.classesJob <- classHash: + queued = true + case <-ctx.Done(): + return ctx.Err() + case <-time.After(time.Second): + s.log.Infow("class queue stall on class") + } + } + return nil +} + +func (s *SnapSyncher) queueStorageRangeJob(ctx context.Context, path, storageRoot, classHash *felt.Felt, nonce uint64) error { + return s.queueStorageRangeJobJob(ctx, &storageRangeJob{ + path: path, + storageRoot: storageRoot, + startAddress: &felt.Zero, + classHash: classHash, + nonce: nonce, + }) +} + +func (s *SnapSyncher) queueStorageRangeJobJob(ctx context.Context, job *storageRangeJob) error { + queued := false + for !queued { + select { + case s.storageRangeJobQueue <- job: + queued = true + atomic.AddInt32(&s.storageRangeJobCount, 1) + case <-ctx.Done(): + return ctx.Err() + case <-time.After(JobDuration): + s.log.Infow("queue storage range stall") + } + } + return nil +} + +func (s *SnapSyncher) queueStorageRefreshJob(ctx context.Context, job *storageRangeJob) error { + queued := false + for !queued { + select { + case s.storageRefreshJob <- job: + queued = true + atomic.AddInt32(&s.storageRangeJobCount, 1) + case <-ctx.Done(): + return ctx.Err() + case <-time.After(time.Second): + s.log.Infow("storage refresh queue stall") + } + } + return nil +} + +//nolint:funlen,gocyclo +func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) error { + nextjobs := make([]*storageRangeJob, 0) + for { + jobs := nextjobs + + requestloop: + for len(jobs) < storageBatchSize { + contractDoneChecker := s.contractRangeDone + if s.storageRangeJobCount > 0 { + contractDoneChecker = nil // So that it never complete as there are job to be done + } + + select { + case job := <-s.storageRangeJobQueue: + jobs = append(jobs, job) + case <-ctx.Done(): + return ctx.Err() + case <-time.After(JobDuration): + if len(jobs) > 0 { + break requestloop + } + s.log.Infow("waiting for more storage job", "count", s.storageRangeJobCount) + case <-contractDoneChecker: + // Its done... + return nil + } + } + + s.log.Infow("Pending jobs count", "pending", s.storageRangeJobCount) + + requests := make([]*spec.StorageRangeQuery, 0) + for _, job := range jobs { + requests = append(requests, &spec.StorageRangeQuery{ + Address: core2p2p.AdaptAddress(job.path), + Start: &spec.StorageLeafQuery{ + ContractStorageRoot: core2p2p.AdaptHash(job.storageRoot), + + // TODO: Should be address + Key: core2p2p.AdaptFelt(job.startAddress), + }, + }) + } + + var err error + + stateRoot := s.currentGlobalStateRoot + processedJobs := 0 + storage := map[felt.Felt]map[felt.Felt]*felt.Felt{} + totalPath := 0 + maxPerStorageSize := 0 + + s.log.Infow("storage range", + "rootDistance", s.lastBlock.Number-s.startingBlock.Number, + "root", stateRoot.String(), + "requestcount", len(requests), + ) + s.snapServer.GetStorageRange(ctx, &StorageRangeRequest{ + StateRoot: stateRoot, + ChunkPerProof: uint64(storageRangeChunkPerProof), + Queries: requests, + })(func(response *StorageRangeStreamingResult, _err error) bool { + job := jobs[processedJobs] + if !job.path.Equal(response.StorageAddr) { + s.log.Errorw(fmt.Sprintf( + "storage addr differ %s %s %d\n", job.path, response.StorageAddr, workerIdx)) + return false + } + + if response.Range == nil && response.RangeProof == nil { + // State root missing. + return false + } + + // Wait.. why is this needed here? + err = VerifyGlobalStateRoot(stateRoot, response.ClassesRoot, response.ContractsRoot) + if err != nil { + // Root verification failed + return false + } + + // Validate response + paths := make([]*felt.Felt, len(response.Range)) + values := make([]*felt.Felt, len(response.Range)) + + for i, v := range response.Range { + paths[i] = p2p2core.AdaptFelt(v.Key) + values[i] = p2p2core.AdaptFelt(v.Value) + } + + proofs := P2pProofToTrieProofs(response.RangeProof) + hasNext, err := VerifyTrie(job.storageRoot, paths, values, proofs, core.ContractStorageTrieHeight, crypto.Pedersen) + if err != nil { + // It is unclear how to distinguish if the peer is malicious/broken/non-bizantine or the contracts root is outdated. + err = s.queueStorageRefreshJob(ctx, job) + if err != nil { + return false + } + + // Go to next contract + processedJobs++ + return true + } + + if storage[*job.path] == nil { + storage[*job.path] = map[felt.Felt]*felt.Felt{} + } + for i, path := range paths { + storage[*job.path][*path] = values[i] + } + + totalPath += len(paths) + if maxPerStorageSize < len(storage[*job.path]) { + maxPerStorageSize = len(storage[*job.path]) + } + + if totalPath > maxStorageBatchSize || maxPerStorageSize > maxMaxPerStorageSize { + // Only after a certain amount of path, we store it + // so that the storing part is more efficient + storageAddressCount.Observe(float64(len(storage))) + err = s.blockchain.PutStorage(storage) + if err != nil { + s.log.Errorw("error store", "err", err) + return false + } + + totalPath = 0 + maxPerStorageSize = 0 + storage = map[felt.Felt]map[felt.Felt]*felt.Felt{} + } + + if hasNext { + job.startAddress = paths[len(paths)-1] + } else { + processedJobs++ + atomic.AddInt32(&s.storageRangeJobCount, -1) // its... done? + } + + return true + }) + + if err != nil { + s.log.Errorw("Error with storage range", "err", err) + // Well... need to figure out how to determine if its a temporary error or not. + // For sure, the state root can be outdated, so this need to restart + continue + } + + storageAddressCount.Observe(float64(len(storage))) + err = s.blockchain.PutStorage(storage) + if err != nil { + s.log.Errorw("store raw err", "err", err) + return err + } + + // TODO: assign to nil to clear memory + nextjobs = make([]*storageRangeJob, 0) + for i := processedJobs; i < len(jobs); i++ { + unprocessedRequest := jobs[i] + nextjobs = append(nextjobs, unprocessedRequest) + } + } +} + +func (s *SnapSyncher) poolLatestBlock(ctx context.Context) error { + for { + select { + case <-ctx.Done(): + return nil + case <-time.After(JobDuration): + break + case <-s.storageRangeDone: + return nil + } + + newTarget, err := s.getNextStartingBlock(ctx) + if err != nil { + return errors.Join(err, errors.New("error getting current head")) + } + + // TODO: Race issue + if newTarget.Number-s.lastBlock.Number < uint64(maxPivotDistance) { + s.log.Infow("Not updating pivot yet", "lastblock", s.lastBlock.Number, + "newTarget", newTarget.Number, "diff", newTarget.Number-s.lastBlock.Number) + continue + } + + pivotUpdates.Inc() + + s.log.Infow("Switching snap pivot", "hash", newTarget.Hash, "number", newTarget.Number) + s.lastBlock = newTarget.Header + + fmt.Printf("Current state root is %s", s.lastBlock.GlobalStateRoot) + s.currentGlobalStateRoot = s.lastBlock.GlobalStateRoot + } +} + +func (s *SnapSyncher) ApplyStateUpdate(blockNumber uint64) error { + return errors.New("unimplemented") +} + +//nolint:gocyclo +func (s *SnapSyncher) runFetchClassJob(ctx context.Context) error { + keyBatches := make([]*felt.Felt, 0) + for { + requestloop: + for len(keyBatches) < 100 { + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(JobDuration): + // Just request whatever we have + if len(keyBatches) > 0 { + break requestloop + } + s.log.Infow("waiting for more class job", "count", s.storageRangeJobCount) + case key := <-s.classesJob: + if key == nil { + // channel finished. + if len(keyBatches) > 0 { + break requestloop + } else { + // Worker finished + return nil + } + } else { + if key.Equal(&felt.Zero) { + continue + } + + // TODO: Can be done in batches + cls, err := s.blockchain.GetClasses([]*felt.Felt{key}) + if err != nil { + s.log.Errorw("error getting class", "err", err) + return err + } + + if cls[0] == nil { + keyBatches = append(keyBatches, key) + } + } + } + } + + classes, err := s.snapServer.GetClasses(ctx, keyBatches) + if err != nil { + s.log.Errorw("error getting class from outside", "err", err) + return err + } + + processedClasses := map[felt.Felt]bool{} + newClasses := map[felt.Felt]core.Class{} + classHashes := map[felt.Felt]*felt.Felt{} + for i, class := range classes { + if class == nil { + s.log.Infow("class empty", "key", keyBatches[i]) + continue + } + + coreClass := p2p2core.AdaptClass(class) + newClasses[*keyBatches[i]] = coreClass + h, err := coreClass.Hash() + if err != nil { + s.log.Errorw("error hashing class", "err", err) + return err + } + + if !h.Equal(keyBatches[i]) { + s.log.Warnw("invalid classhash", "got", h, "expected", keyBatches[i]) + return errors.New("invalid class hash") + } + + if coreClass.Version() == 1 { + classHashes[*keyBatches[i]] = coreClass.(*core.Cairo1Class).Compiled.Hash() + } + + processedClasses[*keyBatches[i]] = true + } + + if len(newClasses) != 0 { + err = s.blockchain.PutClasses(s.lastBlock.Number, classHashes, newClasses) + if err != nil { + s.log.Errorw("error storing class", "err", err) + return err + } + } else { + s.log.Errorw("Unable to fetch any class from peer") + // TODO: Penalise peer? + } + + newBatch := make([]*felt.Felt, 0) + for _, classHash := range keyBatches { + if _, ok := processedClasses[*classHash]; !ok { + newBatch = append(newBatch, classHash) + } + } + + keyBatches = newBatch + } +} + +//nolint:gocyclo +func (s *SnapSyncher) runStorageRefreshWorker(ctx context.Context) error { + // In ethereum, this is normally done with get tries, but since we don't have that here, we'll have to be + // creative. This does mean that this is impressively inefficient. + var job *storageRangeJob + + for { + if job == nil { + requestloop: + for { + contractDoneChecker := s.contractRangeDone + if s.storageRangeJobCount > 0 { + contractDoneChecker = nil // So that it never complete as there are job to be done + } + + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(JobDuration): + s.log.Infow("waiting for more refresh job", "count", s.storageRangeJobCount) + case <-contractDoneChecker: + // Its done... + return nil + case job = <-s.storageRefreshJob: + break requestloop + } + } + } + + bigIntAdd := job.startAddress.BigInt(&big.Int{}) + bigIntAdd = (&big.Int{}).Add(bigIntAdd, big.NewInt(1)) + elem := fp.NewElement(0) + limitAddr := felt.NewFelt((&elem).SetBigInt(bigIntAdd)) + var err error + + stateRoot := s.currentGlobalStateRoot + s.snapServer.GetContractRange(ctx, &spec.ContractRangeRequest{ + Domain: 0, // What do this do? + StateRoot: core2p2p.AdaptHash(stateRoot), + Start: core2p2p.AdaptAddress(job.startAddress), + End: core2p2p.AdaptAddress(limitAddr), + ChunksPerProof: 10000, + })(func(response *ContractRangeStreamingResult, _err error) bool { + if response.Range == nil && response.RangeProof == nil { + // State root missing. + return false + } + + if len(response.Range) == 0 { + // Unexpected behaviour + return false + } + + err = VerifyGlobalStateRoot(stateRoot, response.ClassesRoot, response.ContractsRoot) + if err != nil { + // Root verification failed + // TODO: Ban peer + return false + } + + paths := make([]*felt.Felt, len(response.Range)) + values := make([]*felt.Felt, len(response.Range)) + + for i, rangeValue := range response.Range { + paths[i] = p2p2core.AdaptAddress(rangeValue.Address) + values[i] = CalculateRangeValueHash(rangeValue) + } + + proofs := P2pProofToTrieProofs(response.RangeProof) + _, err = VerifyTrie(response.ContractsRoot, paths, values, proofs, core.GlobalTrieHeight, crypto.Pedersen) + if err != nil { + // The peer should get penalised in this case + return false + } + + job.storageRoot = p2p2core.AdaptHash(response.Range[0].Storage) + newClass := p2p2core.AdaptHash(response.Range[0].Storage) + if newClass != job.classHash { + err := s.queueClassJob(ctx, newClass) + if err != nil { + return false + } + } + + err = s.queueStorageRangeJobJob(ctx, job) + if err != nil { + return false + } + + job = nil + + return true + }) + + if err != nil { + s.log.Errorw("Error with contract range", "err", err) + // Well... need to figure out how to determine if its a temporary error or not. + // For sure, the state root can be outdated, so this need to restart + continue + } + } +} diff --git a/sync/snapsyncer_test.go b/sync/snapsyncer_test.go new file mode 100644 index 0000000000..b4d5495bd0 --- /dev/null +++ b/sync/snapsyncer_test.go @@ -0,0 +1,384 @@ +package sync + +import ( + "context" + "encoding/hex" + "errors" + "math/big" + "net/http" + "os" + "testing" + "time" + + "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/db/pebble" + "github.com/NethermindEth/juno/encoder" + "github.com/NethermindEth/juno/starknetdata" + "github.com/NethermindEth/juno/utils" + "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/stretchr/testify/assert" +) + +func hexToFelt(str string) *felt.Felt { + flt := felt.Zero.Clone() + + bint, ok := big.NewInt(0).SetString(str, 16) + if !ok { + panic("set fail") + } + + return flt.SetBigInt(bint) +} + +func V0ClassFromString(key, bytes string) (*felt.Felt, core.Class, error) { + clsHash := hexToFelt(key) + cls := &core.DeclaredClass{} + clsBytes, err := hex.DecodeString(bytes) + if err != nil { + return nil, nil, err + } + err = encoder.Unmarshal(clsBytes, cls) + if err != nil { + return nil, nil, err + } + + h, err := cls.Class.Hash() + if err != nil { + return nil, nil, err + } + + if !h.Equal(clsHash) { + return nil, nil, errors.New("recalculated hash missed") + } + + return clsHash, cls.Class, nil +} + +func V1ClassFromString(key, bytes string) (*felt.Felt, *felt.Felt, core.Class, error) { + clsHash := hexToFelt(key) + cls := &core.DeclaredClass{} + clsBytes, err := hex.DecodeString(bytes) + if err != nil { + return nil, nil, nil, err + } + err = encoder.Unmarshal(clsBytes, cls) + if err != nil { + return nil, nil, nil, err + } + compiledClsHash := cls.Class.(*core.Cairo1Class).Compiled.Hash() + + return clsHash, compiledClsHash, cls.Class, nil +} + +func TestSnapOfflineCopy(t *testing.T) { + scenarios := []struct { + name string + scenario func(t *testing.T, bc *blockchain.Blockchain) error + }{ + { + name: "basic contract", + scenario: func(t *testing.T, bc *blockchain.Blockchain) error { + expectedRoot := hexToFelt("07391bf3b040e9df8ad739f0b0be3aa2d83f6c5260f7b745549be2432122d369") + + key := new(felt.Felt).SetUint64(uint64(12)) + nonce := new(felt.Felt).SetUint64(uint64(1)) + classHash, cls, err := V0ClassFromString("000118cec7463a59c23203e8e4dd7769a914da30c68d7b5ff404531b1e85de9a", "a262417419d22165436c617373da00010005a5634162695908355b7b226d656d62657273223a205b7b226e616d65223a2022746f222c20226f6666736574223a20302c202274797065223a202266656c74227d2c207b226e616d65223a202273656c6563746f72222c20226f6666736574223a20312c202274797065223a202266656c74227d2c207b226e616d65223a2022646174615f6f6666736574222c20226f6666736574223a20322c202274797065223a202266656c74227d2c207b226e616d65223a2022646174615f6c656e222c20226f6666736574223a20332c202274797065223a202266656c74227d5d2c20226e616d65223a20224163636f756e7443616c6c4172726179222c202273697a65223a20342c202274797065223a2022737472756374227d2c207b22696e70757473223a205b7b226e616d65223a20227075626c69634b6579222c202274797065223a202266656c74227d5d2c20226e616d65223a2022636f6e7374727563746f72222c20226f757470757473223a205b5d2c202274797065223a2022636f6e7374727563746f72227d2c207b22696e70757473223a205b5d2c20226e616d65223a20226765745075626c69634b6579222c20226f757470757473223a205b7b226e616d65223a20227075626c69634b6579222c202274797065223a202266656c74227d5d2c202273746174654d75746162696c697479223a202276696577222c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a2022696e746572666163654964222c202274797065223a202266656c74227d5d2c20226e616d65223a2022737570706f727473496e74657266616365222c20226f757470757473223a205b7b226e616d65223a202273756363657373222c202274797065223a202266656c74227d5d2c202273746174654d75746162696c697479223a202276696577222c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a20226e65775075626c69634b6579222c202274797065223a202266656c74227d5d2c20226e616d65223a20227365745075626c69634b6579222c20226f757470757473223a205b5d2c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a202268617368222c202274797065223a202266656c74227d2c207b226e616d65223a20227369676e61747572655f6c656e222c202274797065223a202266656c74227d2c207b226e616d65223a20227369676e6174757265222c202274797065223a202266656c742a227d5d2c20226e616d65223a2022697356616c69645369676e6174757265222c20226f757470757473223a205b7b226e616d65223a2022697356616c6964222c202274797065223a202266656c74227d5d2c202273746174654d75746162696c697479223a202276696577222c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a202263616c6c5f61727261795f6c656e222c202274797065223a202266656c74227d2c207b226e616d65223a202263616c6c5f6172726179222c202274797065223a20224163636f756e7443616c6c41727261792a227d2c207b226e616d65223a202263616c6c646174615f6c656e222c202274797065223a202266656c74227d2c207b226e616d65223a202263616c6c64617461222c202274797065223a202266656c742a227d5d2c20226e616d65223a20225f5f76616c69646174655f5f222c20226f757470757473223a205b5d2c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a2022636c6173735f68617368222c202274797065223a202266656c74227d5d2c20226e616d65223a20225f5f76616c69646174655f6465636c6172655f5f222c20226f757470757473223a205b5d2c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a2022636c6173735f68617368222c202274797065223a202266656c74227d2c207b226e616d65223a202273616c74222c202274797065223a202266656c74227d2c207b226e616d65223a20227075626c69634b6579222c202274797065223a202266656c74227d5d2c20226e616d65223a20225f5f76616c69646174655f6465706c6f795f5f222c20226f757470757473223a205b5d2c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a202263616c6c5f61727261795f6c656e222c202274797065223a202266656c74227d2c207b226e616d65223a202263616c6c5f6172726179222c202274797065223a20224163636f756e7443616c6c41727261792a227d2c207b226e616d65223a202263616c6c646174615f6c656e222c202274797065223a202266656c74227d2c207b226e616d65223a202263616c6c64617461222c202274797065223a202266656c742a227d5d2c20226e616d65223a20225f5f657865637574655f5f222c20226f757470757473223a205b7b226e616d65223a2022726573706f6e73655f6c656e222c202274797065223a202266656c74227d2c207b226e616d65223a2022726573706f6e7365222c202274797065223a202266656c742a227d5d2c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b5d2c20226e616d65223a20224465636c617265642066726f6d20737461726b6e65742d7273207465737420636173652e2054696d657374616d7020286d73293a2031373131353832303039313838222c20226f757470757473223a205b5d2c202274797065223a202266756e6374696f6e227d5d6750726f6772616d793c284834734941414141414141412f2b783943322f634e72726f583948317863576d5864654870455253436b347534447254316c6a487a725764377535744130476a346468437870705a5363356a692f7a334137316d394b416b6b714c473432344b6e4c4f78527678652f4637382b496e383432675442512f73364b5678424437626f504966684544755033683062427774764d5137656d6e386467512b5734446141454336584337542f30742f425a39682f6a3849325050794237706373764a706463516966326a326a624273344e486c4d76736c706237323147383942656c666a6164672b33537849394f796b5657517372414c31726245392f376f41564c686f53437a4c6c69776c507476586c4b4b537134716c48624b5a55744768566554514f4954337a4c4a6b6a4271555551674d616d316c5151714a4f46554a4147415755424b6e337131703668344f7138394e59756e66753270565478646249565955756a734b4b5262386c4e536350334834696d70503555534143575957715559434b624967735169715341774e616e5a456b52644a55686c316b76734e644b61503671515670755a677945506d39516979347773536a424742424e49326f726a637858483530363776344e7531736845395239486b456b7049673631434734524f7563534f6d39704c53716574736d66743869586e3235735564747943434f454c5063317556734a7441774c4e703961567074773546582b6d774e632f342b55513031516576455747735246626f6f6774326f42783759354161674c75636562686434704768634e743377677531546e7173715a7062585974566c3253714972697369642b793276754f53565663574553792b4f7555705546585366756e43463575394c6149754730477754466554355656347a556161526b56546678656d3772506d754a726c597749534668645a446d6c4f5a3630574e6c42625a75485244744b6f586e4a514a715a504a56536855793143574e546757514c546974503247736b6b6c57313770777970614330466d3163582f635856474d6a76792f66364a36736c6551434e373457596b534e6b2b65484e4a446c6953383057665966636d377233384c4553346e4941666876723447517154776a5052495336544f703450434c4c6d6a756e34624f347332494935434547346f4973355a67785361386d51505763554951765067516339354d30584e7651646579693366306f313252705278357a443076464f5334624e2b68415754374845303459387836383370314470525132684a794c3776536d476264664949467a6969495355572b77646f4e4e6369766a4636636b672f70374e6a35544f465542553464767049304e4c4357632f386c7a676762794b6b354b72496d51416d41523750636d5a7264457335454f37526c367834316a49732f73533061666c6452716b437335754370326d423278776335475568334b4a73376c507035656e353963514f6d4a616b7a6e47646771354c667077717a4b4c61514d337252466e63556e6d503230786b6a303178317369622f466b3169534c4a3539676332694375376d713744564d4f3366554536476d487153334d6f5a6d317851757073396c345a5934586869594443396a7978723779344a52574e4e67766e4f55435179635653396e66726a736d586736356a4758656453334575706d6e6c4f677a5a3869455459396756564c6178456846776673396e5a666a6f335044424c4b5633746355333044733667726b514578366b6b594b6a484535386151396c5045665772576e3036676841756e706f544e6d6566625a64315a49586c62342b7644424f7a3571452b702b4758634f6b4c31736a6c6e623833612f756a744a53497871392b756b563251775959737257354e586f4d64586b79573276547132476e6a626f57425853725765726535637757327874562b7437583378642f53324d4b74512b426d4964733539365a54702f61475139647967626459744b6254746637692f2f4d7049316c3250794e39573442355738696973667375746448627379383776634e596d6a324246665a6e4a6a4a502b64764d32564f72396251374b4266675254335034656d61796154546d3132716e7532623264774e36764b587873547849335165426370493042656875386845383472574c3352702f52524f716e656e59734b61744d4a3676713270456b62447377734661336b4f2f687231742f773845787636553463563172752b6e4e374f76356e644244556a357a39586e2f2f38615a4a4d655a39666258384f4f6777474f73756553656a6f7359586d38712b436959357061613030647461614858734d75397459526332395a516e62336e2b664a323368665a78652f7166592f6474336877417a6177686265304939542f6d625a713143327154686b4578654a3053397a524a616643496f50714b597435366139594a5238645371397a354f5552786443737a30694b634b3771707072684c7561734475755333734c56386d3069472f706168464a327a5261665852755130657a3847746f6e37783973394979616e4a78594a6151366277756549706d347a4b38784d352f6e375178436d624d375758585052585437546b6659666e4a52337261627a6b50745a502f4b58466b31554a37442b3769644c4a546453334255783033444c7538457955734d4d7855664e77544c527273625062453533576e4d6d66335a786248792f6f364a4e357a675556746d75646c75346b72426455366c2b786c34304275362f767164317a496b425044515a6b582b4756516b4d69512f6f6b4f702f636f5675566c674e65497731795371727272546932384f59645a3034734257667a62616b323650636e574b7278336479576d6370544c517334784d554357304f6d2b4c68303935574c796647522b64505352395937315a726d6e6a38744c6270654739764456374b307a307137374c7972465a6258315a5939355a667a3149707837342b4e6f2f6c6a734571434d4d354f6c4e6d7742597469467162765231353478317a2f6e766b66306a2b5a7634697a4b4441506b6b39426e412b2b4438496b48666e4845556a482f33486b7278665a515463503747456466666e4e3237773358686b787533746759524b6665497646692b3953474a3776737a674f3569766d7876353677334c7363654a46487a353545547678765342616e2f6a7268346431654f4b745675757373747233652f4657537452797466376b4a70486e66776a434f3763344c65655049322b7a665a6a3966526574487a64484c773177624279746c3875594a656b665831504f325a4a464c50535a477977793772352b2f5a6f434a6e5565507a36344c4578596c4c5077346f2b2f684839356151534c2b47544677712f796244367742332f7a705966502f4958795051564f3454436e51726854426a4d4975566967565a644c61507a77796f432f68366b6f2f485759424f456a632f3331356b735133686d764447674553794d302f713842444c614b6d51454f58564a34684b53612f4b657753374852746a703944704a436d77356566306966705342633579332b4576766561755865652b466978614b54374939554e70486e4a79394b2f2f43712f4d6578555137594a4e4772564938716677744a4a767458794a4b5339324a3858426450313174312b6c524568617169676b4d4b4a455a4856516770696b4c5770746b76367a7557754f6b4446726e65596847784f4435416762654a564a473671556671625749616f6a634c3056744151505446374232363842746b716f6a6630696a2b426a6d4e4362444b436244374a79424f3170463378397949655973444648795650425742597a304372354c5245445175424932706d4b412f52554843446c6a53475830716f695a36525a3352305a41314b644e4d4159656566486144634c6b2b51456c5871464f524d39586e51776f71476c4b6d705a516247683038624e5a525967514a69354c316568582f487634654c7150316739475654586e4a76667559424b76594b495a366363796978413343684e3278365065772f76654c6443363837376950352b6c6a343157326550434d2f324f387654352f4d2f73396e426550357274482b5744444d2f37376c54452f4e705a2f53596639345830316774674931346d78596e46734a506465614b776a672f33723056735a79647049416630782f3372796c3553702f3233384649514c7777734e4c2f4972417a494d786e2b6c5164544c666c386e39797a697649424f666739584c4c784c376d505843786475454334436e3858474b2b4f3346393678416234374e6c374d6a52384d3739694136622f7a635438593050676870526c39393534372f69526552386d4c7259414d7a69752f6766652f67666370377755742f325759476247386432487258665237794437377138634657786976754750512b392f672b31524978666f316c58356c485a79716b5046584137342f4e6e7066414f6d71393858766f57455978694c342b4c4265764f6a6b356a6962346f7847392b72583262567275722b632f2f7a4c6439384e4547454f4559484569494263496c424268507771784576752b395967586e4a2f556c6a4169726c4c746c4a4b722b30524b396b324253646541564a6979507a6f7065484944576c4d55657266796955307054336c6b327a42764658632f2f5671784d4a352f354e6a3979305448536a444e6e7847624474396245505169504346783973357031634765673563777435794759536f7a755a6577326b75306865316b50716438642f356b3231452f6537592b503333334563577362513267424e58352b564c57786866542f34793657516c7970506c61484b547963354e516968466465596e59566d71674b69682b466c523253314b30436665597548477756336f4a593852792b59702f333254524366464f7441397a6964772b356f623562467239794257694672623054317a736e336e35434f4c677555584e7964754e31536c6f695a516646616c5a796538464246575a367771374253557151565579687530746f7068393051436b4561436c69572f61707079486836676b6932362f734e6d774237545630364365497a7a68474d7138473053636f4d6b556d546e426c6b75676b794b4e657a62754f3644463453756d3735522f2f656e794e7473574253332f7a3778313247635249392b736f3655536e3031797a48354e66715353776339465a64334c486e374f4638462f742f59463565464b514675784a4c484b46526947676e7656566e4e45767a2b654934664e326b416a382f446845564c7a326361474366696a464d64573546716a46636d57366c2b57744e70334d396d622b34384b5a74422f4b7533436859335731632b656e6f746362334731486b71766c3333593871336c7a445856654b795671366c7448657275626b372b4352734c70692f38694a56646d756c50474c32737475734c7a3852753576562b6f7369743756636d2f54474932492f6d5974795866615a2b592b70436f38335779786f74756b502f767068453678593548356b55527973772b773270424f4954724a384c614d2b347931395875556d574c4177435a5942692f4c7371507a74354e5433313439686b6a31637344674a51693870344b34334c50773332327a594b6c3155354f2b6472494a353545566674754f4f6a61506b5334375057775665665053314973667972544e767454714e49752b4c4b706f6467483538502b5a4e4e542f6d4b794565746f34554c2f756a5845484654546a3953482f7834767652474b74412b74467441385a6f6e4331492f59677253702f683350697069524c37324468614d48386465636b36796d324b66553559464871727a42704b694d7648304d2f6f36774a36636872643564713566467974334e4237714b6c782b39586a7a4f546e7055356e75784e6571695675336c6e7a523857715574764a754e2b536b79342b4d7050614471735067613068557072362f525a3436674461464b462b69725a655935656363776438662f5131485249482f326246316e483561373473364a62322b634e6d466668424969483132704347394f766251344f697a3856544e7371315237576c4c3231484f597271387231582f476f326b794d706d766e61614e715470754b4163695474776e63466b6457685468586c77424c4b635a314873397973716c426652437a65724d4f5970527239306b69524842766c732f7a7637372b724f704c306639304657775a6830472f394e2b662f663362316b33747864585a36635a4e682f756974486c6d6851535734624c336242464a4a4b4c654f435a74777047506151523332544d313376376b6d656464556b614777622b6f613838303543666f4e2f7453707a31793334354478436857414f747a43626747326377384f304f55657474416c3345526a544e4e64724c773464752b392b46374d5656516d43347050316f3447655850724750764e374a37453748617a6f646e38646f44316d474652474e69744874445931554d4c754a5152566f654d744d4630347278564d7153783235633332784b6e554f79744b4959706f78674668796f577a6876367a634366794d434c79644275337756635a664f75376a39747a647245547475736d7a74562f5a5a64655876496f6c75764e70533077394930424e4d715a6b454c36787a7954437972562b6b314f4b2b716648516f6578576573704a5874783872576d365474705a2f444e676e4166577551687a53372f6137645632705342634d534c6347536c426a7538643855396d32674c7031647575473869714a7541725834492f523457314465572f46574b676c76623953334e7a61334e6d4d5a584f714d6f4932303451365a4466383978744b4b35666762666471577a57546e6b7776726f6c6851504f2b6c314b39466f75434e74302f37706e59395a38697957764e524c63444b56365664523874444d6f754a4f6147515174626f395a777355516f624c2f62304e575166586f3757626f584b305450376a4850784d716d6a70367857505155312f6459533768736456395646423579696861434d617746646c446a2b514d6179684f55763534763947743969774a52316538662b45332f4f36545562515478593962434968734232696a6b7a594c662f464b7035446d634a623949464f4144376a534c7674666c6c30556430495955584753594e6d4a363147466e4d73584f363746527459695858562b4a394f6a38736446512b484b506435747469554a7435565048526d57625842524b597950383245696c754e743354685a65346f6c764f336549754e73634c436c7a4f436c2f574c69706c765057576478756d6f37566c464444326334433766614f6c7254643159464c57694676634d504c5279787853396e322b6e6978506f4e4a486251516b3271756f672b494c736652774e4854306345332b4c49336f50723357457472304e52746436615533545841466b667a394659353668393431493730456246476273654a7062695a3151465a7a5072364f302b6b3155656943554e6f6e4435792f7178784d4165724c36794e3670336f67695156324372474957644c54326a486e4259524171684f657835754578456170387567784c732f704d62724a2b2b62335576612f5a696d6a534749616e35675a3178714e766d6b6671485673354a39674b54524c517a307259674d30326431677630694d734f31452f664e49306837424f55326a774741717636674d436f6c553979334e2b43337542444f3572396769777366746f6744474768316b6251746d6659566b57486169446b384138386654476974342f70554f67424a574764566461564d59742f57794f2f46735942435830454856424644314e47543077564f77685246653350476b504d666149776a4f32363649456d595930334c35597969717a35734f6b69785074774e58645a5574466149577731566739586772696164696e6b6753664e514c77654c5174466d79516454454f3468717476596f4a7978375473736462653859634435306b67694e416d337651324f4761564a3071316d776d50316b6e563434656f51463463616d734c366f456c4575706268794e74615638537a6e485a5371475a7a386c465042494a793547743041753472376733774e4d49685442622f4268413958517763494778554842794150586b73374f67544a596f6635486441467248436f5835525357325361674156477165506e4d4f4c65354d7630305a32656e5a426b676865736477794c583743424c57766f64586b6448424c5a4b6a69546133446738595a68485372717668677a5954394a357172336a62554e6a675a7732315a6734494664656164714c31447147684a386f6d6e45416a6c7a4c5052676279767a484f497154466d506c6e754f595470365a4c50496370475a5a3944774b654964454a48466159597936477061735362314f3546683366596e76685157563053677479762b5049674a694679784663395176426c7734675130473133365371496b306f566e625052314f6e623552474e3054494f494d34785a4c4830435754786b4576767065374d5736324b4c326d4b376d3252446d32687a324156704b4c42586e6f416a76366b366576675a316b4b4a4d716335616648494f76345a627538785641736f2f5744577a6c584c316c6e66315573315a5449776b6268484757307654436630544743676c624e39796e7444366c46664d725877632b7378303242446d6368425074672f45595874564e48384537456b345432346d366464626a3634735a7374647a3544476a7239686c4e58474e3842522f574a436c6243355547572b694865536732304b4a7961743176495a78453534754976464e3178446d32595a797146796a476148674e78444d4b666b393768713655614455594d6866554d2f6d632f3974783441504867557470774c5448684575524d736b713434346c6274356d356e366f376d56435237667a72474d6134304e356b43624a455271494e446957506f6a50784c394d39445736697669476a714a4c58394b35366d2b676e79534e79596a795673472f5765566244456a62483136507338594b6d6a476d32414c54554f4b6d62394677624938305852724d746850634e3575566b7433554b343071726d6d734d38362f316e4c6a64687373306c364f61324d625a6173643050344d35305771436b4348627869412b6b786378484d2b61314a316e76704f6f73786631706b39634169597845664648536b39517530504f7366357031686253732b443144373438716d7969566a374f714150346a4e7847452b5a55385369367741396c687676496538766d3072636f4e322b69446766324979303342613255646262416133724445383330486149707970394f6d7834414f6f334f3159526f61367a51565878543248633964746870545774646a647356615753745869697a6c5a736537364453493665466632334c3472562f666b624264506d64633263514c332f545538717335664b5a42584a6669376e6c615671764838567a492f304333546962474c71544b494b33317373716c56457a76474e6f394b494a69704e5a724d444e6656555a356a30716d6f625a504f7970733651336e62665534523039595a314a564830627142706a4f5574784650623172305833794d4a6a35752b58777a53346d4844646653516c5472646c463057782f3230744336395364615264386334594c5251467a467655664538324a7a4f3836536f4e486d654861697050552b4753612f6e6159503874674b526c742b65747852622b4b6632576f585a753176376c4c386c71775a4369374d6f4958364b67714b425335327148495957736e626b3545364d637943654e69655734644c6b785371774768354136424d346e6157584e6c6c3666523448356a656e4a792f4150533535636f5261335679396430783945647941492b39447a7372504d3852314f68757978374c4f4d2b6a6e2f4f6e30346d596d4d59767a39587056444a4b6673674f6f6e7079666e70316476627538646339663830696f6a58314d676c57634830377068556e4d6861464177657a3644424938696f416443486e387a633169395a6c6f515a496e3576623639504c6d394f7a322f4f72532f58563266584e2b64616b734652347346597265536474444e6b5965316273675442416d45746765387848626b664934766456717a54334c6f414e6a396e3478536835626c677a34367a434a504439527a4458724d4f527057415a687675542b77487a662b794374376a487a4e7965374c6f496d4f486d43376c69536659334549735669517655473351596752576f4b3665716770776c4b6a614c6c7876584368627552556457493351567877714b344155474e676f6b764e2b37464838547569726c5a5269444f2f6f4f58334c762b7736593657683731336f3954694465724946486874547053487531484667584c4c323765694656766b524d6a596563524f6b437030355463373843346a394a4267757532656d45506b396f5663312f50666a7039643348726e72352b63333770586c39647a4754584633314a31757a6d35757a713876623636714c4d6c6b7241434a6945517475303644674d3156527743357336434e69576a51572b394f6a4e304344475464676d77596841697a6a386d77446c594c2b5a335a362b50723039625446676d545a463143456163467a507a6d626e763836756d7a6967435947446f47574e6d654a364a7279566b596b74674b457a446a4246304a3164766e737a757a373938574c573168396f49575253694d646a61594b47324b6255786e536b2b715367753259595968505a44674a4541343675475461784361684a7942677475767a31394f4c3864524f7968527a4c495251354934522f64656d574b76726a3665335a4c7955627239326232635873375062717573344d784a6a61784c62354a77374a6f7578485a6746694f6841544f4d49356453324d2b6b394f45674c3937767a79316e62666e50366a727261595078734336344a74305661696357345962456439566e5367374a3653414e7a2b61716f7367416b49374e6c50325a372b4e31686a464d416a57314473576a766e4a6343543034734c392b7079566f66304177534551704236435141784e5446466a6f3354774977416443787341684d35674a725541734243474750627472464e4b48535179512b75413154737337413558467854462b7132494448734c705159456c583851624655713743666859757767392f6462392f386e43304176776758586a2b376e396452382f3275667272732f6662723668394b69306c78356c3974526b35454655537a5730617852596235376d5964684d6e4a7a482b622f6d2f47304c2b47424338443745464d7a4d664755614d366a3958525675614d6a70757a484b433452326e5249754e456573726c69687254714c68506262635269783958676e32777733743059697a2b4c53766d6a5a52544855697248332f7a6d436a6156313571644f504553316764793033364b452f3948704d57416c736267756f474e64456d364279347546304d6b546e43544e3675597859733175464946576943306167456d774a30775834445537386943423855496f536b6f67796a6449454c58567762524767646f524863665449466c6544746b74574337754e632f427537624777636533644d563539704c784f3577525632642f4c6a7864585a333977302b5a664f6a67734947382f2f77425a7547353738504456492b2b6674374d59397633542f666e56644c7a625938706c76412f5454727774345a503174646e5a322b6a66333750547436646e353754394c336d2f304d6c39672b656e647859563766586f375339466b736d3455412f6c466f704749327678305646546b38647a63706a685331584e2f6d6c3363336a524b49574f78544c537a4b304742396e316547647a3545585268364b356b2f486d3271564d624f356151387543737a3058506a754357666c45517149346553306d527558694c52625a684e624444796b7436386d6f6537343864554931554676716f6e644174334c4730547246524b59652b2f4668504748334d6b6e4c6357505331514e703550345638534f6241485533712b704e5555766370477a45576136466e6273512b7369686d4c67735867536354765575483341466f4e483168484e7946624f4575676f39757842356b2f57527276444a4264515634326c7173574c4c3463764130506758774235626c31616d372b65583870306153596c6f413263676b784548414d57324c6d4a594e6251524e44444841467157556d4b426a44314b656743644a4a2b6f6b374b5574614c696c596837634257467938754f70564e747342634275517936444d5a4b4d344f343854457a4a74536b50516d4e567567444343394946464337744c644265796e70562f71375033422b76336c31796d3232484f716d325938644e306c4f73426d723477346774486e3232474b38744c56422f567256704d497231795177666c737a535630336850612b46745a663972717234746974433561586b53504e35777556736c59774479426835386175616b314271556f6777736a41325459494968546145446b5a4549516c73347072646e745a786a515a35436571314a57416847396870466f7363514e4a38466d426745384276524a5043424c7646424169314562574a4137464b6461754a43645635636b774c32644345304c524e4378424348597763322b356f6535504239426230546a327962594951635279467a6f4d6d70683770565a564d49646474597471623947356d5a322f647439666e623262754c2b632f2f7a4b553252505470425942314449684a546143304a4c723168716934654c713738496b4947705245316b6d744d634c50435068657661473078356f49355846537761662b62573857447044623266474b6e36532b62576466386e636f544a61647374646d6b484f39727a6b7a514a6371434f334355737850466e326e4f4c6d5a6336712b73544c6e525870436d4c33337977612b44696c612f7779594b7646466f513645654536584c44457a626b624a356f474b485761486b735a75772b50717a486971515053515644384c356c4e69423643556b447142425566676f78566e796f5952574a795145396453436d6f655070346b524e537046556a685045576a4b51416a7159416a7152413571536e726b52754641573176475145485673346f366735794369556b2f6255612b4f63696f4f4c524c76763774546457375851504a364d63766b387873756d4d4d5a5438765365746b724d67657a364e4f695362786e755844714d492b52796444693656413548465370476836524c355a42556f574a30574c7055446b7556617853656344396a523857544c636f714a427869564e795274393864307962794a36755a633268346f74536754636b366b5633323149677068342b6d4a396335395256716f624d564d4c70495374626a7a68756f553161424e7072412f47485a565659306b615670476773486a6c626b774b363334765343486b3034383131764955396947636d4c34547249554e61336e41777465735a384e325233636e332b4c556f4b434b4f4a475873795546324e684134476b694876365974384f316f4f65495756422b347855696f676a4b616b6242564d316756336f2b5455686a616577494f716a764c6f4f6f5169615a57757739687972787a6a34363243685a6377742b517a3439746c4b2f6241565057744b4f6a32517835502b6450576e5265426e3768656471664e796576415430367a667772767a48554d62327a4e79587a4774596e595235647a5248425053302f4950764647614f2b4779676f4939652f4d6c616f696a532f4e6865657173584f3668322b6d6d5839796b2f3579396868397a4b56624471336662434d792f4f5430347530763956345671523649426a435a73305934517a57644d6c4b48334772472b634769784945416d686851597072556f52695a4671474f36546732784a6851684b434673474e5347324948453873424e7258544879317145644e474e734359514e4f47556c2b5331636e366558627031672b3173616c46546366434546416241456f4a73544331694f5859447157576952437754574935694e6f4555477961794849734730494d5452745a4e67515532635332415345416a5350726e33563977496751516830454b4d5451746b3167457874546a444731495849733238516d734b434667415653796c4d6562476861794c4653455347454d63493267526151366869716b3656306d6b305069456b30376572363961782b754e4950446a477a646851434d51594f67734347446f41494f7a6243304d514f784a62745348333457456335346e68754966696144732f4a7577442b3965677433496a4677654a5275736a534843376e4d427665566c7942326d353630674d6c7671702b415a316449624f664536733777417764647941716334466a453271482b497633493866692f636978654439794c4e3650484176324936657634767172375a4e6864712b532b71756b353156616635554f713645747059626458315565774f5a564e3348373353446f706d4f363432576c53546d6b5478497a446e57662f642b48532b2b7836674c6c2b756f4279746e33596f5332546963382b684375503457757433483965792b385930657978785679454d706c374a3041476a375a4533624a38346e44496f39697056527945464244424c3133754b686672434e425864347266665872374e704637625a7462454c69514f6a594a6a5164516b786932525a4d4d335762554949524a6852595567764241524a4d54756334746977437147506246447555596b6f77535663344e7161325a574a67645234384c4547432f767959683056546c7377443759315355652b497232725a7757564c467248515a356c7a716271646b76616a33337776546c34734e385a666a52632f574e386446326479766b2b486578733369547a2f517844657564734c594f366939654f6d5342703232763731613956546266484b536d492b53684a7a665a49776e316f53504f65694b70636d4c47315377694f6b64467947515671446e34482f6251762f766648584e4958653372676e6a734c574e4248786832446a73732f2b366e48425847385876577666456132384f56754e676a783348344c774d613569674d425552704530636730493063544a526a49323255696558624b5236456f326549414f4a396c4939684a6d6b2b6e43624449717a435a71595461337550467831744561585a4a5263545a5269374e396f70414a7446704555653837465033476350655a6f395232774259685a77306f332f445665346d4f4b415671745966476143554b47706349356171423270665361346c4c44597a7963596b4c344b446a55704e6935626a55432b6a4a34314b5475733634314e525579636a55784b4d784d6a56427930576d396d684a64357a5a33506a49424b453266317a6852693430745566726b34564d614a7047467545364b587556633270726c78734e72776871427778756f55686378693843574e4b31746f633254352b4a42772b6b336c33564845652b6947664e5878612b394661355561584f6e5a6f443767616861554f376a75466976643738464f55307156433447372b66655651506b5457794a316a4e314f4472696859316f50343654494c776b626e2b65764d6c434f38557036774652744a6e6772624c3944595376724c714b6b305656316e6a7072527165546d6b492b56594e2f5545693947786f7337476572335a655864624a6b4b496e4667764b6c7642302b38505a324e663263747631703932537a4c6c745a7a55496970466563635331397449344b73664e4f6c746c44412b31516d584c654946304b2b383843346476776c574c44705a42664e784975686951774d6c616a4b70666f2f37684f3277597a344c4874555675304f387030364248634a395831492f54464339505668552b745678616f6a33306a6778346e4d3174572f55526e795970765931576731682f4469585270694f55554d59784f356e6478323676727a32314965716f592b59762f3749797076716846487668716d6837626c67757967466a463859443247545779754c5157766b564e7a6265506f2b2b33696372774b2f6665465058344b3176613837456d2b683349364a78586f704b396d5a4a5a57644455704e61566b75423755784a2f6d374175565274516a792f59694b366942662b68664b67796731725a30483864536e52597357374542716d397675306d58336170522b6432776f34684e6177494b755a67365958565845372b61675754634855715a737541476b525a6630796e70776770752b543476575649484b31712b376c5543716c72395079576e30756c57672b69516e5663775a4c626d796935703730386b455a2b2b572b4136675862386b355a665469352f633745365247765051746978434c5174516b77494859306a6b767359727762636854374458332f7863514654484f5a385a56484f48565846626b55677564782f6333552b3830567853573553786774444e646f4156713244623453704b732b635358415074666f385261694a2f67704f4453684c4b572b673871584e756d6e6659655a4a48325453786a377041542f4c2b764362752f64376556324c66622f6d33697657705373416c44646d4f754b5371373762536c56424f6630646369576b6633344e6c2f77705a736b32726b6e586b3362475430396576727a6d4e634f713371316e32304e3055586153384f66324865334e3764583336383877397635323961562b73686762446352667377757368444e78356f4e7236566f36574666575832506457712f6a6b4c4533767a713475623639507a3237646d396e46374f7a327176346c4f5149324e6845314c596f5241685a466c6b326741366c743257686f3564754e313175747a745a686d734432666851744271445a2f63582b39636947392f6b6c73567758554c2f6d642b3976316d48637147653150355756526c474172575a43644367546b714a2b724b784c4f4d303955375a6966724957374c51377a72516c672b643669305655486a456a5575597243353475483256507353396c4a6c306f75595667785436644c6b6656422f412f6f503565376b596e4f5a305950334d466f4a61314a423153365a6d2b5974414533544764764c79655863782b50723264755a6e503472737143416e466a675549704c614a414c47524352314349616e3371456d3571693365432b6a2b636e72352b6d4a3233594864524e5143454e73414f52545a466b5957785a6a59466e4a73596c4948517073514374485147536b396c4c793975506f6e487a6e464343434c324468645167353950396d4e675731573679394b716c594d6e637756352f43486e5844374541494a34427a333636677262493169645a6c7163376b724c343764657938575849317a764c516265367445334f476d7170644b624232356973365841304851455765375a716e3433475730667469745377635074716a4d2f654239504b494b4e5762792b5636375033373242393674534c74646637394f4e414749614d54336371314e33574b706e306f6e6c6a4a336e30756e6c44585033707a6675724e665a3563644b544e454e7144414d514845474a676d70705a447261477a6c7271785051544a37435072507a3149595052592f2f474266556c5872714777707151444246556a74645a55453176672b79375a33484e57397650733173327631373538392b6248726977415770594e624163435970735949784d41614a6b32426369304d45434132744c727854622b322f4d3373357662307a64764f784b5237476f345946495445555349685731694934695269536d3171594d6f70685244694a55546b5a53534e41756258627670596e313263384d6e4a4b55697a622b6f44553348424e53324c4174687977596d674a526747774a45724b4872362f71704b4a6575765851514242334c775359424e7343574179304b55336b3446454154554543514252316b49774147372b72724a655a6d39762f657a5337506871514373594f6741327a5478445a3048456942436242704132776a427a735767656c504b545534565a7778424e332b777a322f2f4f6d7167777754556f41633077454957424169434579415276462f2b772f3335767a6e793950626439657a66754e414e73484974414367466f5555456f516f425941366733635a64754e6e79592b7274662f68386a48316345716573674669736b79326a6d63346f3231375741556b6e4d785766536e47353243387a4c566b756a4a4e4a374c693038456950352b6270362b34345535373938766e6266444134735237324978696351646c6375505a6f707259666970344a6a47684668396135483934687453576f795a472b38777071576e313368672b3831597246703157466d594b664e614254476c504e557a546d564d446a573572346e4b68512f59485a55743849657068733650516b4c306b56326251786d315235426872536730776b7870544864654535745245704e32672b4a7a6f6d5950444d716f4f5565706956576346547866504e2b6b456850376f4d4e57434d3656784e5a464e5a313174544c724e7134735854664e77554162574b5531747a504a4e4c43376665786f62752f313848693758716b77576f36653070787a4664465a55777464744f33573652386e336f4f796b49612b526a50467449766e73427156576a6c4f6748492f6b5a7a3739684e2f55506f4254346e3048596c724c32654b5a306e777153446732704c364a78756467764d77507a4a726134745042596b65733258377a30647244366a74416f716277652b716b75546a2f386672302b703935493831515634746c456d5361466b5955573943784961595559476f526841473148597770686735434e72497341676b6d796e744b4e614c3475306e49704151675168334c5242524153436c794d4352457562586e497068485876516c5851637261555a312f475465706f4c6b2b625567636f67664b65676e3749623554326739764a3639765467396d376c6e463664642b3563495977417359434a494c637330715930524a51425332795245656276796d6d31576e732f4f306c6c5255704561674830726878613366444f37664f322b6d6433636e50343863322b7655732f63345a464e414b6b44624570734535695736546941516f66593245475753536b463674756f5a62503739657a30646363574c6743324134686a6d68536b73303441734243773051673358434c392b2f5835626366474d6349327367476b47434362576743446c46644343484b6f7163347343786476386f4d4a62746358384f5a4c7242774a4f6b434e31634e6b4c6430667666472b724e626567754e70657678544f616831474144664f3330766478424374395479547947756d6264516b33706c2f47547874344a6b4f5036325a53794c515775657a364639704a7831685638707464626a586a6c534869304d2f6c7141632f7673704975646771532f5230482f7958686941505936745a5776335455666a6a6451743143534537386d393546466364352f4b6967673331382f686f6d722f41334d672f665a58544a783939367a4e7532374e4a432f4e753372754534694c34793950456c755a30383956776e3639313451757347695071446e5173467748666f4e776e54634b646735382b6e2f33303559686a632f72364639554c7a4136557869574161505a5a49426f3674396647414e314e633333723047456c673537544d74715974503947416d4658424e5635735045543279586c6670745535673939464b31633846586d5a486b52776278624f5878636b6b346f637643564b6a6675715349494b6d7a4d664e626856617835783148763353666559374c55392b45546e3642585764676a523443424939727379694f41346f6472704d70775476574f4a79756b7a796f34324256762f61527158755a4c7467715a2b484c6f4e7174466361676e6b59726f6c445a62642f71722f34636e632f32306950784346694172664577544c574e7732416c4852514a756545396562525643494f79757879554b625a3661444d387051325151646c616e645176495170703770395965356f463956414e744a4a6361464e3561616179505134716c366f422b53716d6e54324f4b764771357264565a4f5171527857453438576c395548564e5a704364796d4b754b307243366e5a51467454737653376253717652373572616e3630366b43787a675856514d796b5763716357687853467867682b4f4853764b3633552f78786b7444754f716c5a5846584a5734696c315343312b474a654c416b485244526c4458524c6764453947564e564b63444b73363563714e79647964336c355a57443152466f753643326c4361567972757658323052744a6f6c39554e37544238566f322b6271655661626d2b524b6d4764514a7656494d2f316831314170503052356b4244687a4e4c4f4b50634764435a4866364979767a5236617750384a542b4b4e50323132796a467a63766f4a636830504b73497a33534255775931775366356474307533574f6766615042674833474735734a78414c666354434f4b613048506c43485335726a593053642b4675322f73786a4b2b693354354c6b773766526557394631457a48643972664c71506e696864316363414c4448712f35324679566f766b5478324e6a583573623078636e4a43776c54422b624a7257663670553733484d68634b324572774a63784c78583457416438434c756e57455a416a676f4347516c3149356a7135684168444b4e7532424443384952583445783076626f51686e47586c6d633955306b5342665048704979475a6672426f6d676475655831596c554354764d757135644776674e6d424c455272684d6a755139696f2b6a414f6971796f53512f525234364b57705748696f506e56545a6c3676314a77363146526271314e666a5071724839324352317a534c4c6a41577838463878647a5958323979786f37574778622b6d323032624257454a77575a4a36763838346555334c37665430353362496d38567834697667355858397959725a5a48373750706c4a4474676d3069356e734a57786a4a5a364e73684b754c4657476e4b6c5a4548486d786f7070534f4163745666615a2b59384a6b78646d78466959524636595a4372626c434a464e536c5361367755726563697876665a436e332b654666755a6f5350713958582f776b4141502f2f304551477335726f4151413d6945787465726e616c7388a2664f6666736574841bffffffffffffc6611bffffffffffffffff1bffffffffffffffff1b07fffffffffc2c706853656c6563746f72841b30b9224cd18a4e7c1bc71f0c828480ecb01b8de0747c8cfdb4821b04c9700ad999c431a2664f6666736574841bffffffffffffa8a11bffffffffffffffff1bffffffffffffffff1b07fffffffffa32b06853656c6563746f72841ba6951e73bb6dfe241b05e5ff539dbeebe71bdd254888370955b41b043ef1349908885ea2664f6666736574841bffffffffffffbb811bffffffffffffffff1bffffffffffffffff1b07fffffffffb73906853656c6563746f72841bb22ea4840c873b8b1baa7fdf9303e1d7731b704e592f223c3de61b00bda65049efea59a2664f6666736574841bffffffffffffcde11bffffffffffffffff1bffffffffffffffff1b07fffffffffcabf06853656c6563746f72841b279f5f4dc8b4c7b31b6ac884a41a1b5bd71bc1307f4321f00b881b04bc8012967069c6a2664f6666736574841bffffffffffffc1811bffffffffffffffff1bffffffffffffffff1b07fffffffffbd9906853656c6563746f72841bc735e82ed1e23e011b9ee8c8991bbc1d151bacb56cf03bbaac5a1b00186ed02a71bd65a2664f6666736574841bffffffffffffb4211bffffffffffffffff1bffffffffffffffff1b07fffffffffaf6306853656c6563746f72841b04b9244ab52a4b811bffdbe496a2c48be11b8a5d0118067bf13c1b07102404b615fdd5a2664f6666736574841bffffffffffffc9c11bffffffffffffffff1bffffffffffffffff1b07fffffffffc65d06853656c6563746f72841bf6c53d175ebbcd9f1bdd07ee6aca2299561bd457faeea1e288671b0754ebc0b261e0b1a2664f6666736574841bffffffffffffaf611bffffffffffffffff1bffffffffffffffff1b07fffffffffaa5706853656c6563746f72841b4298606763e030411ba7525ed75dc70c951b01c0a0b4d48ac5061b0624e8d006b62cda6a4c3148616e646c657273806c436f6e7374727563746f727381a2664f6666736574841bffffffffffffd2411bffffffffffffffff1bffffffffffffffff1b07fffffffffcf6506853656c6563746f72841bd1c59afcd3f9742d1b083befda3709a3ed1b3142bfd1003b62e21b041f80cbf54be289") + assert.NoError(t, err) + + return bc.Store( + &core.Block{ + Header: &core.Header{ + Hash: &felt.Zero, + ParentHash: &felt.Zero, + GlobalStateRoot: expectedRoot, + }, + }, + nil, + &core.StateUpdate{ + NewRoot: expectedRoot, + OldRoot: &felt.Zero, + StateDiff: &core.StateDiff{ + StorageDiffs: nil, + Nonces: map[felt.Felt]*felt.Felt{ + *key: nonce, + }, + DeployedContracts: map[felt.Felt]*felt.Felt{ + *key: classHash, + }, + DeclaredV0Classes: nil, + DeclaredV1Classes: nil, + ReplacedClasses: nil, + }, + }, + map[felt.Felt]core.Class{ + *classHash: cls, + }) + }, + }, + { + name: "basic with storage", + scenario: func(t *testing.T, bc *blockchain.Blockchain) error { + expectedStateRoot := hexToFelt("05e69846cb070d495023a6700872cce39a9be8457b2243fcf8d87d8edf7c8784") + + key := new(felt.Felt).SetUint64(uint64(12)) + nonce := new(felt.Felt).SetUint64(uint64(1)) + classHash, cls, err := V0ClassFromString("000118cec7463a59c23203e8e4dd7769a914da30c68d7b5ff404531b1e85de9a", "a262417419d22165436c617373da00010005a5634162695908355b7b226d656d62657273223a205b7b226e616d65223a2022746f222c20226f6666736574223a20302c202274797065223a202266656c74227d2c207b226e616d65223a202273656c6563746f72222c20226f6666736574223a20312c202274797065223a202266656c74227d2c207b226e616d65223a2022646174615f6f6666736574222c20226f6666736574223a20322c202274797065223a202266656c74227d2c207b226e616d65223a2022646174615f6c656e222c20226f6666736574223a20332c202274797065223a202266656c74227d5d2c20226e616d65223a20224163636f756e7443616c6c4172726179222c202273697a65223a20342c202274797065223a2022737472756374227d2c207b22696e70757473223a205b7b226e616d65223a20227075626c69634b6579222c202274797065223a202266656c74227d5d2c20226e616d65223a2022636f6e7374727563746f72222c20226f757470757473223a205b5d2c202274797065223a2022636f6e7374727563746f72227d2c207b22696e70757473223a205b5d2c20226e616d65223a20226765745075626c69634b6579222c20226f757470757473223a205b7b226e616d65223a20227075626c69634b6579222c202274797065223a202266656c74227d5d2c202273746174654d75746162696c697479223a202276696577222c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a2022696e746572666163654964222c202274797065223a202266656c74227d5d2c20226e616d65223a2022737570706f727473496e74657266616365222c20226f757470757473223a205b7b226e616d65223a202273756363657373222c202274797065223a202266656c74227d5d2c202273746174654d75746162696c697479223a202276696577222c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a20226e65775075626c69634b6579222c202274797065223a202266656c74227d5d2c20226e616d65223a20227365745075626c69634b6579222c20226f757470757473223a205b5d2c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a202268617368222c202274797065223a202266656c74227d2c207b226e616d65223a20227369676e61747572655f6c656e222c202274797065223a202266656c74227d2c207b226e616d65223a20227369676e6174757265222c202274797065223a202266656c742a227d5d2c20226e616d65223a2022697356616c69645369676e6174757265222c20226f757470757473223a205b7b226e616d65223a2022697356616c6964222c202274797065223a202266656c74227d5d2c202273746174654d75746162696c697479223a202276696577222c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a202263616c6c5f61727261795f6c656e222c202274797065223a202266656c74227d2c207b226e616d65223a202263616c6c5f6172726179222c202274797065223a20224163636f756e7443616c6c41727261792a227d2c207b226e616d65223a202263616c6c646174615f6c656e222c202274797065223a202266656c74227d2c207b226e616d65223a202263616c6c64617461222c202274797065223a202266656c742a227d5d2c20226e616d65223a20225f5f76616c69646174655f5f222c20226f757470757473223a205b5d2c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a2022636c6173735f68617368222c202274797065223a202266656c74227d5d2c20226e616d65223a20225f5f76616c69646174655f6465636c6172655f5f222c20226f757470757473223a205b5d2c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a2022636c6173735f68617368222c202274797065223a202266656c74227d2c207b226e616d65223a202273616c74222c202274797065223a202266656c74227d2c207b226e616d65223a20227075626c69634b6579222c202274797065223a202266656c74227d5d2c20226e616d65223a20225f5f76616c69646174655f6465706c6f795f5f222c20226f757470757473223a205b5d2c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a202263616c6c5f61727261795f6c656e222c202274797065223a202266656c74227d2c207b226e616d65223a202263616c6c5f6172726179222c202274797065223a20224163636f756e7443616c6c41727261792a227d2c207b226e616d65223a202263616c6c646174615f6c656e222c202274797065223a202266656c74227d2c207b226e616d65223a202263616c6c64617461222c202274797065223a202266656c742a227d5d2c20226e616d65223a20225f5f657865637574655f5f222c20226f757470757473223a205b7b226e616d65223a2022726573706f6e73655f6c656e222c202274797065223a202266656c74227d2c207b226e616d65223a2022726573706f6e7365222c202274797065223a202266656c742a227d5d2c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b5d2c20226e616d65223a20224465636c617265642066726f6d20737461726b6e65742d7273207465737420636173652e2054696d657374616d7020286d73293a2031373131353832303039313838222c20226f757470757473223a205b5d2c202274797065223a202266756e6374696f6e227d5d6750726f6772616d793c284834734941414141414141412f2b783943322f634e72726f583948317863576d5864654870455253436b347534447254316c6a487a725764377535744130476a346468437870705a5363356a692f7a334137316d394b416b6b714c473432344b6e4c4f78527678652f4637382b496e383432675442512f73364b5678424437626f504966684544755033683062427774764d5137656d6e386467512b5734446141454336584337542f30742f425a39682f6a3849325050794237706373764a706463516966326a326a624273344e486c4d76736c706237323147383942656c666a6164672b33537849394f796b5657517372414c31726245392f376f41564c686f53437a4c6c69776c507476586c4b4b537134716c48624b5a55744768566554514f4954337a4c4a6b6a4271555551674d616d316c5151714a4f46554a4147415755424b6e337131703668344f7138394e59756e66753270565478646249565955756a734b4b5262386c4e536350334834696d70503555534143575957715559434b624967735169715341774e616e5a456b52644a55686c316b76734e644b61503671515670755a677945506d39516979347773536a424742424e49326f726a637858483530363776344e7531736845395239486b456b7049673631434734524f7563534f6d39704c53716574736d66743869586e3235735564747943434f454c5063317556734a7441774c4e703961567074773546582b6d774e632f342b55513031516576455747735246626f6f6774326f42783759354161674c75636562686434704768634e743377677531546e7173715a7062585974566c3253714972697369642b793276754f53565663574553792b4f7555705546585366756e43463575394c6149754730477754466554355656347a556161526b56546678656d3772506d754a726c597749534668645a446d6c4f5a3630574e6c42625a75485244744b6f586e4a514a715a504a56536855793143574e546757514c546974503247736b6b6c57313770777970614330466d3163582f635856474d6a76792f66364a36736c6551434e373457596b534e6b2b65484e4a446c6953383057665966636d377233384c4553346e4941666876723447517154776a5052495336544f703450434c4c6d6a756e34624f347332494935434547346f4973355a67785361386d51505763554951765067516339354d30584e7651646579693366306f313252705278357a443076464f5334624e2b68415754374845303459387836383370314470525132684a794c3776536d476264664949467a6969495355572b77646f4e4e6369766a4636636b672f70374e6a35544f465542553464767049304e4c4357632f386c7a676762794b6b354b72496d51416d41523750636d5a7264457335454f37526c367834316a49732f73533061666c6452716b437335754370326d423278776335475568334b4a73376c507035656e353963514f6d4a616b7a6e47646771354c667077717a4b4c61514d337252466e63556e6d503230786b6a303178317369622f466b3169534c4a3539676332694375376d713744564d4f3366554536476d487153334d6f5a6d317851757073396c345a5934586869594443396a7978723779344a52574e4e67766e4f55435179635653396e66726a736d586736356a4758656453334575706d6e6c4f677a5a3869455459396756564c6178456846776673396e5a666a6f335044424c4b5633746355333044733667726b514578366b6b594b6a484535386151396c5045665772576e3036676841756e706f544e6d6566625a64315a49586c62342b7644424f7a3571452b702b4758634f6b4c31736a6c6e623833612f756a744a53497871392b756b563251775959737257354e586f4d64586b79573276547132476e6a626f57425853725765726535637757327874562b7437583378642f53324d4b74512b426d4964733539365a54702f61475139647967626459744b6254746637692f2f4d7049316c3250794e39573442355738696973667375746448627379383776634e596d6a324246665a6e4a6a4a502b64764d32564f72396251374b4266675254335034656d61796154546d3132716e7532623264774e36764b587873547849335165426370493042656875386845383472574c3352702f52524f716e656e59734b61744d4a3676713270456b62447377734661336b4f2f687231742f773845787636553463563172752b6e4e374f76356e644244556a357a39586e2f2f38615a4a4d655a39666258384f4f6777474f73756553656a6f7359586d38712b436959357061613030647461614858734d75397459526332395a516e62336e2b664a323368665a78652f7166592f6474336877417a6177686265304939542f6d625a713143327154686b4578654a3053397a524a616643496f50714b597435366139594a5238645371397a354f5552786443737a30694b634b3771707072684c7561734475755333734c56386d3069472f706168464a327a5261665852755130657a3847746f6e37783973394979616e4a78594a6151366277756549706d347a4b38784d352f6e375178436d624d375758585052585437546b6659666e4a52337261627a6b50745a502f4b58466b31554a37442b3769644c4a546453334255783033444c7538457955734d4d7855664e77544c527273625062453533576e4d6d66335a786248792f6f364a4e357a675556746d75646c75346b72426455366c2b786c34304275362f767164317a496b425044515a6b582b4756516b4d69512f6f6b4f702f636f5675566c674e65497731795371727272546932384f59645a3034734257667a62616b323650636e574b7278336479576d6370544c517334784d554357304f6d2b4c68303935574c796647522b64505352395937315a726d6e6a38744c6270654739764456374b307a307137374c7972465a6258315a5939355a667a3149707837342b4e6f2f6c6a734571434d4d354f6c4e6d7742597469467162765231353478317a2f6e766b66306a2b5a7634697a4b4441506b6b39426e412b2b4438496b48666e4845556a482f33486b7278665a515463503747456466666e4e3237773358686b787533746759524b6665497646692b3953474a3776737a674f3569766d7876353677334c7363654a46487a353545547678765342616e2f6a7268346431654f4b745675757373747233652f4657537452797466376b4a70486e66776a434f3763344c65655049322b7a665a6a3966526574487a64484c773177624279746c3875594a656b665831504f325a4a464c50535a477977793772352b2f5a6f434a6e5565507a36344c4578596c4c5077346f2b2f684839356151534c2b47544677712f796244367742332f7a705966502f4958795051564f3454436e51726854426a4d4975566967565a644c61507a77796f432f68366b6f2f485759424f456a632f3331356b735133686d764447674553794d302f713842444c614b6d51454f58564a34684b53612f4b657753374852746a703944704a436d77356566306966705342633579332b4576766561755865652b466978614b54374939554e70486e4a79394b2f2f43712f4d6578555137594a4e4772564938716677744a4a767458794a4b5339324a3858426450313174312b6c524568617169676b4d4b4a455a4856516770696b4c5770746b76367a7557754f6b4446726e65596847784f4435416762654a564a473671556671625749616f6a634c3056744151505446374232363842746b716f6a6630696a2b426a6d4e4362444b436244374a79424f3170463378397949655973444648795650425742597a304372354c5245445175424932706d4b412f52554843446c6a53475830716f695a36525a3352305a41314b644e4d4159656566486144634c6b2b51456c5871464f524d39586e51776f71476c4b6d705a516247683038624e5a525967514a69354c316568582f487634654c7150316739475654586e4a76667559424b76594b495a366363796978413343684e3278365065772f76654c6443363837376950352b6c6a343157326550434d2f324f387654352f4d2f73396e426550357274482b5744444d2f37376c54452f4e705a2f53596639345830316774674931346d78596e46734a506465614b776a672f33723056735a79647049416630782f3372796c3553702f3233384649514c7777734e4c2f4972417a494d786e2b6c5164544c666c386e39797a697649424f666739584c4c784c376d505843786475454334436e3858474b2b4f3346393678416234374e6c374d6a52384d3739694136622f7a635438593050676870526c39393534372f69526552386d4c7259414d7a69752f6766652f67666370377755742f325759476247386432487258665237794437377138634657786976754750512b392f672b31524978666f316c58356c485a79716b5046584137342f4e6e7066414f6d71393858766f57455978694c342b4c4265764f6a6b356a6962346f7847392b72583262567275722b632f2f7a4c6439384e4547454f4559484569494263496c424268507771784576752b395967586e4a2f556c6a4169726c4c746c4a4b722b30524b396b324253646541564a6979507a6f7065484944576c4d55657266796955307054336c6b327a42764658632f2f5671784d4a352f354e6a3979305448536a444e6e7847624474396245505169504346783973357031634765673563777435794759536f7a755a6577326b75306865316b50716438642f356b3231452f6537592b503333334563577362513267424e58352b564c57786866542f34793657516c7970506c61484b547963354e516968466465596e59566d71674b69682b466c523253314b30436665597548477756336f4a593852792b59702f333254524366464f7441397a6964772b356f623562467239794257694672623054317a736e336e35434f4c677555584e7964754e31536c6f695a516646616c5a796538464246575a367771374253557151565579687530746f7068393051436b4561436c69572f61707079486836676b6932362f734e6d774237545630364365497a7a68474d7138473053636f4d6b556d546e426c6b75676b794b4e657a62754f3644463453756d3735522f2f656e794e7473574253332f7a3778313247635249392b736f3655536e3031797a48354e66715353776339465a64334c486e374f4638462f742f59463565464b514675784a4c484b46526947676e7656566e4e45767a2b654934664e326b416a382f446845564c7a326361474366696a464d64573546716a46636d57366c2b57744e70334d396d622b34384b5a74422f4b7533436859335731632b656e6f746362334731486b71766c3333593871336c7a445856654b795671366c7448657275626b372b4352734c70692f38694a56646d756c50474c32737475734c7a3852753576562b6f7369743756636d2f54474932492f6d5974795866615a2b592b70436f38335779786f74756b502f767068453678593548356b55527973772b773270424f4954724a384c614d2b347931395875556d574c4177435a5942692f4c7371507a74354e5433313439686b6a31637344674a51693870344b34334c50773332327a594b6c3155354f2b6472494a353545566674754f4f6a61506b5334375057775665665053314973667972544e767454714e49752b4c4b706f6467483538502b5a4e4e542f6d4b794565746f34554c2f756a5845484654546a3953482f7834767652474b74412b74467441385a6f6e4331492f59677253702f683350697069524c37324468614d48386465636b36796d324b66553559464871727a42704b694d7648304d2f6f36774a36636872643564713566467974334e4237714b6c782b39586a7a4f546e7055356e75784e6571695675336c6e7a523857715574764a754e2b536b79342b4d7050614471735067613068557072362f525a3436674461464b462b69725a655935656363776438662f5131485249482f326246316e483561373473364a62322b634e6d466668424969483132704347394f766251344f697a3856544e7371315237576c4c3231484f597271387231582f476f326b794d706d766e61614e715470754b4163695474776e63466b6457685468586c77424c4b635a314873397973716c426652437a65724d4f5970527239306b69524842766c732f7a7637372b724f704c306639304657775a6830472f394e2b662f663362316b33747864585a36635a4e682f756974486c6d6851535734624c336242464a4a4b4c654f435a74777047506151523332544d313376376b6d656464556b614777622b6f613838303543666f4e2f7453707a31793334354478436857414f747a43626747326377384f304f55657474416c3345526a544e4e64724c773464752b392b46374d5656516d43347050316f3447655850724750764e374a37453748617a6f646e38646f44316d474652474e69744874445931554d4c754a5152566f654d744d4630347278564d7153783235633332784b6e554f79744b4959706f78674668796f577a6876367a634366794d434c79644275337756635a664f75376a39747a647245547475736d7a74562f5a5a64655876496f6c75764e70533077394930424e4d715a6b454c36787a7954437972562b6b314f4b2b716648516f6578576573704a5874783872576d365474705a2f444e676e4166577551687a53372f6137645632705342634d534c6347536c426a7538643855396d32674c7031647575473869714a7541725834492f523457314465572f46574b676c76623953334e7a61334e6d4d5a584f714d6f4932303451365a4466383978744b4b35666762666471577a57546e6b7776726f6c6851504f2b6c314b39466f75434e74302f37706e59395a38697957764e524c63444b56365664523874444d6f754a4f6147515174626f395a777355516f624c2f62304e575166586f3757626f584b305450376a4850784d716d6a70367857505155312f6459533768736456395646423579696861434d617746646c446a2b514d6179684f55763534763947743969774a52316538662b45332f4f36545562515478593962434968734232696a6b7a594c662f464b7035446d634a623949464f4144376a534c7674666c6c30556430495955584753594e6d4a363147466e4d73584f363746527459695858562b4a394f6a38736446512b484b506435747469554a7435565048526d57625842524b597950383245696c754e743354685a65346f6c764f336549754e73634c436c7a4f436c2f574c69706c765057576478756d6f37566c464444326334433766614f6c7254643159464c57694676634d504c5279787853396e322b6e6978506f4e4a486251516b3271756f672b494c736652774e4854306345332b4c49336f50723357457472304e52746436615533545841466b667a394659353668393431493730456246476273654a7062695a3151465a7a5072364f302b6b3155656943554e6f6e4435792f7178784d4165724c36794e3670336f67695156324372474957644c54326a486e4259524171684f657835754578456170387567784c732f704d62724a2b2b62335576612f5a696d6a534749616e35675a3178714e766d6b6671485673354a39674b54524c517a307259674d30326431677630694d734f31452f664e49306837424f55326a774741717636674d436f6c553979334e2b43337542444f3572396769777366746f6744474768316b6251746d6659566b57486169446b384138386654476974342f70554f67424a574764566461564d59742f57794f2f46735942435830454856424644314e47543077564f77685246653350476b504d666149776a4f32363649456d595930334c35597969717a35734f6b69785074774e58645a5574466149577731566739586772696164696e6b6753664e514c77654c5174466d79516454454f3468717476596f4a7978375473736462653859634435306b67694e416d337651324f4761564a3071316d776d50316b6e563434656f51463463616d734c366f456c4575706268794e74615638537a6e485a5371475a7a386c465042494a793547743041753472376733774e4d49685442622f4268413958517763494778554842794150586b73374f67544a596f6635486441467248436f5835525357325361674156477165506e4d4f4c65354d7630305a32656e5a426b676865736477794c583743424c57766f64586b6448424c5a4b6a69546133446738595a68485372717668677a5954394a357172336a62554e6a675a7732315a6734494664656164714c31447147684a386f6d6e45416a6c7a4c5052676279767a484f497154466d506c6e754f595470365a4c50496370475a5a3944774b654964454a48466159597936477061735362314f3546683366596e76685157563053677479762b5049674a694679784663395176426c7734675130473133365371496b306f566e625052314f6e623552474e3054494f494d34785a4c4830435754786b4576767065374d5736324b4c326d4b376d3252446d32687a324156704b4c42586e6f416a76366b366576675a316b4b4a4d716335616648494f76345a627538785641736f2f5744577a6c584c316c6e66315573315a5449776b6268484757307654436630544743676c624e39796e7444366c46664d725877632b7378303242446d6368425074672f45595874564e48384537456b345432346d366464626a3634735a7374647a3544476a7239686c4e58474e3842522f574a436c6243355547572b694865536732304b4a7961743176495a78453534754976464e3178446d32595a797146796a476148674e78444d4b666b393768713655614455594d6866554d2f6d632f3974783441504867557470774c5448684575524d736b713434346c6274356d356e366f376d56435237667a72474d6134304e356b43624a455271494e446957506f6a50784c394d39445736697669476a714a4c58394b35366d2b676e79534e79596a795673472f5765566244456a62483136507338594b6d6a476d32414c54554f4b6d62394677624938305852724d746850634e3575566b7433554b343071726d6d734d38362f316e4c6a64687373306c364f61324d625a6173643050344d35305771436b4348627869412b6b786378484d2b61314a316e76704f6f73786631706b39634169597845664648536b39517530504f7366357031686253732b443144373438716d7969566a374f714150346a4e7847452b5a55385369367741396c687676496538766d3072636f4e322b69446766324979303342613255646262416133724445383330486149707970394f6d7834414f6f334f3159526f61367a51565878543248633964746870545774646a647356615753745869697a6c5a736537364453493665466632334c3472562f666b624264506d64633263514c332f545538717335664b5a42584a6669376e6c615671764838567a492f304333546962474c71544b494b33317373716c56457a76474e6f394b494a69704e5a724d444e6656555a356a30716d6f625a504f7970733651336e62665534523039595a314a564830627142706a4f5574784650623172305833794d4a6a35752b58777a53346d4844646653516c5472646c463057782f3230744336395364615264386334594c5251467a467655664538324a7a4f3836536f4e486d654861697050552b4753612f6e6159503874674b526c742b65747852622b4b6632576f585a753176376c4c386c71775a4369374d6f4958364b67714b425335327148495957736e626b3545364d637943654e69655734644c6b785371774768354136424d346e6157584e6c6c3666523448356a656e4a792f4150533535636f5261335679396430783945647941492b39447a7372504d3852314f68757978374c4f4d2b6a6e2f4f6e30346d596d4d59767a39587056444a4b6673674f6f6e7079666e70316476627538646339663830696f6a58314d676c57634830377068556e4d6861464177657a3644424938696f416443486e387a633169395a6c6f515a496e3576623639504c6d394f7a322f4f72532f58563266584e2b64616b734652347346597265536474444e6b5965316273675442416d45746765387848626b664934766456717a54334c6f414e6a396e3478536835626c677a34367a434a504439527a4458724d4f527057415a687675542b77487a662b794374376a487a4e7965374c6f496d4f486d43376c69536659334549735669517655473351596752576f4b3665716770776c4b6a614c6c7876584368627552556457493351567877714b344155474e676f6b764e2b37464838547569726c5a5269444f2f6f4f58334c762b7736593657683731336f3954694465724946486874547053487531484667584c4c323765694656766b524d6a596563524f6b437030355463373843346a394a4267757532656d45506b396f5663312f50666a7039643348726e72352b63333770586c39647a4754584633314a31757a6d35757a713876623636714c4d6c6b7241434a6945517475303644674d3156527743357336434e69576a51572b394f6a4e304344475464676d77596841697a6a386d77446c594c2b5a335a362b50723039625446676d545a463143456163467a507a6d626e763836756d7a6967435947446f47574e6d654a364a7279566b596b74674b457a446a4246304a3164766e737a757a373938574c573168396f49575253694d646a61594b47324b6255786e536b2b715367753259595968505a44674a4541343675475461784361684a7942677475767a31394f4c3864524f7968527a4c495251354934522f64656d574b76726a3665335a4c7955627239326232635873375062717573344d784a6a61784c62354a77374a6f7578485a6746694f6841544f4d49356453324d2b6b394f45674c3937767a79316e62666e50366a727261595078734336344a74305661696357345962456439566e5367374a3653414e7a2b61716f7367416b49374e6c50325a372b4e31686a464d416a57314473576a766e4a6343543034734c392b7079566f66304177534551704236435141784e5446466a6f3354774977416443787341684d35674a725541734243474750627472464e4b48535179512b75413154737337413558467854462b7132494448734c705159456c583851624655713743666859757767392f6462392f386e43304176776758586a2b376e396452382f3275667272732f6662723668394b69306c78356c3974526b35454655537a5730617852596235376d5964684d6e4a7a482b622f6d2f47304c2b47424338443745464d7a4d664755614d366a3958525675614d6a70757a484b433452326e5249754e456573726c69687254714c68506262635269783958676e32777733743059697a2b4c53766d6a5a52544855697248332f7a6d436a6156313571644f504553316764793033364b452f3948704d57416c736267756f474e64456d364279347546304d6b546e43544e3675597859733175464946576943306167456d774a30775834445537386943423855496f536b6f67796a6449454c58567762524767646f524863665449466c6544746b74574337754e632f427537624777636533644d563539704c784f3577525632642f4c6a7864585a333977302b5a664f6a67734947382f2f77425a7547353738504456492b2b6674374d59397633542f666e56644c7a625938706c76412f5454727774345a503174646e5a322b6a66333750547436646e353754394c336d2f304d6c39672b656e647859563766586f375339466b736d3455412f6c466f704749327678305646546b38647a63706a685331584e2f6d6c3363336a524b49574f78544c537a4b304742396e316547647a3545585268364b356b2f486d3271564d624f356151387543737a3058506a754357666c45517149346553306d527558694c52625a684e624444796b7436386d6f6537343864554931554676716f6e644174334c4730547246524b59652b2f4668504748334d6b6e4c6357505331514e703550345638534f6241485533712b704e5555766370477a45576136466e6273512b7369686d4c67735867536354765575483341466f4e483168484e7946624f4575676f39757842356b2f57527276444a4264515634326c7173574c4c3463764130506758774235626c31616d372b65583870306153596c6f413263676b784548414d57324c6d4a594e6251524e44444841467157556d4b426a44314b656743644a4a2b6f6b374b5574614c696c596837634257467938754f70564e747342634275517936444d5a4b4d344f343854457a4a74536b50516d4e567567444343394946464337744c644265796e70562f71375033422b76336c31796d3232484f716d325938644e306c4f73426d723477346774486e3232474b38744c56422f567256704d497231795177666c737a535630336850612b46745a663972717234746974433561586b53504e35777556736c59774479426835386175616b314271556f6777736a41325459494968546145446b5a4549516c73347072646e745a786a515a35436571314a57416847396870466f7363514e4a38466d426745384276524a5043424c7646424169314562574a4137464b6461754a43645635636b774c32644345304c524e4378424348597763322b356f6535504239426230546a327962594951635279467a6f4d6d70683770565a564d49646474597471623947356d5a322f647439666e623262754c2b632f2f7a4b553252505470425942314449684a546143304a4c723168716934654c713738496b4947705245316b6d744d634c50435068657661473078356f49355846537761662b62573857447044623266474b6e36532b62576466386e636f544a61647374646d6b484f39727a6b7a514a6371434f334355737850466e326e4f4c6d5a6336712b73544c6e525870436d4c33337977612b44696c612f7779594b7646466f513645654536584c44457a626b624a356f474b485761486b735a75772b50717a486971515053515644384c356c4e69423643556b447142425566676f78566e796f5952574a795145396453436d6f655070346b524e537046556a685045576a4b51416a7159416a7152413571536e726b52754641573176475145485673346f366735794369556b2f6255612b4f63696f4f4c524c76763774546457375851504a364d63766b387873756d4d4d5a5438765365746b724d67657a364e4f695362786e755844714d492b52796444693656413548465370476836524c355a42556f574a30574c7055446b7556617853656344396a523857544c636f714a427869564e795274393864307962794a36755a633268346f74536754636b366b5633323149677068342b6d4a396335395256716f624d564d4c70495374626a7a68756f553161424e7072412f47485a565659306b615670476773486a6c626b774b363334765343486b3034383131764955396947636d4c34547249554e61336e41777465735a384e325233636e332b4c556f4b434b4f4a475873795546324e684134476b694876365974384f316f4f65495756422b347855696f676a4b616b6242564d316756336f2b5455686a616577494f716a764c6f4f6f5169615a57757739687972787a6a34363243685a6377742b517a3439746c4b2f6241565057744b4f6a32517835502b6450576e5265426e3768656471664e796576415430367a667772767a48554d62327a4e79587a4774596e595235647a5248425053302f4950764647614f2b4779676f4939652f4d6c616f696a532f4e6865657173584f3668322b6d6d5839796b2f3579396868397a4b56624471336662434d792f4f5430347530763956345671523649426a435a73305934517a57644d6c4b48334772472b634769784945416d686851597072556f52695a4671474f36546732784a6851684b434673474e5347324948453873424e7258544879317145644e474e734359514e4f47556c2b5331636e366558627031672b3173616c46546366434546416241456f4a73544331694f5859447157576952437754574935694e6f4555477961794849734730494d5452745a4e67515532635332415345416a5350726e33563977496751516830454b4d5451746b3167457874546a444731495849733238516d734b434667415653796c4d6562476861794c4653455347454d63493267526151366869716b3656306d6b305069456b30376572363961782b754e4950446a477a646851434d51594f67734347446f41494f7a6243304d514f784a62745348333457456335346e68754966696144732f4a7577442b3965677433496a4677654a5275736a534843376e4d427665566c7942326d353630674d6c7671702b415a316449624f664536733777417764647941716334466a453271482b497633493866692f636978654439794c4e3650484176324936657634767172375a4e6864712b532b71756b353156616635554f713645747059626458315565774f5a564e3348373353446f706d4f363432576c53546d6b5478497a446e57662f642b48532b2b7836674c6c2b756f4279746e33596f5332546963382b684375503457757433483965792b385930657978785679454d706c374a3041476a375a4533624a38346e44496f39697056527945464244424c3133754b686672434e425864347266665872374e704637625a7462454c69514f6a594a6a5164516b786932525a4d4d335762554949524a6852595567764241524a4d54756334746977437147506246447555596b6f77535663344e7161325a574a67645234384c4547432f767959683056546c7377443759315355652b497232725a7757564c467248515a356c7a716271646b76616a33337776546c34734e385a666a52632f574e386446326479766b2b486578733369547a2f517844657564734c594f366939654f6d5342703232763731613956546266484b536d492b53684a7a665a49776e316f53504f65694b70636d4c47315377694f6b64467947515671446e34482f6251762f766648584e4958653372676e6a734c574e4248786832446a73732f2b366e48425847385876577666456132384f56754e676a783348344c774d613569674d425552704530636730493063544a526a49323255696558624b5236456f326549414f4a396c4939684a6d6b2b6e43624449717a435a71595461337550467831744561585a4a5263545a5269374e396f70414a7446704555653837465033476350655a6f395232774259685a77306f332f445665346d4f4b415671745966476143554b47706349356171423270665361346c4c44597a7963596b4c344b446a55704e6935626a55432b6a4a34314b5475733634314e525579636a55784b4d784d6a56427930576d396d684a64357a5a33506a49424b453266317a6852693430745566726b34564d614a7047467545364b587556633270726c78734e72776871427778756f55686378693843574e4b31746f633254352b4a42772b6b336c33564845652b6947664e5878612b394661355561584f6e5a6f443767616861554f376a75466976643738464f55307156433447372b66655651506b5457794a316a4e314f4472696859316f50343654494c776b626e2b65764d6c434f38557036774652744a6e6772624c3944595376724c714b6b305656316e6a7072527165546d6b492b56594e2f5545693947786f7337476572335a655864624a6b4b496e4667764b6c7642302b38505a324e663263747631703932537a4c6c745a7a55496970466563635331397449344b73664e4f6c746c44412b31516d584c654946304b2b383843346476776c574c44705a42664e784975686951774d6c616a4b70666f2f37684f3277597a344c4874555675304f387030364248634a395831492f54464339505668552b745678616f6a33306a6778346e4d3174572f55526e795970765931576731682f4469585270694f55554d59784f356e6478323676727a32314965716f592b59762f3749797076716846487668716d6837626c67757967466a463859443247545779754c5157766b564e7a6265506f2b2b33696372774b2f6665465058344b3176613837456d2b683349364a78586f704b396d5a4a5a57644455704e61566b75423755784a2f6d374175565274516a792f59694b366942662b68664b67796731725a30483864536e52597357374542716d397675306d58336170522b6432776f34684e6177494b755a67365958565845372b61675754634855715a737541476b525a6630796e70776770752b543476575649484b31712b376c5543716c72395079576e30756c57672b69516e5663775a4c626d796935703730386b455a2b2b572b4136675862386b355a665469352f633745365247765051746978434c5174516b77494859306a6b767359727762636854374458332f7863514654484f5a385a56484f48565846626b55677564782f6333552b3830567853573553786774444e646f4156713244623453704b732b635358415074666f385261694a2f67704f4453684c4b572b673871584e756d6e6659655a4a48325453786a377041542f4c2b764362752f64376556324c66622f6d33697657705373416c44646d4f754b5371373762536c56424f6630646369576b6633344e6c2f77705a736b32726b6e586b3362475430396576727a6d4e634f713371316e32304e3055586153384f66324865334e3764583336383877397635323961562b73686762446352667377757368444e78356f4e7236566f36574666575832506457712f6a6b4c4533767a713475623639507a3237646d396e46374f7a327176346c4f5149324e6845314c596f5241685a466c6b326741366c743257686f3564754e313175747a745a686d734432666851744271445a2f63582b39636947392f6b6c73567758554c2f6d642b3976316d48637147653150355756526c474172575a43644367546b714a2b724b784c4f4d303955375a6966724957374c51377a72516c672b643669305655486a456a5575597243353475483256507353396c4a6c306f75595667785436644c6b6656422f412f6f503565376b596e4f5a305950334d466f4a61314a423153365a6d2b5974414533544764764c79655863782b50723264755a6e503472737143416e466a675549704c614a414c47524352314349616e3371456d3571693365432b6a2b636e72352b6d4a3233594864524e5143454e73414f52545a466b5957785a6a59466e4a73596c4948517073514374485147536b396c4c793975506f6e487a6e464343434c324468645167353950396d4e675731573679394b716c594d6e637756352f43486e5844374541494a34427a333636677262493169645a6c7163376b724c343764657938575849317a764c516265367445334f476d7170644b624232356973365841304851455765375a716e3433475730667469745377635074716a4d2f654239504b494b4e5762792b5636375033373242393674534c74646637394f4e414749614d54336371314e33574b706e306f6e6c6a4a336e30756e6c44585033707a6675724e665a3563644b544e454e7144414d514845474a676d70705a447261477a6c7271785051544a37435072507a3149595052592f2f474266556c5872714777707151444246556a74645a55453176672b79375a33484e57397650733173327631373538392b6248726977415770594e624163435970735949784d41614a6b32426369304d45434132744c727854622b322f4d3373357662307a64764f784b5237476f345946495445555349685731694934695269536d3171594d6f70685244694a55546b5a53534e41756258627670596e313263384d6e4a4b55697a622b6f44553348424e53324c4174687977596d674a526747774a45724b4872362f71704b4a6575765851514242334c775359424e7343574179304b55336b3446454154554543514252316b49774147372b72724a655a6d39762f657a5337506871514373594f6741327a5478445a3048456942436242704132776a427a735767656c504b545534565a7778424e332b777a322f2f4f6d7167777754556f41633077454957424169434579415276462f2b772f3335767a6e793950626439657a66754e414e73484974414367466f5555456f516f425941366733635a64754e6e79592b7274662f68386a48316345716573674669736b79326a6d63346f3231375741556b6e4d785766536e47353243387a4c566b756a4a4e4a374c693038456950352b6270362b34345535373938766e6266444134735237324978696351646c6375505a6f707259666970344a6a47684668396135483934687453576f795a472b38777071576e313368672b3831597246703157466d594b664e614254476c504e557a546d564d446a573572346e4b68512f59485a55743849657068733650516b4c306b56326251786d315235426872536730776b7870544864654535745245704e32672b4a7a6f6d5950444d716f4f5565706956576346547866504e2b6b456850376f4d4e57434d3656784e5a464e5a313174544c724e7134735854664e77554162574b5531747a504a4e4c43376665786f62752f313848693758716b77576f36653070787a4664465a55777464744f33573652386e336f4f796b49612b526a50467449766e73427156576a6c4f6748492f6b5a7a3739684e2f55506f4254346e3048596c724c32654b5a306e777153446732704c364a78756467764d77507a4a726134745042596b65733258377a30647244366a74416f716277652b716b75546a2f386672302b703935493831515634746c456d5361466b5955573943784961595559476f526841473148597770686735434e72497341676b6d796e744b4e614c3475306e49704151675168334c5242524153436c794d4352457562586e497068485876516c5851637261555a312f475465706f4c6b2b625567636f67664b65676e3749623554326739764a3639765467396d376c6e463664642b3563495977417359434a494c637330715930524a51425332795245656276796d6d31576e732f4f306c6c5255704561674830726878613366444f37664f322b6d6433636e50343863322b7655732f63345a464e414b6b44624570734535695736546941516f66593245475753536b463674756f5a62503739657a30646363574c6743324134686a6d68536b73303441734243773051673358434c392b2f5835626366474d6349327367476b47434362576743446c46644343484b6f7163347343786476386f4d4a62746358384f5a4c7242774a4f6b434e31634e6b4c6430667666472b724e626567754e70657678544f616831474144664f3330766478424374395479547947756d6264516b33706c2f47547874344a6b4f5036325a53794c515775657a364639704a7831685638707464626a586a6c534869304d2f6c7141632f7673704975646771532f5230482f7958686941505936745a5776335455666a6a6451743143534537386d393546466364352f4b6967673331382f686f6d722f41334d672f665a58544a783939367a4e7532374e4a432f4e753372754534694c34793950456c755a30383956776e3639313451757347695071446e5173467748666f4e776e54634b646735382b6e2f33303559686a632f72364639554c7a4136557869574161505a5a49426f3674396647414e314e633333723047456c673537544d74715974503947416d4658424e5635735045543279586c6670745535673939464b31633846586d5a486b52776278624f5878636b6b346f637643564b6a6675715349494b6d7a4d664e626856617835783148763353666559374c55392b45546e3642585764676a523443424939727379694f41346f6472704d70775476574f4a79756b7a796f34324256762f61527158755a4c7467715a2b484c6f4e7174466361676e6b59726f6c445a62642f71722f34636e632f32306950784346694172664577544c574e7732416c4852514a756545396562525643494f79757879554b625a3661444d387051325151646c616e645176495170703770395965356f463956414e744a4a6361464e3561616179505134716c366f422b53716d6e54324f4b764771357264565a4f5171527857453438576c395548564e5a704364796d4b754b307243366e5a51467454737653376253717652373572616e3630366b43787a675856514d796b5763716357687853467867682b4f4853764b3633552f78786b7444754f716c5a5846584a5734696c315343312b474a654c416b485244526c4458524c6764453947564e564b63444b73363563714e79647964336c355a57443152466f753643326c4361567972757658323052744a6f6c39554e37544238566f322b6271655661626d2b524b6d4764514a7656494d2f316831314170503052356b4244687a4e4c4f4b50634764435a4866364979767a5236617750384a542b4b4e50323132796a467a63766f4a636830504b73497a33534255775931775366356474307533574f6766615042674833474735734a78414c666354434f4b613048506c43485335726a593053642b4675322f73786a4b2b693354354c6b773766526557394631457a48643972664c71506e696864316363414c4448712f35324679566f766b5478324e6a583573623078636e4a43776c54422b624a7257663670553733484d68634b324572774a63784c78583457416438434c756e57455a416a676f4347516c3149356a7135684168444b4e7532424443384952583445783076626f51686e47586c6d633955306b5342665048704979475a6672426f6d676475655831596c554354764d757135644776674e6d424c455272684d6a755139696f2b6a414f6971796f53512f525234364b57705748696f506e56545a6c3676314a77363146526271314e666a5071724839324352317a534c4c6a41577838463878647a5958323979786f37574778622b6d323032624257454a77575a4a36763838346555334c37665430353362496d38567834697667355858397959725a5a48373750706c4a4474676d3069356e734a57786a4a5a364e73684b754c4657476e4b6c5a4548486d786f7070534f4163745666615a2b59384a6b78646d78466959524636595a4372626c434a464e536c5361367755726563697876665a436e332b654666755a6f5350713958582f776b4141502f2f304551477335726f4151413d6945787465726e616c7388a2664f6666736574841bffffffffffffc6611bffffffffffffffff1bffffffffffffffff1b07fffffffffc2c706853656c6563746f72841b30b9224cd18a4e7c1bc71f0c828480ecb01b8de0747c8cfdb4821b04c9700ad999c431a2664f6666736574841bffffffffffffa8a11bffffffffffffffff1bffffffffffffffff1b07fffffffffa32b06853656c6563746f72841ba6951e73bb6dfe241b05e5ff539dbeebe71bdd254888370955b41b043ef1349908885ea2664f6666736574841bffffffffffffbb811bffffffffffffffff1bffffffffffffffff1b07fffffffffb73906853656c6563746f72841bb22ea4840c873b8b1baa7fdf9303e1d7731b704e592f223c3de61b00bda65049efea59a2664f6666736574841bffffffffffffcde11bffffffffffffffff1bffffffffffffffff1b07fffffffffcabf06853656c6563746f72841b279f5f4dc8b4c7b31b6ac884a41a1b5bd71bc1307f4321f00b881b04bc8012967069c6a2664f6666736574841bffffffffffffc1811bffffffffffffffff1bffffffffffffffff1b07fffffffffbd9906853656c6563746f72841bc735e82ed1e23e011b9ee8c8991bbc1d151bacb56cf03bbaac5a1b00186ed02a71bd65a2664f6666736574841bffffffffffffb4211bffffffffffffffff1bffffffffffffffff1b07fffffffffaf6306853656c6563746f72841b04b9244ab52a4b811bffdbe496a2c48be11b8a5d0118067bf13c1b07102404b615fdd5a2664f6666736574841bffffffffffffc9c11bffffffffffffffff1bffffffffffffffff1b07fffffffffc65d06853656c6563746f72841bf6c53d175ebbcd9f1bdd07ee6aca2299561bd457faeea1e288671b0754ebc0b261e0b1a2664f6666736574841bffffffffffffaf611bffffffffffffffff1bffffffffffffffff1b07fffffffffaa5706853656c6563746f72841b4298606763e030411ba7525ed75dc70c951b01c0a0b4d48ac5061b0624e8d006b62cda6a4c3148616e646c657273806c436f6e7374727563746f727381a2664f6666736574841bffffffffffffd2411bffffffffffffffff1bffffffffffffffff1b07fffffffffcf6506853656c6563746f72841bd1c59afcd3f9742d1b083befda3709a3ed1b3142bfd1003b62e21b041f80cbf54be289") + assert.NoError(t, err) + + stateDiff := map[felt.Felt]*felt.Felt{} + + for i := 0; i < 10; i++ { + stateDiff[*new(felt.Felt).SetUint64(uint64(i*10 + 1))] = new(felt.Felt).SetUint64(uint64(i*10 + 2)) + } + + return bc.Store( + &core.Block{ + Header: &core.Header{ + Hash: &felt.Zero, + ParentHash: &felt.Zero, + GlobalStateRoot: expectedStateRoot, + }, + }, + nil, + &core.StateUpdate{ + NewRoot: expectedStateRoot, + OldRoot: &felt.Zero, + StateDiff: &core.StateDiff{ + Nonces: map[felt.Felt]*felt.Felt{ + *key: nonce, + }, + DeployedContracts: map[felt.Felt]*felt.Felt{ + *key: classHash, + }, + StorageDiffs: map[felt.Felt]map[felt.Felt]*felt.Felt{ + *key: stateDiff, + }, + DeclaredV0Classes: nil, + DeclaredV1Classes: nil, + ReplacedClasses: nil, + }, + }, + map[felt.Felt]core.Class{ + *classHash: cls, + }) + }, + }, + { + name: "basic with v1 classes", + scenario: func(t *testing.T, bc *blockchain.Blockchain) error { + t.Skip("new test data needed - why?") + expectedStateRoot := hexToFelt("051099998c4c482b6ab11ed287d9fd1bc1b2e0dafca099cfc23e55c8754048bd") + + key := new(felt.Felt).SetUint64(uint64(12)) + nonce := new(felt.Felt).SetUint64(uint64(1)) + + clsHash, compiledClsHash, cls, err := V1ClassFromString("01b5126eb60b96de5b8e45c1334ad44d23dacaa76f748ab08b23dac43f3958", "a262417419aba565436c617373da00010006a763416269785f5b7b2274797065223a20226576656e74222c20226e616d65223a2022627974655f61727261793a3a53696d706c6553746f726167653a3a4576656e74222c20226b696e64223a2022656e756d222c202276617269616e7473223a205b5d7d5d6741626948617368841b194b356d26f0cd761bd738e24f884338411bc2749e3d7f8913051b0459fa72a5e413526750726f6772616d8b841bffffffffffffffe11bffffffffffffffff1bffffffffffffffff1b07fffffffffffdf0841bffffffffffffff811bffffffffffffffff1bffffffffffffffff1b07fffffffffff7908400000000841bffffffffffffffc11bffffffffffffffff1bffffffffffffffff1b07fffffffffffbd0841bffffffffffffff611bffffffffffffffff1bffffffffffffffff1b07fffffffffff570841bffffffffffffff811bffffffffffffffff1bffffffffffffffff1b07fffffffffff790841bffffffffffffffe11bffffffffffffffff1bffffffffffffffff1b07fffffffffffdf0841bffffffffffffe0211bffffffffffffffff1bffffffffffffffff1b07fffffffffde2308400000000841bffffffffffffff811bffffffffffffffff1bffffffffffffffff1b07fffffffffff790840000000068436f6d70696c6564a96548696e7473425b5d655072696d65c2582008000000000000110000000000000000000000000000000000000000000000016842797465636f6465806845787465726e616c80694c3148616e646c6572806b436f6e7374727563746f72806d507974686f6e696348696e7473425b5d6f436f6d70696c657256657273696f6e65322e362e307642797465636f64655365676d656e744c656e67746873a2664c656e67746800684368696c6472656ef66b456e747279506f696e7473a36845787465726e616c80694c3148616e646c6572806b436f6e7374727563746f72806b50726f6772616d48617368841bb3799734fc0a22761b73a1705363df43691b78153b3e72b8b9ff1b0735af66c53fce8d6f53656d616e74696356657273696f6e65302e312e30") + assert.NoError(t, err) + clsHash2, compiledClsHash2, cls2, err := V1ClassFromString("0cc7d5b4b3f7f112209f743ed739f97570d83d7b42c3516bd955d4b053237dd", "a262417419eee165436c617373da00010006a76341626978615b7b2274797065223a20226576656e74222c20226e616d65223a2022746f746f3a3a636f6e7472616374733a3a546f746f3a3a546f746f3a3a4576656e74222c20226b696e64223a2022656e756d222c202276617269616e7473223a205b5d7d5d6741626948617368841b12a4a837ffa449ba1b6eeb23f3ef6440e31b453422a17e9d29381b02cbf391fe0a8c9d6750726f6772616d8b841bffffffffffffffe11bffffffffffffffff1bffffffffffffffff1b07fffffffffffdf0841bffffffffffffff811bffffffffffffffff1bffffffffffffffff1b07fffffffffff7908400000000841bffffffffffffffc11bffffffffffffffff1bffffffffffffffff1b07fffffffffffbd0841bffffffffffffff611bffffffffffffffff1bffffffffffffffff1b07fffffffffff570841bffffffffffffffa11bffffffffffffffff1bffffffffffffffff1b07fffffffffff9b0841bffffffffffffffe11bffffffffffffffff1bffffffffffffffff1b07fffffffffffdf0841bffffffffffffe0211bffffffffffffffff1bffffffffffffffff1b07fffffffffde2308400000000841bffffffffffffff811bffffffffffffffff1bffffffffffffffff1b07fffffffffff790840000000068436f6d70696c6564a96548696e7473425b5d655072696d65c2582008000000000000110000000000000000000000000000000000000000000000016842797465636f6465806845787465726e616c80694c3148616e646c6572806b436f6e7374727563746f72806d507974686f6e696348696e7473425b5d6f436f6d70696c657256657273696f6e65322e362e307642797465636f64655365676d656e744c656e67746873a2664c656e67746800684368696c6472656ef66b456e747279506f696e7473a36845787465726e616c80694c3148616e646c6572806b436f6e7374727563746f72806b50726f6772616d48617368841ba97fcaac59fda9281bcb5930ad34825d5c1bf25090cae1f11f781b008272c3d5d2ae0b6f53656d616e74696356657273696f6e65302e312e30") + assert.NoError(t, err) + + return bc.Store( + &core.Block{ + Header: &core.Header{ + Hash: &felt.Zero, + ParentHash: &felt.Zero, + GlobalStateRoot: expectedStateRoot, + }, + }, + nil, + &core.StateUpdate{ + NewRoot: expectedStateRoot, + OldRoot: &felt.Zero, + StateDiff: &core.StateDiff{ + Nonces: map[felt.Felt]*felt.Felt{ + *key: nonce, + }, + DeployedContracts: map[felt.Felt]*felt.Felt{ + *key: clsHash, + }, + StorageDiffs: map[felt.Felt]map[felt.Felt]*felt.Felt{}, + DeclaredV0Classes: nil, + DeclaredV1Classes: map[felt.Felt]*felt.Felt{ + *clsHash: compiledClsHash, + *clsHash2: compiledClsHash2, + }, + ReplacedClasses: nil, + }, + }, + map[felt.Felt]core.Class{ + *clsHash: cls, + *clsHash2: cls2, + }) + }, + }, + } + + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + d, err := pebble.NewMem() + assert.NoError(t, err) + bc := blockchain.New(d, &utils.Sepolia) + + err = scenario.scenario(t, bc) + assert.NoError(t, err) + + d2, err := pebble.NewMem() + assert.NoError(t, err) + bc2 := blockchain.New(d2, &utils.Sepolia) + + logger, err := utils.NewZapLogger(utils.DEBUG, false) + assert.NoError(t, err) + + syncer := NewSnapSyncer( + &NoopService{ + blockchain: bc, + }, + &localStarknetData{bc}, + &snapServer{ + blockchain: bc, + }, + bc2, + logger, + ) + + err = syncer.Run(context.Background()) + assert.NoError(t, err) + + state1, closer, err := bc.HeadStateFreakingState() + assert.NoError(t, err) + defer func() { _ = closer() }() + + state2, closer, err := bc2.HeadStateFreakingState() + assert.NoError(t, err) + defer func() { _ = closer() }() + + sr, cr, err := state1.StateAndClassRoot() + assert.NoError(t, err) + + sr2, cr2, err := state2.StateAndClassRoot() + assert.NoError(t, err) + + assert.Equal(t, sr, sr2) + assert.Equal(t, cr, cr2) + }) + } +} + +func TestSnapCopyTrie(t *testing.T) { + t.Skip("DB snapshot is needed for this test") + var d db.DB + d, err := pebble.NewWithOptions("/home/amirul/fastworkscratch3/juno_db/juno_mainnet/", 1280, 128, false) + defer func() { _ = d.Close() }() + assert.NoError(t, err) + + bc := blockchain.New(d, &utils.Sepolia) // Needed because class loader need encoder to be registered + + targetdir := "/home/amirul/fastworkscratch3/targetjuno" + os.RemoveAll(targetdir) + + go func() { + http.Handle("/metrics", promhttp.Handler()) + _ = http.ListenAndServe(":9201", nil) + }() + + var d2 db.DB + d2, _ = pebble.NewWithOptions(targetdir, 128000000, 128, false) + defer func() { _ = d2.Close() }() + bc2 := blockchain.New(d2, &utils.Mainnet) // Needed because class loader need encoder to be registered + + logger, err := utils.NewZapLogger(utils.DEBUG, false) + assert.NoError(t, err) + + syncer := NewSnapSyncer( + &NoopService{ + blockchain: bc, + }, + &localStarknetData{bc}, + &snapServer{ + blockchain: bc, + }, + bc2, + logger, + ) + + err = syncer.Run(context.Background()) + assert.NoError(t, err) + + state1, closer, err := bc.HeadStateFreakingState() + assert.NoError(t, err) + defer func() { _ = closer() }() + + state2, closer, err := bc2.HeadStateFreakingState() + assert.NoError(t, err) + defer func() { _ = closer() }() + + sr, cr, err := state1.StateAndClassRoot() + assert.NoError(t, err) + + sr2, cr2, err := state2.StateAndClassRoot() + assert.NoError(t, err) + + assert.Equal(t, sr, sr2) + assert.Equal(t, cr, cr2) +} + +type NoopService struct { + blockchain *blockchain.Blockchain +} + +func (n NoopService) Run(ctx context.Context) error { + return nil +} + +type localStarknetData struct { + blockchain *blockchain.Blockchain +} + +func (l localStarknetData) BlockByNumber(ctx context.Context, blockNumber uint64) (*core.Block, error) { + return l.blockchain.BlockByNumber(blockNumber) +} + +func (l localStarknetData) BlockLatest(ctx context.Context) (*core.Block, error) { + time.Sleep(time.Second) + return l.blockchain.Head() +} + +func (l localStarknetData) BlockPending(ctx context.Context) (*core.Block, error) { + // TODO implement me + panic("implement me") +} + +func (l localStarknetData) Transaction(ctx context.Context, transactionHash *felt.Felt) (core.Transaction, error) { + // TODO implement me + panic("implement me") +} + +func (l localStarknetData) Class(ctx context.Context, classHash *felt.Felt) (core.Class, error) { + // TODO implement me + panic("implement me") +} + +func (l localStarknetData) StateUpdate(ctx context.Context, blockNumber uint64) (*core.StateUpdate, error) { + // TODO implement me + panic("implement me") +} + +func (l localStarknetData) StateUpdatePending(ctx context.Context) (*core.StateUpdate, error) { + // TODO implement me + panic("implement me") +} + +func (l localStarknetData) StateUpdateWithBlock(ctx context.Context, blockNumber uint64) (*core.StateUpdate, *core.Block, error) { + // TODO implement me + panic("implement me") +} + +func (l localStarknetData) StateUpdatePendingWithBlock(ctx context.Context) (*core.StateUpdate, *core.Block, error) { + // TODO implement me + panic("implement me") +} + +var _ starknetdata.StarknetData = &localStarknetData{} diff --git a/sync/sync_test.go b/sync/sync_test.go index 4f1d8a096a..f579840843 100644 --- a/sync/sync_test.go +++ b/sync/sync_test.go @@ -49,6 +49,7 @@ func TestSyncBlocks(t *testing.T) { assert.Equal(t, b, block) height-- } + bc.Close() return nil }()) } @@ -147,6 +148,7 @@ func TestReorg(t *testing.T) { // sync to integration for 2 blocks bc := blockchain.New(testDB, &utils.Integration) + defer bc.Close() synchronizer := sync.New(bc, integGw, utils.NewNopZapLogger(), 0, false) ctx, cancel := context.WithTimeout(context.Background(), timeout) @@ -155,6 +157,7 @@ func TestReorg(t *testing.T) { t.Run("resync to mainnet with the same db", func(t *testing.T) { bc := blockchain.New(testDB, &utils.Mainnet) + defer bc.Close() // Ensure current head is Integration head head, err := bc.HeadsHeader() @@ -182,6 +185,7 @@ func TestPending(t *testing.T) { testDB := pebble.NewMemTest(t) log := utils.NewNopZapLogger() bc := blockchain.New(testDB, &utils.Mainnet) + defer bc.Close() synchronizer := sync.New(bc, gw, log, time.Millisecond*100, false) ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) @@ -201,6 +205,7 @@ func TestSubscribeNewHeads(t *testing.T) { log := utils.NewNopZapLogger() integration := utils.Integration chain := blockchain.New(testDB, &integration) + defer chain.Close() integrationClient := feeder.NewTestClient(t, &integration) gw := adaptfeeder.New(integrationClient) syncer := sync.New(chain, gw, log, 0, false) From ffc6eeba1f6b1cc5a60ba0848f3c43cc7c2d9dd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowosielski?= Date: Fri, 30 Aug 2024 16:50:09 +0200 Subject: [PATCH 02/23] p2p client and looking at the syncService --- p2p/p2p.go | 3 +++ p2p/starknet/client.go | 12 ++++++++++++ p2p/starknet/ids.go | 10 ++++++++++ sync/snap_server.go | 14 ++++++++++++-- sync/snapsyncer.go | 2 ++ 5 files changed, 39 insertions(+), 2 deletions(-) diff --git a/p2p/p2p.go b/p2p/p2p.go index 49633f49ee..7bbf253fa5 100644 --- a/p2p/p2p.go +++ b/p2p/p2p.go @@ -92,6 +92,9 @@ func New(addr, publicAddr, version, peers, privKeyStr string, feederNode bool, b return addrs } + // try to instanciate snap_server here + var snapServer junoSync.SnapServer = junoSync.New() + p2pHost, err := libp2p.New( libp2p.ListenAddrs(sourceMultiAddr), libp2p.Identity(prvKey), diff --git a/p2p/starknet/client.go b/p2p/starknet/client.go index bfeed7ab7a..9c63cf6f65 100644 --- a/p2p/starknet/client.go +++ b/p2p/starknet/client.go @@ -123,3 +123,15 @@ func (c *Client) RequestTransactions(ctx context.Context, req *spec.Transactions return requestAndReceiveStream[*spec.TransactionsRequest, *spec.TransactionsResponse]( ctx, c.newStream, TransactionsPID(), req, c.log) } + +func (c *Client) GetClassRange(ctx context.Context, req *spec.ClassRangeRequest) (iter.Seq[*spec.ClassRangeResponse], error) { + return requestAndReceiveStream[*spec.ClassRangeRequest, *spec.ClassRangeResponse](ctx, c.newStream, SnapshotClassRangePID(), req, c.log) +} + +func (c *Client) GetContractRange(ctx context.Context, req *spec.ContractRangeRequest) (iter.Seq[*spec.ContractRangeResponse], error) { + return requestAndReceiveStream[*spec.ContractRangeRequest, *spec.ContractRangeResponse](ctx, c.newStream, SnapshotContractRangePID(), req, c.log) +} + +func (c *Client) GetStorageRange(ctx context.Context, req *spec.ContractStorageRequest) (iter.Seq[*spec.ContractStorageResponse], error) { + return requestAndReceiveStream[*spec.ContractStorageRequest, *spec.ContractStorageResponse](ctx, c.newStream, SnapshotContractStorageRangePID(), req, c.log) +} diff --git a/p2p/starknet/ids.go b/p2p/starknet/ids.go index d1b97b0ad2..02544d5db0 100644 --- a/p2p/starknet/ids.go +++ b/p2p/starknet/ids.go @@ -25,3 +25,13 @@ func ClassesPID() protocol.ID { func StateDiffPID() protocol.ID { return Prefix + "/state_diffs/0.1.0-rc.0" } + +func SnapshotClassRangePID() protocol.ID { + return Prefix + "/snapshots/class_range/0.1.0-rc.0" +} +func SnapshotContractRangePID() protocol.ID { + return Prefix + "/snapshots/contract_range/0.1.0-rc.0" +} +func SnapshotContractStorageRangePID() protocol.ID { + return Prefix + "/snapshots/storage_range/0.1.0-rc.0" +} diff --git a/sync/snap_server.go b/sync/snap_server.go index b1f3e03c7d..481a6c376c 100644 --- a/sync/snap_server.go +++ b/sync/snap_server.go @@ -55,12 +55,12 @@ type SnapServerBlockchain interface { GetClasses(felts []*felt.Felt) ([]core.Class, error) } +var _ SnapServerBlockchain = (*blockchain.Blockchain)(nil) + type snapServer struct { blockchain SnapServerBlockchain } -var _ SnapServerBlockchain = &blockchain.Blockchain{} - func determineMaxNodes(specifiedMaxNodes uint64) uint64 { const ( defaultMaxNodes = 1024 * 16 @@ -298,6 +298,16 @@ func (b *snapServer) GetStorageRange(ctx context.Context, request *StorageRangeR } } +// GetStorageRangeStd TODO: move/change đŸ‘† - just to check it can work on spec structs +func (b *snapServer) GetStorageRangeStd(ctx context.Context, request *spec.ContractStorageRequest) iter.Seq2[*StorageRangeStreamingResult, error] { + req := &StorageRangeRequest{ + StateRoot: p2p2core.AdaptHash(request.StateRoot), + Queries: request.Query, + ChunkPerProof: uint64(request.ChunksPerProof), + } + return b.GetStorageRange(ctx, req) +} + func (b *snapServer) GetClasses(ctx context.Context, felts []*felt.Felt) ([]*spec.Class, error) { classes := make([]*spec.Class, len(felts)) coreClasses, err := b.blockchain.GetClasses(felts) diff --git a/sync/snapsyncer.go b/sync/snapsyncer.go index d7bad6f7cf..36775ac6c4 100644 --- a/sync/snapsyncer.go +++ b/sync/snapsyncer.go @@ -35,6 +35,8 @@ type Blockchain interface { PutStorage(storage map[felt.Felt]map[felt.Felt]*felt.Felt) error } +var _ Blockchain = (*blockchain.Blockchain)(nil) + type SnapSyncher struct { baseSync service.Service starknetData starknetdata.StarknetData From 0698ab67bcd283ca4e1ae91c94b3bec15ae064d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowosielski?= Date: Wed, 4 Sep 2024 15:32:05 +0200 Subject: [PATCH 03/23] finishing p2p handler - question --- p2p/p2p.go | 3 - sync/snapsyncer.go => p2p/snap_syncer.go | 749 ++++++++++++----------- p2p/starknet/client.go | 10 +- p2p/starknet/handlers.go | 27 +- p2p/starknet/ids.go | 6 + p2p/starknet/snap_provider.go | 131 ++++ p2p/starknet/spec/class.pb.go | 119 +++- p2p/starknet/spec/snapshot.pb.go | 259 ++++---- sync/snap_server.go | 78 ++- sync/snap_server_test.go | 67 +- sync/snapsyncer_test.go | 384 ------------ 11 files changed, 912 insertions(+), 921 deletions(-) rename sync/snapsyncer.go => p2p/snap_syncer.go (83%) create mode 100644 p2p/starknet/snap_provider.go delete mode 100644 sync/snapsyncer_test.go diff --git a/p2p/p2p.go b/p2p/p2p.go index 7bbf253fa5..49633f49ee 100644 --- a/p2p/p2p.go +++ b/p2p/p2p.go @@ -92,9 +92,6 @@ func New(addr, publicAddr, version, peers, privKeyStr string, feederNode bool, b return addrs } - // try to instanciate snap_server here - var snapServer junoSync.SnapServer = junoSync.New() - p2pHost, err := libp2p.New( libp2p.ListenAddrs(sourceMultiAddr), libp2p.Identity(prvKey), diff --git a/sync/snapsyncer.go b/p2p/snap_syncer.go similarity index 83% rename from sync/snapsyncer.go rename to p2p/snap_syncer.go index 36775ac6c4..572d84e91a 100644 --- a/sync/snapsyncer.go +++ b/p2p/snap_syncer.go @@ -1,9 +1,10 @@ -package sync +package p2p import ( "context" "errors" "fmt" + "github.com/NethermindEth/juno/p2p/starknet" big "math/big" "sync" "sync/atomic" @@ -19,6 +20,7 @@ import ( "github.com/NethermindEth/juno/p2p/starknet/spec" "github.com/NethermindEth/juno/service" "github.com/NethermindEth/juno/starknetdata" + junoSync "github.com/NethermindEth/juno/sync" //TODO: Remove this? "github.com/NethermindEth/juno/utils" "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" "github.com/prometheus/client_golang/prometheus" @@ -40,7 +42,8 @@ var _ Blockchain = (*blockchain.Blockchain)(nil) type SnapSyncher struct { baseSync service.Service starknetData starknetdata.StarknetData - snapServer SnapServer + client starknet.Client + snapServer junoSync.SnapServer // TODO: Remove this? blockchain Blockchain log utils.Logger @@ -74,14 +77,14 @@ type storageRangeJob struct { func NewSnapSyncer( baseSyncher service.Service, consensus starknetdata.StarknetData, - server SnapServer, + client starknet.Client, bc *blockchain.Blockchain, log utils.Logger, ) *SnapSyncher { return &SnapSyncher{ baseSync: baseSyncher, starknetData: consensus, - snapServer: server, + client: client, blockchain: bc, log: log, } @@ -127,7 +130,6 @@ var ( maxPivotDistance = 32 // Set to 1 to test updated storage. newPivotHeadDistance = uint64(0) // This should be the reorg depth - ) func (s *SnapSyncher) Run(ctx context.Context) error { @@ -152,24 +154,6 @@ func (s *SnapSyncher) Run(ctx context.Context) error { return s.baseSync.Run(ctx) } -func VerifyTrie( - expectedRoot *felt.Felt, - paths, hashes []*felt.Felt, - proofs []trie.ProofNode, - height uint8, - hash func(*felt.Felt, *felt.Felt) *felt.Felt, -) (bool, error) { - hasMore, valid, err := trie.VerifyRange(expectedRoot, nil, paths, hashes, proofs, hash, height) - if err != nil { - return false, err - } - if !valid { - return false, errors.New("invalid proof") - } - - return hasMore, nil -} - //nolint:gocyclo,nolintlint func (s *SnapSyncher) runPhase1(ctx context.Context) error { starttime := time.Now() @@ -364,28 +348,39 @@ func (s *SnapSyncher) runClassRangeWorker(ctx context.Context) error { stateRoot := s.currentGlobalStateRoot - var err error - s.log.Infow("class range state root", "stateroot", stateRoot) // TODO: Maybe timeout - s.snapServer.GetClassRange(ctx, &spec.ClassRangeRequest{ + classIter, err := s.client.RequestClassRange(ctx, &spec.ClassRangeRequest{ Root: core2p2p.AdaptHash(stateRoot), Start: core2p2p.AdaptHash(startAddr), ChunksPerProof: uint32(classRangeChunksPerProof), - })(func(response *ClassRangeStreamingResult, reqErr error) bool { - if reqErr != nil { - fmt.Printf("%s\n", errors.Join(reqErr, errors.New("error get address range"))) + }) + if err != nil { + return err + } + + classIter(func(response *spec.ClassRangeResponse) bool { + if response == nil { + // State root missing. return false } - - if response == nil || (response.Range == nil && response.RangeProof == nil) { + classWrp := response.GetClasses() + if classWrp == nil || classWrp.Classes == nil { // State root missing. return false } - s.log.Infow("got", "res", len(response.Range.Classes), "err", reqErr, "startAdr", startAddr) + classes := classWrp.Classes + if response.RangeProof == nil { + // Proofs missing. + return false + } - err = VerifyGlobalStateRoot(stateRoot, response.ClassesRoot, response.ContractsRoot) + s.log.Infow("got", "res", len(classes), "startAdr", startAddr) + + classRoot := p2p2core.AdaptHash(response.ClassesRoot) + contractRoot := p2p2core.AdaptHash(response.ContractsRoot) + err = VerifyGlobalStateRoot(stateRoot, classRoot, contractRoot) if err != nil { s.log.Infow("global state root verification failure") // Root verification failed @@ -393,19 +388,19 @@ func (s *SnapSyncher) runClassRangeWorker(ctx context.Context) error { return false } - if response.ClassesRoot.Equal(&felt.Zero) { + if classRoot.Equal(&felt.Zero) { // Special case, no V1 at all completed = true return false } - paths := make([]*felt.Felt, len(response.Range.Classes)) - values := make([]*felt.Felt, len(response.Range.Classes)) - coreClasses := make([]core.Class, len(response.Range.Classes)) + paths := make([]*felt.Felt, len(classes)) + values := make([]*felt.Felt, len(classes)) + coreClasses := make([]core.Class, len(classes)) egrp := errgroup.Group{} - for i, cls := range response.Range.Classes { + for i, cls := range classes { coreClass := p2p2core.AdaptClass(cls) i := i egrp.Go(func() error { @@ -425,7 +420,7 @@ func (s *SnapSyncher) runClassRangeWorker(ctx context.Context) error { } proofs := P2pProofToTrieProofs(response.RangeProof) - hasNext, err := VerifyTrie(response.ClassesRoot, paths, values, proofs, core.GlobalTrieHeight, crypto.Poseidon) + hasNext, err := VerifyTrie(classRoot, paths, values, proofs, core.GlobalTrieHeight, crypto.Poseidon) if err != nil { // Root verification failed // TODO: Ban peer @@ -465,63 +460,126 @@ func (s *SnapSyncher) runClassRangeWorker(ctx context.Context) error { return nil } -func CalculateCompiledClassHash(cls core.Class) *felt.Felt { - return cls.(*core.Cairo1Class).Compiled.Hash() -} +//nolint:gocyclo +func (s *SnapSyncher) runFetchClassJob(ctx context.Context) error { + keyBatches := make([]*felt.Felt, 0) + for { + requestloop: + for len(keyBatches) < 100 { + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(JobDuration): + // Just request whatever we have + if len(keyBatches) > 0 { + break requestloop + } + s.log.Infow("waiting for more class job", "count", s.storageRangeJobCount) + case key := <-s.classesJob: + if key == nil { + // channel finished. + if len(keyBatches) > 0 { + break requestloop + } else { + // Worker finished + return nil + } + } else { + if key.Equal(&felt.Zero) { + continue + } -func P2pProofToTrieProofs(proof *spec.PatriciaRangeProof) []trie.ProofNode { - // TODO: Move to adapter + // TODO: Can be done in batches + cls, err := s.blockchain.GetClasses([]*felt.Felt{key}) + if err != nil { + s.log.Errorw("error getting class", "err", err) + return err + } - proofs := make([]trie.ProofNode, len(proof.Nodes)) - for i, node := range proof.Nodes { - if node.GetBinary() != nil { - binary := node.GetBinary() - proofs[i] = trie.ProofNode{ - Binary: &trie.Binary{ - LeftHash: p2p2core.AdaptFelt(binary.Left), - RightHash: p2p2core.AdaptFelt(binary.Right), - }, - } - } else { - edge := node.GetEdge() - // TODO. What if edge is nil too? - key := trie.NewKey(uint8(edge.Length), edge.Path.Elements) - proofs[i] = trie.ProofNode{ - Edge: &trie.Edge{ - Child: p2p2core.AdaptFelt(edge.Value), - Path: &key, - }, + if cls[0] == nil { + keyBatches = append(keyBatches, key) + } + } } } - } - return proofs -} + var hashes []*spec.Hash + for _, key := range keyBatches { + hashes = append(hashes, core2p2p.AdaptHash(key)) + } -var stateVersion = new(felt.Felt).SetBytes([]byte(`STARKNET_STATE_V0`)) + classIter, err := s.client.RequestClassesByKeys(ctx, &spec.ClassHashesRequest{ + ClassHashes: hashes, + }) -func VerifyGlobalStateRoot(globalStateRoot, classRoot, storageRoot *felt.Felt) error { - if classRoot.IsZero() { - if globalStateRoot.Equal(storageRoot) { - return nil + if err != nil { + s.log.Errorw("error getting class from client", "err", err) + return err + } + + classes := make([]*spec.Class, len(keyBatches)) + classIter(func(response *spec.ClassesResponse) bool { + switch v := response.ClassMessage.(type) { + case *spec.ClassesResponse_Class: + classes = append(classes, v.Class) + return true + case *spec.ClassesResponse_Fin: + return false + default: + s.log.Warnw("Unexpected ClassMessage from getClasses", "v", v) + return false + } + }) + + processedClasses := map[felt.Felt]bool{} + newClasses := map[felt.Felt]core.Class{} + classHashes := map[felt.Felt]*felt.Felt{} + for i, class := range classes { + if class == nil { + s.log.Infow("class empty", "key", keyBatches[i]) + continue + } + + coreClass := p2p2core.AdaptClass(class) + newClasses[*keyBatches[i]] = coreClass + h, err := coreClass.Hash() + if err != nil { + s.log.Errorw("error hashing class", "err", err) + return err + } + + if !h.Equal(keyBatches[i]) { + s.log.Warnw("invalid classhash", "got", h, "expected", keyBatches[i]) + return errors.New("invalid class hash") + } + + if coreClass.Version() == 1 { + classHashes[*keyBatches[i]] = coreClass.(*core.Cairo1Class).Compiled.Hash() + } + + processedClasses[*keyBatches[i]] = true + } + + if len(newClasses) != 0 { + err = s.blockchain.PutClasses(s.lastBlock.Number, classHashes, newClasses) + if err != nil { + s.log.Errorw("error storing class", "err", err) + return err + } } else { - return errors.New("invalid global state root") + s.log.Errorw("Unable to fetch any class from peer") + // TODO: Penalise peer? } - } - if !crypto.PoseidonArray(stateVersion, storageRoot, classRoot).Equal(globalStateRoot) { - return errors.New("invalid global state root") - } - return nil -} + newBatch := make([]*felt.Felt, 0) + for _, classHash := range keyBatches { + if _, ok := processedClasses[*classHash]; !ok { + newBatch = append(newBatch, classHash) + } + } -func CalculateClassHash(cls core.Class) *felt.Felt { - hash, err := cls.Hash() - if err != nil { - panic(err) + keyBatches = newBatch } - - return hash } //nolint:gocyclo @@ -533,22 +591,35 @@ func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { var outherErr error stateRoot := s.currentGlobalStateRoot - s.snapServer.GetContractRange(ctx, &spec.ContractRangeRequest{ + iter, err := s.client.RequestContractRange(ctx, &spec.ContractRangeRequest{ Domain: 0, // What do this do? StateRoot: core2p2p.AdaptHash(stateRoot), Start: core2p2p.AdaptAddress(startAddr), End: nil, // No need for now. ChunksPerProof: uint32(contractRangeChunkPerProof), - })(func(response *ContractRangeStreamingResult, _err error) bool { + }) + if err != nil { + return err + } + + iter(func(response *spec.ContractRangeResponse) bool { + if response == nil { + return false + } s.log.Infow("snap range progress", "progress", calculatePercentage(startAddr), "addr", startAddr) rangeProgress.Set(float64(calculatePercentage(startAddr))) - if response == nil || (response.Range == nil && response.RangeProof == nil) { - // State root missing. + crange := response.GetRange() + if crange == nil || crange.State == nil { + return false + } + if response.RangeProof == nil { return false } - err := VerifyGlobalStateRoot(stateRoot, response.ClassesRoot, response.ContractsRoot) + classRoot := p2p2core.AdaptHash(response.ClassesRoot) + contractRoot := p2p2core.AdaptHash(response.ContractsRoot) + err := VerifyGlobalStateRoot(stateRoot, classRoot, contractRoot) outherErr = err if err != nil { // Root verification failed @@ -556,16 +627,16 @@ func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { return false } - paths := make([]*felt.Felt, len(response.Range)) - values := make([]*felt.Felt, len(response.Range)) + paths := make([]*felt.Felt, len(crange.State)) + values := make([]*felt.Felt, len(crange.State)) - for i, rangeValue := range response.Range { + for i, rangeValue := range crange.State { paths[i] = p2p2core.AdaptAddress(rangeValue.Address) values[i] = CalculateRangeValueHash(rangeValue) } proofs := P2pProofToTrieProofs(response.RangeProof) - hasNext, err := VerifyTrie(response.ContractsRoot, paths, values, proofs, core.GlobalTrieHeight, crypto.Pedersen) + hasNext, err := VerifyTrie(contractRoot, paths, values, proofs, core.GlobalTrieHeight, crypto.Pedersen) outherErr = err if err != nil { // The peer should get penalised in this case @@ -574,7 +645,7 @@ func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { classes := []*felt.Felt{} nonces := []*felt.Felt{} - for _, r := range response.Range { + for _, r := range crange.State { classHash := p2p2core.AdaptHash(r.Class) classes = append(classes, classHash) nonces = append(nonces, (&felt.Felt{}).SetUint64(r.Nonce)) @@ -589,7 +660,7 @@ func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { // We don't actually store it directly here... only put it as part of job. // Can't remember why. Could be because it would be some wasted work. - for _, r := range response.Range { + for _, r := range crange.State { path := p2p2core.AdaptAddress(r.Address) storageRoot := p2p2core.AdaptHash(r.Storage) classHash := p2p2core.AdaptHash(r.Class) @@ -634,76 +705,6 @@ func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { return nil } -func CalculateRangeValueHash(value *spec.ContractState) *felt.Felt { - nonce := fp.NewElement(value.Nonce) - return calculateContractCommitment( - p2p2core.AdaptHash(value.Storage), - p2p2core.AdaptHash(value.Class), - felt.NewFelt(&nonce), - ) -} - -func calculateContractCommitment(storageRoot, classHash, nonce *felt.Felt) *felt.Felt { - return crypto.Pedersen(crypto.Pedersen(crypto.Pedersen(classHash, storageRoot), nonce), &felt.Zero) -} - -func (s *SnapSyncher) queueClassJob(ctx context.Context, classHash *felt.Felt) error { - queued := false - for !queued { - select { - case s.classesJob <- classHash: - queued = true - case <-ctx.Done(): - return ctx.Err() - case <-time.After(time.Second): - s.log.Infow("class queue stall on class") - } - } - return nil -} - -func (s *SnapSyncher) queueStorageRangeJob(ctx context.Context, path, storageRoot, classHash *felt.Felt, nonce uint64) error { - return s.queueStorageRangeJobJob(ctx, &storageRangeJob{ - path: path, - storageRoot: storageRoot, - startAddress: &felt.Zero, - classHash: classHash, - nonce: nonce, - }) -} - -func (s *SnapSyncher) queueStorageRangeJobJob(ctx context.Context, job *storageRangeJob) error { - queued := false - for !queued { - select { - case s.storageRangeJobQueue <- job: - queued = true - atomic.AddInt32(&s.storageRangeJobCount, 1) - case <-ctx.Done(): - return ctx.Err() - case <-time.After(JobDuration): - s.log.Infow("queue storage range stall") - } - } - return nil -} - -func (s *SnapSyncher) queueStorageRefreshJob(ctx context.Context, job *storageRangeJob) error { - queued := false - for !queued { - select { - case s.storageRefreshJob <- job: - queued = true - atomic.AddInt32(&s.storageRangeJobCount, 1) - case <-ctx.Done(): - return ctx.Err() - case <-time.After(time.Second): - s.log.Infow("storage refresh queue stall") - } - } - return nil -} - //nolint:funlen,gocyclo func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) error { nextjobs := make([]*storageRangeJob, 0) @@ -761,35 +762,41 @@ func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) "root", stateRoot.String(), "requestcount", len(requests), ) - s.snapServer.GetStorageRange(ctx, &StorageRangeRequest{ - StateRoot: stateRoot, - ChunkPerProof: uint64(storageRangeChunkPerProof), - Queries: requests, - })(func(response *StorageRangeStreamingResult, _err error) bool { - job := jobs[processedJobs] - if !job.path.Equal(response.StorageAddr) { - s.log.Errorw(fmt.Sprintf( - "storage addr differ %s %s %d\n", job.path, response.StorageAddr, workerIdx)) + iter, err := s.client.RequestStorageRange(ctx, &spec.ContractStorageRequest{ + StateRoot: core2p2p.AdaptHash(stateRoot), + ChunksPerProof: uint32(storageRangeChunkPerProof), + Query: requests, + }) + + iter(func(response *spec.ContractStorageResponse) bool { + if response == nil { return false } - - if response.Range == nil && response.RangeProof == nil { - // State root missing. + response.GetResponses() + csto := response.GetStorage() + if csto == nil || csto.KeyValue == nil { return false } + storageRange := csto.KeyValue - // Wait.. why is this needed here? - err = VerifyGlobalStateRoot(stateRoot, response.ClassesRoot, response.ContractsRoot) - if err != nil { - // Root verification failed + if response.RangeProof == nil { + return false + } + + job := jobs[processedJobs] + + storageAddr := p2p2core.AdaptFelt(response.ContractAddress) + if !job.path.Equal(storageAddr) { + s.log.Errorw(fmt.Sprintf( + "storage addr differ %s %s %d\n", job.path, storageAddr, workerIdx)) return false } // Validate response - paths := make([]*felt.Felt, len(response.Range)) - values := make([]*felt.Felt, len(response.Range)) + paths := make([]*felt.Felt, len(storageRange)) + values := make([]*felt.Felt, len(storageRange)) - for i, v := range response.Range { + for i, v := range storageRange { paths[i] = p2p2core.AdaptFelt(v.Key) values[i] = p2p2core.AdaptFelt(v.Value) } @@ -868,143 +875,6 @@ func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) } } -func (s *SnapSyncher) poolLatestBlock(ctx context.Context) error { - for { - select { - case <-ctx.Done(): - return nil - case <-time.After(JobDuration): - break - case <-s.storageRangeDone: - return nil - } - - newTarget, err := s.getNextStartingBlock(ctx) - if err != nil { - return errors.Join(err, errors.New("error getting current head")) - } - - // TODO: Race issue - if newTarget.Number-s.lastBlock.Number < uint64(maxPivotDistance) { - s.log.Infow("Not updating pivot yet", "lastblock", s.lastBlock.Number, - "newTarget", newTarget.Number, "diff", newTarget.Number-s.lastBlock.Number) - continue - } - - pivotUpdates.Inc() - - s.log.Infow("Switching snap pivot", "hash", newTarget.Hash, "number", newTarget.Number) - s.lastBlock = newTarget.Header - - fmt.Printf("Current state root is %s", s.lastBlock.GlobalStateRoot) - s.currentGlobalStateRoot = s.lastBlock.GlobalStateRoot - } -} - -func (s *SnapSyncher) ApplyStateUpdate(blockNumber uint64) error { - return errors.New("unimplemented") -} - -//nolint:gocyclo -func (s *SnapSyncher) runFetchClassJob(ctx context.Context) error { - keyBatches := make([]*felt.Felt, 0) - for { - requestloop: - for len(keyBatches) < 100 { - select { - case <-ctx.Done(): - return ctx.Err() - case <-time.After(JobDuration): - // Just request whatever we have - if len(keyBatches) > 0 { - break requestloop - } - s.log.Infow("waiting for more class job", "count", s.storageRangeJobCount) - case key := <-s.classesJob: - if key == nil { - // channel finished. - if len(keyBatches) > 0 { - break requestloop - } else { - // Worker finished - return nil - } - } else { - if key.Equal(&felt.Zero) { - continue - } - - // TODO: Can be done in batches - cls, err := s.blockchain.GetClasses([]*felt.Felt{key}) - if err != nil { - s.log.Errorw("error getting class", "err", err) - return err - } - - if cls[0] == nil { - keyBatches = append(keyBatches, key) - } - } - } - } - - classes, err := s.snapServer.GetClasses(ctx, keyBatches) - if err != nil { - s.log.Errorw("error getting class from outside", "err", err) - return err - } - - processedClasses := map[felt.Felt]bool{} - newClasses := map[felt.Felt]core.Class{} - classHashes := map[felt.Felt]*felt.Felt{} - for i, class := range classes { - if class == nil { - s.log.Infow("class empty", "key", keyBatches[i]) - continue - } - - coreClass := p2p2core.AdaptClass(class) - newClasses[*keyBatches[i]] = coreClass - h, err := coreClass.Hash() - if err != nil { - s.log.Errorw("error hashing class", "err", err) - return err - } - - if !h.Equal(keyBatches[i]) { - s.log.Warnw("invalid classhash", "got", h, "expected", keyBatches[i]) - return errors.New("invalid class hash") - } - - if coreClass.Version() == 1 { - classHashes[*keyBatches[i]] = coreClass.(*core.Cairo1Class).Compiled.Hash() - } - - processedClasses[*keyBatches[i]] = true - } - - if len(newClasses) != 0 { - err = s.blockchain.PutClasses(s.lastBlock.Number, classHashes, newClasses) - if err != nil { - s.log.Errorw("error storing class", "err", err) - return err - } - } else { - s.log.Errorw("Unable to fetch any class from peer") - // TODO: Penalise peer? - } - - newBatch := make([]*felt.Felt, 0) - for _, classHash := range keyBatches { - if _, ok := processedClasses[*classHash]; !ok { - newBatch = append(newBatch, classHash) - } - } - - keyBatches = newBatch - } -} - //nolint:gocyclo func (s *SnapSyncher) runStorageRefreshWorker(ctx context.Context) error { // In ethereum, this is normally done with get tries, but since we don't have that here, we'll have to be @@ -1038,59 +908,68 @@ func (s *SnapSyncher) runStorageRefreshWorker(ctx context.Context) error { bigIntAdd = (&big.Int{}).Add(bigIntAdd, big.NewInt(1)) elem := fp.NewElement(0) limitAddr := felt.NewFelt((&elem).SetBigInt(bigIntAdd)) - var err error + var outherErr error stateRoot := s.currentGlobalStateRoot - s.snapServer.GetContractRange(ctx, &spec.ContractRangeRequest{ + ctrIter, err := s.client.RequestContractRange(ctx, &spec.ContractRangeRequest{ Domain: 0, // What do this do? StateRoot: core2p2p.AdaptHash(stateRoot), Start: core2p2p.AdaptAddress(job.startAddress), End: core2p2p.AdaptAddress(limitAddr), ChunksPerProof: 10000, - })(func(response *ContractRangeStreamingResult, _err error) bool { - if response.Range == nil && response.RangeProof == nil { - // State root missing. + }) + if err != nil { + return err + } + + ctrIter(func(response *spec.ContractRangeResponse) bool { + if response == nil { return false } - if len(response.Range) == 0 { - // Unexpected behaviour + crange := response.GetRange() + if crange == nil || crange.State == nil || len(crange.State) == 0 { + return false + } + if response.RangeProof == nil { return false } - err = VerifyGlobalStateRoot(stateRoot, response.ClassesRoot, response.ContractsRoot) - if err != nil { + classRoot := p2p2core.AdaptHash(response.ClassesRoot) + contractRoot := p2p2core.AdaptHash(response.ContractsRoot) + outherErr = VerifyGlobalStateRoot(stateRoot, classRoot, contractRoot) + if outherErr != nil { // Root verification failed // TODO: Ban peer return false } - paths := make([]*felt.Felt, len(response.Range)) - values := make([]*felt.Felt, len(response.Range)) + paths := make([]*felt.Felt, len(crange.State)) + values := make([]*felt.Felt, len(crange.State)) - for i, rangeValue := range response.Range { + for i, rangeValue := range crange.State { paths[i] = p2p2core.AdaptAddress(rangeValue.Address) values[i] = CalculateRangeValueHash(rangeValue) } proofs := P2pProofToTrieProofs(response.RangeProof) - _, err = VerifyTrie(response.ContractsRoot, paths, values, proofs, core.GlobalTrieHeight, crypto.Pedersen) - if err != nil { + _, outherErr = VerifyTrie(contractRoot, paths, values, proofs, core.GlobalTrieHeight, crypto.Pedersen) + if outherErr != nil { // The peer should get penalised in this case return false } - job.storageRoot = p2p2core.AdaptHash(response.Range[0].Storage) - newClass := p2p2core.AdaptHash(response.Range[0].Storage) + job.storageRoot = p2p2core.AdaptHash(crange.State[0].Storage) + newClass := p2p2core.AdaptHash(crange.State[0].Storage) if newClass != job.classHash { - err := s.queueClassJob(ctx, newClass) - if err != nil { + outherErr = s.queueClassJob(ctx, newClass) + if outherErr != nil { return false } } - err = s.queueStorageRangeJobJob(ctx, job) - if err != nil { + outherErr = s.queueStorageRangeJobJob(ctx, job) + if outherErr != nil { return false } @@ -1099,11 +978,195 @@ func (s *SnapSyncher) runStorageRefreshWorker(ctx context.Context) error { return true }) - if err != nil { - s.log.Errorw("Error with contract range", "err", err) + if outherErr != nil { + s.log.Errorw("Error with contract range", "err", outherErr) // Well... need to figure out how to determine if its a temporary error or not. // For sure, the state root can be outdated, so this need to restart continue } } } + +func (s *SnapSyncher) queueClassJob(ctx context.Context, classHash *felt.Felt) error { + queued := false + for !queued { + select { + case s.classesJob <- classHash: + queued = true + case <-ctx.Done(): + return ctx.Err() + case <-time.After(time.Second): + s.log.Infow("class queue stall on class") + } + } + return nil +} + +func (s *SnapSyncher) queueStorageRangeJob(ctx context.Context, path, storageRoot, classHash *felt.Felt, nonce uint64) error { + return s.queueStorageRangeJobJob(ctx, &storageRangeJob{ + path: path, + storageRoot: storageRoot, + startAddress: &felt.Zero, + classHash: classHash, + nonce: nonce, + }) +} + +func (s *SnapSyncher) queueStorageRangeJobJob(ctx context.Context, job *storageRangeJob) error { + queued := false + for !queued { + select { + case s.storageRangeJobQueue <- job: + queued = true + atomic.AddInt32(&s.storageRangeJobCount, 1) + case <-ctx.Done(): + return ctx.Err() + case <-time.After(JobDuration): + s.log.Infow("queue storage range stall") + } + } + return nil +} + +func (s *SnapSyncher) queueStorageRefreshJob(ctx context.Context, job *storageRangeJob) error { + queued := false + for !queued { + select { + case s.storageRefreshJob <- job: + queued = true + atomic.AddInt32(&s.storageRangeJobCount, 1) + case <-ctx.Done(): + return ctx.Err() + case <-time.After(time.Second): + s.log.Infow("storage refresh queue stall") + } + } + return nil +} + +func (s *SnapSyncher) poolLatestBlock(ctx context.Context) error { + for { + select { + case <-ctx.Done(): + return nil + case <-time.After(JobDuration): + break + case <-s.storageRangeDone: + return nil + } + + newTarget, err := s.getNextStartingBlock(ctx) + if err != nil { + return errors.Join(err, errors.New("error getting current head")) + } + + // TODO: Race issue + if newTarget.Number-s.lastBlock.Number < uint64(maxPivotDistance) { + s.log.Infow("Not updating pivot yet", "lastblock", s.lastBlock.Number, + "newTarget", newTarget.Number, "diff", newTarget.Number-s.lastBlock.Number) + continue + } + + pivotUpdates.Inc() + + s.log.Infow("Switching snap pivot", "hash", newTarget.Hash, "number", newTarget.Number) + s.lastBlock = newTarget.Header + + fmt.Printf("Current state root is %s", s.lastBlock.GlobalStateRoot) + s.currentGlobalStateRoot = s.lastBlock.GlobalStateRoot + } +} + +func (s *SnapSyncher) ApplyStateUpdate(blockNumber uint64) error { + return errors.New("unimplemented") +} + +func P2pProofToTrieProofs(proof *spec.PatriciaRangeProof) []trie.ProofNode { + // TODO: Move to adapter + + proofs := make([]trie.ProofNode, len(proof.Nodes)) + for i, node := range proof.Nodes { + if node.GetBinary() != nil { + binary := node.GetBinary() + proofs[i] = trie.ProofNode{ + Binary: &trie.Binary{ + LeftHash: p2p2core.AdaptFelt(binary.Left), + RightHash: p2p2core.AdaptFelt(binary.Right), + }, + } + } else { + edge := node.GetEdge() + // TODO. What if edge is nil too? + key := trie.NewKey(uint8(edge.Length), edge.Path.Elements) + proofs[i] = trie.ProofNode{ + Edge: &trie.Edge{ + Child: p2p2core.AdaptFelt(edge.Value), + Path: &key, + }, + } + } + } + + return proofs +} + +func VerifyGlobalStateRoot(globalStateRoot, classRoot, storageRoot *felt.Felt) error { + var stateVersion = new(felt.Felt).SetBytes([]byte(`STARKNET_STATE_V0`)) + + if classRoot.IsZero() { + if globalStateRoot.Equal(storageRoot) { + return nil + } else { + return errors.New("invalid global state root") + } + } + + if !crypto.PoseidonArray(stateVersion, storageRoot, classRoot).Equal(globalStateRoot) { + return errors.New("invalid global state root") + } + return nil +} + +func VerifyTrie( + expectedRoot *felt.Felt, + paths, hashes []*felt.Felt, + proofs []trie.ProofNode, + height uint8, + hash func(*felt.Felt, *felt.Felt) *felt.Felt, +) (bool, error) { + hasMore, valid, err := trie.VerifyRange(expectedRoot, nil, paths, hashes, proofs, hash, height) + if err != nil { + return false, err + } + if !valid { + return false, errors.New("invalid proof") + } + + return hasMore, nil +} + +func CalculateClassHash(cls core.Class) *felt.Felt { + hash, err := cls.Hash() + if err != nil { + panic(err) + } + + return hash +} + +func CalculateCompiledClassHash(cls core.Class) *felt.Felt { + return cls.(*core.Cairo1Class).Compiled.Hash() +} + +func CalculateRangeValueHash(value *spec.ContractState) *felt.Felt { + nonce := fp.NewElement(value.Nonce) + return calculateContractCommitment( + p2p2core.AdaptHash(value.Storage), + p2p2core.AdaptHash(value.Class), + felt.NewFelt(&nonce), + ) +} + +func calculateContractCommitment(storageRoot, classHash, nonce *felt.Felt) *felt.Felt { + return crypto.Pedersen(crypto.Pedersen(crypto.Pedersen(classHash, storageRoot), nonce), &felt.Zero) +} diff --git a/p2p/starknet/client.go b/p2p/starknet/client.go index 9c63cf6f65..f4c113f5a7 100644 --- a/p2p/starknet/client.go +++ b/p2p/starknet/client.go @@ -124,14 +124,18 @@ func (c *Client) RequestTransactions(ctx context.Context, req *spec.Transactions ctx, c.newStream, TransactionsPID(), req, c.log) } -func (c *Client) GetClassRange(ctx context.Context, req *spec.ClassRangeRequest) (iter.Seq[*spec.ClassRangeResponse], error) { +func (c *Client) RequestClassRange(ctx context.Context, req *spec.ClassRangeRequest) (iter.Seq[*spec.ClassRangeResponse], error) { return requestAndReceiveStream[*spec.ClassRangeRequest, *spec.ClassRangeResponse](ctx, c.newStream, SnapshotClassRangePID(), req, c.log) } -func (c *Client) GetContractRange(ctx context.Context, req *spec.ContractRangeRequest) (iter.Seq[*spec.ContractRangeResponse], error) { +func (c *Client) RequestContractRange(ctx context.Context, req *spec.ContractRangeRequest) (iter.Seq[*spec.ContractRangeResponse], error) { return requestAndReceiveStream[*spec.ContractRangeRequest, *spec.ContractRangeResponse](ctx, c.newStream, SnapshotContractRangePID(), req, c.log) } -func (c *Client) GetStorageRange(ctx context.Context, req *spec.ContractStorageRequest) (iter.Seq[*spec.ContractStorageResponse], error) { +func (c *Client) RequestStorageRange(ctx context.Context, req *spec.ContractStorageRequest) (iter.Seq[*spec.ContractStorageResponse], error) { return requestAndReceiveStream[*spec.ContractStorageRequest, *spec.ContractStorageResponse](ctx, c.newStream, SnapshotContractStorageRangePID(), req, c.log) } + +func (c *Client) RequestClassesByKeys(ctx context.Context, req *spec.ClassHashesRequest) (iter.Seq[*spec.ClassesResponse], error) { + return requestAndReceiveStream[*spec.ClassHashesRequest, *spec.ClassesResponse](ctx, c.newStream, SnapshotClassesPID(), req, c.log) +} diff --git a/p2p/starknet/handlers.go b/p2p/starknet/handlers.go index 77b69349d2..2e7b165f95 100644 --- a/p2p/starknet/handlers.go +++ b/p2p/starknet/handlers.go @@ -15,6 +15,7 @@ import ( "github.com/NethermindEth/juno/core" "github.com/NethermindEth/juno/core/felt" "github.com/NethermindEth/juno/p2p/starknet/spec" + junoSync "github.com/NethermindEth/juno/sync" "github.com/NethermindEth/juno/utils" "github.com/libp2p/go-libp2p/core/network" "google.golang.org/protobuf/encoding/protodelim" @@ -22,8 +23,9 @@ import ( ) type Handler struct { - bcReader blockchain.Reader - log utils.SimpleLogger + bcReader blockchain.Reader + snapProvider *SnapProvider + log utils.SimpleLogger ctx context.Context cancel context.CancelFunc @@ -41,6 +43,11 @@ func NewHandler(bcReader blockchain.Reader, log utils.SimpleLogger) *Handler { } } +func (h *Handler) WithSnapsyncSupport(bc *blockchain.Blockchain) { + // TODO: should it be here? + h.snapProvider = &SnapProvider{SnapServer: junoSync.NewSnapServer(bc)} +} + // bufferPool caches unused buffer objects for later reuse. var bufferPool = sync.Pool{ New: func() any { @@ -124,6 +131,22 @@ func (h *Handler) StateDiffHandler(stream network.Stream) { streamHandler[*spec.StateDiffsRequest](h.ctx, &h.wg, stream, h.onStateDiffRequest, h.log) } +func (h *Handler) ClassRangeRequest(stream network.Stream) { + if h.snapProvider == nil { + h.log.Debugw("SnapProvider not initialized") + return + } + streamHandler[*spec.ClassRangeRequest](h.ctx, &h.wg, stream, h.onClassRangeRequest, h.log) +} + +func (h *Handler) ClassHashesRequest(stream network.Stream) { + if h.snapProvider == nil { + h.log.Debugw("SnapProvider not initialized") + return + } + streamHandler[*spec.ClassHashesRequest](h.ctx, &h.wg, stream, h.onClassHashesRequest, h.log) +} + func (h *Handler) onHeadersRequest(req *spec.BlockHeadersRequest) (iter.Seq[proto.Message], error) { finMsg := &spec.BlockHeadersResponse{ HeaderMessage: &spec.BlockHeadersResponse_Fin{}, diff --git a/p2p/starknet/ids.go b/p2p/starknet/ids.go index 02544d5db0..741ac436e8 100644 --- a/p2p/starknet/ids.go +++ b/p2p/starknet/ids.go @@ -29,9 +29,15 @@ func StateDiffPID() protocol.ID { func SnapshotClassRangePID() protocol.ID { return Prefix + "/snapshots/class_range/0.1.0-rc.0" } + func SnapshotContractRangePID() protocol.ID { return Prefix + "/snapshots/contract_range/0.1.0-rc.0" } + func SnapshotContractStorageRangePID() protocol.ID { return Prefix + "/snapshots/storage_range/0.1.0-rc.0" } + +func SnapshotClassesPID() protocol.ID { + return Prefix + "/snapshots/classes/0.1.0-rc.0" +} diff --git a/p2p/starknet/snap_provider.go b/p2p/starknet/snap_provider.go new file mode 100644 index 0000000000..62fe135514 --- /dev/null +++ b/p2p/starknet/snap_provider.go @@ -0,0 +1,131 @@ +package starknet + +import ( + "fmt" + "github.com/NethermindEth/juno/adapters/core2p2p" + "github.com/NethermindEth/juno/adapters/p2p2core" + "github.com/NethermindEth/juno/core/felt" + "github.com/NethermindEth/juno/p2p/starknet/spec" + "github.com/NethermindEth/juno/sync" + "github.com/NethermindEth/juno/utils/iter" + "google.golang.org/protobuf/proto" +) + +type SnapProvider struct { + SnapServer sync.SnapServer +} + +func (h *Handler) onClassHashesRequest(req *spec.ClassHashesRequest) (iter.Seq[proto.Message], error) { + type yieldFunc = func(proto.Message) bool + finMsg := &spec.ClassesResponse{ + ClassMessage: &spec.ClassesResponse_Fin{}, + } + + return func(yield yieldFunc) { + if req == nil || req.ClassHashes == nil || len(req.ClassHashes) == 0 { + yield(finMsg) + return + } + + // Since we return iterator we may split given keys into smaller chunks if necessary + classHashes := make([]*felt.Felt, len(req.ClassHashes)) + for _, hash := range req.ClassHashes { + classHashes = append(classHashes, p2p2core.AdaptHash(hash)) + } + + classes, err := h.snapProvider.SnapServer.GetClasses(h.ctx, classHashes) + if err != nil { + h.log.Errorw("failed to get classes", "err", err) + return + } + + for _, cls := range classes { + msg := &spec.ClassesResponse{ + ClassMessage: &spec.ClassesResponse_Class{ + Class: cls, + }, + } + if !yield(msg) { + // if caller is not interested in remaining data (example: connection to a peer is closed) exit + // note that in this case we won't send finMsg + return + } + } + + yield(finMsg) + }, nil +} + +func (h *Handler) onClassRangeRequest(req *spec.ClassRangeRequest) (iter.Seq[proto.Message], error) { + if h.snapProvider == nil { + return nil, fmt.Errorf("snapsyncing not supported") + } + + finMsg := &spec.ClassRangeResponse{ + Responses: &spec.ClassRangeResponse_Fin{}, + } + + return func(yield func(message proto.Message) bool) { + // port snap_server here + yield(finMsg) + }, nil +} + +func (h *Handler) onContractRangeRequest(req *spec.ContractRangeRequest) (iter.Seq[proto.Message], error) { + if h.snapProvider == nil { + return nil, fmt.Errorf("snapsyncing not supported") + } + + finMsg := &spec.ContractRangeResponse{ + Responses: &spec.ContractRangeResponse_Fin{}, + } + + return func(yield func(message proto.Message) bool) { + // port snap_server here + yield(finMsg) + }, nil +} + +func (h *Handler) onContractStorageRequest(req *spec.ContractStorageRequest) (iter.Seq[proto.Message], error) { + if h.snapProvider == nil { + return nil, fmt.Errorf("snapsyncing not supported") + } + + finMsg := &spec.ContractStorageResponse{ + Responses: &spec.ContractStorageResponse_Fin{}, + } + return func(yield func(message proto.Message) bool) { + // TODO: adapter method? Do we need separate req/res structs? + srr := &sync.StorageRangeRequest{ + StateRoot: p2p2core.AdaptHash(req.StateRoot), + ChunkPerProof: uint64(req.ChunksPerProof), + Queries: req.Query, + } + stoIter, err := h.snapProvider.SnapServer.GetStorageRange(h.ctx, srr) + if err != nil { + h.log.Errorw("failed to get storage range", "err", err) + return + } + + stoIter(func(result *sync.StorageRangeStreamingResult) bool { + // TODO: again adapter or just return spec types? + res := &spec.ContractStorageResponse{ + StateRoot: req.StateRoot, + ContractAddress: core2p2p.AdaptFelt(result.StorageAddr), + Responses: &spec.ContractStorageResponse_Storage{ + Storage: &spec.ContractStorage{ + KeyValue: result.Range, + }, + }, + } + + if !yield(res) { + return false + } + + return true + }) + + yield(finMsg) + }, nil +} diff --git a/p2p/starknet/spec/class.pb.go b/p2p/starknet/spec/class.pb.go index 53389258a0..a531442a65 100644 --- a/p2p/starknet/spec/class.pb.go +++ b/p2p/starknet/spec/class.pb.go @@ -625,6 +625,53 @@ func (x *Classes) GetClasses() []*Class { return nil } +type ClassHashesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClassHashes []*Hash `protobuf:"bytes,1,rep,name=class_hashes,json=classHashes,proto3" json:"class_hashes,omitempty"` +} + +func (x *ClassHashesRequest) Reset() { + *x = ClassHashesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_p2p_proto_class_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ClassHashesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClassHashesRequest) ProtoMessage() {} + +func (x *ClassHashesRequest) ProtoReflect() protoreflect.Message { + mi := &file_p2p_proto_class_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClassHashesRequest.ProtoReflect.Descriptor instead. +func (*ClassHashesRequest) Descriptor() ([]byte, []int) { + return file_p2p_proto_class_proto_rawDescGZIP(), []int{9} +} + +func (x *ClassHashesRequest) GetClassHashes() []*Hash { + if x != nil { + return x.ClassHashes + } + return nil +} + var File_p2p_proto_class_proto protoreflect.FileDescriptor var file_p2p_proto_class_proto_rawDesc = []byte{ @@ -699,7 +746,11 @@ var file_p2p_proto_class_proto_rawDesc = []byte{ 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x20, 0x0a, 0x07, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x06, 0x2e, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x52, 0x07, 0x63, - 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x22, 0x3e, 0x0a, 0x12, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x48, + 0x61, 0x73, 0x68, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x0c, + 0x63, 0x6c, 0x61, 0x73, 0x73, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x52, 0x0b, 0x63, 0x6c, 0x61, 0x73, 0x73, + 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x64, 0x45, 0x74, 0x68, 0x2f, 0x6a, 0x75, 0x6e, 0x6f, 0x2f, 0x70, 0x32, 0x70, 0x2f, 0x73, 0x74, 0x61, 0x72, 0x6b, 0x6e, 0x65, 0x74, 0x2f, 0x73, 0x70, 0x65, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, @@ -718,45 +769,47 @@ func file_p2p_proto_class_proto_rawDescGZIP() []byte { return file_p2p_proto_class_proto_rawDescData } -var file_p2p_proto_class_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_p2p_proto_class_proto_msgTypes = make([]protoimpl.MessageInfo, 10) var file_p2p_proto_class_proto_goTypes = []any{ - (*EntryPoint)(nil), // 0: EntryPoint - (*Cairo0Class)(nil), // 1: Cairo0Class - (*SierraEntryPoint)(nil), // 2: SierraEntryPoint - (*Cairo1EntryPoints)(nil), // 3: Cairo1EntryPoints - (*Cairo1Class)(nil), // 4: Cairo1Class - (*Class)(nil), // 5: Class - (*ClassesRequest)(nil), // 6: ClassesRequest - (*ClassesResponse)(nil), // 7: ClassesResponse - (*Classes)(nil), // 8: Classes - (*Felt252)(nil), // 9: Felt252 - (*Hash)(nil), // 10: Hash - (*Iteration)(nil), // 11: Iteration - (*Fin)(nil), // 12: Fin + (*EntryPoint)(nil), // 0: EntryPoint + (*Cairo0Class)(nil), // 1: Cairo0Class + (*SierraEntryPoint)(nil), // 2: SierraEntryPoint + (*Cairo1EntryPoints)(nil), // 3: Cairo1EntryPoints + (*Cairo1Class)(nil), // 4: Cairo1Class + (*Class)(nil), // 5: Class + (*ClassesRequest)(nil), // 6: ClassesRequest + (*ClassesResponse)(nil), // 7: ClassesResponse + (*Classes)(nil), // 8: Classes + (*ClassHashesRequest)(nil), // 9: ClassHashesRequest + (*Felt252)(nil), // 10: Felt252 + (*Hash)(nil), // 11: Hash + (*Iteration)(nil), // 12: Iteration + (*Fin)(nil), // 13: Fin } var file_p2p_proto_class_proto_depIdxs = []int32{ - 9, // 0: EntryPoint.selector:type_name -> Felt252 + 10, // 0: EntryPoint.selector:type_name -> Felt252 0, // 1: Cairo0Class.externals:type_name -> EntryPoint 0, // 2: Cairo0Class.l1_handlers:type_name -> EntryPoint 0, // 3: Cairo0Class.constructors:type_name -> EntryPoint - 9, // 4: SierraEntryPoint.selector:type_name -> Felt252 + 10, // 4: SierraEntryPoint.selector:type_name -> Felt252 2, // 5: Cairo1EntryPoints.externals:type_name -> SierraEntryPoint 2, // 6: Cairo1EntryPoints.l1_handlers:type_name -> SierraEntryPoint 2, // 7: Cairo1EntryPoints.constructors:type_name -> SierraEntryPoint 3, // 8: Cairo1Class.entry_points:type_name -> Cairo1EntryPoints - 9, // 9: Cairo1Class.program:type_name -> Felt252 + 10, // 9: Cairo1Class.program:type_name -> Felt252 1, // 10: Class.cairo0:type_name -> Cairo0Class 4, // 11: Class.cairo1:type_name -> Cairo1Class - 10, // 12: Class.class_hash:type_name -> Hash - 11, // 13: ClassesRequest.iteration:type_name -> Iteration + 11, // 12: Class.class_hash:type_name -> Hash + 12, // 13: ClassesRequest.iteration:type_name -> Iteration 5, // 14: ClassesResponse.class:type_name -> Class - 12, // 15: ClassesResponse.fin:type_name -> Fin + 13, // 15: ClassesResponse.fin:type_name -> Fin 5, // 16: Classes.classes:type_name -> Class - 17, // [17:17] is the sub-list for method output_type - 17, // [17:17] is the sub-list for method input_type - 17, // [17:17] is the sub-list for extension type_name - 17, // [17:17] is the sub-list for extension extendee - 0, // [0:17] is the sub-list for field type_name + 11, // 17: ClassHashesRequest.class_hashes:type_name -> Hash + 18, // [18:18] is the sub-list for method output_type + 18, // [18:18] is the sub-list for method input_type + 18, // [18:18] is the sub-list for extension type_name + 18, // [18:18] is the sub-list for extension extendee + 0, // [0:18] is the sub-list for field type_name } func init() { file_p2p_proto_class_proto_init() } @@ -874,6 +927,18 @@ func file_p2p_proto_class_proto_init() { return nil } } + file_p2p_proto_class_proto_msgTypes[9].Exporter = func(v any, i int) any { + switch v := v.(*ClassHashesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_p2p_proto_class_proto_msgTypes[5].OneofWrappers = []any{ (*Class_Cairo0)(nil), @@ -889,7 +954,7 @@ func file_p2p_proto_class_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_p2p_proto_class_proto_rawDesc, NumEnums: 0, - NumMessages: 9, + NumMessages: 10, NumExtensions: 0, NumServices: 0, }, diff --git a/p2p/starknet/spec/snapshot.pb.go b/p2p/starknet/spec/snapshot.pb.go index 8c89f0bdc2..f904de747b 100644 --- a/p2p/starknet/spec/snapshot.pb.go +++ b/p2p/starknet/spec/snapshot.pb.go @@ -363,7 +363,8 @@ type ContractRangeResponse struct { // // *ContractRangeResponse_Range // *ContractRangeResponse_Fin - Responses isContractRangeResponse_Responses `protobuf_oneof:"responses"` + Responses isContractRangeResponse_Responses `protobuf_oneof:"responses"` + RangeProof *PatriciaRangeProof `protobuf:"bytes,6,opt,name=range_proof,json=rangeProof,proto3" json:"range_proof,omitempty"` } func (x *ContractRangeResponse) Reset() { @@ -440,6 +441,13 @@ func (x *ContractRangeResponse) GetFin() *Fin { return nil } +func (x *ContractRangeResponse) GetRangeProof() *PatriciaRangeProof { + if x != nil { + return x.RangeProof + } + return nil +} + type isContractRangeResponse_Responses interface { isContractRangeResponse_Responses() } @@ -541,7 +549,8 @@ type ClassRangeResponse struct { // // *ClassRangeResponse_Classes // *ClassRangeResponse_Fin - Responses isClassRangeResponse_Responses `protobuf_oneof:"responses"` + Responses isClassRangeResponse_Responses `protobuf_oneof:"responses"` + RangeProof *PatriciaRangeProof `protobuf:"bytes,6,opt,name=range_proof,json=rangeProof,proto3" json:"range_proof,omitempty"` } func (x *ClassRangeResponse) Reset() { @@ -618,6 +627,13 @@ func (x *ClassRangeResponse) GetFin() *Fin { return nil } +func (x *ClassRangeResponse) GetRangeProof() *PatriciaRangeProof { + if x != nil { + return x.RangeProof + } + return nil +} + type isClassRangeResponse_Responses interface { isClassRangeResponse_Responses() } @@ -877,12 +893,14 @@ type ContractStorageResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - StateRoot *Hash `protobuf:"bytes,1,opt,name=state_root,json=stateRoot,proto3,oneof" json:"state_root,omitempty"` // may not appear if Fin is sent to end the whole response + StateRoot *Hash `protobuf:"bytes,1,opt,name=state_root,json=stateRoot,proto3,oneof" json:"state_root,omitempty"` // may not appear if Fin is sent to end the whole response + ContractAddress *Felt252 `protobuf:"bytes,2,opt,name=contract_address,json=contractAddress,proto3,oneof" json:"contract_address,omitempty"` // Types that are assignable to Responses: // // *ContractStorageResponse_Storage // *ContractStorageResponse_Fin - Responses isContractStorageResponse_Responses `protobuf_oneof:"responses"` + Responses isContractStorageResponse_Responses `protobuf_oneof:"responses"` + RangeProof *PatriciaRangeProof `protobuf:"bytes,5,opt,name=range_proof,json=rangeProof,proto3" json:"range_proof,omitempty"` } func (x *ContractStorageResponse) Reset() { @@ -924,6 +942,13 @@ func (x *ContractStorageResponse) GetStateRoot() *Hash { return nil } +func (x *ContractStorageResponse) GetContractAddress() *Felt252 { + if x != nil { + return x.ContractAddress + } + return nil +} + func (m *ContractStorageResponse) GetResponses() isContractStorageResponse_Responses { if m != nil { return m.Responses @@ -945,16 +970,23 @@ func (x *ContractStorageResponse) GetFin() *Fin { return nil } +func (x *ContractStorageResponse) GetRangeProof() *PatriciaRangeProof { + if x != nil { + return x.RangeProof + } + return nil +} + type isContractStorageResponse_Responses interface { isContractStorageResponse_Responses() } type ContractStorageResponse_Storage struct { - Storage *ContractStorage `protobuf:"bytes,2,opt,name=storage,proto3,oneof"` + Storage *ContractStorage `protobuf:"bytes,3,opt,name=storage,proto3,oneof"` } type ContractStorageResponse_Fin struct { - Fin *Fin `protobuf:"bytes,3,opt,name=fin,proto3,oneof"` + Fin *Fin `protobuf:"bytes,4,opt,name=fin,proto3,oneof"` } func (*ContractStorageResponse_Storage) isContractStorageResponse_Responses() {} @@ -1133,7 +1165,7 @@ var file_p2p_proto_snapshot_proto_rawDesc = []byte{ 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x24, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, - 0x74, 0x65, 0x22, 0x95, 0x02, 0x0a, 0x15, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x52, + 0x74, 0x65, 0x22, 0xcb, 0x02, 0x0a, 0x15, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x48, 0x01, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, 0x0e, @@ -1147,78 +1179,93 @@ var file_p2p_proto_snapshot_proto_rawDesc = []byte{ 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x48, 0x00, 0x52, 0x05, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x03, 0x66, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x04, 0x2e, 0x46, 0x69, 0x6e, 0x48, 0x00, 0x52, 0x03, 0x66, 0x69, 0x6e, + 0x12, 0x34, 0x0a, 0x0b, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x50, 0x61, 0x74, 0x72, 0x69, 0x63, 0x69, 0x61, + 0x52, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x0a, 0x72, 0x61, 0x6e, 0x67, + 0x65, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x42, 0x0b, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x42, 0x11, 0x0a, 0x0f, + 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x73, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x42, + 0x0f, 0x0a, 0x0d, 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x5f, 0x72, 0x6f, 0x6f, 0x74, + 0x22, 0x8e, 0x01, 0x0a, 0x11, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x52, 0x04, 0x72, 0x6f, 0x6f, + 0x74, 0x12, 0x1b, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x17, + 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, + 0x73, 0x68, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x68, 0x75, 0x6e, 0x6b, + 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x0e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x50, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x6f, + 0x66, 0x22, 0xc6, 0x02, 0x0a, 0x12, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x52, 0x61, 0x6e, 0x67, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x48, 0x01, 0x52, + 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x88, 0x01, 0x01, 0x12, 0x31, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x61, 0x63, 0x74, 0x73, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x48, 0x02, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x61, 0x63, 0x74, 0x73, 0x52, 0x6f, 0x6f, 0x74, 0x88, 0x01, 0x01, 0x12, 0x2d, 0x0a, 0x0c, 0x63, + 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x48, 0x03, 0x52, 0x0b, 0x63, 0x6c, 0x61, 0x73, + 0x73, 0x65, 0x73, 0x52, 0x6f, 0x6f, 0x74, 0x88, 0x01, 0x01, 0x12, 0x24, 0x0a, 0x07, 0x63, 0x6c, + 0x61, 0x73, 0x73, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x43, 0x6c, + 0x61, 0x73, 0x73, 0x65, 0x73, 0x48, 0x00, 0x52, 0x07, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, + 0x12, 0x18, 0x0a, 0x03, 0x66, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x04, 0x2e, + 0x46, 0x69, 0x6e, 0x48, 0x00, 0x52, 0x03, 0x66, 0x69, 0x6e, 0x12, 0x34, 0x0a, 0x0b, 0x72, 0x61, + 0x6e, 0x67, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x13, 0x2e, 0x50, 0x61, 0x74, 0x72, 0x69, 0x63, 0x69, 0x61, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x50, + 0x72, 0x6f, 0x6f, 0x66, 0x52, 0x0a, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x42, 0x0b, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x73, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x63, 0x6c, - 0x61, 0x73, 0x73, 0x65, 0x73, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x22, 0x8e, 0x01, 0x0a, 0x11, 0x43, - 0x6c, 0x61, 0x73, 0x73, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x19, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, - 0x2e, 0x48, 0x61, 0x73, 0x68, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x12, 0x1b, 0x0a, 0x05, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, - 0x68, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x52, 0x03, 0x65, 0x6e, - 0x64, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, - 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x63, 0x68, 0x75, - 0x6e, 0x6b, 0x73, 0x50, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x22, 0x90, 0x02, 0x0a, 0x12, - 0x43, 0x6c, 0x61, 0x73, 0x73, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x48, 0x01, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x74, 0x88, - 0x01, 0x01, 0x12, 0x31, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x73, 0x5f, - 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, - 0x68, 0x48, 0x02, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x73, 0x52, 0x6f, - 0x6f, 0x74, 0x88, 0x01, 0x01, 0x12, 0x2d, 0x0a, 0x0c, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, - 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, - 0x73, 0x68, 0x48, 0x03, 0x52, 0x0b, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x52, 0x6f, 0x6f, - 0x74, 0x88, 0x01, 0x01, 0x12, 0x24, 0x0a, 0x07, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x48, - 0x00, 0x52, 0x07, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x03, 0x66, 0x69, - 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x04, 0x2e, 0x46, 0x69, 0x6e, 0x48, 0x00, 0x52, - 0x03, 0x66, 0x69, 0x6e, 0x42, 0x0b, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x73, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x63, - 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x73, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x42, 0x0f, 0x0a, - 0x0d, 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x22, 0x69, - 0x0a, 0x10, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4c, 0x65, 0x61, 0x66, 0x51, 0x75, 0x65, - 0x72, 0x79, 0x12, 0x39, 0x0a, 0x15, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x5f, 0x73, - 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x52, 0x13, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, - 0x63, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x1a, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x46, 0x65, 0x6c, - 0x74, 0x32, 0x35, 0x32, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x85, 0x01, 0x0a, 0x11, 0x53, 0x74, - 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, - 0x22, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x08, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x12, 0x27, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4c, 0x65, 0x61, 0x66, - 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x23, 0x0a, 0x03, - 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x53, 0x74, 0x6f, 0x72, - 0x61, 0x67, 0x65, 0x4c, 0x65, 0x61, 0x66, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x03, 0x65, 0x6e, - 0x64, 0x22, 0xaa, 0x01, 0x0a, 0x16, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x53, 0x74, - 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, - 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x64, 0x6f, - 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x24, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x6f, - 0x6f, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x52, - 0x09, 0x73, 0x74, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x68, - 0x75, 0x6e, 0x6b, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x50, 0x65, 0x72, 0x50, - 0x72, 0x6f, 0x6f, 0x66, 0x12, 0x28, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x61, 0x6e, - 0x67, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x43, - 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, - 0x65, 0x12, 0x30, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x53, 0x74, - 0x6f, 0x72, 0x65, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x22, 0xa8, 0x01, 0x0a, 0x17, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, - 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x29, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x48, 0x01, 0x52, 0x09, 0x73, 0x74, - 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x88, 0x01, 0x01, 0x12, 0x2c, 0x0a, 0x07, 0x73, 0x74, - 0x6f, 0x72, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x43, 0x6f, - 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, - 0x07, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x03, 0x66, 0x69, 0x6e, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x04, 0x2e, 0x46, 0x69, 0x6e, 0x48, 0x00, 0x52, 0x03, 0x66, - 0x69, 0x6e, 0x42, 0x0b, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x42, - 0x0d, 0x0a, 0x0b, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x73, 0x73, 0x65, 0x73, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x22, 0x69, 0x0a, 0x10, 0x53, 0x74, + 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4c, 0x65, 0x61, 0x66, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x39, + 0x0a, 0x15, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, + 0x67, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, + 0x48, 0x61, 0x73, 0x68, 0x52, 0x13, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x53, 0x74, + 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x1a, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x46, 0x65, 0x6c, 0x74, 0x32, 0x35, 0x32, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x85, 0x01, 0x0a, 0x11, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, + 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x22, 0x0a, 0x07, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x41, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, + 0x27, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, + 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4c, 0x65, 0x61, 0x66, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x23, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4c, + 0x65, 0x61, 0x66, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x22, 0xaa, 0x01, + 0x0a, 0x16, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, + 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + 0x12, 0x24, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x52, 0x09, 0x73, 0x74, 0x61, + 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, + 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x0e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x50, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x6f, 0x66, + 0x12, 0x28, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x12, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x51, 0x75, + 0x65, 0x72, 0x79, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x43, 0x0a, 0x0f, 0x43, 0x6f, + 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x12, 0x30, 0x0a, + 0x08, 0x6b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x14, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x64, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, 0x6b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, + 0xad, 0x02, 0x0a, 0x17, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x53, 0x74, 0x6f, 0x72, + 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, 0x0a, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x05, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x48, 0x01, 0x52, 0x09, 0x73, 0x74, 0x61, 0x74, 0x65, 0x52, + 0x6f, 0x6f, 0x74, 0x88, 0x01, 0x01, 0x12, 0x38, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, + 0x63, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x08, 0x2e, 0x46, 0x65, 0x6c, 0x74, 0x32, 0x35, 0x32, 0x48, 0x02, 0x52, 0x0f, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x88, 0x01, 0x01, + 0x12, 0x2c, 0x0a, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x10, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x53, 0x74, 0x6f, 0x72, + 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x12, 0x18, + 0x0a, 0x03, 0x66, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x04, 0x2e, 0x46, 0x69, + 0x6e, 0x48, 0x00, 0x52, 0x03, 0x66, 0x69, 0x6e, 0x12, 0x34, 0x0a, 0x0b, 0x72, 0x61, 0x6e, 0x67, + 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x6f, 0x66, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, + 0x50, 0x61, 0x74, 0x72, 0x69, 0x63, 0x69, 0x61, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x72, 0x6f, + 0x6f, 0x66, 0x52, 0x0a, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x72, 0x6f, 0x6f, 0x66, 0x42, 0x0b, + 0x0a, 0x09, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1273,34 +1320,38 @@ var file_p2p_proto_snapshot_proto_depIdxs = []int32{ 16, // 12: ContractRangeResponse.classes_root:type_name -> Hash 4, // 13: ContractRangeResponse.range:type_name -> ContractRange 17, // 14: ContractRangeResponse.fin:type_name -> Fin - 16, // 15: ClassRangeRequest.root:type_name -> Hash - 16, // 16: ClassRangeRequest.start:type_name -> Hash - 16, // 17: ClassRangeRequest.end:type_name -> Hash - 16, // 18: ClassRangeResponse.root:type_name -> Hash - 16, // 19: ClassRangeResponse.contracts_root:type_name -> Hash - 16, // 20: ClassRangeResponse.classes_root:type_name -> Hash - 18, // 21: ClassRangeResponse.classes:type_name -> Classes - 17, // 22: ClassRangeResponse.fin:type_name -> Fin - 16, // 23: StorageLeafQuery.contract_storage_root:type_name -> Hash - 19, // 24: StorageLeafQuery.key:type_name -> Felt252 - 15, // 25: StorageRangeQuery.address:type_name -> Address - 8, // 26: StorageRangeQuery.start:type_name -> StorageLeafQuery - 8, // 27: StorageRangeQuery.end:type_name -> StorageLeafQuery - 16, // 28: ContractStorageRequest.state_root:type_name -> Hash - 9, // 29: ContractStorageRequest.query:type_name -> StorageRangeQuery - 20, // 30: ContractStorage.keyValue:type_name -> ContractStoredValue - 16, // 31: ContractStorageResponse.state_root:type_name -> Hash - 11, // 32: ContractStorageResponse.storage:type_name -> ContractStorage - 17, // 33: ContractStorageResponse.fin:type_name -> Fin - 19, // 34: PatriciaNode.Edge.path:type_name -> Felt252 - 19, // 35: PatriciaNode.Edge.value:type_name -> Felt252 - 19, // 36: PatriciaNode.Binary.left:type_name -> Felt252 - 19, // 37: PatriciaNode.Binary.right:type_name -> Felt252 - 38, // [38:38] is the sub-list for method output_type - 38, // [38:38] is the sub-list for method input_type - 38, // [38:38] is the sub-list for extension type_name - 38, // [38:38] is the sub-list for extension extendee - 0, // [0:38] is the sub-list for field type_name + 1, // 15: ContractRangeResponse.range_proof:type_name -> PatriciaRangeProof + 16, // 16: ClassRangeRequest.root:type_name -> Hash + 16, // 17: ClassRangeRequest.start:type_name -> Hash + 16, // 18: ClassRangeRequest.end:type_name -> Hash + 16, // 19: ClassRangeResponse.root:type_name -> Hash + 16, // 20: ClassRangeResponse.contracts_root:type_name -> Hash + 16, // 21: ClassRangeResponse.classes_root:type_name -> Hash + 18, // 22: ClassRangeResponse.classes:type_name -> Classes + 17, // 23: ClassRangeResponse.fin:type_name -> Fin + 1, // 24: ClassRangeResponse.range_proof:type_name -> PatriciaRangeProof + 16, // 25: StorageLeafQuery.contract_storage_root:type_name -> Hash + 19, // 26: StorageLeafQuery.key:type_name -> Felt252 + 15, // 27: StorageRangeQuery.address:type_name -> Address + 8, // 28: StorageRangeQuery.start:type_name -> StorageLeafQuery + 8, // 29: StorageRangeQuery.end:type_name -> StorageLeafQuery + 16, // 30: ContractStorageRequest.state_root:type_name -> Hash + 9, // 31: ContractStorageRequest.query:type_name -> StorageRangeQuery + 20, // 32: ContractStorage.keyValue:type_name -> ContractStoredValue + 16, // 33: ContractStorageResponse.state_root:type_name -> Hash + 19, // 34: ContractStorageResponse.contract_address:type_name -> Felt252 + 11, // 35: ContractStorageResponse.storage:type_name -> ContractStorage + 17, // 36: ContractStorageResponse.fin:type_name -> Fin + 1, // 37: ContractStorageResponse.range_proof:type_name -> PatriciaRangeProof + 19, // 38: PatriciaNode.Edge.path:type_name -> Felt252 + 19, // 39: PatriciaNode.Edge.value:type_name -> Felt252 + 19, // 40: PatriciaNode.Binary.left:type_name -> Felt252 + 19, // 41: PatriciaNode.Binary.right:type_name -> Felt252 + 42, // [42:42] is the sub-list for method output_type + 42, // [42:42] is the sub-list for method input_type + 42, // [42:42] is the sub-list for extension type_name + 42, // [42:42] is the sub-list for extension extendee + 0, // [0:42] is the sub-list for field type_name } func init() { file_p2p_proto_snapshot_proto_init() } diff --git a/sync/snap_server.go b/sync/snap_server.go index 481a6c376c..fa86e9d5a0 100644 --- a/sync/snap_server.go +++ b/sync/snap_server.go @@ -2,7 +2,6 @@ package sync import ( "context" - "errors" "math/big" "github.com/NethermindEth/juno/adapters/core2p2p" @@ -13,6 +12,7 @@ import ( "github.com/NethermindEth/juno/core/trie" "github.com/NethermindEth/juno/p2p/starknet/spec" "github.com/NethermindEth/juno/utils/iter" + "github.com/ethereum/go-ethereum/log" ) type ContractRangeStreamingResult struct { @@ -44,9 +44,9 @@ type ClassRangeStreamingResult struct { } type SnapServer interface { - GetContractRange(ctx context.Context, request *spec.ContractRangeRequest) iter.Seq2[*ContractRangeStreamingResult, error] - GetStorageRange(ctx context.Context, request *StorageRangeRequest) iter.Seq2[*StorageRangeStreamingResult, error] - GetClassRange(ctx context.Context, request *spec.ClassRangeRequest) iter.Seq2[*ClassRangeStreamingResult, error] + GetContractRange(ctx context.Context, request *spec.ContractRangeRequest) (iter.Seq[*ContractRangeStreamingResult], error) + GetStorageRange(ctx context.Context, request *StorageRangeRequest) (iter.Seq[*StorageRangeStreamingResult], error) + GetClassRange(ctx context.Context, request *spec.ClassRangeRequest) (iter.Seq[*ClassRangeStreamingResult], error) GetClasses(ctx context.Context, classHashes []*felt.Felt) ([]*spec.Class, error) } @@ -57,6 +57,12 @@ type SnapServerBlockchain interface { var _ SnapServerBlockchain = (*blockchain.Blockchain)(nil) +func NewSnapServer(blockchain SnapServerBlockchain) SnapServer { + return &snapServer{ + blockchain: blockchain, + } +} + type snapServer struct { blockchain SnapServerBlockchain } @@ -78,25 +84,25 @@ func determineMaxNodes(specifiedMaxNodes uint64) uint64 { return maxNodePerRequest } -func (b *snapServer) GetClassRange(ctx context.Context, request *spec.ClassRangeRequest) iter.Seq2[*ClassRangeStreamingResult, error] { - return func(yield func(*ClassRangeStreamingResult, error) bool) { +func (b *snapServer) GetClassRange(ctx context.Context, request *spec.ClassRangeRequest) (iter.Seq[*ClassRangeStreamingResult], error) { + return func(yield func(*ClassRangeStreamingResult) bool) { stateRoot := p2p2core.AdaptHash(request.Root) s, err := b.blockchain.GetStateForStateRoot(stateRoot) if err != nil { - yield(nil, err) + log.Error("error getting state for state root", "err", err) return } contractRoot, classRoot, err := s.StateAndClassRoot() if err != nil { - yield(nil, err) + log.Error("error getting state and class root", "err", err) return } ctrie, classCloser, err := s.ClassTrie() if err != nil { - yield(nil, err) + log.Error("error getting class trie", "err", err) return } defer func() { _ = classCloser() }() @@ -119,69 +125,61 @@ func (b *snapServer) GetClassRange(ctx context.Context, request *spec.ClassRange return nil }) if err != nil { - yield(nil, err) + log.Error("error iterating class trie", "err", err) return } coreClasses, err := b.blockchain.GetClasses(classkeys) if err != nil { - yield(nil, err) + log.Error("error getting classes", "err", err) return } for _, coreclass := range coreClasses { if coreclass == nil { - yield(nil, errors.New("class is nil")) + log.Error("nil class in the returned array of core classes") return } response.Classes = append(response.Classes, core2p2p.AdaptClass(coreclass)) } - if err != nil { - yield(nil, err) - return - } - shouldContinue := yield(&ClassRangeStreamingResult{ ContractsRoot: contractRoot, ClassesRoot: classRoot, Range: response, RangeProof: Core2P2pProof(proofs), - }, err) + }) if finished || !shouldContinue { break } startAddr = classkeys[len(classkeys)-1] } - - // TODO: not needed? - just stop the loop - yield(nil, nil) - } + }, nil } func (b *snapServer) GetContractRange( ctx context.Context, request *spec.ContractRangeRequest, -) iter.Seq2[*ContractRangeStreamingResult, error] { - return func(yield func(*ContractRangeStreamingResult, error) bool) { +) (iter.Seq[*ContractRangeStreamingResult], error) { + return func(yield func(*ContractRangeStreamingResult) bool) { stateRoot := p2p2core.AdaptHash(request.StateRoot) s, err := b.blockchain.GetStateForStateRoot(stateRoot) if err != nil { - yield(nil, err) + log.Error("error getting state for state root", "err", err) return } contractRoot, classRoot, err := s.StateAndClassRoot() if err != nil { - yield(nil, err) + log.Error("error getting state and class root", "err", err) return } strie, scloser, err := s.StorageTrie() if err != nil { - yield(nil, err) + log.Error("error getting storage trie", "err", err) return } defer func() { _ = scloser() }() @@ -223,7 +221,7 @@ func (b *snapServer) GetContractRange( return nil }) if err != nil { - yield(nil, err) + log.Error("error iterating storage trie", "err", err) return } @@ -232,30 +230,28 @@ func (b *snapServer) GetContractRange( ClassesRoot: classRoot, Range: states, RangeProof: Core2P2pProof(proofs), - }, nil) + }) if finished || !shouldContinue { break } } - - yield(nil, nil) - } + }, nil } -func (b *snapServer) GetStorageRange(ctx context.Context, request *StorageRangeRequest) iter.Seq2[*StorageRangeStreamingResult, error] { - return func(yield func(*StorageRangeStreamingResult, error) bool) { +func (b *snapServer) GetStorageRange(ctx context.Context, request *StorageRangeRequest) (iter.Seq[*StorageRangeStreamingResult], error) { + return func(yield func(*StorageRangeStreamingResult) bool) { stateRoot := request.StateRoot s, err := b.blockchain.GetStateForStateRoot(stateRoot) if err != nil { - yield(nil, err) + log.Error("error getting state for state root", "err", err) return } contractRoot, classRoot, err := s.StateAndClassRoot() if err != nil { - yield(nil, err) + log.Error("error getting state and class root", "err", err) return } @@ -270,7 +266,7 @@ func (b *snapServer) GetStorageRange(ctx context.Context, request *StorageRangeR strie, err := s.StorageTrieForAddr(p2p2core.AdaptAddress(query.Address)) if err != nil { - yield(nil, err) + log.Error("error getting storage trie for address", "addr", query.Address.String(), "err", err) return } @@ -282,10 +278,10 @@ func (b *snapServer) GetStorageRange(ctx context.Context, request *StorageRangeR StorageAddr: p2p2core.AdaptAddress(query.Address), Range: values, RangeProof: Core2P2pProof(proofs), - }, nil) + }) }) if err != nil { - yield(nil, err) + log.Error("error handling storage range request", "err", err) return } @@ -295,11 +291,11 @@ func (b *snapServer) GetStorageRange(ctx context.Context, request *StorageRangeR break } } - } + }, nil } // GetStorageRangeStd TODO: move/change đŸ‘† - just to check it can work on spec structs -func (b *snapServer) GetStorageRangeStd(ctx context.Context, request *spec.ContractStorageRequest) iter.Seq2[*StorageRangeStreamingResult, error] { +func (b *snapServer) GetStorageRangeStd(ctx context.Context, request *spec.ContractStorageRequest) (iter.Seq[*StorageRangeStreamingResult], error) { req := &StorageRangeRequest{ StateRoot: p2p2core.AdaptHash(request.StateRoot), Queries: request.Query, diff --git a/sync/snap_server_test.go b/sync/snap_server_test.go index bd0bf74683..6b4c92e4d6 100644 --- a/sync/snap_server_test.go +++ b/sync/snap_server_test.go @@ -2,7 +2,9 @@ package sync import ( "context" + "errors" "fmt" + "github.com/NethermindEth/juno/core/trie" "testing" "github.com/NethermindEth/juno/adapters/core2p2p" @@ -40,17 +42,17 @@ func TestClassRange(t *testing.T) { chunksPerProof := 150 var classResult *ClassRangeStreamingResult - server.GetClassRange(context.Background(), + iter, err := server.GetClassRange(context.Background(), &spec.ClassRangeRequest{ Root: core2p2p.AdaptHash(stateRoot), Start: core2p2p.AdaptHash(startRange), ChunksPerProof: uint32(chunksPerProof), - })(func(result *ClassRangeStreamingResult, err error) bool { - if err != nil { - fmt.Printf("err %s\n", err) - t.Fatal(err) - } - + }) + if err != nil { + fmt.Printf("err %s\n", err) + t.Fatal(err) + } + iter(func(result *ClassRangeStreamingResult) bool { if result != nil { classResult = result } @@ -86,12 +88,15 @@ func TestContractRange(t *testing.T) { chunksPerProof := 150 var contractResult *ContractRangeStreamingResult - server.GetContractRange(context.Background(), + ctrIter, err := server.GetContractRange(context.Background(), &spec.ContractRangeRequest{ StateRoot: core2p2p.AdaptHash(stateRoot), Start: core2p2p.AdaptAddress(startRange), ChunksPerProof: uint32(chunksPerProof), - })(func(result *ContractRangeStreamingResult, err error) bool { + }) + assert.NoError(t, err) + + ctrIter(func(result *ContractRangeStreamingResult) bool { if err != nil { fmt.Printf("err %s\n", err) t.Fatal(err) @@ -171,12 +176,10 @@ func TestContractStorageRange(t *testing.T) { keys := make([]*felt.Felt, 0, test.expectedLeaves) vals := make([]*felt.Felt, 0, test.expectedLeaves) - server.GetStorageRange(context.Background(), request)(func(result *StorageRangeStreamingResult, err error) bool { - if err != nil { - fmt.Printf("err %s\n", err) - t.Fatal(err) - } + stoIter, err := server.GetStorageRange(context.Background(), request) + assert.NoError(t, err) + stoIter(func(result *StorageRangeStreamingResult) bool { if result != nil { for _, r := range result.Range { keys = append(keys, p2p2core.AdaptFelt(r.Key)) @@ -204,3 +207,39 @@ func feltFromString(str string) *felt.Felt { } return f } + +// TODO: Duplicated methods - maybe they belongs to `sync` or `p2p` - will see +func VerifyGlobalStateRoot(globalStateRoot, classRoot, storageRoot *felt.Felt) error { + var stateVersion = new(felt.Felt).SetBytes([]byte(`STARKNET_STATE_V0`)) + + if classRoot.IsZero() { + if globalStateRoot.Equal(storageRoot) { + return nil + } else { + return errors.New("invalid global state root") + } + } + + if !crypto.PoseidonArray(stateVersion, storageRoot, classRoot).Equal(globalStateRoot) { + return errors.New("invalid global state root") + } + return nil +} + +func VerifyTrie( + expectedRoot *felt.Felt, + paths, hashes []*felt.Felt, + proofs []trie.ProofNode, + height uint8, + hash func(*felt.Felt, *felt.Felt) *felt.Felt, +) (bool, error) { + hasMore, valid, err := trie.VerifyRange(expectedRoot, nil, paths, hashes, proofs, hash, height) + if err != nil { + return false, err + } + if !valid { + return false, errors.New("invalid proof") + } + + return hasMore, nil +} diff --git a/sync/snapsyncer_test.go b/sync/snapsyncer_test.go deleted file mode 100644 index b4d5495bd0..0000000000 --- a/sync/snapsyncer_test.go +++ /dev/null @@ -1,384 +0,0 @@ -package sync - -import ( - "context" - "encoding/hex" - "errors" - "math/big" - "net/http" - "os" - "testing" - "time" - - "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/db/pebble" - "github.com/NethermindEth/juno/encoder" - "github.com/NethermindEth/juno/starknetdata" - "github.com/NethermindEth/juno/utils" - "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/stretchr/testify/assert" -) - -func hexToFelt(str string) *felt.Felt { - flt := felt.Zero.Clone() - - bint, ok := big.NewInt(0).SetString(str, 16) - if !ok { - panic("set fail") - } - - return flt.SetBigInt(bint) -} - -func V0ClassFromString(key, bytes string) (*felt.Felt, core.Class, error) { - clsHash := hexToFelt(key) - cls := &core.DeclaredClass{} - clsBytes, err := hex.DecodeString(bytes) - if err != nil { - return nil, nil, err - } - err = encoder.Unmarshal(clsBytes, cls) - if err != nil { - return nil, nil, err - } - - h, err := cls.Class.Hash() - if err != nil { - return nil, nil, err - } - - if !h.Equal(clsHash) { - return nil, nil, errors.New("recalculated hash missed") - } - - return clsHash, cls.Class, nil -} - -func V1ClassFromString(key, bytes string) (*felt.Felt, *felt.Felt, core.Class, error) { - clsHash := hexToFelt(key) - cls := &core.DeclaredClass{} - clsBytes, err := hex.DecodeString(bytes) - if err != nil { - return nil, nil, nil, err - } - err = encoder.Unmarshal(clsBytes, cls) - if err != nil { - return nil, nil, nil, err - } - compiledClsHash := cls.Class.(*core.Cairo1Class).Compiled.Hash() - - return clsHash, compiledClsHash, cls.Class, nil -} - -func TestSnapOfflineCopy(t *testing.T) { - scenarios := []struct { - name string - scenario func(t *testing.T, bc *blockchain.Blockchain) error - }{ - { - name: "basic contract", - scenario: func(t *testing.T, bc *blockchain.Blockchain) error { - expectedRoot := hexToFelt("07391bf3b040e9df8ad739f0b0be3aa2d83f6c5260f7b745549be2432122d369") - - key := new(felt.Felt).SetUint64(uint64(12)) - nonce := new(felt.Felt).SetUint64(uint64(1)) - classHash, cls, err := V0ClassFromString("000118cec7463a59c23203e8e4dd7769a914da30c68d7b5ff404531b1e85de9a", "a262417419d22165436c617373da00010005a5634162695908355b7b226d656d62657273223a205b7b226e616d65223a2022746f222c20226f6666736574223a20302c202274797065223a202266656c74227d2c207b226e616d65223a202273656c6563746f72222c20226f6666736574223a20312c202274797065223a202266656c74227d2c207b226e616d65223a2022646174615f6f6666736574222c20226f6666736574223a20322c202274797065223a202266656c74227d2c207b226e616d65223a2022646174615f6c656e222c20226f6666736574223a20332c202274797065223a202266656c74227d5d2c20226e616d65223a20224163636f756e7443616c6c4172726179222c202273697a65223a20342c202274797065223a2022737472756374227d2c207b22696e70757473223a205b7b226e616d65223a20227075626c69634b6579222c202274797065223a202266656c74227d5d2c20226e616d65223a2022636f6e7374727563746f72222c20226f757470757473223a205b5d2c202274797065223a2022636f6e7374727563746f72227d2c207b22696e70757473223a205b5d2c20226e616d65223a20226765745075626c69634b6579222c20226f757470757473223a205b7b226e616d65223a20227075626c69634b6579222c202274797065223a202266656c74227d5d2c202273746174654d75746162696c697479223a202276696577222c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a2022696e746572666163654964222c202274797065223a202266656c74227d5d2c20226e616d65223a2022737570706f727473496e74657266616365222c20226f757470757473223a205b7b226e616d65223a202273756363657373222c202274797065223a202266656c74227d5d2c202273746174654d75746162696c697479223a202276696577222c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a20226e65775075626c69634b6579222c202274797065223a202266656c74227d5d2c20226e616d65223a20227365745075626c69634b6579222c20226f757470757473223a205b5d2c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a202268617368222c202274797065223a202266656c74227d2c207b226e616d65223a20227369676e61747572655f6c656e222c202274797065223a202266656c74227d2c207b226e616d65223a20227369676e6174757265222c202274797065223a202266656c742a227d5d2c20226e616d65223a2022697356616c69645369676e6174757265222c20226f757470757473223a205b7b226e616d65223a2022697356616c6964222c202274797065223a202266656c74227d5d2c202273746174654d75746162696c697479223a202276696577222c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a202263616c6c5f61727261795f6c656e222c202274797065223a202266656c74227d2c207b226e616d65223a202263616c6c5f6172726179222c202274797065223a20224163636f756e7443616c6c41727261792a227d2c207b226e616d65223a202263616c6c646174615f6c656e222c202274797065223a202266656c74227d2c207b226e616d65223a202263616c6c64617461222c202274797065223a202266656c742a227d5d2c20226e616d65223a20225f5f76616c69646174655f5f222c20226f757470757473223a205b5d2c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a2022636c6173735f68617368222c202274797065223a202266656c74227d5d2c20226e616d65223a20225f5f76616c69646174655f6465636c6172655f5f222c20226f757470757473223a205b5d2c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a2022636c6173735f68617368222c202274797065223a202266656c74227d2c207b226e616d65223a202273616c74222c202274797065223a202266656c74227d2c207b226e616d65223a20227075626c69634b6579222c202274797065223a202266656c74227d5d2c20226e616d65223a20225f5f76616c69646174655f6465706c6f795f5f222c20226f757470757473223a205b5d2c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a202263616c6c5f61727261795f6c656e222c202274797065223a202266656c74227d2c207b226e616d65223a202263616c6c5f6172726179222c202274797065223a20224163636f756e7443616c6c41727261792a227d2c207b226e616d65223a202263616c6c646174615f6c656e222c202274797065223a202266656c74227d2c207b226e616d65223a202263616c6c64617461222c202274797065223a202266656c742a227d5d2c20226e616d65223a20225f5f657865637574655f5f222c20226f757470757473223a205b7b226e616d65223a2022726573706f6e73655f6c656e222c202274797065223a202266656c74227d2c207b226e616d65223a2022726573706f6e7365222c202274797065223a202266656c742a227d5d2c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b5d2c20226e616d65223a20224465636c617265642066726f6d20737461726b6e65742d7273207465737420636173652e2054696d657374616d7020286d73293a2031373131353832303039313838222c20226f757470757473223a205b5d2c202274797065223a202266756e6374696f6e227d5d6750726f6772616d793c284834734941414141414141412f2b783943322f634e72726f583948317863576d5864654870455253436b347534447254316c6a487a725764377535744130476a346468437870705a5363356a692f7a334137316d394b416b6b714c473432344b6e4c4f78527678652f4637382b496e383432675442512f73364b5678424437626f504966684544755033683062427774764d5137656d6e386467512b5734446141454336584337542f30742f425a39682f6a3849325050794237706373764a706463516966326a326a624273344e486c4d76736c706237323147383942656c666a6164672b33537849394f796b5657517372414c31726245392f376f41564c686f53437a4c6c69776c507476586c4b4b537134716c48624b5a55744768566554514f4954337a4c4a6b6a4271555551674d616d316c5151714a4f46554a4147415755424b6e337131703668344f7138394e59756e66753270565478646249565955756a734b4b5262386c4e536350334834696d70503555534143575957715559434b624967735169715341774e616e5a456b52644a55686c316b76734e644b61503671515670755a677945506d39516979347773536a424742424e49326f726a637858483530363776344e7531736845395239486b456b7049673631434734524f7563534f6d39704c53716574736d66743869586e3235735564747943434f454c5063317556734a7441774c4e703961567074773546582b6d774e632f342b55513031516576455747735246626f6f6774326f42783759354161674c75636562686434704768634e743377677531546e7173715a7062585974566c3253714972697369642b793276754f53565663574553792b4f7555705546585366756e43463575394c6149754730477754466554355656347a556161526b56546678656d3772506d754a726c597749534668645a446d6c4f5a3630574e6c42625a75485244744b6f586e4a514a715a504a56536855793143574e546757514c546974503247736b6b6c57313770777970614330466d3163582f635856474d6a76792f66364a36736c6551434e373457596b534e6b2b65484e4a446c6953383057665966636d377233384c4553346e4941666876723447517154776a5052495336544f703450434c4c6d6a756e34624f347332494935434547346f4973355a67785361386d51505763554951765067516339354d30584e7651646579693366306f313252705278357a443076464f5334624e2b68415754374845303459387836383370314470525132684a794c3776536d476264664949467a6969495355572b77646f4e4e6369766a4636636b672f70374e6a35544f465542553464767049304e4c4357632f386c7a676762794b6b354b72496d51416d41523750636d5a7264457335454f37526c367834316a49732f73533061666c6452716b437335754370326d423278776335475568334b4a73376c507035656e353963514f6d4a616b7a6e47646771354c667077717a4b4c61514d337252466e63556e6d503230786b6a303178317369622f466b3169534c4a3539676332694375376d713744564d4f3366554536476d487153334d6f5a6d317851757073396c345a5934586869594443396a7978723779344a52574e4e67766e4f55435179635653396e66726a736d586736356a4758656453334575706d6e6c4f677a5a3869455459396756564c6178456846776673396e5a666a6f335044424c4b5633746355333044733667726b514578366b6b594b6a484535386151396c5045665772576e3036676841756e706f544e6d6566625a64315a49586c62342b7644424f7a3571452b702b4758634f6b4c31736a6c6e623833612f756a744a53497871392b756b563251775959737257354e586f4d64586b79573276547132476e6a626f57425853725765726535637757327874562b7437583378642f53324d4b74512b426d4964733539365a54702f61475139647967626459744b6254746637692f2f4d7049316c3250794e39573442355738696973667375746448627379383776634e596d6a324246665a6e4a6a4a502b64764d32564f72396251374b4266675254335034656d61796154546d3132716e7532623264774e36764b587873547849335165426370493042656875386845383472574c3352702f52524f716e656e59734b61744d4a3676713270456b62447377734661336b4f2f687231742f773845787636553463563172752b6e4e374f76356e644244556a357a39586e2f2f38615a4a4d655a39666258384f4f6777474f73756553656a6f7359586d38712b436959357061613030647461614858734d75397459526332395a516e62336e2b664a323368665a78652f7166592f6474336877417a6177686265304939542f6d625a713143327154686b4578654a3053397a524a616643496f50714b597435366139594a5238645371397a354f5552786443737a30694b634b3771707072684c7561734475755333734c56386d3069472f706168464a327a5261665852755130657a3847746f6e37783973394979616e4a78594a6151366277756549706d347a4b38784d352f6e375178436d624d375758585052585437546b6659666e4a52337261627a6b50745a502f4b58466b31554a37442b3769644c4a546453334255783033444c7538457955734d4d7855664e77544c527273625062453533576e4d6d66335a786248792f6f364a4e357a675556746d75646c75346b72426455366c2b786c34304275362f767164317a496b425044515a6b582b4756516b4d69512f6f6b4f702f636f5675566c674e65497731795371727272546932384f59645a3034734257667a62616b323650636e574b7278336479576d6370544c517334784d554357304f6d2b4c68303935574c796647522b64505352395937315a726d6e6a38744c6270654739764456374b307a307137374c7972465a6258315a5939355a667a3149707837342b4e6f2f6c6a734571434d4d354f6c4e6d7742597469467162765231353478317a2f6e766b66306a2b5a7634697a4b4441506b6b39426e412b2b4438496b48666e4845556a482f33486b7278665a515463503747456466666e4e3237773358686b787533746759524b6665497646692b3953474a3776737a674f3569766d7876353677334c7363654a46487a353545547678765342616e2f6a7268346431654f4b745675757373747233652f4657537452797466376b4a70486e66776a434f3763344c65655049322b7a665a6a3966526574487a64484c773177624279746c3875594a656b665831504f325a4a464c50535a477977793772352b2f5a6f434a6e5565507a36344c4578596c4c5077346f2b2f684839356151534c2b47544677712f796244367742332f7a705966502f4958795051564f3454436e51726854426a4d4975566967565a644c61507a77796f432f68366b6f2f485759424f456a632f3331356b735133686d764447674553794d302f713842444c614b6d51454f58564a34684b53612f4b657753374852746a703944704a436d77356566306966705342633579332b4576766561755865652b466978614b54374939554e70486e4a79394b2f2f43712f4d6578555137594a4e4772564938716677744a4a767458794a4b5339324a3858426450313174312b6c524568617169676b4d4b4a455a4856516770696b4c5770746b76367a7557754f6b4446726e65596847784f4435416762654a564a473671556671625749616f6a634c3056744151505446374232363842746b716f6a6630696a2b426a6d4e4362444b436244374a79424f3170463378397949655973444648795650425742597a304372354c5245445175424932706d4b412f52554843446c6a53475830716f695a36525a3352305a41314b644e4d4159656566486144634c6b2b51456c5871464f524d39586e51776f71476c4b6d705a516247683038624e5a525967514a69354c316568582f487634654c7150316739475654586e4a76667559424b76594b495a366363796978413343684e3278365065772f76654c6443363837376950352b6c6a343157326550434d2f324f387654352f4d2f73396e426550357274482b5744444d2f37376c54452f4e705a2f53596639345830316774674931346d78596e46734a506465614b776a672f33723056735a79647049416630782f3372796c3553702f3233384649514c7777734e4c2f4972417a494d786e2b6c5164544c666c386e39797a697649424f666739584c4c784c376d505843786475454334436e3858474b2b4f3346393678416234374e6c374d6a52384d3739694136622f7a635438593050676870526c39393534372f69526552386d4c7259414d7a69752f6766652f67666370377755742f325759476247386432487258665237794437377138634657786976754750512b392f672b31524978666f316c58356c485a79716b5046584137342f4e6e7066414f6d71393858766f57455978694c342b4c4265764f6a6b356a6962346f7847392b72583262567275722b632f2f7a4c6439384e4547454f4559484569494263496c424268507771784576752b395967586e4a2f556c6a4169726c4c746c4a4b722b30524b396b324253646541564a6979507a6f7065484944576c4d55657266796955307054336c6b327a42764658632f2f5671784d4a352f354e6a3979305448536a444e6e7847624474396245505169504346783973357031634765673563777435794759536f7a755a6577326b75306865316b50716438642f356b3231452f6537592b503333334563577362513267424e58352b564c57786866542f34793657516c7970506c61484b547963354e516968466465596e59566d71674b69682b466c523253314b30436665597548477756336f4a593852792b59702f333254524366464f7441397a6964772b356f623562467239794257694672623054317a736e336e35434f4c677555584e7964754e31536c6f695a516646616c5a796538464246575a367771374253557151565579687530746f7068393051436b4561436c69572f61707079486836676b6932362f734e6d774237545630364365497a7a68474d7138473053636f4d6b556d546e426c6b75676b794b4e657a62754f3644463453756d3735522f2f656e794e7473574253332f7a3778313247635249392b736f3655536e3031797a48354e66715353776339465a64334c486e374f4638462f742f59463565464b514675784a4c484b46526947676e7656566e4e45767a2b654934664e326b416a382f446845564c7a326361474366696a464d64573546716a46636d57366c2b57744e70334d396d622b34384b5a74422f4b7533436859335731632b656e6f746362334731486b71766c3333593871336c7a445856654b795671366c7448657275626b372b4352734c70692f38694a56646d756c50474c32737475734c7a3852753576562b6f7369743756636d2f54474932492f6d5974795866615a2b592b70436f38335779786f74756b502f767068453678593548356b55527973772b773270424f4954724a384c614d2b347931395875556d574c4177435a5942692f4c7371507a74354e5433313439686b6a31637344674a51693870344b34334c50773332327a594b6c3155354f2b6472494a353545566674754f4f6a61506b5334375057775665665053314973667972544e767454714e49752b4c4b706f6467483538502b5a4e4e542f6d4b794565746f34554c2f756a5845484654546a3953482f7834767652474b74412b74467441385a6f6e4331492f59677253702f683350697069524c37324468614d48386465636b36796d324b66553559464871727a42704b694d7648304d2f6f36774a36636872643564713566467974334e4237714b6c782b39586a7a4f546e7055356e75784e6571695675336c6e7a523857715574764a754e2b536b79342b4d7050614471735067613068557072362f525a3436674461464b462b69725a655935656363776438662f5131485249482f326246316e483561373473364a62322b634e6d466668424969483132704347394f766251344f697a3856544e7371315237576c4c3231484f597271387231582f476f326b794d706d766e61614e715470754b4163695474776e63466b6457685468586c77424c4b635a314873397973716c426652437a65724d4f5970527239306b69524842766c732f7a7637372b724f704c306639304657775a6830472f394e2b662f663362316b33747864585a36635a4e682f756974486c6d6851535734624c336242464a4a4b4c654f435a74777047506151523332544d313376376b6d656464556b614777622b6f613838303543666f4e2f7453707a31793334354478436857414f747a43626747326377384f304f55657474416c3345526a544e4e64724c773464752b392b46374d5656516d43347050316f3447655850724750764e374a37453748617a6f646e38646f44316d474652474e69744874445931554d4c754a5152566f654d744d4630347278564d7153783235633332784b6e554f79744b4959706f78674668796f577a6876367a634366794d434c79644275337756635a664f75376a39747a647245547475736d7a74562f5a5a64655876496f6c75764e70533077394930424e4d715a6b454c36787a7954437972562b6b314f4b2b716648516f6578576573704a5874783872576d365474705a2f444e676e4166577551687a53372f6137645632705342634d534c6347536c426a7538643855396d32674c7031647575473869714a7541725834492f523457314465572f46574b676c76623953334e7a61334e6d4d5a584f714d6f4932303451365a4466383978744b4b35666762666471577a57546e6b7776726f6c6851504f2b6c314b39466f75434e74302f37706e59395a38697957764e524c63444b56365664523874444d6f754a4f6147515174626f395a777355516f624c2f62304e575166586f3757626f584b305450376a4850784d716d6a70367857505155312f6459533768736456395646423579696861434d617746646c446a2b514d6179684f55763534763947743969774a52316538662b45332f4f36545562515478593962434968734232696a6b7a594c662f464b7035446d634a623949464f4144376a534c7674666c6c30556430495955584753594e6d4a363147466e4d73584f363746527459695858562b4a394f6a38736446512b484b506435747469554a7435565048526d57625842524b597950383245696c754e743354685a65346f6c764f336549754e73634c436c7a4f436c2f574c69706c765057576478756d6f37566c464444326334433766614f6c7254643159464c57694676634d504c5279787853396e322b6e6978506f4e4a486251516b3271756f672b494c736652774e4854306345332b4c49336f50723357457472304e52746436615533545841466b667a394659353668393431493730456246476273654a7062695a3151465a7a5072364f302b6b3155656943554e6f6e4435792f7178784d4165724c36794e3670336f67695156324372474957644c54326a486e4259524171684f657835754578456170387567784c732f704d62724a2b2b62335576612f5a696d6a534749616e35675a3178714e766d6b6671485673354a39674b54524c517a307259674d30326431677630694d734f31452f664e49306837424f55326a774741717636674d436f6c553979334e2b43337542444f3572396769777366746f6744474768316b6251746d6659566b57486169446b384138386654476974342f70554f67424a574764566461564d59742f57794f2f46735942435830454856424644314e47543077564f77685246653350476b504d666149776a4f32363649456d595930334c35597969717a35734f6b69785074774e58645a5574466149577731566739586772696164696e6b6753664e514c77654c5174466d79516454454f3468717476596f4a7978375473736462653859634435306b67694e416d337651324f4761564a3071316d776d50316b6e563434656f51463463616d734c366f456c4575706268794e74615638537a6e485a5371475a7a386c465042494a793547743041753472376733774e4d49685442622f4268413958517763494778554842794150586b73374f67544a596f6635486441467248436f5835525357325361674156477165506e4d4f4c65354d7630305a32656e5a426b676865736477794c583743424c57766f64586b6448424c5a4b6a69546133446738595a68485372717668677a5954394a357172336a62554e6a675a7732315a6734494664656164714c31447147684a386f6d6e45416a6c7a4c5052676279767a484f497154466d506c6e754f595470365a4c50496370475a5a3944774b654964454a48466159597936477061735362314f3546683366596e76685157563053677479762b5049674a694679784663395176426c7734675130473133365371496b306f566e625052314f6e623552474e3054494f494d34785a4c4830435754786b4576767065374d5736324b4c326d4b376d3252446d32687a324156704b4c42586e6f416a76366b366576675a316b4b4a4d716335616648494f76345a627538785641736f2f5744577a6c584c316c6e66315573315a5449776b6268484757307654436630544743676c624e39796e7444366c46664d725877632b7378303242446d6368425074672f45595874564e48384537456b345432346d366464626a3634735a7374647a3544476a7239686c4e58474e3842522f574a436c6243355547572b694865536732304b4a7961743176495a78453534754976464e3178446d32595a797146796a476148674e78444d4b666b393768713655614455594d6866554d2f6d632f3974783441504867557470774c5448684575524d736b713434346c6274356d356e366f376d56435237667a72474d6134304e356b43624a455271494e446957506f6a50784c394d39445736697669476a714a4c58394b35366d2b676e79534e79596a795673472f5765566244456a62483136507338594b6d6a476d32414c54554f4b6d62394677624938305852724d746850634e3575566b7433554b343071726d6d734d38362f316e4c6a64687373306c364f61324d625a6173643050344d35305771436b4348627869412b6b786378484d2b61314a316e76704f6f73786631706b39634169597845664648536b39517530504f7366357031686253732b443144373438716d7969566a374f714150346a4e7847452b5a55385369367741396c687676496538766d3072636f4e322b69446766324979303342613255646262416133724445383330486149707970394f6d7834414f6f334f3159526f61367a51565878543248633964746870545774646a647356615753745869697a6c5a736537364453493665466632334c3472562f666b624264506d64633263514c332f545538717335664b5a42584a6669376e6c615671764838567a492f304333546962474c71544b494b33317373716c56457a76474e6f394b494a69704e5a724d444e6656555a356a30716d6f625a504f7970733651336e62665534523039595a314a564830627142706a4f5574784650623172305833794d4a6a35752b58777a53346d4844646653516c5472646c463057782f3230744336395364615264386334594c5251467a467655664538324a7a4f3836536f4e486d654861697050552b4753612f6e6159503874674b526c742b65747852622b4b6632576f585a753176376c4c386c71775a4369374d6f4958364b67714b425335327148495957736e626b3545364d637943654e69655734644c6b785371774768354136424d346e6157584e6c6c3666523448356a656e4a792f4150533535636f5261335679396430783945647941492b39447a7372504d3852314f68757978374c4f4d2b6a6e2f4f6e30346d596d4d59767a39587056444a4b6673674f6f6e7079666e70316476627538646339663830696f6a58314d676c57634830377068556e4d6861464177657a3644424938696f416443486e387a633169395a6c6f515a496e3576623639504c6d394f7a322f4f72532f58563266584e2b64616b734652347346597265536474444e6b5965316273675442416d45746765387848626b664934766456717a54334c6f414e6a396e3478536835626c677a34367a434a504439527a4458724d4f527057415a687675542b77487a662b794374376a487a4e7965374c6f496d4f486d43376c69536659334549735669517655473351596752576f4b3665716770776c4b6a614c6c7876584368627552556457493351567877714b344155474e676f6b764e2b37464838547569726c5a5269444f2f6f4f58334c762b7736593657683731336f3954694465724946486874547053487531484667584c4c323765694656766b524d6a596563524f6b437030355463373843346a394a4267757532656d45506b396f5663312f50666a7039643348726e72352b63333770586c39647a4754584633314a31757a6d35757a713876623636714c4d6c6b7241434a6945517475303644674d3156527743357336434e69576a51572b394f6a4e304344475464676d77596841697a6a386d77446c594c2b5a335a362b50723039625446676d545a463143456163467a507a6d626e763836756d7a6967435947446f47574e6d654a364a7279566b596b74674b457a446a4246304a3164766e737a757a373938574c573168396f49575253694d646a61594b47324b6255786e536b2b715367753259595968505a44674a4541343675475461784361684a7942677475767a31394f4c3864524f7968527a4c495251354934522f64656d574b76726a3665335a4c7955627239326232635873375062717573344d784a6a61784c62354a77374a6f7578485a6746694f6841544f4d49356453324d2b6b394f45674c3937767a79316e62666e50366a727261595078734336344a74305661696357345962456439566e5367374a3653414e7a2b61716f7367416b49374e6c50325a372b4e31686a464d416a57314473576a766e4a6343543034734c392b7079566f66304177534551704236435141784e5446466a6f3354774977416443787341684d35674a725541734243474750627472464e4b48535179512b75413154737337413558467854462b7132494448734c705159456c583851624655713743666859757767392f6462392f386e43304176776758586a2b376e396452382f3275667272732f6662723668394b69306c78356c3974526b35454655537a5730617852596235376d5964684d6e4a7a482b622f6d2f47304c2b47424338443745464d7a4d664755614d366a3958525675614d6a70757a484b433452326e5249754e456573726c69687254714c68506262635269783958676e32777733743059697a2b4c53766d6a5a52544855697248332f7a6d436a6156313571644f504553316764793033364b452f3948704d57416c736267756f474e64456d364279347546304d6b546e43544e3675597859733175464946576943306167456d774a30775834445537386943423855496f536b6f67796a6449454c58567762524767646f524863665449466c6544746b74574337754e632f427537624777636533644d563539704c784f3577525632642f4c6a7864585a333977302b5a664f6a67734947382f2f77425a7547353738504456492b2b6674374d59397633542f666e56644c7a625938706c76412f5454727774345a503174646e5a322b6a66333750547436646e353754394c336d2f304d6c39672b656e647859563766586f375339466b736d3455412f6c466f704749327678305646546b38647a63706a685331584e2f6d6c3363336a524b49574f78544c537a4b304742396e316547647a3545585268364b356b2f486d3271564d624f356151387543737a3058506a754357666c45517149346553306d527558694c52625a684e624444796b7436386d6f6537343864554931554676716f6e644174334c4730547246524b59652b2f4668504748334d6b6e4c6357505331514e703550345638534f6241485533712b704e5555766370477a45576136466e6273512b7369686d4c67735867536354765575483341466f4e483168484e7946624f4575676f39757842356b2f57527276444a4264515634326c7173574c4c3463764130506758774235626c31616d372b65583870306153596c6f413263676b784548414d57324c6d4a594e6251524e44444841467157556d4b426a44314b656743644a4a2b6f6b374b5574614c696c596837634257467938754f70564e747342634275517936444d5a4b4d344f343854457a4a74536b50516d4e567567444343394946464337744c644265796e70562f71375033422b76336c31796d3232484f716d325938644e306c4f73426d723477346774486e3232474b38744c56422f567256704d497231795177666c737a535630336850612b46745a663972717234746974433561586b53504e35777556736c59774479426835386175616b314271556f6777736a41325459494968546145446b5a4549516c73347072646e745a786a515a35436571314a57416847396870466f7363514e4a38466d426745384276524a5043424c7646424169314562574a4137464b6461754a43645635636b774c32644345304c524e4378424348597763322b356f6535504239426230546a327962594951635279467a6f4d6d70683770565a564d49646474597471623947356d5a322f647439666e623262754c2b632f2f7a4b553252505470425942314449684a546143304a4c723168716934654c713738496b4947705245316b6d744d634c50435068657661473078356f49355846537761662b62573857447044623266474b6e36532b62576466386e636f544a61647374646d6b484f39727a6b7a514a6371434f334355737850466e326e4f4c6d5a6336712b73544c6e525870436d4c33337977612b44696c612f7779594b7646466f513645654536584c44457a626b624a356f474b485761486b735a75772b50717a486971515053515644384c356c4e69423643556b447142425566676f78566e796f5952574a795145396453436d6f655070346b524e537046556a685045576a4b51416a7159416a7152413571536e726b52754641573176475145485673346f366735794369556b2f6255612b4f63696f4f4c524c76763774546457375851504a364d63766b387873756d4d4d5a5438765365746b724d67657a364e4f695362786e755844714d492b52796444693656413548465370476836524c355a42556f574a30574c7055446b7556617853656344396a523857544c636f714a427869564e795274393864307962794a36755a633268346f74536754636b366b5633323149677068342b6d4a396335395256716f624d564d4c70495374626a7a68756f553161424e7072412f47485a565659306b615670476773486a6c626b774b363334765343486b3034383131764955396947636d4c34547249554e61336e41777465735a384e325233636e332b4c556f4b434b4f4a475873795546324e684134476b694876365974384f316f4f65495756422b347855696f676a4b616b6242564d316756336f2b5455686a616577494f716a764c6f4f6f5169615a57757739687972787a6a34363243685a6377742b517a3439746c4b2f6241565057744b4f6a32517835502b6450576e5265426e3768656471664e796576415430367a667772767a48554d62327a4e79587a4774596e595235647a5248425053302f4950764647614f2b4779676f4939652f4d6c616f696a532f4e6865657173584f3668322b6d6d5839796b2f3579396868397a4b56624471336662434d792f4f5430347530763956345671523649426a435a73305934517a57644d6c4b48334772472b634769784945416d686851597072556f52695a4671474f36546732784a6851684b434673474e5347324948453873424e7258544879317145644e474e734359514e4f47556c2b5331636e366558627031672b3173616c46546366434546416241456f4a73544331694f5859447157576952437754574935694e6f4555477961794849734730494d5452745a4e67515532635332415345416a5350726e33563977496751516830454b4d5451746b3167457874546a444731495849733238516d734b434667415653796c4d6562476861794c4653455347454d63493267526151366869716b3656306d6b305069456b30376572363961782b754e4950446a477a646851434d51594f67734347446f41494f7a6243304d514f784a62745348333457456335346e68754966696144732f4a7577442b3965677433496a4677654a5275736a534843376e4d427665566c7942326d353630674d6c7671702b415a316449624f664536733777417764647941716334466a453271482b497633493866692f636978654439794c4e3650484176324936657634767172375a4e6864712b532b71756b353156616635554f713645747059626458315565774f5a564e3348373353446f706d4f363432576c53546d6b5478497a446e57662f642b48532b2b7836674c6c2b756f4279746e33596f5332546963382b684375503457757433483965792b385930657978785679454d706c374a3041476a375a4533624a38346e44496f39697056527945464244424c3133754b686672434e425864347266665872374e704637625a7462454c69514f6a594a6a5164516b786932525a4d4d335762554949524a6852595567764241524a4d54756334746977437147506246447555596b6f77535663344e7161325a574a67645234384c4547432f767959683056546c7377443759315355652b497232725a7757564c467248515a356c7a716271646b76616a33337776546c34734e385a666a52632f574e386446326479766b2b486578733369547a2f517844657564734c594f366939654f6d5342703232763731613956546266484b536d492b53684a7a665a49776e316f53504f65694b70636d4c47315377694f6b64467947515671446e34482f6251762f766648584e4958653372676e6a734c574e4248786832446a73732f2b366e48425847385876577666456132384f56754e676a783348344c774d613569674d425552704530636730493063544a526a49323255696558624b5236456f326549414f4a396c4939684a6d6b2b6e43624449717a435a71595461337550467831744561585a4a5263545a5269374e396f70414a7446704555653837465033476350655a6f395232774259685a77306f332f445665346d4f4b415671745966476143554b47706349356171423270665361346c4c44597a7963596b4c344b446a55704e6935626a55432b6a4a34314b5475733634314e525579636a55784b4d784d6a56427930576d396d684a64357a5a33506a49424b453266317a6852693430745566726b34564d614a7047467545364b587556633270726c78734e72776871427778756f55686378693843574e4b31746f633254352b4a42772b6b336c33564845652b6947664e5878612b394661355561584f6e5a6f443767616861554f376a75466976643738464f55307156433447372b66655651506b5457794a316a4e314f4472696859316f50343654494c776b626e2b65764d6c434f38557036774652744a6e6772624c3944595376724c714b6b305656316e6a7072527165546d6b492b56594e2f5545693947786f7337476572335a655864624a6b4b496e4667764b6c7642302b38505a324e663263747631703932537a4c6c745a7a55496970466563635331397449344b73664e4f6c746c44412b31516d584c654946304b2b383843346476776c574c44705a42664e784975686951774d6c616a4b70666f2f37684f3277597a344c4874555675304f387030364248634a395831492f54464339505668552b745678616f6a33306a6778346e4d3174572f55526e795970765931576731682f4469585270694f55554d59784f356e6478323676727a32314965716f592b59762f3749797076716846487668716d6837626c67757967466a463859443247545779754c5157766b564e7a6265506f2b2b33696372774b2f6665465058344b3176613837456d2b683349364a78586f704b396d5a4a5a57644455704e61566b75423755784a2f6d374175565274516a792f59694b366942662b68664b67796731725a30483864536e52597357374542716d397675306d58336170522b6432776f34684e6177494b755a67365958565845372b61675754634855715a737541476b525a6630796e70776770752b543476575649484b31712b376c5543716c72395079576e30756c57672b69516e5663775a4c626d796935703730386b455a2b2b572b4136675862386b355a665469352f633745365247765051746978434c5174516b77494859306a6b767359727762636854374458332f7863514654484f5a385a56484f48565846626b55677564782f6333552b3830567853573553786774444e646f4156713244623453704b732b635358415074666f385261694a2f67704f4453684c4b572b673871584e756d6e6659655a4a48325453786a377041542f4c2b764362752f64376556324c66622f6d33697657705373416c44646d4f754b5371373762536c56424f6630646369576b6633344e6c2f77705a736b32726b6e586b3362475430396576727a6d4e634f713371316e32304e3055586153384f66324865334e3764583336383877397635323961562b73686762446352667377757368444e78356f4e7236566f36574666575832506457712f6a6b4c4533767a713475623639507a3237646d396e46374f7a327176346c4f5149324e6845314c596f5241685a466c6b326741366c743257686f3564754e313175747a745a686d734432666851744271445a2f63582b39636947392f6b6c73567758554c2f6d642b3976316d48637147653150355756526c474172575a43644367546b714a2b724b784c4f4d303955375a6966724957374c51377a72516c672b643669305655486a456a5575597243353475483256507353396c4a6c306f75595667785436644c6b6656422f412f6f503565376b596e4f5a305950334d466f4a61314a423153365a6d2b5974414533544764764c79655863782b50723264755a6e503472737143416e466a675549704c614a414c47524352314349616e3371456d3571693365432b6a2b636e72352b6d4a3233594864524e5143454e73414f52545a466b5957785a6a59466e4a73596c4948517073514374485147536b396c4c793975506f6e487a6e464343434c324468645167353950396d4e675731573679394b716c594d6e637756352f43486e5844374541494a34427a333636677262493169645a6c7163376b724c343764657938575849317a764c516265367445334f476d7170644b624232356973365841304851455765375a716e3433475730667469745377635074716a4d2f654239504b494b4e5762792b5636375033373242393674534c74646637394f4e414749614d54336371314e33574b706e306f6e6c6a4a336e30756e6c44585033707a6675724e665a3563644b544e454e7144414d514845474a676d70705a447261477a6c7271785051544a37435072507a3149595052592f2f474266556c5872714777707151444246556a74645a55453176672b79375a33484e57397650733173327631373538392b6248726977415770594e624163435970735949784d41614a6b32426369304d45434132744c727854622b322f4d3373357662307a64764f784b5237476f345946495445555349685731694934695269536d3171594d6f70685244694a55546b5a53534e41756258627670596e313263384d6e4a4b55697a622b6f44553348424e53324c4174687977596d674a526747774a45724b4872362f71704b4a6575765851514242334c775359424e7343574179304b55336b3446454154554543514252316b49774147372b72724a655a6d39762f657a5337506871514373594f6741327a5478445a3048456942436242704132776a427a735767656c504b545534565a7778424e332b777a322f2f4f6d7167777754556f41633077454957424169434579415276462f2b772f3335767a6e793950626439657a66754e414e73484974414367466f5555456f516f425941366733635a64754e6e79592b7274662f68386a48316345716573674669736b79326a6d63346f3231375741556b6e4d785766536e47353243387a4c566b756a4a4e4a374c693038456950352b6270362b34345535373938766e6266444134735237324978696351646c6375505a6f707259666970344a6a47684668396135483934687453576f795a472b38777071576e313368672b3831597246703157466d594b664e614254476c504e557a546d564d446a573572346e4b68512f59485a55743849657068733650516b4c306b56326251786d315235426872536730776b7870544864654535745245704e32672b4a7a6f6d5950444d716f4f5565706956576346547866504e2b6b456850376f4d4e57434d3656784e5a464e5a313174544c724e7134735854664e77554162574b5531747a504a4e4c43376665786f62752f313848693758716b77576f36653070787a4664465a55777464744f33573652386e336f4f796b49612b526a50467449766e73427156576a6c4f6748492f6b5a7a3739684e2f55506f4254346e3048596c724c32654b5a306e777153446732704c364a78756467764d77507a4a726134745042596b65733258377a30647244366a74416f716277652b716b75546a2f386672302b703935493831515634746c456d5361466b5955573943784961595559476f526841473148597770686735434e72497341676b6d796e744b4e614c3475306e49704151675168334c5242524153436c794d4352457562586e497068485876516c5851637261555a312f475465706f4c6b2b625567636f67664b65676e3749623554326739764a3639765467396d376c6e463664642b3563495977417359434a494c637330715930524a51425332795245656276796d6d31576e732f4f306c6c5255704561674830726878613366444f37664f322b6d6433636e50343863322b7655732f63345a464e414b6b44624570734535695736546941516f66593245475753536b463674756f5a62503739657a30646363574c6743324134686a6d68536b73303441734243773051673358434c392b2f5835626366474d6349327367476b47434362576743446c46644343484b6f7163347343786476386f4d4a62746358384f5a4c7242774a4f6b434e31634e6b4c6430667666472b724e626567754e70657678544f616831474144664f3330766478424374395479547947756d6264516b33706c2f47547874344a6b4f5036325a53794c515775657a364639704a7831685638707464626a586a6c534869304d2f6c7141632f7673704975646771532f5230482f7958686941505936745a5776335455666a6a6451743143534537386d393546466364352f4b6967673331382f686f6d722f41334d672f665a58544a783939367a4e7532374e4a432f4e753372754534694c34793950456c755a30383956776e3639313451757347695071446e5173467748666f4e776e54634b646735382b6e2f33303559686a632f72364639554c7a4136557869574161505a5a49426f3674396647414e314e633333723047456c673537544d74715974503947416d4658424e5635735045543279586c6670745535673939464b31633846586d5a486b52776278624f5878636b6b346f637643564b6a6675715349494b6d7a4d664e626856617835783148763353666559374c55392b45546e3642585764676a523443424939727379694f41346f6472704d70775476574f4a79756b7a796f34324256762f61527158755a4c7467715a2b484c6f4e7174466361676e6b59726f6c445a62642f71722f34636e632f32306950784346694172664577544c574e7732416c4852514a756545396562525643494f79757879554b625a3661444d387051325151646c616e645176495170703770395965356f463956414e744a4a6361464e3561616179505134716c366f422b53716d6e54324f4b764771357264565a4f5171527857453438576c395548564e5a704364796d4b754b307243366e5a51467454737653376253717652373572616e3630366b43787a675856514d796b5763716357687853467867682b4f4853764b3633552f78786b7444754f716c5a5846584a5734696c315343312b474a654c416b485244526c4458524c6764453947564e564b63444b73363563714e79647964336c355a57443152466f753643326c4361567972757658323052744a6f6c39554e37544238566f322b6271655661626d2b524b6d4764514a7656494d2f316831314170503052356b4244687a4e4c4f4b50634764435a4866364979767a5236617750384a542b4b4e50323132796a467a63766f4a636830504b73497a33534255775931775366356474307533574f6766615042674833474735734a78414c666354434f4b613048506c43485335726a593053642b4675322f73786a4b2b693354354c6b773766526557394631457a48643972664c71506e696864316363414c4448712f35324679566f766b5478324e6a583573623078636e4a43776c54422b624a7257663670553733484d68634b324572774a63784c78583457416438434c756e57455a416a676f4347516c3149356a7135684168444b4e7532424443384952583445783076626f51686e47586c6d633955306b5342665048704979475a6672426f6d676475655831596c554354764d757135644776674e6d424c455272684d6a755139696f2b6a414f6971796f53512f525234364b57705748696f506e56545a6c3676314a77363146526271314e666a5071724839324352317a534c4c6a41577838463878647a5958323979786f37574778622b6d323032624257454a77575a4a36763838346555334c37665430353362496d38567834697667355858397959725a5a48373750706c4a4474676d3069356e734a57786a4a5a364e73684b754c4657476e4b6c5a4548486d786f7070534f4163745666615a2b59384a6b78646d78466959524636595a4372626c434a464e536c5361367755726563697876665a436e332b654666755a6f5350713958582f776b4141502f2f304551477335726f4151413d6945787465726e616c7388a2664f6666736574841bffffffffffffc6611bffffffffffffffff1bffffffffffffffff1b07fffffffffc2c706853656c6563746f72841b30b9224cd18a4e7c1bc71f0c828480ecb01b8de0747c8cfdb4821b04c9700ad999c431a2664f6666736574841bffffffffffffa8a11bffffffffffffffff1bffffffffffffffff1b07fffffffffa32b06853656c6563746f72841ba6951e73bb6dfe241b05e5ff539dbeebe71bdd254888370955b41b043ef1349908885ea2664f6666736574841bffffffffffffbb811bffffffffffffffff1bffffffffffffffff1b07fffffffffb73906853656c6563746f72841bb22ea4840c873b8b1baa7fdf9303e1d7731b704e592f223c3de61b00bda65049efea59a2664f6666736574841bffffffffffffcde11bffffffffffffffff1bffffffffffffffff1b07fffffffffcabf06853656c6563746f72841b279f5f4dc8b4c7b31b6ac884a41a1b5bd71bc1307f4321f00b881b04bc8012967069c6a2664f6666736574841bffffffffffffc1811bffffffffffffffff1bffffffffffffffff1b07fffffffffbd9906853656c6563746f72841bc735e82ed1e23e011b9ee8c8991bbc1d151bacb56cf03bbaac5a1b00186ed02a71bd65a2664f6666736574841bffffffffffffb4211bffffffffffffffff1bffffffffffffffff1b07fffffffffaf6306853656c6563746f72841b04b9244ab52a4b811bffdbe496a2c48be11b8a5d0118067bf13c1b07102404b615fdd5a2664f6666736574841bffffffffffffc9c11bffffffffffffffff1bffffffffffffffff1b07fffffffffc65d06853656c6563746f72841bf6c53d175ebbcd9f1bdd07ee6aca2299561bd457faeea1e288671b0754ebc0b261e0b1a2664f6666736574841bffffffffffffaf611bffffffffffffffff1bffffffffffffffff1b07fffffffffaa5706853656c6563746f72841b4298606763e030411ba7525ed75dc70c951b01c0a0b4d48ac5061b0624e8d006b62cda6a4c3148616e646c657273806c436f6e7374727563746f727381a2664f6666736574841bffffffffffffd2411bffffffffffffffff1bffffffffffffffff1b07fffffffffcf6506853656c6563746f72841bd1c59afcd3f9742d1b083befda3709a3ed1b3142bfd1003b62e21b041f80cbf54be289") - assert.NoError(t, err) - - return bc.Store( - &core.Block{ - Header: &core.Header{ - Hash: &felt.Zero, - ParentHash: &felt.Zero, - GlobalStateRoot: expectedRoot, - }, - }, - nil, - &core.StateUpdate{ - NewRoot: expectedRoot, - OldRoot: &felt.Zero, - StateDiff: &core.StateDiff{ - StorageDiffs: nil, - Nonces: map[felt.Felt]*felt.Felt{ - *key: nonce, - }, - DeployedContracts: map[felt.Felt]*felt.Felt{ - *key: classHash, - }, - DeclaredV0Classes: nil, - DeclaredV1Classes: nil, - ReplacedClasses: nil, - }, - }, - map[felt.Felt]core.Class{ - *classHash: cls, - }) - }, - }, - { - name: "basic with storage", - scenario: func(t *testing.T, bc *blockchain.Blockchain) error { - expectedStateRoot := hexToFelt("05e69846cb070d495023a6700872cce39a9be8457b2243fcf8d87d8edf7c8784") - - key := new(felt.Felt).SetUint64(uint64(12)) - nonce := new(felt.Felt).SetUint64(uint64(1)) - classHash, cls, err := V0ClassFromString("000118cec7463a59c23203e8e4dd7769a914da30c68d7b5ff404531b1e85de9a", "a262417419d22165436c617373da00010005a5634162695908355b7b226d656d62657273223a205b7b226e616d65223a2022746f222c20226f6666736574223a20302c202274797065223a202266656c74227d2c207b226e616d65223a202273656c6563746f72222c20226f6666736574223a20312c202274797065223a202266656c74227d2c207b226e616d65223a2022646174615f6f6666736574222c20226f6666736574223a20322c202274797065223a202266656c74227d2c207b226e616d65223a2022646174615f6c656e222c20226f6666736574223a20332c202274797065223a202266656c74227d5d2c20226e616d65223a20224163636f756e7443616c6c4172726179222c202273697a65223a20342c202274797065223a2022737472756374227d2c207b22696e70757473223a205b7b226e616d65223a20227075626c69634b6579222c202274797065223a202266656c74227d5d2c20226e616d65223a2022636f6e7374727563746f72222c20226f757470757473223a205b5d2c202274797065223a2022636f6e7374727563746f72227d2c207b22696e70757473223a205b5d2c20226e616d65223a20226765745075626c69634b6579222c20226f757470757473223a205b7b226e616d65223a20227075626c69634b6579222c202274797065223a202266656c74227d5d2c202273746174654d75746162696c697479223a202276696577222c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a2022696e746572666163654964222c202274797065223a202266656c74227d5d2c20226e616d65223a2022737570706f727473496e74657266616365222c20226f757470757473223a205b7b226e616d65223a202273756363657373222c202274797065223a202266656c74227d5d2c202273746174654d75746162696c697479223a202276696577222c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a20226e65775075626c69634b6579222c202274797065223a202266656c74227d5d2c20226e616d65223a20227365745075626c69634b6579222c20226f757470757473223a205b5d2c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a202268617368222c202274797065223a202266656c74227d2c207b226e616d65223a20227369676e61747572655f6c656e222c202274797065223a202266656c74227d2c207b226e616d65223a20227369676e6174757265222c202274797065223a202266656c742a227d5d2c20226e616d65223a2022697356616c69645369676e6174757265222c20226f757470757473223a205b7b226e616d65223a2022697356616c6964222c202274797065223a202266656c74227d5d2c202273746174654d75746162696c697479223a202276696577222c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a202263616c6c5f61727261795f6c656e222c202274797065223a202266656c74227d2c207b226e616d65223a202263616c6c5f6172726179222c202274797065223a20224163636f756e7443616c6c41727261792a227d2c207b226e616d65223a202263616c6c646174615f6c656e222c202274797065223a202266656c74227d2c207b226e616d65223a202263616c6c64617461222c202274797065223a202266656c742a227d5d2c20226e616d65223a20225f5f76616c69646174655f5f222c20226f757470757473223a205b5d2c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a2022636c6173735f68617368222c202274797065223a202266656c74227d5d2c20226e616d65223a20225f5f76616c69646174655f6465636c6172655f5f222c20226f757470757473223a205b5d2c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a2022636c6173735f68617368222c202274797065223a202266656c74227d2c207b226e616d65223a202273616c74222c202274797065223a202266656c74227d2c207b226e616d65223a20227075626c69634b6579222c202274797065223a202266656c74227d5d2c20226e616d65223a20225f5f76616c69646174655f6465706c6f795f5f222c20226f757470757473223a205b5d2c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b7b226e616d65223a202263616c6c5f61727261795f6c656e222c202274797065223a202266656c74227d2c207b226e616d65223a202263616c6c5f6172726179222c202274797065223a20224163636f756e7443616c6c41727261792a227d2c207b226e616d65223a202263616c6c646174615f6c656e222c202274797065223a202266656c74227d2c207b226e616d65223a202263616c6c64617461222c202274797065223a202266656c742a227d5d2c20226e616d65223a20225f5f657865637574655f5f222c20226f757470757473223a205b7b226e616d65223a2022726573706f6e73655f6c656e222c202274797065223a202266656c74227d2c207b226e616d65223a2022726573706f6e7365222c202274797065223a202266656c742a227d5d2c202274797065223a202266756e6374696f6e227d2c207b22696e70757473223a205b5d2c20226e616d65223a20224465636c617265642066726f6d20737461726b6e65742d7273207465737420636173652e2054696d657374616d7020286d73293a2031373131353832303039313838222c20226f757470757473223a205b5d2c202274797065223a202266756e6374696f6e227d5d6750726f6772616d793c284834734941414141414141412f2b783943322f634e72726f583948317863576d5864654870455253436b347534447254316c6a487a725764377535744130476a346468437870705a5363356a692f7a334137316d394b416b6b714c473432344b6e4c4f78527678652f4637382b496e383432675442512f73364b5678424437626f504966684544755033683062427774764d5137656d6e386467512b5734446141454336584337542f30742f425a39682f6a3849325050794237706373764a706463516966326a326a624273344e486c4d76736c706237323147383942656c666a6164672b33537849394f796b5657517372414c31726245392f376f41564c686f53437a4c6c69776c507476586c4b4b537134716c48624b5a55744768566554514f4954337a4c4a6b6a4271555551674d616d316c5151714a4f46554a4147415755424b6e337131703668344f7138394e59756e66753270565478646249565955756a734b4b5262386c4e536350334834696d70503555534143575957715559434b624967735169715341774e616e5a456b52644a55686c316b76734e644b61503671515670755a677945506d39516979347773536a424742424e49326f726a637858483530363776344e7531736845395239486b456b7049673631434734524f7563534f6d39704c53716574736d66743869586e3235735564747943434f454c5063317556734a7441774c4e703961567074773546582b6d774e632f342b55513031516576455747735246626f6f6774326f42783759354161674c75636562686434704768634e743377677531546e7173715a7062585974566c3253714972697369642b793276754f53565663574553792b4f7555705546585366756e43463575394c6149754730477754466554355656347a556161526b56546678656d3772506d754a726c597749534668645a446d6c4f5a3630574e6c42625a75485244744b6f586e4a514a715a504a56536855793143574e546757514c546974503247736b6b6c57313770777970614330466d3163582f635856474d6a76792f66364a36736c6551434e373457596b534e6b2b65484e4a446c6953383057665966636d377233384c4553346e4941666876723447517154776a5052495336544f703450434c4c6d6a756e34624f347332494935434547346f4973355a67785361386d51505763554951765067516339354d30584e7651646579693366306f313252705278357a443076464f5334624e2b68415754374845303459387836383370314470525132684a794c3776536d476264664949467a6969495355572b77646f4e4e6369766a4636636b672f70374e6a35544f465542553464767049304e4c4357632f386c7a676762794b6b354b72496d51416d41523750636d5a7264457335454f37526c367834316a49732f73533061666c6452716b437335754370326d423278776335475568334b4a73376c507035656e353963514f6d4a616b7a6e47646771354c667077717a4b4c61514d337252466e63556e6d503230786b6a303178317369622f466b3169534c4a3539676332694375376d713744564d4f3366554536476d487153334d6f5a6d317851757073396c345a5934586869594443396a7978723779344a52574e4e67766e4f55435179635653396e66726a736d586736356a4758656453334575706d6e6c4f677a5a3869455459396756564c6178456846776673396e5a666a6f335044424c4b5633746355333044733667726b514578366b6b594b6a484535386151396c5045665772576e3036676841756e706f544e6d6566625a64315a49586c62342b7644424f7a3571452b702b4758634f6b4c31736a6c6e623833612f756a744a53497871392b756b563251775959737257354e586f4d64586b79573276547132476e6a626f57425853725765726535637757327874562b7437583378642f53324d4b74512b426d4964733539365a54702f61475139647967626459744b6254746637692f2f4d7049316c3250794e39573442355738696973667375746448627379383776634e596d6a324246665a6e4a6a4a502b64764d32564f72396251374b4266675254335034656d61796154546d3132716e7532623264774e36764b587873547849335165426370493042656875386845383472574c3352702f52524f716e656e59734b61744d4a3676713270456b62447377734661336b4f2f687231742f773845787636553463563172752b6e4e374f76356e644244556a357a39586e2f2f38615a4a4d655a39666258384f4f6777474f73756553656a6f7359586d38712b436959357061613030647461614858734d75397459526332395a516e62336e2b664a323368665a78652f7166592f6474336877417a6177686265304939542f6d625a713143327154686b4578654a3053397a524a616643496f50714b597435366139594a5238645371397a354f5552786443737a30694b634b3771707072684c7561734475755333734c56386d3069472f706168464a327a5261665852755130657a3847746f6e37783973394979616e4a78594a6151366277756549706d347a4b38784d352f6e375178436d624d375758585052585437546b6659666e4a52337261627a6b50745a502f4b58466b31554a37442b3769644c4a546453334255783033444c7538457955734d4d7855664e77544c527273625062453533576e4d6d66335a786248792f6f364a4e357a675556746d75646c75346b72426455366c2b786c34304275362f767164317a496b425044515a6b582b4756516b4d69512f6f6b4f702f636f5675566c674e65497731795371727272546932384f59645a3034734257667a62616b323650636e574b7278336479576d6370544c517334784d554357304f6d2b4c68303935574c796647522b64505352395937315a726d6e6a38744c6270654739764456374b307a307137374c7972465a6258315a5939355a667a3149707837342b4e6f2f6c6a734571434d4d354f6c4e6d7742597469467162765231353478317a2f6e766b66306a2b5a7634697a4b4441506b6b39426e412b2b4438496b48666e4845556a482f33486b7278665a515463503747456466666e4e3237773358686b787533746759524b6665497646692b3953474a3776737a674f3569766d7876353677334c7363654a46487a353545547678765342616e2f6a7268346431654f4b745675757373747233652f4657537452797466376b4a70486e66776a434f3763344c65655049322b7a665a6a3966526574487a64484c773177624279746c3875594a656b665831504f325a4a464c50535a477977793772352b2f5a6f434a6e5565507a36344c4578596c4c5077346f2b2f684839356151534c2b47544677712f796244367742332f7a705966502f4958795051564f3454436e51726854426a4d4975566967565a644c61507a77796f432f68366b6f2f485759424f456a632f3331356b735133686d764447674553794d302f713842444c614b6d51454f58564a34684b53612f4b657753374852746a703944704a436d77356566306966705342633579332b4576766561755865652b466978614b54374939554e70486e4a79394b2f2f43712f4d6578555137594a4e4772564938716677744a4a767458794a4b5339324a3858426450313174312b6c524568617169676b4d4b4a455a4856516770696b4c5770746b76367a7557754f6b4446726e65596847784f4435416762654a564a473671556671625749616f6a634c3056744151505446374232363842746b716f6a6630696a2b426a6d4e4362444b436244374a79424f3170463378397949655973444648795650425742597a304372354c5245445175424932706d4b412f52554843446c6a53475830716f695a36525a3352305a41314b644e4d4159656566486144634c6b2b51456c5871464f524d39586e51776f71476c4b6d705a516247683038624e5a525967514a69354c316568582f487634654c7150316739475654586e4a76667559424b76594b495a366363796978413343684e3278365065772f76654c6443363837376950352b6c6a343157326550434d2f324f387654352f4d2f73396e426550357274482b5744444d2f37376c54452f4e705a2f53596639345830316774674931346d78596e46734a506465614b776a672f33723056735a79647049416630782f3372796c3553702f3233384649514c7777734e4c2f4972417a494d786e2b6c5164544c666c386e39797a697649424f666739584c4c784c376d505843786475454334436e3858474b2b4f3346393678416234374e6c374d6a52384d3739694136622f7a635438593050676870526c39393534372f69526552386d4c7259414d7a69752f6766652f67666370377755742f325759476247386432487258665237794437377138634657786976754750512b392f672b31524978666f316c58356c485a79716b5046584137342f4e6e7066414f6d71393858766f57455978694c342b4c4265764f6a6b356a6962346f7847392b72583262567275722b632f2f7a4c6439384e4547454f4559484569494263496c424268507771784576752b395967586e4a2f556c6a4169726c4c746c4a4b722b30524b396b324253646541564a6979507a6f7065484944576c4d55657266796955307054336c6b327a42764658632f2f5671784d4a352f354e6a3979305448536a444e6e7847624474396245505169504346783973357031634765673563777435794759536f7a755a6577326b75306865316b50716438642f356b3231452f6537592b503333334563577362513267424e58352b564c57786866542f34793657516c7970506c61484b547963354e516968466465596e59566d71674b69682b466c523253314b30436665597548477756336f4a593852792b59702f333254524366464f7441397a6964772b356f623562467239794257694672623054317a736e336e35434f4c677555584e7964754e31536c6f695a516646616c5a796538464246575a367771374253557151565579687530746f7068393051436b4561436c69572f61707079486836676b6932362f734e6d774237545630364365497a7a68474d7138473053636f4d6b556d546e426c6b75676b794b4e657a62754f3644463453756d3735522f2f656e794e7473574253332f7a3778313247635249392b736f3655536e3031797a48354e66715353776339465a64334c486e374f4638462f742f59463565464b514675784a4c484b46526947676e7656566e4e45767a2b654934664e326b416a382f446845564c7a326361474366696a464d64573546716a46636d57366c2b57744e70334d396d622b34384b5a74422f4b7533436859335731632b656e6f746362334731486b71766c3333593871336c7a445856654b795671366c7448657275626b372b4352734c70692f38694a56646d756c50474c32737475734c7a3852753576562b6f7369743756636d2f54474932492f6d5974795866615a2b592b70436f38335779786f74756b502f767068453678593548356b55527973772b773270424f4954724a384c614d2b347931395875556d574c4177435a5942692f4c7371507a74354e5433313439686b6a31637344674a51693870344b34334c50773332327a594b6c3155354f2b6472494a353545566674754f4f6a61506b5334375057775665665053314973667972544e767454714e49752b4c4b706f6467483538502b5a4e4e542f6d4b794565746f34554c2f756a5845484654546a3953482f7834767652474b74412b74467441385a6f6e4331492f59677253702f683350697069524c37324468614d48386465636b36796d324b66553559464871727a42704b694d7648304d2f6f36774a36636872643564713566467974334e4237714b6c782b39586a7a4f546e7055356e75784e6571695675336c6e7a523857715574764a754e2b536b79342b4d7050614471735067613068557072362f525a3436674461464b462b69725a655935656363776438662f5131485249482f326246316e483561373473364a62322b634e6d466668424969483132704347394f766251344f697a3856544e7371315237576c4c3231484f597271387231582f476f326b794d706d766e61614e715470754b4163695474776e63466b6457685468586c77424c4b635a314873397973716c426652437a65724d4f5970527239306b69524842766c732f7a7637372b724f704c306639304657775a6830472f394e2b662f663362316b33747864585a36635a4e682f756974486c6d6851535734624c336242464a4a4b4c654f435a74777047506151523332544d313376376b6d656464556b614777622b6f613838303543666f4e2f7453707a31793334354478436857414f747a43626747326377384f304f55657474416c3345526a544e4e64724c773464752b392b46374d5656516d43347050316f3447655850724750764e374a37453748617a6f646e38646f44316d474652474e69744874445931554d4c754a5152566f654d744d4630347278564d7153783235633332784b6e554f79744b4959706f78674668796f577a6876367a634366794d434c79644275337756635a664f75376a39747a647245547475736d7a74562f5a5a64655876496f6c75764e70533077394930424e4d715a6b454c36787a7954437972562b6b314f4b2b716648516f6578576573704a5874783872576d365474705a2f444e676e4166577551687a53372f6137645632705342634d534c6347536c426a7538643855396d32674c7031647575473869714a7541725834492f523457314465572f46574b676c76623953334e7a61334e6d4d5a584f714d6f4932303451365a4466383978744b4b35666762666471577a57546e6b7776726f6c6851504f2b6c314b39466f75434e74302f37706e59395a38697957764e524c63444b56365664523874444d6f754a4f6147515174626f395a777355516f624c2f62304e575166586f3757626f584b305450376a4850784d716d6a70367857505155312f6459533768736456395646423579696861434d617746646c446a2b514d6179684f55763534763947743969774a52316538662b45332f4f36545562515478593962434968734232696a6b7a594c662f464b7035446d634a623949464f4144376a534c7674666c6c30556430495955584753594e6d4a363147466e4d73584f363746527459695858562b4a394f6a38736446512b484b506435747469554a7435565048526d57625842524b597950383245696c754e743354685a65346f6c764f336549754e73634c436c7a4f436c2f574c69706c765057576478756d6f37566c464444326334433766614f6c7254643159464c57694676634d504c5279787853396e322b6e6978506f4e4a486251516b3271756f672b494c736652774e4854306345332b4c49336f50723357457472304e52746436615533545841466b667a394659353668393431493730456246476273654a7062695a3151465a7a5072364f302b6b3155656943554e6f6e4435792f7178784d4165724c36794e3670336f67695156324372474957644c54326a486e4259524171684f657835754578456170387567784c732f704d62724a2b2b62335576612f5a696d6a534749616e35675a3178714e766d6b6671485673354a39674b54524c517a307259674d30326431677630694d734f31452f664e49306837424f55326a774741717636674d436f6c553979334e2b43337542444f3572396769777366746f6744474768316b6251746d6659566b57486169446b384138386654476974342f70554f67424a574764566461564d59742f57794f2f46735942435830454856424644314e47543077564f77685246653350476b504d666149776a4f32363649456d595930334c35597969717a35734f6b69785074774e58645a5574466149577731566739586772696164696e6b6753664e514c77654c5174466d79516454454f3468717476596f4a7978375473736462653859634435306b67694e416d337651324f4761564a3071316d776d50316b6e563434656f51463463616d734c366f456c4575706268794e74615638537a6e485a5371475a7a386c465042494a793547743041753472376733774e4d49685442622f4268413958517763494778554842794150586b73374f67544a596f6635486441467248436f5835525357325361674156477165506e4d4f4c65354d7630305a32656e5a426b676865736477794c583743424c57766f64586b6448424c5a4b6a69546133446738595a68485372717668677a5954394a357172336a62554e6a675a7732315a6734494664656164714c31447147684a386f6d6e45416a6c7a4c5052676279767a484f497154466d506c6e754f595470365a4c50496370475a5a3944774b654964454a48466159597936477061735362314f3546683366596e76685157563053677479762b5049674a694679784663395176426c7734675130473133365371496b306f566e625052314f6e623552474e3054494f494d34785a4c4830435754786b4576767065374d5736324b4c326d4b376d3252446d32687a324156704b4c42586e6f416a76366b366576675a316b4b4a4d716335616648494f76345a627538785641736f2f5744577a6c584c316c6e66315573315a5449776b6268484757307654436630544743676c624e39796e7444366c46664d725877632b7378303242446d6368425074672f45595874564e48384537456b345432346d366464626a3634735a7374647a3544476a7239686c4e58474e3842522f574a436c6243355547572b694865536732304b4a7961743176495a78453534754976464e3178446d32595a797146796a476148674e78444d4b666b393768713655614455594d6866554d2f6d632f3974783441504867557470774c5448684575524d736b713434346c6274356d356e366f376d56435237667a72474d6134304e356b43624a455271494e446957506f6a50784c394d39445736697669476a714a4c58394b35366d2b676e79534e79596a795673472f5765566244456a62483136507338594b6d6a476d32414c54554f4b6d62394677624938305852724d746850634e3575566b7433554b343071726d6d734d38362f316e4c6a64687373306c364f61324d625a6173643050344d35305771436b4348627869412b6b786378484d2b61314a316e76704f6f73786631706b39634169597845664648536b39517530504f7366357031686253732b443144373438716d7969566a374f714150346a4e7847452b5a55385369367741396c687676496538766d3072636f4e322b69446766324979303342613255646262416133724445383330486149707970394f6d7834414f6f334f3159526f61367a51565878543248633964746870545774646a647356615753745869697a6c5a736537364453493665466632334c3472562f666b624264506d64633263514c332f545538717335664b5a42584a6669376e6c615671764838567a492f304333546962474c71544b494b33317373716c56457a76474e6f394b494a69704e5a724d444e6656555a356a30716d6f625a504f7970733651336e62665534523039595a314a564830627142706a4f5574784650623172305833794d4a6a35752b58777a53346d4844646653516c5472646c463057782f3230744336395364615264386334594c5251467a467655664538324a7a4f3836536f4e486d654861697050552b4753612f6e6159503874674b526c742b65747852622b4b6632576f585a753176376c4c386c71775a4369374d6f4958364b67714b425335327148495957736e626b3545364d637943654e69655734644c6b785371774768354136424d346e6157584e6c6c3666523448356a656e4a792f4150533535636f5261335679396430783945647941492b39447a7372504d3852314f68757978374c4f4d2b6a6e2f4f6e30346d596d4d59767a39587056444a4b6673674f6f6e7079666e70316476627538646339663830696f6a58314d676c57634830377068556e4d6861464177657a3644424938696f416443486e387a633169395a6c6f515a496e3576623639504c6d394f7a322f4f72532f58563266584e2b64616b734652347346597265536474444e6b5965316273675442416d45746765387848626b664934766456717a54334c6f414e6a396e3478536835626c677a34367a434a504439527a4458724d4f527057415a687675542b77487a662b794374376a487a4e7965374c6f496d4f486d43376c69536659334549735669517655473351596752576f4b3665716770776c4b6a614c6c7876584368627552556457493351567877714b344155474e676f6b764e2b37464838547569726c5a5269444f2f6f4f58334c762b7736593657683731336f3954694465724946486874547053487531484667584c4c323765694656766b524d6a596563524f6b437030355463373843346a394a4267757532656d45506b396f5663312f50666a7039643348726e72352b63333770586c39647a4754584633314a31757a6d35757a713876623636714c4d6c6b7241434a6945517475303644674d3156527743357336434e69576a51572b394f6a4e304344475464676d77596841697a6a386d77446c594c2b5a335a362b50723039625446676d545a463143456163467a507a6d626e763836756d7a6967435947446f47574e6d654a364a7279566b596b74674b457a446a4246304a3164766e737a757a373938574c573168396f49575253694d646a61594b47324b6255786e536b2b715367753259595968505a44674a4541343675475461784361684a7942677475767a31394f4c3864524f7968527a4c495251354934522f64656d574b76726a3665335a4c7955627239326232635873375062717573344d784a6a61784c62354a77374a6f7578485a6746694f6841544f4d49356453324d2b6b394f45674c3937767a79316e62666e50366a727261595078734336344a74305661696357345962456439566e5367374a3653414e7a2b61716f7367416b49374e6c50325a372b4e31686a464d416a57314473576a766e4a6343543034734c392b7079566f66304177534551704236435141784e5446466a6f3354774977416443787341684d35674a725541734243474750627472464e4b48535179512b75413154737337413558467854462b7132494448734c705159456c583851624655713743666859757767392f6462392f386e43304176776758586a2b376e396452382f3275667272732f6662723668394b69306c78356c3974526b35454655537a5730617852596235376d5964684d6e4a7a482b622f6d2f47304c2b47424338443745464d7a4d664755614d366a3958525675614d6a70757a484b433452326e5249754e456573726c69687254714c68506262635269783958676e32777733743059697a2b4c53766d6a5a52544855697248332f7a6d436a6156313571644f504553316764793033364b452f3948704d57416c736267756f474e64456d364279347546304d6b546e43544e3675597859733175464946576943306167456d774a30775834445537386943423855496f536b6f67796a6449454c58567762524767646f524863665449466c6544746b74574337754e632f427537624777636533644d563539704c784f3577525632642f4c6a7864585a333977302b5a664f6a67734947382f2f77425a7547353738504456492b2b6674374d59397633542f666e56644c7a625938706c76412f5454727774345a503174646e5a322b6a66333750547436646e353754394c336d2f304d6c39672b656e647859563766586f375339466b736d3455412f6c466f704749327678305646546b38647a63706a685331584e2f6d6c3363336a524b49574f78544c537a4b304742396e316547647a3545585268364b356b2f486d3271564d624f356151387543737a3058506a754357666c45517149346553306d527558694c52625a684e624444796b7436386d6f6537343864554931554676716f6e644174334c4730547246524b59652b2f4668504748334d6b6e4c6357505331514e703550345638534f6241485533712b704e5555766370477a45576136466e6273512b7369686d4c67735867536354765575483341466f4e483168484e7946624f4575676f39757842356b2f57527276444a4264515634326c7173574c4c3463764130506758774235626c31616d372b65583870306153596c6f413263676b784548414d57324c6d4a594e6251524e44444841467157556d4b426a44314b656743644a4a2b6f6b374b5574614c696c596837634257467938754f70564e747342634275517936444d5a4b4d344f343854457a4a74536b50516d4e567567444343394946464337744c644265796e70562f71375033422b76336c31796d3232484f716d325938644e306c4f73426d723477346774486e3232474b38744c56422f567256704d497231795177666c737a535630336850612b46745a663972717234746974433561586b53504e35777556736c59774479426835386175616b314271556f6777736a41325459494968546145446b5a4549516c73347072646e745a786a515a35436571314a57416847396870466f7363514e4a38466d426745384276524a5043424c7646424169314562574a4137464b6461754a43645635636b774c32644345304c524e4378424348597763322b356f6535504239426230546a327962594951635279467a6f4d6d70683770565a564d49646474597471623947356d5a322f647439666e623262754c2b632f2f7a4b553252505470425942314449684a546143304a4c723168716934654c713738496b4947705245316b6d744d634c50435068657661473078356f49355846537761662b62573857447044623266474b6e36532b62576466386e636f544a61647374646d6b484f39727a6b7a514a6371434f334355737850466e326e4f4c6d5a6336712b73544c6e525870436d4c33337977612b44696c612f7779594b7646466f513645654536584c44457a626b624a356f474b485761486b735a75772b50717a486971515053515644384c356c4e69423643556b447142425566676f78566e796f5952574a795145396453436d6f655070346b524e537046556a685045576a4b51416a7159416a7152413571536e726b52754641573176475145485673346f366735794369556b2f6255612b4f63696f4f4c524c76763774546457375851504a364d63766b387873756d4d4d5a5438765365746b724d67657a364e4f695362786e755844714d492b52796444693656413548465370476836524c355a42556f574a30574c7055446b7556617853656344396a523857544c636f714a427869564e795274393864307962794a36755a633268346f74536754636b366b5633323149677068342b6d4a396335395256716f624d564d4c70495374626a7a68756f553161424e7072412f47485a565659306b615670476773486a6c626b774b363334765343486b3034383131764955396947636d4c34547249554e61336e41777465735a384e325233636e332b4c556f4b434b4f4a475873795546324e684134476b694876365974384f316f4f65495756422b347855696f676a4b616b6242564d316756336f2b5455686a616577494f716a764c6f4f6f5169615a57757739687972787a6a34363243685a6377742b517a3439746c4b2f6241565057744b4f6a32517835502b6450576e5265426e3768656471664e796576415430367a667772767a48554d62327a4e79587a4774596e595235647a5248425053302f4950764647614f2b4779676f4939652f4d6c616f696a532f4e6865657173584f3668322b6d6d5839796b2f3579396868397a4b56624471336662434d792f4f5430347530763956345671523649426a435a73305934517a57644d6c4b48334772472b634769784945416d686851597072556f52695a4671474f36546732784a6851684b434673474e5347324948453873424e7258544879317145644e474e734359514e4f47556c2b5331636e366558627031672b3173616c46546366434546416241456f4a73544331694f5859447157576952437754574935694e6f4555477961794849734730494d5452745a4e67515532635332415345416a5350726e33563977496751516830454b4d5451746b3167457874546a444731495849733238516d734b434667415653796c4d6562476861794c4653455347454d63493267526151366869716b3656306d6b305069456b30376572363961782b754e4950446a477a646851434d51594f67734347446f41494f7a6243304d514f784a62745348333457456335346e68754966696144732f4a7577442b3965677433496a4677654a5275736a534843376e4d427665566c7942326d353630674d6c7671702b415a316449624f664536733777417764647941716334466a453271482b497633493866692f636978654439794c4e3650484176324936657634767172375a4e6864712b532b71756b353156616635554f713645747059626458315565774f5a564e3348373353446f706d4f363432576c53546d6b5478497a446e57662f642b48532b2b7836674c6c2b756f4279746e33596f5332546963382b684375503457757433483965792b385930657978785679454d706c374a3041476a375a4533624a38346e44496f39697056527945464244424c3133754b686672434e425864347266665872374e704637625a7462454c69514f6a594a6a5164516b786932525a4d4d335762554949524a6852595567764241524a4d54756334746977437147506246447555596b6f77535663344e7161325a574a67645234384c4547432f767959683056546c7377443759315355652b497232725a7757564c467248515a356c7a716271646b76616a33337776546c34734e385a666a52632f574e386446326479766b2b486578733369547a2f517844657564734c594f366939654f6d5342703232763731613956546266484b536d492b53684a7a665a49776e316f53504f65694b70636d4c47315377694f6b64467947515671446e34482f6251762f766648584e4958653372676e6a734c574e4248786832446a73732f2b366e48425847385876577666456132384f56754e676a783348344c774d613569674d425552704530636730493063544a526a49323255696558624b5236456f326549414f4a396c4939684a6d6b2b6e43624449717a435a71595461337550467831744561585a4a5263545a5269374e396f70414a7446704555653837465033476350655a6f395232774259685a77306f332f445665346d4f4b415671745966476143554b47706349356171423270665361346c4c44597a7963596b4c344b446a55704e6935626a55432b6a4a34314b5475733634314e525579636a55784b4d784d6a56427930576d396d684a64357a5a33506a49424b453266317a6852693430745566726b34564d614a7047467545364b587556633270726c78734e72776871427778756f55686378693843574e4b31746f633254352b4a42772b6b336c33564845652b6947664e5878612b394661355561584f6e5a6f443767616861554f376a75466976643738464f55307156433447372b66655651506b5457794a316a4e314f4472696859316f50343654494c776b626e2b65764d6c434f38557036774652744a6e6772624c3944595376724c714b6b305656316e6a7072527165546d6b492b56594e2f5545693947786f7337476572335a655864624a6b4b496e4667764b6c7642302b38505a324e663263747631703932537a4c6c745a7a55496970466563635331397449344b73664e4f6c746c44412b31516d584c654946304b2b383843346476776c574c44705a42664e784975686951774d6c616a4b70666f2f37684f3277597a344c4874555675304f387030364248634a395831492f54464339505668552b745678616f6a33306a6778346e4d3174572f55526e795970765931576731682f4469585270694f55554d59784f356e6478323676727a32314965716f592b59762f3749797076716846487668716d6837626c67757967466a463859443247545779754c5157766b564e7a6265506f2b2b33696372774b2f6665465058344b3176613837456d2b683349364a78586f704b396d5a4a5a57644455704e61566b75423755784a2f6d374175565274516a792f59694b366942662b68664b67796731725a30483864536e52597357374542716d397675306d58336170522b6432776f34684e6177494b755a67365958565845372b61675754634855715a737541476b525a6630796e70776770752b543476575649484b31712b376c5543716c72395079576e30756c57672b69516e5663775a4c626d796935703730386b455a2b2b572b4136675862386b355a665469352f633745365247765051746978434c5174516b77494859306a6b767359727762636854374458332f7863514654484f5a385a56484f48565846626b55677564782f6333552b3830567853573553786774444e646f4156713244623453704b732b635358415074666f385261694a2f67704f4453684c4b572b673871584e756d6e6659655a4a48325453786a377041542f4c2b764362752f64376556324c66622f6d33697657705373416c44646d4f754b5371373762536c56424f6630646369576b6633344e6c2f77705a736b32726b6e586b3362475430396576727a6d4e634f713371316e32304e3055586153384f66324865334e3764583336383877397635323961562b73686762446352667377757368444e78356f4e7236566f36574666575832506457712f6a6b4c4533767a713475623639507a3237646d396e46374f7a327176346c4f5149324e6845314c596f5241685a466c6b326741366c743257686f3564754e313175747a745a686d734432666851744271445a2f63582b39636947392f6b6c73567758554c2f6d642b3976316d48637147653150355756526c474172575a43644367546b714a2b724b784c4f4d303955375a6966724957374c51377a72516c672b643669305655486a456a5575597243353475483256507353396c4a6c306f75595667785436644c6b6656422f412f6f503565376b596e4f5a305950334d466f4a61314a423153365a6d2b5974414533544764764c79655863782b50723264755a6e503472737143416e466a675549704c614a414c47524352314349616e3371456d3571693365432b6a2b636e72352b6d4a3233594864524e5143454e73414f52545a466b5957785a6a59466e4a73596c4948517073514374485147536b396c4c793975506f6e487a6e464343434c324468645167353950396d4e675731573679394b716c594d6e637756352f43486e5844374541494a34427a333636677262493169645a6c7163376b724c343764657938575849317a764c516265367445334f476d7170644b624232356973365841304851455765375a716e3433475730667469745377635074716a4d2f654239504b494b4e5762792b5636375033373242393674534c74646637394f4e414749614d54336371314e33574b706e306f6e6c6a4a336e30756e6c44585033707a6675724e665a3563644b544e454e7144414d514845474a676d70705a447261477a6c7271785051544a37435072507a3149595052592f2f474266556c5872714777707151444246556a74645a55453176672b79375a33484e57397650733173327631373538392b6248726977415770594e624163435970735949784d41614a6b32426369304d45434132744c727854622b322f4d3373357662307a64764f784b5237476f345946495445555349685731694934695269536d3171594d6f70685244694a55546b5a53534e41756258627670596e313263384d6e4a4b55697a622b6f44553348424e53324c4174687977596d674a526747774a45724b4872362f71704b4a6575765851514242334c775359424e7343574179304b55336b3446454154554543514252316b49774147372b72724a655a6d39762f657a5337506871514373594f6741327a5478445a3048456942436242704132776a427a735767656c504b545534565a7778424e332b777a322f2f4f6d7167777754556f41633077454957424169434579415276462f2b772f3335767a6e793950626439657a66754e414e73484974414367466f5555456f516f425941366733635a64754e6e79592b7274662f68386a48316345716573674669736b79326a6d63346f3231375741556b6e4d785766536e47353243387a4c566b756a4a4e4a374c693038456950352b6270362b34345535373938766e6266444134735237324978696351646c6375505a6f707259666970344a6a47684668396135483934687453576f795a472b38777071576e313368672b3831597246703157466d594b664e614254476c504e557a546d564d446a573572346e4b68512f59485a55743849657068733650516b4c306b56326251786d315235426872536730776b7870544864654535745245704e32672b4a7a6f6d5950444d716f4f5565706956576346547866504e2b6b456850376f4d4e57434d3656784e5a464e5a313174544c724e7134735854664e77554162574b5531747a504a4e4c43376665786f62752f313848693758716b77576f36653070787a4664465a55777464744f33573652386e336f4f796b49612b526a50467449766e73427156576a6c4f6748492f6b5a7a3739684e2f55506f4254346e3048596c724c32654b5a306e777153446732704c364a78756467764d77507a4a726134745042596b65733258377a30647244366a74416f716277652b716b75546a2f386672302b703935493831515634746c456d5361466b5955573943784961595559476f526841473148597770686735434e72497341676b6d796e744b4e614c3475306e49704151675168334c5242524153436c794d4352457562586e497068485876516c5851637261555a312f475465706f4c6b2b625567636f67664b65676e3749623554326739764a3639765467396d376c6e463664642b3563495977417359434a494c637330715930524a51425332795245656276796d6d31576e732f4f306c6c5255704561674830726878613366444f37664f322b6d6433636e50343863322b7655732f63345a464e414b6b44624570734535695736546941516f66593245475753536b463674756f5a62503739657a30646363574c6743324134686a6d68536b73303441734243773051673358434c392b2f5835626366474d6349327367476b47434362576743446c46644343484b6f7163347343786476386f4d4a62746358384f5a4c7242774a4f6b434e31634e6b4c6430667666472b724e626567754e70657678544f616831474144664f3330766478424374395479547947756d6264516b33706c2f47547874344a6b4f5036325a53794c515775657a364639704a7831685638707464626a586a6c534869304d2f6c7141632f7673704975646771532f5230482f7958686941505936745a5776335455666a6a6451743143534537386d393546466364352f4b6967673331382f686f6d722f41334d672f665a58544a783939367a4e7532374e4a432f4e753372754534694c34793950456c755a30383956776e3639313451757347695071446e5173467748666f4e776e54634b646735382b6e2f33303559686a632f72364639554c7a4136557869574161505a5a49426f3674396647414e314e633333723047456c673537544d74715974503947416d4658424e5635735045543279586c6670745535673939464b31633846586d5a486b52776278624f5878636b6b346f637643564b6a6675715349494b6d7a4d664e626856617835783148763353666559374c55392b45546e3642585764676a523443424939727379694f41346f6472704d70775476574f4a79756b7a796f34324256762f61527158755a4c7467715a2b484c6f4e7174466361676e6b59726f6c445a62642f71722f34636e632f32306950784346694172664577544c574e7732416c4852514a756545396562525643494f79757879554b625a3661444d387051325151646c616e645176495170703770395965356f463956414e744a4a6361464e3561616179505134716c366f422b53716d6e54324f4b764771357264565a4f5171527857453438576c395548564e5a704364796d4b754b307243366e5a51467454737653376253717652373572616e3630366b43787a675856514d796b5763716357687853467867682b4f4853764b3633552f78786b7444754f716c5a5846584a5734696c315343312b474a654c416b485244526c4458524c6764453947564e564b63444b73363563714e79647964336c355a57443152466f753643326c4361567972757658323052744a6f6c39554e37544238566f322b6271655661626d2b524b6d4764514a7656494d2f316831314170503052356b4244687a4e4c4f4b50634764435a4866364979767a5236617750384a542b4b4e50323132796a467a63766f4a636830504b73497a33534255775931775366356474307533574f6766615042674833474735734a78414c666354434f4b613048506c43485335726a593053642b4675322f73786a4b2b693354354c6b773766526557394631457a48643972664c71506e696864316363414c4448712f35324679566f766b5478324e6a583573623078636e4a43776c54422b624a7257663670553733484d68634b324572774a63784c78583457416438434c756e57455a416a676f4347516c3149356a7135684168444b4e7532424443384952583445783076626f51686e47586c6d633955306b5342665048704979475a6672426f6d676475655831596c554354764d757135644776674e6d424c455272684d6a755139696f2b6a414f6971796f53512f525234364b57705748696f506e56545a6c3676314a77363146526271314e666a5071724839324352317a534c4c6a41577838463878647a5958323979786f37574778622b6d323032624257454a77575a4a36763838346555334c37665430353362496d38567834697667355858397959725a5a48373750706c4a4474676d3069356e734a57786a4a5a364e73684b754c4657476e4b6c5a4548486d786f7070534f4163745666615a2b59384a6b78646d78466959524636595a4372626c434a464e536c5361367755726563697876665a436e332b654666755a6f5350713958582f776b4141502f2f304551477335726f4151413d6945787465726e616c7388a2664f6666736574841bffffffffffffc6611bffffffffffffffff1bffffffffffffffff1b07fffffffffc2c706853656c6563746f72841b30b9224cd18a4e7c1bc71f0c828480ecb01b8de0747c8cfdb4821b04c9700ad999c431a2664f6666736574841bffffffffffffa8a11bffffffffffffffff1bffffffffffffffff1b07fffffffffa32b06853656c6563746f72841ba6951e73bb6dfe241b05e5ff539dbeebe71bdd254888370955b41b043ef1349908885ea2664f6666736574841bffffffffffffbb811bffffffffffffffff1bffffffffffffffff1b07fffffffffb73906853656c6563746f72841bb22ea4840c873b8b1baa7fdf9303e1d7731b704e592f223c3de61b00bda65049efea59a2664f6666736574841bffffffffffffcde11bffffffffffffffff1bffffffffffffffff1b07fffffffffcabf06853656c6563746f72841b279f5f4dc8b4c7b31b6ac884a41a1b5bd71bc1307f4321f00b881b04bc8012967069c6a2664f6666736574841bffffffffffffc1811bffffffffffffffff1bffffffffffffffff1b07fffffffffbd9906853656c6563746f72841bc735e82ed1e23e011b9ee8c8991bbc1d151bacb56cf03bbaac5a1b00186ed02a71bd65a2664f6666736574841bffffffffffffb4211bffffffffffffffff1bffffffffffffffff1b07fffffffffaf6306853656c6563746f72841b04b9244ab52a4b811bffdbe496a2c48be11b8a5d0118067bf13c1b07102404b615fdd5a2664f6666736574841bffffffffffffc9c11bffffffffffffffff1bffffffffffffffff1b07fffffffffc65d06853656c6563746f72841bf6c53d175ebbcd9f1bdd07ee6aca2299561bd457faeea1e288671b0754ebc0b261e0b1a2664f6666736574841bffffffffffffaf611bffffffffffffffff1bffffffffffffffff1b07fffffffffaa5706853656c6563746f72841b4298606763e030411ba7525ed75dc70c951b01c0a0b4d48ac5061b0624e8d006b62cda6a4c3148616e646c657273806c436f6e7374727563746f727381a2664f6666736574841bffffffffffffd2411bffffffffffffffff1bffffffffffffffff1b07fffffffffcf6506853656c6563746f72841bd1c59afcd3f9742d1b083befda3709a3ed1b3142bfd1003b62e21b041f80cbf54be289") - assert.NoError(t, err) - - stateDiff := map[felt.Felt]*felt.Felt{} - - for i := 0; i < 10; i++ { - stateDiff[*new(felt.Felt).SetUint64(uint64(i*10 + 1))] = new(felt.Felt).SetUint64(uint64(i*10 + 2)) - } - - return bc.Store( - &core.Block{ - Header: &core.Header{ - Hash: &felt.Zero, - ParentHash: &felt.Zero, - GlobalStateRoot: expectedStateRoot, - }, - }, - nil, - &core.StateUpdate{ - NewRoot: expectedStateRoot, - OldRoot: &felt.Zero, - StateDiff: &core.StateDiff{ - Nonces: map[felt.Felt]*felt.Felt{ - *key: nonce, - }, - DeployedContracts: map[felt.Felt]*felt.Felt{ - *key: classHash, - }, - StorageDiffs: map[felt.Felt]map[felt.Felt]*felt.Felt{ - *key: stateDiff, - }, - DeclaredV0Classes: nil, - DeclaredV1Classes: nil, - ReplacedClasses: nil, - }, - }, - map[felt.Felt]core.Class{ - *classHash: cls, - }) - }, - }, - { - name: "basic with v1 classes", - scenario: func(t *testing.T, bc *blockchain.Blockchain) error { - t.Skip("new test data needed - why?") - expectedStateRoot := hexToFelt("051099998c4c482b6ab11ed287d9fd1bc1b2e0dafca099cfc23e55c8754048bd") - - key := new(felt.Felt).SetUint64(uint64(12)) - nonce := new(felt.Felt).SetUint64(uint64(1)) - - clsHash, compiledClsHash, cls, err := V1ClassFromString("01b5126eb60b96de5b8e45c1334ad44d23dacaa76f748ab08b23dac43f3958", "a262417419aba565436c617373da00010006a763416269785f5b7b2274797065223a20226576656e74222c20226e616d65223a2022627974655f61727261793a3a53696d706c6553746f726167653a3a4576656e74222c20226b696e64223a2022656e756d222c202276617269616e7473223a205b5d7d5d6741626948617368841b194b356d26f0cd761bd738e24f884338411bc2749e3d7f8913051b0459fa72a5e413526750726f6772616d8b841bffffffffffffffe11bffffffffffffffff1bffffffffffffffff1b07fffffffffffdf0841bffffffffffffff811bffffffffffffffff1bffffffffffffffff1b07fffffffffff7908400000000841bffffffffffffffc11bffffffffffffffff1bffffffffffffffff1b07fffffffffffbd0841bffffffffffffff611bffffffffffffffff1bffffffffffffffff1b07fffffffffff570841bffffffffffffff811bffffffffffffffff1bffffffffffffffff1b07fffffffffff790841bffffffffffffffe11bffffffffffffffff1bffffffffffffffff1b07fffffffffffdf0841bffffffffffffe0211bffffffffffffffff1bffffffffffffffff1b07fffffffffde2308400000000841bffffffffffffff811bffffffffffffffff1bffffffffffffffff1b07fffffffffff790840000000068436f6d70696c6564a96548696e7473425b5d655072696d65c2582008000000000000110000000000000000000000000000000000000000000000016842797465636f6465806845787465726e616c80694c3148616e646c6572806b436f6e7374727563746f72806d507974686f6e696348696e7473425b5d6f436f6d70696c657256657273696f6e65322e362e307642797465636f64655365676d656e744c656e67746873a2664c656e67746800684368696c6472656ef66b456e747279506f696e7473a36845787465726e616c80694c3148616e646c6572806b436f6e7374727563746f72806b50726f6772616d48617368841bb3799734fc0a22761b73a1705363df43691b78153b3e72b8b9ff1b0735af66c53fce8d6f53656d616e74696356657273696f6e65302e312e30") - assert.NoError(t, err) - clsHash2, compiledClsHash2, cls2, err := V1ClassFromString("0cc7d5b4b3f7f112209f743ed739f97570d83d7b42c3516bd955d4b053237dd", "a262417419eee165436c617373da00010006a76341626978615b7b2274797065223a20226576656e74222c20226e616d65223a2022746f746f3a3a636f6e7472616374733a3a546f746f3a3a546f746f3a3a4576656e74222c20226b696e64223a2022656e756d222c202276617269616e7473223a205b5d7d5d6741626948617368841b12a4a837ffa449ba1b6eeb23f3ef6440e31b453422a17e9d29381b02cbf391fe0a8c9d6750726f6772616d8b841bffffffffffffffe11bffffffffffffffff1bffffffffffffffff1b07fffffffffffdf0841bffffffffffffff811bffffffffffffffff1bffffffffffffffff1b07fffffffffff7908400000000841bffffffffffffffc11bffffffffffffffff1bffffffffffffffff1b07fffffffffffbd0841bffffffffffffff611bffffffffffffffff1bffffffffffffffff1b07fffffffffff570841bffffffffffffffa11bffffffffffffffff1bffffffffffffffff1b07fffffffffff9b0841bffffffffffffffe11bffffffffffffffff1bffffffffffffffff1b07fffffffffffdf0841bffffffffffffe0211bffffffffffffffff1bffffffffffffffff1b07fffffffffde2308400000000841bffffffffffffff811bffffffffffffffff1bffffffffffffffff1b07fffffffffff790840000000068436f6d70696c6564a96548696e7473425b5d655072696d65c2582008000000000000110000000000000000000000000000000000000000000000016842797465636f6465806845787465726e616c80694c3148616e646c6572806b436f6e7374727563746f72806d507974686f6e696348696e7473425b5d6f436f6d70696c657256657273696f6e65322e362e307642797465636f64655365676d656e744c656e67746873a2664c656e67746800684368696c6472656ef66b456e747279506f696e7473a36845787465726e616c80694c3148616e646c6572806b436f6e7374727563746f72806b50726f6772616d48617368841ba97fcaac59fda9281bcb5930ad34825d5c1bf25090cae1f11f781b008272c3d5d2ae0b6f53656d616e74696356657273696f6e65302e312e30") - assert.NoError(t, err) - - return bc.Store( - &core.Block{ - Header: &core.Header{ - Hash: &felt.Zero, - ParentHash: &felt.Zero, - GlobalStateRoot: expectedStateRoot, - }, - }, - nil, - &core.StateUpdate{ - NewRoot: expectedStateRoot, - OldRoot: &felt.Zero, - StateDiff: &core.StateDiff{ - Nonces: map[felt.Felt]*felt.Felt{ - *key: nonce, - }, - DeployedContracts: map[felt.Felt]*felt.Felt{ - *key: clsHash, - }, - StorageDiffs: map[felt.Felt]map[felt.Felt]*felt.Felt{}, - DeclaredV0Classes: nil, - DeclaredV1Classes: map[felt.Felt]*felt.Felt{ - *clsHash: compiledClsHash, - *clsHash2: compiledClsHash2, - }, - ReplacedClasses: nil, - }, - }, - map[felt.Felt]core.Class{ - *clsHash: cls, - *clsHash2: cls2, - }) - }, - }, - } - - for _, scenario := range scenarios { - t.Run(scenario.name, func(t *testing.T) { - d, err := pebble.NewMem() - assert.NoError(t, err) - bc := blockchain.New(d, &utils.Sepolia) - - err = scenario.scenario(t, bc) - assert.NoError(t, err) - - d2, err := pebble.NewMem() - assert.NoError(t, err) - bc2 := blockchain.New(d2, &utils.Sepolia) - - logger, err := utils.NewZapLogger(utils.DEBUG, false) - assert.NoError(t, err) - - syncer := NewSnapSyncer( - &NoopService{ - blockchain: bc, - }, - &localStarknetData{bc}, - &snapServer{ - blockchain: bc, - }, - bc2, - logger, - ) - - err = syncer.Run(context.Background()) - assert.NoError(t, err) - - state1, closer, err := bc.HeadStateFreakingState() - assert.NoError(t, err) - defer func() { _ = closer() }() - - state2, closer, err := bc2.HeadStateFreakingState() - assert.NoError(t, err) - defer func() { _ = closer() }() - - sr, cr, err := state1.StateAndClassRoot() - assert.NoError(t, err) - - sr2, cr2, err := state2.StateAndClassRoot() - assert.NoError(t, err) - - assert.Equal(t, sr, sr2) - assert.Equal(t, cr, cr2) - }) - } -} - -func TestSnapCopyTrie(t *testing.T) { - t.Skip("DB snapshot is needed for this test") - var d db.DB - d, err := pebble.NewWithOptions("/home/amirul/fastworkscratch3/juno_db/juno_mainnet/", 1280, 128, false) - defer func() { _ = d.Close() }() - assert.NoError(t, err) - - bc := blockchain.New(d, &utils.Sepolia) // Needed because class loader need encoder to be registered - - targetdir := "/home/amirul/fastworkscratch3/targetjuno" - os.RemoveAll(targetdir) - - go func() { - http.Handle("/metrics", promhttp.Handler()) - _ = http.ListenAndServe(":9201", nil) - }() - - var d2 db.DB - d2, _ = pebble.NewWithOptions(targetdir, 128000000, 128, false) - defer func() { _ = d2.Close() }() - bc2 := blockchain.New(d2, &utils.Mainnet) // Needed because class loader need encoder to be registered - - logger, err := utils.NewZapLogger(utils.DEBUG, false) - assert.NoError(t, err) - - syncer := NewSnapSyncer( - &NoopService{ - blockchain: bc, - }, - &localStarknetData{bc}, - &snapServer{ - blockchain: bc, - }, - bc2, - logger, - ) - - err = syncer.Run(context.Background()) - assert.NoError(t, err) - - state1, closer, err := bc.HeadStateFreakingState() - assert.NoError(t, err) - defer func() { _ = closer() }() - - state2, closer, err := bc2.HeadStateFreakingState() - assert.NoError(t, err) - defer func() { _ = closer() }() - - sr, cr, err := state1.StateAndClassRoot() - assert.NoError(t, err) - - sr2, cr2, err := state2.StateAndClassRoot() - assert.NoError(t, err) - - assert.Equal(t, sr, sr2) - assert.Equal(t, cr, cr2) -} - -type NoopService struct { - blockchain *blockchain.Blockchain -} - -func (n NoopService) Run(ctx context.Context) error { - return nil -} - -type localStarknetData struct { - blockchain *blockchain.Blockchain -} - -func (l localStarknetData) BlockByNumber(ctx context.Context, blockNumber uint64) (*core.Block, error) { - return l.blockchain.BlockByNumber(blockNumber) -} - -func (l localStarknetData) BlockLatest(ctx context.Context) (*core.Block, error) { - time.Sleep(time.Second) - return l.blockchain.Head() -} - -func (l localStarknetData) BlockPending(ctx context.Context) (*core.Block, error) { - // TODO implement me - panic("implement me") -} - -func (l localStarknetData) Transaction(ctx context.Context, transactionHash *felt.Felt) (core.Transaction, error) { - // TODO implement me - panic("implement me") -} - -func (l localStarknetData) Class(ctx context.Context, classHash *felt.Felt) (core.Class, error) { - // TODO implement me - panic("implement me") -} - -func (l localStarknetData) StateUpdate(ctx context.Context, blockNumber uint64) (*core.StateUpdate, error) { - // TODO implement me - panic("implement me") -} - -func (l localStarknetData) StateUpdatePending(ctx context.Context) (*core.StateUpdate, error) { - // TODO implement me - panic("implement me") -} - -func (l localStarknetData) StateUpdateWithBlock(ctx context.Context, blockNumber uint64) (*core.StateUpdate, *core.Block, error) { - // TODO implement me - panic("implement me") -} - -func (l localStarknetData) StateUpdatePendingWithBlock(ctx context.Context) (*core.StateUpdate, *core.Block, error) { - // TODO implement me - panic("implement me") -} - -var _ starknetdata.StarknetData = &localStarknetData{} From a3e662bb1814e32d0f5b2af5aa14ab71bd21f027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowosielski?= Date: Wed, 4 Sep 2024 21:57:08 +0200 Subject: [PATCH 04/23] protobuf changes --- p2p/starknet/p2p/proto/class.proto | 4 ++++ p2p/starknet/p2p/proto/snapshot.proto | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/p2p/starknet/p2p/proto/class.proto b/p2p/starknet/p2p/proto/class.proto index f68235350c..227adca4e7 100644 --- a/p2p/starknet/p2p/proto/class.proto +++ b/p2p/starknet/p2p/proto/class.proto @@ -61,3 +61,7 @@ message Classes { uint32 domain = 1; repeated Class classes = 2; } + +message ClassHashesRequest { + repeated Hash class_hashes = 1; +} diff --git a/p2p/starknet/p2p/proto/snapshot.proto b/p2p/starknet/p2p/proto/snapshot.proto index 47b5ae12e0..997c58c47a 100644 --- a/p2p/starknet/p2p/proto/snapshot.proto +++ b/p2p/starknet/p2p/proto/snapshot.proto @@ -58,6 +58,7 @@ message ContractRangeResponse { ContractRange range = 4; Fin fin = 5; } + PatriciaRangeProof range_proof = 6; } // duplicate of GetContractRange. Can introduce a 'type' instead. @@ -77,6 +78,7 @@ message ClassRangeResponse { Classes classes = 4; Fin fin = 5; } + PatriciaRangeProof range_proof = 6; } // A position in some contract's state tree is identified by the state tree's root and the key in it @@ -105,8 +107,10 @@ message ContractStorage { message ContractStorageResponse { optional Hash state_root = 1; // may not appear if Fin is sent to end the whole response + optional Address contract_address = 2; oneof responses { - ContractStorage storage = 2; - Fin fin = 3; + ContractStorage storage = 3; + Fin fin = 4; } + PatriciaRangeProof range_proof = 5; } From f55bd3c7dcfc051a69da7d856019d9be90ab51d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowosielski?= Date: Thu, 5 Sep 2024 16:11:39 +0200 Subject: [PATCH 05/23] snap server implements handler methods directly --- {sync => p2p}/snap_server.go | 199 +++++++++++++++++------------- {sync => p2p}/snap_server_test.go | 42 +------ p2p/snap_syncer.go | 4 +- p2p/starknet/handlers.go | 27 +++- p2p/starknet/snap_provider.go | 127 +------------------ p2p/starknet/spec/snapshot.pb.go | 8 +- 6 files changed, 147 insertions(+), 260 deletions(-) rename {sync => p2p}/snap_server.go (68%) rename {sync => p2p}/snap_server_test.go (84%) diff --git a/sync/snap_server.go b/p2p/snap_server.go similarity index 68% rename from sync/snap_server.go rename to p2p/snap_server.go index fa86e9d5a0..1810489cb8 100644 --- a/sync/snap_server.go +++ b/p2p/snap_server.go @@ -1,7 +1,7 @@ -package sync +package p2p import ( - "context" + "google.golang.org/protobuf/proto" "math/big" "github.com/NethermindEth/juno/adapters/core2p2p" @@ -43,11 +43,12 @@ type ClassRangeStreamingResult struct { RangeProof *spec.PatriciaRangeProof } +// TODO: delete, duplicate of SnapProvider type SnapServer interface { - GetContractRange(ctx context.Context, request *spec.ContractRangeRequest) (iter.Seq[*ContractRangeStreamingResult], error) - GetStorageRange(ctx context.Context, request *StorageRangeRequest) (iter.Seq[*StorageRangeStreamingResult], error) - GetClassRange(ctx context.Context, request *spec.ClassRangeRequest) (iter.Seq[*ClassRangeStreamingResult], error) - GetClasses(ctx context.Context, classHashes []*felt.Felt) ([]*spec.Class, error) + GetClassRange(request *spec.ClassRangeRequest) (iter.Seq[proto.Message], error) + GetContractRange(request *spec.ContractRangeRequest) (iter.Seq[proto.Message], error) + GetStorageRange(request *spec.ContractStorageRequest) (iter.Seq[proto.Message], error) + GetClasses(request *spec.ClassHashesRequest) (iter.Seq[proto.Message], error) } type SnapServerBlockchain interface { @@ -55,6 +56,8 @@ type SnapServerBlockchain interface { GetClasses(felts []*felt.Felt) ([]core.Class, error) } +type yieldFunc = func(proto.Message) bool + var _ SnapServerBlockchain = (*blockchain.Blockchain)(nil) func NewSnapServer(blockchain SnapServerBlockchain) SnapServer { @@ -67,7 +70,7 @@ type snapServer struct { blockchain SnapServerBlockchain } -func determineMaxNodes(specifiedMaxNodes uint64) uint64 { +func determineMaxNodes(specifiedMaxNodes uint32) uint32 { const ( defaultMaxNodes = 1024 * 16 maxNodePerRequest = 1024 * 1024 // I just want it to process faster @@ -84,8 +87,12 @@ func determineMaxNodes(specifiedMaxNodes uint64) uint64 { return maxNodePerRequest } -func (b *snapServer) GetClassRange(ctx context.Context, request *spec.ClassRangeRequest) (iter.Seq[*ClassRangeStreamingResult], error) { - return func(yield func(*ClassRangeStreamingResult) bool) { +func (b *snapServer) GetClassRange(request *spec.ClassRangeRequest) (iter.Seq[proto.Message], error) { + var finMsg proto.Message = &spec.ClassRangeResponse{ + Responses: &spec.ClassRangeResponse_Fin{}, + } + + return func(yield yieldFunc) { stateRoot := p2p2core.AdaptHash(request.Root) s, err := b.blockchain.GetStateForStateRoot(stateRoot) @@ -119,7 +126,7 @@ func (b *snapServer) GetClassRange(ctx context.Context, request *spec.ClassRange } classkeys := []*felt.Felt{} - proofs, finished, err := iterateWithLimit(ctrie, startAddr, limitAddr, determineMaxNodes(uint64(request.ChunksPerProof)), + proofs, finished, err := iterateWithLimit(ctrie, startAddr, limitAddr, determineMaxNodes(request.ChunksPerProof), func(key, value *felt.Felt) error { classkeys = append(classkeys, key) return nil @@ -143,26 +150,32 @@ func (b *snapServer) GetClassRange(ctx context.Context, request *spec.ClassRange response.Classes = append(response.Classes, core2p2p.AdaptClass(coreclass)) } - shouldContinue := yield(&ClassRangeStreamingResult{ - ContractsRoot: contractRoot, - ClassesRoot: classRoot, - Range: response, - RangeProof: Core2P2pProof(proofs), - }) + clsMsg := &spec.ClassRangeResponse{ + ContractsRoot: core2p2p.AdaptHash(contractRoot), + ClassesRoot: core2p2p.AdaptHash(classRoot), + Responses: &spec.ClassRangeResponse_Classes{ + Classes: response, + }, + RangeProof: Core2P2pProof(proofs), + } + shouldContinue := yield(clsMsg) if finished || !shouldContinue { break } startAddr = classkeys[len(classkeys)-1] } + + yield(finMsg) }, nil } -func (b *snapServer) GetContractRange( - ctx context.Context, - request *spec.ContractRangeRequest, -) (iter.Seq[*ContractRangeStreamingResult], error) { - return func(yield func(*ContractRangeStreamingResult) bool) { +func (b *snapServer) GetContractRange(request *spec.ContractRangeRequest) (iter.Seq[proto.Message], error) { + var finMsg proto.Message = &spec.ContractRangeResponse{ + Responses: &spec.ContractRangeResponse_Fin{}, + } + + return func(yield yieldFunc) { stateRoot := p2p2core.AdaptHash(request.StateRoot) s, err := b.blockchain.GetStateForStateRoot(stateRoot) @@ -189,7 +202,7 @@ func (b *snapServer) GetContractRange( states := []*spec.ContractState{} for { - proofs, finished, err := iterateWithLimit(strie, startAddr, limitAddr, determineMaxNodes(uint64(request.ChunksPerProof)), + proofs, finished, err := iterateWithLimit(strie, startAddr, limitAddr, determineMaxNodes(request.ChunksPerProof), func(key, value *felt.Felt) error { classHash, err := s.ContractClassHash(key) if err != nil { @@ -225,23 +238,35 @@ func (b *snapServer) GetContractRange( return } - shouldContinue := yield(&ContractRangeStreamingResult{ - ContractsRoot: contractRoot, - ClassesRoot: classRoot, - Range: states, + cntrMsg := &spec.ContractRangeResponse{ + Root: request.StateRoot, + ContractsRoot: core2p2p.AdaptHash(contractRoot), + ClassesRoot: core2p2p.AdaptHash(classRoot), RangeProof: Core2P2pProof(proofs), - }) + Responses: &spec.ContractRangeResponse_Range{ + Range: &spec.ContractRange{ + State: states, + }, + }, + } + shouldContinue := yield(cntrMsg) if finished || !shouldContinue { break } } + + yield(finMsg) }, nil } -func (b *snapServer) GetStorageRange(ctx context.Context, request *StorageRangeRequest) (iter.Seq[*StorageRangeStreamingResult], error) { - return func(yield func(*StorageRangeStreamingResult) bool) { - stateRoot := request.StateRoot +func (b *snapServer) GetStorageRange(request *spec.ContractStorageRequest) (iter.Seq[proto.Message], error) { + var finMsg proto.Message = &spec.ContractStorageResponse{ + Responses: &spec.ContractStorageResponse_Fin{}, + } + + return func(yield yieldFunc) { + stateRoot := p2p2core.AdaptHash(request.StateRoot) s, err := b.blockchain.GetStateForStateRoot(stateRoot) if err != nil { @@ -249,20 +274,10 @@ func (b *snapServer) GetStorageRange(ctx context.Context, request *StorageRangeR return } - contractRoot, classRoot, err := s.StateAndClassRoot() - if err != nil { - log.Error("error getting state and class root", "err", err) - return - } + var curNodeLimit uint32 = 1000000 - var curNodeLimit int64 = 1000000 - - for _, query := range request.Queries { - if ctxerr := ctx.Err(); ctxerr != nil { - break - } - - contractLimit := uint64(curNodeLimit) + for _, query := range request.Query { + contractLimit := curNodeLimit strie, err := s.StorageTrieForAddr(p2p2core.AdaptAddress(query.Address)) if err != nil { @@ -270,16 +285,24 @@ func (b *snapServer) GetStorageRange(ctx context.Context, request *StorageRangeR return } - handled, err := b.handleStorageRangeRequest(ctx, strie, query, request.ChunkPerProof, contractLimit, - func(values []*spec.ContractStoredValue, proofs []trie.ProofNode) { - yield(&StorageRangeStreamingResult{ - ContractsRoot: contractRoot, - ClassesRoot: classRoot, - StorageAddr: p2p2core.AdaptAddress(query.Address), - Range: values, - RangeProof: Core2P2pProof(proofs), - }) + handled, err := b.handleStorageRangeRequest(strie, query, request.ChunksPerProof, contractLimit, + func(values []*spec.ContractStoredValue, proofs []trie.ProofNode) bool { + stoMsg := &spec.ContractStorageResponse{ + StateRoot: request.StateRoot, + ContractAddress: query.Address, + RangeProof: Core2P2pProof(proofs), + Responses: &spec.ContractStorageResponse_Storage{ + Storage: &spec.ContractStorage{ + KeyValue: values, + }, + }, + } + if !yield(stoMsg) { + return false + } + return true }) + if err != nil { log.Error("error handling storage range request", "err", err) return @@ -291,42 +314,50 @@ func (b *snapServer) GetStorageRange(ctx context.Context, request *StorageRangeR break } } + + yield(finMsg) }, nil } -// GetStorageRangeStd TODO: move/change đŸ‘† - just to check it can work on spec structs -func (b *snapServer) GetStorageRangeStd(ctx context.Context, request *spec.ContractStorageRequest) (iter.Seq[*StorageRangeStreamingResult], error) { - req := &StorageRangeRequest{ - StateRoot: p2p2core.AdaptHash(request.StateRoot), - Queries: request.Query, - ChunkPerProof: uint64(request.ChunksPerProof), +func (b *snapServer) GetClasses(request *spec.ClassHashesRequest) (iter.Seq[proto.Message], error) { + var finMsg proto.Message = &spec.ClassesResponse{ + ClassMessage: &spec.ClassesResponse_Fin{}, } - return b.GetStorageRange(ctx, req) -} -func (b *snapServer) GetClasses(ctx context.Context, felts []*felt.Felt) ([]*spec.Class, error) { - classes := make([]*spec.Class, len(felts)) - coreClasses, err := b.blockchain.GetClasses(felts) - if err != nil { - return nil, err - } + return func(yield yieldFunc) { + felts := make([]*felt.Felt, len(request.ClassHashes)) + for _, hash := range request.ClassHashes { + felts = append(felts, p2p2core.AdaptHash(hash)) + } - for i, class := range coreClasses { - classes[i] = core2p2p.AdaptClass(class) - } + coreClasses, err := b.blockchain.GetClasses(felts) + if err != nil { + log.Error("error getting classes", "err", err) + return + } + + for _, cls := range coreClasses { + clsMsg := &spec.ClassesResponse{ + ClassMessage: &spec.ClassesResponse_Class{ + Class: core2p2p.AdaptClass(cls), + }, + } + if !yield(clsMsg) { + break + } + } - return classes, nil + yield(finMsg) + }, nil } func (b *snapServer) handleStorageRangeRequest( - ctx context.Context, stTrie *trie.Trie, request *spec.StorageRangeQuery, - maxChunkPerProof uint64, - nodeLimit uint64, - yield func([]*spec.ContractStoredValue, []trie.ProofNode), -) (int64, error) { - totalSent := int64(0) + maxChunkPerProof, nodeLimit uint32, + yield func([]*spec.ContractStoredValue, []trie.ProofNode) bool, +) (uint32, error) { + totalSent := 0 finished := false startAddr := p2p2core.AdaptFelt(request.Start.Key) var endAddr *felt.Felt = nil @@ -335,10 +366,6 @@ func (b *snapServer) handleStorageRangeRequest( } for !finished { - if ctxErr := ctx.Err(); ctxErr != nil { - return totalSent, ctxErr - } - response := []*spec.ContractStoredValue{} limit := maxChunkPerProof @@ -365,9 +392,11 @@ func (b *snapServer) handleStorageRangeRequest( finished = true } - yield(response, proofs) + if !yield(response, proofs) { + finished = true + } - totalSent += int64(len(response)) + totalSent += len(response) nodeLimit -= limit asBint := startAddr.BigInt(big.NewInt(0)) @@ -375,21 +404,21 @@ func (b *snapServer) handleStorageRangeRequest( startAddr = startAddr.SetBigInt(asBint) } - return totalSent, nil + return uint32(totalSent), nil } func iterateWithLimit( srcTrie *trie.Trie, startAddr *felt.Felt, limitAddr *felt.Felt, - maxNodes uint64, + maxNodes uint32, consumer func(key, value *felt.Felt) error, ) ([]trie.ProofNode, bool, error) { pathes := make([]*felt.Felt, 0) hashes := make([]*felt.Felt, 0) // TODO: Verify class trie - count := uint64(0) + count := uint32(0) proof, finished, err := srcTrie.IterateAndGenerateProof(startAddr, func(key *felt.Felt, value *felt.Felt) (bool, error) { // Need at least one. if limitAddr != nil && key.Cmp(limitAddr) > 1 && count > 0 { diff --git a/sync/snap_server_test.go b/p2p/snap_server_test.go similarity index 84% rename from sync/snap_server_test.go rename to p2p/snap_server_test.go index 6b4c92e4d6..ea85ee4137 100644 --- a/sync/snap_server_test.go +++ b/p2p/snap_server_test.go @@ -1,10 +1,8 @@ -package sync +package p2p import ( "context" - "errors" "fmt" - "github.com/NethermindEth/juno/core/trie" "testing" "github.com/NethermindEth/juno/adapters/core2p2p" @@ -22,7 +20,7 @@ import ( func TestClassRange(t *testing.T) { var d db.DB - t.Skip("DB snapshot is needed for this test") + //t.Skip("DB snapshot is needed for this test") d, _ = pebble.NewWithOptions("/Users/pnowosie/juno/snapshots/juno-sepolia", 128000000, 128, false) defer func() { _ = d.Close() }() bc := blockchain.New(d, &utils.Sepolia) // Needed because class loader need encoder to be registered @@ -207,39 +205,3 @@ func feltFromString(str string) *felt.Felt { } return f } - -// TODO: Duplicated methods - maybe they belongs to `sync` or `p2p` - will see -func VerifyGlobalStateRoot(globalStateRoot, classRoot, storageRoot *felt.Felt) error { - var stateVersion = new(felt.Felt).SetBytes([]byte(`STARKNET_STATE_V0`)) - - if classRoot.IsZero() { - if globalStateRoot.Equal(storageRoot) { - return nil - } else { - return errors.New("invalid global state root") - } - } - - if !crypto.PoseidonArray(stateVersion, storageRoot, classRoot).Equal(globalStateRoot) { - return errors.New("invalid global state root") - } - return nil -} - -func VerifyTrie( - expectedRoot *felt.Felt, - paths, hashes []*felt.Felt, - proofs []trie.ProofNode, - height uint8, - hash func(*felt.Felt, *felt.Felt) *felt.Felt, -) (bool, error) { - hasMore, valid, err := trie.VerifyRange(expectedRoot, nil, paths, hashes, proofs, hash, height) - if err != nil { - return false, err - } - if !valid { - return false, errors.New("invalid proof") - } - - return hasMore, nil -} diff --git a/p2p/snap_syncer.go b/p2p/snap_syncer.go index 572d84e91a..cb8930cacd 100644 --- a/p2p/snap_syncer.go +++ b/p2p/snap_syncer.go @@ -20,7 +20,6 @@ import ( "github.com/NethermindEth/juno/p2p/starknet/spec" "github.com/NethermindEth/juno/service" "github.com/NethermindEth/juno/starknetdata" - junoSync "github.com/NethermindEth/juno/sync" //TODO: Remove this? "github.com/NethermindEth/juno/utils" "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" "github.com/prometheus/client_golang/prometheus" @@ -43,7 +42,6 @@ type SnapSyncher struct { baseSync service.Service starknetData starknetdata.StarknetData client starknet.Client - snapServer junoSync.SnapServer // TODO: Remove this? blockchain Blockchain log utils.Logger @@ -785,7 +783,7 @@ func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) job := jobs[processedJobs] - storageAddr := p2p2core.AdaptFelt(response.ContractAddress) + storageAddr := p2p2core.AdaptAddress(response.ContractAddress) if !job.path.Equal(storageAddr) { s.log.Errorw(fmt.Sprintf( "storage addr differ %s %s %d\n", job.path, storageAddr, workerIdx)) diff --git a/p2p/starknet/handlers.go b/p2p/starknet/handlers.go index 2e7b165f95..276403c3f2 100644 --- a/p2p/starknet/handlers.go +++ b/p2p/starknet/handlers.go @@ -15,7 +15,6 @@ import ( "github.com/NethermindEth/juno/core" "github.com/NethermindEth/juno/core/felt" "github.com/NethermindEth/juno/p2p/starknet/spec" - junoSync "github.com/NethermindEth/juno/sync" "github.com/NethermindEth/juno/utils" "github.com/libp2p/go-libp2p/core/network" "google.golang.org/protobuf/encoding/protodelim" @@ -24,7 +23,7 @@ import ( type Handler struct { bcReader blockchain.Reader - snapProvider *SnapProvider + snapProvider SnapProvider log utils.SimpleLogger ctx context.Context @@ -43,9 +42,9 @@ func NewHandler(bcReader blockchain.Reader, log utils.SimpleLogger) *Handler { } } -func (h *Handler) WithSnapsyncSupport(bc *blockchain.Blockchain) { +func (h *Handler) WithSnapsyncSupport(provider SnapProvider) { // TODO: should it be here? - h.snapProvider = &SnapProvider{SnapServer: junoSync.NewSnapServer(bc)} + h.snapProvider = provider } // bufferPool caches unused buffer objects for later reuse. @@ -136,7 +135,23 @@ func (h *Handler) ClassRangeRequest(stream network.Stream) { h.log.Debugw("SnapProvider not initialized") return } - streamHandler[*spec.ClassRangeRequest](h.ctx, &h.wg, stream, h.onClassRangeRequest, h.log) + streamHandler[*spec.ClassRangeRequest](h.ctx, &h.wg, stream, h.snapProvider.GetClassRange, h.log) +} + +func (h *Handler) ContractRangeRequest(stream network.Stream) { + if h.snapProvider == nil { + h.log.Debugw("SnapProvider not initialized") + return + } + streamHandler[*spec.ContractRangeRequest](h.ctx, &h.wg, stream, h.snapProvider.GetContractRange, h.log) +} + +func (h *Handler) ContractStorageRequest(stream network.Stream) { + if h.snapProvider == nil { + h.log.Debugw("SnapProvider not initialized") + return + } + streamHandler[*spec.ContractStorageRequest](h.ctx, &h.wg, stream, h.snapProvider.GetStorageRange, h.log) } func (h *Handler) ClassHashesRequest(stream network.Stream) { @@ -144,7 +159,7 @@ func (h *Handler) ClassHashesRequest(stream network.Stream) { h.log.Debugw("SnapProvider not initialized") return } - streamHandler[*spec.ClassHashesRequest](h.ctx, &h.wg, stream, h.onClassHashesRequest, h.log) + streamHandler[*spec.ClassHashesRequest](h.ctx, &h.wg, stream, h.snapProvider.GetClasses, h.log) } func (h *Handler) onHeadersRequest(req *spec.BlockHeadersRequest) (iter.Seq[proto.Message], error) { diff --git a/p2p/starknet/snap_provider.go b/p2p/starknet/snap_provider.go index 62fe135514..8832a9f6ac 100644 --- a/p2p/starknet/snap_provider.go +++ b/p2p/starknet/snap_provider.go @@ -1,131 +1,14 @@ package starknet import ( - "fmt" - "github.com/NethermindEth/juno/adapters/core2p2p" - "github.com/NethermindEth/juno/adapters/p2p2core" - "github.com/NethermindEth/juno/core/felt" "github.com/NethermindEth/juno/p2p/starknet/spec" - "github.com/NethermindEth/juno/sync" "github.com/NethermindEth/juno/utils/iter" "google.golang.org/protobuf/proto" ) -type SnapProvider struct { - SnapServer sync.SnapServer -} - -func (h *Handler) onClassHashesRequest(req *spec.ClassHashesRequest) (iter.Seq[proto.Message], error) { - type yieldFunc = func(proto.Message) bool - finMsg := &spec.ClassesResponse{ - ClassMessage: &spec.ClassesResponse_Fin{}, - } - - return func(yield yieldFunc) { - if req == nil || req.ClassHashes == nil || len(req.ClassHashes) == 0 { - yield(finMsg) - return - } - - // Since we return iterator we may split given keys into smaller chunks if necessary - classHashes := make([]*felt.Felt, len(req.ClassHashes)) - for _, hash := range req.ClassHashes { - classHashes = append(classHashes, p2p2core.AdaptHash(hash)) - } - - classes, err := h.snapProvider.SnapServer.GetClasses(h.ctx, classHashes) - if err != nil { - h.log.Errorw("failed to get classes", "err", err) - return - } - - for _, cls := range classes { - msg := &spec.ClassesResponse{ - ClassMessage: &spec.ClassesResponse_Class{ - Class: cls, - }, - } - if !yield(msg) { - // if caller is not interested in remaining data (example: connection to a peer is closed) exit - // note that in this case we won't send finMsg - return - } - } - - yield(finMsg) - }, nil -} - -func (h *Handler) onClassRangeRequest(req *spec.ClassRangeRequest) (iter.Seq[proto.Message], error) { - if h.snapProvider == nil { - return nil, fmt.Errorf("snapsyncing not supported") - } - - finMsg := &spec.ClassRangeResponse{ - Responses: &spec.ClassRangeResponse_Fin{}, - } - - return func(yield func(message proto.Message) bool) { - // port snap_server here - yield(finMsg) - }, nil -} - -func (h *Handler) onContractRangeRequest(req *spec.ContractRangeRequest) (iter.Seq[proto.Message], error) { - if h.snapProvider == nil { - return nil, fmt.Errorf("snapsyncing not supported") - } - - finMsg := &spec.ContractRangeResponse{ - Responses: &spec.ContractRangeResponse_Fin{}, - } - - return func(yield func(message proto.Message) bool) { - // port snap_server here - yield(finMsg) - }, nil -} - -func (h *Handler) onContractStorageRequest(req *spec.ContractStorageRequest) (iter.Seq[proto.Message], error) { - if h.snapProvider == nil { - return nil, fmt.Errorf("snapsyncing not supported") - } - - finMsg := &spec.ContractStorageResponse{ - Responses: &spec.ContractStorageResponse_Fin{}, - } - return func(yield func(message proto.Message) bool) { - // TODO: adapter method? Do we need separate req/res structs? - srr := &sync.StorageRangeRequest{ - StateRoot: p2p2core.AdaptHash(req.StateRoot), - ChunkPerProof: uint64(req.ChunksPerProof), - Queries: req.Query, - } - stoIter, err := h.snapProvider.SnapServer.GetStorageRange(h.ctx, srr) - if err != nil { - h.log.Errorw("failed to get storage range", "err", err) - return - } - - stoIter(func(result *sync.StorageRangeStreamingResult) bool { - // TODO: again adapter or just return spec types? - res := &spec.ContractStorageResponse{ - StateRoot: req.StateRoot, - ContractAddress: core2p2p.AdaptFelt(result.StorageAddr), - Responses: &spec.ContractStorageResponse_Storage{ - Storage: &spec.ContractStorage{ - KeyValue: result.Range, - }, - }, - } - - if !yield(res) { - return false - } - - return true - }) - - yield(finMsg) - }, nil +type SnapProvider interface { + GetClassRange(request *spec.ClassRangeRequest) (iter.Seq[proto.Message], error) + GetContractRange(request *spec.ContractRangeRequest) (iter.Seq[proto.Message], error) + GetStorageRange(request *spec.ContractStorageRequest) (iter.Seq[proto.Message], error) + GetClasses(request *spec.ClassHashesRequest) (iter.Seq[proto.Message], error) } diff --git a/p2p/starknet/spec/snapshot.pb.go b/p2p/starknet/spec/snapshot.pb.go index f904de747b..fd6ee7f3a6 100644 --- a/p2p/starknet/spec/snapshot.pb.go +++ b/p2p/starknet/spec/snapshot.pb.go @@ -894,7 +894,7 @@ type ContractStorageResponse struct { unknownFields protoimpl.UnknownFields StateRoot *Hash `protobuf:"bytes,1,opt,name=state_root,json=stateRoot,proto3,oneof" json:"state_root,omitempty"` // may not appear if Fin is sent to end the whole response - ContractAddress *Felt252 `protobuf:"bytes,2,opt,name=contract_address,json=contractAddress,proto3,oneof" json:"contract_address,omitempty"` + ContractAddress *Address `protobuf:"bytes,2,opt,name=contract_address,json=contractAddress,proto3,oneof" json:"contract_address,omitempty"` // Types that are assignable to Responses: // // *ContractStorageResponse_Storage @@ -942,7 +942,7 @@ func (x *ContractStorageResponse) GetStateRoot() *Hash { return nil } -func (x *ContractStorageResponse) GetContractAddress() *Felt252 { +func (x *ContractStorageResponse) GetContractAddress() *Address { if x != nil { return x.ContractAddress } @@ -1252,7 +1252,7 @@ var file_p2p_proto_snapshot_proto_rawDesc = []byte{ 0x05, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x48, 0x01, 0x52, 0x09, 0x73, 0x74, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x88, 0x01, 0x01, 0x12, 0x38, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x08, 0x2e, 0x46, 0x65, 0x6c, 0x74, 0x32, 0x35, 0x32, 0x48, 0x02, 0x52, 0x0f, 0x63, 0x6f, + 0x32, 0x08, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x48, 0x02, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x88, 0x01, 0x01, 0x12, 0x2c, 0x0a, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x53, 0x74, 0x6f, 0x72, @@ -1339,7 +1339,7 @@ var file_p2p_proto_snapshot_proto_depIdxs = []int32{ 9, // 31: ContractStorageRequest.query:type_name -> StorageRangeQuery 20, // 32: ContractStorage.keyValue:type_name -> ContractStoredValue 16, // 33: ContractStorageResponse.state_root:type_name -> Hash - 19, // 34: ContractStorageResponse.contract_address:type_name -> Felt252 + 15, // 34: ContractStorageResponse.contract_address:type_name -> Address 11, // 35: ContractStorageResponse.storage:type_name -> ContractStorage 17, // 36: ContractStorageResponse.fin:type_name -> Fin 1, // 37: ContractStorageResponse.range_proof:type_name -> PatriciaRangeProof From ba2ebdd37912f7454fdea3755e2eb2481b0924e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowosielski?= Date: Fri, 6 Sep 2024 18:52:57 +0200 Subject: [PATCH 06/23] fix snap server tests after refactor --- p2p/snap_server.go | 31 ++++-- p2p/snap_server_test.go | 214 +++++++++++++++++++++++++++++++--------- 2 files changed, 190 insertions(+), 55 deletions(-) diff --git a/p2p/snap_server.go b/p2p/snap_server.go index 1810489cb8..142bd58d7a 100644 --- a/p2p/snap_server.go +++ b/p2p/snap_server.go @@ -159,8 +159,11 @@ func (b *snapServer) GetClassRange(request *spec.ClassRangeRequest) (iter.Seq[pr RangeProof: Core2P2pProof(proofs), } - shouldContinue := yield(clsMsg) - if finished || !shouldContinue { + if !yield(clsMsg) { + // we should not send `FinMsg` when the client explicitly asks to stop + return + } + if finished { break } startAddr = classkeys[len(classkeys)-1] @@ -250,8 +253,11 @@ func (b *snapServer) GetContractRange(request *spec.ContractRangeRequest) (iter. }, } - shouldContinue := yield(cntrMsg) - if finished || !shouldContinue { + if !yield(cntrMsg) { + // we should not send `FinMsg` when the client explicitly asks to stop + return + } + if finished { break } } @@ -276,6 +282,8 @@ func (b *snapServer) GetStorageRange(request *spec.ContractStorageRequest) (iter var curNodeLimit uint32 = 1000000 + // shouldContinue is a return value from the yield function which specify whether the iteration should continue + var shouldContinue bool = true for _, query := range request.Query { contractLimit := curNodeLimit @@ -297,7 +305,7 @@ func (b *snapServer) GetStorageRange(request *spec.ContractStorageRequest) (iter }, }, } - if !yield(stoMsg) { + if shouldContinue = yield(stoMsg); !shouldContinue { return false } return true @@ -314,8 +322,10 @@ func (b *snapServer) GetStorageRange(request *spec.ContractStorageRequest) (iter break } } - - yield(finMsg) + if shouldContinue { + // we should `Fin` only when client expects iteration to continue + yield(finMsg) + } }, nil } @@ -326,8 +336,8 @@ func (b *snapServer) GetClasses(request *spec.ClassHashesRequest) (iter.Seq[prot return func(yield yieldFunc) { felts := make([]*felt.Felt, len(request.ClassHashes)) - for _, hash := range request.ClassHashes { - felts = append(felts, p2p2core.AdaptHash(hash)) + for i, hash := range request.ClassHashes { + felts[i] = p2p2core.AdaptHash(hash) } coreClasses, err := b.blockchain.GetClasses(felts) @@ -343,7 +353,8 @@ func (b *snapServer) GetClasses(request *spec.ClassHashesRequest) (iter.Seq[prot }, } if !yield(clsMsg) { - break + // we should not send `FinMsg` when the client explicitly asks to stop + return } } diff --git a/p2p/snap_server_test.go b/p2p/snap_server_test.go index ea85ee4137..5b00cbe982 100644 --- a/p2p/snap_server_test.go +++ b/p2p/snap_server_test.go @@ -1,15 +1,14 @@ package p2p import ( - "context" "fmt" + "github.com/NethermindEth/juno/core" + "github.com/NethermindEth/juno/core/crypto" "testing" "github.com/NethermindEth/juno/adapters/core2p2p" "github.com/NethermindEth/juno/adapters/p2p2core" "github.com/NethermindEth/juno/blockchain" - "github.com/NethermindEth/juno/core" - "github.com/NethermindEth/juno/core/crypto" "github.com/NethermindEth/juno/core/felt" "github.com/NethermindEth/juno/db" "github.com/NethermindEth/juno/db/pebble" @@ -19,8 +18,10 @@ import ( ) func TestClassRange(t *testing.T) { + // Note: set to true to make test super long to complete + shouldFetchAllClasses := false var d db.DB - //t.Skip("DB snapshot is needed for this test") + t.Skip("DB snapshot is needed for this test") d, _ = pebble.NewWithOptions("/Users/pnowosie/juno/snapshots/juno-sepolia", 128000000, 128, false) defer func() { _ = d.Close() }() bc := blockchain.New(d, &utils.Sepolia) // Needed because class loader need encoder to be registered @@ -37,31 +38,50 @@ func TestClassRange(t *testing.T) { } startRange := (&felt.Felt{}).SetUint64(0) + finMsgReceived := false + chunksReceived := 0 chunksPerProof := 150 - var classResult *ClassRangeStreamingResult - iter, err := server.GetClassRange(context.Background(), + iter, err := server.GetClassRange( &spec.ClassRangeRequest{ Root: core2p2p.AdaptHash(stateRoot), Start: core2p2p.AdaptHash(startRange), ChunksPerProof: uint32(chunksPerProof), }) - if err != nil { - fmt.Printf("err %s\n", err) - t.Fatal(err) - } - iter(func(result *ClassRangeStreamingResult) bool { - if result != nil { - classResult = result + assert.NoError(t, err) + + for res := range iter { + assert.NotNil(t, res) + + resT, ok := res.(*spec.ClassRangeResponse) + assert.True(t, ok) + assert.NotNil(t, resT) + + switch v := resT.GetResponses().(type) { + case *spec.ClassRangeResponse_Classes: + assert.True(t, chunksPerProof >= len(v.Classes.Classes)) + classesRoot := p2p2core.AdaptHash(resT.ClassesRoot) + contractsRoot := p2p2core.AdaptHash(resT.ContractsRoot) + verifyErr := VerifyGlobalStateRoot(stateRoot, classesRoot, contractsRoot) + assert.NoError(t, verifyErr) + chunksReceived++ + case *spec.ClassRangeResponse_Fin: + finMsgReceived = true } - return false - }) + if !shouldFetchAllClasses { + break + } + } - assert.NotNil(t, classResult) - assert.Equal(t, chunksPerProof, len(classResult.Range.Classes)) - verifyErr := VerifyGlobalStateRoot(stateRoot, classResult.ClassesRoot, classResult.ContractsRoot) - assert.NoError(t, verifyErr) + if !shouldFetchAllClasses { + assert.Equal(t, 1, chunksReceived) + assert.False(t, finMsgReceived) + } else { + fmt.Printf("ClassesReceived: \t%d\n", chunksReceived) + assert.True(t, finMsgReceived) + assert.True(t, chunksReceived > 1) + } } func TestContractRange(t *testing.T) { @@ -83,10 +103,10 @@ func TestContractRange(t *testing.T) { } startRange := (&felt.Felt{}).SetUint64(0) + chunksReceived := 0 chunksPerProof := 150 - var contractResult *ContractRangeStreamingResult - ctrIter, err := server.GetContractRange(context.Background(), + ctrIter, err := server.GetContractRange( &spec.ContractRangeRequest{ StateRoot: core2p2p.AdaptHash(stateRoot), Start: core2p2p.AdaptAddress(startRange), @@ -94,23 +114,61 @@ func TestContractRange(t *testing.T) { }) assert.NoError(t, err) - ctrIter(func(result *ContractRangeStreamingResult) bool { - if err != nil { - fmt.Printf("err %s\n", err) - t.Fatal(err) + for res := range ctrIter { + assert.NotNil(t, res) + + resT, ok := res.(*spec.ContractRangeResponse) + assert.True(t, ok) + assert.NotNil(t, resT) + + switch v := resT.GetResponses().(type) { + case *spec.ContractRangeResponse_Range: + assert.True(t, chunksPerProof == len(v.Range.State)) + classesRoot := p2p2core.AdaptHash(resT.ClassesRoot) + contractsRoot := p2p2core.AdaptHash(resT.ContractsRoot) + verifyErr := VerifyGlobalStateRoot(stateRoot, classesRoot, contractsRoot) + assert.NoError(t, verifyErr) + chunksReceived++ + default: + // we expect no any other message only just one range because we break the iteration + t.Fatal("received unexpected message", "type", v) } - if result != nil { - contractResult = result - } + // we don't need to fetch all contracts + break + } + + assert.Equal(t, 1, chunksReceived) +} - return false - }) +func TestContractRange_FinMsg_Received(t *testing.T) { + // TODO: Fix the test so it demonstrated FinMsg is returned at the iteration end + t.Skip("Fix me") + var d db.DB = pebble.NewMemTest(t) + bc := blockchain.New(d, &utils.Sepolia) + defer bc.Close() + server := &snapServer{blockchain: bc} - assert.NotNil(t, contractResult) - assert.Equal(t, chunksPerProof, len(contractResult.Range)) - verifyErr := VerifyGlobalStateRoot(stateRoot, contractResult.ClassesRoot, contractResult.ContractsRoot) - assert.NoError(t, verifyErr) + zero := new(felt.Felt).SetUint64(0) + iter, err := server.GetContractRange( + &spec.ContractRangeRequest{ + StateRoot: core2p2p.AdaptHash(zero), + Start: core2p2p.AdaptAddress(zero), + ChunksPerProof: uint32(10), + }) + assert.NoError(t, err) + fmt.Printf("All Good!\n") + + finMsgReceived := false + for res := range iter { + assert.NotNil(t, res) + resT, ok := res.(*spec.ContractRangeResponse) + assert.True(t, ok) + assert.NotNil(t, resT) + assert.IsType(t, spec.ContractRangeResponse_Fin{}, resT) + finMsgReceived = true + } + assert.True(t, finMsgReceived) } func TestContractStorageRange(t *testing.T) { @@ -157,10 +215,10 @@ func TestContractStorageRange(t *testing.T) { for _, test := range tests { t.Run(fmt.Sprintf("%.7s...", test.address), func(t *testing.T) { - request := &StorageRangeRequest{ - StateRoot: stateRoot, - ChunkPerProof: 100, - Queries: []*spec.StorageRangeQuery{ + request := &spec.ContractStorageRequest{ + StateRoot: core2p2p.AdaptHash(stateRoot), + ChunksPerProof: 100, + Query: []*spec.StorageRangeQuery{ { Address: core2p2p.AdaptAddress(test.address), Start: &spec.StorageLeafQuery{ @@ -174,19 +232,29 @@ func TestContractStorageRange(t *testing.T) { keys := make([]*felt.Felt, 0, test.expectedLeaves) vals := make([]*felt.Felt, 0, test.expectedLeaves) - stoIter, err := server.GetStorageRange(context.Background(), request) + stoIter, err := server.GetStorageRange(request) assert.NoError(t, err) - stoIter(func(result *StorageRangeStreamingResult) bool { - if result != nil { - for _, r := range result.Range { + finMsgReceived := false + for res := range stoIter { + assert.NotNil(t, res) + resT, ok := res.(*spec.ContractStorageResponse) + assert.True(t, ok) + assert.NotNil(t, resT) + + switch v := resT.GetResponses().(type) { + case *spec.ContractStorageResponse_Storage: + assert.False(t, finMsgReceived) + for _, r := range v.Storage.KeyValue { keys = append(keys, p2p2core.AdaptFelt(r.Key)) vals = append(vals, p2p2core.AdaptFelt(r.Value)) } + case *spec.ContractStorageResponse_Fin: + // we expect just one fin message at the iteration end + finMsgReceived = true } - - return true - }) + } + assert.True(t, finMsgReceived) fmt.Println("Address:", test.address, "storage length:", len(keys)) assert.Equal(t, test.expectedLeaves, len(keys)) @@ -198,6 +266,62 @@ func TestContractStorageRange(t *testing.T) { } } +func TestGetClassesByHash(t *testing.T) { + var d db.DB + t.Skip("DB snapshot is needed for this test") + d, _ = pebble.NewWithOptions("/Users/pnowosie/juno/snapshots/juno-sepolia", 128000000, 128, false) + defer func() { _ = d.Close() }() + bc := blockchain.New(d, &utils.Sepolia) // Needed because class loader need encoder to be registered + + b, err := bc.Head() + assert.NoError(t, err) + + fmt.Printf("headblock %d\n", b.Number) + + server := &snapServer{ + blockchain: bc, + } + + hashes := []*spec.Hash{ + // Block , type v0 + core2p2p.AdaptHash(feltFromString("0x7db5c2c2676c2a5bfc892ee4f596b49514e3056a0eee8ad125870b4fb1dd909")), + // Block , type v0 + core2p2p.AdaptHash(feltFromString("0x28d1671fb74ecb54d848d463cefccffaef6df3ae40db52130e19fe8299a7b43")), + // Block , type v0 + core2p2p.AdaptHash(feltFromString("0x772164c9d6179a89e7f1167f099219f47d752304b16ed01f081b6e0b45c93c3")), + // Block , type v0 + core2p2p.AdaptHash(feltFromString("0x78401746828463e2c3f92ebb261fc82f7d4d4c8d9a80a356c44580dab124cb0")), + } + + finMsgReceived := false + iter, err := server.GetClasses( + &spec.ClassHashesRequest{ + ClassHashes: hashes, + }) + assert.NoError(t, err) + + i := 0 + for res := range iter { + assert.NotNil(t, res) + + resT, ok := res.(*spec.ClassesResponse) + assert.True(t, ok) + assert.NotNil(t, resT) + + switch v := resT.GetClassMessage().(type) { + case *spec.ClassesResponse_Class: + assert.True(t, i < len(hashes)) + assert.Equal(t, v.Class.GetClassHash(), hashes[i]) + case *spec.ClassesResponse_Fin: + assert.Equal(t, len(hashes), i) + finMsgReceived = true + } + + i++ + } + assert.True(t, finMsgReceived) +} + func feltFromString(str string) *felt.Felt { f, err := (&felt.Felt{}).SetString(str) if err != nil { From b8fca3a33b04b88cb3039aef69737b7a9d3e7403 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowosielski?= Date: Mon, 9 Sep 2024 11:46:35 +0200 Subject: [PATCH 07/23] refactor syncer to loop over iterator, add client message handling --- p2p/snap_syncer.go | 191 ++++++++++++++++++++++++--------------------- 1 file changed, 104 insertions(+), 87 deletions(-) diff --git a/p2p/snap_syncer.go b/p2p/snap_syncer.go index cb8930cacd..d4a08136e5 100644 --- a/p2p/snap_syncer.go +++ b/p2p/snap_syncer.go @@ -509,25 +509,23 @@ func (s *SnapSyncher) runFetchClassJob(ctx context.Context) error { classIter, err := s.client.RequestClassesByKeys(ctx, &spec.ClassHashesRequest{ ClassHashes: hashes, }) - if err != nil { s.log.Errorw("error getting class from client", "err", err) - return err + continue } classes := make([]*spec.Class, len(keyBatches)) - classIter(func(response *spec.ClassesResponse) bool { + for response := range classIter { switch v := response.ClassMessage.(type) { case *spec.ClassesResponse_Class: classes = append(classes, v.Class) - return true case *spec.ClassesResponse_Fin: - return false + s.log.Infow("class batch completed", "classes", len(classes)) + break default: s.log.Warnw("Unexpected ClassMessage from getClasses", "v", v) - return false } - }) + } processedClasses := map[felt.Felt]bool{} newClasses := map[felt.Felt]core.Class{} @@ -586,8 +584,6 @@ func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { completed := false for !completed { - var outherErr error - stateRoot := s.currentGlobalStateRoot iter, err := s.client.RequestContractRange(ctx, &spec.ContractRangeRequest{ Domain: 0, // What do this do? @@ -597,32 +593,47 @@ func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { ChunksPerProof: uint32(contractRangeChunkPerProof), }) if err != nil { - return err + s.log.Errorw("error getting contract range from client", "err", err) + continue } - iter(func(response *spec.ContractRangeResponse) bool { + for response := range iter { if response == nil { - return false + s.log.Errorw("contract range respond with nil response") + continue } + s.log.Infow("snap range progress", "progress", calculatePercentage(startAddr), "addr", startAddr) rangeProgress.Set(float64(calculatePercentage(startAddr))) - crange := response.GetRange() + var crange *spec.ContractRange + switch v := response.GetResponses().(type) { + case *spec.ContractRangeResponse_Range: + crange = v.Range + case *spec.ContractRangeResponse_Fin: + s.log.Infow("[finMsg] contract range completed") + break + default: + s.log.Warnw("Unexpected contract range message", "GetResponses", v) + continue + } + if crange == nil || crange.State == nil { - return false + s.log.Errorw("contract range respond with nil state") + continue } if response.RangeProof == nil { - return false + s.log.Errorw("contract range respond with nil proof") + continue } classRoot := p2p2core.AdaptHash(response.ClassesRoot) contractRoot := p2p2core.AdaptHash(response.ContractsRoot) err := VerifyGlobalStateRoot(stateRoot, classRoot, contractRoot) - outherErr = err if err != nil { // Root verification failed // TODO: Ban peer - return false + return err } paths := make([]*felt.Felt, len(crange.State)) @@ -635,10 +646,9 @@ func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { proofs := P2pProofToTrieProofs(response.RangeProof) hasNext, err := VerifyTrie(contractRoot, paths, values, proofs, core.GlobalTrieHeight, crypto.Pedersen) - outherErr = err if err != nil { // The peer should get penalised in this case - return false + return err } classes := []*felt.Felt{} @@ -650,9 +660,8 @@ func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { } err = s.blockchain.PutContracts(paths, nonces, classes) - outherErr = err if err != nil { - fmt.Printf("%s\n", err) + fmt.Printf("Unable to update the chain %s\n", err) panic(err) } @@ -665,38 +674,29 @@ func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { nonce := r.Nonce err = s.queueClassJob(ctx, classHash) - outherErr = err if err != nil { - return false + s.log.Errorw("error queue class fetch job", "err", err) + return err } err = s.queueStorageRangeJob(ctx, path, storageRoot, classHash, nonce) - outherErr = err if err != nil { - return false + s.log.Errorw("error queue storage refresh job", "err", err) + return err } } if !hasNext { - s.log.Infow("address range completed") + s.log.Infow("[hasNext] contract range completed") completed = true - return false + return nil } if len(paths) == 0 { - return false + return nil } startAddr = paths[len(paths)-1] - return true - }) - - if outherErr != nil { - s.log.Errorw("Error with contract range", "err", outherErr) - // TODO: address the error properly - // Well... need to figure out how to determine if its a temporary error or not. - // For sure, the state root can be outdated, so this need to restart - return outherErr } } @@ -747,8 +747,6 @@ func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) }) } - var err error - stateRoot := s.currentGlobalStateRoot processedJobs := 0 storage := map[felt.Felt]map[felt.Felt]*felt.Felt{} @@ -765,20 +763,40 @@ func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) ChunksPerProof: uint32(storageRangeChunkPerProof), Query: requests, }) + if err != nil { + s.log.Errorw("Error with storage range request", "err", err) + // Well... need to figure out how to determine if its a temporary error or not. + // For sure, the state root can be outdated, so this need to restart + continue + } - iter(func(response *spec.ContractStorageResponse) bool { + for response := range iter { if response == nil { - return false + s.log.Errorw("storage range respond with nil response") + continue } - response.GetResponses() - csto := response.GetStorage() + + var csto *spec.ContractStorage + switch v := response.GetResponses().(type) { + case *spec.ContractStorageResponse_Storage: + csto = v.Storage + case *spec.ContractStorageResponse_Fin: + s.log.Infow("storage range completed", "totalPath", totalPath) + break + default: + s.log.Warnw("Unexpected storage range message", "GetResponses", v) + continue + } + if csto == nil || csto.KeyValue == nil { - return false + s.log.Errorw("storage range respond with nil storage") + continue } storageRange := csto.KeyValue if response.RangeProof == nil { - return false + s.log.Errorw("storage range respond with nil proof") + continue } job := jobs[processedJobs] @@ -787,7 +805,7 @@ func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) if !job.path.Equal(storageAddr) { s.log.Errorw(fmt.Sprintf( "storage addr differ %s %s %d\n", job.path, storageAddr, workerIdx)) - return false + continue } // Validate response @@ -805,12 +823,13 @@ func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) // It is unclear how to distinguish if the peer is malicious/broken/non-bizantine or the contracts root is outdated. err = s.queueStorageRefreshJob(ctx, job) if err != nil { - return false + s.log.Errorw("error queue storage refresh job", "err", err) + return err } // Go to next contract processedJobs++ - return true + continue } if storage[*job.path] == nil { @@ -832,7 +851,7 @@ func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) err = s.blockchain.PutStorage(storage) if err != nil { s.log.Errorw("error store", "err", err) - return false + return err } totalPath = 0 @@ -846,15 +865,6 @@ func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) processedJobs++ atomic.AddInt32(&s.storageRangeJobCount, -1) // its... done? } - - return true - }) - - if err != nil { - s.log.Errorw("Error with storage range", "err", err) - // Well... need to figure out how to determine if its a temporary error or not. - // For sure, the state root can be outdated, so this need to restart - continue } storageAddressCount.Observe(float64(len(storage))) @@ -906,7 +916,6 @@ func (s *SnapSyncher) runStorageRefreshWorker(ctx context.Context) error { bigIntAdd = (&big.Int{}).Add(bigIntAdd, big.NewInt(1)) elem := fp.NewElement(0) limitAddr := felt.NewFelt((&elem).SetBigInt(bigIntAdd)) - var outherErr error stateRoot := s.currentGlobalStateRoot ctrIter, err := s.client.RequestContractRange(ctx, &spec.ContractRangeRequest{ @@ -917,29 +926,44 @@ func (s *SnapSyncher) runStorageRefreshWorker(ctx context.Context) error { ChunksPerProof: 10000, }) if err != nil { - return err + s.log.Errorw("Error with contract range request", "err", err) + continue } - ctrIter(func(response *spec.ContractRangeResponse) bool { + for response := range ctrIter { if response == nil { - return false + s.log.Errorw("contract range [storage refresh] respond with nil response") + continue } - crange := response.GetRange() - if crange == nil || crange.State == nil || len(crange.State) == 0 { - return false + var crange *spec.ContractRange + switch v := response.GetResponses().(type) { + case *spec.ContractRangeResponse_Range: + crange = v.Range + case *spec.ContractRangeResponse_Fin: + s.log.Infow("[finMsg] contract range [storage refresh] completed") + break + default: + s.log.Warnw("Unexpected contract range message [storage refresh]", "GetResponses", v) + continue + } + + if crange == nil || crange.State == nil { + s.log.Errorw("contract range [storage refresh] respond with nil state") + continue } if response.RangeProof == nil { - return false + s.log.Errorw("contract range [storage refresh] respond with nil proof") + continue } classRoot := p2p2core.AdaptHash(response.ClassesRoot) contractRoot := p2p2core.AdaptHash(response.ContractsRoot) - outherErr = VerifyGlobalStateRoot(stateRoot, classRoot, contractRoot) - if outherErr != nil { + err := VerifyGlobalStateRoot(stateRoot, classRoot, contractRoot) + if err != nil { // Root verification failed // TODO: Ban peer - return false + return err } paths := make([]*felt.Felt, len(crange.State)) @@ -951,36 +975,29 @@ func (s *SnapSyncher) runStorageRefreshWorker(ctx context.Context) error { } proofs := P2pProofToTrieProofs(response.RangeProof) - _, outherErr = VerifyTrie(contractRoot, paths, values, proofs, core.GlobalTrieHeight, crypto.Pedersen) - if outherErr != nil { + _, err = VerifyTrie(contractRoot, paths, values, proofs, core.GlobalTrieHeight, crypto.Pedersen) + if err != nil { // The peer should get penalised in this case - return false + return err } job.storageRoot = p2p2core.AdaptHash(crange.State[0].Storage) newClass := p2p2core.AdaptHash(crange.State[0].Storage) if newClass != job.classHash { - outherErr = s.queueClassJob(ctx, newClass) - if outherErr != nil { - return false + err = s.queueClassJob(ctx, newClass) + if err != nil { + s.log.Errorw("error queue class fetch job", "err", err) + return err } } - outherErr = s.queueStorageRangeJobJob(ctx, job) - if outherErr != nil { - return false + err = s.queueStorageRangeJobJob(ctx, job) + if err != nil { + s.log.Errorw("error queue storage refresh job", "err", err) + return err } job = nil - - return true - }) - - if outherErr != nil { - s.log.Errorw("Error with contract range", "err", outherErr) - // Well... need to figure out how to determine if its a temporary error or not. - // For sure, the state root can be outdated, so this need to restart - continue } } } From 7ebc6338c21a200f3d84c594cf0bd064ec48bab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowosielski?= Date: Tue, 10 Sep 2024 17:27:33 +0200 Subject: [PATCH 08/23] p2p complete - no data exchange --- Makefile | 2 +- blockchain/snap_server_interface.go | 24 +++++- node/node.go | 9 ++ p2p/p2p.go | 26 +++++- p2p/snap_server.go | 22 +++-- p2p/snap_syncer.go | 125 ++++++++++++++++++---------- p2p/starknet/handlers.go | 8 +- p2p/starknetdata.go | 67 +++++++++++++++ 8 files changed, 225 insertions(+), 58 deletions(-) create mode 100644 p2p/starknetdata.go diff --git a/Makefile b/Makefile index 951eb6d93c..91ae02dcaf 100644 --- a/Makefile +++ b/Makefile @@ -117,7 +117,7 @@ feedernode: juno-cached ./build/juno \ --network=sepolia \ --log-level=debug \ - --db-path=./p2p-dbs/feedernode \ + --db-path="/Users/pnowosie/juno/snapshots/juno-sepolia" \ --p2p \ --p2p-feeder-node \ --p2p-addr=/ip4/0.0.0.0/tcp/7777 \ diff --git a/blockchain/snap_server_interface.go b/blockchain/snap_server_interface.go index e06e3b6c7a..725062e89c 100644 --- a/blockchain/snap_server_interface.go +++ b/blockchain/snap_server_interface.go @@ -1,12 +1,14 @@ package blockchain import ( + "context" "errors" "fmt" - "github.com/NethermindEth/juno/core" "github.com/NethermindEth/juno/core/felt" "github.com/NethermindEth/juno/db" + "github.com/NethermindEth/juno/service" + "github.com/NethermindEth/juno/utils" ) const MaxSnapshots = 128 @@ -162,3 +164,23 @@ func (b *Blockchain) Close() { _ = snapshot.closer() } } + +type Blockchain_Closer struct { + log utils.SimpleLogger + bc *Blockchain +} + +var _ service.Service = (*Blockchain_Closer)(nil) + +func NewBlockchainCloser(bc *Blockchain, log utils.SimpleLogger) *Blockchain_Closer { + return &Blockchain_Closer{log, bc} +} + +func (b *Blockchain_Closer) Run(ctx context.Context) error { + b.log.Infow("Blockchain_Closer has started") + + <-ctx.Done() + b.bc.Close() + b.log.Infow("Blockchain_Closer has stopped") + return nil +} diff --git a/node/node.go b/node/node.go index 38df2d9d16..b8af111e98 100644 --- a/node/node.go +++ b/node/node.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "net/url" + "os" "reflect" "runtime" "time" @@ -130,6 +131,9 @@ func New(cfg *Config, version string) (*Node, error) { //nolint:gocyclo,funlen chain := blockchain.New(database, &cfg.Network) + //TODO: close a blockchain? better way? + services = append(services, blockchain.NewBlockchainCloser(chain, log)) + // Verify that cfg.Network is compatible with the database. head, err := chain.Head() if err != nil && !errors.Is(err, db.ErrKeyNotFound) { @@ -179,6 +183,10 @@ func New(cfg *Config, version string) (*Node, error) { //nolint:gocyclo,funlen // Do not start the feeder synchronisation synchronizer = nil } + if os.Getenv("JUNO_P2P_NO_SYNC") != "" { + log.Warnw("Got 'JUNO_P2P_NO_SYNC' to not syncing from p2p network") + synchronizer = nil + } p2pService, err = p2p.New(cfg.P2PAddr, cfg.P2PPublicAddr, version, cfg.P2PPeers, cfg.P2PPrivateKey, cfg.P2PFeederNode, chain, &cfg.Network, log, database) if err != nil { @@ -376,6 +384,7 @@ func (n *Node) Run(ctx context.Context) { } <-ctx.Done() + //TODO: chain.Close() - which service should do this? n.log.Infow("Shutting down Juno...") } diff --git a/p2p/p2p.go b/p2p/p2p.go index 49633f49ee..0115049f01 100644 --- a/p2p/p2p.go +++ b/p2p/p2p.go @@ -5,7 +5,9 @@ import ( "encoding/hex" "errors" "fmt" + "github.com/NethermindEth/juno/service" "math/rand" + "os" "strings" "sync" "time" @@ -50,6 +52,8 @@ type Service struct { synchroniser *syncService gossipTracer *gossipTracer + snapSyncher service.Service + //snapServer *snapServer feederNode bool database db.DB @@ -147,18 +151,21 @@ func NewWithHost(p2phost host.Host, peers string, feederNode bool, bc *blockchai return nil, err } - // todo: reconsider initialising synchroniser here because if node is a feedernode we shouldn't not create an instance of it. + // todo: reconsider initialising synchroniser here because if node is a feedernode we should not create an instance of it. synchroniser := newSyncService(bc, p2phost, snNetwork, log) + handler := starknet.NewHandler(bc, log) + handler.WithSnapsyncSupport(NewSnapServer(bc, log)) s := &Service{ synchroniser: synchroniser, + snapSyncher: NewSnapSyncer(synchroniser, bc, log), log: log, host: p2phost, network: snNetwork, dht: p2pdht, feederNode: feederNode, topics: make(map[string]*pubsub.Topic), - handler: starknet.NewHandler(bc, log), + handler: handler, database: database, } return s, nil @@ -268,7 +275,16 @@ func (s *Service) Run(ctx context.Context) error { s.setProtocolHandlers() if !s.feederNode { - s.synchroniser.start(ctx) + //s.synchroniser.start(ctx) + if os.Getenv("JUNO_P2P_NO_SYNC") == "" { + err := s.snapSyncher.Run(ctx) + if err != nil { + s.log.Errorw("Snapsyncer failed to start") + return err + } + } else { + s.log.Infow("Syncing is disabled") + } } <-ctx.Done() @@ -287,6 +303,10 @@ func (s *Service) setProtocolHandlers() { s.SetProtocolHandler(starknet.TransactionsPID(), s.handler.TransactionsHandler) s.SetProtocolHandler(starknet.ClassesPID(), s.handler.ClassesHandler) s.SetProtocolHandler(starknet.StateDiffPID(), s.handler.StateDiffHandler) + s.SetProtocolHandler(starknet.SnapshotClassRangePID(), s.handler.ClassRangeHandler) + s.SetProtocolHandler(starknet.SnapshotContractRangePID(), s.handler.ContractRangeHandler) + s.SetProtocolHandler(starknet.SnapshotContractStorageRangePID(), s.handler.ContractStorageHandler) + s.SetProtocolHandler(starknet.SnapshotClassesPID(), s.handler.ClassHashesHandler) } func (s *Service) callAndLogErr(f func() error, msg string) { diff --git a/p2p/snap_server.go b/p2p/snap_server.go index 142bd58d7a..50c50d4062 100644 --- a/p2p/snap_server.go +++ b/p2p/snap_server.go @@ -1,6 +1,7 @@ package p2p import ( + "github.com/NethermindEth/juno/utils" "google.golang.org/protobuf/proto" "math/big" @@ -60,13 +61,15 @@ type yieldFunc = func(proto.Message) bool var _ SnapServerBlockchain = (*blockchain.Blockchain)(nil) -func NewSnapServer(blockchain SnapServerBlockchain) SnapServer { +func NewSnapServer(blockchain SnapServerBlockchain, log utils.SimpleLogger) SnapServer { return &snapServer{ + log: log, blockchain: blockchain, } } type snapServer struct { + log utils.SimpleLogger blockchain SnapServerBlockchain } @@ -92,9 +95,11 @@ func (b *snapServer) GetClassRange(request *spec.ClassRangeRequest) (iter.Seq[pr Responses: &spec.ClassRangeResponse_Fin{}, } - return func(yield yieldFunc) { - stateRoot := p2p2core.AdaptHash(request.Root) + stateRoot := p2p2core.AdaptHash(request.Root) + startAddr := p2p2core.AdaptHash(request.Start) + b.log.Debugw("GetClassRange", "root", stateRoot.String(), "start", startAddr.String(), "chunks", request.ChunksPerProof) + return func(yield yieldFunc) { s, err := b.blockchain.GetStateForStateRoot(stateRoot) if err != nil { log.Error("error getting state for state root", "err", err) @@ -159,6 +164,7 @@ func (b *snapServer) GetClassRange(request *spec.ClassRangeRequest) (iter.Seq[pr RangeProof: Core2P2pProof(proofs), } + b.log.Infow("sending class range response", "len(classes)", len(classkeys)) if !yield(clsMsg) { // we should not send `FinMsg` when the client explicitly asks to stop return @@ -170,6 +176,7 @@ func (b *snapServer) GetClassRange(request *spec.ClassRangeRequest) (iter.Seq[pr } yield(finMsg) + b.log.Infow("class range iteration completed") }, nil } @@ -177,10 +184,11 @@ func (b *snapServer) GetContractRange(request *spec.ContractRangeRequest) (iter. var finMsg proto.Message = &spec.ContractRangeResponse{ Responses: &spec.ContractRangeResponse_Fin{}, } + stateRoot := p2p2core.AdaptHash(request.StateRoot) + startAddr := p2p2core.AdaptAddress(request.Start) + b.log.Debugw("GetContractRange", "root", stateRoot.String(), "start", startAddr.String(), "chunks", request.ChunksPerProof) return func(yield yieldFunc) { - stateRoot := p2p2core.AdaptHash(request.StateRoot) - s, err := b.blockchain.GetStateForStateRoot(stateRoot) if err != nil { log.Error("error getting state for state root", "err", err) @@ -253,6 +261,7 @@ func (b *snapServer) GetContractRange(request *spec.ContractRangeRequest) (iter. }, } + b.log.Infow("sending contract range response", "len(states)", len(states)) if !yield(cntrMsg) { // we should not send `FinMsg` when the client explicitly asks to stop return @@ -263,6 +272,7 @@ func (b *snapServer) GetContractRange(request *spec.ContractRangeRequest) (iter. } yield(finMsg) + b.log.Infow("contract range iteration completed") }, nil } @@ -270,6 +280,7 @@ func (b *snapServer) GetStorageRange(request *spec.ContractStorageRequest) (iter var finMsg proto.Message = &spec.ContractStorageResponse{ Responses: &spec.ContractStorageResponse_Fin{}, } + b.log.Debugw("GetStorageRange", "root", request.StateRoot.String(), "start[0]", request.Query[0].Start.Key.String()) return func(yield yieldFunc) { stateRoot := p2p2core.AdaptHash(request.StateRoot) @@ -333,6 +344,7 @@ func (b *snapServer) GetClasses(request *spec.ClassHashesRequest) (iter.Seq[prot var finMsg proto.Message = &spec.ClassesResponse{ ClassMessage: &spec.ClassesResponse_Fin{}, } + b.log.Debugw("GetClasses", "len(hashes)", len(request.ClassHashes)) return func(yield yieldFunc) { felts := make([]*felt.Felt, len(request.ClassHashes)) diff --git a/p2p/snap_syncer.go b/p2p/snap_syncer.go index d4a08136e5..2724519f04 100644 --- a/p2p/snap_syncer.go +++ b/p2p/snap_syncer.go @@ -39,11 +39,11 @@ type Blockchain interface { var _ Blockchain = (*blockchain.Blockchain)(nil) type SnapSyncher struct { - baseSync service.Service + baseSync *syncService starknetData starknetdata.StarknetData - client starknet.Client + client *starknet.Client blockchain Blockchain - log utils.Logger + log utils.SimpleLogger startingBlock *core.Header lastBlock *core.Header @@ -64,6 +64,8 @@ type SnapSyncher struct { mtxL *sync.Mutex } +var _ service.Service = (*SnapSyncher)(nil) + type storageRangeJob struct { path *felt.Felt storageRoot *felt.Felt @@ -73,18 +75,14 @@ type storageRangeJob struct { } func NewSnapSyncer( - baseSyncher service.Service, - consensus starknetdata.StarknetData, - client starknet.Client, + baseSyncher *syncService, bc *blockchain.Blockchain, - log utils.Logger, + log utils.SimpleLogger, ) *SnapSyncher { return &SnapSyncher{ - baseSync: baseSyncher, - starknetData: consensus, - client: client, - blockchain: bc, - log: log, + baseSync: baseSyncher, + blockchain: bc, + log: log, } } @@ -143,13 +141,23 @@ func (s *SnapSyncher) Run(ctx context.Context) error { // 6. Probably download old state updato/bodies too // 7. Send back control to base sync. + // TODO: hacky client + if s.baseSync == nil { + panic("can't start snap syncer without base syncer") + } + s.client = s.baseSync.Client() + s.starknetData = &MockStarkData{} + err := s.runPhase1(ctx) if err != nil { return err } s.log.Infow("delegating to standard synchronizer") - return s.baseSync.Run(ctx) + + return nil + // TODO: start p2p syncer + //s.baseSync.start(ctx) } //nolint:gocyclo,nolintlint @@ -321,6 +329,7 @@ func (s *SnapSyncher) initState(ctx context.Context) error { s.mtxN = &sync.Mutex{} s.mtxL = &sync.Mutex{} + s.log.Infow("init state completed", "startingBlock", s.startingBlock.Number) return nil } @@ -341,13 +350,11 @@ func (s *SnapSyncher) runClassRangeWorker(ctx context.Context) error { totaladded := 0 completed := false startAddr := &felt.Zero - for !completed { - s.log.Infow("class range progress", "progress", calculatePercentage(startAddr)) + s.log.Infow("class range worker entering infinite loop") + for !completed { stateRoot := s.currentGlobalStateRoot - s.log.Infow("class range state root", "stateroot", stateRoot) - // TODO: Maybe timeout classIter, err := s.client.RequestClassRange(ctx, &spec.ClassRangeRequest{ Root: core2p2p.AdaptHash(stateRoot), @@ -355,23 +362,38 @@ func (s *SnapSyncher) runClassRangeWorker(ctx context.Context) error { ChunksPerProof: uint32(classRangeChunksPerProof), }) if err != nil { - return err + s.log.Errorw("error getting classes from client", "err", err) + continue } - classIter(func(response *spec.ClassRangeResponse) bool { + for response := range classIter { if response == nil { - // State root missing. - return false + if response == nil { + s.log.Errorw("contract range respond with nil response") + continue + } + } + + var classes []*spec.Class + switch v := response.GetResponses().(type) { + case *spec.ClassRangeResponse_Classes: + classes = v.Classes.Classes + case *spec.ClassRangeResponse_Fin: + s.log.Infow("[finMsg] class range completed") + break + default: + s.log.Warnw("Unexpected class range message", "GetResponses", v) + continue } - classWrp := response.GetClasses() - if classWrp == nil || classWrp.Classes == nil { - // State root missing. - return false + s.log.Infow("class range worker received response", "classes", len(classes)) + + if classes == nil || len(classes) == 0 { + s.log.Errorw("class range respond with empty classes") + continue } - classes := classWrp.Classes if response.RangeProof == nil { - // Proofs missing. - return false + s.log.Errorw("class range respond with nil proof") + continue } s.log.Infow("got", "res", len(classes), "startAdr", startAddr) @@ -380,16 +402,20 @@ func (s *SnapSyncher) runClassRangeWorker(ctx context.Context) error { contractRoot := p2p2core.AdaptHash(response.ContractsRoot) err = VerifyGlobalStateRoot(stateRoot, classRoot, contractRoot) if err != nil { - s.log.Infow("global state root verification failure") // Root verification failed // TODO: Ban peer - return false + s.log.Errorw("global state root verification failure", "err", err) + return err } + s.log.Infow("class range progress", "progress", calculatePercentage(startAddr)) + s.log.Infow("class range state root", "stateroot", stateRoot) + if classRoot.Equal(&felt.Zero) { // Special case, no V1 at all + s.log.Infow("class range completed", "totalClass", totaladded) completed = true - return false + return nil } paths := make([]*felt.Felt, len(classes)) @@ -414,15 +440,16 @@ func (s *SnapSyncher) runClassRangeWorker(ctx context.Context) error { err = egrp.Wait() if err != nil { - return false + s.log.Infow("class range adaptation failure", "err", err) + return err } proofs := P2pProofToTrieProofs(response.RangeProof) hasNext, err := VerifyTrie(classRoot, paths, values, proofs, core.GlobalTrieHeight, crypto.Poseidon) if err != nil { - // Root verification failed // TODO: Ban peer - return false + s.log.Infow("trie verification failed", "err", err) + return err } // Ingest @@ -435,26 +462,22 @@ func (s *SnapSyncher) runClassRangeWorker(ctx context.Context) error { err = s.blockchain.PutClasses(s.lastBlock.Number, coreClassesHashMap, coreClassesMap) if err != nil { - return false + fmt.Printf("Unable to update the chain %s\n", err) + panic(err) } if !hasNext { s.log.Infow("class range completed", "totalClass", totaladded) completed = true - return false + return nil } // Increment addr, start loop again startAddr = paths[len(paths)-1] - - return true - }) - - if err != nil { - return err } } + s.log.Infow("class range worker exits infinite loop") return nil } @@ -510,12 +533,17 @@ func (s *SnapSyncher) runFetchClassJob(ctx context.Context) error { ClassHashes: hashes, }) if err != nil { - s.log.Errorw("error getting class from client", "err", err) + s.log.Errorw("error getting classes from client", "err", err) continue } classes := make([]*spec.Class, len(keyBatches)) for response := range classIter { + if response == nil { + s.log.Errorw("class by keys respond with nil response") + continue + } + switch v := response.ClassMessage.(type) { case *spec.ClassesResponse_Class: classes = append(classes, v.Class) @@ -526,6 +554,7 @@ func (s *SnapSyncher) runFetchClassJob(ctx context.Context) error { s.log.Warnw("Unexpected ClassMessage from getClasses", "v", v) } } + s.log.Infow("class fetch job received response", "classes", len(classes)) processedClasses := map[felt.Felt]bool{} newClasses := map[felt.Felt]core.Class{} @@ -583,6 +612,7 @@ func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { startAddr := &felt.Zero completed := false + s.log.Infow("contract range worker entering infinite loop") for !completed { stateRoot := s.currentGlobalStateRoot iter, err := s.client.RequestContractRange(ctx, &spec.ContractRangeRequest{ @@ -617,6 +647,7 @@ func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { s.log.Warnw("Unexpected contract range message", "GetResponses", v) continue } + s.log.Infow("contract range worker received response", "states", len(crange.State)) if crange == nil || crange.State == nil { s.log.Errorw("contract range respond with nil state") @@ -631,8 +662,8 @@ func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { contractRoot := p2p2core.AdaptHash(response.ContractsRoot) err := VerifyGlobalStateRoot(stateRoot, classRoot, contractRoot) if err != nil { - // Root verification failed // TODO: Ban peer + s.log.Errorw("global state root verification failure", "err", err) return err } @@ -648,6 +679,7 @@ func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { hasNext, err := VerifyTrie(contractRoot, paths, values, proofs, core.GlobalTrieHeight, crypto.Pedersen) if err != nil { // The peer should get penalised in this case + s.log.Infow("trie verification failed", "err", err) return err } @@ -699,6 +731,7 @@ func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { startAddr = paths[len(paths)-1] } } + s.log.Infow("contract range worker exits infinite loop") return nil } @@ -889,6 +922,7 @@ func (s *SnapSyncher) runStorageRefreshWorker(ctx context.Context) error { // creative. This does mean that this is impressively inefficient. var job *storageRangeJob + s.log.Infow("storage refresh worker entering infinite loop") for { if job == nil { requestloop: @@ -947,6 +981,7 @@ func (s *SnapSyncher) runStorageRefreshWorker(ctx context.Context) error { s.log.Warnw("Unexpected contract range message [storage refresh]", "GetResponses", v) continue } + s.log.Infow("storage refresh worker received response", "states", len(crange.State)) if crange == nil || crange.State == nil { s.log.Errorw("contract range [storage refresh] respond with nil state") @@ -1000,6 +1035,8 @@ func (s *SnapSyncher) runStorageRefreshWorker(ctx context.Context) error { job = nil } } + s.log.Infow("storage refresh worker exits infinite loop") + return nil } func (s *SnapSyncher) queueClassJob(ctx context.Context, classHash *felt.Felt) error { diff --git a/p2p/starknet/handlers.go b/p2p/starknet/handlers.go index 276403c3f2..d80de690d9 100644 --- a/p2p/starknet/handlers.go +++ b/p2p/starknet/handlers.go @@ -130,7 +130,7 @@ func (h *Handler) StateDiffHandler(stream network.Stream) { streamHandler[*spec.StateDiffsRequest](h.ctx, &h.wg, stream, h.onStateDiffRequest, h.log) } -func (h *Handler) ClassRangeRequest(stream network.Stream) { +func (h *Handler) ClassRangeHandler(stream network.Stream) { if h.snapProvider == nil { h.log.Debugw("SnapProvider not initialized") return @@ -138,7 +138,7 @@ func (h *Handler) ClassRangeRequest(stream network.Stream) { streamHandler[*spec.ClassRangeRequest](h.ctx, &h.wg, stream, h.snapProvider.GetClassRange, h.log) } -func (h *Handler) ContractRangeRequest(stream network.Stream) { +func (h *Handler) ContractRangeHandler(stream network.Stream) { if h.snapProvider == nil { h.log.Debugw("SnapProvider not initialized") return @@ -146,7 +146,7 @@ func (h *Handler) ContractRangeRequest(stream network.Stream) { streamHandler[*spec.ContractRangeRequest](h.ctx, &h.wg, stream, h.snapProvider.GetContractRange, h.log) } -func (h *Handler) ContractStorageRequest(stream network.Stream) { +func (h *Handler) ContractStorageHandler(stream network.Stream) { if h.snapProvider == nil { h.log.Debugw("SnapProvider not initialized") return @@ -154,7 +154,7 @@ func (h *Handler) ContractStorageRequest(stream network.Stream) { streamHandler[*spec.ContractStorageRequest](h.ctx, &h.wg, stream, h.snapProvider.GetStorageRange, h.log) } -func (h *Handler) ClassHashesRequest(stream network.Stream) { +func (h *Handler) ClassHashesHandler(stream network.Stream) { if h.snapProvider == nil { h.log.Debugw("SnapProvider not initialized") return diff --git a/p2p/starknetdata.go b/p2p/starknetdata.go new file mode 100644 index 0000000000..f6e8736cff --- /dev/null +++ b/p2p/starknetdata.go @@ -0,0 +1,67 @@ +package p2p + +import ( + "context" + + "github.com/NethermindEth/juno/core" + "github.com/NethermindEth/juno/core/felt" + "github.com/NethermindEth/juno/starknetdata" + "github.com/ethereum/go-ethereum/log" +) + +type MockStarkData struct { +} + +var _ starknetdata.StarknetData = (*MockStarkData)(nil) + +func (m MockStarkData) BlockByNumber(ctx context.Context, blockNumber uint64) (*core.Block, error) { + log.Info("BlockByNumber", "blockNumber", blockNumber) + return m.BlockLatest(ctx) +} + +func (m MockStarkData) BlockLatest(ctx context.Context) (*core.Block, error) { + // This is snapshot I have + root, _ := (&felt.Felt{}).SetString("0x472e84b65d387c9364b5117f4afaba3fb88897db1f28867b398506e2af89f25") + + return &core.Block{ + Header: &core.Header{ + Number: uint64(66477), + GlobalStateRoot: root, + }, + }, nil +} + +func (m MockStarkData) BlockPending(ctx context.Context) (*core.Block, error) { + //TODO implement me + panic("implement me") +} + +func (m MockStarkData) Transaction(ctx context.Context, transactionHash *felt.Felt) (core.Transaction, error) { + //TODO implement me + panic("implement me") +} + +func (m MockStarkData) Class(ctx context.Context, classHash *felt.Felt) (core.Class, error) { + //TODO implement me + panic("implement me") +} + +func (m MockStarkData) StateUpdate(ctx context.Context, blockNumber uint64) (*core.StateUpdate, error) { + //TODO implement me + panic("implement me") +} + +func (m MockStarkData) StateUpdatePending(ctx context.Context) (*core.StateUpdate, error) { + //TODO implement me + panic("implement me") +} + +func (m MockStarkData) StateUpdateWithBlock(ctx context.Context, blockNumber uint64) (*core.StateUpdate, *core.Block, error) { + //TODO implement me + panic("implement me") +} + +func (m MockStarkData) StateUpdatePendingWithBlock(ctx context.Context) (*core.StateUpdate, *core.Block, error) { + //TODO implement me + panic("implement me") +} From 96ff9a51fe6c7628df75bd68cebb86cb0d90cac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowosielski?= Date: Wed, 11 Sep 2024 12:34:28 +0200 Subject: [PATCH 09/23] Managed to sync ~200MB data --- p2p/snap_server.go | 39 ++++++++++++++++++++++++--------------- p2p/snap_server_test.go | 23 +++++++++++++++++------ p2p/snap_syncer.go | 6 +++--- p2p/starknetdata.go | 4 ++-- utils/log.go | 1 + 5 files changed, 47 insertions(+), 26 deletions(-) diff --git a/p2p/snap_server.go b/p2p/snap_server.go index 50c50d4062..08bf0b3def 100644 --- a/p2p/snap_server.go +++ b/p2p/snap_server.go @@ -97,7 +97,7 @@ func (b *snapServer) GetClassRange(request *spec.ClassRangeRequest) (iter.Seq[pr stateRoot := p2p2core.AdaptHash(request.Root) startAddr := p2p2core.AdaptHash(request.Start) - b.log.Debugw("GetClassRange", "root", stateRoot.String(), "start", startAddr.String(), "chunks", request.ChunksPerProof) + b.log.Debugw("GetClassRange", "root", stateRoot, "start", startAddr, "chunks", request.ChunksPerProof) return func(yield yieldFunc) { s, err := b.blockchain.GetStateForStateRoot(stateRoot) @@ -131,7 +131,7 @@ func (b *snapServer) GetClassRange(request *spec.ClassRangeRequest) (iter.Seq[pr } classkeys := []*felt.Felt{} - proofs, finished, err := iterateWithLimit(ctrie, startAddr, limitAddr, determineMaxNodes(request.ChunksPerProof), + proofs, finished, err := iterateWithLimit(ctrie, startAddr, limitAddr, determineMaxNodes(request.ChunksPerProof), b.log, func(key, value *felt.Felt) error { classkeys = append(classkeys, key) return nil @@ -186,7 +186,7 @@ func (b *snapServer) GetContractRange(request *spec.ContractRangeRequest) (iter. } stateRoot := p2p2core.AdaptHash(request.StateRoot) startAddr := p2p2core.AdaptAddress(request.Start) - b.log.Debugw("GetContractRange", "root", stateRoot.String(), "start", startAddr.String(), "chunks", request.ChunksPerProof) + b.log.Debugw("GetContractRange", "root", stateRoot, "start", startAddr, "chunks", request.ChunksPerProof) return func(yield yieldFunc) { s, err := b.blockchain.GetStateForStateRoot(stateRoot) @@ -213,7 +213,7 @@ func (b *snapServer) GetContractRange(request *spec.ContractRangeRequest) (iter. states := []*spec.ContractState{} for { - proofs, finished, err := iterateWithLimit(strie, startAddr, limitAddr, determineMaxNodes(request.ChunksPerProof), + proofs, finished, err := iterateWithLimit(strie, startAddr, limitAddr, determineMaxNodes(request.ChunksPerProof), b.log, func(key, value *felt.Felt) error { classHash, err := s.ContractClassHash(key) if err != nil { @@ -280,7 +280,9 @@ func (b *snapServer) GetStorageRange(request *spec.ContractStorageRequest) (iter var finMsg proto.Message = &spec.ContractStorageResponse{ Responses: &spec.ContractStorageResponse_Fin{}, } - b.log.Debugw("GetStorageRange", "root", request.StateRoot.String(), "start[0]", request.Query[0].Start.Key.String()) + stateRoot := p2p2core.AdaptHash(request.StateRoot) + startKey := p2p2core.AdaptFelt(request.Query[0].Start.Key) + b.log.Debugw("GetStorageRange", "root", stateRoot, "start[0]", startKey) return func(yield yieldFunc) { stateRoot := p2p2core.AdaptHash(request.StateRoot) @@ -304,7 +306,7 @@ func (b *snapServer) GetStorageRange(request *spec.ContractStorageRequest) (iter return } - handled, err := b.handleStorageRangeRequest(strie, query, request.ChunksPerProof, contractLimit, + handled, err := b.handleStorageRangeRequest(strie, query, request.ChunksPerProof, contractLimit, b.log, func(values []*spec.ContractStoredValue, proofs []trie.ProofNode) bool { stoMsg := &spec.ContractStorageResponse{ StateRoot: request.StateRoot, @@ -378,6 +380,7 @@ func (b *snapServer) handleStorageRangeRequest( stTrie *trie.Trie, request *spec.StorageRangeQuery, maxChunkPerProof, nodeLimit uint32, + logger utils.SimpleLogger, yield func([]*spec.ContractStoredValue, []trie.ProofNode) bool, ) (uint32, error) { totalSent := 0 @@ -396,15 +399,16 @@ func (b *snapServer) handleStorageRangeRequest( limit = nodeLimit } - proofs, finish, err := iterateWithLimit(stTrie, startAddr, endAddr, limit, func(key, value *felt.Felt) error { - response = append(response, &spec.ContractStoredValue{ - Key: core2p2p.AdaptFelt(key), - Value: core2p2p.AdaptFelt(value), - }) + proofs, finish, err := iterateWithLimit(stTrie, startAddr, endAddr, limit, logger, + func(key, value *felt.Felt) error { + response = append(response, &spec.ContractStoredValue{ + Key: core2p2p.AdaptFelt(key), + Value: core2p2p.AdaptFelt(value), + }) - startAddr = key - return nil - }) + startAddr = key + return nil + }) finished = finish if err != nil { @@ -435,12 +439,13 @@ func iterateWithLimit( startAddr *felt.Felt, limitAddr *felt.Felt, maxNodes uint32, + logger utils.SimpleLogger, consumer func(key, value *felt.Felt) error, ) ([]trie.ProofNode, bool, error) { pathes := make([]*felt.Felt, 0) hashes := make([]*felt.Felt, 0) - // TODO: Verify class trie + logger.Infow("entering IterateAndGenerateProof", "startAddr", startAddr, "endAddr", limitAddr, "maxNodes", maxNodes) count := uint32(0) proof, finished, err := srcTrie.IterateAndGenerateProof(startAddr, func(key *felt.Felt, value *felt.Felt) (bool, error) { // Need at least one. @@ -453,19 +458,23 @@ func iterateWithLimit( err := consumer(key, value) if err != nil { + logger.Errorw("error from consumer function", "err", err) return false, err } count++ if count >= maxNodes { + logger.Infow("Max nodes reached", "count", count) return false, nil } return true, nil }) if err != nil { + logger.Errorw("IterateAndGenerateProof", "err", err, "finished", finished) return nil, finished, err } + logger.Infow("exiting IterateAndGenerateProof", "len(proof)", len(proof), "finished", finished, "err", err) return proof, finished, err } diff --git a/p2p/snap_server_test.go b/p2p/snap_server_test.go index 5b00cbe982..227b1b65c1 100644 --- a/p2p/snap_server_test.go +++ b/p2p/snap_server_test.go @@ -19,9 +19,9 @@ import ( func TestClassRange(t *testing.T) { // Note: set to true to make test super long to complete - shouldFetchAllClasses := false + shouldFetchAllClasses := true var d db.DB - t.Skip("DB snapshot is needed for this test") + //t.Skip("DB snapshot is needed for this test") d, _ = pebble.NewWithOptions("/Users/pnowosie/juno/snapshots/juno-sepolia", 128000000, 128, false) defer func() { _ = d.Close() }() bc := blockchain.New(d, &utils.Sepolia) // Needed because class loader need encoder to be registered @@ -32,8 +32,9 @@ func TestClassRange(t *testing.T) { fmt.Printf("headblock %d\n", b.Number) stateRoot := b.GlobalStateRoot - + logger, _ := utils.NewZapLogger(utils.DEBUG, false) server := &snapServer{ + log: logger, blockchain: bc, } @@ -42,6 +43,10 @@ func TestClassRange(t *testing.T) { chunksReceived := 0 chunksPerProof := 150 + if shouldFetchAllClasses { + // decrease iteration count and hence speed up a bit + chunksPerProof *= 4 + } iter, err := server.GetClassRange( &spec.ClassRangeRequest{ Root: core2p2p.AdaptHash(stateRoot), @@ -86,7 +91,7 @@ func TestClassRange(t *testing.T) { func TestContractRange(t *testing.T) { var d db.DB - t.Skip("DB snapshot is needed for this test") + //t.Skip("DB snapshot is needed for this test") d, _ = pebble.NewWithOptions("/Users/pnowosie/juno/snapshots/juno-sepolia", 128000000, 128, false) defer func() { _ = d.Close() }() bc := blockchain.New(d, &utils.Sepolia) // Needed because class loader need encoder to be registered @@ -98,7 +103,9 @@ func TestContractRange(t *testing.T) { stateRoot := b.GlobalStateRoot + logger, _ := utils.NewZapLogger(utils.DEBUG, false) server := &snapServer{ + log: logger, blockchain: bc, } @@ -173,7 +180,7 @@ func TestContractRange_FinMsg_Received(t *testing.T) { func TestContractStorageRange(t *testing.T) { var d db.DB - t.Skip("DB snapshot is needed for this test") + //t.Skip("DB snapshot is needed for this test") d, _ = pebble.NewWithOptions("/Users/pnowosie/juno/snapshots/juno-sepolia", 128000000, 128, false) defer func() { _ = d.Close() }() bc := blockchain.New(d, &utils.Sepolia) // Needed because class loader need encoder to be registered @@ -185,7 +192,9 @@ func TestContractStorageRange(t *testing.T) { stateRoot := b.GlobalStateRoot + logger, _ := utils.NewZapLogger(utils.DEBUG, false) server := &snapServer{ + log: logger, blockchain: bc, } @@ -268,7 +277,7 @@ func TestContractStorageRange(t *testing.T) { func TestGetClassesByHash(t *testing.T) { var d db.DB - t.Skip("DB snapshot is needed for this test") + //t.Skip("DB snapshot is needed for this test") d, _ = pebble.NewWithOptions("/Users/pnowosie/juno/snapshots/juno-sepolia", 128000000, 128, false) defer func() { _ = d.Close() }() bc := blockchain.New(d, &utils.Sepolia) // Needed because class loader need encoder to be registered @@ -278,7 +287,9 @@ func TestGetClassesByHash(t *testing.T) { fmt.Printf("headblock %d\n", b.Number) + logger, _ := utils.NewZapLogger(utils.DEBUG, false) server := &snapServer{ + log: logger, blockchain: bc, } diff --git a/p2p/snap_syncer.go b/p2p/snap_syncer.go index 2724519f04..141961ec01 100644 --- a/p2p/snap_syncer.go +++ b/p2p/snap_syncer.go @@ -537,7 +537,7 @@ func (s *SnapSyncher) runFetchClassJob(ctx context.Context) error { continue } - classes := make([]*spec.Class, len(keyBatches)) + classes := make([]*spec.Class, 0, len(keyBatches)) for response := range classIter { if response == nil { s.log.Errorw("class by keys respond with nil response") @@ -554,14 +554,14 @@ func (s *SnapSyncher) runFetchClassJob(ctx context.Context) error { s.log.Warnw("Unexpected ClassMessage from getClasses", "v", v) } } - s.log.Infow("class fetch job received response", "classes", len(classes)) + s.log.Infow("class fetch job received response", "classes", len(classes), "asked keys", len(keyBatches)) processedClasses := map[felt.Felt]bool{} newClasses := map[felt.Felt]core.Class{} classHashes := map[felt.Felt]*felt.Felt{} for i, class := range classes { if class == nil { - s.log.Infow("class empty", "key", keyBatches[i]) + s.log.Infow("class empty", "hash", keyBatches[i]) continue } diff --git a/p2p/starknetdata.go b/p2p/starknetdata.go index f6e8736cff..b561a5f275 100644 --- a/p2p/starknetdata.go +++ b/p2p/starknetdata.go @@ -21,11 +21,11 @@ func (m MockStarkData) BlockByNumber(ctx context.Context, blockNumber uint64) (* func (m MockStarkData) BlockLatest(ctx context.Context) (*core.Block, error) { // This is snapshot I have - root, _ := (&felt.Felt{}).SetString("0x472e84b65d387c9364b5117f4afaba3fb88897db1f28867b398506e2af89f25") + root, _ := (&felt.Felt{}).SetString("0x6df37678051ab529c243a5ae08e95eea4ddb40b874b4c537e2e6a9a459e2548") return &core.Block{ Header: &core.Header{ - Number: uint64(66477), + Number: uint64(66489), GlobalStateRoot: root, }, }, nil diff --git a/utils/log.go b/utils/log.go index 3d88ef3cbd..a38857facb 100644 --- a/utils/log.go +++ b/utils/log.go @@ -122,6 +122,7 @@ func (l *ZapLogger) Tracew(msg string, keysAndValues ...interface{}) { } var _ Logger = (*ZapLogger)(nil) +var _ SimpleLogger = (*ZapLogger)(nil) func NewNopZapLogger() *ZapLogger { return &ZapLogger{zap.NewNop().Sugar()} From 4711c3f81917b27780bcf8e2d1aaabddbb042847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowosielski?= Date: Fri, 13 Sep 2024 13:28:35 +0200 Subject: [PATCH 10/23] Fix: Trie iteration --- adapters/p2p2core/felt.go | 2 +- core/trie/snap_support.go | 43 +++++++ core/trie/snap_support_test.go | 220 +++++++++++++++++++++++++++++++++ p2p/snap_server.go | 73 +++-------- p2p/snap_server_test.go | 113 ++++++++++++++++- p2p/snap_syncer.go | 4 +- 6 files changed, 394 insertions(+), 61 deletions(-) diff --git a/adapters/p2p2core/felt.go b/adapters/p2p2core/felt.go index cd4b3080b2..60a25065ec 100644 --- a/adapters/p2p2core/felt.go +++ b/adapters/p2p2core/felt.go @@ -7,6 +7,7 @@ import ( "github.com/NethermindEth/juno/core/felt" "github.com/NethermindEth/juno/p2p/starknet/spec" "github.com/ethereum/go-ethereum/common" + "reflect" ) func AdaptHash(h *spec.Hash) *felt.Felt { @@ -31,7 +32,6 @@ func adapt(v interface{ GetElements() []byte }) *felt.Felt { if v == nil || reflect.ValueOf(v).IsNil() { return nil } - return new(felt.Felt).SetBytes(v.GetElements()) } diff --git a/core/trie/snap_support.go b/core/trie/snap_support.go index 84f3f1e2c4..4f6fdb2061 100644 --- a/core/trie/snap_support.go +++ b/core/trie/snap_support.go @@ -2,6 +2,7 @@ package trie import ( "github.com/NethermindEth/juno/core/felt" + "github.com/NethermindEth/juno/utils" ) func (t *Trie) IterateAndGenerateProof(startValue *felt.Felt, consumer func(key, value *felt.Felt) (bool, error), @@ -56,6 +57,48 @@ func (t *Trie) IterateAndGenerateProof(startValue *felt.Felt, consumer func(key, return proofs, finished, nil } +func (t *Trie) IterateWithLimit( + startAddr *felt.Felt, + limitAddr *felt.Felt, + maxNodes uint32, + // TODO: remove the logger - and move to the tree + logger utils.SimpleLogger, + consumer func(key, value *felt.Felt) error, +) ([]ProofNode, bool, error) { + pathes := make([]*felt.Felt, 0) + hashes := make([]*felt.Felt, 0) + + count := uint32(0) + proof, finished, err := t.IterateAndGenerateProof(startAddr, func(key *felt.Felt, value *felt.Felt) (bool, error) { + // Need at least one. + if limitAddr != nil && key.Cmp(limitAddr) > 0 { + return true, nil + } + + pathes = append(pathes, key) + hashes = append(hashes, value) + + err := consumer(key, value) + if err != nil { + logger.Errorw("error from consumer function", "err", err) + return false, err + } + + count++ + if count >= maxNodes { + logger.Infow("Max nodes reached", "count", count) + return false, nil + } + return true, nil + }) + if err != nil { + logger.Errorw("IterateAndGenerateProof", "err", err, "finished", finished) + return nil, finished, err + } + + return proof, finished, err +} + func VerifyRange(root, startKey *felt.Felt, keys, values []*felt.Felt, proofs []ProofNode, hash hashFunc, treeHeight uint8, ) (hasMore, valid bool, oerr error) { diff --git a/core/trie/snap_support_test.go b/core/trie/snap_support_test.go index 9a3501bb6d..e77e974a02 100644 --- a/core/trie/snap_support_test.go +++ b/core/trie/snap_support_test.go @@ -1,6 +1,11 @@ package trie_test import ( + "fmt" + "github.com/NethermindEth/juno/db/pebble" + "github.com/NethermindEth/juno/utils" + "github.com/stretchr/testify/require" + "math" "testing" "github.com/NethermindEth/juno/core/crypto" @@ -276,3 +281,218 @@ func TestRangeAndVerifyReject(t *testing.T) { }) } } + +func TestIterateOverTrie(t *testing.T) { + memdb := pebble.NewMemTest(t) + txn, err := memdb.NewTransaction(true) + require.NoError(t, err) + logger := utils.NewNopZapLogger() + + tempTrie, err := trie.NewTriePedersen(trie.NewStorage(txn, []byte{0}), 251) + require.NoError(t, err) + + // key ranges + var ( + bigPrefix = uint64(1000 * 1000 * 1000 * 1000) + count = 100 + ranges = 5 + fstInt, lstInt uint64 + fstKey, lstKey *felt.Felt + ) + for i := range ranges { + for j := range count { + lstInt = bigPrefix*uint64(i) + uint64(count+j) + lstKey = new(felt.Felt).SetUint64(lstInt) + value := new(felt.Felt).SetUint64(uint64(10*count + j + i)) + + if fstKey == nil { + fstKey = lstKey + fstInt = lstInt + } + + _, err := tempTrie.Put(lstKey, value) + require.NoError(t, err) + } + } + + maxNodes := uint32(ranges*count + 1) + startZero := felt.Zero.Clone() + + visitor := func(start, limit *felt.Felt, max uint32) (int, bool, *felt.Felt, *felt.Felt) { + visited := 0 + var fst, lst *felt.Felt + _, finish, err := tempTrie.IterateWithLimit( + start, + limit, + max, + logger, + func(key, value *felt.Felt) error { + if fst == nil { + fst = key + } + lst = key + visited++ + return nil + }) + require.NoError(t, err) + return visited, finish, fst, lst + } + + t.Run("iterate without limit", func(t *testing.T) { + expectedLeaves := ranges * count + visited, finish, fst, lst := visitor(nil, nil, maxNodes) + require.Equal(t, expectedLeaves, visited) + require.True(t, finish) + require.Equal(t, fstKey, fst) + require.Equal(t, lstKey, lst) + fmt.Println("Visited:", visited, "\tFinish:", finish, "\tRange:", fst.Uint64(), "-", lst.Uint64()) + }) + + t.Run("iterate over trie im chunks", func(t *testing.T) { + chunkSize := 77 + lstChunkSize := int(math.Mod(float64(ranges*count), float64(chunkSize))) + startKey := startZero + for { + visited, finish, fst, lst := visitor(startKey, nil, uint32(chunkSize)) + fmt.Println("Finish:", finish, "\tstart:", startKey.Uint64(), "\trange:", fst.Uint64(), "-", lst.Uint64()) + if finish { + require.Equal(t, lstChunkSize, visited) + break + } + require.Equal(t, chunkSize, visited) + require.False(t, finish) + startKey = new(felt.Felt).SetUint64(lst.Uint64() + 1) + } + }) + + t.Run("iterate over trie im groups", func(t *testing.T) { + startKey := startZero + for { + visited, finish, fst, lst := visitor(startKey, nil, uint32(count)) + if finish { + require.Equal(t, 0, visited) + fmt.Println("Finish:", finish, "\tstart:", startKey.Uint64(), "\trange: ") + break + } + fmt.Println("Finish:", finish, "\tstart:", startKey.Uint64(), "\trange:", fst.Uint64(), "-", lst.Uint64()) + require.Equal(t, count, visited) + require.False(t, finish) + if lst != nil { + startKey = new(felt.Felt).SetUint64(lst.Uint64() + 1) + } + } + }) + + t.Run("stop before first key", func(t *testing.T) { + lowerBound := new(felt.Felt).SetUint64(fstInt - 1) + visited, finish, _, _ := visitor(startZero, lowerBound, maxNodes) + require.True(t, finish) + require.Equal(t, 0, visited) + }) + + t.Run("first key is a limit", func(t *testing.T) { + visited, finish, fst, lst := visitor(startZero, fstKey, maxNodes) + require.Equal(t, 1, visited) + require.True(t, finish) + require.Equal(t, fstKey, fst) + require.Equal(t, fstKey, lst) + }) + + t.Run("start is the last key", func(t *testing.T) { + visited, finish, fst, lst := visitor(lstKey, nil, maxNodes) + require.Equal(t, 1, visited) + require.True(t, finish) + require.Equal(t, lstKey, fst) + require.Equal(t, lstKey, lst) + }) + + t.Run("start and limit are the last key", func(t *testing.T) { + visited, finish, fst, lst := visitor(lstKey, lstKey, maxNodes) + require.Equal(t, 1, visited) + require.True(t, finish) + require.Equal(t, lstKey, fst) + require.Equal(t, lstKey, lst) + }) + + t.Run("iterate after last key yields no key", func(t *testing.T) { + upperBound := new(felt.Felt).SetUint64(lstInt + 1) + visited, finish, fst, _ := visitor(upperBound, nil, maxNodes) + require.Equal(t, 0, visited) + require.True(t, finish) + require.Nil(t, fst) + }) + + t.Run("iterate with reversed bounds yields no key", func(t *testing.T) { + visited, finish, fst, _ := visitor(lstKey, fstKey, maxNodes) + require.Equal(t, 0, visited) + require.True(t, finish) + require.Nil(t, fst) + }) + + t.Run("iterate over the first group", func(t *testing.T) { + fstGrpBound := new(felt.Felt).SetUint64(fstInt + uint64(count-1)) + visited, finish, fst, lst := visitor(fstKey, fstGrpBound, maxNodes) + require.Equal(t, count, visited) + require.True(t, finish) + require.Equal(t, fstKey, fst) + require.Equal(t, fstGrpBound, lst) + }) + + t.Run("iterate over the first group no lower bound", func(t *testing.T) { + fstGrpBound := new(felt.Felt).SetUint64(fstInt + uint64(count-1)) + visited, finish, fst, lst := visitor(nil, fstGrpBound, maxNodes) + require.Equal(t, count, visited) + require.True(t, finish) + require.Equal(t, fstKey, fst) + require.Equal(t, fstGrpBound, lst) + }) + + t.Run("iterate over the first group by max nodes", func(t *testing.T) { + fstGrpBound := new(felt.Felt).SetUint64(fstInt + uint64(count-1)) + visited, finish, fst, lst := visitor(fstKey, nil, uint32(count)) + require.Equal(t, count, visited) + require.False(t, finish) + require.Equal(t, fstKey, fst) + require.Equal(t, fstGrpBound, lst) + }) + + t.Run("iterate over the last group, start before group bound", func(t *testing.T) { + lstGrpStartInt := lstInt - uint64(count-1) + lstGrpFstKey := new(felt.Felt).SetUint64(lstGrpStartInt) + startKey := new(felt.Felt).SetUint64(lstGrpStartInt - uint64(count)) + + visited, finish, fst, lst := visitor(startKey, nil, maxNodes) + require.Equal(t, count, visited) + require.True(t, finish) + require.Equal(t, lstGrpFstKey, fst) + require.Equal(t, lstKey, lst) + }) + + sndGrpFstKey := new(felt.Felt).SetUint64(bigPrefix + uint64(count)) + sndGrpLstKey := new(felt.Felt).SetUint64(bigPrefix + uint64(2*count-1)) + t.Run("second group key selection", func(t *testing.T) { + visited, _, _, lst := visitor(fstKey, nil, uint32(count+1)) + require.Equal(t, count+1, visited) + require.Equal(t, sndGrpFstKey, lst) + + visited, finish, fst, lst := visitor(sndGrpFstKey, sndGrpLstKey, maxNodes) + require.Equal(t, count, visited) + require.True(t, finish) + require.Equal(t, sndGrpFstKey, fst) + require.Equal(t, sndGrpLstKey, lst) + }) + + t.Run("second group key selection 2", func(t *testing.T) { + nodeAfterFstGrp := new(felt.Felt).SetUint64(fstInt + uint64(count+1)) + visited, _, fst, lst := visitor(nodeAfterFstGrp, nil, 1) + require.Equal(t, 1, visited) + require.Equal(t, sndGrpFstKey, fst) + require.Equal(t, fst, lst) + + visited, finish, fst, lst := visitor(sndGrpFstKey, nil, uint32(count)) + require.Equal(t, count, visited) + require.False(t, finish) + require.Equal(t, sndGrpFstKey, fst) + require.Equal(t, sndGrpLstKey, lst) + }) +} diff --git a/p2p/snap_server.go b/p2p/snap_server.go index 08bf0b3def..a6e9ec7fe5 100644 --- a/p2p/snap_server.go +++ b/p2p/snap_server.go @@ -97,7 +97,7 @@ func (b *snapServer) GetClassRange(request *spec.ClassRangeRequest) (iter.Seq[pr stateRoot := p2p2core.AdaptHash(request.Root) startAddr := p2p2core.AdaptHash(request.Start) - b.log.Debugw("GetClassRange", "root", stateRoot, "start", startAddr, "chunks", request.ChunksPerProof) + b.log.Debugw("GetClassRange", "start", startAddr, "chunks", request.ChunksPerProof) return func(yield yieldFunc) { s, err := b.blockchain.GetStateForStateRoot(stateRoot) @@ -121,7 +121,7 @@ func (b *snapServer) GetClassRange(request *spec.ClassRangeRequest) (iter.Seq[pr startAddr := p2p2core.AdaptHash(request.Start) limitAddr := p2p2core.AdaptHash(request.End) - if limitAddr.IsZero() { + if limitAddr != nil && limitAddr.IsZero() { limitAddr = nil } @@ -131,7 +131,7 @@ func (b *snapServer) GetClassRange(request *spec.ClassRangeRequest) (iter.Seq[pr } classkeys := []*felt.Felt{} - proofs, finished, err := iterateWithLimit(ctrie, startAddr, limitAddr, determineMaxNodes(request.ChunksPerProof), b.log, + proofs, finished, err := ctrie.IterateWithLimit(startAddr, limitAddr, determineMaxNodes(request.ChunksPerProof), b.log, func(key, value *felt.Felt) error { classkeys = append(classkeys, key) return nil @@ -164,7 +164,9 @@ func (b *snapServer) GetClassRange(request *spec.ClassRangeRequest) (iter.Seq[pr RangeProof: Core2P2pProof(proofs), } - b.log.Infow("sending class range response", "len(classes)", len(classkeys)) + first := classkeys[0] + last := classkeys[len(classkeys)-1] + b.log.Infow("sending class range response", "len(classes)", len(classkeys), "first", first, "last", last) if !yield(clsMsg) { // we should not send `FinMsg` when the client explicitly asks to stop return @@ -176,7 +178,7 @@ func (b *snapServer) GetClassRange(request *spec.ClassRangeRequest) (iter.Seq[pr } yield(finMsg) - b.log.Infow("class range iteration completed") + b.log.Infow("GetClassRange iteration completed") }, nil } @@ -213,7 +215,7 @@ func (b *snapServer) GetContractRange(request *spec.ContractRangeRequest) (iter. states := []*spec.ContractState{} for { - proofs, finished, err := iterateWithLimit(strie, startAddr, limitAddr, determineMaxNodes(request.ChunksPerProof), b.log, + proofs, finished, err := strie.IterateWithLimit(startAddr, limitAddr, determineMaxNodes(request.ChunksPerProof), b.log, func(key, value *felt.Felt) error { classHash, err := s.ContractClassHash(key) if err != nil { @@ -244,6 +246,7 @@ func (b *snapServer) GetContractRange(request *spec.ContractRangeRequest) (iter. }) return nil }) + if err != nil { log.Error("error iterating storage trie", "err", err) return @@ -261,7 +264,12 @@ func (b *snapServer) GetContractRange(request *spec.ContractRangeRequest) (iter. }, } - b.log.Infow("sending contract range response", "len(states)", len(states)) + var first, last *felt.Felt + if len(states) > 0 { + first = p2p2core.AdaptAddress(states[0].Address) + last = p2p2core.AdaptAddress(states[len(states)-1].Address) + } + b.log.Infow("sending contract range response", "len(states)", len(states), "first", first, "last", last) if !yield(cntrMsg) { // we should not send `FinMsg` when the client explicitly asks to stop return @@ -269,6 +277,8 @@ func (b *snapServer) GetContractRange(request *spec.ContractRangeRequest) (iter. if finished { break } + + states = states[:0] } yield(finMsg) @@ -302,7 +312,8 @@ func (b *snapServer) GetStorageRange(request *spec.ContractStorageRequest) (iter strie, err := s.StorageTrieForAddr(p2p2core.AdaptAddress(query.Address)) if err != nil { - log.Error("error getting storage trie for address", "addr", query.Address.String(), "err", err) + addr := p2p2core.AdaptAddress(query.Address) + log.Error("error getting storage trie for address", "addr", addr, "err", err) return } @@ -399,7 +410,7 @@ func (b *snapServer) handleStorageRangeRequest( limit = nodeLimit } - proofs, finish, err := iterateWithLimit(stTrie, startAddr, endAddr, limit, logger, + proofs, finish, err := stTrie.IterateWithLimit(startAddr, endAddr, limit, logger, func(key, value *felt.Felt) error { response = append(response, &spec.ContractStoredValue{ Key: core2p2p.AdaptFelt(key), @@ -434,50 +445,6 @@ func (b *snapServer) handleStorageRangeRequest( return uint32(totalSent), nil } -func iterateWithLimit( - srcTrie *trie.Trie, - startAddr *felt.Felt, - limitAddr *felt.Felt, - maxNodes uint32, - logger utils.SimpleLogger, - consumer func(key, value *felt.Felt) error, -) ([]trie.ProofNode, bool, error) { - pathes := make([]*felt.Felt, 0) - hashes := make([]*felt.Felt, 0) - - logger.Infow("entering IterateAndGenerateProof", "startAddr", startAddr, "endAddr", limitAddr, "maxNodes", maxNodes) - count := uint32(0) - proof, finished, err := srcTrie.IterateAndGenerateProof(startAddr, func(key *felt.Felt, value *felt.Felt) (bool, error) { - // Need at least one. - if limitAddr != nil && key.Cmp(limitAddr) > 1 && count > 0 { - return false, nil - } - - pathes = append(pathes, key) - hashes = append(hashes, value) - - err := consumer(key, value) - if err != nil { - logger.Errorw("error from consumer function", "err", err) - return false, err - } - - count++ - if count >= maxNodes { - logger.Infow("Max nodes reached", "count", count) - return false, nil - } - return true, nil - }) - if err != nil { - logger.Errorw("IterateAndGenerateProof", "err", err, "finished", finished) - return nil, finished, err - } - - logger.Infow("exiting IterateAndGenerateProof", "len(proof)", len(proof), "finished", finished, "err", err) - return proof, finished, err -} - func Core2P2pProof(proofs []trie.ProofNode) *spec.PatriciaRangeProof { nodes := make([]*spec.PatriciaNode, len(proofs)) diff --git a/p2p/snap_server_test.go b/p2p/snap_server_test.go index 227b1b65c1..f315d3cdb1 100644 --- a/p2p/snap_server_test.go +++ b/p2p/snap_server_test.go @@ -19,9 +19,9 @@ import ( func TestClassRange(t *testing.T) { // Note: set to true to make test super long to complete - shouldFetchAllClasses := true + shouldFetchAllClasses := false var d db.DB - //t.Skip("DB snapshot is needed for this test") + t.Skip("DB snapshot is needed for this test") d, _ = pebble.NewWithOptions("/Users/pnowosie/juno/snapshots/juno-sepolia", 128000000, 128, false) defer func() { _ = d.Close() }() bc := blockchain.New(d, &utils.Sepolia) // Needed because class loader need encoder to be registered @@ -91,7 +91,7 @@ func TestClassRange(t *testing.T) { func TestContractRange(t *testing.T) { var d db.DB - //t.Skip("DB snapshot is needed for this test") + t.Skip("DB snapshot is needed for this test") d, _ = pebble.NewWithOptions("/Users/pnowosie/juno/snapshots/juno-sepolia", 128000000, 128, false) defer func() { _ = d.Close() }() bc := blockchain.New(d, &utils.Sepolia) // Needed because class loader need encoder to be registered @@ -180,7 +180,7 @@ func TestContractRange_FinMsg_Received(t *testing.T) { func TestContractStorageRange(t *testing.T) { var d db.DB - //t.Skip("DB snapshot is needed for this test") + t.Skip("DB snapshot is needed for this test") d, _ = pebble.NewWithOptions("/Users/pnowosie/juno/snapshots/juno-sepolia", 128000000, 128, false) defer func() { _ = d.Close() }() bc := blockchain.New(d, &utils.Sepolia) // Needed because class loader need encoder to be registered @@ -205,6 +205,16 @@ func TestContractStorageRange(t *testing.T) { storageRoot *felt.Felt expectedLeaves int }{ + { + address: feltFromString("0x5eb8d1bc5aaf2f323f2a807d429686ac012ca16f90740071d2f3a160dc231"), + storageRoot: feltFromString("0x0"), + expectedLeaves: 0, + }, + { + address: feltFromString("0x614a5e0519963324acb5640321240827c0cd6a9f7cf5f17a80c1596e607d0"), + storageRoot: feltFromString("0x55ee7fd57d0aa3da8b89ea2feda16f9435186988a8b00b6f22f5ba39f3cf172"), + expectedLeaves: 1, + }, { address: feltFromString("0x3deecdb26a60e4c062d5bd98ab37f72ea2acc37f28dae6923359627ebde9"), storageRoot: feltFromString("0x276edbc91a945d11645ba0b8298c7d657e554d06ab2bb765cbc44d61fa01fd5"), @@ -277,7 +287,7 @@ func TestContractStorageRange(t *testing.T) { func TestGetClassesByHash(t *testing.T) { var d db.DB - //t.Skip("DB snapshot is needed for this test") + t.Skip("DB snapshot is needed for this test") d, _ = pebble.NewWithOptions("/Users/pnowosie/juno/snapshots/juno-sepolia", 128000000, 128, false) defer func() { _ = d.Close() }() bc := blockchain.New(d, &utils.Sepolia) // Needed because class loader need encoder to be registered @@ -333,6 +343,99 @@ func TestGetClassesByHash(t *testing.T) { assert.True(t, finMsgReceived) } +func TestGetContractStorageRoot(t *testing.T) { + var d db.DB + t.Skip("DB snapshot is needed for this test") + d, _ = pebble.NewWithOptions("/Users/pnowosie/juno/snapshots/juno-sepolia", 128000000, 128, false) + defer func() { _ = d.Close() }() + bc := blockchain.New(d, &utils.Sepolia) // Needed because class loader need encoder to be registered + + b, err := bc.Head() + assert.NoError(t, err) + + fmt.Printf("headblock %d\n", b.Number) + + stateRoot := b.GlobalStateRoot + + logger, _ := utils.NewZapLogger(utils.DEBUG, false) + server := &snapServer{ + log: logger, + blockchain: bc, + } + + tests := []struct { + address *felt.Felt + storageRoot *felt.Felt + }{ + { + address: feltFromString("0x5eb8d1bc5aaf2f323f2a807d429686ac012ca16f90740071d2f3a160dc231"), + storageRoot: feltFromString("0x0"), + }, + { + address: feltFromString("0x5ec87443bcb74e1e58762be15c3c513926a91a5d5b4a204e9e7b5ca884fb7"), + storageRoot: feltFromString("0x36fc3942926334a24b739065f26ffe547044af7466a6f8d391e0750603ffa8c"), + }, + { + address: feltFromString("0x614a5e0519963324acb5640321240827c0cd6a9f7cf5f17a80c1596e607d0"), + storageRoot: feltFromString("0x55ee7fd57d0aa3da8b89ea2feda16f9435186988a8b00b6f22f5ba39f3cf172"), + }, + { + address: feltFromString("0x6b7d60ec8176d8a1c77afdca05191dad1e1a20fef2e5e3aceccee0b3cbd6a"), + storageRoot: feltFromString("0x726d42240f103a32ce1b6acc7498f52fdf83e308cf70e0a6394591cee1886c8"), + }, + { + address: feltFromString("0x3deecdb26a60e4c062d5bd98ab37f72ea2acc37f28dae6923359627ebde9"), + storageRoot: feltFromString("0x276edbc91a945d11645ba0b8298c7d657e554d06ab2bb765cbc44d61fa01fd5"), + }, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("%.7s...", test.address), func(t *testing.T) { + request := &spec.ContractRangeRequest{ + ChunksPerProof: 10, + Start: core2p2p.AdaptAddress(test.address), + End: core2p2p.AdaptAddress(test.address), + StateRoot: core2p2p.AdaptHash(stateRoot), + } + + iter, err := server.GetContractRange(request) + assert.NoError(t, err) + + finMsgReceived := false + for res := range iter { + assert.NotNil(t, res) + resT, ok := res.(*spec.ContractRangeResponse) + assert.True(t, ok) + assert.NotNil(t, resT) + + i := 0 + switch v := resT.GetResponses().(type) { + case *spec.ContractRangeResponse_Range: + assert.False(t, finMsgReceived) + assert.Len(t, v.Range.State, 1) + contract := v.Range.State[0] + fmt.Println("Contract:", p2p2core.AdaptAddress(contract.Address), "StorageRoot:", p2p2core.AdaptHash(contract.Storage)) + assert.Equal(t, test.address, p2p2core.AdaptAddress(contract.Address)) + assert.Equal(t, test.storageRoot, p2p2core.AdaptHash(contract.Storage)) + + for j, s := range v.Range.State { + fmt.Println("[", j, "] Contract:", p2p2core.AdaptAddress(s.Address), "StorageRoot:", p2p2core.AdaptHash(s.Storage)) + } + i++ + if i > 5 { + t.Fatal("Too many contracts received") + } + case *spec.ContractRangeResponse_Fin: + finMsgReceived = true + default: + // we expect no any other message only just one range because we break the iteration + t.Fatal("received unexpected message", "type", v) + } + } + }) + } +} + func feltFromString(str string) *felt.Felt { f, err := (&felt.Felt{}).SetString(str) if err != nil { diff --git a/p2p/snap_syncer.go b/p2p/snap_syncer.go index 141961ec01..7998b3c360 100644 --- a/p2p/snap_syncer.go +++ b/p2p/snap_syncer.go @@ -116,8 +116,8 @@ var ( // For some reason, the trie throughput is higher if the batch size is small. classRangeChunksPerProof = 500 - contractRangeChunkPerProof = 500 - storageRangeChunkPerProof = 500 + contractRangeChunkPerProof = 501 + storageRangeChunkPerProof = 502 maxStorageBatchSize = 500 maxMaxPerStorageSize = 500 From 2151668ee96f77b6b2b06c3b3fe18eefcf2a8217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowosielski?= Date: Tue, 17 Sep 2024 17:09:22 +0200 Subject: [PATCH 11/23] test describing how storage of big contracts are responded --- p2p/snap_server.go | 2 +- p2p/snap_server_test.go | 254 ++++++++++++++++++++++++++++++++-------- 2 files changed, 203 insertions(+), 53 deletions(-) diff --git a/p2p/snap_server.go b/p2p/snap_server.go index a6e9ec7fe5..a6ae0487c6 100644 --- a/p2p/snap_server.go +++ b/p2p/snap_server.go @@ -291,7 +291,7 @@ func (b *snapServer) GetStorageRange(request *spec.ContractStorageRequest) (iter Responses: &spec.ContractStorageResponse_Fin{}, } stateRoot := p2p2core.AdaptHash(request.StateRoot) - startKey := p2p2core.AdaptFelt(request.Query[0].Start.Key) + startKey := p2p2core.AdaptAddress(request.Query[0].Address) b.log.Debugw("GetStorageRange", "root", stateRoot, "start[0]", startKey) return func(yield yieldFunc) { diff --git a/p2p/snap_server_test.go b/p2p/snap_server_test.go index f315d3cdb1..52b769f715 100644 --- a/p2p/snap_server_test.go +++ b/p2p/snap_server_test.go @@ -4,6 +4,8 @@ import ( "fmt" "github.com/NethermindEth/juno/core" "github.com/NethermindEth/juno/core/crypto" + "github.com/stretchr/testify/require" + "maps" "testing" "github.com/NethermindEth/juno/adapters/core2p2p" @@ -343,6 +345,125 @@ func TestGetClassesByHash(t *testing.T) { assert.True(t, finMsgReceived) } +func Test__Finding_Storage_Heavy_Contract(t *testing.T) { + var d db.DB + t.Skip("DB snapshot is needed for this test") + d, _ = pebble.NewWithOptions("/Users/pnowosie/juno/snapshots/juno-sepolia", 128000000, 128, false) + defer func() { _ = d.Close() }() + bc := blockchain.New(d, &utils.Sepolia) // Needed because class loader need encoder to be registered + + b, err := bc.Head() + assert.NoError(t, err) + + fmt.Printf("headblock %d\n", b.Number) + + stateRoot := b.GlobalStateRoot + + logger, _ := utils.NewZapLogger(utils.DEBUG, false) + server := &snapServer{ + log: logger, + blockchain: bc, + } + + ctso := make(map[felt.Felt]*felt.Felt) + request := &spec.ContractRangeRequest{ + ChunksPerProof: 100, + Start: core2p2p.AdaptAddress(felt.Zero.Clone()), + End: nil, //core2p2p.AdaptAddress(test.address), + StateRoot: core2p2p.AdaptHash(stateRoot), + } + + iter, err := server.GetContractRange(request) + assert.NoError(t, err) + + contracts := 0 + for res := range iter { + assert.NotNil(t, res) + resT, ok := res.(*spec.ContractRangeResponse) + assert.True(t, ok) + assert.NotNil(t, resT) + + switch v := resT.GetResponses().(type) { + case *spec.ContractRangeResponse_Range: + for _, contract := range v.Range.State { + addr := p2p2core.AdaptAddress(contract.Address) + strt := p2p2core.AdaptHash(contract.Storage) + //assert.Equal(t, test.address, addr) + //assert.Equal(t, test.storageRoot, strt) + if !(strt.IsZero() || addr.IsOne()) { + ctso[*addr] = strt + contracts++ + } + } + case *spec.ContractRangeResponse_Fin: + fmt.Println("Contract iteration ends", "contracts", contracts) + default: + // we expect no any other message only just one range because we break the iteration + t.Fatal("received unexpected message", "type", v) + } + + if contracts > 1000 { + break + } + } + + keys := make([]*felt.Felt, 0, len(ctso)) + stoCnt := make(map[felt.Felt]int) + for k := range maps.Keys(ctso) { + keys = append(keys, k.Clone()) + } + + for len(keys) > 10 { + var queries []*spec.StorageRangeQuery + for i := range 10 { + addr := keys[i] + queries = append(queries, &spec.StorageRangeQuery{ + Address: core2p2p.AdaptAddress(addr), + Start: &spec.StorageLeafQuery{ + ContractStorageRoot: core2p2p.AdaptHash(ctso[*addr]), + Key: core2p2p.AdaptFelt(felt.Zero.Clone()), + }, + End: nil, + }) + } + keys = keys[10:] + + sreq := &spec.ContractStorageRequest{ + StateRoot: core2p2p.AdaptHash(stateRoot), + ChunksPerProof: uint32(500), + Query: queries, + } + + iter, err := server.GetStorageRange(sreq) + require.NoError(t, err) + + for res := range iter { + assert.NotNil(t, res) + resT, ok := res.(*spec.ContractStorageResponse) + assert.True(t, ok) + assert.NotNil(t, resT) + + addr := p2p2core.AdaptAddress(resT.ContractAddress) + + switch v := resT.GetResponses().(type) { + case *spec.ContractStorageResponse_Storage: + vl := stoCnt[*addr] + //if !ok { stoCnt[*addr] = 0 } + stoCnt[*addr] = vl + len(v.Storage.KeyValue) + case *spec.ContractStorageResponse_Fin: + // we expect just one fin message at the iteration end + fmt.Println("End of iter", "no addr") + } + } + } + + for addr, cnt := range stoCnt { + if cnt > 100 { + fmt.Printf("[%5d]: address %s, storageRoot %s\n", cnt, &addr, ctso[addr]) + } + } +} + func TestGetContractStorageRoot(t *testing.T) { var d db.DB t.Skip("DB snapshot is needed for this test") @@ -364,74 +485,103 @@ func TestGetContractStorageRoot(t *testing.T) { } tests := []struct { - address *felt.Felt - storageRoot *felt.Felt + address *felt.Felt + storageRoot *felt.Felt + expectedLeaves int }{ { - address: feltFromString("0x5eb8d1bc5aaf2f323f2a807d429686ac012ca16f90740071d2f3a160dc231"), - storageRoot: feltFromString("0x0"), + address: feltFromString("0x2375219f8c73b77eef29d7cfd3749d64d2cca6ad7a776ed85bd33ff09201ea"), + storageRoot: feltFromString("0x24e11cc263e8d4c37519a95dc5cc4bc2627a991da6048b9a41f011e586cd3cc"), + expectedLeaves: 140, }, { - address: feltFromString("0x5ec87443bcb74e1e58762be15c3c513926a91a5d5b4a204e9e7b5ca884fb7"), - storageRoot: feltFromString("0x36fc3942926334a24b739065f26ffe547044af7466a6f8d391e0750603ffa8c"), + address: feltFromString("0xec1131fe035c235c03e0ad43646d8cbfd59d048b1825b0a36a167c468d5bf"), + storageRoot: feltFromString("0x21c409ca7f7d064d5e580e756cc945d3b266ab852e6d982697177e57d4c96a0"), + expectedLeaves: 193, }, { - address: feltFromString("0x614a5e0519963324acb5640321240827c0cd6a9f7cf5f17a80c1596e607d0"), - storageRoot: feltFromString("0x55ee7fd57d0aa3da8b89ea2feda16f9435186988a8b00b6f22f5ba39f3cf172"), + address: feltFromString("0xc41025be6d90828b1af119d384cecf1a57da8190ce79a2ffd925f02b59df"), + storageRoot: feltFromString("0x3f6b341ce4fb8441a0b350932a89cb19e195479e43ade9eb9e2fcde31a64680"), + expectedLeaves: 187, }, { - address: feltFromString("0x6b7d60ec8176d8a1c77afdca05191dad1e1a20fef2e5e3aceccee0b3cbd6a"), - storageRoot: feltFromString("0x726d42240f103a32ce1b6acc7498f52fdf83e308cf70e0a6394591cee1886c8"), + address: feltFromString("0xb5a14ddd6d1a6b33a10411e45bbff54f92265ede856cd0e12fee4a638c389"), + storageRoot: feltFromString("0x331a990cf32f6eedf01ca9b577cb75b53d937333729f48091945e255fff4a3d"), + expectedLeaves: 137, }, { - address: feltFromString("0x3deecdb26a60e4c062d5bd98ab37f72ea2acc37f28dae6923359627ebde9"), - storageRoot: feltFromString("0x276edbc91a945d11645ba0b8298c7d657e554d06ab2bb765cbc44d61fa01fd5"), + address: feltFromString("0x130b5a3035eef0470cff2f9a450a7a6856a3c5a4ea3f5b7886c2d03a50d2bf"), + storageRoot: feltFromString("0x4cf3afb0828518a24c0f2a5cc6e87d188df58b5faf40c6b81d6d476cf7897f6"), + expectedLeaves: 338, + }, + { + address: feltFromString("0x267311365224e8d4eb4dd580f1b737f990dfc81112ca71ecce147e774bcecb"), + storageRoot: feltFromString("0x71b0e71d2b69bfbfa25fd54e0cd3673f27f07110c3be72cb81c20aa0c6df4b0"), + expectedLeaves: 701, + }, + { + address: feltFromString("0x28f61c91275111e8c5af6febfa5c9d2191442b4fe48d30a84e51a09e8f18b5"), + storageRoot: feltFromString("0x1de1bf792cd9221c8d6ba2953671d6a4f0890375eca3718989082225dccb7eb"), + expectedLeaves: 540, }, } - for _, test := range tests { - t.Run(fmt.Sprintf("%.7s...", test.address), func(t *testing.T) { - request := &spec.ContractRangeRequest{ - ChunksPerProof: 10, - Start: core2p2p.AdaptAddress(test.address), - End: core2p2p.AdaptAddress(test.address), - StateRoot: core2p2p.AdaptHash(stateRoot), - } - - iter, err := server.GetContractRange(request) - assert.NoError(t, err) + ctso := make(map[felt.Felt]*felt.Felt) + stoCnt := make(map[felt.Felt]int) + + t.Run("Storage validation ", func(t *testing.T) { + queries := []*spec.StorageRangeQuery{} + for _, dt := range tests { + queries = append(queries, &spec.StorageRangeQuery{ + Address: core2p2p.AdaptAddress(dt.address), + Start: &spec.StorageLeafQuery{ + ContractStorageRoot: core2p2p.AdaptHash(dt.storageRoot), + Key: core2p2p.AdaptFelt(felt.Zero.Clone()), + }, + End: nil, + }) + ctso[*dt.address] = dt.storageRoot + } - finMsgReceived := false - for res := range iter { - assert.NotNil(t, res) - resT, ok := res.(*spec.ContractRangeResponse) - assert.True(t, ok) - assert.NotNil(t, resT) + // Contract Storage request contains queries for each tests' contract + // also note we specify `chainsPerProof` to be 200, which will be not enough + // to get all contract's keys in one iteration + sreq := &spec.ContractStorageRequest{ + StateRoot: core2p2p.AdaptHash(stateRoot), + ChunksPerProof: uint32(200), + Query: queries, + } - i := 0 - switch v := resT.GetResponses().(type) { - case *spec.ContractRangeResponse_Range: - assert.False(t, finMsgReceived) - assert.Len(t, v.Range.State, 1) - contract := v.Range.State[0] - fmt.Println("Contract:", p2p2core.AdaptAddress(contract.Address), "StorageRoot:", p2p2core.AdaptHash(contract.Storage)) - assert.Equal(t, test.address, p2p2core.AdaptAddress(contract.Address)) - assert.Equal(t, test.storageRoot, p2p2core.AdaptHash(contract.Storage)) - - for j, s := range v.Range.State { - fmt.Println("[", j, "] Contract:", p2p2core.AdaptAddress(s.Address), "StorageRoot:", p2p2core.AdaptHash(s.Storage)) - } - i++ - if i > 5 { - t.Fatal("Too many contracts received") - } - case *spec.ContractRangeResponse_Fin: - finMsgReceived = true - default: - // we expect no any other message only just one range because we break the iteration - t.Fatal("received unexpected message", "type", v) - } + iter, err := server.GetStorageRange(sreq) + require.NoError(t, err) + + // There will be one iteration for contracts where `expectedLeaves` < `chunksPerProof` + // and several iterations otherwise + // only at the end (all queries are responded) we will get `Fin` message + for res := range iter { + assert.NotNil(t, res) + resT, ok := res.(*spec.ContractStorageResponse) + assert.True(t, ok) + assert.NotNil(t, resT) + + switch v := resT.GetResponses().(type) { + case *spec.ContractStorageResponse_Storage: + addr := p2p2core.AdaptAddress(resT.ContractAddress) + vl := stoCnt[*addr] + stoCnt[*addr] = vl + len(v.Storage.KeyValue) + fmt.Printf("Response for %s: %d\n", addr, stoCnt[*addr]) + + case *spec.ContractStorageResponse_Fin: + // we expect just one fin message at the iteration end + fmt.Println("End of iter", "no addr") } + } + }) + + for _, test := range tests { + t.Run(fmt.Sprintf("%.7s...", test.address), func(t *testing.T) { + // validate storage leaves count + assert.Equal(t, test.expectedLeaves, stoCnt[*test.address]) }) } } From 093f36db3a31507e31582e2f06231a67e3b7eb80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowosielski?= Date: Mon, 23 Sep 2024 12:27:54 +0200 Subject: [PATCH 12/23] Storage ranges - fix unexpected contract change --- p2p/snap_server_test.go | 112 +++++++++++++++++++++++++++- p2p/snap_syncer.go | 160 ++++++++++++++++++++++++---------------- 2 files changed, 207 insertions(+), 65 deletions(-) diff --git a/p2p/snap_server_test.go b/p2p/snap_server_test.go index 52b769f715..3ed08995d2 100644 --- a/p2p/snap_server_test.go +++ b/p2p/snap_server_test.go @@ -150,6 +150,112 @@ func TestContractRange(t *testing.T) { assert.Equal(t, 1, chunksReceived) } +func TestContractRangeByOneContract(t *testing.T) { + var d db.DB + //t.Skip("DB snapshot is needed for this test") + d, _ = pebble.NewWithOptions("/Users/pnowosie/juno/snapshots/juno-sepolia", 128000000, 128, false) + defer func() { _ = d.Close() }() + bc := blockchain.New(d, &utils.Sepolia) // Needed because class loader need encoder to be registered + + b, err := bc.Head() + assert.NoError(t, err) + + fmt.Printf("headblock %d\n", b.Number) + + stateRoot := b.GlobalStateRoot + + logger, _ := utils.NewZapLogger(utils.DEBUG, false) + server := &snapServer{ + log: logger, + blockchain: bc, + } + + tests := []struct { + address *felt.Felt + expectedStorageRoot *felt.Felt + expectedClassHash *felt.Felt + expectedNonce uint64 + }{ + { + address: feltFromString("0x27b0a1ba755185b8d05126a1e00ca687e6680e51d634b5218760b716b8d06"), + expectedStorageRoot: feltFromString("0xa8d7943793ddd09e49b8650a71755ed04d0de087b28ad5967b519864f9844"), + expectedClassHash: feltFromString("0x772164c9d6179a89e7f1167f099219f47d752304b16ed01f081b6e0b45c93c3"), + expectedNonce: 0, + }, + { + address: feltFromString("0x292854fdd7653f65d8adc66739866567c212f2ef15ad8616e713eafc97e0a"), + expectedStorageRoot: feltFromString("0x718f57f8cd2950a0f240941876eafdffe86c84bd2601de4ea244956d96d85b6"), + expectedClassHash: feltFromString("0x29927c8af6bccf3f6fda035981e765a7bdbf18a2dc0d630494f8758aa908e2b"), + expectedNonce: 2, + }, + { + address: feltFromString("0xf3c569521d6ca43a0e2b86fd251031e2158aae502bf199f7eec986fe348f"), + expectedStorageRoot: feltFromString("0x41c2705457dfa3872cbc862ac86c85d118259154f08408c3cd350a15646d596"), + expectedClassHash: feltFromString("0x29927c8af6bccf3f6fda035981e765a7bdbf18a2dc0d630494f8758aa908e2b"), + expectedNonce: 1, + }, + { + address: feltFromString("0x5edd7ece0dc39633f9825e16e39e17a0bded7bf540a685876ceb75cdfd9eb"), + expectedStorageRoot: feltFromString("0x26f6e269f9462b6bf1649394aa4080d40ca4f4bc792bd0c1a72a8b524a93a9d"), + expectedClassHash: feltFromString("0x66559c86e66214ba1bc5d6512f6411aa066493e6086ff5d54f41a970d47fc5a"), + expectedNonce: 0, + }, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("%.7s...", test.address), func(t *testing.T) { + chunksPerProof := 5 + ctrIter, err := server.GetContractRange( + &spec.ContractRangeRequest{ + StateRoot: core2p2p.AdaptHash(stateRoot), + Start: core2p2p.AdaptAddress(test.address), + End: core2p2p.AdaptAddress(test.address), + ChunksPerProof: uint32(chunksPerProof), + }) + assert.NoError(t, err) + + finReceived := false + chunksReceived := 0 + + for res := range ctrIter { + assert.NotNil(t, res) + + resT, ok := res.(*spec.ContractRangeResponse) + assert.True(t, ok) + assert.NotNil(t, resT) + + switch v := resT.GetResponses().(type) { + case *spec.ContractRangeResponse_Range: + crctStates := v.Range.State + assert.Len(t, crctStates, 1) + + crct := crctStates[0] + address := p2p2core.AdaptAddress(crct.Address) + assert.Equal(t, test.address, address) + + storageRoot := p2p2core.AdaptHash(crct.Storage) + assert.Equal(t, test.expectedStorageRoot, storageRoot) + + classHash := p2p2core.AdaptHash(crct.Class) + assert.Equal(t, test.expectedClassHash, classHash, "classHash", classHash) + + assert.Equal(t, test.expectedNonce, crct.Nonce) + + chunksReceived++ + case *spec.ContractRangeResponse_Fin: + assert.Equal(t, 1, chunksReceived) + finReceived = true + default: + // we expect no any other message only just one range because we break the iteration + t.Fatal("received unexpected message", "type", v) + } + } + + assert.True(t, finReceived) + }) + } +} + func TestContractRange_FinMsg_Received(t *testing.T) { // TODO: Fix the test so it demonstrated FinMsg is returned at the iteration end t.Skip("Fix me") @@ -347,7 +453,7 @@ func TestGetClassesByHash(t *testing.T) { func Test__Finding_Storage_Heavy_Contract(t *testing.T) { var d db.DB - t.Skip("DB snapshot is needed for this test") + //t.Skip("DB snapshot is needed for this test") d, _ = pebble.NewWithOptions("/Users/pnowosie/juno/snapshots/juno-sepolia", 128000000, 128, false) defer func() { _ = d.Close() }() bc := blockchain.New(d, &utils.Sepolia) // Needed because class loader need encoder to be registered @@ -402,7 +508,7 @@ func Test__Finding_Storage_Heavy_Contract(t *testing.T) { t.Fatal("received unexpected message", "type", v) } - if contracts > 1000 { + if contracts > 100 { break } } @@ -458,7 +564,7 @@ func Test__Finding_Storage_Heavy_Contract(t *testing.T) { } for addr, cnt := range stoCnt { - if cnt > 100 { + if cnt <= 3 { fmt.Printf("[%5d]: address %s, storageRoot %s\n", cnt, &addr, ctso[addr]) } } diff --git a/p2p/snap_syncer.go b/p2p/snap_syncer.go index 7998b3c360..a05236562b 100644 --- a/p2p/snap_syncer.go +++ b/p2p/snap_syncer.go @@ -56,7 +56,8 @@ type SnapSyncher struct { storageRangeJobQueue chan *storageRangeJob storageRefreshJob chan *storageRangeJob - classesJob chan *felt.Felt + classFetchJobCount int32 + classesJob chan *felt.Felt // Three lock priority lock mtxM *sync.Mutex @@ -67,11 +68,11 @@ type SnapSyncher struct { var _ service.Service = (*SnapSyncher)(nil) type storageRangeJob struct { - path *felt.Felt - storageRoot *felt.Felt - startAddress *felt.Felt - classHash *felt.Felt - nonce uint64 + path *felt.Felt + storageRoot *felt.Felt + startKey *felt.Felt + classHash *felt.Felt + nonce uint64 } func NewSnapSyncer( @@ -111,15 +112,15 @@ var ( var ( storageJobWorker = 8 - storageBatchSize = 500 + storageBatchSize = 50 storageJobQueueSize = storageJobWorker * storageBatchSize // Too high and the progress from address range would be inaccurate. // For some reason, the trie throughput is higher if the batch size is small. classRangeChunksPerProof = 500 contractRangeChunkPerProof = 501 - storageRangeChunkPerProof = 502 - maxStorageBatchSize = 500 - maxMaxPerStorageSize = 500 + storageRangeChunkPerProof = 1000 + maxStorageBatchSize = 1000 + maxMaxPerStorageSize = 1000 fetchClassWorkerCount = 8 // Fairly parallelizable. But this is brute force... classesJobQueueSize = 128 @@ -265,7 +266,7 @@ func (s *SnapSyncher) runPhase1(ctx context.Context) error { for i := 0; i < fetchClassWorkerCount; i++ { i := i eg.Go(func() error { - err := s.runFetchClassJob(ectx) + err := s.runFetchClassWorker(ectx, i) if err != nil { s.log.Errorw("fetch class failed", "err", err) } @@ -362,10 +363,13 @@ func (s *SnapSyncher) runClassRangeWorker(ctx context.Context) error { ChunksPerProof: uint32(classRangeChunksPerProof), }) if err != nil { - s.log.Errorw("error getting classes from client", "err", err) - continue + s.log.Errorw("error getting class range from client", "err", err) + // retry policy? with increased intervals + // reason for stopping - it's irrecoverable - we don't have a peer + return err } + ResponseIter: for response := range classIter { if response == nil { if response == nil { @@ -380,7 +384,7 @@ func (s *SnapSyncher) runClassRangeWorker(ctx context.Context) error { classes = v.Classes.Classes case *spec.ClassRangeResponse_Fin: s.log.Infow("[finMsg] class range completed") - break + break ResponseIter default: s.log.Warnw("Unexpected class range message", "GetResponses", v) continue @@ -482,7 +486,7 @@ func (s *SnapSyncher) runClassRangeWorker(ctx context.Context) error { } //nolint:gocyclo -func (s *SnapSyncher) runFetchClassJob(ctx context.Context) error { +func (s *SnapSyncher) runFetchClassWorker(ctx context.Context, workerIdx int) error { keyBatches := make([]*felt.Felt, 0) for { requestloop: @@ -495,7 +499,7 @@ func (s *SnapSyncher) runFetchClassJob(ctx context.Context) error { if len(keyBatches) > 0 { break requestloop } - s.log.Infow("waiting for more class job", "count", s.storageRangeJobCount) + s.log.Infow("waiting for more class job", "worker", workerIdx, "count", s.classFetchJobCount) case key := <-s.classesJob: if key == nil { // channel finished. @@ -510,7 +514,7 @@ func (s *SnapSyncher) runFetchClassJob(ctx context.Context) error { continue } - // TODO: Can be done in batches + // TODO: Can be done in batches, Note: return nil if class is not found, no error cls, err := s.blockchain.GetClasses([]*felt.Felt{key}) if err != nil { s.log.Errorw("error getting class", "err", err) @@ -521,6 +525,7 @@ func (s *SnapSyncher) runFetchClassJob(ctx context.Context) error { keyBatches = append(keyBatches, key) } } + atomic.AddInt32(&s.classFetchJobCount, -1) } } @@ -533,11 +538,13 @@ func (s *SnapSyncher) runFetchClassJob(ctx context.Context) error { ClassHashes: hashes, }) if err != nil { - s.log.Errorw("error getting classes from client", "err", err) - continue + s.log.Errorw("error fetching classes by hash from client", "err", err) + // retry? + return err } classes := make([]*spec.Class, 0, len(keyBatches)) + ResponseIter: for response := range classIter { if response == nil { s.log.Errorw("class by keys respond with nil response") @@ -548,13 +555,13 @@ func (s *SnapSyncher) runFetchClassJob(ctx context.Context) error { case *spec.ClassesResponse_Class: classes = append(classes, v.Class) case *spec.ClassesResponse_Fin: - s.log.Infow("class batch completed", "classes", len(classes)) - break + s.log.Infow("[FinMsg] class batch completed", "classes", len(classes), "workers", workerIdx) + break ResponseIter default: s.log.Warnw("Unexpected ClassMessage from getClasses", "v", v) } } - s.log.Infow("class fetch job received response", "classes", len(classes), "asked keys", len(keyBatches)) + s.log.Infow("class fetch job received response", "classes", len(classes), "asked keys", len(keyBatches), "worker", workerIdx) processedClasses := map[felt.Felt]bool{} newClasses := map[felt.Felt]core.Class{} @@ -624,9 +631,11 @@ func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { }) if err != nil { s.log.Errorw("error getting contract range from client", "err", err) - continue + // retry? + return err } + ResponseIter: for response := range iter { if response == nil { s.log.Errorw("contract range respond with nil response") @@ -642,7 +651,7 @@ func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { crange = v.Range case *spec.ContractRangeResponse_Fin: s.log.Infow("[finMsg] contract range completed") - break + break ResponseIter default: s.log.Warnw("Unexpected contract range message", "GetResponses", v) continue @@ -775,13 +784,16 @@ func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) ContractStorageRoot: core2p2p.AdaptHash(job.storageRoot), // TODO: Should be address - Key: core2p2p.AdaptFelt(job.startAddress), + Key: core2p2p.AdaptFelt(job.startKey), }, }) } stateRoot := s.currentGlobalStateRoot - processedJobs := 0 + processedJobs := struct { + jobIdx int + address *felt.Felt + }{} storage := map[felt.Felt]map[felt.Felt]*felt.Felt{} totalPath := 0 maxPerStorageSize := 0 @@ -800,12 +812,14 @@ func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) s.log.Errorw("Error with storage range request", "err", err) // Well... need to figure out how to determine if its a temporary error or not. // For sure, the state root can be outdated, so this need to restart - continue + return err } + ResponseIter: for response := range iter { if response == nil { - s.log.Errorw("storage range respond with nil response") + s.log.Errorw("storage range respond with nil response", + "worker", workerIdx, "job", processedJobs.jobIdx) continue } @@ -814,31 +828,50 @@ func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) case *spec.ContractStorageResponse_Storage: csto = v.Storage case *spec.ContractStorageResponse_Fin: - s.log.Infow("storage range completed", "totalPath", totalPath) - break + s.log.Infow("[FinMsg] storage range completed", + "totalPath", totalPath, "worker", workerIdx, "jobs", processedJobs.jobIdx) + break ResponseIter default: s.log.Warnw("Unexpected storage range message", "GetResponses", v) continue } if csto == nil || csto.KeyValue == nil { - s.log.Errorw("storage range respond with nil storage") + s.log.Errorw("storage range respond with nil storage", + "worker", workerIdx, "job", processedJobs.jobIdx, + "address", p2p2core.AdaptAddress(response.ContractAddress), + "job addr", processedJobs.address) continue } - storageRange := csto.KeyValue - if response.RangeProof == nil { - s.log.Errorw("storage range respond with nil proof") + s.log.Errorw("storage range respond with nil proof", + "worker", workerIdx, "job", processedJobs.jobIdx, + "address", p2p2core.AdaptAddress(response.ContractAddress), + "job addr", processedJobs.address) continue } - job := jobs[processedJobs] - storageAddr := p2p2core.AdaptAddress(response.ContractAddress) + storageRange := csto.KeyValue + if processedJobs.address == nil { + processedJobs.address = storageAddr + } + + job := jobs[processedJobs.jobIdx] if !job.path.Equal(storageAddr) { - s.log.Errorw(fmt.Sprintf( - "storage addr differ %s %s %d\n", job.path, storageAddr, workerIdx)) - continue + s.log.Infow("[Missed response?] storage chunks completed", + "address", storageAddr, "worker", workerIdx, "job", processedJobs.jobIdx) + // move to the next job + processedJobs.jobIdx++ + job = jobs[processedJobs.jobIdx] + + // sanity check + if !job.path.Equal(storageAddr) { + s.log.Errorw("Sanity check: next job does not match response", + "job addr", job.path, "got", storageAddr, "worker", workerIdx) + // what to do? + } + processedJobs.address = storageAddr } // Validate response @@ -853,6 +886,8 @@ func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) proofs := P2pProofToTrieProofs(response.RangeProof) hasNext, err := VerifyTrie(job.storageRoot, paths, values, proofs, core.ContractStorageTrieHeight, crypto.Pedersen) if err != nil { + s.log.Errorw("trie verification failed", + "contract", storageAddr, "expected root", job.storageRoot, "err", err) // It is unclear how to distinguish if the peer is malicious/broken/non-bizantine or the contracts root is outdated. err = s.queueStorageRefreshJob(ctx, job) if err != nil { @@ -860,8 +895,8 @@ func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) return err } - // Go to next contract - processedJobs++ + // Ok, what now - we cannot just move on to next job, because server will + // still respond to this contract until it exhaust all leaves continue } @@ -893,9 +928,11 @@ func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) } if hasNext { - job.startAddress = paths[len(paths)-1] + // note this is just tracking leaves keys on our side + // but it cannot change the request. Maybe can be used for retry procedure + job.startKey = paths[len(paths)-1] } else { - processedJobs++ + processedJobs.jobIdx++ atomic.AddInt32(&s.storageRangeJobCount, -1) // its... done? } } @@ -909,10 +946,12 @@ func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) // TODO: assign to nil to clear memory nextjobs = make([]*storageRangeJob, 0) - for i := processedJobs; i < len(jobs); i++ { + for i := processedJobs.jobIdx; i < len(jobs); i++ { unprocessedRequest := jobs[i] nextjobs = append(nextjobs, unprocessedRequest) } + processedJobs.jobIdx = 0 + processedJobs.address = nil } } @@ -946,24 +985,20 @@ func (s *SnapSyncher) runStorageRefreshWorker(ctx context.Context) error { } } - bigIntAdd := job.startAddress.BigInt(&big.Int{}) - bigIntAdd = (&big.Int{}).Add(bigIntAdd, big.NewInt(1)) - elem := fp.NewElement(0) - limitAddr := felt.NewFelt((&elem).SetBigInt(bigIntAdd)) - stateRoot := s.currentGlobalStateRoot ctrIter, err := s.client.RequestContractRange(ctx, &spec.ContractRangeRequest{ Domain: 0, // What do this do? StateRoot: core2p2p.AdaptHash(stateRoot), - Start: core2p2p.AdaptAddress(job.startAddress), - End: core2p2p.AdaptAddress(limitAddr), - ChunksPerProof: 10000, + Start: core2p2p.AdaptAddress(job.path), + End: core2p2p.AdaptAddress(job.path), + ChunksPerProof: 10, }) if err != nil { - s.log.Errorw("Error with contract range request", "err", err) - continue + s.log.Errorw("Error with contract range refresh request", "err", err) + return err } + ResponseIter: for response := range ctrIter { if response == nil { s.log.Errorw("contract range [storage refresh] respond with nil response") @@ -976,7 +1011,7 @@ func (s *SnapSyncher) runStorageRefreshWorker(ctx context.Context) error { crange = v.Range case *spec.ContractRangeResponse_Fin: s.log.Infow("[finMsg] contract range [storage refresh] completed") - break + break ResponseIter default: s.log.Warnw("Unexpected contract range message [storage refresh]", "GetResponses", v) continue @@ -1044,10 +1079,11 @@ func (s *SnapSyncher) queueClassJob(ctx context.Context, classHash *felt.Felt) e for !queued { select { case s.classesJob <- classHash: + atomic.AddInt32(&s.classFetchJobCount, 1) queued = true case <-ctx.Done(): return ctx.Err() - case <-time.After(time.Second): + case <-time.After(30 * time.Second): s.log.Infow("class queue stall on class") } } @@ -1056,11 +1092,11 @@ func (s *SnapSyncher) queueClassJob(ctx context.Context, classHash *felt.Felt) e func (s *SnapSyncher) queueStorageRangeJob(ctx context.Context, path, storageRoot, classHash *felt.Felt, nonce uint64) error { return s.queueStorageRangeJobJob(ctx, &storageRangeJob{ - path: path, - storageRoot: storageRoot, - startAddress: &felt.Zero, - classHash: classHash, - nonce: nonce, + path: path, + storageRoot: storageRoot, + startKey: &felt.Zero, + classHash: classHash, + nonce: nonce, }) } @@ -1089,7 +1125,7 @@ func (s *SnapSyncher) queueStorageRefreshJob(ctx context.Context, job *storageRa atomic.AddInt32(&s.storageRangeJobCount, 1) case <-ctx.Done(): return ctx.Err() - case <-time.After(time.Second): + case <-time.After(10 * time.Second): s.log.Infow("storage refresh queue stall") } } From e08a36e2bad2bcf26d4ae96045c522849f0fc62f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowosielski?= Date: Wed, 25 Sep 2024 13:28:29 +0200 Subject: [PATCH 13/23] Tweak message size params, trim logs --- core/trie/snap_support.go | 1 - p2p/snap_server.go | 5 +- p2p/snap_server_test.go | 68 ++++++++++++++++++++++-- p2p/snap_syncer.go | 107 +++++++++++++++++++++----------------- p2p/starknetdata.go | 4 +- 5 files changed, 127 insertions(+), 58 deletions(-) diff --git a/core/trie/snap_support.go b/core/trie/snap_support.go index 4f6fdb2061..6caf6b834d 100644 --- a/core/trie/snap_support.go +++ b/core/trie/snap_support.go @@ -86,7 +86,6 @@ func (t *Trie) IterateWithLimit( count++ if count >= maxNodes { - logger.Infow("Max nodes reached", "count", count) return false, nil } return true, nil diff --git a/p2p/snap_server.go b/p2p/snap_server.go index a6ae0487c6..c9e3390b57 100644 --- a/p2p/snap_server.go +++ b/p2p/snap_server.go @@ -290,9 +290,10 @@ func (b *snapServer) GetStorageRange(request *spec.ContractStorageRequest) (iter var finMsg proto.Message = &spec.ContractStorageResponse{ Responses: &spec.ContractStorageResponse_Fin{}, } - stateRoot := p2p2core.AdaptHash(request.StateRoot) startKey := p2p2core.AdaptAddress(request.Query[0].Address) - b.log.Debugw("GetStorageRange", "root", stateRoot, "start[0]", startKey) + last := len(request.Query) - 1 + endKey := p2p2core.AdaptAddress(request.Query[last].Address) + b.log.Debugw("GetStorageRange", "query[0]", startKey, "query[", last, "]", endKey) return func(yield yieldFunc) { stateRoot := p2p2core.AdaptHash(request.StateRoot) diff --git a/p2p/snap_server_test.go b/p2p/snap_server_test.go index 3ed08995d2..374a2db3b6 100644 --- a/p2p/snap_server_test.go +++ b/p2p/snap_server_test.go @@ -152,7 +152,7 @@ func TestContractRange(t *testing.T) { func TestContractRangeByOneContract(t *testing.T) { var d db.DB - //t.Skip("DB snapshot is needed for this test") + t.Skip("DB snapshot is needed for this test") d, _ = pebble.NewWithOptions("/Users/pnowosie/juno/snapshots/juno-sepolia", 128000000, 128, false) defer func() { _ = d.Close() }() bc := blockchain.New(d, &utils.Sepolia) // Needed because class loader need encoder to be registered @@ -200,6 +200,26 @@ func TestContractRangeByOneContract(t *testing.T) { expectedClassHash: feltFromString("0x66559c86e66214ba1bc5d6512f6411aa066493e6086ff5d54f41a970d47fc5a"), expectedNonce: 0, }, + { + address: feltFromString("0x4c04ec7c3c5a82df2d194095f090af83a9f26e22544d968c3d67c1b320d43"), + expectedStorageRoot: feltFromString("0x0"), + expectedNonce: 0, + }, + { + address: feltFromString("0x4ccd60176f9e757031f04691beb09832a0ea583eeb5158b05277547957514"), + expectedStorageRoot: feltFromString("0x0"), + expectedNonce: 1, + }, + { + address: feltFromString("0xd94fd19a7730f84df43999562cbbf5cf8d48a6cb92f5bc5d6795f34c15f72"), + expectedStorageRoot: feltFromString("0x0"), + expectedNonce: 0, + }, + { + address: feltFromString("0xdd92645559c6dca08c6e947b4a40a55142a0a8b65552be8b31c885f37ef87"), + expectedStorageRoot: feltFromString("0x0"), + expectedNonce: 1, + }, } for _, test := range tests { @@ -236,8 +256,8 @@ func TestContractRangeByOneContract(t *testing.T) { storageRoot := p2p2core.AdaptHash(crct.Storage) assert.Equal(t, test.expectedStorageRoot, storageRoot) - classHash := p2p2core.AdaptHash(crct.Class) - assert.Equal(t, test.expectedClassHash, classHash, "classHash", classHash) + //classHash := p2p2core.AdaptHash(crct.Class) + //assert.Equal(t, test.expectedClassHash, classHash, "classHash", classHash) assert.Equal(t, test.expectedNonce, crct.Nonce) @@ -453,7 +473,7 @@ func TestGetClassesByHash(t *testing.T) { func Test__Finding_Storage_Heavy_Contract(t *testing.T) { var d db.DB - //t.Skip("DB snapshot is needed for this test") + t.Skip("DB snapshot is needed for this test") d, _ = pebble.NewWithOptions("/Users/pnowosie/juno/snapshots/juno-sepolia", 128000000, 128, false) defer func() { _ = d.Close() }() bc := blockchain.New(d, &utils.Sepolia) // Needed because class loader need encoder to be registered @@ -630,6 +650,46 @@ func TestGetContractStorageRoot(t *testing.T) { storageRoot: feltFromString("0x1de1bf792cd9221c8d6ba2953671d6a4f0890375eca3718989082225dccb7eb"), expectedLeaves: 540, }, + { + address: feltFromString("0x4c04ec7c3c5a82df2d194095f090af83a9f26e22544d968c3d67c1b320d43"), + storageRoot: feltFromString("0x0"), + expectedLeaves: 0, + }, + { + address: feltFromString("0x4ccd60176f9e757031f04691beb09832a0ea583eeb5158b05277547957514"), + storageRoot: feltFromString("0x0"), + expectedLeaves: 1, + }, + { + address: feltFromString("0xd94fd19a7730f84df43999562cbbf5cf8d48a6cb92f5bc5d6795f34c15f72"), + storageRoot: feltFromString("0x0"), + expectedLeaves: 0, + }, + { + address: feltFromString("0xdd92645559c6dca08c6e947b4a40a55142a0a8b65552be8b31c885f37ef87"), + storageRoot: feltFromString("0x0"), + expectedLeaves: 1, + }, + { + address: feltFromString("0x807dd1766d3c833ac82290e38a000b7d48acce3cf125cffde15ea9e583a95"), + storageRoot: feltFromString("0x0"), + expectedLeaves: 0, + }, + { + address: feltFromString("0x8502b172cb17395511c4bfabb7ded748cd930fec8201ad6b444f9d6a9df7a"), + storageRoot: feltFromString("0x0"), + expectedLeaves: 1, + }, + { + address: feltFromString("0x5788cee963fe68a76b1ef9b04f1ba404043d853ad593c366c723322381b14"), + storageRoot: feltFromString("0x0"), + expectedLeaves: 0, + }, + { + address: feltFromString("0x5e8dab06aaf28538be2077fddd679cac934c5e082303f892235fb989e800c"), + storageRoot: feltFromString("0x0"), + expectedLeaves: 1, + }, } ctso := make(map[felt.Felt]*felt.Felt) diff --git a/p2p/snap_syncer.go b/p2p/snap_syncer.go index a05236562b..b3ac7f7b5c 100644 --- a/p2p/snap_syncer.go +++ b/p2p/snap_syncer.go @@ -111,19 +111,20 @@ var ( ) var ( - storageJobWorker = 8 - storageBatchSize = 50 - storageJobQueueSize = storageJobWorker * storageBatchSize // Too high and the progress from address range would be inaccurate. + storageJobWorkerCount = 4 + storageBatchSize = 10 + storageJobQueueSize = storageJobWorkerCount * storageBatchSize // Too high and the progress from address range would be inaccurate. // For some reason, the trie throughput is higher if the batch size is small. - classRangeChunksPerProof = 500 - contractRangeChunkPerProof = 501 - storageRangeChunkPerProof = 1000 + classRangeChunksPerProof = 50 + contractRangeChunkPerProof = 150 + storageRangeChunkPerProof = 300 maxStorageBatchSize = 1000 maxMaxPerStorageSize = 1000 - fetchClassWorkerCount = 8 // Fairly parallelizable. But this is brute force... + fetchClassWorkerCount = 4 // Fairly parallelizable. But this is brute force... classesJobQueueSize = 128 + classBatchSize = 50 maxPivotDistance = 32 // Set to 1 to test updated storage. newPivotHeadDistance = uint64(0) // This should be the reorg depth @@ -217,7 +218,7 @@ func (s *SnapSyncher) runPhase1(ctx context.Context) error { }) storageEg, sctx := errgroup.WithContext(ectx) - for i := 0; i < storageJobWorker; i++ { + for i := 0; i < storageJobWorkerCount; i++ { i := i storageEg.Go(func() error { defer func() { @@ -348,7 +349,7 @@ func calculatePercentage(f *felt.Felt) uint64 { //nolint:gocyclo,nolintlint func (s *SnapSyncher) runClassRangeWorker(ctx context.Context) error { - totaladded := 0 + totalAdded := 0 completed := false startAddr := &felt.Zero @@ -389,7 +390,6 @@ func (s *SnapSyncher) runClassRangeWorker(ctx context.Context) error { s.log.Warnw("Unexpected class range message", "GetResponses", v) continue } - s.log.Infow("class range worker received response", "classes", len(classes)) if classes == nil || len(classes) == 0 { s.log.Errorw("class range respond with empty classes") @@ -400,8 +400,6 @@ func (s *SnapSyncher) runClassRangeWorker(ctx context.Context) error { continue } - s.log.Infow("got", "res", len(classes), "startAdr", startAddr) - classRoot := p2p2core.AdaptHash(response.ClassesRoot) contractRoot := p2p2core.AdaptHash(response.ContractsRoot) err = VerifyGlobalStateRoot(stateRoot, classRoot, contractRoot) @@ -413,14 +411,7 @@ func (s *SnapSyncher) runClassRangeWorker(ctx context.Context) error { } s.log.Infow("class range progress", "progress", calculatePercentage(startAddr)) - s.log.Infow("class range state root", "stateroot", stateRoot) - - if classRoot.Equal(&felt.Zero) { - // Special case, no V1 at all - s.log.Infow("class range completed", "totalClass", totaladded) - completed = true - return nil - } + s.log.Infow("class range info", "classes", len(classes), "totalAdded", totalAdded, "startAddr", startAddr) paths := make([]*felt.Felt, len(classes)) values := make([]*felt.Felt, len(classes)) @@ -447,6 +438,7 @@ func (s *SnapSyncher) runClassRangeWorker(ctx context.Context) error { s.log.Infow("class range adaptation failure", "err", err) return err } + s.log.Infow("class range adaptation completed", "classes", len(classes)) proofs := P2pProofToTrieProofs(response.RangeProof) hasNext, err := VerifyTrie(classRoot, paths, values, proofs, core.GlobalTrieHeight, crypto.Poseidon) @@ -469,9 +461,11 @@ func (s *SnapSyncher) runClassRangeWorker(ctx context.Context) error { fmt.Printf("Unable to update the chain %s\n", err) panic(err) } + totalAdded += len(classes) + s.log.Infow("class range added classes into state", "classes", len(classes), "total", totalAdded) if !hasNext { - s.log.Infow("class range completed", "totalClass", totaladded) + s.log.Infow("class range completed", "totalClass", totalAdded) completed = true return nil } @@ -488,9 +482,10 @@ func (s *SnapSyncher) runClassRangeWorker(ctx context.Context) error { //nolint:gocyclo func (s *SnapSyncher) runFetchClassWorker(ctx context.Context, workerIdx int) error { keyBatches := make([]*felt.Felt, 0) + s.log.Infow("class fetch worker entering infinite loop", "worker", workerIdx) for { requestloop: - for len(keyBatches) < 100 { + for len(keyBatches) < classBatchSize { select { case <-ctx.Done(): return ctx.Err() @@ -499,7 +494,7 @@ func (s *SnapSyncher) runFetchClassWorker(ctx context.Context, workerIdx int) er if len(keyBatches) > 0 { break requestloop } - s.log.Infow("waiting for more class job", "worker", workerIdx, "count", s.classFetchJobCount) + s.log.Infow("waiting for more class job", "worker", workerIdx, "pendind", s.classFetchJobCount) case key := <-s.classesJob: if key == nil { // channel finished. @@ -528,6 +523,7 @@ func (s *SnapSyncher) runFetchClassWorker(ctx context.Context, workerIdx int) er atomic.AddInt32(&s.classFetchJobCount, -1) } } + s.log.Infow("class fetch job completes batch", "asked keys", len(keyBatches), "worker", workerIdx, "pending", s.classFetchJobCount) var hashes []*spec.Hash for _, key := range keyBatches { @@ -555,13 +551,12 @@ func (s *SnapSyncher) runFetchClassWorker(ctx context.Context, workerIdx int) er case *spec.ClassesResponse_Class: classes = append(classes, v.Class) case *spec.ClassesResponse_Fin: - s.log.Infow("[FinMsg] class batch completed", "classes", len(classes), "workers", workerIdx) + s.log.Infow("[FinMsg] class batch completed", "classes", len(classes), "worker", workerIdx) break ResponseIter default: s.log.Warnw("Unexpected ClassMessage from getClasses", "v", v) } } - s.log.Infow("class fetch job received response", "classes", len(classes), "asked keys", len(keyBatches), "worker", workerIdx) processedClasses := map[felt.Felt]bool{} newClasses := map[felt.Felt]core.Class{} @@ -598,6 +593,7 @@ func (s *SnapSyncher) runFetchClassWorker(ctx context.Context, workerIdx int) er s.log.Errorw("error storing class", "err", err) return err } + s.log.Infow("class fetch job added classes into state", "classes", len(newClasses), "worker", workerIdx) } else { s.log.Errorw("Unable to fetch any class from peer") // TODO: Penalise peer? @@ -611,11 +607,13 @@ func (s *SnapSyncher) runFetchClassWorker(ctx context.Context, workerIdx int) er } keyBatches = newBatch + s.log.Infow("class fetch job completed batch", "processed", len(processedClasses), "unprocessed", len(newBatch), "worker", workerIdx) } } //nolint:gocyclo func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { + totalAdded := 0 startAddr := &felt.Zero completed := false @@ -642,21 +640,17 @@ func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { continue } - s.log.Infow("snap range progress", "progress", calculatePercentage(startAddr), "addr", startAddr) - rangeProgress.Set(float64(calculatePercentage(startAddr))) - var crange *spec.ContractRange switch v := response.GetResponses().(type) { case *spec.ContractRangeResponse_Range: crange = v.Range case *spec.ContractRangeResponse_Fin: - s.log.Infow("[finMsg] contract range completed") + s.log.Infow("[finMsg] contract range completed", "totalAdded", totalAdded) break ResponseIter default: s.log.Warnw("Unexpected contract range message", "GetResponses", v) continue } - s.log.Infow("contract range worker received response", "states", len(crange.State)) if crange == nil || crange.State == nil { s.log.Errorw("contract range respond with nil state") @@ -667,6 +661,10 @@ func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { continue } + s.log.Infow("contract range progress", "progress", calculatePercentage(startAddr)) + s.log.Infow("contract range info", "states", len(crange.State), "totalAdded", totalAdded, "startAddr", startAddr) + rangeProgress.Set(float64(calculatePercentage(startAddr))) + classRoot := p2p2core.AdaptHash(response.ClassesRoot) contractRoot := p2p2core.AdaptHash(response.ContractsRoot) err := VerifyGlobalStateRoot(stateRoot, classRoot, contractRoot) @@ -679,9 +677,9 @@ func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { paths := make([]*felt.Felt, len(crange.State)) values := make([]*felt.Felt, len(crange.State)) - for i, rangeValue := range crange.State { - paths[i] = p2p2core.AdaptAddress(rangeValue.Address) - values[i] = CalculateRangeValueHash(rangeValue) + for i, state := range crange.State { + paths[i] = p2p2core.AdaptAddress(state.Address) + values[i] = CalculateContractStateHash(state) } proofs := P2pProofToTrieProofs(response.RangeProof) @@ -691,6 +689,7 @@ func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { s.log.Infow("trie verification failed", "err", err) return err } + s.log.Infow("contract range adaptation completed", "hasNext", hasNext, "states", len(paths), "totalAdded", totalAdded) classes := []*felt.Felt{} nonces := []*felt.Felt{} @@ -705,6 +704,8 @@ func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { fmt.Printf("Unable to update the chain %s\n", err) panic(err) } + totalAdded += len(paths) + s.log.Infow("contract range added contracts into state", "contracts", len(paths), "totalAdded", totalAdded) // We don't actually store it directly here... only put it as part of job. // Can't remember why. Could be because it would be some wasted work. @@ -767,14 +768,14 @@ func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) if len(jobs) > 0 { break requestloop } - s.log.Infow("waiting for more storage job", "count", s.storageRangeJobCount) + s.log.Infow("waiting for more storage job", "len(jobs)", len(jobs), "worker", workerIdx, "pending", s.storageRangeJobCount) case <-contractDoneChecker: // Its done... return nil } } - s.log.Infow("Pending jobs count", "pending", s.storageRangeJobCount) + //s.log.Infow("storage range job completes batch", "jobs", len(jobs), "worker", workerIdx, "pending", s.storageRangeJobCount) requests := make([]*spec.StorageRangeQuery, 0) for _, job := range jobs { @@ -792,17 +793,19 @@ func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) stateRoot := s.currentGlobalStateRoot processedJobs := struct { jobIdx int + jobAddr *felt.Felt address *felt.Felt }{} storage := map[felt.Felt]map[felt.Felt]*felt.Felt{} totalPath := 0 maxPerStorageSize := 0 - s.log.Infow("storage range", - "rootDistance", s.lastBlock.Number-s.startingBlock.Number, - "root", stateRoot.String(), - "requestcount", len(requests), - ) + //s.log.Infow("storage range", + // "rootDistance", s.lastBlock.Number-s.startingBlock.Number, + // "root", stateRoot.String(), + // "requestcount", len(requests), + // "worker", workerIdx, + //) iter, err := s.client.RequestStorageRange(ctx, &spec.ContractStorageRequest{ StateRoot: core2p2p.AdaptHash(stateRoot), ChunksPerProof: uint32(storageRangeChunkPerProof), @@ -829,7 +832,8 @@ func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) csto = v.Storage case *spec.ContractStorageResponse_Fin: s.log.Infow("[FinMsg] storage range completed", - "totalPath", totalPath, "worker", workerIdx, "jobs", processedJobs.jobIdx) + "totalPath", totalPath, "worker", workerIdx, "jobs", processedJobs.jobIdx, + "pending", s.storageRangeJobCount, "contract", processedJobs.jobAddr) break ResponseIter default: s.log.Warnw("Unexpected storage range message", "GetResponses", v) @@ -853,14 +857,13 @@ func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) storageAddr := p2p2core.AdaptAddress(response.ContractAddress) storageRange := csto.KeyValue - if processedJobs.address == nil { - processedJobs.address = storageAddr - } + processedJobs.address = storageAddr job := jobs[processedJobs.jobIdx] + processedJobs.jobAddr = job.path if !job.path.Equal(storageAddr) { s.log.Infow("[Missed response?] storage chunks completed", - "address", storageAddr, "worker", workerIdx, "job", processedJobs.jobIdx) + "address", storageAddr, "jobAddr", processedJobs.jobAddr, "worker", workerIdx, "job", processedJobs.jobIdx) // move to the next job processedJobs.jobIdx++ job = jobs[processedJobs.jobIdx] @@ -872,6 +875,7 @@ func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) // what to do? } processedJobs.address = storageAddr + processedJobs.jobAddr = job.path } // Validate response @@ -952,6 +956,7 @@ func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) } processedJobs.jobIdx = 0 processedJobs.address = nil + processedJobs.jobAddr = nil } } @@ -975,7 +980,7 @@ func (s *SnapSyncher) runStorageRefreshWorker(ctx context.Context) error { case <-ctx.Done(): return ctx.Err() case <-time.After(JobDuration): - s.log.Infow("waiting for more refresh job", "count", s.storageRangeJobCount) + s.log.Infow("no storagge refresh job") case <-contractDoneChecker: // Its done... return nil @@ -1041,7 +1046,7 @@ func (s *SnapSyncher) runStorageRefreshWorker(ctx context.Context) error { for i, rangeValue := range crange.State { paths[i] = p2p2core.AdaptAddress(rangeValue.Address) - values[i] = CalculateRangeValueHash(rangeValue) + values[i] = CalculateContractStateHash(rangeValue) } proofs := P2pProofToTrieProofs(response.RangeProof) @@ -1101,6 +1106,11 @@ func (s *SnapSyncher) queueStorageRangeJob(ctx context.Context, path, storageRoo } func (s *SnapSyncher) queueStorageRangeJobJob(ctx context.Context, job *storageRangeJob) error { + if job.storageRoot == nil || job.storageRoot.IsZero() { + // contract's with storage root of 0x0 has no storage + return nil + } + queued := false for !queued { select { @@ -1122,7 +1132,6 @@ func (s *SnapSyncher) queueStorageRefreshJob(ctx context.Context, job *storageRa select { case s.storageRefreshJob <- job: queued = true - atomic.AddInt32(&s.storageRangeJobCount, 1) case <-ctx.Done(): return ctx.Err() case <-time.After(10 * time.Second): @@ -1246,7 +1255,7 @@ func CalculateCompiledClassHash(cls core.Class) *felt.Felt { return cls.(*core.Cairo1Class).Compiled.Hash() } -func CalculateRangeValueHash(value *spec.ContractState) *felt.Felt { +func CalculateContractStateHash(value *spec.ContractState) *felt.Felt { nonce := fp.NewElement(value.Nonce) return calculateContractCommitment( p2p2core.AdaptHash(value.Storage), diff --git a/p2p/starknetdata.go b/p2p/starknetdata.go index b561a5f275..f6e8736cff 100644 --- a/p2p/starknetdata.go +++ b/p2p/starknetdata.go @@ -21,11 +21,11 @@ func (m MockStarkData) BlockByNumber(ctx context.Context, blockNumber uint64) (* func (m MockStarkData) BlockLatest(ctx context.Context) (*core.Block, error) { // This is snapshot I have - root, _ := (&felt.Felt{}).SetString("0x6df37678051ab529c243a5ae08e95eea4ddb40b874b4c537e2e6a9a459e2548") + root, _ := (&felt.Felt{}).SetString("0x472e84b65d387c9364b5117f4afaba3fb88897db1f28867b398506e2af89f25") return &core.Block{ Header: &core.Header{ - Number: uint64(66489), + Number: uint64(66477), GlobalStateRoot: root, }, }, nil From c43113f6a696b50503e3471093cf3cd7ac7a6c0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowosielski?= Date: Thu, 26 Sep 2024 11:40:12 +0200 Subject: [PATCH 14/23] limit memory usage & verification --- p2p/snap_server_test.go | 32 +++++++++++++++++ p2p/snap_syncer.go | 80 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 104 insertions(+), 8 deletions(-) diff --git a/p2p/snap_server_test.go b/p2p/snap_server_test.go index 374a2db3b6..9697a545c8 100644 --- a/p2p/snap_server_test.go +++ b/p2p/snap_server_test.go @@ -752,6 +752,38 @@ func TestGetContractStorageRoot(t *testing.T) { } } +func TestPercentageCalculation(t *testing.T) { + tests := []struct { + actual *felt.Felt + percent uint64 + }{ + { + actual: feltFromString("0x0"), + percent: 0, + }, + { + // actual felt.MaxValue:2^251 + 17 * 2^192 + actual: feltFromString("0x800000000000011000000000000000000000000000000000000000000000000"), + percent: 100, + }, + { + actual: feltFromString("0x400000000000008800000000000000000000000000000000000000000000000"), + percent: 50, + }, + { + actual: feltFromString("0x0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + percent: 12, + }, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("%d%%", test.percent), func(t *testing.T) { + percent := CalculatePercentage(test.actual) + assert.Equal(t, test.percent, percent) + }) + } +} + func feltFromString(str string) *felt.Felt { f, err := (&felt.Felt{}).SetString(str) if err != nil { diff --git a/p2p/snap_syncer.go b/p2p/snap_syncer.go index b3ac7f7b5c..80bbd859a3 100644 --- a/p2p/snap_syncer.go +++ b/p2p/snap_syncer.go @@ -122,9 +122,9 @@ var ( maxStorageBatchSize = 1000 maxMaxPerStorageSize = 1000 - fetchClassWorkerCount = 4 // Fairly parallelizable. But this is brute force... - classesJobQueueSize = 128 - classBatchSize = 50 + fetchClassWorkerCount = 3 // Fairly parallelizable. But this is brute force... + classesJobQueueSize = 64 + classBatchSize = 30 maxPivotDistance = 32 // Set to 1 to test updated storage. newPivotHeadDistance = uint64(0) // This should be the reorg depth @@ -155,6 +155,11 @@ func (s *SnapSyncher) Run(ctx context.Context) error { return err } + s.log.Infow("phase 1 completed") + if err = s.PhraseVerify(ctx); err != nil { + return err + } + s.log.Infow("delegating to standard synchronizer") return nil @@ -286,6 +291,64 @@ func (s *SnapSyncher) runPhase1(ctx context.Context) error { return nil } +func (s *SnapSyncher) PhraseVerify(ctx context.Context) error { + // 1. Get the correct tries roots (again) + iter, err := s.client.RequestContractRange(ctx, &spec.ContractRangeRequest{ + StateRoot: core2p2p.AdaptHash(s.currentGlobalStateRoot), + ChunksPerProof: 1, + }) + if err != nil { + s.log.Errorw("error getting contract range from client", "err", err) + return err + } + + var classRoot, contractRoot *felt.Felt + iter(func(response *spec.ContractRangeResponse) bool { + if _, ok := response.GetResponses().(*spec.ContractRangeResponse_Range); ok { + classRoot = p2p2core.AdaptHash(response.ClassesRoot) + contractRoot = p2p2core.AdaptHash(response.ContractsRoot) + } else { + s.log.Errorw("unexpected response", "response", response) + } + + return false + }) + if classRoot == nil || contractRoot == nil { + s.log.Errorw("cannot obtain the trie roots from client response") + return errors.New("cannot obtain the trie roots") + } + + // 2. Verify the global state root + if err = VerifyGlobalStateRoot(s.currentGlobalStateRoot, classRoot, contractRoot); err != nil { + s.log.Errorw("global state root verification failure", "err", err) + return err + } + + // 3. Verify the class & contract trie roots + st, err := s.blockchain.(*blockchain.Blockchain).GetStateForStateRoot(s.currentGlobalStateRoot) + if err != nil { + s.log.Errorw("error getting state for state root", "err", err) + return err + } + ctrtRoot, clsRoot, err := st.StateAndClassRoot() + if err != nil { + s.log.Errorw("error getting contract and class root", "err", err) + return err + } + + if !classRoot.Equal(clsRoot) { + s.log.Errorw("class root mismatch", "got", clsRoot, "expected", classRoot) + return errors.New("class root mismatch") + } + + if !contractRoot.Equal(ctrtRoot) { + s.log.Errorw("contract root mismatch", "got", ctrtRoot, "expected", contractRoot) + return errors.New("contract root mismatch") + } + + return nil +} + func (s *SnapSyncher) getNextStartingBlock(ctx context.Context) (*core.Block, error) { for { select { @@ -335,7 +398,7 @@ func (s *SnapSyncher) initState(ctx context.Context) error { return nil } -func calculatePercentage(f *felt.Felt) uint64 { +func CalculatePercentage(f *felt.Felt) uint64 { const maxPercent = 100 maxint := big.NewInt(1) maxint.Lsh(maxint, core.GlobalTrieHeight) @@ -410,7 +473,7 @@ func (s *SnapSyncher) runClassRangeWorker(ctx context.Context) error { return err } - s.log.Infow("class range progress", "progress", calculatePercentage(startAddr)) + s.log.Infow("class range progress", "progress", CalculatePercentage(startAddr)) s.log.Infow("class range info", "classes", len(classes), "totalAdded", totalAdded, "startAddr", startAddr) paths := make([]*felt.Felt, len(classes)) @@ -661,9 +724,9 @@ func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { continue } - s.log.Infow("contract range progress", "progress", calculatePercentage(startAddr)) + s.log.Infow("contract range progress", "progress", CalculatePercentage(startAddr)) s.log.Infow("contract range info", "states", len(crange.State), "totalAdded", totalAdded, "startAddr", startAddr) - rangeProgress.Set(float64(calculatePercentage(startAddr))) + rangeProgress.Set(float64(CalculatePercentage(startAddr))) classRoot := p2p2core.AdaptHash(response.ClassesRoot) contractRoot := p2p2core.AdaptHash(response.ContractsRoot) @@ -749,6 +812,7 @@ func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { //nolint:funlen,gocyclo func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) error { nextjobs := make([]*storageRangeJob, 0) + s.log.Infow("storage range worker entering infinite loop", "worker", workerIdx) for { jobs := nextjobs @@ -980,7 +1044,7 @@ func (s *SnapSyncher) runStorageRefreshWorker(ctx context.Context) error { case <-ctx.Done(): return ctx.Err() case <-time.After(JobDuration): - s.log.Infow("no storagge refresh job") + s.log.Infow("no storage refresh job") case <-contractDoneChecker: // Its done... return nil From 5583f62105a3cbb7b7f4bb4cb93b2ae9859a15f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowosielski?= Date: Thu, 26 Sep 2024 12:53:47 +0200 Subject: [PATCH 15/23] verification bug fix --- p2p/snap_syncer.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/p2p/snap_syncer.go b/p2p/snap_syncer.go index 80bbd859a3..50810d6d34 100644 --- a/p2p/snap_syncer.go +++ b/p2p/snap_syncer.go @@ -295,6 +295,7 @@ func (s *SnapSyncher) PhraseVerify(ctx context.Context) error { // 1. Get the correct tries roots (again) iter, err := s.client.RequestContractRange(ctx, &spec.ContractRangeRequest{ StateRoot: core2p2p.AdaptHash(s.currentGlobalStateRoot), + Start: core2p2p.AdaptAddress(&felt.Zero), ChunksPerProof: 1, }) if err != nil { @@ -338,12 +339,10 @@ func (s *SnapSyncher) PhraseVerify(ctx context.Context) error { if !classRoot.Equal(clsRoot) { s.log.Errorw("class root mismatch", "got", clsRoot, "expected", classRoot) - return errors.New("class root mismatch") } if !contractRoot.Equal(ctrtRoot) { s.log.Errorw("contract root mismatch", "got", ctrtRoot, "expected", contractRoot) - return errors.New("contract root mismatch") } return nil From 61b6df3e2b4cd5f8484f60be994e9a8800c0af55 Mon Sep 17 00:00:00 2001 From: Ng Wei Han <47109095+weiihann@users.noreply.github.com> Date: Mon, 30 Sep 2024 19:31:27 +0800 Subject: [PATCH 16/23] Add sync mode p2p CLI flag (#2186) * Add sync mode p2p CLI flag * Return struct instead of interface * Change flag usage string * Remove SnapServer interface * Revert makefile * Minor fixes * Update docs --- Makefile | 5 +++- cmd/juno/juno.go | 4 +++ node/node.go | 21 ++++++------- p2p/downloader.go | 74 ++++++++++++++++++++++++++++++++++++++++++++++ p2p/modes.go | 74 ++++++++++++++++++++++++++++++++++++++++++++++ p2p/p2p.go | 48 +++++++++++------------------- p2p/p2p_test.go | 5 ++++ p2p/snap_server.go | 15 ++-------- p2p/snap_syncer.go | 58 ++++++++++++++++-------------------- p2p/sync.go | 47 ++++++++++++++++------------- 10 files changed, 245 insertions(+), 106 deletions(-) create mode 100644 p2p/downloader.go create mode 100644 p2p/modes.go diff --git a/Makefile b/Makefile index 91ae02dcaf..00e8575c40 100644 --- a/Makefile +++ b/Makefile @@ -133,7 +133,10 @@ node1: juno-cached --p2p-peers=/ip4/127.0.0.1/tcp/7777/p2p/12D3KooWLdURCjbp1D7hkXWk6ZVfcMDPtsNnPHuxoTcWXFtvrxGG \ --p2p-addr=/ip4/0.0.0.0/tcp/7778 \ --p2p-private-key="8aeffc26c3c371565dbe634c5248ae26f4fa5c33bc8f7328ac95e73fb94eaf263550f02449521f7cf64af17d248c5f170be46c06986a29803124c0819cb8fac3" \ - --metrics-port=9091 + --metrics-port=9091 \ + --pprof \ + --pprof-port=9096 \ + --p2p-sync-mode="snap" # --p2p-peers=/ip4/127.0.0.1/tcp/7778/p2p/12D3KooWDQVMmK6cQrfFcWUoFF8Ch5vYegfwiP5Do2SFC2NAXeBk \ diff --git a/cmd/juno/juno.go b/cmd/juno/juno.go index 6739a63357..fd957bf863 100644 --- a/cmd/juno/juno.go +++ b/cmd/juno/juno.go @@ -83,6 +83,7 @@ const ( corsEnableF = "rpc-cors-enable" versionedConstantsFileF = "versioned-constants-file" pluginPathF = "plugin-path" + p2pSyncModeF = "p2p-sync-mode" defaultConfig = "" defaulHost = "localhost" @@ -121,6 +122,7 @@ const ( defaultCorsEnable = false defaultVersionedConstantsFile = "" defaultPluginPath = "" + defaultP2pSyncMode = "full" configFlagUsage = "The YAML configuration file." logLevelFlagUsage = "Options: trace, debug, info, warn, error." @@ -154,6 +156,7 @@ const ( p2pFeederNodeUsage = "EXPERIMENTAL: Run juno as a feeder node which will only sync from feeder gateway and gossip the new" + " blocks to the network." p2pPrivateKeyUsage = "EXPERIMENTAL: Hexadecimal representation of a private key on the Ed25519 elliptic curve." + p2pSyncModeUsage = "EXPERIMENTAL: Synchronization mode: 'full' (default), 'snap'" metricsUsage = "Enables the Prometheus metrics endpoint on the default port." metricsHostUsage = "The interface on which the Prometheus endpoint will listen for requests." metricsPortUsage = "The port on which the Prometheus endpoint will listen for requests." @@ -338,6 +341,7 @@ func NewCmd(config *node.Config, run func(*cobra.Command, []string) error) *cobr junoCmd.Flags().String(p2pPeersF, defaultP2pPeers, p2pPeersUsage) junoCmd.Flags().Bool(p2pFeederNodeF, defaultP2pFeederNode, p2pFeederNodeUsage) junoCmd.Flags().String(p2pPrivateKey, defaultP2pPrivateKey, p2pPrivateKeyUsage) + junoCmd.Flags().String(p2pSyncModeF, defaultP2pSyncMode, p2pSyncModeUsage) junoCmd.Flags().Bool(metricsF, defaultMetrics, metricsUsage) junoCmd.Flags().String(metricsHostF, defaulHost, metricsHostUsage) junoCmd.Flags().Uint16(metricsPortF, defaultMetricsPort, metricsPortUsage) diff --git a/node/node.go b/node/node.go index b8af111e98..fd30973832 100644 --- a/node/node.go +++ b/node/node.go @@ -73,12 +73,13 @@ type Config struct { MetricsHost string `mapstructure:"metrics-host"` MetricsPort uint16 `mapstructure:"metrics-port"` - P2P bool `mapstructure:"p2p"` - P2PAddr string `mapstructure:"p2p-addr"` - P2PPublicAddr string `mapstructure:"p2p-public-addr"` - P2PPeers string `mapstructure:"p2p-peers"` - P2PFeederNode bool `mapstructure:"p2p-feeder-node"` - P2PPrivateKey string `mapstructure:"p2p-private-key"` + P2P bool `mapstructure:"p2p"` + P2PAddr string `mapstructure:"p2p-addr"` + P2PPublicAddr string `mapstructure:"p2p-public-addr"` + P2PPeers string `mapstructure:"p2p-peers"` + P2PFeederNode bool `mapstructure:"p2p-feeder-node"` + P2PPrivateKey string `mapstructure:"p2p-private-key"` + P2PSyncMode p2p.SyncMode `mapstructure:"p2p-sync-mode"` MaxVMs uint `mapstructure:"max-vms"` MaxVMQueue uint `mapstructure:"max-vm-queue"` @@ -131,7 +132,7 @@ func New(cfg *Config, version string) (*Node, error) { //nolint:gocyclo,funlen chain := blockchain.New(database, &cfg.Network) - //TODO: close a blockchain? better way? + // TODO: close a blockchain? better way? services = append(services, blockchain.NewBlockchainCloser(chain, log)) // Verify that cfg.Network is compatible with the database. @@ -183,12 +184,12 @@ func New(cfg *Config, version string) (*Node, error) { //nolint:gocyclo,funlen // Do not start the feeder synchronisation synchronizer = nil } - if os.Getenv("JUNO_P2P_NO_SYNC") != "" { + if os.Getenv("JUNO_P2P_NO_SYNC") != "" { // TODO(weiihann): remove this in the future log.Warnw("Got 'JUNO_P2P_NO_SYNC' to not syncing from p2p network") synchronizer = nil } p2pService, err = p2p.New(cfg.P2PAddr, cfg.P2PPublicAddr, version, cfg.P2PPeers, cfg.P2PPrivateKey, cfg.P2PFeederNode, - chain, &cfg.Network, log, database) + cfg.P2PSyncMode, chain, &cfg.Network, log, database) if err != nil { return nil, fmt.Errorf("set up p2p service: %w", err) } @@ -384,7 +385,7 @@ func (n *Node) Run(ctx context.Context) { } <-ctx.Done() - //TODO: chain.Close() - which service should do this? + // TODO: chain.Close() - which service should do this? n.log.Infow("Shutting down Juno...") } diff --git a/p2p/downloader.go b/p2p/downloader.go new file mode 100644 index 0000000000..8f61d5b354 --- /dev/null +++ b/p2p/downloader.go @@ -0,0 +1,74 @@ +package p2p + +import ( + "context" + "os" + "sync/atomic" + + "github.com/NethermindEth/juno/blockchain" + "github.com/NethermindEth/juno/sync" + "github.com/NethermindEth/juno/utils" + "github.com/libp2p/go-libp2p/core/host" +) + +type Downloader struct { + isFeeder bool + mode atomic.Uint32 + baseSyncer *SyncService + snapSyncer *SnapSyncer + log utils.SimpleLogger +} + +func NewDownloader(isFeeder bool, syncMode SyncMode, p2pHost host.Host, network *utils.Network, bc *blockchain.Blockchain, log utils.SimpleLogger) *Downloader { + dl := &Downloader{ + isFeeder: isFeeder, + log: log, + } + + dl.baseSyncer = newSyncService(bc, p2pHost, network, log) + + var snapSyncer *SnapSyncer + if syncMode == SnapSync { + snapSyncer = NewSnapSyncer(dl.baseSyncer.Client(), bc, log) + } + dl.snapSyncer = snapSyncer + + // TODO: when syncing becomes more mature, we need a way to dynamically determine which sync mode to use + // For now, we will use the sync mode that is passed in the constructor + dl.mode.Store(uint32(syncMode)) + + return dl +} + +func (d *Downloader) Start(ctx context.Context) error { + // Feeder node doesn't sync using P2P + if d.isFeeder { + return nil + } + + d.log.Infow("Downloader start", "mode", d.getMode()) + if d.getMode() == SnapSync { + // TODO: a hack, remove this + if os.Getenv("JUNO_P2P_NO_SYNC") == "" { + err := d.snapSyncer.Run(ctx) + if err != nil { + d.log.Errorw("Snapsyncer failed to start") + return err + } + } else { + d.log.Infow("Syncing is disabled") + return nil + } + } + + d.baseSyncer.Start(ctx) + return nil +} + +func (d *Downloader) getMode() SyncMode { + return SyncMode(d.mode.Load()) +} + +func (d *Downloader) WithListener(l sync.EventListener) { + d.baseSyncer.WithListener(l) +} diff --git a/p2p/modes.go b/p2p/modes.go new file mode 100644 index 0000000000..ef77530adb --- /dev/null +++ b/p2p/modes.go @@ -0,0 +1,74 @@ +package p2p + +import ( + "encoding" + "fmt" + + "github.com/spf13/pflag" +) + +// The following are necessary for Cobra and Viper, respectively, to unmarshal +// CLI/config parameters properly. +var ( + _ pflag.Value = (*SyncMode)(nil) + _ encoding.TextUnmarshaler = (*SyncMode)(nil) +) + +// SyncMode represents the synchronisation mode of the downloader. +// It is a uint32 as it is used with atomic operations. +type SyncMode uint32 + +const ( + FullSync SyncMode = iota // Synchronize by downloading blocks and applying them to the chain sequentially + SnapSync // Download the chain and the state via snap protocol +) + +func (s SyncMode) IsValid() bool { + return s == FullSync || s == SnapSync +} + +func (s SyncMode) String() string { + switch s { + case FullSync: + return "full" + case SnapSync: + return "snap" + default: + return "unknown" + } +} + +func (s SyncMode) Type() string { + return "SyncMode" +} + +func (s SyncMode) MarshalYAML() (interface{}, error) { + return s.String(), nil +} + +func (s *SyncMode) Set(mode string) error { + switch mode { + case "full": + *s = FullSync + case "snap": + *s = SnapSync + default: + return fmt.Errorf("unknown sync mode %q, want \"full\" or \"snap\"", mode) + } + return nil +} + +func (s SyncMode) MarshalText() ([]byte, error) { + switch s { + case FullSync: + return []byte("full"), nil + case SnapSync: + return []byte("snap"), nil + default: + return nil, fmt.Errorf("unknown sync mode %d", s) + } +} + +func (s *SyncMode) UnmarshalText(text []byte) error { + return s.Set(string(text)) +} diff --git a/p2p/p2p.go b/p2p/p2p.go index 0115049f01..00730316f5 100644 --- a/p2p/p2p.go +++ b/p2p/p2p.go @@ -5,9 +5,7 @@ import ( "encoding/hex" "errors" "fmt" - "github.com/NethermindEth/juno/service" "math/rand" - "os" "strings" "sync" "time" @@ -50,6 +48,7 @@ type Service struct { topics map[string]*pubsub.Topic topicsLock sync.RWMutex + downloader *Downloader synchroniser *syncService gossipTracer *gossipTracer snapSyncher service.Service @@ -59,7 +58,7 @@ type Service struct { database db.DB } -func New(addr, publicAddr, version, peers, privKeyStr string, feederNode bool, bc *blockchain.Blockchain, snNetwork *utils.Network, +func New(addr, publicAddr, version, peers, privKeyStr string, feederNode bool, syncMode SyncMode, bc *blockchain.Blockchain, snNetwork *utils.Network, log utils.SimpleLogger, database db.DB, ) (*Service, error) { if addr == "" { @@ -117,10 +116,10 @@ func New(addr, publicAddr, version, peers, privKeyStr string, feederNode bool, b // Todo: try to understand what will happen if user passes a multiaddr with p2p public and a private key which doesn't match. // For example, a user passes the following multiaddr: --p2p-addr=/ip4/0.0.0.0/tcp/7778/p2p/(SomePublicKey) and also passes a // --p2p-private-key="SomePrivateKey". However, the private public key pair don't match, in this case what will happen? - return NewWithHost(p2pHost, peers, feederNode, bc, snNetwork, log, database) + return NewWithHost(p2pHost, peers, feederNode, syncMode, bc, snNetwork, log, database) } -func NewWithHost(p2phost host.Host, peers string, feederNode bool, bc *blockchain.Blockchain, snNetwork *utils.Network, +func NewWithHost(p2phost host.Host, peers string, feederNode bool, syncMode SyncMode, bc *blockchain.Blockchain, snNetwork *utils.Network, log utils.SimpleLogger, database db.DB, ) (*Service, error) { var ( @@ -151,22 +150,19 @@ func NewWithHost(p2phost host.Host, peers string, feederNode bool, bc *blockchai return nil, err } - // todo: reconsider initialising synchroniser here because if node is a feedernode we should not create an instance of it. - - synchroniser := newSyncService(bc, p2phost, snNetwork, log) + downloader := NewDownloader(feederNode, syncMode, p2phost, snNetwork, bc, log) handler := starknet.NewHandler(bc, log) - handler.WithSnapsyncSupport(NewSnapServer(bc, log)) + handler.WithSnapsyncSupport(NewSnapServer(bc, log)) // TODO: initialize the snap server in the starknet handler + s := &Service{ - synchroniser: synchroniser, - snapSyncher: NewSnapSyncer(synchroniser, bc, log), - log: log, - host: p2phost, - network: snNetwork, - dht: p2pdht, - feederNode: feederNode, - topics: make(map[string]*pubsub.Topic), - handler: handler, - database: database, + downloader: downloader, + log: log, + host: p2phost, + network: snNetwork, + dht: p2pdht, + topics: make(map[string]*pubsub.Topic), + handler: handler, + database: database, } return s, nil } @@ -274,18 +270,8 @@ func (s *Service) Run(ctx context.Context) error { s.setProtocolHandlers() - if !s.feederNode { - //s.synchroniser.start(ctx) - if os.Getenv("JUNO_P2P_NO_SYNC") == "" { - err := s.snapSyncher.Run(ctx) - if err != nil { - s.log.Errorw("Snapsyncer failed to start") - return err - } - } else { - s.log.Infow("Syncing is disabled") - } - } + // Start the syncing process + s.downloader.Start(ctx) <-ctx.Done() if err := s.persistPeers(); err != nil { diff --git a/p2p/p2p_test.go b/p2p/p2p_test.go index 070a9eedb8..0dd493027b 100644 --- a/p2p/p2p_test.go +++ b/p2p/p2p_test.go @@ -34,6 +34,7 @@ func TestService(t *testing.T) { peerHosts[0], "", false, + p2p.FullSync, nil, &utils.Integration, utils.NewNopZapLogger(), @@ -56,6 +57,7 @@ func TestService(t *testing.T) { peerHosts[1], strings.Join(peerAddrsString, ","), true, + p2p.FullSync, nil, &utils.Integration, utils.NewNopZapLogger(), @@ -144,6 +146,7 @@ func TestInvalidKey(t *testing.T) { "", "something", false, + p2p.FullSync, nil, &utils.Integration, utils.NewNopZapLogger(), @@ -162,6 +165,7 @@ func TestValidKey(t *testing.T) { "", "08011240333b4a433f16d7ca225c0e99d0d8c437b835cb74a98d9279c561977690c80f681b25ccf3fa45e2f2de260149c112fa516b69057dd3b0151a879416c0cb12d9b3", false, + p2p.FullSync, nil, &utils.Integration, utils.NewNopZapLogger(), @@ -199,6 +203,7 @@ func TestLoadAndPersistPeers(t *testing.T) { "", "5f6cdc3aebcc74af494df054876100368ef6126e3a33fa65b90c765b381ffc37a0a63bbeeefab0740f24a6a38dabb513b9233254ad0020c721c23e69bc820089", false, + p2p.FullSync, nil, &utils.Integration, utils.NewNopZapLogger(), diff --git a/p2p/snap_server.go b/p2p/snap_server.go index c9e3390b57..dc30fe684a 100644 --- a/p2p/snap_server.go +++ b/p2p/snap_server.go @@ -1,9 +1,10 @@ package p2p import ( + "math/big" + "github.com/NethermindEth/juno/utils" "google.golang.org/protobuf/proto" - "math/big" "github.com/NethermindEth/juno/adapters/core2p2p" "github.com/NethermindEth/juno/adapters/p2p2core" @@ -44,14 +45,6 @@ type ClassRangeStreamingResult struct { RangeProof *spec.PatriciaRangeProof } -// TODO: delete, duplicate of SnapProvider -type SnapServer interface { - GetClassRange(request *spec.ClassRangeRequest) (iter.Seq[proto.Message], error) - GetContractRange(request *spec.ContractRangeRequest) (iter.Seq[proto.Message], error) - GetStorageRange(request *spec.ContractStorageRequest) (iter.Seq[proto.Message], error) - GetClasses(request *spec.ClassHashesRequest) (iter.Seq[proto.Message], error) -} - type SnapServerBlockchain interface { GetStateForStateRoot(stateRoot *felt.Felt) (*core.State, error) GetClasses(felts []*felt.Felt) ([]core.Class, error) @@ -61,7 +54,7 @@ type yieldFunc = func(proto.Message) bool var _ SnapServerBlockchain = (*blockchain.Blockchain)(nil) -func NewSnapServer(blockchain SnapServerBlockchain, log utils.SimpleLogger) SnapServer { +func NewSnapServer(blockchain SnapServerBlockchain, log utils.SimpleLogger) *snapServer { return &snapServer{ log: log, blockchain: blockchain, @@ -246,7 +239,6 @@ func (b *snapServer) GetContractRange(request *spec.ContractRangeRequest) (iter. }) return nil }) - if err != nil { log.Error("error iterating storage trie", "err", err) return @@ -335,7 +327,6 @@ func (b *snapServer) GetStorageRange(request *spec.ContractStorageRequest) (iter } return true }) - if err != nil { log.Error("error handling storage range request", "err", err) return diff --git a/p2p/snap_syncer.go b/p2p/snap_syncer.go index 50810d6d34..6bfc607708 100644 --- a/p2p/snap_syncer.go +++ b/p2p/snap_syncer.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "github.com/NethermindEth/juno/p2p/starknet" big "math/big" "sync" "sync/atomic" @@ -17,6 +16,7 @@ import ( "github.com/NethermindEth/juno/core/crypto" "github.com/NethermindEth/juno/core/felt" "github.com/NethermindEth/juno/core/trie" + "github.com/NethermindEth/juno/p2p/starknet" "github.com/NethermindEth/juno/p2p/starknet/spec" "github.com/NethermindEth/juno/service" "github.com/NethermindEth/juno/starknetdata" @@ -38,8 +38,7 @@ type Blockchain interface { var _ Blockchain = (*blockchain.Blockchain)(nil) -type SnapSyncher struct { - baseSync *syncService +type SnapSyncer struct { starknetData starknetdata.StarknetData client *starknet.Client blockchain Blockchain @@ -65,7 +64,7 @@ type SnapSyncher struct { mtxL *sync.Mutex } -var _ service.Service = (*SnapSyncher)(nil) +var _ service.Service = (*SnapSyncer)(nil) type storageRangeJob struct { path *felt.Felt @@ -76,12 +75,12 @@ type storageRangeJob struct { } func NewSnapSyncer( - baseSyncher *syncService, + client *starknet.Client, bc *blockchain.Blockchain, log utils.SimpleLogger, -) *SnapSyncher { - return &SnapSyncher{ - baseSync: baseSyncher, +) *SnapSyncer { + return &SnapSyncer{ + client: client, blockchain: bc, log: log, } @@ -130,7 +129,7 @@ var ( newPivotHeadDistance = uint64(0) // This should be the reorg depth ) -func (s *SnapSyncher) Run(ctx context.Context) error { +func (s *SnapSyncer) Run(ctx context.Context) error { s.log.Infow("starting snap sync") // 1. Get the current head // 2. Start the snap sync with pivot set to that head @@ -143,11 +142,6 @@ func (s *SnapSyncher) Run(ctx context.Context) error { // 6. Probably download old state updato/bodies too // 7. Send back control to base sync. - // TODO: hacky client - if s.baseSync == nil { - panic("can't start snap syncer without base syncer") - } - s.client = s.baseSync.Client() s.starknetData = &MockStarkData{} err := s.runPhase1(ctx) @@ -164,11 +158,11 @@ func (s *SnapSyncher) Run(ctx context.Context) error { return nil // TODO: start p2p syncer - //s.baseSync.start(ctx) + // s.baseSync.start(ctx) } //nolint:gocyclo,nolintlint -func (s *SnapSyncher) runPhase1(ctx context.Context) error { +func (s *SnapSyncer) runPhase1(ctx context.Context) error { starttime := time.Now() err := s.initState(ctx) @@ -291,7 +285,7 @@ func (s *SnapSyncher) runPhase1(ctx context.Context) error { return nil } -func (s *SnapSyncher) PhraseVerify(ctx context.Context) error { +func (s *SnapSyncer) PhraseVerify(ctx context.Context) error { // 1. Get the correct tries roots (again) iter, err := s.client.RequestContractRange(ctx, &spec.ContractRangeRequest{ StateRoot: core2p2p.AdaptHash(s.currentGlobalStateRoot), @@ -348,7 +342,7 @@ func (s *SnapSyncher) PhraseVerify(ctx context.Context) error { return nil } -func (s *SnapSyncher) getNextStartingBlock(ctx context.Context) (*core.Block, error) { +func (s *SnapSyncer) getNextStartingBlock(ctx context.Context) (*core.Block, error) { for { select { case <-ctx.Done(): @@ -371,7 +365,7 @@ func (s *SnapSyncher) getNextStartingBlock(ctx context.Context) (*core.Block, er } } -func (s *SnapSyncher) initState(ctx context.Context) error { +func (s *SnapSyncer) initState(ctx context.Context) error { startingBlock, err := s.getNextStartingBlock(ctx) if err != nil { return errors.Join(err, errors.New("error getting current head")) @@ -410,7 +404,7 @@ func CalculatePercentage(f *felt.Felt) uint64 { } //nolint:gocyclo,nolintlint -func (s *SnapSyncher) runClassRangeWorker(ctx context.Context) error { +func (s *SnapSyncer) runClassRangeWorker(ctx context.Context) error { totalAdded := 0 completed := false startAddr := &felt.Zero @@ -542,7 +536,7 @@ func (s *SnapSyncher) runClassRangeWorker(ctx context.Context) error { } //nolint:gocyclo -func (s *SnapSyncher) runFetchClassWorker(ctx context.Context, workerIdx int) error { +func (s *SnapSyncer) runFetchClassWorker(ctx context.Context, workerIdx int) error { keyBatches := make([]*felt.Felt, 0) s.log.Infow("class fetch worker entering infinite loop", "worker", workerIdx) for { @@ -674,7 +668,7 @@ func (s *SnapSyncher) runFetchClassWorker(ctx context.Context, workerIdx int) er } //nolint:gocyclo -func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { +func (s *SnapSyncer) runContractRangeWorker(ctx context.Context) error { totalAdded := 0 startAddr := &felt.Zero completed := false @@ -809,7 +803,7 @@ func (s *SnapSyncher) runContractRangeWorker(ctx context.Context) error { } //nolint:funlen,gocyclo -func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) error { +func (s *SnapSyncer) runStorageRangeWorker(ctx context.Context, workerIdx int) error { nextjobs := make([]*storageRangeJob, 0) s.log.Infow("storage range worker entering infinite loop", "worker", workerIdx) for { @@ -838,7 +832,7 @@ func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) } } - //s.log.Infow("storage range job completes batch", "jobs", len(jobs), "worker", workerIdx, "pending", s.storageRangeJobCount) + // s.log.Infow("storage range job completes batch", "jobs", len(jobs), "worker", workerIdx, "pending", s.storageRangeJobCount) requests := make([]*spec.StorageRangeQuery, 0) for _, job := range jobs { @@ -1024,7 +1018,7 @@ func (s *SnapSyncher) runStorageRangeWorker(ctx context.Context, workerIdx int) } //nolint:gocyclo -func (s *SnapSyncher) runStorageRefreshWorker(ctx context.Context) error { +func (s *SnapSyncer) runStorageRefreshWorker(ctx context.Context) error { // In ethereum, this is normally done with get tries, but since we don't have that here, we'll have to be // creative. This does mean that this is impressively inefficient. var job *storageRangeJob @@ -1142,7 +1136,7 @@ func (s *SnapSyncher) runStorageRefreshWorker(ctx context.Context) error { return nil } -func (s *SnapSyncher) queueClassJob(ctx context.Context, classHash *felt.Felt) error { +func (s *SnapSyncer) queueClassJob(ctx context.Context, classHash *felt.Felt) error { queued := false for !queued { select { @@ -1158,7 +1152,7 @@ func (s *SnapSyncher) queueClassJob(ctx context.Context, classHash *felt.Felt) e return nil } -func (s *SnapSyncher) queueStorageRangeJob(ctx context.Context, path, storageRoot, classHash *felt.Felt, nonce uint64) error { +func (s *SnapSyncer) queueStorageRangeJob(ctx context.Context, path, storageRoot, classHash *felt.Felt, nonce uint64) error { return s.queueStorageRangeJobJob(ctx, &storageRangeJob{ path: path, storageRoot: storageRoot, @@ -1168,7 +1162,7 @@ func (s *SnapSyncher) queueStorageRangeJob(ctx context.Context, path, storageRoo }) } -func (s *SnapSyncher) queueStorageRangeJobJob(ctx context.Context, job *storageRangeJob) error { +func (s *SnapSyncer) queueStorageRangeJobJob(ctx context.Context, job *storageRangeJob) error { if job.storageRoot == nil || job.storageRoot.IsZero() { // contract's with storage root of 0x0 has no storage return nil @@ -1189,7 +1183,7 @@ func (s *SnapSyncher) queueStorageRangeJobJob(ctx context.Context, job *storageR return nil } -func (s *SnapSyncher) queueStorageRefreshJob(ctx context.Context, job *storageRangeJob) error { +func (s *SnapSyncer) queueStorageRefreshJob(ctx context.Context, job *storageRangeJob) error { queued := false for !queued { select { @@ -1204,7 +1198,7 @@ func (s *SnapSyncher) queueStorageRefreshJob(ctx context.Context, job *storageRa return nil } -func (s *SnapSyncher) poolLatestBlock(ctx context.Context) error { +func (s *SnapSyncer) poolLatestBlock(ctx context.Context) error { for { select { case <-ctx.Done(): @@ -1237,7 +1231,7 @@ func (s *SnapSyncher) poolLatestBlock(ctx context.Context) error { } } -func (s *SnapSyncher) ApplyStateUpdate(blockNumber uint64) error { +func (s *SnapSyncer) ApplyStateUpdate(blockNumber uint64) error { return errors.New("unimplemented") } @@ -1271,7 +1265,7 @@ func P2pProofToTrieProofs(proof *spec.PatriciaRangeProof) []trie.ProofNode { } func VerifyGlobalStateRoot(globalStateRoot, classRoot, storageRoot *felt.Felt) error { - var stateVersion = new(felt.Felt).SetBytes([]byte(`STARKNET_STATE_V0`)) + stateVersion := new(felt.Felt).SetBytes([]byte(`STARKNET_STATE_V0`)) if classRoot.IsZero() { if globalStateRoot.Equal(storageRoot) { diff --git a/p2p/sync.go b/p2p/sync.go index 2b2c79b0e6..37856ec041 100644 --- a/p2p/sync.go +++ b/p2p/sync.go @@ -26,7 +26,7 @@ import ( "go.uber.org/zap" ) -type syncService struct { +type SyncService struct { host host.Host network *utils.Network client *starknet.Client // todo: merge all the functionality of Client with p2p SyncService @@ -36,22 +36,29 @@ type syncService struct { log utils.SimpleLogger } -func newSyncService(bc *blockchain.Blockchain, h host.Host, n *utils.Network, log utils.SimpleLogger) *syncService { - return &syncService{ +func newSyncService(bc *blockchain.Blockchain, h host.Host, n *utils.Network, log utils.SimpleLogger) *SyncService { + s := &SyncService{ host: h, network: n, blockchain: bc, log: log, listener: &junoSync.SelectiveListener{}, } + + s.client = starknet.NewClient(s.randomPeerStream, s.network, s.log) + + return s +} + +func (s *SyncService) Client() *starknet.Client { + return s.client } -func (s *syncService) start(ctx context.Context) { +//nolint:funlen +func (s *SyncService) Start(ctx context.Context) { ctx, cancel := context.WithCancel(ctx) defer cancel() - s.client = starknet.NewClient(s.randomPeerStream, s.network, s.log) - for i := 0; ; i++ { if err := ctx.Err(); err != nil { break @@ -145,7 +152,7 @@ func specBlockPartsFunc[T specBlockHeaderAndSigs | specTxWithReceipts | specEven return specBlockParts(i) } -func (s *syncService) logError(msg string, err error) { +func (s *SyncService) logError(msg string, err error) { if !errors.Is(err, context.Canceled) { var log utils.SimpleLogger if v, ok := s.log.(*utils.ZapLogger); ok { @@ -171,7 +178,7 @@ type blockBody struct { } //nolint:gocyclo -func (s *syncService) processSpecBlockParts( +func (s *SyncService) processSpecBlockParts( ctx context.Context, startingBlockNum uint64, specBlockPartsCh <-chan specBlockParts, ) <-chan <-chan blockBody { orderedBlockBodiesCh := make(chan (<-chan blockBody)) @@ -263,8 +270,8 @@ func (s *syncService) processSpecBlockParts( return orderedBlockBodiesCh } -//nolint:gocyclo -func (s *syncService) adaptAndSanityCheckBlock(ctx context.Context, header *spec.SignedBlockHeader, contractDiffs []*spec.ContractDiff, +//nolint:gocyclo,funlen +func (s *SyncService) adaptAndSanityCheckBlock(ctx context.Context, header *spec.SignedBlockHeader, contractDiffs []*spec.ContractDiff, classes []*spec.Class, txs []*spec.Transaction, receipts []*spec.Receipt, events []*spec.Event, prevBlockRoot *felt.Felt, ) <-chan blockBody { bodyCh := make(chan blockBody) @@ -393,7 +400,7 @@ func (s specBlockHeaderAndSigs) blockNumber() uint64 { return s.header.Number } -func (s *syncService) genHeadersAndSigs(ctx context.Context, blockNumber uint64) (<-chan specBlockHeaderAndSigs, error) { +func (s *SyncService) genHeadersAndSigs(ctx context.Context, blockNumber uint64) (<-chan specBlockHeaderAndSigs, error) { it := s.createIteratorForBlock(blockNumber) headersIt, err := s.client.RequestBlockHeaders(ctx, &spec.BlockHeadersRequest{Iteration: it}) if err != nil { @@ -437,7 +444,7 @@ func (s specClasses) blockNumber() uint64 { return s.number } -func (s *syncService) genClasses(ctx context.Context, blockNumber uint64) (<-chan specClasses, error) { +func (s *SyncService) genClasses(ctx context.Context, blockNumber uint64) (<-chan specClasses, error) { it := s.createIteratorForBlock(blockNumber) classesIt, err := s.client.RequestClasses(ctx, &spec.ClassesRequest{Iteration: it}) if err != nil { @@ -483,7 +490,7 @@ func (s specContractDiffs) blockNumber() uint64 { return s.number } -func (s *syncService) genStateDiffs(ctx context.Context, blockNumber uint64) (<-chan specContractDiffs, error) { +func (s *SyncService) genStateDiffs(ctx context.Context, blockNumber uint64) (<-chan specContractDiffs, error) { it := s.createIteratorForBlock(blockNumber) stateDiffsIt, err := s.client.RequestStateDiffs(ctx, &spec.StateDiffsRequest{Iteration: it}) if err != nil { @@ -531,7 +538,7 @@ func (s specEvents) blockNumber() uint64 { return s.number } -func (s *syncService) genEvents(ctx context.Context, blockNumber uint64) (<-chan specEvents, error) { +func (s *SyncService) genEvents(ctx context.Context, blockNumber uint64) (<-chan specEvents, error) { it := s.createIteratorForBlock(blockNumber) eventsIt, err := s.client.RequestEvents(ctx, &spec.EventsRequest{Iteration: it}) if err != nil { @@ -578,7 +585,7 @@ func (s specTxWithReceipts) blockNumber() uint64 { return s.number } -func (s *syncService) genTransactions(ctx context.Context, blockNumber uint64) (<-chan specTxWithReceipts, error) { +func (s *SyncService) genTransactions(ctx context.Context, blockNumber uint64) (<-chan specTxWithReceipts, error) { it := s.createIteratorForBlock(blockNumber) txsIt, err := s.client.RequestTransactions(ctx, &spec.TransactionsRequest{Iteration: it}) if err != nil { @@ -644,7 +651,7 @@ func (s *syncService) randomPeer() peer.ID { var errNoPeers = errors.New("no peers available") -func (s *syncService) randomPeerStream(ctx context.Context, pids ...protocol.ID) (network.Stream, error) { +func (s *SyncService) randomPeerStream(ctx context.Context, pids ...protocol.ID) (network.Stream, error) { randPeer := s.randomPeer() if randPeer == "" { return nil, errNoPeers @@ -658,13 +665,13 @@ func (s *syncService) randomPeerStream(ctx context.Context, pids ...protocol.ID) return stream, err } -func (s *syncService) removePeer(id peer.ID) { +func (s *SyncService) removePeer(id peer.ID) { s.log.Debugw("Removing peer", "peerID", id) s.host.Peerstore().RemovePeer(id) s.host.Peerstore().ClearAddrs(id) } -func (s *syncService) createIteratorForBlock(blockNumber uint64) *spec.Iteration { +func (s *SyncService) createIteratorForBlock(blockNumber uint64) *spec.Iteration { return &spec.Iteration{ Start: &spec.Iteration_BlockNumber{BlockNumber: blockNumber}, Direction: spec.Iteration_Forward, @@ -673,12 +680,12 @@ func (s *syncService) createIteratorForBlock(blockNumber uint64) *spec.Iteration } } -func (s *syncService) WithListener(l junoSync.EventListener) { +func (s *SyncService) WithListener(l junoSync.EventListener) { s.listener = l } //nolint:unused -func (s *syncService) sleep(d time.Duration) { +func (s *SyncService) sleep(d time.Duration) { s.log.Debugw("Sleeping...", "for", d) time.Sleep(d) } From 22cc1edf0db9df5c8c3cc373c553bcec8b77bc79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowosielski?= Date: Mon, 30 Sep 2024 16:28:35 +0200 Subject: [PATCH 17/23] Less logs, verification fixed --- p2p/snap_server.go | 17 +------- p2p/snap_server_test.go | 19 +++++++++ p2p/snap_syncer.go | 90 ++++++++++++++++++----------------------- 3 files changed, 60 insertions(+), 66 deletions(-) diff --git a/p2p/snap_server.go b/p2p/snap_server.go index dc30fe684a..bb698fb68f 100644 --- a/p2p/snap_server.go +++ b/p2p/snap_server.go @@ -89,8 +89,6 @@ func (b *snapServer) GetClassRange(request *spec.ClassRangeRequest) (iter.Seq[pr } stateRoot := p2p2core.AdaptHash(request.Root) - startAddr := p2p2core.AdaptHash(request.Start) - b.log.Debugw("GetClassRange", "start", startAddr, "chunks", request.ChunksPerProof) return func(yield yieldFunc) { s, err := b.blockchain.GetStateForStateRoot(stateRoot) @@ -171,7 +169,7 @@ func (b *snapServer) GetClassRange(request *spec.ClassRangeRequest) (iter.Seq[pr } yield(finMsg) - b.log.Infow("GetClassRange iteration completed") + b.log.Infow("class range iteration completed") }, nil } @@ -180,8 +178,6 @@ func (b *snapServer) GetContractRange(request *spec.ContractRangeRequest) (iter. Responses: &spec.ContractRangeResponse_Fin{}, } stateRoot := p2p2core.AdaptHash(request.StateRoot) - startAddr := p2p2core.AdaptAddress(request.Start) - b.log.Debugw("GetContractRange", "root", stateRoot, "start", startAddr, "chunks", request.ChunksPerProof) return func(yield yieldFunc) { s, err := b.blockchain.GetStateForStateRoot(stateRoot) @@ -256,12 +252,6 @@ func (b *snapServer) GetContractRange(request *spec.ContractRangeRequest) (iter. }, } - var first, last *felt.Felt - if len(states) > 0 { - first = p2p2core.AdaptAddress(states[0].Address) - last = p2p2core.AdaptAddress(states[len(states)-1].Address) - } - b.log.Infow("sending contract range response", "len(states)", len(states), "first", first, "last", last) if !yield(cntrMsg) { // we should not send `FinMsg` when the client explicitly asks to stop return @@ -282,10 +272,6 @@ func (b *snapServer) GetStorageRange(request *spec.ContractStorageRequest) (iter var finMsg proto.Message = &spec.ContractStorageResponse{ Responses: &spec.ContractStorageResponse_Fin{}, } - startKey := p2p2core.AdaptAddress(request.Query[0].Address) - last := len(request.Query) - 1 - endKey := p2p2core.AdaptAddress(request.Query[last].Address) - b.log.Debugw("GetStorageRange", "query[0]", startKey, "query[", last, "]", endKey) return func(yield yieldFunc) { stateRoot := p2p2core.AdaptHash(request.StateRoot) @@ -349,7 +335,6 @@ func (b *snapServer) GetClasses(request *spec.ClassHashesRequest) (iter.Seq[prot var finMsg proto.Message = &spec.ClassesResponse{ ClassMessage: &spec.ClassesResponse_Fin{}, } - b.log.Debugw("GetClasses", "len(hashes)", len(request.ClassHashes)) return func(yield yieldFunc) { felts := make([]*felt.Felt, len(request.ClassHashes)) diff --git a/p2p/snap_server_test.go b/p2p/snap_server_test.go index 9697a545c8..1634b38eae 100644 --- a/p2p/snap_server_test.go +++ b/p2p/snap_server_test.go @@ -1,6 +1,7 @@ package p2p import ( + "context" "fmt" "github.com/NethermindEth/juno/core" "github.com/NethermindEth/juno/core/crypto" @@ -752,6 +753,24 @@ func TestGetContractStorageRoot(t *testing.T) { } } +func TestReadAndVerifySnapshot(t *testing.T) { + var d db.DB + t.Skip("DB snapshot is needed for this test") + d, _ = pebble.NewWithOptions("/Users/pnowosie/juno/snapshots/node1", 128000000, 128, false) + defer func() { _ = d.Close() }() + bc := blockchain.New(d, &utils.Sepolia) + + logger, _ := utils.NewZapLogger(utils.DEBUG, false) + syncer := SnapSyncer{ + log: logger, + blockchain: bc, + currentGlobalStateRoot: feltFromString("0x472e84b65d387c9364b5117f4afaba3fb88897db1f28867b398506e2af89f25"), + } + + err := syncer.PhraseVerify(context.Background()) + assert.NoError(t, err) +} + func TestPercentageCalculation(t *testing.T) { tests := []struct { actual *felt.Felt diff --git a/p2p/snap_syncer.go b/p2p/snap_syncer.go index 6bfc607708..a7afbb58c9 100644 --- a/p2p/snap_syncer.go +++ b/p2p/snap_syncer.go @@ -148,11 +148,12 @@ func (s *SnapSyncer) Run(ctx context.Context) error { if err != nil { return err } - s.log.Infow("phase 1 completed") + if err = s.PhraseVerify(ctx); err != nil { return err } + s.log.Infow("trie roots verification completed") s.log.Infow("delegating to standard synchronizer") @@ -286,7 +287,33 @@ func (s *SnapSyncer) runPhase1(ctx context.Context) error { } func (s *SnapSyncer) PhraseVerify(ctx context.Context) error { - // 1. Get the correct tries roots (again) + // 1. Get the actual class & contract trie roots + st, closer, err := s.blockchain.(*blockchain.Blockchain).HeadStateFreakingState() + defer func() { _ = closer() }() + if err != nil { + s.log.Errorw("error getting state for state root", "err", err) + return err + } + contractRoot, classRoot, err := st.StateAndClassRoot() + if err != nil { + s.log.Errorw("error getting contract and class root", "err", err) + return err + } + + // 2. Verify the global state root + err = VerifyGlobalStateRoot(s.currentGlobalStateRoot, classRoot, contractRoot) + if err == nil { + s.log.Infow("PhraseVerify", + "global state root", s.currentGlobalStateRoot, "contract root", contractRoot, "class root", classRoot) + // all good no need for additional verification + return nil + } + + if err != nil { + s.log.Errorw("global state root verification failure", "err", err) + } + + // 3. Get the correct tries roots from the client iter, err := s.client.RequestContractRange(ctx, &spec.ContractRangeRequest{ StateRoot: core2p2p.AdaptHash(s.currentGlobalStateRoot), Start: core2p2p.AdaptAddress(&felt.Zero), @@ -297,49 +324,27 @@ func (s *SnapSyncer) PhraseVerify(ctx context.Context) error { return err } - var classRoot, contractRoot *felt.Felt + var classR, contractR *felt.Felt iter(func(response *spec.ContractRangeResponse) bool { if _, ok := response.GetResponses().(*spec.ContractRangeResponse_Range); ok { - classRoot = p2p2core.AdaptHash(response.ClassesRoot) - contractRoot = p2p2core.AdaptHash(response.ContractsRoot) + classR = p2p2core.AdaptHash(response.ClassesRoot) + contractR = p2p2core.AdaptHash(response.ContractsRoot) } else { s.log.Errorw("unexpected response", "response", response) } return false }) - if classRoot == nil || contractRoot == nil { + if classR == nil || contractR == nil { s.log.Errorw("cannot obtain the trie roots from client response") return errors.New("cannot obtain the trie roots") } - // 2. Verify the global state root - if err = VerifyGlobalStateRoot(s.currentGlobalStateRoot, classRoot, contractRoot); err != nil { - s.log.Errorw("global state root verification failure", "err", err) - return err - } - - // 3. Verify the class & contract trie roots - st, err := s.blockchain.(*blockchain.Blockchain).GetStateForStateRoot(s.currentGlobalStateRoot) - if err != nil { - s.log.Errorw("error getting state for state root", "err", err) - return err - } - ctrtRoot, clsRoot, err := st.StateAndClassRoot() - if err != nil { - s.log.Errorw("error getting contract and class root", "err", err) - return err - } + // 4. Log which one is incorrect + s.log.Infow("Contract trie root", "expected", contractR, "actual", contractRoot) + s.log.Infow("Class trie root", "expected", classR, "actual", classRoot) - if !classRoot.Equal(clsRoot) { - s.log.Errorw("class root mismatch", "got", clsRoot, "expected", classRoot) - } - - if !contractRoot.Equal(ctrtRoot) { - s.log.Errorw("contract root mismatch", "got", ctrtRoot, "expected", contractRoot) - } - - return nil + return errors.New("trie roots verification failed") } func (s *SnapSyncer) getNextStartingBlock(ctx context.Context) (*core.Block, error) { @@ -440,7 +445,6 @@ func (s *SnapSyncer) runClassRangeWorker(ctx context.Context) error { case *spec.ClassRangeResponse_Classes: classes = v.Classes.Classes case *spec.ClassRangeResponse_Fin: - s.log.Infow("[finMsg] class range completed") break ResponseIter default: s.log.Warnw("Unexpected class range message", "GetResponses", v) @@ -491,16 +495,15 @@ func (s *SnapSyncer) runClassRangeWorker(ctx context.Context) error { err = egrp.Wait() if err != nil { - s.log.Infow("class range adaptation failure", "err", err) + s.log.Errorw("class range adaptation failure", "err", err) return err } - s.log.Infow("class range adaptation completed", "classes", len(classes)) proofs := P2pProofToTrieProofs(response.RangeProof) hasNext, err := VerifyTrie(classRoot, paths, values, proofs, core.GlobalTrieHeight, crypto.Poseidon) if err != nil { // TODO: Ban peer - s.log.Infow("trie verification failed", "err", err) + s.log.Errorw("trie verification failed", "err", err) return err } @@ -518,7 +521,6 @@ func (s *SnapSyncer) runClassRangeWorker(ctx context.Context) error { panic(err) } totalAdded += len(classes) - s.log.Infow("class range added classes into state", "classes", len(classes), "total", totalAdded) if !hasNext { s.log.Infow("class range completed", "totalClass", totalAdded) @@ -579,7 +581,6 @@ func (s *SnapSyncer) runFetchClassWorker(ctx context.Context, workerIdx int) err atomic.AddInt32(&s.classFetchJobCount, -1) } } - s.log.Infow("class fetch job completes batch", "asked keys", len(keyBatches), "worker", workerIdx, "pending", s.classFetchJobCount) var hashes []*spec.Hash for _, key := range keyBatches { @@ -607,7 +608,6 @@ func (s *SnapSyncer) runFetchClassWorker(ctx context.Context, workerIdx int) err case *spec.ClassesResponse_Class: classes = append(classes, v.Class) case *spec.ClassesResponse_Fin: - s.log.Infow("[FinMsg] class batch completed", "classes", len(classes), "worker", workerIdx) break ResponseIter default: s.log.Warnw("Unexpected ClassMessage from getClasses", "v", v) @@ -649,7 +649,6 @@ func (s *SnapSyncer) runFetchClassWorker(ctx context.Context, workerIdx int) err s.log.Errorw("error storing class", "err", err) return err } - s.log.Infow("class fetch job added classes into state", "classes", len(newClasses), "worker", workerIdx) } else { s.log.Errorw("Unable to fetch any class from peer") // TODO: Penalise peer? @@ -663,7 +662,6 @@ func (s *SnapSyncer) runFetchClassWorker(ctx context.Context, workerIdx int) err } keyBatches = newBatch - s.log.Infow("class fetch job completed batch", "processed", len(processedClasses), "unprocessed", len(newBatch), "worker", workerIdx) } } @@ -701,7 +699,6 @@ func (s *SnapSyncer) runContractRangeWorker(ctx context.Context) error { case *spec.ContractRangeResponse_Range: crange = v.Range case *spec.ContractRangeResponse_Fin: - s.log.Infow("[finMsg] contract range completed", "totalAdded", totalAdded) break ResponseIter default: s.log.Warnw("Unexpected contract range message", "GetResponses", v) @@ -742,10 +739,9 @@ func (s *SnapSyncer) runContractRangeWorker(ctx context.Context) error { hasNext, err := VerifyTrie(contractRoot, paths, values, proofs, core.GlobalTrieHeight, crypto.Pedersen) if err != nil { // The peer should get penalised in this case - s.log.Infow("trie verification failed", "err", err) + s.log.Errorw("trie verification failed", "err", err) return err } - s.log.Infow("contract range adaptation completed", "hasNext", hasNext, "states", len(paths), "totalAdded", totalAdded) classes := []*felt.Felt{} nonces := []*felt.Felt{} @@ -761,7 +757,6 @@ func (s *SnapSyncer) runContractRangeWorker(ctx context.Context) error { panic(err) } totalAdded += len(paths) - s.log.Infow("contract range added contracts into state", "contracts", len(paths), "totalAdded", totalAdded) // We don't actually store it directly here... only put it as part of job. // Can't remember why. Could be because it would be some wasted work. @@ -888,9 +883,6 @@ func (s *SnapSyncer) runStorageRangeWorker(ctx context.Context, workerIdx int) e case *spec.ContractStorageResponse_Storage: csto = v.Storage case *spec.ContractStorageResponse_Fin: - s.log.Infow("[FinMsg] storage range completed", - "totalPath", totalPath, "worker", workerIdx, "jobs", processedJobs.jobIdx, - "pending", s.storageRangeJobCount, "contract", processedJobs.jobAddr) break ResponseIter default: s.log.Warnw("Unexpected storage range message", "GetResponses", v) @@ -1072,13 +1064,11 @@ func (s *SnapSyncer) runStorageRefreshWorker(ctx context.Context) error { case *spec.ContractRangeResponse_Range: crange = v.Range case *spec.ContractRangeResponse_Fin: - s.log.Infow("[finMsg] contract range [storage refresh] completed") break ResponseIter default: s.log.Warnw("Unexpected contract range message [storage refresh]", "GetResponses", v) continue } - s.log.Infow("storage refresh worker received response", "states", len(crange.State)) if crange == nil || crange.State == nil { s.log.Errorw("contract range [storage refresh] respond with nil state") From 0de87e1dfddebdf9f4f48dae7cd0a4f7818e6bc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowosielski?= Date: Tue, 1 Oct 2024 14:40:16 +0200 Subject: [PATCH 18/23] code improvements --- p2p/snap_syncer.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/p2p/snap_syncer.go b/p2p/snap_syncer.go index a7afbb58c9..5494bca192 100644 --- a/p2p/snap_syncer.go +++ b/p2p/snap_syncer.go @@ -434,10 +434,8 @@ func (s *SnapSyncer) runClassRangeWorker(ctx context.Context) error { ResponseIter: for response := range classIter { if response == nil { - if response == nil { - s.log.Errorw("contract range respond with nil response") - continue - } + s.log.Errorw("contract range respond with nil response") + continue } var classes []*spec.Class @@ -451,7 +449,7 @@ func (s *SnapSyncer) runClassRangeWorker(ctx context.Context) error { continue } - if classes == nil || len(classes) == 0 { + if len(classes) == 0 { s.log.Errorw("class range respond with empty classes") continue } From 3fcef80ef80b91d73cdfaf5d226d964c158e6ff7 Mon Sep 17 00:00:00 2001 From: weiihann Date: Fri, 25 Oct 2024 13:34:02 +0800 Subject: [PATCH 19/23] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 8ca930797e6d76acb92c7ca99e8283733c1cff67 Author: wojciechos Date: Thu Oct 24 14:19:52 2024 +0200 Fix spec version for 0_7 endpoint (#2238) commit 00ac988d3f5febdd91ed2f5a11884da1a5deefc9 Author: Kirill Date: Tue Oct 22 18:11:24 2024 +0400 Remove v0.6 endpoints, added v0.8 endpoints (#2229) commit 731099040a8fdd4b966f201b7637a814834291fb Author: Mario Apra Date: Mon Oct 21 15:31:04 2024 +0100 chore: Update instructions on how to build Juno Signed-off-by: Mario Apra commit 0ab532f25325f75e4eba3d5389c4f29ce4eb4988 Author: Kirill Date: Mon Oct 21 15:45:15 2024 +0400 Pass required libbz2 to linker to fix compilation error on macOS (#2228) commit bdc598982e86bf950bbbe19ce88f4a9b701ff09c Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Oct 21 11:15:33 2024 +0000 Bump github.com/prometheus/client_golang from 1.20.4 to 1.20.5 Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.20.4 to 1.20.5. - [Release notes](https://github.com/prometheus/client_golang/releases) - [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md) - [Commits](https://github.com/prometheus/client_golang/compare/v1.20.4...v1.20.5) --- updated-dependencies: - dependency-name: github.com/prometheus/client_golang dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] commit bf38adb53cae5eec67fd32e4a0dd9ec5607be0df Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Oct 21 11:15:38 2024 +0000 Bump go.uber.org/mock from 0.4.0 to 0.5.0 Bumps [go.uber.org/mock](https://github.com/uber/mock) from 0.4.0 to 0.5.0. - [Release notes](https://github.com/uber/mock/releases) - [Changelog](https://github.com/uber-go/mock/blob/main/CHANGELOG.md) - [Commits](https://github.com/uber/mock/compare/v0.4.0...v0.5.0) --- updated-dependencies: - dependency-name: go.uber.org/mock dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] commit b9d987eb11401b2ddb8b6a128c564938e315b2db Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Oct 21 11:18:58 2024 +0000 Bump aquasecurity/trivy-action from 0.27.0 to 0.28.0 Bumps [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action) from 0.27.0 to 0.28.0. - [Release notes](https://github.com/aquasecurity/trivy-action/releases) - [Commits](https://github.com/aquasecurity/trivy-action/compare/5681af892cd0f4997658e2bacc62bd0a894cf564...915b19bbe73b92a6cf82a1bc12b087c9a19a5fe2) --- updated-dependencies: - dependency-name: aquasecurity/trivy-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] commit c67b458aa039c50b1855cca177f00edf114a1bb0 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Oct 21 11:19:05 2024 +0000 Update github/codeql-action requirement to 5618c9fc1e675841ca52c1c6b1304f5255a905a0 Updates the requirements on [github/codeql-action](https://github.com/github/codeql-action) to permit the latest version. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/commits/5618c9fc1e675841ca52c1c6b1304f5255a905a0) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production ... Signed-off-by: dependabot[bot] commit 8bdff962b22b3dcedff96350271df6ec140e3be0 Author: yevh Date: Mon Oct 21 11:12:37 2024 +0200 Create trivy.yml (#2226) Signed-off-by: yevh Co-authored-by: Mario Apra commit 0d02f9e75e449f047bef59fb0bbfcad0d03e68f5 Author: wojciechos Date: Fri Oct 18 12:57:02 2024 +0200 Update starknet.go, starknet-js and starknet-rs tests (#2217) commit 3833c0095d171404fd9474a50684663be0525749 Author: Mario Apra Date: Fri Oct 18 10:44:26 2024 +0100 Remove UPX from macOS build process Updated the GitHub Actions workflow to exclude UPX installation and usage on macOS. This change addresses compatibility issues with UPX on macOS, ensuring smoother build processes for macOS environments. More info: https://github.com/upx/upx/issues/612 commit eb19859609fd496feee39fafa0c9504141ee6b04 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Oct 7 11:30:13 2024 +0000 Bump actions/setup-go from 4 to 5 Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4 to 5. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] commit 7faba7c05c0de7cc9d972432b11416eccbac7d9c Author: Kirill Date: Thu Oct 17 18:05:36 2024 +0400 Add missing execution resources to fetched traces (#2222) commit f4d9d698792fd8ec711b9bf2cfc6405a098a7874 Author: Kirill Date: Thu Oct 17 15:56:02 2024 +0400 Add missing gomock.Controller.Finish() calls in plugin tests (#2225) commit f5dc02caac7c59d36015845cf9d5b3355d312da4 Author: Kirill Date: Thu Oct 17 11:15:15 2024 +0400 Small restructure of plugin logic (#2221) commit b43c46f48189926713c58a9760168ef2496c3bff Author: Rian Hughes Date: Wed Oct 16 15:42:07 2024 +0300 Support plugins (#2051) Co-authored-by: rian Co-authored-by: LordGhostX commit ca006de578cfbc5a99dc13c2f751ed0cd03adcb7 Author: Kirill Date: Wed Oct 16 16:14:16 2024 +0400 Replace UnmarshalJSON() with UnmarshalText() for transaction statuses (#2220) * Replace UnmarshalJSON() with UnmarshalText() for transaction statuses UnmarshalText avoids issues with forgetting quotes in JSON, making it simpler for parsing plain text values. commit 935b903f8dcd1fa435aa365d0735bc7a99502300 Author: Kirill Date: Wed Oct 16 13:40:55 2024 +0400 Add db revert cmd (#2216) commit a1be2ebdec563c0e08189f4abc30dbbd07420b05 Author: Mario Apra Date: Fri Oct 11 07:56:32 2024 +0100 chore: Fix how to send comments to github Added necessary permissions to the GitHub Actions workflow to allow writing comments on issues and pull requests. This enables automated feedback and notifications, improving workflow efficiency and communication. commit 6937def6ee72dfe40443f0798cb78fbd636ef2d0 Author: Mario Apra Date: Wed Oct 9 13:00:52 2024 +0100 feat: Check for rust version in Makefile - Enhanced MSRV check in GitHub workflow to include Makefile updates. - Added a new target in Makefile to ensure Rust version compliance. - Updated dependencies and formatting targets to include Rust version check. - Fixed minor formatting issues in Makefile comments. commit f83a70e5190d5f76ed8239195ed4bf672208bc4e Author: Rian Hughes Date: Tue Oct 15 17:22:32 2024 +0300 Fix traces (fees, state diff, events) (#2118) commit 384fe56ca7f20760122de8558181f11d24ee6abb Author: Ng Wei Han <47109095+weiihann@users.noreply.github.com> Date: Tue Oct 15 18:48:39 2024 +0800 Fix core/state_test tests failure (#2198) commit 9ed2accb6ecc731c67727d48817e9957aede8dec Author: wojciechos Date: Mon Oct 14 14:21:08 2024 +0200 Update starknet.go version to fix failing tests (#2215) commit 9c8370362a0d9eebf65b67651a2d037f7e99995f Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Oct 14 11:33:00 2024 +0000 Bump github.com/libp2p/go-libp2p-kad-dht from 0.26.1 to 0.27.0 Bumps [github.com/libp2p/go-libp2p-kad-dht](https://github.com/libp2p/go-libp2p-kad-dht) from 0.26.1 to 0.27.0. - [Release notes](https://github.com/libp2p/go-libp2p-kad-dht/releases) - [Commits](https://github.com/libp2p/go-libp2p-kad-dht/compare/v0.26.1...v0.27.0) --- updated-dependencies: - dependency-name: github.com/libp2p/go-libp2p-kad-dht dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] commit c28429eebda04ed57f8b36e4d8e6a822ef3663c6 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Oct 14 11:32:53 2024 +0000 Bump google.golang.org/protobuf from 1.34.2 to 1.35.1 Bumps google.golang.org/protobuf from 1.34.2 to 1.35.1. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] commit 584414117bb139c59d50b87ad203faa6d9b009d4 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri Oct 11 22:25:39 2024 +0000 Bump the npm_and_yarn group across 1 directory with 2 updates Bumps the npm_and_yarn group with 2 updates in the /docs directory: [cookie](https://github.com/jshttp/cookie) and [express](https://github.com/expressjs/express). Updates `cookie` from 0.6.0 to 0.7.1 - [Release notes](https://github.com/jshttp/cookie/releases) - [Commits](https://github.com/jshttp/cookie/compare/v0.6.0...v0.7.1) Updates `express` from 4.21.0 to 4.21.1 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/4.21.1/History.md) - [Commits](https://github.com/expressjs/express/compare/4.21.0...4.21.1) --- updated-dependencies: - dependency-name: cookie dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: express dependency-type: indirect dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] commit 1786cb3874444c37429c281c4235e0fa08a0ef5f Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Oct 7 20:36:59 2024 +0400 Bump google.golang.org/grpc from 1.67.0 to 1.67.1 (#2202) Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.67.0 to 1.67.1. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.67.0...v1.67.1) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: Kirill Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kirill commit 45ce05df1eb932ab19c04f5662506bb01fdf4e9f Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Oct 7 20:23:26 2024 +0400 Bump golang.org/x/crypto from 0.27.0 to 0.28.0 (#2201) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.27.0 to 0.28.0. - [Commits](https://github.com/golang/crypto/compare/v0.27.0...v0.28.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 73be13e913b1fa3a972a19647731c3eb5c0aafda Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Oct 7 16:55:01 2024 +0400 Bump github.com/ethereum/go-ethereum from 1.14.10 to 1.14.11 (#2203) Bump github.com/ethereum/go-ethereum from 1.14.10 to 1.14.11 Bumps [github.com/ethereum/go-ethereum](https://github.com/ethereum/go-ethereum) from 1.14.10 to 1.14.11. - [Release notes](https://github.com/ethereum/go-ethereum/releases) - [Commits](https://github.com/ethereum/go-ethereum/compare/v1.14.10...v1.14.11) --- updated-dependencies: - dependency-name: github.com/ethereum/go-ethereum dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit f20c3decbfd8292b703013ee7714be92cb3e5e11 Author: Kirill Date: Mon Oct 7 14:37:19 2024 +0400 Change tx commitment calculation for tx with empty signature (#2196) commit c07eb9d4a057e17963023a3e54af17f21233fb88 Author: Kirill Date: Mon Oct 7 14:20:12 2024 +0400 Update README.md: change header of a network commit 702f25b2296cc419d823d09b671431c14d5b2244 Author: Ng Wei Han <47109095+weiihann@users.noreply.github.com> Date: Fri Oct 4 17:37:05 2024 +0800 Add metrics for gossipsub (#2163) commit 8fcbb938319ff17193849f24b3058ae136e7b62f Author: Kirill Date: Fri Oct 4 11:33:04 2024 +0400 Remove p2p/starnket.StaticStream (#2195) commit 1b89289fefd69500fabde9e7b37d250b7a424887 Author: wojo Date: Wed Sep 18 10:24:48 2024 +0200 Improve starknet-go-tests workflow flexibility and security - Make TEST_RPC_URL and TEST_ACCOUNT_PRIVATE_KEY secrets required - Add 'ref' input parameter to allow testing different branches/tags commit 3e1044dcaecd53125c789bba6940f8a80e0e6021 Author: wojo Date: Fri Sep 13 19:09:42 2024 +0200 Add Starknet Go tests to CI/CD pipeline - Integrate starknet-go-tests workflow into development, staging, and production stages - Mirror the structure used for Rust and JavaScript tests - Use appropriate RPC URLs and account private keys for each environment - Ensure Go tests run after validation/promotion in each stage commit 7e9652c056e0ef507fe506c146cbf6f5a11cd870 Author: Ng Wei Han <47109095+weiihann@users.noreply.github.com> Date: Thu Oct 3 17:48:24 2024 +0800 Move starknet/rust deps to separate package (#2148) Co-authored-by: Mario Apra commit 262c51f7f2643ec08f2a476b0005a30e2c21c2f5 Author: Ng Wei Han <47109095+weiihann@users.noreply.github.com> Date: Thu Oct 3 17:47:46 2024 +0800 Refactor transaction into batch and snapshot (#2182) commit 0c0700c1f47ebd892ea3afbaa85440621d95de18 Author: Daniil Ankushin Date: Tue Oct 1 23:39:32 2024 +0300 Refactor commitment parallel processing (#2169) commit 6b683d8a5c73197d544ba4b0e1069743d0b5ed48 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Sep 30 17:56:49 2024 +0000 Bump github.com/ethereum/go-ethereum from 1.14.9 to 1.14.10 (#2190) Bumps [github.com/ethereum/go-ethereum](https://github.com/ethereum/go-ethereum) from 1.14.9 to 1.14.10. - [Release notes](https://github.com/ethereum/go-ethereum/releases) - [Commits](https://github.com/ethereum/go-ethereum/compare/v1.14.9...v1.14.10) --- updated-dependencies: - dependency-name: github.com/ethereum/go-ethereum dependency-type: direct:production update-type: version-update:semver-patch ... commit 9d609b62086543a62febe84a929b3491e2a7759d Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Sep 30 17:55:59 2024 +0000 Bump go.uber.org/automaxprocs from 1.5.3 to 1.6.0 (#2191) Bumps [go.uber.org/automaxprocs](https://github.com/uber-go/automaxprocs) from 1.5.3 to 1.6.0. - [Release notes](https://github.com/uber-go/automaxprocs/releases) - [Changelog](https://github.com/uber-go/automaxprocs/blob/master/CHANGELOG.md) - [Commits](https://github.com/uber-go/automaxprocs/compare/v1.5.3...v1.6.0) --- updated-dependencies: - dependency-name: go.uber.org/automaxprocs dependency-type: direct:production update-type: version-update:semver-minor ... commit 546f62ded729489eda1400dbae3576af71afa2c2 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Sep 30 17:54:40 2024 +0000 Bump google.golang.org/grpc from 1.66.2 to 1.67.0 (#2192) Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.66.2 to 1.67.0. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.66.2...v1.67.0) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 9a3f2638b77ff8f897f41ea77369b6b86a4e1bcc Author: yevh Date: Mon Sep 30 09:28:59 2024 +0200 Update codeql-analysis.yml Signed-off-by: yevh commit 201d12ee867eb99e737ef623b9bace22ad4f26d3 Author: Mario Apra Date: Wed Sep 25 15:41:53 2024 +0100 chore: Switch to ORAS for artifact promotion Updated CI/CD pipeline to use ORAS instead of Docker for artifact promotion. Changed repository references to align with ORAS. Also, change from docker registry to OCI registry commit 48fb6c54711e7475b33cd04f5c04d63941274847 Author: Daniil Ankushin Date: Wed Sep 25 16:18:14 2024 +0300 Add fuzz tests for utils (#1982) commit b240aba4392b94b058ff4d1cf1e2c42538225e2a Author: Daniil Ankushin Date: Wed Sep 25 11:51:01 2024 +0300 Refactor `syncService.start` method in p2p (#2174) commit 38638ca710f9ba63eeb7296fc7572c98651efbc0 Author: Ng Wei Han <47109095+weiihann@users.noreply.github.com> Date: Tue Sep 24 20:29:02 2024 +0800 Refactor ProofNode to use interface (#2176) commit 67430decdec8c8218a0bbe00ecba89b0863ef10b Author: Daniil Ankushin Date: Tue Sep 24 12:23:19 2024 +0300 feat: Update transactionCommitmentPoseidon function to handle different transaction types (#2153) * feat: Update transactionCommitmentPoseidon function to handle different transaction types The code changes in `transactionCommitmentPoseidon` function update the logic to handle different transaction types. With the changes, the function now checks the type of the transaction and updates the digest accordingly. For `DeployTransaction` and `L1HandlerTransaction`, the digest is updated with a zero value, while for other transaction types, the digest is updated with the transaction signature. * Added tests * Replace dynamic hashes with hardcoded value in test --------- Co-authored-by: Kirill commit 72535cc41d7b8fc78429732751d331c83de6d35b Author: Kirill Date: Tue Sep 24 11:58:05 2024 +0400 Add receipt commitment calculation in post07Hash function (#2133) commit aa241f1c07e5b0a31d779844cfa019d9d91a8395 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Sep 23 17:37:06 2024 +0400 Bump github.com/ethereum/go-ethereum from 1.14.8 to 1.14.9 (#2170) Bumps [github.com/ethereum/go-ethereum](https://github.com/ethereum/go-ethereum) from 1.14.8 to 1.14.9. - [Release notes](https://github.com/ethereum/go-ethereum/releases) - [Commits](https://github.com/ethereum/go-ethereum/compare/v1.14.8...v1.14.9) --- updated-dependencies: - dependency-name: github.com/ethereum/go-ethereum dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 21d69294b6817a9c416e2be97523bfae404dfdae Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Sep 23 13:23:29 2024 +0000 Bump github.com/bits-and-blooms/bitset from 1.14.2 to 1.14.3 (#2171) Bumps [github.com/bits-and-blooms/bitset](https://github.com/bits-and-blooms/bitset) from 1.14.2 to 1.14.3. - [Release notes](https://github.com/bits-and-blooms/bitset/releases) - [Commits](https://github.com/bits-and-blooms/bitset/compare/v1.14.2...v1.14.3) --- updated-dependencies: - dependency-name: github.com/bits-and-blooms/bitset dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 4e3d53a6d8c4122b60ac49d710bf0d81b1becf1e Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Sep 23 17:13:02 2024 +0400 Bump github.com/prometheus/client_golang from 1.20.3 to 1.20.4 (#2172) * Bump github.com/prometheus/client_golang from 1.20.3 to 1.20.4 Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.20.3 to 1.20.4. - [Release notes](https://github.com/prometheus/client_golang/releases) - [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md) - [Commits](https://github.com/prometheus/client_golang/compare/v1.20.3...v1.20.4) --- updated-dependencies: - dependency-name: github.com/prometheus/client_golang dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Bump github.com/prometheus/client_golang from 1.20.3 to 1.20.4 Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.20.3 to 1.20.4. - [Release notes](https://github.com/prometheus/client_golang/releases) - [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md) - [Commits](https://github.com/prometheus/client_golang/compare/v1.20.3...v1.20.4) --- updated-dependencies: - dependency-name: github.com/prometheus/client_golang dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kirill commit 107876a16086ee6fe2251e37035848b827748dfe Author: Daniil Ankushin Date: Mon Sep 23 14:23:58 2024 +0300 Improve receiptCommitment function with parallel processing (#2165) commit 37ed0958cf1713affc1a4f344edaf23491839053 Author: Kirill Date: Thu Sep 19 17:50:46 2024 +0400 Fix p2p sync for 0.13.2 blocks (#2146) commit 4fb5d0f59b5132945ef9904303925f0940be26aa Author: Ng Wei Han <47109095+weiihann@users.noreply.github.com> Date: Thu Sep 19 15:55:14 2024 +0800 Remove repeated close host call (#2147) commit f3c885fa02bef81f2ae140ee81d85145e3d2b7a7 Author: Daniil Ankushin Date: Wed Sep 18 18:03:02 2024 +0300 Enable nilness check in govet linter (#2161) commit 793edae5bcebc01a5d92432c4f4b7228b1d5a417 Author: Daniil Ankushin Date: Wed Sep 18 17:48:20 2024 +0300 Fix error handling in trie/node.go (#2159) chore: Fix error handling in trie/node.go commit 494f9cc12a054ae35ba51a47b16cd8b368e48001 Author: Mario Apra Date: Mon Sep 16 11:15:42 2024 +0100 chore: Add minimum rust version into readme - Fix #2139 commit 3f34c50cff70adcb4c338380f70e3af7b3b3ddb6 Author: Mario Apra Date: Fri Sep 13 21:24:25 2024 +0100 feat: Add GitHub Actions workflow to find the smallest supported Rust version commit d637844217b1aa97c864a026a6dfd838afee9b9b Author: Daniil Ankushin Date: Wed Sep 18 17:47:48 2024 +0300 L1->L2 message hashes are 256 bit hashes (#2160) * refactor: Update Hash message to use Hash256 type * Regenerate spec * refactor: Update MsgHash to use Hash256 type in AdaptReceipt function commit 5c6ffd9c5203a9d7b4dc225635900eca593075a8 Author: Daniil Ankushin Date: Wed Sep 18 12:44:08 2024 +0300 Refactor randomPeer function to use local variable (#2158) commit 1ff5e2c049bdc9cdeaced7cc7edf135560960d31 Author: Daniil Ankushin Date: Tue Sep 17 11:31:16 2024 +0300 chore: Update golangci-lint to version 1.61.0 (#2154) * chore: Update golangci-lint to version 1.61.0 * chore: Exclude G115 from gosec linter Exclude G115 from the gosec linter to address the issue with https://github.com/securego/gosec/issues/1212. * chore: Remove unused variable assignments Remove unused variable assignments in node/node.go and utils/pipeline/pipeline_test.go files. * chore: Remove unused nolint directives Remove unused nolint directives in the starknet client.go and handlers.go files. commit 6735ed5cfcf8db08276774768899a48bc5b45d11 Author: Daniil Ankushin Date: Mon Sep 16 15:19:29 2024 +0300 Add a filter for nodes to exclude a host node (#2149) * Add a filter for nodes to exclude a host node * Update p2p/p2p.go Co-authored-by: Ng Wei Han <47109095+weiihann@users.noreply.github.com> Signed-off-by: Daniil Ankushin --------- Signed-off-by: Daniil Ankushin Co-authored-by: Ng Wei Han <47109095+weiihann@users.noreply.github.com> commit 175cff21a0c4f38e9abc6f8087a83b9fbd8327e6 Author: PaweÅ‚ Nowosielski Date: Fri Sep 13 13:36:33 2024 +0200 fix: iterface is not nil unless never assigned commit 6ce6441e104b0975a15a1dd5b4c435a609954eaf Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Sep 16 11:57:32 2024 +0000 Bump golang.org/x/crypto from 0.26.0 to 0.27.0 (#2152) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.26.0 to 0.27.0. - [Commits](https://github.com/golang/crypto/compare/v0.26.0...v0.27.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 903ced6d810fd21cd7ddf27b1eba323c4fa20cd2 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Sep 16 11:21:28 2024 +0000 Bump google.golang.org/grpc from 1.66.0 to 1.66.2 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.66.0 to 1.66.2. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.66.0...v1.66.2) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] commit 471831b1f80e5ab37b702cde49f998c193057eab Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Sep 16 11:21:34 2024 +0000 Bump github.com/consensys/gnark-crypto from 0.13.0 to 0.14.0 Bumps [github.com/consensys/gnark-crypto](https://github.com/consensys/gnark-crypto) from 0.13.0 to 0.14.0. - [Release notes](https://github.com/consensys/gnark-crypto/releases) - [Changelog](https://github.com/Consensys/gnark-crypto/blob/master/CHANGELOG.md) - [Commits](https://github.com/consensys/gnark-crypto/compare/v0.13.0...v0.14.0) --- updated-dependencies: - dependency-name: github.com/consensys/gnark-crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] commit 153e651f156fd579a9e4e6676fe3ca9b881b05c2 Author: Mario Apra Date: Fri Sep 13 12:42:40 2024 +0100 chore: Update jFrog Docker credentials in CI/CD pipeline commit e6725072b9d80c4bbba81a9146a48a20129de848 Author: Mario Apra Date: Fri Sep 13 11:52:48 2024 +0100 chore: update how to deploy dev and staging envs With the https://github.com/NethermindEth/argo/pull/1094 change, it will now always track the latest tag which can be overwriten everytime this workflow runs. It means that even performing roll-backs will work commit e3b6939b6fbb67dd317460301b7fe9a456dcb752 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu Sep 12 19:42:46 2024 +0200 Bump body-parser and express in /docs (#2135) Bumps [body-parser](https://github.com/expressjs/body-parser) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together. Updates `body-parser` from 1.20.2 to 1.20.3 - [Release notes](https://github.com/expressjs/body-parser/releases) - [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md) - [Commits](https://github.com/expressjs/body-parser/compare/1.20.2...1.20.3) Updates `express` from 4.19.2 to 4.21.0 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md) - [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.0) --- updated-dependencies: - dependency-name: body-parser dependency-type: indirect - dependency-name: express dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 94158fedd889b8c6db247d68299febe3c499724d Author: AnkushinDaniil Date: Tue Sep 10 14:37:09 2024 +0300 Bump Go version to 1.23.1 commit b2f565e19e8430dca7e1cd2e879d412925ff015d Author: Kirill Date: Wed Sep 11 11:07:43 2024 +0200 Fix p2p header adapters (#2127) commit 71c7ca2a75d29adac03d55387e4b9f9368303fcc Author: Daniil Ankushin Date: Tue Sep 10 12:37:58 2024 +0300 Remove unused code in node.newL1Client() (#2128) commit b89e0786091a683049a62c7eac83588557934fe7 Author: Kanishka Date: Mon Sep 9 18:57:43 2024 +0530 Add l1 metrics (#1678) Co-authored-by: Kirill commit 8a96b78a679b39b8fce8e3c88d4c75831c2c82a7 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Sep 9 12:57:25 2024 +0100 Bump github.com/go-playground/validator/v10 from 10.22.0 to 10.22.1 (#2124) * Bump github.com/go-playground/validator/v10 from 10.22.0 to 10.22.1 Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.22.0 to 10.22.1. - [Release notes](https://github.com/go-playground/validator/releases) - [Commits](https://github.com/go-playground/validator/compare/v10.22.0...v10.22.1) --- updated-dependencies: - dependency-name: github.com/go-playground/validator/v10 dependency-type: direct:production update-type: version-update:semver-patch ... --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 4456d93b21012cb0c6b55eb760a4f5d31e5ee8da Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Sep 9 12:56:56 2024 +0100 Bump github.com/prometheus/client_golang from 1.20.2 to 1.20.3 (#2125) * Bump github.com/prometheus/client_golang from 1.20.2 to 1.20.3 Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.20.2 to 1.20.3. - [Release notes](https://github.com/prometheus/client_golang/releases) - [Changelog](https://github.com/prometheus/client_golang/blob/v1.20.3/CHANGELOG.md) - [Commits](https://github.com/prometheus/client_golang/compare/v1.20.2...v1.20.3) --- updated-dependencies: - dependency-name: github.com/prometheus/client_golang dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 990a5804d79e1b4f81cbade00e426f6472cdaa39 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Sep 9 11:11:29 2024 +0000 Bump github.com/libp2p/go-libp2p-kad-dht from 0.25.2 to 0.26.1 Bumps [github.com/libp2p/go-libp2p-kad-dht](https://github.com/libp2p/go-libp2p-kad-dht) from 0.25.2 to 0.26.1. - [Release notes](https://github.com/libp2p/go-libp2p-kad-dht/releases) - [Commits](https://github.com/libp2p/go-libp2p-kad-dht/compare/v0.25.2...v0.26.1) --- updated-dependencies: - dependency-name: github.com/libp2p/go-libp2p-kad-dht dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] commit 238c43cb2d42e6b3c11be7d30d9c1b36bde207c6 Author: Onur Yuce <63292060+onuruci@users.noreply.github.com> Date: Fri Sep 6 12:41:52 2024 +0300 merge and split proofs (#1984) * implement logic to support ordering, merging and splitting of proof nodes sent over P2P --------- Co-authored-by: Rian Hughes commit dc91f9b3be9e9412ca352e1e74148bdde30459ca Author: Mario Apra Date: Thu Sep 5 10:26:47 2024 +0100 Improve build binaries pipeline Changes made: - build macOS arm and x86 - rename architecture from amd64 to x86_64, which is much easier to distinct from arm - remove unnecessary step for self-hosted which is not being used anymore - create ARTIFACT_NAME env var, so it's much easier to read the code - remove unnecessary id - add trigger on PR that change the build binaries files - get architecture during runtime commit 4f3231ba53b15f180faf7b6104a900d58782b937 Author: Ă–mer Faruk Irmak Date: Thu Sep 5 17:17:33 2024 +0300 Optimize reading felts from state (#1713) Co-authored-by: onuruci commit c0008d7161a88e37f722322b2cbe69b64b0024ee Author: Mario Apra Date: Tue Sep 3 13:16:49 2024 +0100 Update number of cores for ubuntu-arm64 in juno-test workflow commit 1862240f8ff8da6f94fb7c2e1a0f062bbe5780ba Author: Kirill Date: Tue Sep 3 14:48:32 2024 +0200 Update blockifier to 0.8.0-rc.3 (#2116) commit cb350dffa9f7accddc64162b2341e73043f13a87 Author: Ng Wei Han <47109095+weiihann@users.noreply.github.com> Date: Mon Sep 2 23:07:46 2024 +0800 Remove additional "kad" substring from p2p discovery protocol name (#2113) commit 52d16ab18ca9466c02f7f4d4e0d04d2682d72fd5 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Sep 2 11:12:50 2024 +0000 Bump google.golang.org/grpc from 1.65.0 to 1.66.0 Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.65.0 to 1.66.0. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.65.0...v1.66.0) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] commit 552327aa74dcf4871eecde74901c7a63ea985780 Author: Kirill Date: Mon Sep 2 15:23:49 2024 +0200 Rewrite semver usage with new methods .LessThanEqual() and .GreaterThanEqual() (#2112) commit 8f8d37a4b8f5b2d7e60790b4f8d01e10a4741b69 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Sep 2 11:12:57 2024 +0000 Bump github.com/Masterminds/semver/v3 from 3.2.1 to 3.3.0 Bumps [github.com/Masterminds/semver/v3](https://github.com/Masterminds/semver) from 3.2.1 to 3.3.0. - [Release notes](https://github.com/Masterminds/semver/releases) - [Changelog](https://github.com/Masterminds/semver/blob/master/CHANGELOG.md) - [Commits](https://github.com/Masterminds/semver/compare/v3.2.1...v3.3.0) --- updated-dependencies: - dependency-name: github.com/Masterminds/semver/v3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] commit ef162c31edec97680027793d07c97cfe1b9165d7 Author: Kirill Date: Mon Sep 2 14:19:06 2024 +0200 Rewrite func iterator to range loop in p2p/starknet.streamHandler (#2106) commit 77b0d60d3ccbb368c8ac290e834469fcf08735aa Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Sep 2 12:04:14 2024 +0000 Bump github.com/rs/cors from 1.11.0 to 1.11.1 (#2108) Bumps [github.com/rs/cors](https://github.com/rs/cors) from 1.11.0 to 1.11.1. - [Commits](https://github.com/rs/cors/compare/v1.11.0...v1.11.1) --- updated-dependencies: - dependency-name: github.com/rs/cors dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 5e6e86c14442e82bd44cc17cc608e84115472e5f Author: Priyank Makwana <117025290+RuneRogue@users.noreply.github.com> Date: Fri Aug 30 18:57:33 2024 +0530 Emit events on remote db (#2088) commit daf1cde54900ce59df45eba742285d4524197067 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri Aug 30 12:57:27 2024 +0200 Bump webpack from 5.91.0 to 5.94.0 in /docs (#2100) Bumps [webpack](https://github.com/webpack/webpack) from 5.91.0 to 5.94.0. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.91.0...v5.94.0) --- updated-dependencies: - dependency-name: webpack dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit a4eac4cd0d484299669a7654a331d7af3751433b Author: Kirill Date: Fri Aug 30 12:28:33 2024 +0200 Improve trace logging (#2101) Improve trace logging. 1. "DEBUG" prefix has been removed 2. Added color support 3. Fixed caller reference commit 90d04becbb85382caaea42130a34d755019bd915 Author: Onur Yuce <63292060+onuruci@users.noreply.github.com> Date: Thu Aug 29 01:23:26 2024 +0300 Added /ready/sync endpoint (#2060) commit 54e20a6b6bf9854b723cc6ef462308aa52c5732f Author: Kirill Date: Wed Aug 28 11:53:00 2024 +0200 Add output field to vm.ComputationResources (#2096) commit 0f2d88bbc4150b6e09f33ca0492f0fb343089852 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue Aug 27 09:28:58 2024 +0000 Bump micromatch from 4.0.7 to 4.0.8 in /docs (#2094) Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.7 to 4.0.8. - [Release notes](https://github.com/micromatch/micromatch/releases) - [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/micromatch/compare/4.0.7...4.0.8) --- updated-dependencies: - dependency-name: micromatch dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit dd99bc16a3a53ebc58b7f86a092d1da2370d4314 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue Aug 27 11:14:24 2024 +0200 Bump github.com/bits-and-blooms/bitset from 1.13.0 to 1.14.2 (#2091) Bumps [github.com/bits-and-blooms/bitset](https://github.com/bits-and-blooms/bitset) from 1.13.0 to 1.14.2. - [Release notes](https://github.com/bits-and-blooms/bitset/releases) - [Commits](https://github.com/bits-and-blooms/bitset/compare/v1.13.0...v1.14.2) --- updated-dependencies: - dependency-name: github.com/bits-and-blooms/bitset dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit fe5add8deba59f91ba6dffd92d7314ea21486b92 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue Aug 27 10:59:24 2024 +0200 Bump github.com/spf13/cobra from 1.8.0 to 1.8.1 (#2090) Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.8.0 to 1.8.1. - [Release notes](https://github.com/spf13/cobra/releases) - [Commits](https://github.com/spf13/cobra/compare/v1.8.0...v1.8.1) --- updated-dependencies: - dependency-name: github.com/spf13/cobra dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 7e2e13f3e34d4f2c05945f1a846177877d8c77ac Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue Aug 27 10:32:22 2024 +0200 Bump github.com/prometheus/client_golang from 1.20.0 to 1.20.2 (#2089) Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.20.0 to 1.20.2. - [Release notes](https://github.com/prometheus/client_golang/releases) - [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md) - [Commits](https://github.com/prometheus/client_golang/compare/v1.20.0...v1.20.2) --- updated-dependencies: - dependency-name: github.com/prometheus/client_golang dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit acda2a222b74434fc9fe7ecd977dc9b3b7f4943f Author: Osakpolor Obaseki <12957252+obasekiosa@users.noreply.github.com> Date: Fri Aug 23 10:28:14 2024 +0100 Add execution stepcount in header [X-Cairo-Steps] (#1989) Co-authored-by: Kirill commit 692bd490dd508d7b5bfcdff0d31e2656b42fdc1f Author: Mario Apra Date: Wed Aug 21 18:13:50 2024 +0100 chore: Remove concurrency settings in child workflow When the parent and the child workflow set the concurrency, it creates a deadlock. More info: https://github.com/github/vscode-github-actions/issues/135 commit eb2eb04fa95faec1c7f999c3ee19277dd6150905 Author: Daniil Ankushin Date: Wed Aug 21 20:19:11 2024 +0300 Update `blockifier` to 0.8.0-rc.2 (#2081) * Update blockifier dependency to version 0.8.0-rc.2 * Update starknet_api and cairo-vm dependencies This commit updates the `starknet_api` dependency to version 0.13.0-rc.1 and the `cairo-vm` dependency to version 1.0.1 in the `Cargo.toml` file. * Update once_cell dependency to version 1.19.0 * Update dependencies in Cargo.toml * Update serde dependency to version 1.0.208 * Update serde_json dependency to version 1.0.125 * Update cairo-vm dependency to version 1.0.1 * Update dependencies in Cargo.toml commit 5c333255c71f9c19d56127a4537889fcb6a49f68 Author: Osakpolor Obaseki <12957252+obasekiosa@users.noreply.github.com> Date: Wed Aug 21 10:49:18 2024 +0100 Make starknet_getTransactionReceipt and other function calls response conform to the 0.7.1 spec (#2078) Co-authored-by: Kirill commit af674c3decc9cd6bd50b583d00a1012a8f0a8ddc Author: Kirill Date: Wed Aug 21 11:39:44 2024 +0300 Replace custom code with maps package functions (#2073) commit c391201aba707d33fa7f7ae4315856a0ca3d26a0 Author: Mario Apra Date: Tue Aug 20 14:45:54 2024 +0100 Replace nhooyr.io/websocket with github.com/coder/websocket (#2079) * Replace nhooyr.io/websocket with github.com/coder/websocket * format jsonrpc/websocket_test.go with gci fix lint errors --------- Co-authored-by: onuruci commit 52c97ba1daf21ccc69d6cbea212d8f3c1fdafc12 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue Aug 20 11:28:02 2024 +0000 Bump github.com/ethereum/go-ethereum from 1.14.7 to 1.14.8 (#2075) Bumps [github.com/ethereum/go-ethereum](https://github.com/ethereum/go-ethereum) from 1.14.7 to 1.14.8. - [Release notes](https://github.com/ethereum/go-ethereum/releases) - [Commits](https://github.com/ethereum/go-ethereum/compare/v1.14.7...v1.14.8) --- updated-dependencies: - dependency-name: github.com/ethereum/go-ethereum dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 4e99b580b1aabfe0d485ed39171793ae8c67d824 Author: Kirill Date: Tue Aug 20 14:15:03 2024 +0300 Add adapter for starknet.TransactionStatus in rpc (#2072) commit 6f1d6d9b078088d794f7edb4cb1f032be3533b5d Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue Aug 20 09:48:42 2024 +0000 Bump github.com/libp2p/go-libp2p-pubsub from 0.11.0 to 0.12.0 (#2076) Bumps [github.com/libp2p/go-libp2p-pubsub](https://github.com/libp2p/go-libp2p-pubsub) from 0.11.0 to 0.12.0. - [Release notes](https://github.com/libp2p/go-libp2p-pubsub/releases) - [Commits](https://github.com/libp2p/go-libp2p-pubsub/compare/v0.11.0...v0.12.0) --- updated-dependencies: - dependency-name: github.com/libp2p/go-libp2p-pubsub dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 946560bb3709b95067080b672971e665f88909d9 Author: wojciechos Date: Mon Aug 19 21:54:45 2024 +0200 Add sepolia integration snapshot to docs (#2071) commit 5855946e378db12e53fe7aa988d4235c4e0c2de3 Author: Mario Apra Date: Mon Aug 19 20:36:40 2024 +0100 chore: Ignore specific dependencies in dependabot.yml commit de9f20b4951e4d248db9e25764d327aed3b40c16 Author: Mario Apra Date: Mon Aug 19 11:49:53 2024 +0100 ci: Update concurrency settings in GitHub workflows If there are two jobs running for the same branch, cancel the oldest one. This is useful when merging many PRs at the same time (for example the ones from dependabot), avoiding running the tests/deployment for every single commit commit 32ef2d6a117ed835e1a60fe8f1200fc51c2c85ba Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Aug 19 18:08:17 2024 +0300 Bump github.com/prometheus/client_golang from 1.19.1 to 1.20.0 (#2064) Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.19.1 to 1.20.0. - [Release notes](https://github.com/prometheus/client_golang/releases) - [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md) - [Commits](https://github.com/prometheus/client_golang/compare/v1.19.1...v1.20.0) --- updated-dependencies: - dependency-name: github.com/prometheus/client_golang dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit 0ac7e8744fed40cfcf049dbb2863c48b1b8646a5 Author: Daniil Ankushin Date: Mon Aug 19 17:36:00 2024 +0300 Rewrite iterator functions with new for loop statement (#1954) commit 50bc46d15cdb8649ac1d2e9a4bf438f7ace0e6ce Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Aug 19 16:54:07 2024 +0300 Bump github.com/cockroachdb/pebble from 1.1.1 to 1.1.2 (#2063) Bumps [github.com/cockroachdb/pebble](https://github.com/cockroachdb/pebble) from 1.1.1 to 1.1.2. - [Release notes](https://github.com/cockroachdb/pebble/releases) - [Commits](https://github.com/cockroachdb/pebble/compare/v1.1.1...v1.1.2) --- updated-dependencies: - dependency-name: github.com/cockroachdb/pebble dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> commit efe0fc1eba6616a10d3d99b7d0c8348ced7455b3 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Aug 19 16:18:50 2024 +0300 Bump github.com/bits-and-blooms/bloom/v3 from 3.6.0 to 3.7.0 (#2065) Bumps [github.com/bits-and-blooms/bloom/v3](https://github.com/bits-and-blooms/bloom) from 3.6.0 to 3.7.0. - [Release notes](https://github.com/bits-and-blooms/bloom/releases) - [Commits](https://github.com/bits-and-blooms/bloom/compare/v3.6.0...v3.7.0) --- updated-dependencies: - dependency-name: github.com/bits-and-blooms/bloom/v3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Onur Yuce <63292060+onuruci@users.noreply.github.com> commit d65d4b48da2fae522c24cc4dd955346dd9313011 Author: Mario Apra Date: Mon Aug 19 12:37:18 2024 +0100 fix: Remove unnecessary go in matrix CI is getting the go version from go.mod, and there is no reference for matrix.go. Therefore we can conclude that this is not being used. The annoying part with this is when upgrading the go version, the job will be a different name, and someone needs to manually go to the protected rule for the main branch and update what's the name of the tests --- adapters/p2p2core/felt.go | 1 - core/trie/snap_support.go | 15 +++++++-------- db/pebble/batch.go | 20 +++++++++++++++----- db/pebble/db.go | 1 - p2p/p2p.go | 6 ++---- p2p/snap_server.go | 30 ++++++++++++++---------------- p2p/snap_syncer.go | 16 ++++++---------- p2p/starknet/snap_provider.go | 3 ++- p2p/sync.go | 8 ++++---- 9 files changed, 50 insertions(+), 50 deletions(-) diff --git a/adapters/p2p2core/felt.go b/adapters/p2p2core/felt.go index 60a25065ec..0d200fcf18 100644 --- a/adapters/p2p2core/felt.go +++ b/adapters/p2p2core/felt.go @@ -7,7 +7,6 @@ import ( "github.com/NethermindEth/juno/core/felt" "github.com/NethermindEth/juno/p2p/starknet/spec" "github.com/ethereum/go-ethereum/common" - "reflect" ) func AdaptHash(h *spec.Hash) *felt.Felt { diff --git a/core/trie/snap_support.go b/core/trie/snap_support.go index 6caf6b834d..7c2ae76363 100644 --- a/core/trie/snap_support.go +++ b/core/trie/snap_support.go @@ -175,25 +175,24 @@ func buildKeys(currentKey Key, currentNode *felt.Felt, proofMap map[felt.Felt]Pr return nil } - if proofNode.Edge != nil { - chKey := currentKey.Append(proofNode.Edge.Path) - ch := proofNode.Edge.Child + switch node := proofNode.(type) { + case *Edge: + chKey := currentKey.Append(node.Path) + ch := node.Child err := buildKeys(chKey, ch, proofMap, keys, depth+1) if err != nil { return err } - } else { - binary := proofNode.Binary - + case *Binary: chKey := currentKey.AppendBit(false) - ch := binary.LeftHash + ch := node.LeftHash err := buildKeys(chKey, ch, proofMap, keys, depth+1) if err != nil { return err } chKey = currentKey.AppendBit(true) - ch = binary.RightHash + ch = node.RightHash err = buildKeys(chKey, ch, proofMap, keys, depth+1) if err != nil { return err diff --git a/db/pebble/batch.go b/db/pebble/batch.go index 60747d6a86..5646f341c8 100644 --- a/db/pebble/batch.go +++ b/db/pebble/batch.go @@ -14,14 +14,15 @@ var _ db.Transaction = (*batch)(nil) type batch struct { batch *pebble.Batch - lock *sync.Mutex + dbLock *sync.Mutex + rwlock sync.RWMutex listener db.EventListener } -func NewBatch(dbBatch *pebble.Batch, lock *sync.Mutex, listener db.EventListener) *batch { +func NewBatch(dbBatch *pebble.Batch, dbLock *sync.Mutex, listener db.EventListener) *batch { return &batch{ batch: dbBatch, - lock: lock, + dbLock: dbLock, listener: listener, } } @@ -34,8 +35,8 @@ func (b *batch) Discard() error { err := b.batch.Close() b.batch = nil - b.lock.Unlock() - b.lock = nil + b.dbLock.Unlock() + b.dbLock = nil return err } @@ -53,6 +54,9 @@ func (b *batch) Commit() error { // Set : see db.Transaction.Set func (b *batch) Set(key, val []byte) error { + b.rwlock.Lock() + defer b.rwlock.Unlock() + start := time.Now() if len(key) == 0 { return errors.New("empty key") @@ -69,6 +73,9 @@ func (b *batch) Set(key, val []byte) error { // Delete : see db.Transaction.Delete func (b *batch) Delete(key []byte) error { + b.rwlock.Lock() + defer b.rwlock.Unlock() + if b.batch == nil { return ErrDiscardedTransaction } @@ -81,6 +88,9 @@ func (b *batch) Delete(key []byte) error { // Get : see db.Transaction.Get func (b *batch) Get(key []byte, cb func([]byte) error) error { + b.rwlock.RLock() + defer b.rwlock.RUnlock() + if b.batch == nil { return ErrDiscardedTransaction } diff --git a/db/pebble/db.go b/db/pebble/db.go index d3b930335c..079bbd064d 100644 --- a/db/pebble/db.go +++ b/db/pebble/db.go @@ -94,7 +94,6 @@ func (d *DB) NewTransaction(update bool) (db.Transaction, error) { d.wMutex.Lock() return NewBatch(d.pebble.NewIndexedBatch(), d.wMutex, d.listener), nil } - txn.rwlock = &sync.RWMutex{} return NewSnapshot(d.pebble.NewSnapshot(), d.listener), nil } diff --git a/p2p/p2p.go b/p2p/p2p.go index 00730316f5..c5dfca89ad 100644 --- a/p2p/p2p.go +++ b/p2p/p2p.go @@ -48,11 +48,9 @@ type Service struct { topics map[string]*pubsub.Topic topicsLock sync.RWMutex - downloader *Downloader - synchroniser *syncService + downloader *Downloader + synchroniser *SyncService gossipTracer *gossipTracer - snapSyncher service.Service - //snapServer *snapServer feederNode bool database db.DB diff --git a/p2p/snap_server.go b/p2p/snap_server.go index bb698fb68f..33612d04ef 100644 --- a/p2p/snap_server.go +++ b/p2p/snap_server.go @@ -1,6 +1,7 @@ package p2p import ( + "iter" "math/big" "github.com/NethermindEth/juno/utils" @@ -13,7 +14,6 @@ import ( "github.com/NethermindEth/juno/core/felt" "github.com/NethermindEth/juno/core/trie" "github.com/NethermindEth/juno/p2p/starknet/spec" - "github.com/NethermindEth/juno/utils/iter" "github.com/ethereum/go-ethereum/log" ) @@ -426,26 +426,24 @@ func Core2P2pProof(proofs []trie.ProofNode) *spec.PatriciaRangeProof { nodes := make([]*spec.PatriciaNode, len(proofs)) for i := range proofs { - if proofs[i].Binary != nil { - binary := proofs[i].Binary + switch node := proofs[i].(type) { + case *trie.Edge: + pathFelt := node.Path.Felt() nodes[i] = &spec.PatriciaNode{ - Node: &spec.PatriciaNode_Binary_{ - Binary: &spec.PatriciaNode_Binary{ - Left: core2p2p.AdaptFelt(binary.LeftHash), - Right: core2p2p.AdaptFelt(binary.RightHash), + Node: &spec.PatriciaNode_Edge_{ + Edge: &spec.PatriciaNode_Edge{ + Length: uint32(node.Path.Len()), + Path: core2p2p.AdaptFelt(&pathFelt), + Value: core2p2p.AdaptFelt(node.Child), }, }, } - } - if proofs[i].Edge != nil { - edge := proofs[i].Edge - pathfeld := edge.Path.Felt() + case *trie.Binary: nodes[i] = &spec.PatriciaNode{ - Node: &spec.PatriciaNode_Edge_{ - Edge: &spec.PatriciaNode_Edge{ - Length: uint32(edge.Path.Len()), - Path: core2p2p.AdaptFelt(&pathfeld), - Value: core2p2p.AdaptFelt(edge.Child), + Node: &spec.PatriciaNode_Binary_{ + Binary: &spec.PatriciaNode_Binary{ + Left: core2p2p.AdaptFelt(node.LeftHash), + Right: core2p2p.AdaptFelt(node.RightHash), }, }, } diff --git a/p2p/snap_syncer.go b/p2p/snap_syncer.go index 5494bca192..44231742a4 100644 --- a/p2p/snap_syncer.go +++ b/p2p/snap_syncer.go @@ -1230,21 +1230,17 @@ func P2pProofToTrieProofs(proof *spec.PatriciaRangeProof) []trie.ProofNode { for i, node := range proof.Nodes { if node.GetBinary() != nil { binary := node.GetBinary() - proofs[i] = trie.ProofNode{ - Binary: &trie.Binary{ - LeftHash: p2p2core.AdaptFelt(binary.Left), - RightHash: p2p2core.AdaptFelt(binary.Right), - }, + proofs[i] = &trie.Binary{ + LeftHash: p2p2core.AdaptFelt(binary.Left), + RightHash: p2p2core.AdaptFelt(binary.Right), } } else { edge := node.GetEdge() // TODO. What if edge is nil too? key := trie.NewKey(uint8(edge.Length), edge.Path.Elements) - proofs[i] = trie.ProofNode{ - Edge: &trie.Edge{ - Child: p2p2core.AdaptFelt(edge.Value), - Path: &key, - }, + proofs[i] = &trie.Edge{ + Child: p2p2core.AdaptFelt(edge.Value), + Path: &key, } } } diff --git a/p2p/starknet/snap_provider.go b/p2p/starknet/snap_provider.go index 8832a9f6ac..59ede0d192 100644 --- a/p2p/starknet/snap_provider.go +++ b/p2p/starknet/snap_provider.go @@ -1,8 +1,9 @@ package starknet import ( + "iter" + "github.com/NethermindEth/juno/p2p/starknet/spec" - "github.com/NethermindEth/juno/utils/iter" "google.golang.org/protobuf/proto" ) diff --git a/p2p/sync.go b/p2p/sync.go index 37856ec041..1c11ba64e0 100644 --- a/p2p/sync.go +++ b/p2p/sync.go @@ -87,7 +87,7 @@ func (s *SyncService) Start(ctx context.Context) { } } -func (s *syncService) getNextHeight() (int, error) { +func (s *SyncService) getNextHeight() (int, error) { curHeight, err := s.blockchain.Height() if err == nil { return int(curHeight) + 1, nil @@ -97,7 +97,7 @@ func (s *syncService) getNextHeight() (int, error) { return 0, err } -func (s *syncService) processBlock(ctx context.Context, blockNumber uint64) error { +func (s *SyncService) processBlock(ctx context.Context, blockNumber uint64) error { headersAndSigsCh, err := s.genHeadersAndSigs(ctx, blockNumber) if err != nil { return fmt.Errorf("failed to get block headers parts: %w", err) @@ -270,7 +270,7 @@ func (s *SyncService) processSpecBlockParts( return orderedBlockBodiesCh } -//nolint:gocyclo,funlen +//nolint:gocyclo func (s *SyncService) adaptAndSanityCheckBlock(ctx context.Context, header *spec.SignedBlockHeader, contractDiffs []*spec.ContractDiff, classes []*spec.Class, txs []*spec.Transaction, receipts []*spec.Receipt, events []*spec.Event, prevBlockRoot *felt.Felt, ) <-chan blockBody { @@ -631,7 +631,7 @@ func (s *SyncService) genTransactions(ctx context.Context, blockNumber uint64) ( return txsCh, nil } -func (s *syncService) randomPeer() peer.ID { +func (s *SyncService) randomPeer() peer.ID { store := s.host.Peerstore() // todo do not request same block from all peers peers := utils.Filter(store.Peers(), func(peerID peer.ID) bool { From d0b83e299252cb22c338e1eb3672b9040c3354a8 Mon Sep 17 00:00:00 2001 From: weiihann Date: Fri, 25 Oct 2024 13:52:12 +0800 Subject: [PATCH 20/23] fix lint --- blockchain/snap_server_interface.go | 15 +++++++------- cmd/juno/juno.go | 2 +- core/trie/snap_support_test.go | 6 +++--- p2p/downloader.go | 18 +++++++++++------ p2p/modes.go | 2 +- p2p/p2p.go | 15 +++++++++----- p2p/snap_server.go | 15 +++++++------- p2p/snap_server_test.go | 21 +++++++++---------- p2p/snap_syncer.go | 25 +++++++---------------- p2p/starknet/client.go | 31 +++++++++++++++++++++-------- p2p/starknet/handlers.go | 8 ++++---- p2p/starknetdata.go | 19 +++++++++--------- p2p/sync.go | 1 - 13 files changed, 96 insertions(+), 82 deletions(-) diff --git a/blockchain/snap_server_interface.go b/blockchain/snap_server_interface.go index 725062e89c..d60e74fadd 100644 --- a/blockchain/snap_server_interface.go +++ b/blockchain/snap_server_interface.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/NethermindEth/juno/core" "github.com/NethermindEth/juno/core/felt" "github.com/NethermindEth/juno/db" @@ -165,22 +166,22 @@ func (b *Blockchain) Close() { } } -type Blockchain_Closer struct { +type BlockchainCloser struct { log utils.SimpleLogger bc *Blockchain } -var _ service.Service = (*Blockchain_Closer)(nil) +var _ service.Service = (*BlockchainCloser)(nil) -func NewBlockchainCloser(bc *Blockchain, log utils.SimpleLogger) *Blockchain_Closer { - return &Blockchain_Closer{log, bc} +func NewBlockchainCloser(bc *Blockchain, log utils.SimpleLogger) *BlockchainCloser { + return &BlockchainCloser{log, bc} } -func (b *Blockchain_Closer) Run(ctx context.Context) error { - b.log.Infow("Blockchain_Closer has started") +func (b *BlockchainCloser) Run(ctx context.Context) error { + b.log.Infow("BlockchainCloser has started") <-ctx.Done() b.bc.Close() - b.log.Infow("Blockchain_Closer has stopped") + b.log.Infow("BlockchainCloser has stopped") return nil } diff --git a/cmd/juno/juno.go b/cmd/juno/juno.go index fd957bf863..d5296e99b2 100644 --- a/cmd/juno/juno.go +++ b/cmd/juno/juno.go @@ -156,7 +156,7 @@ const ( p2pFeederNodeUsage = "EXPERIMENTAL: Run juno as a feeder node which will only sync from feeder gateway and gossip the new" + " blocks to the network." p2pPrivateKeyUsage = "EXPERIMENTAL: Hexadecimal representation of a private key on the Ed25519 elliptic curve." - p2pSyncModeUsage = "EXPERIMENTAL: Synchronization mode: 'full' (default), 'snap'" + p2pSyncModeUsage = "EXPERIMENTAL: Synchronisation mode: 'full' (default), 'snap'" metricsUsage = "Enables the Prometheus metrics endpoint on the default port." metricsHostUsage = "The interface on which the Prometheus endpoint will listen for requests." metricsPortUsage = "The port on which the Prometheus endpoint will listen for requests." diff --git a/core/trie/snap_support_test.go b/core/trie/snap_support_test.go index e77e974a02..dd1264c29c 100644 --- a/core/trie/snap_support_test.go +++ b/core/trie/snap_support_test.go @@ -2,9 +2,6 @@ package trie_test import ( "fmt" - "github.com/NethermindEth/juno/db/pebble" - "github.com/NethermindEth/juno/utils" - "github.com/stretchr/testify/require" "math" "testing" @@ -12,7 +9,10 @@ import ( "github.com/NethermindEth/juno/core/felt" "github.com/NethermindEth/juno/core/trie" "github.com/NethermindEth/juno/db" + "github.com/NethermindEth/juno/db/pebble" + "github.com/NethermindEth/juno/utils" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const trieHeight = 251 diff --git a/p2p/downloader.go b/p2p/downloader.go index 8f61d5b354..6bb2b466aa 100644 --- a/p2p/downloader.go +++ b/p2p/downloader.go @@ -19,7 +19,14 @@ type Downloader struct { log utils.SimpleLogger } -func NewDownloader(isFeeder bool, syncMode SyncMode, p2pHost host.Host, network *utils.Network, bc *blockchain.Blockchain, log utils.SimpleLogger) *Downloader { +func NewDownloader( + isFeeder bool, + syncMode SyncMode, + p2pHost host.Host, + network *utils.Network, + bc *blockchain.Blockchain, + log utils.SimpleLogger, +) *Downloader { dl := &Downloader{ isFeeder: isFeeder, log: log, @@ -40,10 +47,10 @@ func NewDownloader(isFeeder bool, syncMode SyncMode, p2pHost host.Host, network return dl } -func (d *Downloader) Start(ctx context.Context) error { +func (d *Downloader) Start(ctx context.Context) { // Feeder node doesn't sync using P2P if d.isFeeder { - return nil + return } d.log.Infow("Downloader start", "mode", d.getMode()) @@ -53,16 +60,15 @@ func (d *Downloader) Start(ctx context.Context) error { err := d.snapSyncer.Run(ctx) if err != nil { d.log.Errorw("Snapsyncer failed to start") - return err + return } } else { d.log.Infow("Syncing is disabled") - return nil + return } } d.baseSyncer.Start(ctx) - return nil } func (d *Downloader) getMode() SyncMode { diff --git a/p2p/modes.go b/p2p/modes.go index ef77530adb..32da59e42f 100644 --- a/p2p/modes.go +++ b/p2p/modes.go @@ -19,7 +19,7 @@ var ( type SyncMode uint32 const ( - FullSync SyncMode = iota // Synchronize by downloading blocks and applying them to the chain sequentially + FullSync SyncMode = iota // Synchronise by downloading blocks and applying them to the chain sequentially SnapSync // Download the chain and the state via snap protocol ) diff --git a/p2p/p2p.go b/p2p/p2p.go index c5dfca89ad..ec2153111b 100644 --- a/p2p/p2p.go +++ b/p2p/p2p.go @@ -52,12 +52,17 @@ type Service struct { synchroniser *SyncService gossipTracer *gossipTracer - feederNode bool - database db.DB + database db.DB } -func New(addr, publicAddr, version, peers, privKeyStr string, feederNode bool, syncMode SyncMode, bc *blockchain.Blockchain, snNetwork *utils.Network, - log utils.SimpleLogger, database db.DB, +func New( + addr, publicAddr, version, peers, privKeyStr string, + feederNode bool, + syncMode SyncMode, + bc *blockchain.Blockchain, + snNetwork *utils.Network, + log utils.SimpleLogger, + database db.DB, ) (*Service, error) { if addr == "" { // 0.0.0.0/tcp/0 will listen on any interface device and assing a free port. @@ -150,7 +155,7 @@ func NewWithHost(p2phost host.Host, peers string, feederNode bool, syncMode Sync downloader := NewDownloader(feederNode, syncMode, p2phost, snNetwork, bc, log) handler := starknet.NewHandler(bc, log) - handler.WithSnapsyncSupport(NewSnapServer(bc, log)) // TODO: initialize the snap server in the starknet handler + handler.WithSnapsyncSupport(NewSnapServer(bc, log)) // TODO: initialise the snap server in the starknet handler s := &Service{ downloader: downloader, diff --git a/p2p/snap_server.go b/p2p/snap_server.go index 33612d04ef..728f3d588e 100644 --- a/p2p/snap_server.go +++ b/p2p/snap_server.go @@ -1,12 +1,8 @@ package p2p import ( - "iter" "math/big" - "github.com/NethermindEth/juno/utils" - "google.golang.org/protobuf/proto" - "github.com/NethermindEth/juno/adapters/core2p2p" "github.com/NethermindEth/juno/adapters/p2p2core" "github.com/NethermindEth/juno/blockchain" @@ -14,7 +10,10 @@ import ( "github.com/NethermindEth/juno/core/felt" "github.com/NethermindEth/juno/core/trie" "github.com/NethermindEth/juno/p2p/starknet/spec" + "github.com/NethermindEth/juno/utils" "github.com/ethereum/go-ethereum/log" + "google.golang.org/protobuf/proto" + "iter" ) type ContractRangeStreamingResult struct { @@ -54,10 +53,10 @@ type yieldFunc = func(proto.Message) bool var _ SnapServerBlockchain = (*blockchain.Blockchain)(nil) -func NewSnapServer(blockchain SnapServerBlockchain, log utils.SimpleLogger) *snapServer { +func NewSnapServer(bc SnapServerBlockchain, logger utils.SimpleLogger) *snapServer { return &snapServer{ - log: log, - blockchain: blockchain, + log: logger, + blockchain: bc, } } @@ -285,7 +284,7 @@ func (b *snapServer) GetStorageRange(request *spec.ContractStorageRequest) (iter var curNodeLimit uint32 = 1000000 // shouldContinue is a return value from the yield function which specify whether the iteration should continue - var shouldContinue bool = true + shouldContinue := true for _, query := range request.Query { contractLimit := curNodeLimit diff --git a/p2p/snap_server_test.go b/p2p/snap_server_test.go index 1634b38eae..22adbf56bb 100644 --- a/p2p/snap_server_test.go +++ b/p2p/snap_server_test.go @@ -3,21 +3,21 @@ package p2p import ( "context" "fmt" - "github.com/NethermindEth/juno/core" - "github.com/NethermindEth/juno/core/crypto" - "github.com/stretchr/testify/require" "maps" "testing" "github.com/NethermindEth/juno/adapters/core2p2p" "github.com/NethermindEth/juno/adapters/p2p2core" "github.com/NethermindEth/juno/blockchain" + "github.com/NethermindEth/juno/core" + "github.com/NethermindEth/juno/core/crypto" "github.com/NethermindEth/juno/core/felt" "github.com/NethermindEth/juno/db" "github.com/NethermindEth/juno/db/pebble" "github.com/NethermindEth/juno/p2p/starknet/spec" "github.com/NethermindEth/juno/utils" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestClassRange(t *testing.T) { @@ -257,8 +257,8 @@ func TestContractRangeByOneContract(t *testing.T) { storageRoot := p2p2core.AdaptHash(crct.Storage) assert.Equal(t, test.expectedStorageRoot, storageRoot) - //classHash := p2p2core.AdaptHash(crct.Class) - //assert.Equal(t, test.expectedClassHash, classHash, "classHash", classHash) + //nolint:nolintlint // classHash := p2p2core.AdaptHash(crct.Class) + //nolint:nolintlint // assert.Equal(t, test.expectedClassHash, classHash, "classHash", classHash) assert.Equal(t, test.expectedNonce, crct.Nonce) @@ -280,7 +280,7 @@ func TestContractRangeByOneContract(t *testing.T) { func TestContractRange_FinMsg_Received(t *testing.T) { // TODO: Fix the test so it demonstrated FinMsg is returned at the iteration end t.Skip("Fix me") - var d db.DB = pebble.NewMemTest(t) + d := pebble.NewMemTest(t) bc := blockchain.New(d, &utils.Sepolia) defer bc.Close() server := &snapServer{blockchain: bc} @@ -472,6 +472,7 @@ func TestGetClassesByHash(t *testing.T) { assert.True(t, finMsgReceived) } +//nolint:gocyclo func Test__Finding_Storage_Heavy_Contract(t *testing.T) { var d db.DB t.Skip("DB snapshot is needed for this test") @@ -496,7 +497,7 @@ func Test__Finding_Storage_Heavy_Contract(t *testing.T) { request := &spec.ContractRangeRequest{ ChunksPerProof: 100, Start: core2p2p.AdaptAddress(felt.Zero.Clone()), - End: nil, //core2p2p.AdaptAddress(test.address), + End: nil, // core2p2p.AdaptAddress(test.address), StateRoot: core2p2p.AdaptHash(stateRoot), } @@ -515,8 +516,8 @@ func Test__Finding_Storage_Heavy_Contract(t *testing.T) { for _, contract := range v.Range.State { addr := p2p2core.AdaptAddress(contract.Address) strt := p2p2core.AdaptHash(contract.Storage) - //assert.Equal(t, test.address, addr) - //assert.Equal(t, test.storageRoot, strt) + //nolint:nolintlint // assert.Equal(t, test.address, addr) + //nolint:nolintlint // assert.Equal(t, test.storageRoot, strt) if !(strt.IsZero() || addr.IsOne()) { ctso[*addr] = strt contracts++ @@ -575,7 +576,7 @@ func Test__Finding_Storage_Heavy_Contract(t *testing.T) { switch v := resT.GetResponses().(type) { case *spec.ContractStorageResponse_Storage: vl := stoCnt[*addr] - //if !ok { stoCnt[*addr] = 0 } + //nolint:nolintlint // if !ok { stoCnt[*addr] = 0 } stoCnt[*addr] = vl + len(v.Storage.KeyValue) case *spec.ContractStorageResponse_Fin: // we expect just one fin message at the iteration end diff --git a/p2p/snap_syncer.go b/p2p/snap_syncer.go index 44231742a4..f425776532 100644 --- a/p2p/snap_syncer.go +++ b/p2p/snap_syncer.go @@ -219,7 +219,6 @@ func (s *SnapSyncer) runPhase1(ctx context.Context) error { storageEg, sctx := errgroup.WithContext(ectx) for i := 0; i < storageJobWorkerCount; i++ { - i := i storageEg.Go(func() error { defer func() { if err := recover(); err != nil { @@ -265,7 +264,6 @@ func (s *SnapSyncer) runPhase1(ctx context.Context) error { }) for i := 0; i < fetchClassWorkerCount; i++ { - i := i eg.Go(func() error { err := s.runFetchClassWorker(ectx, i) if err != nil { @@ -307,9 +305,7 @@ func (s *SnapSyncer) PhraseVerify(ctx context.Context) error { "global state root", s.currentGlobalStateRoot, "contract root", contractRoot, "class root", classRoot) // all good no need for additional verification return nil - } - - if err != nil { + } else { s.log.Errorw("global state root verification failure", "err", err) } @@ -408,7 +404,7 @@ func CalculatePercentage(f *felt.Felt) uint64 { return percent.Uint64() } -//nolint:gocyclo,nolintlint +//nolint:gocyclo,funlen func (s *SnapSyncer) runClassRangeWorker(ctx context.Context) error { totalAdded := 0 completed := false @@ -479,7 +475,6 @@ func (s *SnapSyncer) runClassRangeWorker(ctx context.Context) error { for i, cls := range classes { coreClass := p2p2core.AdaptClass(cls) - i := i egrp.Go(func() error { coreClasses[i] = coreClass paths[i] = CalculateClassHash(coreClass) @@ -522,7 +517,6 @@ func (s *SnapSyncer) runClassRangeWorker(ctx context.Context) error { if !hasNext { s.log.Infow("class range completed", "totalClass", totalAdded) - completed = true return nil } @@ -535,7 +529,7 @@ func (s *SnapSyncer) runClassRangeWorker(ctx context.Context) error { return nil } -//nolint:gocyclo +//nolint:gocyclo,funlen func (s *SnapSyncer) runFetchClassWorker(ctx context.Context, workerIdx int) error { keyBatches := make([]*felt.Felt, 0) s.log.Infow("class fetch worker entering infinite loop", "worker", workerIdx) @@ -663,7 +657,7 @@ func (s *SnapSyncer) runFetchClassWorker(ctx context.Context, workerIdx int) err } } -//nolint:gocyclo +//nolint:gocyclo,funlen func (s *SnapSyncer) runContractRangeWorker(ctx context.Context) error { totalAdded := 0 startAddr := &felt.Zero @@ -779,7 +773,6 @@ func (s *SnapSyncer) runContractRangeWorker(ctx context.Context) error { if !hasNext { s.log.Infow("[hasNext] contract range completed") - completed = true return nil } @@ -825,8 +818,6 @@ func (s *SnapSyncer) runStorageRangeWorker(ctx context.Context, workerIdx int) e } } - // s.log.Infow("storage range job completes batch", "jobs", len(jobs), "worker", workerIdx, "pending", s.storageRangeJobCount) - requests := make([]*spec.StorageRangeQuery, 0) for _, job := range jobs { requests = append(requests, &spec.StorageRangeQuery{ @@ -850,7 +841,7 @@ func (s *SnapSyncer) runStorageRangeWorker(ctx context.Context, workerIdx int) e totalPath := 0 maxPerStorageSize := 0 - //s.log.Infow("storage range", + //nolint:nolintlint // s.log.Infow("storage range", // "rootDistance", s.lastBlock.Number-s.startingBlock.Number, // "root", stateRoot.String(), // "requestcount", len(requests), @@ -1120,8 +1111,6 @@ func (s *SnapSyncer) runStorageRefreshWorker(ctx context.Context) error { job = nil } } - s.log.Infow("storage refresh worker exits infinite loop") - return nil } func (s *SnapSyncer) queueClassJob(ctx context.Context, classHash *felt.Felt) error { @@ -1133,7 +1122,7 @@ func (s *SnapSyncer) queueClassJob(ctx context.Context, classHash *felt.Felt) er queued = true case <-ctx.Done(): return ctx.Err() - case <-time.After(30 * time.Second): + case <-time.After(30 * time.Second): //nolint:mnd // TODO: remove this, only use temporarily for snap sync s.log.Infow("class queue stall on class") } } @@ -1179,7 +1168,7 @@ func (s *SnapSyncer) queueStorageRefreshJob(ctx context.Context, job *storageRan queued = true case <-ctx.Done(): return ctx.Err() - case <-time.After(10 * time.Second): + case <-time.After(10 * time.Second): //nolint:mnd // TODO: remove this, only use temporarily for snap sync s.log.Infow("storage refresh queue stall") } } diff --git a/p2p/starknet/client.go b/p2p/starknet/client.go index f4c113f5a7..a103c3f2d4 100644 --- a/p2p/starknet/client.go +++ b/p2p/starknet/client.go @@ -108,32 +108,47 @@ func (c *Client) RequestBlockHeaders( } func (c *Client) RequestEvents(ctx context.Context, req *spec.EventsRequest) (iter.Seq[*spec.EventsResponse], error) { - return requestAndReceiveStream[*spec.EventsRequest, *spec.EventsResponse](ctx, c.newStream, EventsPID(), req, c.log) + return requestAndReceiveStream[*spec.EventsRequest, *spec.EventsResponse]( + ctx, c.newStream, EventsPID(), req, c.log, + ) } func (c *Client) RequestClasses(ctx context.Context, req *spec.ClassesRequest) (iter.Seq[*spec.ClassesResponse], error) { - return requestAndReceiveStream[*spec.ClassesRequest, *spec.ClassesResponse](ctx, c.newStream, ClassesPID(), req, c.log) + return requestAndReceiveStream[*spec.ClassesRequest, *spec.ClassesResponse]( + ctx, c.newStream, ClassesPID(), req, c.log, + ) } func (c *Client) RequestStateDiffs(ctx context.Context, req *spec.StateDiffsRequest) (iter.Seq[*spec.StateDiffsResponse], error) { - return requestAndReceiveStream[*spec.StateDiffsRequest, *spec.StateDiffsResponse](ctx, c.newStream, StateDiffPID(), req, c.log) + return requestAndReceiveStream[*spec.StateDiffsRequest, *spec.StateDiffsResponse]( + ctx, c.newStream, StateDiffPID(), req, c.log, + ) } func (c *Client) RequestTransactions(ctx context.Context, req *spec.TransactionsRequest) (iter.Seq[*spec.TransactionsResponse], error) { return requestAndReceiveStream[*spec.TransactionsRequest, *spec.TransactionsResponse]( - ctx, c.newStream, TransactionsPID(), req, c.log) + ctx, c.newStream, TransactionsPID(), req, c.log, + ) } func (c *Client) RequestClassRange(ctx context.Context, req *spec.ClassRangeRequest) (iter.Seq[*spec.ClassRangeResponse], error) { - return requestAndReceiveStream[*spec.ClassRangeRequest, *spec.ClassRangeResponse](ctx, c.newStream, SnapshotClassRangePID(), req, c.log) + return requestAndReceiveStream[*spec.ClassRangeRequest, *spec.ClassRangeResponse]( + ctx, c.newStream, SnapshotClassRangePID(), req, c.log, + ) } func (c *Client) RequestContractRange(ctx context.Context, req *spec.ContractRangeRequest) (iter.Seq[*spec.ContractRangeResponse], error) { - return requestAndReceiveStream[*spec.ContractRangeRequest, *spec.ContractRangeResponse](ctx, c.newStream, SnapshotContractRangePID(), req, c.log) + return requestAndReceiveStream[*spec.ContractRangeRequest, *spec.ContractRangeResponse]( + ctx, c.newStream, SnapshotContractRangePID(), req, c.log, + ) } -func (c *Client) RequestStorageRange(ctx context.Context, req *spec.ContractStorageRequest) (iter.Seq[*spec.ContractStorageResponse], error) { - return requestAndReceiveStream[*spec.ContractStorageRequest, *spec.ContractStorageResponse](ctx, c.newStream, SnapshotContractStorageRangePID(), req, c.log) +func (c *Client) RequestStorageRange( + ctx context.Context, req *spec.ContractStorageRequest, +) (iter.Seq[*spec.ContractStorageResponse], error) { + return requestAndReceiveStream[*spec.ContractStorageRequest, *spec.ContractStorageResponse]( + ctx, c.newStream, SnapshotContractStorageRangePID(), req, c.log, + ) } func (c *Client) RequestClassesByKeys(ctx context.Context, req *spec.ClassHashesRequest) (iter.Seq[*spec.ClassesResponse], error) { diff --git a/p2p/starknet/handlers.go b/p2p/starknet/handlers.go index d80de690d9..138103b292 100644 --- a/p2p/starknet/handlers.go +++ b/p2p/starknet/handlers.go @@ -132,7 +132,7 @@ func (h *Handler) StateDiffHandler(stream network.Stream) { func (h *Handler) ClassRangeHandler(stream network.Stream) { if h.snapProvider == nil { - h.log.Debugw("SnapProvider not initialized") + h.log.Debugw("SnapProvider not initialised") return } streamHandler[*spec.ClassRangeRequest](h.ctx, &h.wg, stream, h.snapProvider.GetClassRange, h.log) @@ -140,7 +140,7 @@ func (h *Handler) ClassRangeHandler(stream network.Stream) { func (h *Handler) ContractRangeHandler(stream network.Stream) { if h.snapProvider == nil { - h.log.Debugw("SnapProvider not initialized") + h.log.Debugw("SnapProvider not initialised") return } streamHandler[*spec.ContractRangeRequest](h.ctx, &h.wg, stream, h.snapProvider.GetContractRange, h.log) @@ -148,7 +148,7 @@ func (h *Handler) ContractRangeHandler(stream network.Stream) { func (h *Handler) ContractStorageHandler(stream network.Stream) { if h.snapProvider == nil { - h.log.Debugw("SnapProvider not initialized") + h.log.Debugw("SnapProvider not initialised") return } streamHandler[*spec.ContractStorageRequest](h.ctx, &h.wg, stream, h.snapProvider.GetStorageRange, h.log) @@ -156,7 +156,7 @@ func (h *Handler) ContractStorageHandler(stream network.Stream) { func (h *Handler) ClassHashesHandler(stream network.Stream) { if h.snapProvider == nil { - h.log.Debugw("SnapProvider not initialized") + h.log.Debugw("SnapProvider not initialised") return } streamHandler[*spec.ClassHashesRequest](h.ctx, &h.wg, stream, h.snapProvider.GetClasses, h.log) diff --git a/p2p/starknetdata.go b/p2p/starknetdata.go index f6e8736cff..4af4717827 100644 --- a/p2p/starknetdata.go +++ b/p2p/starknetdata.go @@ -9,8 +9,7 @@ import ( "github.com/ethereum/go-ethereum/log" ) -type MockStarkData struct { -} +type MockStarkData struct{} var _ starknetdata.StarknetData = (*MockStarkData)(nil) @@ -25,43 +24,43 @@ func (m MockStarkData) BlockLatest(ctx context.Context) (*core.Block, error) { return &core.Block{ Header: &core.Header{ - Number: uint64(66477), + Number: uint64(66477), //nolint:mnd // TODO: remove this, only use temporarily for snap sync GlobalStateRoot: root, }, }, nil } func (m MockStarkData) BlockPending(ctx context.Context) (*core.Block, error) { - //TODO implement me + // TODO implement me panic("implement me") } func (m MockStarkData) Transaction(ctx context.Context, transactionHash *felt.Felt) (core.Transaction, error) { - //TODO implement me + // TODO implement me panic("implement me") } func (m MockStarkData) Class(ctx context.Context, classHash *felt.Felt) (core.Class, error) { - //TODO implement me + // TODO implement me panic("implement me") } func (m MockStarkData) StateUpdate(ctx context.Context, blockNumber uint64) (*core.StateUpdate, error) { - //TODO implement me + // TODO implement me panic("implement me") } func (m MockStarkData) StateUpdatePending(ctx context.Context) (*core.StateUpdate, error) { - //TODO implement me + // TODO implement me panic("implement me") } func (m MockStarkData) StateUpdateWithBlock(ctx context.Context, blockNumber uint64) (*core.StateUpdate, *core.Block, error) { - //TODO implement me + // TODO implement me panic("implement me") } func (m MockStarkData) StateUpdatePendingWithBlock(ctx context.Context) (*core.StateUpdate, *core.Block, error) { - //TODO implement me + // TODO implement me panic("implement me") } diff --git a/p2p/sync.go b/p2p/sync.go index 1c11ba64e0..c0a1543251 100644 --- a/p2p/sync.go +++ b/p2p/sync.go @@ -54,7 +54,6 @@ func (s *SyncService) Client() *starknet.Client { return s.client } -//nolint:funlen func (s *SyncService) Start(ctx context.Context) { ctx, cancel := context.WithCancel(ctx) defer cancel() From 3d320d838d689c05769829310b2329ac7f42dfa1 Mon Sep 17 00:00:00 2001 From: weiihann Date: Fri, 25 Oct 2024 14:03:22 +0800 Subject: [PATCH 21/23] fix minor errors --- Makefile | 3 +-- blockchain/blockchain.go | 9 +++++---- blockchain/snap_server_interface.go | 3 --- p2p/p2p.go | 3 +-- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 00e8575c40..406be65c74 100644 --- a/Makefile +++ b/Makefile @@ -117,7 +117,7 @@ feedernode: juno-cached ./build/juno \ --network=sepolia \ --log-level=debug \ - --db-path="/Users/pnowosie/juno/snapshots/juno-sepolia" \ + --db-path=./p2p-dbs/feedernode \ --p2p \ --p2p-feeder-node \ --p2p-addr=/ip4/0.0.0.0/tcp/7777 \ @@ -136,7 +136,6 @@ node1: juno-cached --metrics-port=9091 \ --pprof \ --pprof-port=9096 \ - --p2p-sync-mode="snap" # --p2p-peers=/ip4/127.0.0.1/tcp/7778/p2p/12D3KooWDQVMmK6cQrfFcWUoFF8Ch5vYegfwiP5Do2SFC2NAXeBk \ diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index 2101f0c3e5..b1f312636a 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -90,10 +90,11 @@ func New(database db.DB, network *utils.Network) *Blockchain { } // TODO: Used only for testing though... - err := bc.seedSnapshot() - if err != nil { - fmt.Printf("Error seeding snapshot %s", err) - } + // TODO: the following is only used for snap sync, uncomment when we need it again + // err := bc.seedSnapshot() + // if err != nil { + // fmt.Printf("Error seeding snapshot %s", err) + // } return bc } diff --git a/blockchain/snap_server_interface.go b/blockchain/snap_server_interface.go index d60e74fadd..2717bdae09 100644 --- a/blockchain/snap_server_interface.go +++ b/blockchain/snap_server_interface.go @@ -178,10 +178,7 @@ func NewBlockchainCloser(bc *Blockchain, log utils.SimpleLogger) *BlockchainClos } func (b *BlockchainCloser) Run(ctx context.Context) error { - b.log.Infow("BlockchainCloser has started") - <-ctx.Done() b.bc.Close() - b.log.Infow("BlockchainCloser has stopped") return nil } diff --git a/p2p/p2p.go b/p2p/p2p.go index ec2153111b..e344ca41d6 100644 --- a/p2p/p2p.go +++ b/p2p/p2p.go @@ -49,7 +49,6 @@ type Service struct { topicsLock sync.RWMutex downloader *Downloader - synchroniser *SyncService gossipTracer *gossipTracer database db.DB @@ -414,7 +413,7 @@ func (s *Service) SetProtocolHandler(pid protocol.ID, handler func(network.Strea } func (s *Service) WithListener(l junoSync.EventListener) { - s.synchroniser.WithListener(l) + s.downloader.WithListener(l) } func (s *Service) WithGossipTracer() { From ed1c1d079d6216e1160a7dd13215e0a953a729dd Mon Sep 17 00:00:00 2001 From: weiihann Date: Fri, 25 Oct 2024 14:32:19 +0800 Subject: [PATCH 22/23] more fixes --- blockchain/blockchain.go | 9 ++- blockchain/snap_server_interface.go | 114 ++++++++++++++-------------- 2 files changed, 62 insertions(+), 61 deletions(-) diff --git a/blockchain/blockchain.go b/blockchain/blockchain.go index b1f312636a..19fc26c6c4 100644 --- a/blockchain/blockchain.go +++ b/blockchain/blockchain.go @@ -387,10 +387,11 @@ func (b *Blockchain) Store(block *core.Block, blockCommitments *core.BlockCommit return err } - err = b.seedSnapshot() - if err != nil { - return err - } + // TODO: the following is only used for snap sync, uncomment when we need it again + // err = b.seedSnapshot() + // if err != nil { + // return err + // } return nil } diff --git a/blockchain/snap_server_interface.go b/blockchain/snap_server_interface.go index 2717bdae09..ce530af9a9 100644 --- a/blockchain/snap_server_interface.go +++ b/blockchain/snap_server_interface.go @@ -3,7 +3,6 @@ package blockchain import ( "context" "errors" - "fmt" "github.com/NethermindEth/juno/core" "github.com/NethermindEth/juno/core/felt" @@ -14,6 +13,7 @@ import ( const MaxSnapshots = 128 +//nolint:unused type snapshotRecord struct { stateRoot *felt.Felt contractsRoot *felt.Felt @@ -102,62 +102,62 @@ func (b *Blockchain) GetDClasses(felts []*felt.Felt) ([]*core.DeclaredClass, err return classes, nil } -func (b *Blockchain) seedSnapshot() error { - headheader, err := b.HeadsHeader() - if err != nil { - return err - } - - stateR, srCloser, err := b.HeadState() - if err != nil { - return err - } - - defer func() { _ = srCloser() }() - - state := stateR.(*core.State) - contractsRoot, classRoot, err := state.StateAndClassRoot() - if err != nil { - return err - } - - stateRoot, err := state.Root() - if err != nil { - return err - } - - txn, closer, err := b.database.PersistedView() - if err != nil { - return err - } - - dbsnap := snapshotRecord{ - stateRoot: stateRoot, - contractsRoot: contractsRoot, - classRoot: classRoot, - blockHash: headheader.Hash, - txn: txn, - closer: closer, - } - - fmt.Printf("Snapshot %d %s %s\n", headheader.Number, headheader.GlobalStateRoot, stateRoot) - - // TODO: Reorgs - b.snapshots = append(b.snapshots, &dbsnap) - if len(b.snapshots) > MaxSnapshots { - toremove := b.snapshots[0] - err = toremove.closer() - if err != nil { - return err - } - - // TODO: I think internally, it keep the old array. - // maybe the append copy it to a new array, who knows... - b.snapshots = b.snapshots[1:] - } - - return nil -} +// func (b *Blockchain) seedSnapshot() error { +// headheader, err := b.HeadsHeader() +// if err != nil { +// return err +// } + +// stateR, srCloser, err := b.HeadState() +// if err != nil { +// return err +// } + +// defer func() { _ = srCloser() }() + +// state := stateR.(*core.State) +// contractsRoot, classRoot, err := state.StateAndClassRoot() +// if err != nil { +// return err +// } + +// stateRoot, err := state.Root() +// if err != nil { +// return err +// } + +// txn, closer, err := b.database.PersistedView() +// if err != nil { +// return err +// } + +// dbsnap := snapshotRecord{ +// stateRoot: stateRoot, +// contractsRoot: contractsRoot, +// classRoot: classRoot, +// blockHash: headheader.Hash, +// txn: txn, +// closer: closer, +// } + +// fmt.Printf("Snapshot %d %s %s\n", headheader.Number, headheader.GlobalStateRoot, stateRoot) + +// // TODO: Reorgs +// b.snapshots = append(b.snapshots, &dbsnap) +// if len(b.snapshots) > MaxSnapshots { +// toremove := b.snapshots[0] +// err = toremove.closer() +// if err != nil { +// return err +// } + +// // TODO: I think internally, it keep the old array. +// // maybe the append copy it to a new array, who knows... +// b.snapshots = b.snapshots[1:] +// } + +// return nil +// } func (b *Blockchain) Close() { for _, snapshot := range b.snapshots { From 68209b14b27641e689eceffa67567f4b074bb510 Mon Sep 17 00:00:00 2001 From: weiihann Date: Fri, 25 Oct 2024 16:00:39 +0800 Subject: [PATCH 23/23] fix lint --- p2p/snap_server.go | 2 +- utils/log.go | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/p2p/snap_server.go b/p2p/snap_server.go index 728f3d588e..d757dede5c 100644 --- a/p2p/snap_server.go +++ b/p2p/snap_server.go @@ -1,6 +1,7 @@ package p2p import ( + "iter" "math/big" "github.com/NethermindEth/juno/adapters/core2p2p" @@ -13,7 +14,6 @@ import ( "github.com/NethermindEth/juno/utils" "github.com/ethereum/go-ethereum/log" "google.golang.org/protobuf/proto" - "iter" ) type ContractRangeStreamingResult struct { diff --git a/utils/log.go b/utils/log.go index a38857facb..6f0d02bf72 100644 --- a/utils/log.go +++ b/utils/log.go @@ -121,8 +121,10 @@ func (l *ZapLogger) Tracew(msg string, keysAndValues ...interface{}) { } } -var _ Logger = (*ZapLogger)(nil) -var _ SimpleLogger = (*ZapLogger)(nil) +var ( + _ Logger = (*ZapLogger)(nil) + _ SimpleLogger = (*ZapLogger)(nil) +) func NewNopZapLogger() *ZapLogger { return &ZapLogger{zap.NewNop().Sugar()}