diff --git a/core/state/statefactory/state_factory.go b/core/state/factory/state_factory.go similarity index 98% rename from core/state/statefactory/state_factory.go rename to core/state/factory/state_factory.go index 3007517065..10c9ff189b 100644 --- a/core/state/statefactory/state_factory.go +++ b/core/state/factory/state_factory.go @@ -1,4 +1,4 @@ -package statefactory +package factory import ( "github.com/NethermindEth/juno/core" diff --git a/core/state/statefactory/state_factory_test.go b/core/state/factory/state_factory_test.go similarity index 98% rename from core/state/statefactory/state_factory_test.go rename to core/state/factory/state_factory_test.go index d6edf5ec0f..1004469460 100644 --- a/core/state/statefactory/state_factory_test.go +++ b/core/state/factory/state_factory_test.go @@ -1,4 +1,4 @@ -package statefactory +package factory import ( "testing" diff --git a/core/state/history.go b/core/state/history.go index 489e303fe4..2eb23a36e9 100644 --- a/core/state/history.go +++ b/core/state/history.go @@ -11,7 +11,7 @@ var _ core.StateReader = (*stateHistory)(nil) // StateHistory represents a snapshot of the blockchain state at a specific block number. type stateHistory struct { blockNum uint64 - state *State + state *StateReader } func NewStateHistory(blockNum uint64, stateRoot *felt.Felt, db *StateDB) (stateHistory, error) { @@ -28,33 +28,33 @@ func NewStateHistory(blockNum uint64, stateRoot *felt.Felt, db *StateDB) (stateH func (s *stateHistory) ContractClassHash(addr *felt.Felt) (felt.Felt, error) { if err := s.checkDeployed(addr); err != nil { - return felt.Felt{}, err + return felt.Zero, err } ret, err := s.state.ContractClassHashAt(addr, s.blockNum) if err != nil { - return felt.Felt{}, err + return felt.Zero, err } return ret, nil } func (s *stateHistory) ContractNonce(addr *felt.Felt) (felt.Felt, error) { if err := s.checkDeployed(addr); err != nil { - return felt.Felt{}, err + return felt.Zero, err } ret, err := s.state.ContractNonceAt(addr, s.blockNum) if err != nil { - return felt.Felt{}, err + return felt.Zero, err } return ret, nil } func (s *stateHistory) ContractStorage(addr, key *felt.Felt) (felt.Felt, error) { if err := s.checkDeployed(addr); err != nil { - return felt.Felt{}, err + return felt.Zero, err } ret, err := s.state.ContractStorageAt(addr, key, s.blockNum) if err != nil { - return felt.Felt{}, err + return felt.Zero, err } return ret, nil } diff --git a/core/state/object.go b/core/state/object.go index 6e7a1f0d43..5a0056a621 100644 --- a/core/state/object.go +++ b/core/state/object.go @@ -6,8 +6,6 @@ import ( "github.com/NethermindEth/juno/core/felt" "github.com/NethermindEth/juno/core/trie2" "github.com/NethermindEth/juno/core/trie2/trienode" - "github.com/NethermindEth/juno/core/trie2/trieutils" - "github.com/NethermindEth/juno/db" "golang.org/x/exp/maps" ) @@ -40,40 +38,15 @@ func (s *stateObject) setNonce(nonce *felt.Felt) { s.contract.Nonce = *nonce } -func (s *stateObject) getStorage(key *felt.Felt) (felt.Felt, error) { - if value, ok := s.dirtyStorage[*key]; ok { - return *value, nil - } - - tr, err := s.getStorageTrie() - if err != nil { - return felt.Zero, err - } - - path := tr.FeltToPath(key) - v, err := trieutils.GetNodeByPath( - s.state.db.disk, - db.ContractTrieStorage, - (*felt.Address)(&s.addr), - &path, - true, - ) - if err != nil { - return felt.Zero, err - } - - var val felt.Felt - val.SetBytes(v) - - return val, nil -} - func (s *stateObject) getStorageTrie() (*trie2.Trie, error) { if s.storageTrie != nil { return s.storageTrie, nil } - storageTrie, err := s.state.db.ContractStorageTrie(&s.state.initRoot, &s.addr) + storageTrie, err := s.state.db.ContractStorageTrie( + &s.state.initRoot, + &s.addr, + ) if err != nil { return nil, err } @@ -82,17 +55,20 @@ func (s *stateObject) getStorageTrie() (*trie2.Trie, error) { return storageTrie, nil } -func (s *stateObject) getStorageRoot() felt.Felt { +func (s *stateObject) getStorageRoot() (felt.Felt, error) { // If the storage trie is loaded, it may be modified somewhere already. // Return the hash of the trie and update the contract's storage root. if s.storageTrie != nil { - root, _ := s.storageTrie.Hash() + root, err := s.storageTrie.Hash() + if err != nil { + return felt.Zero, err + } s.contract.StorageRoot = root - return root + return root, nil } // Otherwise, return the storage root from the contract. - return s.contract.StorageRoot + return s.contract.StorageRoot, nil } func (s *stateObject) commit() (*trienode.NodeSet, error) { diff --git a/core/state/state.go b/core/state/state.go index 90ee9d670d..2d45b59686 100644 --- a/core/state/state.go +++ b/core/state/state.go @@ -1,11 +1,9 @@ package state import ( - "encoding/binary" "errors" "fmt" "maps" - "math" "runtime" "slices" "sync" @@ -13,7 +11,6 @@ import ( "github.com/NethermindEth/juno/core" "github.com/NethermindEth/juno/core/crypto" "github.com/NethermindEth/juno/core/felt" - "github.com/NethermindEth/juno/core/trie2" "github.com/NethermindEth/juno/core/trie2/trienode" "github.com/NethermindEth/juno/core/trie2/trieutils" "github.com/NethermindEth/juno/db" @@ -26,8 +23,8 @@ const ( ) var ( - stateVersion0 = new(felt.Felt).SetBytes([]byte(`STARKNET_STATE_V0`)) - leafVersion0 = new(felt.Felt).SetBytes([]byte(`CONTRACT_CLASS_LEAF_V0`)) + stateVersion0 = felt.NewFromBytes[felt.Felt]([]byte(`STARKNET_STATE_V0`)) + leafVersion0 = felt.NewFromBytes[felt.Felt]([]byte(`CONTRACT_CLASS_LEAF_V0`)) noClassContractsClassHash = felt.Zero noClassContracts = map[felt.Felt]struct{}{ felt.FromUint64[felt.Felt](systemContract1Addr): {}, @@ -35,17 +32,17 @@ var ( } ) -var _ core.State = &State{} +var ( + _ core.StateReader = &StateReader{} + _ core.State = &State{} +) +// State extends StateReader with mutation operations. type State struct { - initRoot felt.Felt - db *StateDB - contractTrie *trie2.Trie - classTrie *trie2.Trie + StateReader stateObjects map[felt.Felt]*stateObject - - batch db.Batch + batch db.Batch } // New creates a writable state at the given root. The caller must provide a non-nil batch. @@ -55,152 +52,20 @@ func New(stateRoot *felt.Felt, db *StateDB, batch db.Batch) (*State, error) { if batch == nil { return nil, errors.New("cannot create state, nil Batch received") } - contractTrie, err := db.ContractTrie(stateRoot) - if err != nil { - return nil, err - } - - classTrie, err := db.ClassTrie(stateRoot) + reader, err := NewStateReader(stateRoot, db) if err != nil { return nil, err } return &State{ - initRoot: *stateRoot, - db: db, - contractTrie: contractTrie, - classTrie: classTrie, + StateReader: *reader, stateObjects: make(map[felt.Felt]*stateObject), batch: batch, }, nil } -// NewStateReader creates a read-only view of the state at the given root. -// Should be used for read operations that don't require state mutations -func NewStateReader(stateRoot *felt.Felt, db *StateDB) (*State, error) { - contractTrie, err := db.ContractTrie(stateRoot) - if err != nil { - return nil, err - } - - classTrie, err := db.ClassTrie(stateRoot) - if err != nil { - return nil, err - } - - return &State{ - initRoot: *stateRoot, - db: db, - contractTrie: contractTrie, - classTrie: classTrie, - stateObjects: make(map[felt.Felt]*stateObject), - }, nil -} - -func (s *State) ContractClassHash(addr *felt.Felt) (felt.Felt, error) { - contract, err := GetContract(s.db.disk, addr) - if err != nil { - return felt.Felt{}, err - } - return contract.ClassHash, nil -} - -func (s *State) ContractNonce(addr *felt.Felt) (felt.Felt, error) { - contract, err := GetContract(s.db.disk, addr) - if err != nil { - return felt.Felt{}, err - } - return contract.Nonce, nil -} - -func (s *State) ContractStorage(addr, key *felt.Felt) (felt.Felt, error) { - obj, err := s.getStateObject(addr) - if err != nil { - return felt.Felt{}, err - } - - ret, err := obj.getStorage(key) - if err != nil { - return felt.Felt{}, err - } - - return ret, nil -} - -// ContractStorageLastUpdatedBlock returns the most recent block number at which a given storage -// slot key of a given contract was last updated. -func (s *State) ContractStorageLastUpdatedBlock( - addr *felt.Address, - key *felt.Felt, -) (uint64, error) { - prefix := db.ContractStorageHistoryKey((*felt.Felt)(addr), key) - return s.lastUpdatedBlockNumber(prefix, math.MaxUint64) -} - -func (s *State) ContractDeployedAt(addr *felt.Felt, blockNum uint64) (bool, error) { - contract, err := GetContract(s.db.disk, addr) - if err != nil { - if errors.Is(err, ErrContractNotDeployed) { - return false, nil - } - return false, err - } - - return contract.DeployedHeight <= blockNum, nil -} - -func (s *State) Class(classHash *felt.Felt) (*core.DeclaredClassDefinition, error) { - return GetClass(s.db.disk, classHash) -} - -func (s *State) ClassTrie() (core.Trie, error) { - return s.classTrie, nil -} - -func (s *State) ContractTrie() (core.Trie, error) { - return s.contractTrie, nil -} - -func (s *State) ContractStorageTrie(addr *felt.Felt) (core.Trie, error) { - return s.db.ContractStorageTrie(&s.initRoot, addr) -} - -func (s *State) CompiledClassHash( - classHash *felt.SierraClassHash, -) (felt.CasmClassHash, error) { - metadata, err := core.GetClassCasmHashMetadata(s.db.disk, classHash) - if err != nil { - return felt.CasmClassHash{}, err - } - return metadata.CasmHash(), nil -} - -func (s *State) CompiledClassHashV2( - classHash *felt.SierraClassHash, -) (felt.CasmClassHash, error) { - metadata, err := core.GetClassCasmHashMetadata(s.db.disk, classHash) - if err != nil { - return felt.CasmClassHash{}, err - } - return metadata.CasmHashV2(), nil -} - -// Returns the state commitment -func (s *State) Commitment(protocolVersion string) (felt.Felt, error) { - contractRoot, err := s.contractTrie.Hash() - if err != nil { - return felt.Felt{}, err - } - classRoot, err := s.classTrie.Hash() - if err != nil { - return felt.Felt{}, err - } - return stateCommitment(&contractRoot, &classRoot, protocolVersion), nil -} - // Applies a state update to a given state. If any error is encountered, state is not updated. // After a state update is applied, the root of the state must match the given new root in the state update. -// TODO(weiihann): deal with flush atomicity func (s *State) Update( header *core.Header, update *core.StateUpdate, @@ -357,65 +222,11 @@ func (s *State) Revert(header *core.Header, update *core.StateUpdate) error { if !newComm.Equal(update.OldRoot) { return fmt.Errorf("state commitment mismatch: %v (expected) != %v (actual)", update.OldRoot, &newComm) } - if s.batch != nil { - if err := s.flush(blockNum, &stateUpdate, dirtyClasses, false); err != nil { - return err - } - if err := s.deleteHistory(blockNum, update.StateDiff); err != nil { - return err - } - } - return nil -} - -func (s *State) GetReverseStateDiff(blockNum uint64, diff *core.StateDiff) (core.StateDiff, error) { - reverse := core.StateDiff{ - StorageDiffs: make(map[felt.Felt]map[felt.Felt]*felt.Felt, len(diff.StorageDiffs)), - Nonces: make(map[felt.Felt]*felt.Felt, len(diff.Nonces)), - ReplacedClasses: make(map[felt.Felt]*felt.Felt, len(diff.ReplacedClasses)), - } - - for addr, stDiffs := range diff.StorageDiffs { - reverse.StorageDiffs[addr] = make(map[felt.Felt]*felt.Felt, len(stDiffs)) - for key := range stDiffs { - value := felt.Zero - if blockNum > 0 { - oldValue, err := s.ContractStorageAt(&addr, &key, blockNum-1) - if err != nil { - return core.StateDiff{}, err - } - value = oldValue - } - reverse.StorageDiffs[addr][key] = &value - } - } - - for addr := range diff.Nonces { - oldNonce := felt.Zero - if blockNum > 0 { - var err error - oldNonce, err = s.ContractNonceAt(&addr, blockNum-1) - if err != nil { - return core.StateDiff{}, err - } - } - reverse.Nonces[addr] = &oldNonce - } - - for addr := range diff.ReplacedClasses { - oldCh := felt.Zero - if blockNum > 0 { - var err error - oldCh, err = s.ContractClassHashAt(&addr, blockNum-1) - if err != nil { - return core.StateDiff{}, err - } - } - reverse.ReplacedClasses[addr] = &oldCh + if err := s.flush(blockNum, &stateUpdate, dirtyClasses, false); err != nil { + return err } - - return reverse, nil + return s.deleteHistory(blockNum, update.StateDiff) } //nolint:gocyclo @@ -444,13 +255,12 @@ func (s *State) commit(protocolVersion string) (felt.Felt, stateUpdate, error) { for i, addr := range keys { obj := s.stateObjects[addr] - - p.Go(func() error { + if obj == nil { // Object is marked as delete - if obj == nil { - return nil - } + continue + } + p.Go(func() error { nodes, err := obj.commit() if err != nil { return err @@ -480,7 +290,10 @@ func (s *State) commit(protocolVersion string) (felt.Felt, stateUpdate, error) { for nAddr := range noClassContracts { if addr.Equal(&nAddr) { obj := s.stateObjects[addr] - root := obj.getStorageRoot() + root, err := obj.getStorageRoot() + if err != nil { + return felt.Zero, emptyStateUpdate, err + } if root.IsZero() { if err := s.contractTrie.Update(&nAddr, &felt.Zero); err != nil { @@ -715,107 +528,6 @@ func (s *State) getStateObject(addr *felt.Felt) (*stateObject, error) { return obj, nil } -// Returns the storage value of a contract at a given storage key at a given block number. -func (s *State) ContractStorageAt(addr, key *felt.Felt, blockNum uint64) (felt.Felt, error) { - prefix := db.ContractStorageHistoryKey(addr, key) - return s.getHistoricalValue(prefix, blockNum) -} - -// Returns the block number at which a given storage slot key of a given contract was -// last updated, up to and including the given block number. -func (s *State) ContractStorageLastUpdatedAt( - addr *felt.Address, - key *felt.Felt, - blockNum uint64, -) (uint64, error) { - prefix := db.ContractStorageHistoryKey((*felt.Felt)(addr), key) - return s.lastUpdatedBlockNumber(prefix, blockNum) -} - -// Returns the nonce of a contract at a given block number. -func (s *State) ContractNonceAt(addr *felt.Felt, blockNum uint64) (felt.Felt, error) { - prefix := db.ContractNonceHistoryKey(addr) - return s.getHistoricalValue(prefix, blockNum) -} - -// Returns the class hash of a contract at a given block number. -func (s *State) ContractClassHashAt(addr *felt.Felt, blockNum uint64) (felt.Felt, error) { - prefix := db.ContractClassHashHistoryKey(addr) - return s.getHistoricalValue(prefix, blockNum) -} - -func (s *State) CompiledClassHashAt( - classHash *felt.SierraClassHash, - blockNumber uint64, -) (felt.CasmClassHash, error) { - metadata, err := core.GetClassCasmHashMetadata(s.db.disk, classHash) - if err != nil { - return felt.CasmClassHash{}, err - } - return metadata.CasmHashAt(blockNumber) -} - -func (s *State) getHistoricalValue(prefix []byte, blockNum uint64) (felt.Felt, error) { - var ret felt.Felt - - err := s.valueAt(prefix, blockNum, func(val []byte) error { - ret.SetBytes(val) - return nil - }) - if err != nil { - if errors.Is(err, ErrNoHistoryValue) { - return felt.Zero, nil - } - return felt.Zero, err - } - - return ret, nil -} - -// Returns the value at the given block number. -// First, it attempts to seek for the key at the given block number. -// If the key exists, it means that the key was modified at the given block number. -// Otherwise, it moves the iterator one step back. -// If a key-value pair exists, we have found the value. -func (s *State) valueAt(prefix []byte, blockNum uint64, cb func(val []byte) error) error { - it, err := s.db.disk.NewIterator(prefix, true) - if err != nil { - return err - } - defer it.Close() - - seekKey := binary.BigEndian.AppendUint64(prefix, blockNum) - if !it.Seek(seekKey) { - return ErrNoHistoryValue - } - - key := it.Key() - keyBlockNum := binary.BigEndian.Uint64(key[len(prefix):]) - if keyBlockNum == blockNum { - // Found the value - val, err := it.Value() - if err != nil { - return err - } - - return cb(val) - } - - // Otherwise, move the iterator backwards - if !it.Prev() { - // Moving iterator backwards is invalid, this means we were already at the first key - // No values will be found beyond the first key - return ErrNoHistoryValue - } - - val, err := it.Value() - if err != nil { - return err - } - - return cb(val) -} - func (s *State) deleteHistory(blockNum uint64, diff *core.StateDiff) error { for addr, storage := range diff.StorageDiffs { for key := range storage { @@ -864,51 +576,3 @@ func (s *State) compareContracts(a, b felt.Felt) int { return len(contractB.dirtyStorage) - len(contractA.dirtyStorage) } - -// Calculate the commitment of the state. -// Since v0.14.0, the Poseidon hash is always applied even when classRoot is zero. -func stateCommitment(contractRoot, classRoot *felt.Felt, protocolVersion string) felt.Felt { - if classRoot.IsZero() && contractRoot.IsZero() { - return felt.Zero - } - - ver, _ := core.ParseBlockVersion(protocolVersion) - if classRoot.IsZero() && ver.LessThan(core.Ver0_14_0) { - return *contractRoot - } - - return crypto.PoseidonArray(stateVersion0, contractRoot, classRoot) -} - -// lastUpdatedBlockNumber finds the most recent block number (up to upToBlock) recorded in -// a history bucket for the given key prefix. All history buckets ([ContractStorageHistory], -// [ContractNonceHistory], and [ContractClassHashHistory]) share the same key layout: -// prefix + uint64 block number in big-endian. -func (s *State) lastUpdatedBlockNumber( - historyKeyPrefix []byte, - upToBlock uint64, -) (uint64, error) { - it, err := s.db.disk.NewIterator(historyKeyPrefix, true) - if err != nil { - return 0, err - } - defer it.Close() - - seekKey := binary.BigEndian.AppendUint64(historyKeyPrefix, upToBlock) - - if it.Seek(seekKey) { - seekedKey := it.Key() - seekedBlock := binary.BigEndian.Uint64(seekedKey[len(historyKeyPrefix):]) - if seekedBlock == upToBlock { - return upToBlock, nil - } - } - - if !it.Prev() { - return 0, nil - } - - foundKey := it.Key() - blockNum := binary.BigEndian.Uint64(foundKey[len(historyKeyPrefix):]) - return blockNum, nil -} diff --git a/core/state/state_reader.go b/core/state/state_reader.go new file mode 100644 index 0000000000..c4c80f2252 --- /dev/null +++ b/core/state/state_reader.go @@ -0,0 +1,351 @@ +package state + +import ( + "encoding/binary" + "errors" + "math" + + "github.com/NethermindEth/juno/core" + "github.com/NethermindEth/juno/core/crypto" + "github.com/NethermindEth/juno/core/felt" + "github.com/NethermindEth/juno/core/trie2" + "github.com/NethermindEth/juno/core/trie2/trieutils" + "github.com/NethermindEth/juno/db" +) + +// StateReader is a read-only view of the state at a given root. +type StateReader struct { + initRoot felt.Felt + db *StateDB + contractTrie *trie2.Trie + classTrie *trie2.Trie +} + +// NewStateReader creates a read-only view of the state at the given root. +// Should be used for read operations that don't require state mutations. +func NewStateReader(stateRoot *felt.Felt, db *StateDB) (*StateReader, error) { + contractTrie, err := db.ContractTrie(stateRoot) + if err != nil { + return nil, err + } + + classTrie, err := db.ClassTrie(stateRoot) + if err != nil { + return nil, err + } + + return &StateReader{ + initRoot: *stateRoot, + db: db, + contractTrie: contractTrie, + classTrie: classTrie, + }, nil +} + +func (s *StateReader) ContractClassHash(addr *felt.Felt) (felt.Felt, error) { + contract, err := GetContract(s.db.disk, addr) + if err != nil { + return felt.Felt{}, err + } + return contract.ClassHash, nil +} + +func (s *StateReader) ContractNonce(addr *felt.Felt) (felt.Felt, error) { + contract, err := GetContract(s.db.disk, addr) + if err != nil { + return felt.Felt{}, err + } + return contract.Nonce, nil +} + +// ContractStorage reads a storage slot directly from the trie at this reader's +// root. Missing slots read as felt.Zero. +func (s *StateReader) ContractStorage(addr, key *felt.Felt) (felt.Felt, error) { + path := trieutils.FeltToPath(key, ContractStorageTrieHeight) + v, err := trieutils.GetNodeByPath( + s.db.disk, + db.ContractTrieStorage, + (*felt.Address)(addr), + &path, + true, + ) + if err != nil { + if errors.Is(err, db.ErrKeyNotFound) { + return felt.Zero, nil + } + return felt.Zero, err + } + + return felt.FromBytes[felt.Felt](v), nil +} + +// ContractStorageLastUpdatedBlock returns the most recent block number at which a given storage +// slot key of a given contract was last updated. +func (s *StateReader) ContractStorageLastUpdatedBlock( + addr *felt.Address, + key *felt.Felt, +) (uint64, error) { + prefix := db.ContractStorageHistoryKey((*felt.Felt)(addr), key) + return s.lastUpdatedBlockNumber(prefix, math.MaxUint64) +} + +func (s *StateReader) ContractDeployedAt(addr *felt.Felt, blockNum uint64) (bool, error) { + contract, err := GetContract(s.db.disk, addr) + if err != nil { + if errors.Is(err, ErrContractNotDeployed) { + return false, nil + } + return false, err + } + + return contract.DeployedHeight <= blockNum, nil +} + +func (s *StateReader) Class(classHash *felt.Felt) (*core.DeclaredClassDefinition, error) { + return GetClass(s.db.disk, classHash) +} + +func (s *StateReader) ClassTrie() (core.Trie, error) { + return s.classTrie, nil +} + +func (s *StateReader) ContractTrie() (core.Trie, error) { + return s.contractTrie, nil +} + +func (s *StateReader) ContractStorageTrie(addr *felt.Felt) (core.Trie, error) { + return s.db.ContractStorageTrie(&s.initRoot, addr) +} + +func (s *StateReader) CompiledClassHash( + classHash *felt.SierraClassHash, +) (felt.CasmClassHash, error) { + metadata, err := core.GetClassCasmHashMetadata(s.db.disk, classHash) + if err != nil { + return felt.CasmClassHash{}, err + } + return metadata.CasmHash(), nil +} + +func (s *StateReader) CompiledClassHashV2( + classHash *felt.SierraClassHash, +) (felt.CasmClassHash, error) { + metadata, err := core.GetClassCasmHashMetadata(s.db.disk, classHash) + if err != nil { + return felt.CasmClassHash{}, err + } + return metadata.CasmHashV2(), nil +} + +func (s *StateReader) Commitment(protocolVersion string) (felt.Felt, error) { + contractRoot, err := s.contractTrie.Hash() + if err != nil { + return felt.Felt{}, err + } + classRoot, err := s.classTrie.Hash() + if err != nil { + return felt.Felt{}, err + } + return stateCommitment(&contractRoot, &classRoot, protocolVersion), nil +} + +func (s *StateReader) GetReverseStateDiff( + blockNum uint64, + diff *core.StateDiff, +) (core.StateDiff, error) { + reverse := core.StateDiff{ + StorageDiffs: make(map[felt.Felt]map[felt.Felt]*felt.Felt, len(diff.StorageDiffs)), + Nonces: make(map[felt.Felt]*felt.Felt, len(diff.Nonces)), + ReplacedClasses: make(map[felt.Felt]*felt.Felt, len(diff.ReplacedClasses)), + } + + for addr, stDiffs := range diff.StorageDiffs { + reverse.StorageDiffs[addr] = make(map[felt.Felt]*felt.Felt, len(stDiffs)) + for key := range stDiffs { + value := felt.Zero + if blockNum > 0 { + oldValue, err := s.ContractStorageAt(&addr, &key, blockNum-1) + if err != nil { + return core.StateDiff{}, err + } + value = oldValue + } + reverse.StorageDiffs[addr][key] = &value + } + } + + for addr := range diff.Nonces { + oldNonce := felt.Zero + if blockNum > 0 { + var err error + oldNonce, err = s.ContractNonceAt(&addr, blockNum-1) + if err != nil { + return core.StateDiff{}, err + } + } + reverse.Nonces[addr] = &oldNonce + } + + for addr := range diff.ReplacedClasses { + oldCh := felt.Zero + if blockNum > 0 { + var err error + oldCh, err = s.ContractClassHashAt(&addr, blockNum-1) + if err != nil { + return core.StateDiff{}, err + } + } + reverse.ReplacedClasses[addr] = &oldCh + } + + return reverse, nil +} + +// Returns the storage value of a contract at a given storage key at a given block number. +func (s *StateReader) ContractStorageAt(addr, key *felt.Felt, blockNum uint64) (felt.Felt, error) { + prefix := db.ContractStorageHistoryKey(addr, key) + return s.getHistoricalValue(prefix, blockNum) +} + +// Returns the block number at which a given storage slot key of a given contract was +// last updated, up to and including the given block number. +func (s *StateReader) ContractStorageLastUpdatedAt( + addr *felt.Address, + key *felt.Felt, + blockNum uint64, +) (uint64, error) { + prefix := db.ContractStorageHistoryKey((*felt.Felt)(addr), key) + return s.lastUpdatedBlockNumber(prefix, blockNum) +} + +// Returns the nonce of a contract at a given block number. +func (s *StateReader) ContractNonceAt(addr *felt.Felt, blockNum uint64) (felt.Felt, error) { + prefix := db.ContractNonceHistoryKey(addr) + return s.getHistoricalValue(prefix, blockNum) +} + +// Returns the class hash of a contract at a given block number. +func (s *StateReader) ContractClassHashAt(addr *felt.Felt, blockNum uint64) (felt.Felt, error) { + prefix := db.ContractClassHashHistoryKey(addr) + return s.getHistoricalValue(prefix, blockNum) +} + +func (s *StateReader) CompiledClassHashAt( + classHash *felt.SierraClassHash, + blockNumber uint64, +) (felt.CasmClassHash, error) { + metadata, err := core.GetClassCasmHashMetadata(s.db.disk, classHash) + if err != nil { + return felt.CasmClassHash{}, err + } + return metadata.CasmHashAt(blockNumber) +} + +func (s *StateReader) getHistoricalValue(prefix []byte, blockNum uint64) (felt.Felt, error) { + var ret felt.Felt + + err := s.valueAt(prefix, blockNum, func(val []byte) error { + ret.SetBytes(val) + return nil + }) + if err != nil { + if errors.Is(err, ErrNoHistoryValue) { + return felt.Zero, nil + } + return felt.Zero, err + } + + return ret, nil +} + +// Returns the value at the given block number. +// First, it attempts to seek for the key at the given block number. +// If the key exists, it means that the key was modified at the given block number. +// Otherwise, it moves the iterator one step back. +// If a key-value pair exists, we have found the value. +func (s *StateReader) valueAt(prefix []byte, blockNum uint64, cb func(val []byte) error) error { + it, err := s.db.disk.NewIterator(prefix, true) + if err != nil { + return err + } + defer it.Close() + + seekKey := binary.BigEndian.AppendUint64(prefix, blockNum) + if !it.Seek(seekKey) { + return ErrNoHistoryValue + } + + key := it.Key() + keyBlockNum := binary.BigEndian.Uint64(key[len(prefix):]) + if keyBlockNum == blockNum { + // Found the value + val, err := it.Value() + if err != nil { + return err + } + + return cb(val) + } + + // Otherwise, move the iterator backwards + if !it.Prev() { + // Moving iterator backwards is invalid, this means we were already at the first key + // No values will be found beyond the first key + return ErrNoHistoryValue + } + + val, err := it.Value() + if err != nil { + return err + } + + return cb(val) +} + +// lastUpdatedBlockNumber finds the most recent block number (up to upToBlock) recorded in +// a history bucket for the given key prefix. All history buckets ([ContractStorageHistory], +// [ContractNonceHistory], and [ContractClassHashHistory]) share the same key layout: +// prefix + uint64 block number in big-endian. +func (s *StateReader) lastUpdatedBlockNumber( + historyKeyPrefix []byte, + upToBlock uint64, +) (uint64, error) { + it, err := s.db.disk.NewIterator(historyKeyPrefix, true) + if err != nil { + return 0, err + } + defer it.Close() + + seekKey := binary.BigEndian.AppendUint64(historyKeyPrefix, upToBlock) + + if it.Seek(seekKey) { + seekedKey := it.Key() + seekedBlock := binary.BigEndian.Uint64(seekedKey[len(historyKeyPrefix):]) + if seekedBlock == upToBlock { + return upToBlock, nil + } + } + + if !it.Prev() { + return 0, nil + } + + foundKey := it.Key() + blockNum := binary.BigEndian.Uint64(foundKey[len(historyKeyPrefix):]) + return blockNum, nil +} + +// Calculate the commitment of the state. +// Since v0.14.0, the Poseidon hash is always applied even when classRoot is zero. +func stateCommitment(contractRoot, classRoot *felt.Felt, protocolVersion string) felt.Felt { + if classRoot.IsZero() && contractRoot.IsZero() { + return felt.Zero + } + + ver, _ := core.ParseBlockVersion(protocolVersion) + if classRoot.IsZero() && ver.LessThan(core.Ver0_14_0) { + return *contractRoot + } + + return crypto.PoseidonArray(stateVersion0, contractRoot, classRoot) +} diff --git a/core/state/state_reader_test.go b/core/state/state_reader_test.go new file mode 100644 index 0000000000..00f6741124 --- /dev/null +++ b/core/state/state_reader_test.go @@ -0,0 +1,548 @@ +package state + +import ( + "maps" + "testing" + + "github.com/NethermindEth/juno/clients/feeder" + "github.com/NethermindEth/juno/core" + "github.com/NethermindEth/juno/core/felt" + adaptfeeder "github.com/NethermindEth/juno/starknetdata/feeder" + "github.com/NethermindEth/juno/utils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewStateReader(t *testing.T) { + t.Run("returns read-only state at root", func(t *testing.T) { + stateDB := newTestStateDB() + st, err := NewStateReader(&felt.Zero, stateDB) + require.NoError(t, err) + assert.NotNil(t, st) + }) + + t.Run("state commitment readable", func(t *testing.T) { + stateDB := newTestStateDB() + st, err := NewStateReader(&felt.Zero, stateDB) + require.NoError(t, err) + + _, err = st.Commitment("") + require.NoError(t, err) + }) +} + +func TestContractClassHash(t *testing.T) { + stateUpdates := getStateUpdates(t) + stateUpdates = stateUpdates[:2] + + su0 := stateUpdates[0] + su1 := stateUpdates[1] + + stateDB := setupState(t, stateUpdates, 2) + state, err := NewStateReader(su1.NewRoot, stateDB) + require.NoError(t, err) + + allDeployedContracts := make(map[felt.Felt]*felt.Felt) + maps.Copy(allDeployedContracts, su0.StateDiff.DeployedContracts) + maps.Copy(allDeployedContracts, su1.StateDiff.DeployedContracts) + + for addr, expected := range allDeployedContracts { + gotClassHash, err := state.ContractClassHash(&addr) + require.NoError(t, err) + assert.Equal(t, *expected, gotClassHash) + } +} + +func TestNonce(t *testing.T) { + addr := felt.UnsafeFromString[felt.Felt]( + "0x20cfa74ee3564b4cd5435cdace0f9c4d43b939620e4a0bb5076105df0a626c6", + ) + root := felt.NewUnsafeFromString[felt.Felt]( + "0x4bdef7bf8b81a868aeab4b48ef952415fe105ab479e2f7bc671c92173542368", + ) + + su0 := &core.StateUpdate{ + OldRoot: &felt.Zero, + NewRoot: root, + StateDiff: &core.StateDiff{ + DeployedContracts: map[felt.Felt]*felt.Felt{ + addr: felt.NewUnsafeFromString[felt.Felt]( + "0x10455c752b86932ce552f2b0fe81a880746649b9aee7e0d842bf3f52378f9f8", + ), + }, + }, + } + + t.Run("newly deployed contract has zero nonce", func(t *testing.T) { + stateDB := setupState(t, nil, 0) + batch := stateDB.disk.NewBatch() + state, err := New(&felt.Zero, stateDB, batch) + require.NoError(t, err) + require.NoError(t, state.Update(&core.Header{Number: block0}, su0, nil, false)) + require.NoError(t, batch.Write()) + + reader, err := NewStateReader(su0.NewRoot, stateDB) + require.NoError(t, err) + gotNonce, err := reader.ContractNonce(&addr) + require.NoError(t, err) + assert.Equal(t, felt.Zero, gotNonce) + }) + + t.Run("update contract nonce", func(t *testing.T) { + stateDB := setupState(t, nil, 0) + batch := stateDB.disk.NewBatch() + state, err := New(&felt.Zero, stateDB, batch) + require.NoError(t, err) + require.NoError(t, state.Update(&core.Header{Number: block0}, su0, nil, false)) + require.NoError(t, batch.Write()) + + su1 := &core.StateUpdate{ + NewRoot: felt.NewUnsafeFromString[felt.Felt]( + "0x6210642ffd49f64617fc9e5c0bbe53a6a92769e2996eb312a42d2bdb7f2afc1", + ), + OldRoot: root, + StateDiff: &core.StateDiff{ + Nonces: map[felt.Felt]*felt.Felt{addr: &felt.One}, + }, + } + + batch1 := stateDB.disk.NewBatch() + state1, err := New(su1.OldRoot, stateDB, batch1) + require.NoError(t, err) + require.NoError(t, state1.Update(&core.Header{Number: block1}, su1, nil, false)) + require.NoError(t, batch1.Write()) + + reader, err := NewStateReader(su1.NewRoot, stateDB) + require.NoError(t, err) + gotNonce, err := reader.ContractNonce(&addr) + require.NoError(t, err) + assert.Equal(t, felt.One, gotNonce) + }) +} + +func TestClass(t *testing.T) { + stateDB := setupState(t, nil, 0) + + client := feeder.NewTestClient(t, &utils.Integration) + gw := adaptfeeder.New(client) + + deprecatedCairoHash := felt.NewUnsafeFromString[felt.Felt]( + "0x4631b6b3fa31e140524b7d21ba784cea223e618bffe60b5bbdca44a8b45be04", + ) + deprecatedCairoClass, err := gw.Class(t.Context(), deprecatedCairoHash) + require.NoError(t, err) + sierraHash := felt.NewUnsafeFromString[felt.Felt]( + "0x1cd2edfb485241c4403254d550de0a097fa76743cd30696f714a491a454bad5", + ) + sierraClass, err := gw.Class(t.Context(), sierraHash) + require.NoError(t, err) + + batch := stateDB.disk.NewBatch() + state, err := New(&felt.Zero, stateDB, batch) + require.NoError(t, err) + + su0, err := gw.StateUpdate(t.Context(), 0) + require.NoError(t, err) + require.NoError(t, state.Update(&core.Header{Number: 0}, su0, map[felt.Felt]core.ClassDefinition{ + *deprecatedCairoHash: deprecatedCairoClass, + *sierraHash: sierraClass, + }, false)) + require.NoError(t, batch.Write()) + + reader, err := NewStateReader(su0.NewRoot, stateDB) + require.NoError(t, err) + + gotSierraClass, err := reader.Class(sierraHash) + require.NoError(t, err) + assert.Zero(t, gotSierraClass.At) + assert.Equal(t, sierraClass, gotSierraClass.Class) + + gotDeprecatedCairoClass, err := reader.Class(deprecatedCairoHash) + require.NoError(t, err) + assert.Zero(t, gotDeprecatedCairoClass.At) + assert.Equal(t, deprecatedCairoClass, gotDeprecatedCairoClass.Class) +} + +func TestStateTries(t *testing.T) { + stateUpdates := getStateUpdates(t) + stateDB := setupState(t, stateUpdates, 1) + root := *stateUpdates[0].NewRoot + + state, err := NewStateReader(&root, stateDB) + require.NoError(t, err) + + classTrie, err := state.ClassTrie() + require.NoError(t, err) + require.NotNil(t, classTrie) + + contractTrie, err := state.ContractTrie() + require.NoError(t, err) + require.NotNil(t, contractTrie) + + // ContractStorageTrie for a deployed contract (first deployed in mainnet block 1) + addr := &su1FirstDeployedAddress + storageTrie, err := state.ContractStorageTrie(addr) + require.NoError(t, err) + require.NotNil(t, storageTrie) +} + +func TestContractDeployedAt(t *testing.T) { + stateUpdates := getStateUpdates(t) + stateDB := setupState(t, stateUpdates, 2) + root := *stateUpdates[1].NewRoot + + t.Run("deployed on genesis", func(t *testing.T) { + state, err := NewStateReader(&root, stateDB) + require.NoError(t, err) + + d0 := felt.NewUnsafeFromString[felt.Felt]( + "0x20cfa74ee3564b4cd5435cdace0f9c4d43b939620e4a0bb5076105df0a626c6", + ) + deployed, err := state.ContractDeployedAt(d0, block0) + require.NoError(t, err) + assert.True(t, deployed) + + deployed, err = state.ContractDeployedAt(d0, block1) + require.NoError(t, err) + assert.True(t, deployed) + }) + + t.Run("deployed after genesis", func(t *testing.T) { + state, err := NewStateReader(&root, stateDB) + require.NoError(t, err) + + d1 := felt.NewUnsafeFromString[felt.Felt]( + "0x20cfa74ee3564b4cd5435cdace0f9c4d43b939620e4a0bb5076105df0a626c6", + ) + deployed, err := state.ContractDeployedAt(d1, block0) + require.NoError(t, err) + assert.True(t, deployed) + + deployed, err = state.ContractDeployedAt(d1, block1) + require.NoError(t, err) + assert.True(t, deployed) + }) + + t.Run("not deployed", func(t *testing.T) { + state, err := NewStateReader(&root, stateDB) + require.NoError(t, err) + + notDeployed := felt.NewUnsafeFromString[felt.Felt]("0xDEADBEEF") + deployed, err := state.ContractDeployedAt(notDeployed, block0) + require.NoError(t, err) + assert.False(t, deployed) + }) +} + +func TestContractHistory(t *testing.T) { + addr := felt.UnsafeFromString[felt.Felt]("0x1234567890abcdef") + classHash := felt.NewUnsafeFromString[felt.Felt]("0xc1a55") + nonce := felt.NewUnsafeFromString[felt.Felt]("0xace") + storageKey := felt.NewUnsafeFromString[felt.Felt]("0x5107e") + storageValue := felt.NewUnsafeFromString[felt.Felt]("0x5a1") + + emptyStateUpdate := &core.StateUpdate{ + OldRoot: &felt.Zero, + NewRoot: &felt.Zero, + StateDiff: &core.StateDiff{}, + } + + su := &core.StateUpdate{ + OldRoot: &felt.Zero, + NewRoot: felt.NewUnsafeFromString[felt.Felt]( + "0x164c1b480d2a20afe107d1cc193a78128452d5205d1721f9a7aee35babca845", + ), + StateDiff: &core.StateDiff{ + DeployedContracts: map[felt.Felt]*felt.Felt{addr: classHash}, + Nonces: map[felt.Felt]*felt.Felt{addr: nonce}, + StorageDiffs: map[felt.Felt]map[felt.Felt]*felt.Felt{addr: {*storageKey: storageValue}}, + }, + } + + t.Run("empty", func(t *testing.T) { + stateDB := newTestStateDB() + state, err := NewStateReader(&felt.Zero, stateDB) + require.NoError(t, err) + + nonce, err := state.ContractNonceAt(&addr, block0) + require.NoError(t, err) + assert.Equal(t, felt.Zero, nonce) + + classHash, err := state.ContractClassHashAt(&addr, block0) + require.NoError(t, err) + assert.Equal(t, felt.Zero, classHash) + + storage, err := state.ContractStorageAt(&addr, storageKey, block0) + require.NoError(t, err) + assert.Equal(t, felt.Zero, storage) + }) + + t.Run("retrieve block height is the same as update", func(t *testing.T) { + stateDB := newTestStateDB() + batch := stateDB.disk.NewBatch() + state, err := New(&felt.Zero, stateDB, batch) + require.NoError(t, err) + + su0 := &core.StateUpdate{ + OldRoot: &felt.Zero, + NewRoot: felt.NewUnsafeFromString[felt.Felt]( + "0x164c1b480d2a20afe107d1cc193a78128452d5205d1721f9a7aee35babca845", + ), + StateDiff: &core.StateDiff{ + DeployedContracts: map[felt.Felt]*felt.Felt{addr: classHash}, + Nonces: map[felt.Felt]*felt.Felt{addr: nonce}, + StorageDiffs: map[felt.Felt]map[felt.Felt]*felt.Felt{addr: {*storageKey: storageValue}}, + }, + } + + require.NoError(t, state.Update(&core.Header{Number: block0}, su0, nil, false)) + require.NoError(t, batch.Write()) + + reader, err := NewStateReader(su0.NewRoot, stateDB) + require.NoError(t, err) + + gotNonce, err := reader.ContractNonceAt(&addr, block0) + require.NoError(t, err) + assert.Equal(t, *nonce, gotNonce) + + gotClassHash, err := reader.ContractClassHashAt(&addr, block0) + require.NoError(t, err) + assert.Equal(t, *classHash, gotClassHash) + + gotStorage, err := reader.ContractStorageAt(&addr, storageKey, block0) + require.NoError(t, err) + assert.Equal(t, *storageValue, gotStorage) + }) + + t.Run("retrieve block height before update", func(t *testing.T) { + stateDB := newTestStateDB() + batch := stateDB.disk.NewBatch() + state0, err := New(&felt.Zero, stateDB, batch) + require.NoError(t, err) + su0 := emptyStateUpdate + require.NoError(t, state0.Update(&core.Header{Number: block0}, su0, nil, false)) + require.NoError(t, batch.Write()) + + batch = stateDB.disk.NewBatch() + state1, err := New(su0.NewRoot, stateDB, batch) + require.NoError(t, err) + su1 := su + require.NoError(t, state1.Update(&core.Header{Number: block1}, su1, nil, false)) + require.NoError(t, batch.Write()) + + reader, err := NewStateReader(su1.NewRoot, stateDB) + require.NoError(t, err) + + gotNonce, err := reader.ContractNonceAt(&addr, block0) + require.NoError(t, err) + assert.Equal(t, felt.Zero, gotNonce) + + gotClassHash, err := reader.ContractClassHashAt(&addr, block0) + require.NoError(t, err) + assert.Equal(t, felt.Zero, gotClassHash) + + gotStorage, err := reader.ContractStorageAt(&addr, storageKey, block0) + require.NoError(t, err) + assert.Equal(t, felt.Zero, gotStorage) + }) + + t.Run("retrieve block height in between updates", func(t *testing.T) { + stateDB := newTestStateDB() + batch := stateDB.disk.NewBatch() + state0, err := New(&felt.Zero, stateDB, batch) + require.NoError(t, err) + su0 := su + require.NoError(t, state0.Update(&core.Header{Number: block0}, su0, nil, false)) + require.NoError(t, batch.Write()) + + batch = stateDB.disk.NewBatch() + state1, err := New(su0.NewRoot, stateDB, batch) + require.NoError(t, err) + su1 := &core.StateUpdate{ + OldRoot: su0.NewRoot, + NewRoot: su0.NewRoot, + StateDiff: &core.StateDiff{}, + } + require.NoError(t, state1.Update(&core.Header{Number: block1}, su1, nil, false)) + require.NoError(t, batch.Write()) + + batch = stateDB.disk.NewBatch() + state2, err := New(su1.NewRoot, stateDB, batch) + require.NoError(t, err) + su2 := &core.StateUpdate{ + OldRoot: su1.NewRoot, + NewRoot: felt.NewUnsafeFromString[felt.Felt]( + "0x719e4c1206c30bb8e2924c821910582045c2acd0be342ad4ec9037d15f955be", + ), + StateDiff: &core.StateDiff{ + Nonces: map[felt.Felt]*felt.Felt{addr: felt.NewUnsafeFromString[felt.Felt]("0xaced")}, + StorageDiffs: map[felt.Felt]map[felt.Felt]*felt.Felt{ + addr: {*storageKey: felt.NewUnsafeFromString[felt.Felt]("0x5a1e")}, + }, + DeployedContracts: map[felt.Felt]*felt.Felt{ + *classHash: felt.NewUnsafeFromString[felt.Felt]("0xc1a5e"), + }, + }, + } + require.NoError(t, state2.Update(&core.Header{Number: block2}, su2, nil, false)) + require.NoError(t, batch.Write()) + + reader, err := NewStateReader(su2.NewRoot, stateDB) + require.NoError(t, err) + + gotNonce, err := reader.ContractNonceAt(&addr, block1) + require.NoError(t, err) + assert.Equal(t, *nonce, gotNonce) + + gotClassHash, err := reader.ContractClassHashAt(&addr, block1) + require.NoError(t, err) + assert.Equal(t, *classHash, gotClassHash) + + gotStorage, err := reader.ContractStorageAt(&addr, storageKey, block1) + require.NoError(t, err) + assert.Equal(t, *storageValue, gotStorage) + }) +} + +func TestContractStorageLastUpdatedBlock(t *testing.T) { + addr := felt.FromUint64[felt.Address](1) + addrFelt := felt.Felt(addr) + key := felt.NewFromUint64[felt.Felt](10) + value := felt.NewFromUint64[felt.Felt](99) + + stateDB := newTestStateDB() + batch := stateDB.disk.NewBatch() + state, err := New(&felt.Zero, stateDB, batch) + require.NoError(t, err) + + t.Run("storage never updated returns not found", func(t *testing.T) { + blockNum, err := state.ContractStorageLastUpdatedBlock(&addr, key) + require.NoError(t, err) + assert.Equal(t, uint64(0), blockNum) + }) + + su := &core.StateUpdate{ + OldRoot: &felt.Zero, + NewRoot: &felt.Zero, + StateDiff: &core.StateDiff{ + StorageDiffs: map[felt.Felt]map[felt.Felt]*felt.Felt{ + addrFelt: {*key: value}, + }, + }, + } + require.NoError(t, state.Update(&core.Header{Number: block0}, su, nil, true)) + require.NoError(t, batch.Write()) + + t.Run("storage updated at block 0 returns block 0", func(t *testing.T) { + blockNum, err := state.ContractStorageLastUpdatedBlock(&addr, key) + require.NoError(t, err) + assert.Equal(t, uint64(block0), blockNum) + }) + + // update the key at block 1 + root0, err := state.Commitment("") + require.NoError(t, err) + batch = stateDB.disk.NewBatch() + state, err = New(&root0, stateDB, batch) + require.NoError(t, err) + su1 := &core.StateUpdate{ + OldRoot: &root0, + NewRoot: &felt.Zero, + StateDiff: &core.StateDiff{ + StorageDiffs: map[felt.Felt]map[felt.Felt]*felt.Felt{ + addrFelt: {*key: value}, + }, + }, + } + require.NoError(t, state.Update(&core.Header{Number: block1}, su1, nil, true)) + require.NoError(t, batch.Write()) + + // two unrelated updates: block 2 and 3 + root1, err := state.Commitment("") + require.NoError(t, err) + batch = stateDB.disk.NewBatch() + state, err = New(&root1, stateDB, batch) + require.NoError(t, err) + su2 := &core.StateUpdate{ + OldRoot: &root0, + NewRoot: &felt.Zero, + StateDiff: &core.StateDiff{ + StorageDiffs: map[felt.Felt]map[felt.Felt]*felt.Felt{ + addrFelt: {*felt.NewRandom[felt.Felt](): value}, // unrelated key + }, + }, + } + require.NoError(t, state.Update(&core.Header{Number: block2}, su2, nil, true)) + require.NoError(t, batch.Write()) + + root2, err := state.Commitment("") + require.NoError(t, err) + batch = stateDB.disk.NewBatch() + state, err = New(&root2, stateDB, batch) + require.NoError(t, err) + su3 := &core.StateUpdate{ + OldRoot: &root2, + NewRoot: &felt.Zero, + StateDiff: &core.StateDiff{ + StorageDiffs: map[felt.Felt]map[felt.Felt]*felt.Felt{ + addrFelt: {*felt.NewRandom[felt.Felt](): value}, // unrelated key + }, + }, + } + require.NoError(t, state.Update(&core.Header{Number: block3}, su3, nil, true)) + require.NoError(t, batch.Write()) + + t.Run("returns latest updated block", func(t *testing.T) { + blockNum, err := state.ContractStorageLastUpdatedBlock(&addr, key) + require.NoError(t, err) + assert.Equal(t, uint64(block1), blockNum) + }) + + t.Run("unrelated key returns not found", func(t *testing.T) { + blockNum, err := state.ContractStorageLastUpdatedBlock( + &addr, felt.NewFromUint64[felt.Felt](999), + ) + require.NoError(t, err) + assert.Equal(t, uint64(0), blockNum) + }) +} + +func TestStateCommitmentPre014ReturnsContractRoot(t *testing.T) { + // Pre-0.14 with no declared classes (classRoot == 0): commitment must equal + // the raw contractRoot, not a Poseidon hash. + stateDB := newTestStateDB() + batch := stateDB.disk.NewBatch() + state, err := New(&felt.Zero, stateDB, batch) + require.NoError(t, err) + + storageKey := felt.NewFromUint64[felt.Felt](0) + storageVal := felt.NewFromUint64[felt.Felt](0x80) + contractAddr := felt.NewFromUint64[felt.Felt](2) + + su := &core.StateUpdate{ + OldRoot: &felt.Zero, + NewRoot: &felt.Zero, // skip root verification + StateDiff: &core.StateDiff{ + StorageDiffs: map[felt.Felt]map[felt.Felt]*felt.Felt{ + *contractAddr: {*storageKey: storageVal}, + }, + }, + } + + require.NoError(t, state.Update(&core.Header{Number: 0, ProtocolVersion: "0.13.0"}, su, nil, true)) + + // Pre-0.14: classRoot is zero, so commitment == contractRoot (no Poseidon) + pre014, err := state.Commitment("0.13.0") + require.NoError(t, err) + assert.False(t, pre014.IsZero()) + + // v0.14+: same state but Poseidon is always applied + v014, err := state.Commitment("0.14.2") + require.NoError(t, err) + + assert.NotEqual(t, + pre014, + v014, + "pre-0.14 commitment (raw contractRoot) must differ from v0.14 (Poseidon)", + ) +} diff --git a/core/state/state_test.go b/core/state/state_test.go index 4b523bc892..59ebef94fd 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -3,7 +3,6 @@ package state import ( "context" "encoding/json" - "maps" "testing" "github.com/NethermindEth/juno/clients/feeder" @@ -57,24 +56,6 @@ func TestNew(t *testing.T) { }) } -func TestNewStateReader(t *testing.T) { - t.Run("returns read-only state at root", func(t *testing.T) { - stateDB := newTestStateDB() - st, err := NewStateReader(&felt.Zero, stateDB) - require.NoError(t, err) - assert.NotNil(t, st) - }) - - t.Run("state commitment readable", func(t *testing.T) { - stateDB := newTestStateDB() - st, err := NewStateReader(&felt.Zero, stateDB) - require.NoError(t, err) - - _, err = st.Commitment("") - require.NoError(t, err) - }) -} - func TestUpdate(t *testing.T) { // These value were taken from part of integration state update number 299762 // https://external.integration.starknet.io/feeder_gateway/get_state_update?blockNumber=299762 @@ -198,209 +179,6 @@ func TestUpdate(t *testing.T) { }) } -func TestContractClassHash(t *testing.T) { - stateUpdates := getStateUpdates(t) - stateUpdates = stateUpdates[:2] - - su0 := stateUpdates[0] - su1 := stateUpdates[1] - - stateDB := setupState(t, stateUpdates, 2) - state, err := NewStateReader(su1.NewRoot, stateDB) - require.NoError(t, err) - - allDeployedContracts := make(map[felt.Felt]*felt.Felt) - maps.Copy(allDeployedContracts, su0.StateDiff.DeployedContracts) - maps.Copy(allDeployedContracts, su1.StateDiff.DeployedContracts) - - for addr, expected := range allDeployedContracts { - gotClassHash, err := state.ContractClassHash(&addr) - require.NoError(t, err) - assert.Equal(t, *expected, gotClassHash) - } -} - -func TestNonce(t *testing.T) { - addr := felt.UnsafeFromString[felt.Felt]( - "0x20cfa74ee3564b4cd5435cdace0f9c4d43b939620e4a0bb5076105df0a626c6", - ) - root := felt.NewUnsafeFromString[felt.Felt]( - "0x4bdef7bf8b81a868aeab4b48ef952415fe105ab479e2f7bc671c92173542368", - ) - - su0 := &core.StateUpdate{ - OldRoot: &felt.Zero, - NewRoot: root, - StateDiff: &core.StateDiff{ - DeployedContracts: map[felt.Felt]*felt.Felt{ - addr: felt.NewUnsafeFromString[felt.Felt]( - "0x10455c752b86932ce552f2b0fe81a880746649b9aee7e0d842bf3f52378f9f8", - ), - }, - }, - } - - t.Run("newly deployed contract has zero nonce", func(t *testing.T) { - stateDB := setupState(t, nil, 0) - batch := stateDB.disk.NewBatch() - state, err := New(&felt.Zero, stateDB, batch) - require.NoError(t, err) - require.NoError(t, state.Update(&core.Header{Number: block0}, su0, nil, false)) - require.NoError(t, batch.Write()) - - reader, err := NewStateReader(su0.NewRoot, stateDB) - require.NoError(t, err) - gotNonce, err := reader.ContractNonce(&addr) - require.NoError(t, err) - assert.Equal(t, felt.Zero, gotNonce) - }) - - t.Run("update contract nonce", func(t *testing.T) { - stateDB := setupState(t, nil, 0) - batch := stateDB.disk.NewBatch() - state, err := New(&felt.Zero, stateDB, batch) - require.NoError(t, err) - require.NoError(t, state.Update(&core.Header{Number: block0}, su0, nil, false)) - require.NoError(t, batch.Write()) - - su1 := &core.StateUpdate{ - NewRoot: felt.NewUnsafeFromString[felt.Felt]( - "0x6210642ffd49f64617fc9e5c0bbe53a6a92769e2996eb312a42d2bdb7f2afc1", - ), - OldRoot: root, - StateDiff: &core.StateDiff{ - Nonces: map[felt.Felt]*felt.Felt{addr: &felt.One}, - }, - } - - batch1 := stateDB.disk.NewBatch() - state1, err := New(su1.OldRoot, stateDB, batch1) - require.NoError(t, err) - require.NoError(t, state1.Update(&core.Header{Number: block1}, su1, nil, false)) - require.NoError(t, batch1.Write()) - - reader, err := NewStateReader(su1.NewRoot, stateDB) - require.NoError(t, err) - gotNonce, err := reader.ContractNonce(&addr) - require.NoError(t, err) - assert.Equal(t, felt.One, gotNonce) - }) -} - -func TestClass(t *testing.T) { - stateDB := setupState(t, nil, 0) - - client := feeder.NewTestClient(t, &utils.Integration) - gw := adaptfeeder.New(client) - - deprecatedCairoHash := felt.NewUnsafeFromString[felt.Felt]( - "0x4631b6b3fa31e140524b7d21ba784cea223e618bffe60b5bbdca44a8b45be04", - ) - deprecatedCairoClass, err := gw.Class(t.Context(), deprecatedCairoHash) - require.NoError(t, err) - sierraHash := felt.NewUnsafeFromString[felt.Felt]( - "0x1cd2edfb485241c4403254d550de0a097fa76743cd30696f714a491a454bad5", - ) - sierraClass, err := gw.Class(t.Context(), sierraHash) - require.NoError(t, err) - - batch := stateDB.disk.NewBatch() - state, err := New(&felt.Zero, stateDB, batch) - require.NoError(t, err) - - su0, err := gw.StateUpdate(t.Context(), 0) - require.NoError(t, err) - require.NoError(t, state.Update(&core.Header{Number: 0}, su0, map[felt.Felt]core.ClassDefinition{ - *deprecatedCairoHash: deprecatedCairoClass, - *sierraHash: sierraClass, - }, false)) - require.NoError(t, batch.Write()) - - reader, err := NewStateReader(su0.NewRoot, stateDB) - require.NoError(t, err) - - gotSierraClass, err := reader.Class(sierraHash) - require.NoError(t, err) - assert.Zero(t, gotSierraClass.At) - assert.Equal(t, sierraClass, gotSierraClass.Class) - - gotDeprecatedCairoClass, err := reader.Class(deprecatedCairoHash) - require.NoError(t, err) - assert.Zero(t, gotDeprecatedCairoClass.At) - assert.Equal(t, deprecatedCairoClass, gotDeprecatedCairoClass.Class) -} - -func TestStateTries(t *testing.T) { - stateUpdates := getStateUpdates(t) - stateDB := setupState(t, stateUpdates, 1) - root := *stateUpdates[0].NewRoot - - state, err := NewStateReader(&root, stateDB) - require.NoError(t, err) - - classTrie, err := state.ClassTrie() - require.NoError(t, err) - require.NotNil(t, classTrie) - - contractTrie, err := state.ContractTrie() - require.NoError(t, err) - require.NotNil(t, contractTrie) - - // ContractStorageTrie for a deployed contract (first deployed in mainnet block 1) - addr := &su1FirstDeployedAddress - storageTrie, err := state.ContractStorageTrie(addr) - require.NoError(t, err) - require.NotNil(t, storageTrie) -} - -func TestContractDeployedAt(t *testing.T) { - stateUpdates := getStateUpdates(t) - stateDB := setupState(t, stateUpdates, 2) - root := *stateUpdates[1].NewRoot - - t.Run("deployed on genesis", func(t *testing.T) { - state, err := NewStateReader(&root, stateDB) - require.NoError(t, err) - - d0 := felt.NewUnsafeFromString[felt.Felt]( - "0x20cfa74ee3564b4cd5435cdace0f9c4d43b939620e4a0bb5076105df0a626c6", - ) - deployed, err := state.ContractDeployedAt(d0, block0) - require.NoError(t, err) - assert.True(t, deployed) - - deployed, err = state.ContractDeployedAt(d0, block1) - require.NoError(t, err) - assert.True(t, deployed) - }) - - t.Run("deployed after genesis", func(t *testing.T) { - state, err := NewStateReader(&root, stateDB) - require.NoError(t, err) - - d1 := felt.NewUnsafeFromString[felt.Felt]( - "0x20cfa74ee3564b4cd5435cdace0f9c4d43b939620e4a0bb5076105df0a626c6", - ) - deployed, err := state.ContractDeployedAt(d1, block0) - require.NoError(t, err) - assert.True(t, deployed) - - deployed, err = state.ContractDeployedAt(d1, block1) - require.NoError(t, err) - assert.True(t, deployed) - }) - - t.Run("not deployed", func(t *testing.T) { - state, err := NewStateReader(&root, stateDB) - require.NoError(t, err) - - notDeployed := felt.NewUnsafeFromString[felt.Felt]("0xDEADBEEF") - deployed, err := state.ContractDeployedAt(notDeployed, block0) - require.NoError(t, err) - assert.False(t, deployed) - }) -} - func TestRevert(t *testing.T) { stateUpdates := getStateUpdates(t) @@ -919,279 +697,6 @@ func TestRevert(t *testing.T) { }) } -func TestContractHistory(t *testing.T) { - addr := felt.UnsafeFromString[felt.Felt]("0x1234567890abcdef") - classHash := felt.NewUnsafeFromString[felt.Felt]("0xc1a55") - nonce := felt.NewUnsafeFromString[felt.Felt]("0xace") - storageKey := felt.NewUnsafeFromString[felt.Felt]("0x5107e") - storageValue := felt.NewUnsafeFromString[felt.Felt]("0x5a1") - - emptyStateUpdate := &core.StateUpdate{ - OldRoot: &felt.Zero, - NewRoot: &felt.Zero, - StateDiff: &core.StateDiff{}, - } - - su := &core.StateUpdate{ - OldRoot: &felt.Zero, - NewRoot: felt.NewUnsafeFromString[felt.Felt]( - "0x164c1b480d2a20afe107d1cc193a78128452d5205d1721f9a7aee35babca845", - ), - StateDiff: &core.StateDiff{ - DeployedContracts: map[felt.Felt]*felt.Felt{addr: classHash}, - Nonces: map[felt.Felt]*felt.Felt{addr: nonce}, - StorageDiffs: map[felt.Felt]map[felt.Felt]*felt.Felt{addr: {*storageKey: storageValue}}, - }, - } - - t.Run("empty", func(t *testing.T) { - stateDB := newTestStateDB() - state, err := NewStateReader(&felt.Zero, stateDB) - require.NoError(t, err) - - nonce, err := state.ContractNonceAt(&addr, block0) - require.NoError(t, err) - assert.Equal(t, felt.Zero, nonce) - - classHash, err := state.ContractClassHashAt(&addr, block0) - require.NoError(t, err) - assert.Equal(t, felt.Zero, classHash) - - storage, err := state.ContractStorageAt(&addr, storageKey, block0) - require.NoError(t, err) - assert.Equal(t, felt.Zero, storage) - }) - - t.Run("retrieve block height is the same as update", func(t *testing.T) { - stateDB := newTestStateDB() - batch := stateDB.disk.NewBatch() - state, err := New(&felt.Zero, stateDB, batch) - require.NoError(t, err) - - su0 := &core.StateUpdate{ - OldRoot: &felt.Zero, - NewRoot: felt.NewUnsafeFromString[felt.Felt]( - "0x164c1b480d2a20afe107d1cc193a78128452d5205d1721f9a7aee35babca845", - ), - StateDiff: &core.StateDiff{ - DeployedContracts: map[felt.Felt]*felt.Felt{addr: classHash}, - Nonces: map[felt.Felt]*felt.Felt{addr: nonce}, - StorageDiffs: map[felt.Felt]map[felt.Felt]*felt.Felt{addr: {*storageKey: storageValue}}, - }, - } - - require.NoError(t, state.Update(&core.Header{Number: block0}, su0, nil, false)) - require.NoError(t, batch.Write()) - - reader, err := NewStateReader(su0.NewRoot, stateDB) - require.NoError(t, err) - - gotNonce, err := reader.ContractNonceAt(&addr, block0) - require.NoError(t, err) - assert.Equal(t, *nonce, gotNonce) - - gotClassHash, err := reader.ContractClassHashAt(&addr, block0) - require.NoError(t, err) - assert.Equal(t, *classHash, gotClassHash) - - gotStorage, err := reader.ContractStorageAt(&addr, storageKey, block0) - require.NoError(t, err) - assert.Equal(t, *storageValue, gotStorage) - }) - - t.Run("retrieve block height before update", func(t *testing.T) { - stateDB := newTestStateDB() - batch := stateDB.disk.NewBatch() - state0, err := New(&felt.Zero, stateDB, batch) - require.NoError(t, err) - su0 := emptyStateUpdate - require.NoError(t, state0.Update(&core.Header{Number: block0}, su0, nil, false)) - require.NoError(t, batch.Write()) - - batch = stateDB.disk.NewBatch() - state1, err := New(su0.NewRoot, stateDB, batch) - require.NoError(t, err) - su1 := su - require.NoError(t, state1.Update(&core.Header{Number: block1}, su1, nil, false)) - require.NoError(t, batch.Write()) - - reader, err := NewStateReader(su1.NewRoot, stateDB) - require.NoError(t, err) - - gotNonce, err := reader.ContractNonceAt(&addr, block0) - require.NoError(t, err) - assert.Equal(t, felt.Zero, gotNonce) - - gotClassHash, err := reader.ContractClassHashAt(&addr, block0) - require.NoError(t, err) - assert.Equal(t, felt.Zero, gotClassHash) - - gotStorage, err := reader.ContractStorageAt(&addr, storageKey, block0) - require.NoError(t, err) - assert.Equal(t, felt.Zero, gotStorage) - }) - - t.Run("retrieve block height in between updates", func(t *testing.T) { - stateDB := newTestStateDB() - batch := stateDB.disk.NewBatch() - state0, err := New(&felt.Zero, stateDB, batch) - require.NoError(t, err) - su0 := su - require.NoError(t, state0.Update(&core.Header{Number: block0}, su0, nil, false)) - require.NoError(t, batch.Write()) - - batch = stateDB.disk.NewBatch() - state1, err := New(su0.NewRoot, stateDB, batch) - require.NoError(t, err) - su1 := &core.StateUpdate{ - OldRoot: su0.NewRoot, - NewRoot: su0.NewRoot, - StateDiff: &core.StateDiff{}, - } - require.NoError(t, state1.Update(&core.Header{Number: block1}, su1, nil, false)) - require.NoError(t, batch.Write()) - - batch = stateDB.disk.NewBatch() - state2, err := New(su1.NewRoot, stateDB, batch) - require.NoError(t, err) - su2 := &core.StateUpdate{ - OldRoot: su1.NewRoot, - NewRoot: felt.NewUnsafeFromString[felt.Felt]( - "0x719e4c1206c30bb8e2924c821910582045c2acd0be342ad4ec9037d15f955be", - ), - StateDiff: &core.StateDiff{ - Nonces: map[felt.Felt]*felt.Felt{addr: felt.NewUnsafeFromString[felt.Felt]("0xaced")}, - StorageDiffs: map[felt.Felt]map[felt.Felt]*felt.Felt{ - addr: {*storageKey: felt.NewUnsafeFromString[felt.Felt]("0x5a1e")}, - }, - DeployedContracts: map[felt.Felt]*felt.Felt{ - *classHash: felt.NewUnsafeFromString[felt.Felt]("0xc1a5e"), - }, - }, - } - require.NoError(t, state2.Update(&core.Header{Number: block2}, su2, nil, false)) - require.NoError(t, batch.Write()) - - reader, err := NewStateReader(su2.NewRoot, stateDB) - require.NoError(t, err) - - gotNonce, err := reader.ContractNonceAt(&addr, block1) - require.NoError(t, err) - assert.Equal(t, *nonce, gotNonce) - - gotClassHash, err := reader.ContractClassHashAt(&addr, block1) - require.NoError(t, err) - assert.Equal(t, *classHash, gotClassHash) - - gotStorage, err := reader.ContractStorageAt(&addr, storageKey, block1) - require.NoError(t, err) - assert.Equal(t, *storageValue, gotStorage) - }) -} - -func TestContractStorageLastUpdatedBlock(t *testing.T) { - addr := felt.FromUint64[felt.Address](1) - addrFelt := felt.Felt(addr) - key := felt.NewFromUint64[felt.Felt](10) - value := felt.NewFromUint64[felt.Felt](99) - - stateDB := newTestStateDB() - batch := stateDB.disk.NewBatch() - state, err := New(&felt.Zero, stateDB, batch) - require.NoError(t, err) - - t.Run("storage never updated returns not found", func(t *testing.T) { - blockNum, err := state.ContractStorageLastUpdatedBlock(&addr, key) - require.NoError(t, err) - assert.Equal(t, uint64(0), blockNum) - }) - - su := &core.StateUpdate{ - OldRoot: &felt.Zero, - NewRoot: &felt.Zero, - StateDiff: &core.StateDiff{ - StorageDiffs: map[felt.Felt]map[felt.Felt]*felt.Felt{ - addrFelt: {*key: value}, - }, - }, - } - require.NoError(t, state.Update(&core.Header{Number: block0}, su, nil, true)) - require.NoError(t, batch.Write()) - - t.Run("storage updated at block 0 returns block 0", func(t *testing.T) { - blockNum, err := state.ContractStorageLastUpdatedBlock(&addr, key) - require.NoError(t, err) - assert.Equal(t, uint64(block0), blockNum) - }) - - // update the key at block 1 - root0, err := state.Commitment("") - require.NoError(t, err) - batch = stateDB.disk.NewBatch() - state, err = New(&root0, stateDB, batch) - require.NoError(t, err) - su1 := &core.StateUpdate{ - OldRoot: &root0, - NewRoot: &felt.Zero, - StateDiff: &core.StateDiff{ - StorageDiffs: map[felt.Felt]map[felt.Felt]*felt.Felt{ - addrFelt: {*key: value}, - }, - }, - } - require.NoError(t, state.Update(&core.Header{Number: block1}, su1, nil, true)) - require.NoError(t, batch.Write()) - - // two unrelated updates: block 2 and 3 - root1, err := state.Commitment("") - require.NoError(t, err) - batch = stateDB.disk.NewBatch() - state, err = New(&root1, stateDB, batch) - require.NoError(t, err) - su2 := &core.StateUpdate{ - OldRoot: &root0, - NewRoot: &felt.Zero, - StateDiff: &core.StateDiff{ - StorageDiffs: map[felt.Felt]map[felt.Felt]*felt.Felt{ - addrFelt: {*felt.NewRandom[felt.Felt](): value}, // unrelated key - }, - }, - } - require.NoError(t, state.Update(&core.Header{Number: block2}, su2, nil, true)) - require.NoError(t, batch.Write()) - - root2, err := state.Commitment("") - require.NoError(t, err) - batch = stateDB.disk.NewBatch() - state, err = New(&root2, stateDB, batch) - require.NoError(t, err) - su3 := &core.StateUpdate{ - OldRoot: &root2, - NewRoot: &felt.Zero, - StateDiff: &core.StateDiff{ - StorageDiffs: map[felt.Felt]map[felt.Felt]*felt.Felt{ - addrFelt: {*felt.NewRandom[felt.Felt](): value}, // unrelated key - }, - }, - } - require.NoError(t, state.Update(&core.Header{Number: block3}, su3, nil, true)) - require.NoError(t, batch.Write()) - - t.Run("returns latest updated block", func(t *testing.T) { - blockNum, err := state.ContractStorageLastUpdatedBlock(&addr, key) - require.NoError(t, err) - assert.Equal(t, uint64(block1), blockNum) - }) - - t.Run("unrelated key returns not found", func(t *testing.T) { - blockNum, err := state.ContractStorageLastUpdatedBlock( - &addr, felt.NewFromUint64[felt.Felt](999), - ) - require.NoError(t, err) - assert.Equal(t, uint64(0), blockNum) - }) -} - func BenchmarkStateUpdate(b *testing.B) { client := feeder.NewTestClient(b, &utils.Mainnet) gw := adaptfeeder.New(client) @@ -1268,46 +773,6 @@ func setupState(t *testing.T, stateUpdates []*core.StateUpdate, blocks uint64) * return stateDB } -func TestStateCommitmentPre014ReturnsContractRoot(t *testing.T) { - // Pre-0.14 with no declared classes (classRoot == 0): commitment must equal - // the raw contractRoot, not a Poseidon hash. - stateDB := newTestStateDB() - batch := stateDB.disk.NewBatch() - state, err := New(&felt.Zero, stateDB, batch) - require.NoError(t, err) - - storageKey := felt.NewFromUint64[felt.Felt](0) - storageVal := felt.NewFromUint64[felt.Felt](0x80) - contractAddr := felt.NewFromUint64[felt.Felt](2) - - su := &core.StateUpdate{ - OldRoot: &felt.Zero, - NewRoot: &felt.Zero, // skip root verification - StateDiff: &core.StateDiff{ - StorageDiffs: map[felt.Felt]map[felt.Felt]*felt.Felt{ - *contractAddr: {*storageKey: storageVal}, - }, - }, - } - - require.NoError(t, state.Update(&core.Header{Number: 0, ProtocolVersion: "0.13.0"}, su, nil, true)) - - // Pre-0.14: classRoot is zero, so commitment == contractRoot (no Poseidon) - pre014, err := state.Commitment("0.13.0") - require.NoError(t, err) - assert.False(t, pre014.IsZero()) - - // v0.14+: same state but Poseidon is always applied - v014, err := state.Commitment("0.14.2") - require.NoError(t, err) - - assert.NotEqual(t, - pre014, - v014, - "pre-0.14 commitment (raw contractRoot) must differ from v0.14 (Poseidon)", - ) -} - func newTestStateDB() *StateDB { memDB := memory.New() db, err := triedb.New(memDB, nil) diff --git a/core/state/statetestutils/new_state_flag.go b/core/state/testutils/new_state_flag.go similarity index 87% rename from core/state/statetestutils/new_state_flag.go rename to core/state/testutils/new_state_flag.go index efd51163f6..fdd5bf065a 100644 --- a/core/state/statetestutils/new_state_flag.go +++ b/core/state/testutils/new_state_flag.go @@ -1,4 +1,4 @@ -package statetestutils +package testutils import ( "os" diff --git a/core/trie2/proof.go b/core/trie2/proof.go index a8bfccd865..7340118f53 100644 --- a/core/trie2/proof.go +++ b/core/trie2/proof.go @@ -7,6 +7,7 @@ import ( "github.com/NethermindEth/juno/core/crypto" "github.com/NethermindEth/juno/core/felt" "github.com/NethermindEth/juno/core/trie2/trienode" + "github.com/NethermindEth/juno/core/trie2/trieutils" "github.com/NethermindEth/juno/utils" ) @@ -25,7 +26,7 @@ func (t *Trie) Prove(key *felt.Felt, proof *ProofNodeSet) error { return ErrCommitted } - path := t.FeltToPath(key) + path := trieutils.FeltToPath(key, t.height) var ( nodes []trienode.Node diff --git a/core/trie2/trie.go b/core/trie2/trie.go index 1acbd3271d..ea5a03e2ed 100644 --- a/core/trie2/trie.go +++ b/core/trie2/trie.go @@ -155,7 +155,7 @@ func (t *Trie) Get(key *felt.Felt) (felt.Felt, error) { return felt.Zero, ErrCommitted } - k := t.FeltToPath(key) + k := trieutils.FeltToPath(key, t.height) var ret felt.Felt val, root, didResolve, err := t.get(t.root, new(Path), &k) @@ -179,7 +179,7 @@ func (t *Trie) Delete(key *felt.Felt) error { return ErrCommitted } - k := t.FeltToPath(key) + k := trieutils.FeltToPath(key, t.height) n, _, err := t.delete(t.root, new(Path), &k) if err != nil { return err @@ -306,7 +306,7 @@ func (t *Trie) get(n trienode.Node, prefix, key *Path) (*felt.Felt, trienode.Nod // Modifies the trie by either inserting/updating a value or deleting a key. // The operation is determined by whether the value is zero (delete) or non-zero (insert/update). func (t *Trie) update(key, value *felt.Felt) error { - k := t.FeltToPath(key) + k := trieutils.FeltToPath(key, t.height) if value.IsZero() { n, _, err := t.delete(t.root, new(Path), &k) if err != nil { @@ -564,14 +564,6 @@ func (t *Trie) hashRoot() (trienode.Node, trienode.Node) { return hashed, cached } -// Converts a Felt value into a Path representation suitable to -// use as a trie key with the specified height. -func (t *Trie) FeltToPath(f *felt.Felt) Path { - var key Path - key.SetFelt(t.height, f) - return key -} - func (t *Trie) String() string { if t.root == nil { return "" diff --git a/core/trie2/trieutils/helpers.go b/core/trie2/trieutils/helpers.go new file mode 100644 index 0000000000..2e2e107215 --- /dev/null +++ b/core/trie2/trieutils/helpers.go @@ -0,0 +1,11 @@ +package trieutils + +import "github.com/NethermindEth/juno/core/felt" + +// Converts a Felt value into a Path representation suitable to +// use as a trie key with the specified height. +func FeltToPath(f *felt.Felt, height uint8) Path { + var key Path + key.SetFelt(height, f) + return key +}