Repository: NethermindEth/juno
Pull request #295 (2022-07-22 01:20:41)

refactor config
Fixes: #230 #300 

## Changes
- Process configurations from various sources (in order of precedence): CLI parameters, environment variables, config file entries, and default values.
- Use consistent naming conventions regardless of the configuration source
- Never modify the user's environment except for creating database files in locations explicitly provided by the user or in clearly defined default locations.

## Types of changes

What types of changes does your code introduce?

- [X] New feature (more CLI flags and corresponding environment variables)
- [X] Breaking change (previous configurations will break)
- [X] Refactoring

## Testing
**Requires testing**

- [ ] Yes
- [X] No (currently we do not test anything in `cmd/juno`)

**In case you checked yes, did you write tests??**

- [ ] Yes
- [X] No

**Note**: now that `main.go` is getting more modular, I think we can add tests for most of it in the near future.

codecov[bot]: # [Codecov](https://codecov.io/gh/NethermindEth/juno/pull/295?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=NethermindEth) Report
> Merging [#295](https://codecov.io/gh/NethermindEth/juno/pull/295?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=NethermindEth) (2cff070) into [main](https://codecov.io/gh/NethermindEth/juno/commit/a0f507e2ab38f98d5a35068b77f177263e32fe2f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=NethermindEth) (a0f507e) will **not change** coverage.
> The diff coverage is `100.00%`.

```diff
@@            Coverage Diff            @@
##              main      #295   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           60        59    -1     
  Lines         6721      6659   -62     
=========================================
- Hits          6721      6659   -62     
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `100.00% <100.00%> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=NethermindEth#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/NethermindEth/juno/pull/295?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=NethermindEth) | Coverage Δ | |
|---|---|---|
| [pkg/starknet/state.go](https://codecov.io/gh/NethermindEth/juno/pull/295/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=NethermindEth#diff-cGtnL3N0YXJrbmV0L3N0YXRlLmdv) | `100.00% <ø> (ø)` | |
| [pkg/rest/handler.go](https://codecov.io/gh/NethermindEth/juno/pull/295/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=NethermindEth#diff-cGtnL3Jlc3QvaGFuZGxlci5nbw==) | `100.00% <100.00%> (ø)` | |
| [pkg/felt/felt.go](https://codecov.io/gh/NethermindEth/juno/pull/295/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=NethermindEth#diff-cGtnL2ZlbHQvZmVsdC5nbw==) | `100.00% <0.00%> (ø)` | |
| [internal/config/config.go](https://codecov.io/gh/NethermindEth/juno/pull/295/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=NethermindEth#diff-aW50ZXJuYWwvY29uZmlnL2NvbmZpZy5nbw==) | | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/NethermindEth/juno/pull/295?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=NethermindEth).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=NethermindEth)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/NethermindEth/juno/pull/295?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=NethermindEth). Last update [a0f507e...2cff070](https://codecov.io/gh/NethermindEth/juno/pull/295?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=NethermindEth). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=NethermindEth).

D-DePablos: If we expect the user to use the --starknet-enable flag, it is not routed with make all `starknet enable`. 

Since our [docs](https://gojuno.xyz/docs/intro) indicate the user to `make run`, or `make all`, this would not run correctly as the docs stand. 

![image](https://user-images.githubusercontent.com/42889395/181473213-015fadf6-ce43-4780-806f-ea8bf817ea68.png)

stdevMac: Can we add more details around Environment vars and params on the docs? @joshklop Thanks in advance

Patch:
diff --git a/.github/workflows/juno-build.yml b/.github/workflows/juno-build.yml
index 3163bf6c..31648424 100644
--- a/.github/workflows/juno-build.yml
+++ b/.github/workflows/juno-build.yml
@@ -19,7 +19,7 @@ jobs:
         uses: actions/setup-go@v3
         with:
           go-version: ${{ matrix.go }}
-      - run: make compile
+      - run: make all
 
       - name: Install gofumpt
         run: make install-gofumpt
diff --git a/Dockerfile b/Dockerfile
index 0e4b3d6e..85c1dc3e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -10,7 +10,7 @@ RUN apk update && apk upgrade && apk add --update alpine-sdk && \
 COPY . .
 RUN go mod download
 
-RUN make compile
+RUN make juno
 
 FROM alpine:3.15
 
diff --git a/Makefile b/Makefile
index 851e75b2..ce1d1ff0 100644
--- a/Makefile
+++ b/Makefile
@@ -1,15 +1,15 @@
 export CC = clang
-.DEFAULT_GOAL 	:= help
+.DEFAULT_GOAL := help
 
-compile: ## compile
+juno: ## compile
 	@mkdir -p build
-	@go build -o build/juno-cli cmd/juno-cli/main.go
-	@go build -o build/juno cmd/juno/main.go
+	@go build -o build/juno cmd/juno/*.go
 
-run: ## run
-	@./build/juno
+juno-cli:
+	@mkdir -p build
+	@go build -o build/juno-cli cmd/juno-cli/main.go
 
-all: compile run ## build and run
+all: juno juno-cli
 
 generate: ## generate
 	@cd internal/db && $(MAKE) generate
diff --git a/cmd/juno-cli/cli/root.go b/cmd/juno-cli/cli/root.go
index 841ad7ab..e8fab79b 100644
--- a/cmd/juno-cli/cli/root.go
+++ b/cmd/juno-cli/cli/root.go
@@ -5,55 +5,47 @@ import (
 	_ "embed"
 	"encoding/json"
 	"fmt"
-	"os"
-	"path/filepath"
 	"strconv"
 
-	"github.com/NethermindEth/juno/internal/config"
 	. "github.com/NethermindEth/juno/internal/log"
 	"github.com/NethermindEth/juno/pkg/feeder"
 	"github.com/spf13/cobra"
 	"github.com/spf13/viper"
 )
 
+type Config struct {
+	Network   string
+	FeederUrl string
+}
+
 // Cobra configuration.
 var (
-	// cfgFile is the path of the juno configuration file.
-	cfgFile string
-	// dataDir is the path of the directory to read and save user-specific
-	// application data
-	dataDir string
+	cfg = &Config{}
 	// longMsg is the long message shown in the "juno --help" output.
 	//go:embed long.txt
 	longMsg string
-	// selectedNetwork is the network selected by the config or user.
-	selectedNetwork string
 
 	// rootCmd is the root command of the application.
 	rootCmd = &cobra.Command{
 		Use:   "juno-cli [command] [flags]",
 		Short: "Starknet client implementation in Go.",
 		Long:  longMsg,
-		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
+		PersistentPreRun: func(cmd *cobra.Command, _ []string) {
 			if network, _ := cmd.Flags().GetString("network"); network != "" {
 				handleNetwork(network)
 			}
-			return initConfig()
 		},
 	}
 )
 
 // Define flags and load config.
 func init() {
-	// Set flags shared accross commands as persistent flags.
-	rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", fmt.Sprintf(
-		"config file (default is %s).", filepath.Join(config.Dir, "juno.yaml")))
-
 	// Pretty print flag.
 	rootCmd.PersistentFlags().BoolP("pretty", "p", false, "Pretty print the response.")
 
 	// Network flag.
-	rootCmd.PersistentFlags().StringVarP(&selectedNetwork, "network", "n", "", "Use a network different to config. Available: 'mainnet', 'goerli'.")
+	rootCmd.PersistentFlags().StringVarP(&cfg.Network, "network", "n", "", "the StarkNet network. Options: 'mainnet', 'goerli'.")
+	rootCmd.PersistentFlags().StringVarP(&cfg.FeederUrl, "feeder", "f", "", "the URL of the feeder gateway.")
 }
 
 // handle networks by changing active value during call only
@@ -90,55 +82,8 @@ func isInteger(input string) bool {
 	return err == nil
 }
 
-// initConfig reads in Config file or environment variables if set.
-func initConfig() error {
-	if dataDir != "" {
-		info, err := os.Stat(dataDir)
-		if err != nil || !info.IsDir() {
-			dataDir = config.DataDir
-			Logger.Infof("Invalid data directory. The default data directory (%s) will be used.", dataDir)
-		}
-	}
-	if cfgFile != "" {
-		// If a specific config file is given, read it in.
-		viper.SetConfigFile(cfgFile)
-	} else {
-		// Use the default path for user configuration.
-		viper.AddConfigPath(config.Dir)
-		viper.SetConfigName("juno")
-		viper.SetConfigType("yaml")
-	}
-
-	// Fetch other configs from the environment.
-	viper.AutomaticEnv()
-
-	err := viper.ReadInConfig()
-	if err != nil {
-		Logger.Info("Config file not found.")
-		if !config.Exists() {
-			config.New()
-		}
-		viper.SetConfigFile(filepath.Join(config.Dir, "juno.yaml"))
-		err = viper.ReadInConfig()
-		if err != nil {
-			Logger.Fatal("Failed to read Juno configuration file.")
-		}
-	}
-
-	// Unmarshal and log runtime config instance.
-	err = viper.Unmarshal(&config.Runtime)
-	if err != nil {
-		Logger.Fatal("Failed to parse runtime configuration.")
-	}
-
-	// If config successfully loaded, return no error.
-	return nil
-}
-
 func initClient() *feeder.Client {
-	feederUrl := config.Runtime.Starknet.FeederGateway
-	client := feeder.NewClient(feederUrl, "/feeder_gateway", nil)
-	return client
+	return feeder.NewClient(cfg.FeederUrl, "/feeder_gateway", nil)
 }
 
 // Execute handle flags for Cobra execution.
diff --git a/cmd/juno/cli/create_dynamic_config.go b/cmd/juno/cli/create_dynamic_config.go
deleted file mode 100644
index 30a31072..00000000
--- a/cmd/juno/cli/create_dynamic_config.go
+++ /dev/null
@@ -1,189 +0,0 @@
-package cli
-
-import (
-	"fmt"
-	"os"
-	"strconv"
-	"strings"
-
-	"github.com/NethermindEth/juno/internal/config"
-	"github.com/gdamore/tcell/v2"
-	"github.com/rivo/tview"
-	"github.com/spf13/cobra"
-	"github.com/spf13/viper"
-	"gopkg.in/yaml.v2"
-)
-
-// updateConfig updates the configuration values from an instance of the Config struct.
-func updateConfig(newConfig config.Config) error {
-	file := viper.ConfigFileUsed()
-	data, err := yaml.Marshal(&newConfig)
-	if err != nil {
-		return err
-	}
-	err = os.WriteFile(file, data, 0o644)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-// createDynamicConfigCmd represents the createDynamicConfig command
-var createDynamicConfigCmd = &cobra.Command{
-	Use:   "configure",
-	Short: "Update your juno configuration file",
-	Long: `Available fields:
-		- Enable/Disable:
-			- RPC Server 
-			- Node Metrics Server
-			- Node REST API
-			- API Synchronization Mode
-
-		- Ethereum Node URL
-		- Network ID
-
-		Advanced:
-		- Port Settings
-		- Logger Verbosity Settings
-	.`,
-	Run: func(cmd *cobra.Command, args []string) {
-		main()
-	},
-}
-
-func main() {
-	app := tview.NewApplication()
-	runtime := *config.Runtime
-	newConfig := &runtime
-
-	networkList := []string{"mainnet", "goerli"}
-	selectedNw := runtime.Starknet.Network
-	// Find index of selected network inside the networkList
-	selectedNwIndex := -1
-	for i, nw := range networkList {
-		if strings.EqualFold(nw, selectedNw) {
-			selectedNwIndex = i
-			break
-		}
-	}
-	verbosityList := []string{"DEBUG", "INFO", "WARN", "ERROR", "DPANIC", "PANIC", "FATAL"}
-	selectedVerb := runtime.Logger.VerbosityLevel
-	// Find index of selected verbosity level inside the verbosityList
-	selectedVerbIndex := -1
-	for i, verb := range verbosityList {
-		if strings.EqualFold(verb, selectedVerb) {
-			selectedVerbIndex = i
-			break
-		}
-	}
-
-	// Create two forms which are shown in the same flex window.
-	formLeft := tview.NewForm()
-	formLeft.SetBorder(true).SetTitle(fmt.Sprintf("Juno Config (%s)", viper.ConfigFileUsed())).SetTitleColor(tcell.ColorPurple)
-
-	// Add fields to the form.
-	formLeft.
-		AddCheckbox("RPC Enabled", runtime.RPC.Enabled, func(checked bool) {
-			newConfig.RPC.Enabled = checked
-		}).
-		AddCheckbox("Metrics Enabled", runtime.Metrics.Enabled, func(checked bool) {
-			newConfig.Metrics.Enabled = checked
-		}).
-		AddCheckbox("REST API Enabled", runtime.REST.Enabled, func(checked bool) {
-			newConfig.REST.Enabled = checked
-		}).
-		AddInputField("Ethereum Node URL", runtime.Ethereum.Node, 20, nil, func(nodeURL string) {
-			newConfig.Ethereum.Node = nodeURL
-		}).
-		AddDropDown("Network", networkList, selectedNwIndex, func(option string, index int) {
-			// Set both the network and feeder gateway URL
-			switch option {
-			case "mainnet":
-				newConfig.Starknet.FeederGateway = "https://alpha-mainnet.starknet.io"
-				newConfig.Starknet.Network = "mainnet"
-			case "goerli":
-				newConfig.Starknet.FeederGateway = "https://alpha4.starknet.io"
-				newConfig.Starknet.Network = "goerli"
-			}
-		}).
-		AddCheckbox("API Sync Enabled", runtime.Starknet.ApiSync, func(checked bool) {
-			newConfig.Starknet.ApiSync = checked
-		}).
-		AddButton("Save (CTRL + S)", func() {
-			// Close the application
-			app.Stop()
-			// Update the config file with the new values
-			updateConfig(*newConfig)
-		}).
-		AddButton("Cancel (esc)", func() {
-			// Close the application
-			app.Stop()
-			fmt.Println("Cancelled Config Update.")
-		})
-
-	// Create new form and set title to Juno Config, draw box edges.
-	formRight := tview.NewForm()
-	formRight.SetBorder(true).SetTitle("Advanced Settings").SetTitleAlign(tview.AlignCenter)
-
-	// Add checkboxes, input fields, and buttons.
-	formRight.
-		AddInputField("RPC Port", fmt.Sprintf("%d", runtime.RPC.Port), 5, nil, func(rpcPort string) {
-			port, err := strconv.Atoi(rpcPort)
-			// If the port is not a number, set it to the previous value.
-			if err != nil {
-				newConfig.RPC.Port = port
-			}
-			newConfig.RPC.Port = port
-		}).
-		AddInputField("Metrics Port", fmt.Sprintf("%d", runtime.Metrics.Port), 5, nil, func(metricsPort string) {
-			port, err := strconv.Atoi(metricsPort)
-			// If the port is not a number, set it to the previous value.
-			if err != nil {
-				newConfig.Metrics.Port = port
-			}
-			newConfig.Metrics.Port = port
-		}).
-		AddInputField("REST Port", fmt.Sprintf("%d", runtime.REST.Port), 5, nil, func(restPort string) {
-			port, err := strconv.Atoi(restPort)
-			// If the port is not a number, set it to the previous value.
-			if err != nil {
-				newConfig.REST.Port = port
-			}
-			newConfig.REST.Port = port
-		}).
-		AddInputField("Database Path", runtime.DbPath, 50, nil, func(dbPath string) {
-			newConfig.DbPath = dbPath
-		}).
-		AddDropDown("Logger Verbosity", verbosityList, selectedVerbIndex, func(option string, index int) {
-			newConfig.Logger.VerbosityLevel = option
-		}).
-		AddCheckbox("Logger JSON output", runtime.Logger.EnableJsonOutput, func(checked bool) {
-			newConfig.Logger.EnableJsonOutput = checked
-		})
-
-	// Create a layout where half of the available space is given to the left form and the other half to the right form.
-	flex := tview.NewFlex().
-		AddItem(formLeft, 0, 1, true).
-		AddItem(formRight, 0, 1, true)
-
-	// Allow q to quit the application.
-	app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
-		// If user presses Ctrl+q, quit the application.
-		if event.Key() == tcell.KeyEscape {
-			app.Stop()
-		} else if event.Key() == tcell.KeyCtrlS {
-			app.Stop()
-			updateConfig(*newConfig)
-		}
-		return event
-	})
-	// Open the application last
-	if err := app.SetRoot(flex, true).EnableMouse(true).Run(); err != nil {
-		panic(err)
-	}
-}
-
-func init() {
-	rootCmd.AddCommand(createDynamicConfigCmd)
-}
diff --git a/cmd/juno/cli/long.txt b/cmd/juno/cli/long.txt
deleted file mode 100644
index d2b15dc4..00000000
--- a/cmd/juno/cli/long.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-       _                    
-      | |                   
-      | |_   _ _ __   ___   
-  _   | | | | | '_ \ / _ \  
- | |__| | |_| | | | | (_) | 
-  \____/ \__,_|_| |_|\___/  
-
-Juno is a Go implementation of a StarkNet full node client made with ❤️ by Nethermind.
diff --git a/cmd/juno/cli/root.go b/cmd/juno/cli/root.go
deleted file mode 100644
index 97e28c3e..00000000
--- a/cmd/juno/cli/root.go
+++ /dev/null
@@ -1,356 +0,0 @@
-package cli
-
-import (
-	_ "embed"
-	"fmt"
-	"os"
-	"os/signal"
-	"path/filepath"
-	"strconv"
-	"syscall"
-	"time"
-
-	"github.com/NethermindEth/juno/pkg/jsonrpc"
-
-	"github.com/NethermindEth/juno/internal/cairovm"
-	"github.com/NethermindEth/juno/internal/config"
-	"github.com/NethermindEth/juno/internal/db"
-	"github.com/NethermindEth/juno/internal/db/block"
-	"github.com/NethermindEth/juno/internal/db/state"
-	"github.com/NethermindEth/juno/internal/db/sync"
-	"github.com/NethermindEth/juno/internal/db/transaction"
-	. "github.com/NethermindEth/juno/internal/log"
-	metric "github.com/NethermindEth/juno/internal/metrics/prometheus"
-	"github.com/NethermindEth/juno/internal/rpc"
-	"github.com/NethermindEth/juno/internal/rpc/starknet"
-	syncService "github.com/NethermindEth/juno/internal/sync"
-	"github.com/NethermindEth/juno/pkg/feeder"
-	"github.com/NethermindEth/juno/pkg/rest"
-	"github.com/spf13/cobra"
-	"github.com/spf13/viper"
-	"github.com/torquem-ch/mdbx-go/mdbx"
-)
-
-const (
-	mdbxOptMaxDb uint64 = 100
-	mdbxFlags    uint   = 0
-	minPort      int    = 1_024
-	maxPort      int    = 49_151
-)
-
-// Cobra configuration.
-var (
-	// cfgFile is the path of the juno configuration file.
-	cfgFile string
-	// dataDir is the path of the directory to read and save user-specific
-	// application data
-	dataDir string
-	// longMsg is the long message shown in the "juno --help" output.
-	//go:embed long.txt
-	longMsg string
-)
-
-var (
-	mdbxEnv *mdbx.Env
-
-	rpcServer     *rpc.HttpRpc
-	metricsServer *metric.Server
-	restServer    *rest.Server
-
-	feederGatewayClient *feeder.Client
-
-	synchronizer   *syncService.Synchronizer
-	virtualMachine *cairovm.VirtualMachine
-
-	stateManager       *state.Manager
-	transactionManager *transaction.Manager
-	blockManager       *block.Manager
-	syncManager        *sync.Manager
-)
-
-const shutdownTimeout = 5 * time.Second
-
-// rootCmd is the root command of the application.
-var rootCmd = &cobra.Command{
-	Use:   "juno [options]",
-	Short: "Starknet client implementation in Go.",
-	Run:   juno,
-}
-
-// Execute handle flags for Cobra execution.
-func Execute() {
-	if err := rootCmd.Execute(); err != nil {
-		Logger.With("Error", err).Error("Failed to execute CLI.")
-	}
-}
-
-// init defines flags and handles configuration.
-func init() {
-	fmt.Println(longMsg)
-	// Set the functions to be run when rootCmd.Execute() is called.
-	initConfig()
-
-	rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", fmt.Sprintf(
-		"config file (default is %s)", filepath.Join(config.Dir, "juno.yaml")))
-	rootCmd.PersistentFlags().StringVar(&dataDir, "dataDir", "", fmt.Sprintf(
-		"data path (default is %s)", config.DataDir))
-}
-
-func juno(_ *cobra.Command, _ []string) {
-	Logger.With(
-		"Database Path", config.Runtime.DbPath,
-		"Rpc Port", config.Runtime.RPC.Port,
-		"Rpc Enabled", config.Runtime.RPC.Enabled,
-		"Rest Port", config.Runtime.REST.Port,
-		"Rest Enabled", config.Runtime.REST.Enabled,
-		"Rest Prefix", config.Runtime.REST.Prefix,
-	).Info("Juno config values:")
-
-	setupInterruptHandler()
-	setupDatabaseManagers()
-	setupFeederGateway()
-	setupSynchronizer()
-	setupVirtualMachine()
-	setupServers()
-
-	const numOfErrCh = 3
-	errChs := make([]chan error, numOfErrCh)
-	for i := 0; i < numOfErrCh; i++ {
-		errChs[i] = make(chan error)
-	}
-
-	if config.Runtime.RPC.Enabled {
-		rpcServer.ListenAndServe(errChs[0])
-	}
-	if config.Runtime.Metrics.Enabled {
-		metricsServer.ListenAndServe(errChs[1])
-	}
-	if config.Runtime.REST.Enabled {
-		restServer.ListenAndServe(errChs[2])
-	}
-	if config.Runtime.Starknet.Enabled {
-		synchronizer.Run()
-	}
-
-	checkErrChs(errChs)
-}
-
-func setupVirtualMachine() {
-	virtualMachine = cairovm.New(stateManager)
-}
-
-func setupSynchronizer() {
-	synchronizer = syncService.NewSynchronizer(feederGatewayClient, syncManager, stateManager, blockManager,
-		transactionManager)
-}
-
-func setupServers() {
-	var err error
-	if config.Runtime.RPC.Enabled {
-		checkPort("JSON-RPC", config.Runtime.RPC.Port)
-		starknetApi := starknet.New(stateManager, blockManager, transactionManager, synchronizer, virtualMachine)
-		jsonRpc := jsonrpc.NewJsonRpc()
-		handlers := []struct {
-			name       string
-			function   any
-			paramNames []string
-		}{
-			{"starknet_getBlockWithTxHashes", starknetApi.GetBlockWithTxHashes, []string{"block_id"}},
-			{"starknet_getBlockWithTxs", starknetApi.GetBlockWithTxs, []string{"block_id"}},
-			{"starknet_getStateUpdate", starknetApi.GetStateUpdate, []string{"block_id"}},
-			{"starknet_getStorageAt", starknetApi.GetStorageAt, []string{"block_id", "address", "key"}},
-			{"starknet_getTransactionByHash", starknetApi.GetTransactionByHash, []string{"transaction_hash"}},
-			{"starknet_getTransactionByBlockIdAndIndex", starknetApi.GetTransactionByBlockIdAndIndex, []string{"block_id", "index"}},
-			{"starknet_getTransactionReceipt", starknetApi.GetTransactionReceipt, []string{"transaction_hash"}},
-			{"starknet_getClass", starknetApi.GetClass, []string{"class_hash"}},
-			{"starknet_getClassHashAt", starknetApi.GetClassHashAt, []string{"block_id", "address"}},
-			{"starknet_getBlockTransactionCount", starknetApi.GetBlockTransactionCount, []string{"block_id"}},
-			{"starknet_call", starknetApi.Call, []string{"block_id", "request"}},
-			{"starknet_estimateFee", starknetApi.EstimateFee, []string{"block_id", "request"}},
-			{"starknet_blockNumber", starknetApi.BlockNumber, nil},
-			{"starknet_blockHashAndNumber", starknetApi.BlockHashAndNumber, nil},
-			{"starknet_chainId", starknetApi.ChainId, nil},
-			{"starkent_pendingTrnasactions", starknetApi.PendingTransactions, nil},
-			{"starknet_protocolVersion", starknetApi.ProtocolVersion, nil},
-			{"starknet_syncing", starknetApi.Syncing, nil},
-		}
-		for _, handler := range handlers {
-			if err := jsonRpc.RegisterFunc(handler.name, handler.function, handler.paramNames...); err != nil {
-				Logger.With("Error", err).Error("Failed to register RPC handler.")
-			}
-		}
-		rpcServer, err = rpc.NewHttpRpc(":"+strconv.Itoa(config.Runtime.RPC.Port), "/rpc", jsonRpc)
-		if err != nil {
-			Logger.Fatal("Failed to initialise RPC Server", err)
-		}
-	}
-
-	if config.Runtime.Metrics.Enabled {
-		checkPort("Metrics", config.Runtime.Metrics.Port)
-		metricsServer = metric.SetupMetric(":" + strconv.Itoa(config.Runtime.Metrics.Port))
-	}
-
-	if config.Runtime.REST.Enabled {
-		checkPort("API", config.Runtime.REST.Port)
-		restServer = rest.NewServer(":"+strconv.Itoa(config.Runtime.REST.Port),
-			config.Runtime.Starknet.FeederGateway, config.Runtime.REST.Prefix)
-	}
-}
-
-func checkPort(server string, port int) {
-	if port < minPort || port > maxPort {
-		Logger.Fatalf("%s port must be between %d and %d", server, minPort, maxPort)
-	}
-}
-
-func setupFeederGateway() {
-	feederGatewayClient = feeder.NewClient(config.Runtime.Starknet.FeederGateway, "/feeder_gateway", nil)
-}
-
-func setupDatabaseManagers() {
-	var err error
-	var dbName string
-
-	mdbxEnv, err = db.NewMDBXEnv(config.Runtime.DbPath, mdbxOptMaxDb, mdbxFlags)
-	if err != nil {
-		Logger.Fatal("Failed to create MDBX Database environment: ", err)
-	}
-
-	logDBErr := func(name string, err error) {
-		if err != nil {
-			Logger.Fatalf("Failed to create %s database: %s", name, err.Error())
-		}
-	}
-
-	dbName = "SYNC"
-	syncDb, err := db.NewMDBXDatabase(mdbxEnv, dbName)
-	logDBErr(dbName, err)
-
-	dbName = "CONTRACT_DEF"
-	contractDefDb, err := db.NewMDBXDatabase(mdbxEnv, dbName)
-	logDBErr(dbName, err)
-
-	dbName = "STATE"
-	stateDb, err := db.NewMDBXDatabase(mdbxEnv, dbName)
-	logDBErr(dbName, err)
-
-	dbName = "TRANSACTION"
-	txDb, err := db.NewMDBXDatabase(mdbxEnv, dbName)
-	logDBErr(dbName, err)
-
-	dbName = "RECEIPT"
-	receiptDb, err := db.NewMDBXDatabase(mdbxEnv, dbName)
-	logDBErr(dbName, err)
-
-	dbName = "BLOCK"
-	blockDb, err := db.NewMDBXDatabase(mdbxEnv, dbName)
-	logDBErr(dbName, err)
-
-	syncManager = sync.NewManager(syncDb)
-	stateManager = state.NewManager(stateDb, contractDefDb)
-	transactionManager = transaction.NewManager(txDb, receiptDb)
-	blockManager = block.NewManager(blockDb)
-}
-
-func setupInterruptHandler() {
-	// Handle signal interrupts and exits.
-	sig := make(chan os.Signal)
-	signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
-	go func(sig chan os.Signal) {
-		<-sig
-		Logger.Info("Shutting down...")
-		shutdown()
-		os.Exit(0)
-	}(sig)
-}
-
-func shutdown() {
-	stateManager.Close()
-	transactionManager.Close()
-	blockManager.Close()
-	stateManager.Close()
-	syncManager.Close()
-	synchronizer.Close()
-
-	if err := rpcServer.Close(shutdownTimeout); err != nil {
-		Logger.Fatal("Failed to shutdown RPC server gracefully: ", err.Error())
-	}
-
-	if err := metricsServer.Close(shutdownTimeout); err != nil {
-		Logger.Fatal("Failed to shutdown Metrics server gracefully: ", err.Error())
-	}
-
-	if err := restServer.Close(shutdownTimeout); err != nil {
-		Logger.Fatal("Failed to shutdown REST server gracefully: ", err.Error())
-	}
-}
-
-func checkErrChs(errChs []chan error) {
-	for _, errCh := range errChs {
-		for {
-			if err, ok := <-errCh; err != nil {
-				Logger.Fatal(err)
-			} else if !ok {
-				continue
-			}
-		}
-	}
-}
-
-// initConfig reads in Config file or environment variables if set.
-func initConfig() {
-	if dataDir != "" {
-		info, err := os.Stat(dataDir)
-		if err != nil || !info.IsDir() {
-			dataDir = config.DataDir
-			Logger.Infof("Invalid data directory. The default data directory (%s) will be used.", dataDir)
-		}
-	}
-	if cfgFile != "" {
-		// Use Config file specified by the flag.
-		viper.SetConfigFile(cfgFile)
-	} else {
-		// Use the default path for user configuration.
-		viper.AddConfigPath(config.Dir)
-		viper.SetConfigType("yaml")
-		viper.SetConfigName("juno")
-	}
-
-	// Check whether the environment variables match any of the existing
-	// keys and loads them if they are found.
-	viper.AutomaticEnv()
-
-	err := viper.ReadInConfig()
-	if err == nil {
-		Logger.Infof("Using config file: %s.", viper.ConfigFileUsed())
-	} else {
-		Logger.Info("Config file not found.")
-		if !config.Exists() {
-			config.New()
-		}
-		viper.SetConfigFile(filepath.Join(config.Dir, "juno.yaml"))
-		err = viper.ReadInConfig()
-		if err != nil {
-			Logger.Fatal("Failed to read in config file after generation.")
-		}
-	}
-
-	// Unmarshal and log runtime config instance.
-	err = viper.Unmarshal(&config.Runtime)
-	if err != nil {
-		Logger.Fatal("Failed to parse runtime configuration.")
-	}
-
-	// Configure logger - we want the logger to be created right after the config has been set
-	enableJsonOutput := config.Runtime.Logger.EnableJsonOutput
-	verbosityLevel := config.Runtime.Logger.VerbosityLevel
-	err = ReplaceGlobalLogger(enableJsonOutput, verbosityLevel)
-	if err != nil {
-		Logger.Fatal("Failed to initialise global logger.")
-	}
-
-	Logger.With(
-		"Verbosity Level", verbosityLevel,
-		"Json Output", enableJsonOutput,
-	).Info("Logger settings:")
-}
diff --git a/cmd/juno/configure.go b/cmd/juno/configure.go
new file mode 100644
index 00000000..995e1802
--- /dev/null
+++ b/cmd/juno/configure.go
@@ -0,0 +1,168 @@
+package main
+
+import (
+	"fmt"
+	"os"
+	"strconv"
+	"strings"
+
+	"github.com/NethermindEth/juno/internal/config"
+	"github.com/gdamore/tcell/v2"
+	"github.com/rivo/tview"
+	"github.com/spf13/cobra"
+	"gopkg.in/yaml.v2"
+)
+
+func newConfigureCmd(cfg *config.Juno, configFile *string) *cobra.Command {
+	configureCmd := &cobra.Command{
+		Use:   "configure",
+		Short: "Interactively build your juno configuration file",
+		PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
+			return loadConfig(cmd, configFile)
+		},
+		Run: func(_ *cobra.Command, _ []string) {
+			configure(cfg, *configFile)
+		},
+	}
+
+	return configureCmd
+}
+
+func configure(cfg *config.Juno, configFile string) {
+	app := tview.NewApplication()
+
+	networkList := []string{"mainnet", "goerli"}
+	network := cfg.Sync.Network
+	// Find index of selected network inside the networkList
+	selectedNwIndex := -1
+	for i, nw := range networkList {
+		if strings.EqualFold(nw, network) {
+			selectedNwIndex = i
+			break
+		}
+	}
+	// Find index of selected verbosity level inside the verbosityList
+	currentLogLevelIndex := -1
+	currentLogLevel := cfg.Log.Level
+	verbosityList := []string{"DEBUG", "INFO", "WARN", "ERROR", "DPANIC", "PANIC", "FATAL"}
+	for i, verb := range verbosityList {
+		if strings.EqualFold(verb, currentLogLevel) {
+			currentLogLevelIndex = i
+			break
+		}
+	}
+
+	// Create two forms which are shown in the same flex window.
+	formLeft := tview.NewForm()
+	formLeft.SetBorder(true).SetTitle(fmt.Sprintf("Juno Config (%s)", configFile)).SetTitleColor(tcell.ColorPurple)
+
+	// Add fields to the form.
+	formLeft.
+		AddCheckbox("RPC Enabled", cfg.Rpc.Enable, func(checked bool) {
+			cfg.Rpc.Enable = checked
+		}).
+		AddCheckbox("Metrics Enabled", cfg.Metrics.Enable, func(checked bool) {
+			cfg.Metrics.Enable = checked
+		}).
+		AddCheckbox("REST API Enabled", cfg.Rest.Enable, func(checked bool) {
+			cfg.Rest.Enable = checked
+		}).
+		AddInputField("Ethereum Node URL", cfg.Sync.EthNode, 20, nil, func(nodeURL string) {
+			cfg.Sync.EthNode = nodeURL
+		}).
+		AddDropDown("Network", networkList, selectedNwIndex, func(option string, _ int) {
+			// Set both the network and feeder gateway URL
+			switch option {
+			case "mainnet":
+				cfg.Sync.Sequencer = "https://alpha-mainnet.starknet.io"
+				cfg.Sync.Network = "mainnet"
+			case "goerli":
+				cfg.Sync.Sequencer = "https://alpha4.starknet.io"
+				cfg.Sync.Network = "goerli"
+			}
+		}).
+		AddCheckbox("API Sync Enabled", cfg.Sync.Trusted, func(checked bool) {
+			cfg.Sync.Trusted = checked
+		}).
+		AddButton("Save (CTRL + S)", func() {
+			// Close the application
+			app.Stop()
+			// Update the config file with the new values
+			updateConfig(cfg, configFile)
+		}).
+		AddButton("Cancel (esc)", func() {
+			// Close the application
+			app.Stop()
+			fmt.Println("Cancelled Config Update.")
+		})
+
+	// Create new form and set title to Juno Config, draw box edges.
+	formRight := tview.NewForm()
+	formRight.SetBorder(true).SetTitle("Advanced Settings").SetTitleAlign(tview.AlignCenter)
+
+	// Add checkboxes, input fields, and buttons.
+	formRight.
+		AddInputField("RPC Port", formatPortNumber(cfg.Rpc.Port), 5, isPortNumber, func(rpcPort string) {
+			cfg.Rpc.Port, _ = parsePortNumber(rpcPort)
+		}).
+		AddInputField("Metrics Port", formatPortNumber(cfg.Metrics.Port), 5, isPortNumber, func(metricsPort string) {
+			cfg.Metrics.Port, _ = parsePortNumber(metricsPort)
+		}).
+		AddInputField("REST Port", formatPortNumber(cfg.Rest.Port), 5, isPortNumber, func(restPort string) {
+			cfg.Rest.Port, _ = parsePortNumber(restPort)
+		}).
+		AddInputField("Database Path", cfg.Database.Path, 50, nil, func(dbPath string) {
+			cfg.Database.Path = dbPath
+		}).
+		AddDropDown("Logger Verbosity", verbosityList, currentLogLevelIndex, func(option string, _ int) {
+			cfg.Log.Level = option
+		}).
+		AddCheckbox("Logger JSON output", cfg.Log.Json, func(checked bool) {
+			cfg.Log.Json = checked
+		})
+
+	// Create a layout where half of the available space is given to the left form and the other half to the right form.
+	flex := tview.NewFlex().
+		AddItem(formLeft, 0, 1, true).
+		AddItem(formRight, 0, 1, true)
+
+	// Allow q to quit the application.
+	app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
+		// If user presses Ctrl+q, quit the application.
+		if event.Key() == tcell.KeyEscape {
+			app.Stop()
+		} else if event.Key() == tcell.KeyCtrlS {
+			app.Stop()
+			updateConfig(cfg, configFile)
+		}
+		return event
+	})
+
+	// Open the application
+	if err := app.SetRoot(flex, true).EnableMouse(true).Run(); err != nil {
+		panic(err)
+	}
+}
+
+func formatPortNumber(portNumber uint) string {
+	return strconv.FormatUint(uint64(portNumber), 10)
+}
+
+func isPortNumber(input string, _ rune) bool {
+	_, err := parsePortNumber(input)
+	return err == nil
+}
+
+func parsePortNumber(input string) (uint, error) {
+	val, err := strconv.ParseUint(input, 10, 16) // Maximum port number is 2^16 - 1
+	return uint(val), err
+}
+
+// updateConfig updates the configuration values from an instance of the config struct.
+func updateConfig(cfg *config.Juno, configFile string) error {
+	data, err := yaml.Marshal(cfg)
+	if err != nil {
+		return err
+	}
+	return os.WriteFile(configFile, data, os.ModePerm)
+}
diff --git a/cmd/juno/main.go b/cmd/juno/main.go
index 059a8529..6ab5cab4 100644
--- a/cmd/juno/main.go
+++ b/cmd/juno/main.go
@@ -1,7 +1,401 @@
 package main
 
-import "github.com/NethermindEth/juno/cmd/juno/cli"
+// notest
+import (
+	_ "embed"
+	"fmt"
+	"os"
+	"os/signal"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"syscall"
+	"time"
+
+	"github.com/NethermindEth/juno/pkg/jsonrpc"
+
+	"github.com/NethermindEth/juno/internal/cairovm"
+	"github.com/NethermindEth/juno/internal/config"
+	"github.com/NethermindEth/juno/internal/db"
+	"github.com/NethermindEth/juno/internal/db/block"
+	"github.com/NethermindEth/juno/internal/db/state"
+	"github.com/NethermindEth/juno/internal/db/sync"
+	"github.com/NethermindEth/juno/internal/db/transaction"
+	. "github.com/NethermindEth/juno/internal/log"
+	metric "github.com/NethermindEth/juno/internal/metrics/prometheus"
+	"github.com/NethermindEth/juno/internal/rpc"
+	"github.com/NethermindEth/juno/internal/rpc/starknet"
+	syncService "github.com/NethermindEth/juno/internal/sync"
+	"github.com/NethermindEth/juno/pkg/feeder"
+	"github.com/NethermindEth/juno/pkg/rest"
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+	"github.com/spf13/viper"
+	"github.com/torquem-ch/mdbx-go/mdbx"
+)
+
+var (
+	mdbxEnv *mdbx.Env
+
+	rpcServer     *rpc.HttpRpc
+	metricsServer *metric.Server
+	restServer    *rest.Server
+
+	synchronizer   *syncService.Synchronizer
+	virtualMachine *cairovm.VirtualMachine
+
+	stateManager       *state.Manager
+	transactionManager *transaction.Manager
+	blockManager       *block.Manager
+	syncManager        *sync.Manager
+)
 
 func main() {
-	cli.Execute()
+	fmt.Printf(`
+       _                    
+      | |                   
+      | |_   _ _ __   ___   
+  _   | | | | | '_ \ / _ \  
+ | |__| | |_| | | | | (_) |  
+  \____/ \__,_|_| |_|\___/  
+
+Juno is a Go implementation of a StarkNet full node client made with ❤️ by Nethermind.
+
+`)
+
+	// Start juno
+	if err := newRootCmd().Execute(); err != nil {
+		fmt.Fprint(os.Stderr, err)
+		os.Exit(1)
+	}
+}
+
+func newRootCmd() *cobra.Command {
+	cfg := &config.Juno{}
+	var configFile string
+
+	rootCmd := &cobra.Command{
+		Use:   "juno [options]",
+		Short: "StarkNet client implementation in Go.",
+		PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
+			return loadConfig(cmd, &configFile)
+		},
+		Run: func(_ *cobra.Command, _ []string) {
+			juno(cfg)
+		},
+	}
+
+	// Commands
+	rootCmd.AddCommand(newConfigureCmd(cfg, &configFile))
+
+	// Flags
+	if configDir, err := os.UserConfigDir(); err != nil {
+		configFile = ""
+	} else {
+		configFile = filepath.Join(configDir, "juno", "juno.yaml")
+	}
+	rootCmd.PersistentFlags().StringVarP(&configFile, "config", "c", configFile, "the yaml configuration file.")
+
+	// Log
+	rootCmd.PersistentFlags().StringVar(&cfg.Log.Level, "log-level", "info", "verbosity of the logs. Options: debug, info, warn, error, dpanic, panic, fatal.")
+	rootCmd.PersistentFlags().BoolVar(&cfg.Log.Json, "log-json", false, "print logs in json format. Useful for automated processing. Typically omitted if logs are only viewed from console.")
+
+	// RPC
+	rootCmd.PersistentFlags().BoolVar(&cfg.Rpc.Enable, "rpc-enable", false, "enable the RPC server. Warning: this exposes the node to external requests and potentially DoS attacks.")
+	rootCmd.PersistentFlags().UintVar(&cfg.Rpc.Port, "rpc-port", 8080, "the port on which the RPC server will listen for requests.")
+
+	// Metrics
+	rootCmd.PersistentFlags().BoolVar(&cfg.Metrics.Enable, "metrics-enable", false, "enable the metrics server.")
+	rootCmd.PersistentFlags().UintVar(&cfg.Metrics.Port, "metrics-port", 9090, "the port on which the metrics server listens.")
+
+	// REST
+	rootCmd.PersistentFlags().BoolVar(&cfg.Rest.Enable, "rest-enable", false, "enable the REST server.")
+	rootCmd.PersistentFlags().UintVar(&cfg.Rest.Port, "rest-port", 8100, "the port on which the rest server will listen. Warning: this exposes the node to external requests and potentially DoS attacks.")
+	rootCmd.PersistentFlags().StringVar(&cfg.Rest.Prefix, "rest-prefix", "/feeder_gateway", "part of the the url endpoint for the REST server.")
+
+	// Database
+	cfg.Database.Path, _ = config.UserDataDir() // Empty string is a sensible default if there is an error
+	rootCmd.PersistentFlags().StringVar(&cfg.Database.Path, "database-path", cfg.Database.Path, "location of the database files.")
+
+	// StarkNet
+	rootCmd.PersistentFlags().BoolVar(&cfg.Sync.Enable, "starknet-enable", false, "if set, the node will synchronize with the StarkNet chain.")
+	rootCmd.PersistentFlags().StringVar(&cfg.Sync.Sequencer, "starknet-sequencer", "https://alpha-mainnet.starknet.io", "the sequencer endpoint. Useful for those wishing to cache feeder gateway responses in a proxy.")
+	rootCmd.PersistentFlags().StringVar(&cfg.Sync.Network, "starknet-network", "mainnet", "the StarkNet network with which to sync. Options: mainnet, goerli")
+	rootCmd.PersistentFlags().BoolVar(&cfg.Sync.Trusted, "starknet-trusted", false, "sync with the feeder gateway, not against L1. Only set if an Ethereum node is not available. The node provides no guarantees about the integrity of the state when syncing with the feeder gateway.")
+	rootCmd.PersistentFlags().StringVar(&cfg.Sync.EthNode, "starknet-ethnode", "", "the endpoint to the ethereum node. Required if one wants to maintain the state in a truly trustless way.")
+
+	return rootCmd
+}
+
+type keyToEnvReplacer struct{}
+
+// Replace is used to conviently convert from a viper key to an
+// environment variable. E.g. "database-path" --> "JUNO_DATABASE_PATH"
+//
+// It is public to fulfill the viper.EnvKeyReplacer interface.
+// Not meant for external use.
+func (r *keyToEnvReplacer) Replace(key string) string {
+	return "JUNO_" + strings.ToUpper(strings.ReplaceAll(key, "-", "_"))
+}
+
+// Loads the viper configuration and handles precedence in the following order:
+//
+// 1. CLI params
+// 2. Environment variables
+// 3. Config file
+// 4. Defaults
+//
+// Inspired by https://github.com/carolynvs/stingoftheviper/blob/e0d04fd2334bdf677a7f8825404a70e3c2c7e7d0/main.go
+func loadConfig(cmd *cobra.Command, configFile *string) error {
+	v := viper.NewWithOptions(viper.KeyDelimiter("-"), viper.EnvKeyReplacer(&keyToEnvReplacer{}))
+
+	// The `--config` flag is unique. We need to evaluate its
+	// argument/env var before loading the configuration file.
+	setFlagValue(cmd, v, cmd.Flag("config"))
+
+	v.AddConfigPath(filepath.Dir(*configFile))
+	v.SetConfigType("yaml")
+	v.SetConfigName(filepath.Base(*configFile))
+
+	if err := v.ReadInConfig(); err != nil {
+		if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
+			return err
+		}
+		fmt.Printf("config file not found at %s: falling back to CLI params, environment vars, and defaults\n", *configFile)
+	}
+
+	// Copy environment vars and config file values to the
+	// config struct.
+	cmd.Flags().VisitAll(func(f *pflag.Flag) { setFlagValue(cmd, v, f) })
+
+	return nil
+}
+
+// setFlagValue binds each cobra flag to its associated viper
+// configuration (config file and environment variable)
+func setFlagValue(cmd *cobra.Command, v *viper.Viper, f *pflag.Flag) {
+	// Bind flag to environment variable
+	v.BindEnv(f.Name)
+
+	// Apply the viper config value to the flag when the flag is not
+	// set and viper has a value
+	if !f.Changed && v.IsSet(f.Name) {
+		val := v.Get(f.Name)
+		cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val))
+	}
+}
+
+// juno is the main entrypoint for the Juno node.
+func juno(cfg *config.Juno) {
+	fmt.Printf("using config: %+v\n\n", cfg)
+
+	// Configure the logger first so we can use it
+	setupLogger(&cfg.Log)
+	setupInterruptHandler(cfg)
+	setupDatabase(&cfg.Database)
+	feederClient := setupFeederGateway(&cfg.Sync)
+	virtualMachine = cairovm.New(stateManager)
+
+	errChs := make([]chan error, 0)
+
+	if cfg.Sync.Enable {
+		errChs = append(errChs, make(chan error))
+		setupSynchronizer(&cfg.Sync, feederClient, errChs[len(errChs)-1])
+	} else {
+		// Currently, Juno is only useful for storing StarkNet
+		// state locally. We notify the user of this. We don't
+		// exit since some RPCs are useful on a stale database.
+		fmt.Println("StarkNet synchronization is disabled. To enable it, use the --starknet-enable flag.")
+	}
+	if cfg.Rpc.Enable {
+		errChs = append(errChs, make(chan error))
+		setupRpc(&cfg.Rpc, synchronizer, errChs[len(errChs)-1])
+	}
+	if cfg.Metrics.Enable {
+		errChs = append(errChs, make(chan error))
+		setupMetrics(&cfg.Metrics, errChs[len(errChs)-1])
+	}
+	if cfg.Rest.Enable {
+		errChs = append(errChs, make(chan error))
+		setupRest(&cfg.Rest, feederClient, errChs[len(errChs)-1])
+	}
+
+	// Wait until error
+	checkErrChs(errChs)
+}
+
+func setupVirtualMachine() {
+	virtualMachine = cairovm.New(stateManager)
+}
+
+func setupLogger(cfg *config.Log) {
+	if err := ReplaceGlobalLogger(cfg.Json, cfg.Level); err != nil {
+		fmt.Printf("failed to initialize logger: %s\n", err)
+		os.Exit(1)
+	}
+}
+
+func setupSynchronizer(cfg *config.Sync, feederClient *feeder.Client, errChan chan error) {
+	syncService.NewSynchronizer(cfg, feederClient, syncManager, stateManager, blockManager, transactionManager).Run(cfg.Trusted, errChan)
+}
+
+func setupRpc(cfg *config.Rpc, synchronizer *syncService.Synchronizer, errChan chan error) {
+	checkPort("JSON-RPC", cfg.Port)
+	starknetApi := starknet.New(stateManager, blockManager, transactionManager, synchronizer, virtualMachine)
+	jsonRpc := jsonrpc.NewJsonRpc()
+	handlers := []struct {
+		name       string
+		function   any
+		paramNames []string
+	}{
+		{"starknet_getBlockWithTxHashes", starknetApi.GetBlockWithTxHashes, []string{"block_id"}},
+		{"starknet_getBlockWithTxs", starknetApi.GetBlockWithTxs, []string{"block_id"}},
+		{"starknet_getStateUpdate", starknetApi.GetStateUpdate, []string{"block_id"}},
+		{"starknet_getStorageAt", starknetApi.GetStorageAt, []string{"block_id", "address", "key"}},
+		{"starknet_getTransactionByHash", starknetApi.GetTransactionByHash, []string{"transaction_hash"}},
+		{"starknet_getTransactionByBlockIdAndIndex", starknetApi.GetTransactionByBlockIdAndIndex, []string{"block_id", "index"}},
+		{"starknet_getTransactionReceipt", starknetApi.GetTransactionReceipt, []string{"transaction_hash"}},
+		{"starknet_getClass", starknetApi.GetClass, []string{"class_hash"}},
+		{"starknet_getClassHashAt", starknetApi.GetClassHashAt, []string{"block_id", "address"}},
+		{"starknet_getBlockTransactionCount", starknetApi.GetBlockTransactionCount, []string{"block_id"}},
+		{"starknet_call", starknetApi.Call, []string{"block_id", "request"}},
+		{"starknet_estimateFee", starknetApi.EstimateFee, []string{"block_id", "request"}},
+		{"starknet_blockNumber", starknetApi.BlockNumber, nil},
+		{"starknet_blockHashAndNumber", starknetApi.BlockHashAndNumber, nil},
+		{"starknet_chainId", starknetApi.ChainId, nil},
+		{"starkent_pendingTrnasactions", starknetApi.PendingTransactions, nil},
+		{"starknet_protocolVersion", starknetApi.ProtocolVersion, nil},
+		{"starknet_syncing", starknetApi.Syncing, nil},
+	}
+	for _, handler := range handlers {
+		if err := jsonRpc.RegisterFunc(handler.name, handler.function, handler.paramNames...); err != nil {
+			Logger.With("Error", err).Error("Failed to register RPC handler.")
+		}
+	}
+	rpcServer, err := rpc.NewHttpRpc(":"+strconv.FormatUint(uint64(cfg.Port), 10), "/rpc", jsonRpc)
+	if err != nil {
+		Logger.Fatal("Failed to initialise RPC Server", err)
+	}
+	rpcServer.ListenAndServe(errChan)
+}
+
+func setupMetrics(cfg *config.Metrics, errChan chan error) {
+	checkPort("Metrics", cfg.Port)
+	metricsServer = metric.SetupMetric(":" + strconv.FormatUint(uint64(cfg.Port), 10))
+	metricsServer.ListenAndServe(errChan)
+}
+
+func setupRest(cfg *config.Rest, feederClient *feeder.Client, errChan chan error) {
+	checkPort("API", cfg.Port)
+	restServer = rest.NewServer(":"+strconv.FormatUint(uint64(cfg.Port), 10), feederClient)
+	restServer.ListenAndServe(errChan)
+}
+
+func checkPort(server string, port uint) {
+	minPort, maxPort := uint(1_024), uint(49_151)
+	if port < minPort || port > maxPort {
+		Logger.Fatalf("%s port must be between %d and %d", server, minPort, maxPort)
+	}
+}
+
+func setupFeederGateway(cfg *config.Sync) *feeder.Client {
+	return feeder.NewClient(cfg.Sequencer, "/feeder_gateway", nil)
+}
+
+func setupDatabase(cfg *config.Database) {
+	var err error
+	var dbName string
+
+	mdbxEnv, err = db.NewMDBXEnv(cfg.Path, 100, 0)
+	if err != nil {
+		Logger.Fatal("Failed to create MDBX Database environment: ", err)
+	}
+
+	logDBErr := func(name string, err error) {
+		if err != nil {
+			Logger.Fatalf("Failed to create %s database: %s", name, err)
+		}
+	}
+
+	dbName = "SYNC"
+	syncDb, err := db.NewMDBXDatabase(mdbxEnv, dbName)
+	logDBErr(dbName, err)
+
+	dbName = "CONTRACT_DEF"
+	contractDefDb, err := db.NewMDBXDatabase(mdbxEnv, dbName)
+	logDBErr(dbName, err)
+
+	dbName = "STATE"
+	stateDb, err := db.NewMDBXDatabase(mdbxEnv, dbName)
+	logDBErr(dbName, err)
+
+	dbName = "TRANSACTION"
+	txDb, err := db.NewMDBXDatabase(mdbxEnv, dbName)
+	logDBErr(dbName, err)
+
+	dbName = "RECEIPT"
+	receiptDb, err := db.NewMDBXDatabase(mdbxEnv, dbName)
+	logDBErr(dbName, err)
+
+	dbName = "BLOCK"
+	blockDb, err := db.NewMDBXDatabase(mdbxEnv, dbName)
+	logDBErr(dbName, err)
+
+	syncManager = sync.NewManager(syncDb)
+	stateManager = state.NewManager(stateDb, contractDefDb)
+	transactionManager = transaction.NewManager(txDb, receiptDb)
+	blockManager = block.NewManager(blockDb)
+}
+
+func setupInterruptHandler(cfg *config.Juno) {
+	// Handle signal interrupts and exits.
+	sig := make(chan os.Signal, 1)
+	signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
+	go func(sig chan os.Signal) {
+		<-sig
+		Logger.Info("Shutting down Juno...")
+		shutdown(cfg)
+		os.Exit(0)
+	}(sig)
+}
+
+func shutdown(cfg *config.Juno) {
+	stateManager.Close()
+	transactionManager.Close()
+	blockManager.Close()
+	stateManager.Close()
+	syncManager.Close()
+	if cfg.Sync.Enable {
+		synchronizer.Close()
+	}
+
+	shutdownTimeout := 5 * time.Second
+
+	if cfg.Rpc.Enable {
+		if err := rpcServer.Close(shutdownTimeout); err != nil {
+			Logger.Fatal("Failed to shutdown RPC server gracefully: ", err)
+		}
+	}
+
+	if cfg.Metrics.Enable {
+		if err := metricsServer.Close(shutdownTimeout); err != nil {
+			Logger.Fatal("Failed to shutdown Metrics server gracefully: ", err)
+		}
+	}
+
+	if cfg.Rest.Enable {
+		if err := restServer.Close(shutdownTimeout); err != nil {
+			Logger.Fatal("Failed to shutdown REST server gracefully: ", err)
+		}
+	}
+}
+
+func checkErrChs(errChs []chan error) {
+	for _, errCh := range errChs {
+		for {
+			if err := <-errCh; err != nil {
+				Logger.Fatal(err)
+			}
+		}
+	}
 }
diff --git a/docs/docs/intro.md b/docs/docs/intro.md
index 33c6ada9..0a0d2c10 100644
--- a/docs/docs/intro.md
+++ b/docs/docs/intro.md
@@ -37,22 +37,30 @@ $ go get ./...
 Compile Juno:
 
 ```bash
-$ make compile
+$ make all
 ```
 
-After compilation, you will have 2 commands inside the `build` folder of the project:
+After compilation, you will have two executables inside the `build` folder:
 
 - juno
-    - `juno` is the command that initialize the node
+    - `juno` is the command that initializes the node.
 - juno-cli
-    - `juno-cli` is the command that handle a set of different commands about the StarkNet ecosystem.
+    - `juno-cli` is the command that direct interactions with the StarkNet ecosystem.
+
+To synchronize with the StarkNet state from the centralized feeder gateway, run the following command:
+
+```bash
+$ ./build/juno --starknet-enable --starknet-apisync
+```
+
+To sync the state without relying on the feeder gateway, you need to configure an Ethereum node and run the following command:
 
 ```bash
-$ make run
+$ ./build/juno --starknet-enable --starknet-ethnode <node-endpoint>
 ```
 
-For more details on the configuration, check the [config description](./running/config.mdx). 
+For more configuration details, check the [config description](https://gojuno.xyz/docs/running/config).
 
 ### Using Docker
 
-If you prefer to use docker, you can follow [this](./running/docker.mdx) guide.
\ No newline at end of file
+If you prefer to use docker, you can follow [this](https://gojuno.xyz/docs/running/docker) guide.
diff --git a/docs/docs/running/config.mdx b/docs/docs/running/config.mdx
index 81a21822..73ff20f5 100644
--- a/docs/docs/running/config.mdx
+++ b/docs/docs/running/config.mdx
@@ -1,94 +1,45 @@
 ---
-title: Config File
+title: Configuration File
 position: 2
 ---
 
-# Change config once created:
-
 To quickly edit the configuration file, you can use the following command:
 
 ```
 ./build/juno configure
 ```
 
-Which will open a Terminal User Interface displaying your current configuration values and allowing you to change them.
+This will open a Terminal User Interface displaying the current configuration and allowing you to edit them.
+Note that the command will overwrite your existing configuration with your changes.
+
+# juno.yaml
 
-# About juno.yaml
+Each of the configuration options has a corresponding CLI flag and environment variable.
+For example, the CLI flag for the database path is `--database-path` and the environment variable is `JUNO_DATABASE_PATH` (all environment variables are prefixed by `JUNO_` to avoid collisions).
 
-The config file for `Juno` represents all settings needed to handle all supported functionalities for 
-node execution.
+To see a description of the configuration options, run `./build/juno --help`.
 
-The basic structure is:
+Structure:
 ```yaml
-logger:
-  verbosity_level: "debug"
-  enable_json_output: true
-ethereum:
-  node: "you_node_here"
+log:
+  level: INFO
+  json: false
 rpc:
-  enabled: false
+  enable: false
   port: 8080
+metrics:
+  enable: false
+  port: 9090
 rest:
-  enabled: false
+  enable: false
   port: 8100
   prefix: /feeder_gateway
-metrics:
-  enabled: true
-  port: 8100
-db_path: /path/to/database
-starknet:
-  enabled: true
-  feeder_gateway: https://alpha-mainnet.starknet.io
-  api_sync: true
+database:
+  path: /path/to/database
+sync:
+  enable: false
+  sequencer: https://alpha-mainnet.starknet.io
   network: mainnet
+  trusted: false
+  ethnode: ""
 ```
-
-## Params
-
-### logger
-
-- `verbosity_level`: set the verbosity level of the node. Accepted values are an extension of zapcore.Level (debug, info, warn, error, dpanic, panic, fatal).
-- `enable_json_output`: enable the log output to be in JSON format.
-
-### ethereum
-
-Configuration of the Ethereum node.
-
-- `node`: put the address to an archive node, for example, an Infura archive node.
-
-### rpc
-
-Configuration of the RPC server.
-
-- `enabled`: Whether the RPC server is enabled.
-- `port`: The node in which the RPC server is going to be served.
-
-### rest
-
-Configuration for the REST server that handles all the feeder_gateway methods like http requests.
-
-- `enabled`: Whether the REST server is enabled.
-- `port`: Node in which the REST server is going to be served.
-- `prefix`: Prefix fot the service, it should be: `node_ip:port/feeder_gateway`.
-
-### metrics
-
-Configuration for Prometheus metrics.
-
-- `enabled`: Whether metrics are enabled.
-- `port`: The node in which metrics are going to be served.
-
-### db_path
-
-Represents the path in which the data of the node is going to be saved.
-
-### starknet
-
-Represent the configuration for the StarkNet network and sync details.
-
-- `enabled`: Represent if the REST server is enabled.
-- `feeder_gateway`: Represent the StarkNet endpoint that we are going to connect, should be from another `Juno` node or
-from the feeder gateway, that defines mainnet or goerli, if `enabled` starknet, always needed.
-- `api_sync`: To declare if synchronization is with the Feeder Gateway, or we are syncing against Layer 1.
-- `network`: Used in case you don't have an ethereum node and want to do an API sync. By default, `mainnet` is the
-value, anything else will be considered as goerli. Not needed if you aren't running an API sync.
diff --git a/docs/docs/running/useful-commands.mdx b/docs/docs/running/useful-commands.mdx
index 8eec9e54..561f6a63 100644
--- a/docs/docs/running/useful-commands.mdx
+++ b/docs/docs/running/useful-commands.mdx
@@ -9,10 +9,10 @@ Install dependencies:
 make install-deps
 ```
 
-In the `juno` directory, run:
+In the `juno` directory, run the following to compile Juno:
 
 ```bash
-make compile && make run
+make juno
 ```
 
 To run tests:
@@ -31,4 +31,4 @@ For a full list of options, use:
 
 ```bash
 ./build/juno -h
-```
\ No newline at end of file
+```
diff --git a/internal/cairovm/vm.go b/internal/cairovm/vm.go
index fd9d42c3..2ff177f9 100644
--- a/internal/cairovm/vm.go
+++ b/internal/cairovm/vm.go
@@ -13,7 +13,6 @@ import (
 
 	vmrpc2 "github.com/NethermindEth/juno/internal/cairovm/vmrpc"
 
-	"github.com/NethermindEth/juno/internal/config"
 	statedb "github.com/NethermindEth/juno/internal/db/state"
 	"github.com/NethermindEth/juno/pkg/felt"
 	"github.com/NethermindEth/juno/pkg/state"
@@ -93,11 +92,11 @@ func freePorts(n int) ([]int, error) {
 	return ports, nil
 }
 
-func (s *VirtualMachine) Run() error {
+func (s *VirtualMachine) Run(dataDir string) error {
 	s.rpcServer = grpc.NewServer()
 
 	// Generate the Python environment in the data dir.
-	s.vmDir = config.DataDir
+	s.vmDir = dataDir
 
 	files := [...]struct {
 		name     string
diff --git a/internal/config/config.go b/internal/config/config.go
index 3ce1fb5a..390a0aa8 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -4,175 +4,100 @@ package config
 
 import (
 	"errors"
+	"fmt"
 	"os"
 	"path/filepath"
 	"runtime"
-
-	. "github.com/NethermindEth/juno/internal/log"
-	"gopkg.in/yaml.v2"
 )
 
-// loggerConfig represents the logger configuration
-type loggerConfig struct {
-	VerbosityLevel   string `yaml:"verbosity_level" mapstructure:"verbosity_level"`
-	EnableJsonOutput bool   `yaml:"enable_json_output" mapstructure:"enable_json_output"`
+// Log represents the logger configuration
+type Log struct {
+	Level string `yaml:"level" mapstructure:"level"`
+	Json  bool   `yaml:"json" mapstructure:"json"`
 }
 
-// rpcConfig represents the juno RPC configuration.
-type rpcConfig struct {
-	Enabled bool `yaml:"enabled" mapstructure:"enabled"`
-	Port    int  `yaml:"port" mapstructure:"port"`
+// Rpc represents the juno RPC configuration.
+type Rpc struct {
+	Enable bool `yaml:"enable" mapstructure:"enable"`
+	Port   uint `yaml:"port" mapstructure:"port"`
 }
 
-// metricsConfig represents the Prometheus Metrics configuration.
-type metricsConfig struct {
-	Enabled bool `yaml:"enabled" mapstructure:"enabled"`
-	Port    int  `yaml:"port" mapstructure:"port"`
+// Metrics represents the Prometheus Metrics configuration.
+type Metrics struct {
+	Enable bool `yaml:"enable" mapstructure:"enable"`
+	Port   uint `yaml:"port" mapstructure:"port"`
 }
 
-// ethereumConfig represents the juno Ethereum configuration.
-type ethereumConfig struct {
-	Node string `yaml:"node" mapstructure:"node"`
+// Rest represents the juno REST configuration.
+type Rest struct {
+	Enable bool   `yaml:"enable" mapstructure:"enable"`
+	Port   uint   `yaml:"port" mapstructure:"port"`
+	Prefix string `yaml:"prefix" mapstructure:"prefix"`
 }
 
-// restConfig represents the juno REST configuration.
-type restConfig struct {
-	Enabled bool   `yaml:"enabled" mapstructure:"enabled"`
-	Port    int    `yaml:"port" mapstructure:"port"`
-	Prefix  string `yaml:"prefix" mapstructure:"prefix"`
+// Database represents the juno database configuration.
+type Database struct {
+	Path string `yaml:"path" mapstructure:"path"`
 }
 
-// starknetConfig represents the juno StarkNet configuration.
-type starknetConfig struct {
-	Enabled       bool   `yaml:"enabled" mapstructure:"enabled"`
-	FeederGateway string `yaml:"feeder_gateway" mapstructure:"feeder_gateway"`
-	Network       string `yaml:"network" mapstructure:"network"`
-	ApiSync       bool   `yaml:"api_sync" mapstructure:"api_sync"`
+// Sync represents the juno StarkNet configuration.
+type Sync struct {
+	Enable    bool   `yaml:"enable" mapstructure:"enable"`
+	Sequencer string `yaml:"sequencer" mapstructure:"sequencer"`
+	Network   string `yaml:"network" mapstructure:"network"`
+	Trusted   bool   `yaml:"trusted" mapstructure:"trusted"`
+	EthNode   string `yaml:"ethnode" mapstructure:"ethnode"`
 }
 
-// Config represents the juno configuration.
-type Config struct {
-	Logger   loggerConfig   `yaml:"logger" mapstructure:"logger"`
-	Ethereum ethereumConfig `yaml:"ethereum" mapstructure:"ethereum"`
-	RPC      rpcConfig      `yaml:"rpc" mapstructure:"rpc"`
-	Metrics  metricsConfig  `yaml:"metrics" mapstructure:"metrics"`
-	REST     restConfig     `yaml:"rest" mapstructure:"rest"`
-	DbPath   string         `yaml:"db_path" mapstructure:"db_path"`
-	Starknet starknetConfig `yaml:"starknet" mapstructure:"starknet"`
+// Juno is the top-level juno configuration.
+type Juno struct {
+	Log      Log      `yaml:"log" mapstructure:"log"`
+	Rpc      Rpc      `yaml:"rpc" mapstructure:"rpc"`
+	Metrics  Metrics  `yaml:"metrics" mapstructure:"metrics"`
+	Rest     Rest     `yaml:"rest" mapstructure:"rest"`
+	Database Database `yaml:"database" mapstructure:"database"`
+	Sync     Sync     `yaml:"sync" mapstructure:"sync"`
 }
 
-var (
-	// Dir is the default root directory for user-specific
-	// configuration data.
-	//
-	// On Darwin this is $HOME/Library/Application Support/juno/, on other
-	// Unix systems $XDG_CONFIG_HOME/juno/, and on Windows,
-	// %APPDATA%/juno.
-	Dir string
-	// DataDir is the is the default root directory for user-specific
-	// application data.
-	//
-	// On Unix this is $XDG_DATA_HOME/juno/ and on Windows,
-	// %APPDATA%/juno/.
-	DataDir string
-)
-
-// Runtime is the runtime configuration of the application.
-var Runtime *Config
-
-func init() {
-	// Set user config directory.
+// UserDataDir finds the user's default data directory, returning the
+// empty string and an error otherwise.
+//
+// It is analagous to os.UserConfigDir.
+func UserDataDir() (string, error) {
 	// notest
-	d, err := os.UserConfigDir()
-	if err != nil {
-		Logger.Fatal("Could not get config directory.")
-	}
-	Dir = filepath.Join(d, "juno")
+	const junoDir = "juno"
 
-	// Set user data directory.
 	// notest
-	DataDir, err = func() (string, error) {
-		// notest
-		switch runtime.GOOS {
-		case "windows":
-			// On Windows ConfigDir and DataDir share the same path. See:
-			// https://stackoverflow.com/questions/43853548/xdg-basedir-directories-for-windows.
-			return Dir, nil
-		case "darwin", "dragonfly", "freebsd", "illumos", "ios", "linux", "netbsd",
-			"openbsd", "solaris":
-			// Use XDG_DATA_HOME. If it is not configured, try $HOME/.local/share
-			// See https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
-			dataHome := os.Getenv("XDG_DATA_HOME")
-			if dataHome == "" {
-				home := os.Getenv("HOME")
-				if home == "" {
-					return "", errors.New("user home directory not found")
-				}
-				result := filepath.Join(home, ".local", "share", "juno")
-				// Create Juno data directory if it does not exist
-				if _, err := os.Stat(result); errors.Is(err, os.ErrNotExist) {
-					err = os.MkdirAll(result, 0o744)
-					if err != nil {
-						Logger.Fatal("Could not create user data directory.")
-					}
-				}
-				return result, nil
-			}
-			return filepath.Join(dataHome, "juno"), nil
-		default: // js/wasm, plan9
-			return "", errors.New("user data directory not found")
-		}
-	}()
-	// If there is an error
-	if err != nil {
-		Logger.Fatal("Could not get user data directory.")
-	}
-}
-
-// New creates a new configuration file with default values.
-func New() {
-	f := filepath.Join(Dir, "juno.yaml")
-	Logger.With("Path", f).Info("Creating default config.")
-	// Create the juno configuration directory if it does not exist.
-	if _, err := os.Stat(Dir); os.IsNotExist(err) {
-		// notest
-		err := os.MkdirAll(Dir, 0o755)
+	switch runtime.GOOS {
+	case "windows":
+		// On Windows ConfigDir and DataDir share the same path. See:
+		// https://stackoverflow.com/questions/43853548/xdg-basedir-directories-for-windows.
+		configDir, err := os.UserConfigDir()
 		if err != nil {
-			Logger.Fatal("Could not create config directory.")
+			return "", fmt.Errorf("user data directory not found: %w", err)
 		}
-	}
-	// notest
-	data, err := yaml.Marshal(&Config{
-		Logger: loggerConfig{
-			VerbosityLevel:   "debug",
-			EnableJsonOutput: false,
-		},
-		Ethereum: ethereumConfig{Node: ""},
-		RPC:      rpcConfig{Enabled: true, Port: 8080},
-		Metrics:  metricsConfig{Enabled: true, Port: 2048},
-		DbPath:   DataDir,
-		REST:     restConfig{Enabled: true, Port: 8100, Prefix: "/feeder_gateway"},
-		Starknet: starknetConfig{
-			Enabled: true, ApiSync: true, FeederGateway: "https://alpha-mainnet.starknet.io",
-			Network: "mainnet",
-		},
-	})
-	if err != nil {
-		Logger.Fatal("Could not marshal default config.")
-	}
-	// Create default Juno configuration file if it does not exist
-	if _, err := os.Stat(f); errors.Is(err, os.ErrNotExist) {
-		// notest
-		err = os.WriteFile(f, data, 0o644)
-		if err != nil {
-			Logger.Fatal("Could not create default config file.")
+		return filepath.Join(configDir, junoDir), nil
+	case "darwin", "dragonfly", "freebsd", "illumos", "ios", "linux", "netbsd",
+		"openbsd", "solaris":
+		// Use XDG_DATA_HOME. If it is not configured, try $HOME/.local/share
+		// See https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
+		dataHome := os.Getenv("XDG_DATA_HOME")
+		if dataHome == "" {
+			home := os.Getenv("HOME")
+			if home == "" {
+				return "", errors.New("could not find database directory: home directory not found")
+			}
+			result := filepath.Join(home, ".local", "share", junoDir)
+			// Create Juno data directory if it does not exist
+			if _, err := os.Stat(result); err != nil {
+				if err := os.Mkdir(result, os.ModePerm); err != nil {
+					return "", fmt.Errorf("could not create data directory %s: %w", result, err)
+				}
+			}
+			return result, nil
 		}
+		return filepath.Join(dataHome, junoDir), nil
+	default: // js/wasm, plan9
+		return "", errors.New("user data directory not found")
 	}
 }
-
-// Exists checks if the default configuration file already exists
-func Exists() bool {
-	f := filepath.Join(Dir, "juno.yaml")
-	_, err := os.Stat(f)
-	return err == nil
-}
diff --git a/internal/sync/sync.go b/internal/sync/sync.go
index 9438b82e..b608909f 100644
--- a/internal/sync/sync.go
+++ b/internal/sync/sync.go
@@ -65,14 +65,14 @@ type Synchronizer struct {
 
 // NewSynchronizer creates a new Synchronizer.
 // notest
-func NewSynchronizer(feederClient *feeder.Client, syncManager *sync.Manager,
+func NewSynchronizer(cfg *config.Sync, feederClient *feeder.Client, syncManager *sync.Manager,
 	stateManager state.StateManager, blockManager *blockDB.Manager, transactionManager *transaction.Manager,
 ) *Synchronizer {
 	synchro := new(Synchronizer)
 	synchro.logger = Logger.Named("Sync Service")
 	synchro.feeder = feederClient
-	if !config.Runtime.Starknet.ApiSync {
-		ethereumClient, err := ethclient.Dial(config.Runtime.Ethereum.Node)
+	if !cfg.Trusted {
+		ethereumClient, err := ethclient.Dial(cfg.EthNode)
 		if err != nil {
 			synchro.logger.Fatal("Unable to connect to Ethereum Client", err)
 		}
@@ -86,15 +86,15 @@ func NewSynchronizer(feederClient *feeder.Client, syncManager *sync.Manager,
 
 	synchro.Running = false
 
-	synchro.setChainId()
+	synchro.setChainId(cfg.Network)
 	synchro.setStateToLatestRoot()
-	synchro.setStateDiffCollector()
+	synchro.setStateDiffCollector(cfg.Trusted)
 	return synchro
 }
 
 // setStateDiffCollector sets the stateDiffCollector.
-func (s *Synchronizer) setStateDiffCollector() {
-	if config.Runtime.Starknet.ApiSync {
+func (s *Synchronizer) setStateDiffCollector(apiSync bool) {
+	if apiSync {
 		s.stateDiffCollector = NewApiCollector(s.syncManager, s.feeder)
 	} else {
 		s.stateDiffCollector = NewL1Collector(s.syncManager, s.feeder, s.l1Client,
@@ -103,22 +103,23 @@ func (s *Synchronizer) setStateDiffCollector() {
 }
 
 // Run starts the service.
-func (s *Synchronizer) Run() {
+func (s *Synchronizer) Run(apiSync bool, errChan chan error) {
 	s.Running = true
 	go s.updateBlocksInfo()
-	go s.handleSync()
+	go s.handleSync(apiSync, errChan)
 }
 
-func (s *Synchronizer) handleSync() {
+func (s *Synchronizer) handleSync(apiSync bool, errChan chan error) {
 	for {
 		err := s.sync()
 		if err == nil {
+			errChan <- err
 			return
 		}
 		s.logger.With("Error", err).Info("Sync Failed, restarting iterator in 10 seconds")
 		time.Sleep(10 * time.Second)
 		s.stateDiffCollector.Close()
-		s.setStateDiffCollector()
+		s.setStateDiffCollector(apiSync)
 	}
 }
 
@@ -269,10 +270,10 @@ func (s *Synchronizer) Close() {
 	s.stateDiffCollector.Close()
 }
 
-func (s *Synchronizer) setChainId() {
+func (s *Synchronizer) setChainId(network string) {
 	if s.l1Client == nil {
 		// notest
-		if config.Runtime.Starknet.Network == "mainnet" {
+		if network == "mainnet" {
 			s.chainId = new(big.Int).SetInt64(1)
 		} else {
 			s.chainId = new(big.Int).SetInt64(0)
diff --git a/pkg/rest/handler.go b/pkg/rest/handler.go
index f4dad4e4..1b970d4c 100644
--- a/pkg/rest/handler.go
+++ b/pkg/rest/handler.go
@@ -10,8 +10,9 @@ import (
 var rest_handler RestHandler
 
 // NewServer creates a REST API server with the listed endpoints
-func NewServer(rest_port string, feeder_gateway string, prefix string) *Server {
-	rest_handler.RestFeeder = feeder.NewClient(feeder_gateway, "/feeder_gateway", nil)
+func NewServer(rest_port string, feederClient *feeder.Client) *Server {
+	rest_handler.RestFeeder = feederClient
+	prefix := feederClient.BaseAPI
 	m := http.NewServeMux()
 
 	m.HandleFunc(prefix+"/get_block", rest_handler.GetBlock)


Test patch:
diff --git a/internal/cairovm/vm_test.go b/internal/cairovm/vm_test.go
index aa24e099..246e96e2 100644
--- a/internal/cairovm/vm_test.go
+++ b/internal/cairovm/vm_test.go
@@ -43,7 +43,7 @@ func TestVMCall(t *testing.T) {
 	}
 	vm := New(statedb.NewManager(stateDb, contractDefDb))
 
-	if err := vm.Run(); err != nil {
+	if err := vm.Run(t.TempDir()); err != nil {
 		t.Errorf("unexpected error starting the service: %s", err)
 	}
 	defer vm.Close(context.Background())
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
deleted file mode 100644
index 67973a24..00000000
--- a/internal/config/config_test.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package config
-
-import (
-	"os"
-	"path/filepath"
-	"testing"
-
-	"gopkg.in/yaml.v2"
-)
-
-func TestConfig(t *testing.T) {
-	New()
-	f, err := os.ReadFile(filepath.Join(Dir, "juno.yaml"))
-	if err != nil {
-		t.Fatal(err)
-	}
-	var cfg *Config
-	err = yaml.Unmarshal(f, &cfg)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if !Exists() {
-		t.Fatal("default config file must exist.")
-	}
-}
diff --git a/pkg/rest/tests/rest_test.go b/pkg/rest/tests/rest_test.go
index 3340ba34..378e4df3 100644
--- a/pkg/rest/tests/rest_test.go
+++ b/pkg/rest/tests/rest_test.go
@@ -74,7 +74,8 @@ func StructFaker(a interface{}) (string, error) {
 
 // TestRestClient
 func TestRestClient(t *testing.T) {
-	r := rest.NewServer(":8100", "http://localhost/", "feeder_gateway")
+	feederClient := feeder.NewClient("http://localhost/", "feeder_gateway", nil)
+	r := rest.NewServer(":8100", feederClient)
 	errCh := make(chan error)
 
 	r.ListenAndServe(errCh)