github.com/tacshi/go-ethereum@v0.0.0-20230616113857-84a434e20921/cmd/geth/snapshot.go (about)

     1  // Copyright 2021 The go-ethereum Authors
     2  // This file is part of go-ethereum.
     3  //
     4  // go-ethereum is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // go-ethereum is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU General Public License
    15  // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package main
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/json"
    22  	"errors"
    23  	"os"
    24  	"time"
    25  
    26  	"github.com/tacshi/go-ethereum/cmd/utils"
    27  	"github.com/tacshi/go-ethereum/common"
    28  	"github.com/tacshi/go-ethereum/core/rawdb"
    29  	"github.com/tacshi/go-ethereum/core/state"
    30  	"github.com/tacshi/go-ethereum/core/state/pruner"
    31  	"github.com/tacshi/go-ethereum/core/state/snapshot"
    32  	"github.com/tacshi/go-ethereum/core/types"
    33  	"github.com/tacshi/go-ethereum/crypto"
    34  	"github.com/tacshi/go-ethereum/internal/flags"
    35  	"github.com/tacshi/go-ethereum/log"
    36  	"github.com/tacshi/go-ethereum/rlp"
    37  	"github.com/tacshi/go-ethereum/trie"
    38  	cli "github.com/urfave/cli/v2"
    39  )
    40  
    41  var (
    42  	snapshotCommand = &cli.Command{
    43  		Name:        "snapshot",
    44  		Usage:       "A set of commands based on the snapshot",
    45  		Description: "",
    46  		Subcommands: []*cli.Command{
    47  			{
    48  				Name:      "prune-state",
    49  				Usage:     "Prune stale ethereum state data based on the snapshot",
    50  				ArgsUsage: "<root>",
    51  				Action:    pruneState,
    52  				Flags: flags.Merge([]cli.Flag{
    53  					utils.CacheTrieJournalFlag,
    54  					utils.BloomFilterSizeFlag,
    55  				}, utils.NetworkFlags, utils.DatabasePathFlags),
    56  				Description: `
    57  geth snapshot prune-state <state-root>
    58  will prune historical state data with the help of the state snapshot.
    59  All trie nodes and contract codes that do not belong to the specified
    60  version state will be deleted from the database. After pruning, only
    61  two version states are available: genesis and the specific one.
    62  
    63  The default pruning target is the HEAD-127 state.
    64  
    65  WARNING: It's necessary to delete the trie clean cache after the pruning.
    66  If you specify another directory for the trie clean cache via "--cache.trie.journal"
    67  during the use of Geth, please also specify it here for correct deletion. Otherwise
    68  the trie clean cache with default directory will be deleted.
    69  `,
    70  			},
    71  			{
    72  				Name:      "verify-state",
    73  				Usage:     "Recalculate state hash based on the snapshot for verification",
    74  				ArgsUsage: "<root>",
    75  				Action:    verifyState,
    76  				Flags:     flags.Merge(utils.NetworkFlags, utils.DatabasePathFlags),
    77  				Description: `
    78  geth snapshot verify-state <state-root>
    79  will traverse the whole accounts and storages set based on the specified
    80  snapshot and recalculate the root hash of state for verification.
    81  In other words, this command does the snapshot to trie conversion.
    82  `,
    83  			},
    84  			{
    85  				Name:      "check-dangling-storage",
    86  				Usage:     "Check that there is no 'dangling' snap storage",
    87  				ArgsUsage: "<root>",
    88  				Action:    checkDanglingStorage,
    89  				Flags:     flags.Merge(utils.NetworkFlags, utils.DatabasePathFlags),
    90  				Description: `
    91  geth snapshot check-dangling-storage <state-root> traverses the snap storage 
    92  data, and verifies that all snapshot storage data has a corresponding account. 
    93  `,
    94  			},
    95  			{
    96  				Name:      "inspect-account",
    97  				Usage:     "Check all snapshot layers for the a specific account",
    98  				ArgsUsage: "<address | hash>",
    99  				Action:    checkAccount,
   100  				Flags:     flags.Merge(utils.NetworkFlags, utils.DatabasePathFlags),
   101  				Description: `
   102  geth snapshot inspect-account <address | hash> checks all snapshot layers and prints out
   103  information about the specified address. 
   104  `,
   105  			},
   106  			{
   107  				Name:      "traverse-state",
   108  				Usage:     "Traverse the state with given root hash and perform quick verification",
   109  				ArgsUsage: "<root>",
   110  				Action:    traverseState,
   111  				Flags:     flags.Merge(utils.NetworkFlags, utils.DatabasePathFlags),
   112  				Description: `
   113  geth snapshot traverse-state <state-root>
   114  will traverse the whole state from the given state root and will abort if any
   115  referenced trie node or contract code is missing. This command can be used for
   116  state integrity verification. The default checking target is the HEAD state.
   117  
   118  It's also usable without snapshot enabled.
   119  `,
   120  			},
   121  			{
   122  				Name:      "traverse-rawstate",
   123  				Usage:     "Traverse the state with given root hash and perform detailed verification",
   124  				ArgsUsage: "<root>",
   125  				Action:    traverseRawState,
   126  				Flags:     flags.Merge(utils.NetworkFlags, utils.DatabasePathFlags),
   127  				Description: `
   128  geth snapshot traverse-rawstate <state-root>
   129  will traverse the whole state from the given root and will abort if any referenced
   130  trie node or contract code is missing. This command can be used for state integrity
   131  verification. The default checking target is the HEAD state. It's basically identical
   132  to traverse-state, but the check granularity is smaller. 
   133  
   134  It's also usable without snapshot enabled.
   135  `,
   136  			},
   137  			{
   138  				Name:      "dump",
   139  				Usage:     "Dump a specific block from storage (same as 'geth dump' but using snapshots)",
   140  				ArgsUsage: "[? <blockHash> | <blockNum>]",
   141  				Action:    dumpState,
   142  				Flags: flags.Merge([]cli.Flag{
   143  					utils.ExcludeCodeFlag,
   144  					utils.ExcludeStorageFlag,
   145  					utils.StartKeyFlag,
   146  					utils.DumpLimitFlag,
   147  				}, utils.NetworkFlags, utils.DatabasePathFlags),
   148  				Description: `
   149  This command is semantically equivalent to 'geth dump', but uses the snapshots
   150  as the backend data source, making this command a lot faster. 
   151  
   152  The argument is interpreted as block number or hash. If none is provided, the latest
   153  block is used.
   154  `,
   155  			},
   156  		},
   157  	}
   158  )
   159  
   160  // Deprecation: this command should be deprecated once the hash-based
   161  // scheme is deprecated.
   162  func pruneState(ctx *cli.Context) error {
   163  	stack, config := makeConfigNode(ctx)
   164  	defer stack.Close()
   165  
   166  	chaindb := utils.MakeChainDatabase(ctx, stack, false)
   167  	defer chaindb.Close()
   168  
   169  	prunerconfig := pruner.Config{
   170  		Datadir:   stack.ResolvePath(""),
   171  		Cachedir:  stack.ResolvePath(config.Eth.TrieCleanCacheJournal),
   172  		BloomSize: ctx.Uint64(utils.BloomFilterSizeFlag.Name),
   173  	}
   174  	pruner, err := pruner.NewPruner(chaindb, prunerconfig)
   175  	if err != nil {
   176  		log.Error("Failed to open snapshot tree", "err", err)
   177  		return err
   178  	}
   179  	if ctx.NArg() > 1 {
   180  		log.Error("Too many arguments given")
   181  		return errors.New("too many arguments")
   182  	}
   183  	var targetRoots []common.Hash
   184  	if ctx.NArg() == 1 {
   185  		root, err := parseRoot(ctx.Args().First())
   186  		if err != nil {
   187  			log.Error("Failed to resolve state root", "err", err)
   188  			return err
   189  		}
   190  		targetRoots = append(targetRoots, root)
   191  	} else {
   192  		// Prune to the last snapshot
   193  		targetRoots = append(targetRoots, common.Hash{})
   194  	}
   195  	if err = pruner.Prune(targetRoots); err != nil {
   196  		log.Error("Failed to prune state", "err", err)
   197  		return err
   198  	}
   199  	return nil
   200  }
   201  
   202  func verifyState(ctx *cli.Context) error {
   203  	stack, _ := makeConfigNode(ctx)
   204  	defer stack.Close()
   205  
   206  	chaindb := utils.MakeChainDatabase(ctx, stack, true)
   207  	defer chaindb.Close()
   208  
   209  	headBlock := rawdb.ReadHeadBlock(chaindb)
   210  	if headBlock == nil {
   211  		log.Error("Failed to load head block")
   212  		return errors.New("no head block")
   213  	}
   214  	snapconfig := snapshot.Config{
   215  		CacheSize:  256,
   216  		Recovery:   false,
   217  		NoBuild:    true,
   218  		AsyncBuild: false,
   219  	}
   220  	snaptree, err := snapshot.New(snapconfig, chaindb, trie.NewDatabase(chaindb), headBlock.Root())
   221  	if err != nil {
   222  		log.Error("Failed to open snapshot tree", "err", err)
   223  		return err
   224  	}
   225  	if ctx.NArg() > 1 {
   226  		log.Error("Too many arguments given")
   227  		return errors.New("too many arguments")
   228  	}
   229  	var root = headBlock.Root()
   230  	if ctx.NArg() == 1 {
   231  		root, err = parseRoot(ctx.Args().First())
   232  		if err != nil {
   233  			log.Error("Failed to resolve state root", "err", err)
   234  			return err
   235  		}
   236  	}
   237  	if err := snaptree.Verify(root); err != nil {
   238  		log.Error("Failed to verify state", "root", root, "err", err)
   239  		return err
   240  	}
   241  	log.Info("Verified the state", "root", root)
   242  	return snapshot.CheckDanglingStorage(chaindb)
   243  }
   244  
   245  // checkDanglingStorage iterates the snap storage data, and verifies that all
   246  // storage also has corresponding account data.
   247  func checkDanglingStorage(ctx *cli.Context) error {
   248  	stack, _ := makeConfigNode(ctx)
   249  	defer stack.Close()
   250  
   251  	return snapshot.CheckDanglingStorage(utils.MakeChainDatabase(ctx, stack, true))
   252  }
   253  
   254  // traverseState is a helper function used for pruning verification.
   255  // Basically it just iterates the trie, ensure all nodes and associated
   256  // contract codes are present.
   257  func traverseState(ctx *cli.Context) error {
   258  	stack, _ := makeConfigNode(ctx)
   259  	defer stack.Close()
   260  
   261  	chaindb := utils.MakeChainDatabase(ctx, stack, true)
   262  	headBlock := rawdb.ReadHeadBlock(chaindb)
   263  	if headBlock == nil {
   264  		log.Error("Failed to load head block")
   265  		return errors.New("no head block")
   266  	}
   267  	if ctx.NArg() > 1 {
   268  		log.Error("Too many arguments given")
   269  		return errors.New("too many arguments")
   270  	}
   271  	var (
   272  		root common.Hash
   273  		err  error
   274  	)
   275  	if ctx.NArg() == 1 {
   276  		root, err = parseRoot(ctx.Args().First())
   277  		if err != nil {
   278  			log.Error("Failed to resolve state root", "err", err)
   279  			return err
   280  		}
   281  		log.Info("Start traversing the state", "root", root)
   282  	} else {
   283  		root = headBlock.Root()
   284  		log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64())
   285  	}
   286  	triedb := trie.NewDatabase(chaindb)
   287  	t, err := trie.NewStateTrie(trie.StateTrieID(root), triedb)
   288  	if err != nil {
   289  		log.Error("Failed to open trie", "root", root, "err", err)
   290  		return err
   291  	}
   292  	var (
   293  		accounts   int
   294  		slots      int
   295  		codes      int
   296  		lastReport time.Time
   297  		start      = time.Now()
   298  	)
   299  	accIter := trie.NewIterator(t.NodeIterator(nil))
   300  	for accIter.Next() {
   301  		accounts += 1
   302  		var acc types.StateAccount
   303  		if err := rlp.DecodeBytes(accIter.Value, &acc); err != nil {
   304  			log.Error("Invalid account encountered during traversal", "err", err)
   305  			return err
   306  		}
   307  		if acc.Root != types.EmptyRootHash {
   308  			id := trie.StorageTrieID(root, common.BytesToHash(accIter.Key), acc.Root)
   309  			storageTrie, err := trie.NewStateTrie(id, triedb)
   310  			if err != nil {
   311  				log.Error("Failed to open storage trie", "root", acc.Root, "err", err)
   312  				return err
   313  			}
   314  			storageIter := trie.NewIterator(storageTrie.NodeIterator(nil))
   315  			for storageIter.Next() {
   316  				slots += 1
   317  			}
   318  			if storageIter.Err != nil {
   319  				log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Err)
   320  				return storageIter.Err
   321  			}
   322  		}
   323  		if !bytes.Equal(acc.CodeHash, types.EmptyCodeHash.Bytes()) {
   324  			if !rawdb.HasCode(chaindb, common.BytesToHash(acc.CodeHash)) {
   325  				log.Error("Code is missing", "hash", common.BytesToHash(acc.CodeHash))
   326  				return errors.New("missing code")
   327  			}
   328  			codes += 1
   329  		}
   330  		if time.Since(lastReport) > time.Second*8 {
   331  			log.Info("Traversing state", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start)))
   332  			lastReport = time.Now()
   333  		}
   334  	}
   335  	if accIter.Err != nil {
   336  		log.Error("Failed to traverse state trie", "root", root, "err", accIter.Err)
   337  		return accIter.Err
   338  	}
   339  	log.Info("State is complete", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start)))
   340  	return nil
   341  }
   342  
   343  // traverseRawState is a helper function used for pruning verification.
   344  // Basically it just iterates the trie, ensure all nodes and associated
   345  // contract codes are present. It's basically identical to traverseState
   346  // but it will check each trie node.
   347  func traverseRawState(ctx *cli.Context) error {
   348  	stack, _ := makeConfigNode(ctx)
   349  	defer stack.Close()
   350  
   351  	chaindb := utils.MakeChainDatabase(ctx, stack, true)
   352  	headBlock := rawdb.ReadHeadBlock(chaindb)
   353  	if headBlock == nil {
   354  		log.Error("Failed to load head block")
   355  		return errors.New("no head block")
   356  	}
   357  	if ctx.NArg() > 1 {
   358  		log.Error("Too many arguments given")
   359  		return errors.New("too many arguments")
   360  	}
   361  	var (
   362  		root common.Hash
   363  		err  error
   364  	)
   365  	if ctx.NArg() == 1 {
   366  		root, err = parseRoot(ctx.Args().First())
   367  		if err != nil {
   368  			log.Error("Failed to resolve state root", "err", err)
   369  			return err
   370  		}
   371  		log.Info("Start traversing the state", "root", root)
   372  	} else {
   373  		root = headBlock.Root()
   374  		log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64())
   375  	}
   376  	triedb := trie.NewDatabase(chaindb)
   377  	t, err := trie.NewStateTrie(trie.StateTrieID(root), triedb)
   378  	if err != nil {
   379  		log.Error("Failed to open trie", "root", root, "err", err)
   380  		return err
   381  	}
   382  	var (
   383  		nodes      int
   384  		accounts   int
   385  		slots      int
   386  		codes      int
   387  		lastReport time.Time
   388  		start      = time.Now()
   389  		hasher     = crypto.NewKeccakState()
   390  		got        = make([]byte, 32)
   391  	)
   392  	accIter := t.NodeIterator(nil)
   393  	for accIter.Next(true) {
   394  		nodes += 1
   395  		node := accIter.Hash()
   396  
   397  		// Check the present for non-empty hash node(embedded node doesn't
   398  		// have their own hash).
   399  		if node != (common.Hash{}) {
   400  			blob := rawdb.ReadLegacyTrieNode(chaindb, node)
   401  			if len(blob) == 0 {
   402  				log.Error("Missing trie node(account)", "hash", node)
   403  				return errors.New("missing account")
   404  			}
   405  			hasher.Reset()
   406  			hasher.Write(blob)
   407  			hasher.Read(got)
   408  			if !bytes.Equal(got, node.Bytes()) {
   409  				log.Error("Invalid trie node(account)", "hash", node.Hex(), "value", blob)
   410  				return errors.New("invalid account node")
   411  			}
   412  		}
   413  		// If it's a leaf node, yes we are touching an account,
   414  		// dig into the storage trie further.
   415  		if accIter.Leaf() {
   416  			accounts += 1
   417  			var acc types.StateAccount
   418  			if err := rlp.DecodeBytes(accIter.LeafBlob(), &acc); err != nil {
   419  				log.Error("Invalid account encountered during traversal", "err", err)
   420  				return errors.New("invalid account")
   421  			}
   422  			if acc.Root != types.EmptyRootHash {
   423  				id := trie.StorageTrieID(root, common.BytesToHash(accIter.LeafKey()), acc.Root)
   424  				storageTrie, err := trie.NewStateTrie(id, triedb)
   425  				if err != nil {
   426  					log.Error("Failed to open storage trie", "root", acc.Root, "err", err)
   427  					return errors.New("missing storage trie")
   428  				}
   429  				storageIter := storageTrie.NodeIterator(nil)
   430  				for storageIter.Next(true) {
   431  					nodes += 1
   432  					node := storageIter.Hash()
   433  
   434  					// Check the presence for non-empty hash node(embedded node doesn't
   435  					// have their own hash).
   436  					if node != (common.Hash{}) {
   437  						blob := rawdb.ReadLegacyTrieNode(chaindb, node)
   438  						if len(blob) == 0 {
   439  							log.Error("Missing trie node(storage)", "hash", node)
   440  							return errors.New("missing storage")
   441  						}
   442  						hasher.Reset()
   443  						hasher.Write(blob)
   444  						hasher.Read(got)
   445  						if !bytes.Equal(got, node.Bytes()) {
   446  							log.Error("Invalid trie node(storage)", "hash", node.Hex(), "value", blob)
   447  							return errors.New("invalid storage node")
   448  						}
   449  					}
   450  					// Bump the counter if it's leaf node.
   451  					if storageIter.Leaf() {
   452  						slots += 1
   453  					}
   454  				}
   455  				if storageIter.Error() != nil {
   456  					log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Error())
   457  					return storageIter.Error()
   458  				}
   459  			}
   460  			if !bytes.Equal(acc.CodeHash, types.EmptyCodeHash.Bytes()) {
   461  				if !rawdb.HasCode(chaindb, common.BytesToHash(acc.CodeHash)) {
   462  					log.Error("Code is missing", "account", common.BytesToHash(accIter.LeafKey()))
   463  					return errors.New("missing code")
   464  				}
   465  				codes += 1
   466  			}
   467  			if time.Since(lastReport) > time.Second*8 {
   468  				log.Info("Traversing state", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start)))
   469  				lastReport = time.Now()
   470  			}
   471  		}
   472  	}
   473  	if accIter.Error() != nil {
   474  		log.Error("Failed to traverse state trie", "root", root, "err", accIter.Error())
   475  		return accIter.Error()
   476  	}
   477  	log.Info("State is complete", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start)))
   478  	return nil
   479  }
   480  
   481  func parseRoot(input string) (common.Hash, error) {
   482  	var h common.Hash
   483  	if err := h.UnmarshalText([]byte(input)); err != nil {
   484  		return h, err
   485  	}
   486  	return h, nil
   487  }
   488  
   489  func dumpState(ctx *cli.Context) error {
   490  	stack, _ := makeConfigNode(ctx)
   491  	defer stack.Close()
   492  
   493  	conf, db, root, err := parseDumpConfig(ctx, stack)
   494  	if err != nil {
   495  		return err
   496  	}
   497  	snapConfig := snapshot.Config{
   498  		CacheSize:  256,
   499  		Recovery:   false,
   500  		NoBuild:    true,
   501  		AsyncBuild: false,
   502  	}
   503  	snaptree, err := snapshot.New(snapConfig, db, trie.NewDatabase(db), root)
   504  	if err != nil {
   505  		return err
   506  	}
   507  	accIt, err := snaptree.AccountIterator(root, common.BytesToHash(conf.Start))
   508  	if err != nil {
   509  		return err
   510  	}
   511  	defer accIt.Release()
   512  
   513  	log.Info("Snapshot dumping started", "root", root)
   514  	var (
   515  		start    = time.Now()
   516  		logged   = time.Now()
   517  		accounts uint64
   518  	)
   519  	enc := json.NewEncoder(os.Stdout)
   520  	enc.Encode(struct {
   521  		Root common.Hash `json:"root"`
   522  	}{root})
   523  	for accIt.Next() {
   524  		account, err := snapshot.FullAccount(accIt.Account())
   525  		if err != nil {
   526  			return err
   527  		}
   528  		da := &state.DumpAccount{
   529  			Balance:   account.Balance.String(),
   530  			Nonce:     account.Nonce,
   531  			Root:      account.Root,
   532  			CodeHash:  account.CodeHash,
   533  			SecureKey: accIt.Hash().Bytes(),
   534  		}
   535  		if !conf.SkipCode && !bytes.Equal(account.CodeHash, types.EmptyCodeHash.Bytes()) {
   536  			da.Code = rawdb.ReadCode(db, common.BytesToHash(account.CodeHash))
   537  		}
   538  		if !conf.SkipStorage {
   539  			da.Storage = make(map[common.Hash]string)
   540  
   541  			stIt, err := snaptree.StorageIterator(root, accIt.Hash(), common.Hash{})
   542  			if err != nil {
   543  				return err
   544  			}
   545  			for stIt.Next() {
   546  				da.Storage[stIt.Hash()] = common.Bytes2Hex(stIt.Slot())
   547  			}
   548  		}
   549  		enc.Encode(da)
   550  		accounts++
   551  		if time.Since(logged) > 8*time.Second {
   552  			log.Info("Snapshot dumping in progress", "at", accIt.Hash(), "accounts", accounts,
   553  				"elapsed", common.PrettyDuration(time.Since(start)))
   554  			logged = time.Now()
   555  		}
   556  		if conf.Max > 0 && accounts >= conf.Max {
   557  			break
   558  		}
   559  	}
   560  	log.Info("Snapshot dumping complete", "accounts", accounts,
   561  		"elapsed", common.PrettyDuration(time.Since(start)))
   562  	return nil
   563  }
   564  
   565  // checkAccount iterates the snap data layers, and looks up the given account
   566  // across all layers.
   567  func checkAccount(ctx *cli.Context) error {
   568  	if ctx.NArg() != 1 {
   569  		return errors.New("need <address|hash> arg")
   570  	}
   571  	var (
   572  		hash common.Hash
   573  		addr common.Address
   574  	)
   575  	switch arg := ctx.Args().First(); len(arg) {
   576  	case 40, 42:
   577  		addr = common.HexToAddress(arg)
   578  		hash = crypto.Keccak256Hash(addr.Bytes())
   579  	case 64, 66:
   580  		hash = common.HexToHash(arg)
   581  	default:
   582  		return errors.New("malformed address or hash")
   583  	}
   584  	stack, _ := makeConfigNode(ctx)
   585  	defer stack.Close()
   586  	chaindb := utils.MakeChainDatabase(ctx, stack, true)
   587  	defer chaindb.Close()
   588  	start := time.Now()
   589  	log.Info("Checking difflayer journal", "address", addr, "hash", hash)
   590  	if err := snapshot.CheckJournalAccount(chaindb, hash); err != nil {
   591  		return err
   592  	}
   593  	log.Info("Checked the snapshot journalled storage", "time", common.PrettyDuration(time.Since(start)))
   594  	return nil
   595  }