Skip to content

Commit

Permalink
More dev work (#5)
Browse files Browse the repository at this point in the history
* extract restake

* fix linter

* pretty consistent restaking

* structured logging

* more files

* lint

* work work

* bump pickaxe

* remove some debug checks

* commit

* fix evm gas

* more work
  • Loading branch information
keefertaylor authored Oct 24, 2023
1 parent 63e571a commit a1c72dd
Show file tree
Hide file tree
Showing 21 changed files with 602 additions and 356 deletions.
72 changes: 25 additions & 47 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,73 +2,51 @@

DOCKER := $(shell which docker)

CURRENT_DIR = $(shell pwd)
BUILDDIR ?= $(CURDIR)/build

BUILD_FLAGS := -tags "$(build_tags)" -ldflags '$(ldflags)'
# check for nostrip option
ifeq (,$(findstring nostrip,$(COSMOS_BUILD_OPTIONS)))
BUILD_FLAGS += -trimpath
endif
BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
COMMIT := $(shell git log -1 --format='%H')

# Check for debug option
ifeq (debug,$(findstring debug,$(COSMOS_BUILD_OPTIONS)))
BUILD_FLAGS += -gcflags "all=-N -l"
DIRTY := -dirty
ifeq (,$(shell git status --porcelain))
DIRTY :=
endif

###############################################################################
### Protobuf ###
###############################################################################

protoImageName=proto-genc
protoImage=$(DOCKER) run --rm -v $(CURDIR):/workspace --workdir /workspace/proto $(protoImageName)

proto-all: proto-build-docker proto-format proto-lint make proto-format proto-update-deps proto-gen

proto-build-docker:
@echo "Building Docker Container '$(protoImageName)' for Protobuf Compilation"
@docker build -t $(protoImageName) -f ./proto/Dockerfile .

proto-gen:
@echo "Generating Protobuf Files"
@$(protoImage) sh -c "cd .. && sh ./scripts/protocgen.sh"
VERSION := $(shell git describe --tags --exact-match 2>/dev/null)
# if VERSION is empty, then populate it with branch's name and raw commit hash
ifeq (,$(VERSION))
VERSION := $(BRANCH)-$(COMMIT)
endif

proto-format:
@echo "Formatting Protobuf Files with Clang"
@$(protoImage) find ./ -name "*.proto" -exec clang-format -i {} \;
VERSION := $(VERSION)$(DIRTY)

proto-lint:
@echo "Linting Protobuf Files With Buf"
@$(protoImage) buf lint
GIT_REVISION := $(shell git rev-parse HEAD)$(DIRTY)

proto-check-breaking:
@$(protoImage) buf breaking --against $(HTTPS_GIT)#branch=main
GO_SYSTEM_VERSION = $(shell go version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f1-2)

proto-update-deps:
@echo "Updating Protobuf dependencies"
@$(protoImage) buf mod update
ldflags= -X github.com/tessellated-io/restake-go/cmd/restake-go/cmd.RestakeVersion=${VERSION} \
-X github.com/tessellated-io/restake-go/cmd/restake-go/cmd.GitRevision=${GIT_REVISION} \
-X github.com/tessellated-io/restake-go/cmd/restake-go/cmd.GoVersion=${GO_SYSTEM_VERSION}

.PHONY: proto-all proto-gen proto-format proto-lint proto-check-breaking proto-update-deps
BUILD_FLAGS := -tags "$(build_tags)" -ldflags '$(ldflags)'

###############################################################################
### Build ###
###############################################################################

BUILD_TARGETS := build

build: BUILD_ARGS=

build-linux-amd64:
@GOOS=linux GOARCH=amd64 LEDGER_ENABLED=false $(MAKE) build
build:
mkdir -p $(BUILDDIR)/
go build -mod=readonly -ldflags '$(ldflags)' -trimpath -o $(BUILDDIR) ./...;

build-linux-arm64:
@GOOS=linux GOARCH=arm64 LEDGER_ENABLED=false $(MAKE) build
install: go.sum
go install $(BUILD_FLAGS) ./cmd/restake-go

$(BUILD_TARGETS): go.sum $(BUILDDIR)/
@cd ${CURRENT_DIR} && go $@ -mod=readonly $(BUILD_FLAGS) $(BUILD_ARGS) ./...
clean:
rm -rf $(BUILDDIR)/*

$(BUILDDIR)/:
@mkdir -p $(BUILDDIR)/
.PHONY: build

###############################################################################
### Tools & Dependencies ###
Expand Down
51 changes: 49 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,54 @@ Restake-Go is an implementation of the Restake Daemon in native golang. Restake-

Restake-Go is provided at a beta-level. Tessellated uses this software in production, and you may too, but we make no warranties or guarantees.

## Installation

Installing `restake-go` is easy. Simply clone the repository and run `make`. You'll need `go`, `git`, `make` and probably some other standard dev tools you already have installed.

```shell
# Get restake-go
$ git clone https://github.com/tessellated-io/restake-go/

# Install restake-go
$ cd restake-go
$ make install

# Find out what restake-go can do.
$ restake-go --help
```

## Quick Start

// TODO: Sample config
Copy the sample config:
```shell
make install
restake-go --help
$ mkdir ~/.restake
$ cp config.sample.yml ~/.restake/config.yml
```

Fill out the config, then run `restake-go`:

```shell
$ restake-go start
```

## Features

Restake-go offers a number of features over the restake go.

TODO: Fill this out
- automatic discover
- retry
- parallel execution
- tx inclusion polling
- gas normalization / auto discover
- ignores
- auto incrementing features

As well as the features you know and love
- healthchecks


## Configuration

TODO
21 changes: 11 additions & 10 deletions TODO
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
- Periodically reconnect the clients?\
- Context with timeout
GAS
- health is only off by one

- make makefile work
- default gas price increase
- pull blocks
- what is with too many pings
- consider grant sizes or limits and query them correctly
DOCS
- header + credits (ascii art)
- README + branding
- Add memo for restake go

- print out what the gas fee is
GENERAL
- min restake amount in mine
- Reap panics
- configure restake time
- Fix default config parsing
- Rip out sleep
- healthchecks only on success pings or obvious failures?

- fix package in go.mod
- move towards debug log statements

31 changes: 31 additions & 0 deletions cmd/restake-go/cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
Copyright © 2023 Tessellated <tessellated.io>
*/
package cmd

import (
"os"

"github.com/spf13/cobra"
)

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "restake",
Short: "Restake-Go implements the Restake protocol.",
Long: `Restake-Go is an alternative implementation of the Restake protocol by Tessellated.
See also: https://github.com/eco-stake/restake.`,
}

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}

func init() {
}
176 changes: 176 additions & 0 deletions cmd/restake-go/cmd/start.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
Copyright © 2023 Tessellated <tessellated.io>
*/
package cmd

import (
"context"
"fmt"
"os/user"
"sort"
"strings"
"sync"
"time"

os2 "github.com/cometbft/cometbft/libs/os"
"github.com/spf13/cobra"
"github.com/tessellated-io/restake-go/codec"
"github.com/tessellated-io/restake-go/config"
"github.com/tessellated-io/restake-go/health"
"github.com/tessellated-io/restake-go/log"
"github.com/tessellated-io/restake-go/restake"
"github.com/tessellated-io/restake-go/rpc"
)

var (
configFile string
gasMultiplier float64
)

type RestakeResult struct {
network string
txHash string
err error
}

type RestakeResults []*RestakeResult

func (rr RestakeResults) Len() int { return len(rr) }
func (rr RestakeResults) Swap(i, j int) { rr[i], rr[j] = rr[j], rr[i] }
func (rr RestakeResults) Less(i, j int) bool { return rr[i].network < rr[j].network }

// startCmd represents the start command
var startCmd = &cobra.Command{
Use: "start",
Short: "Start the Restake Service",
Long: `Starts the Restake Service with the given configuration.`,
Run: func(cmd *cobra.Command, args []string) {
ctx := cmd.Context()

fmt.Println()
fmt.Println("============================================================")
fmt.Println("Go Go... Restake-Go!")
fmt.Println()
fmt.Println("A Product Of Tessellated / tessellated.io")
fmt.Println("============================================================")
fmt.Println("")

// Configure a logger
log := log.NewLogger()

// Load config
expandedConfigFile := expandHomeDir(configFile)
configOk := os2.FileExists(expandedConfigFile)
if !configOk {
panic(fmt.Sprintf("Failed to load config file at: %s", configFile))
}
log.Info().Str("config file", expandedConfigFile).Msg("Loading config from file")

// Parse config
config, err := config.GetRestakeConfig(ctx, expandedConfigFile, log)
if err != nil {
panic(err)
}

cdc := codec.GetCodec()

// Make restake clients
restakeManagers := []*restake.RestakeManager{}
healthClients := []*health.HealthCheckClient{}
for _, chain := range config.Chains {
prefixedLogger := log.ApplyPrefix(fmt.Sprintf(" [%s]", chain.Network))

rpcClient, err := rpc.NewRpcClient(chain.NodeGrpcURI, cdc, prefixedLogger)
if err != nil {
panic(err)
}

healthcheckId := chain.HealthcheckId
if healthcheckId == "" {
panic(fmt.Sprintf("No health check id found for network %s", chain.Network))
}
healthClient := health.NewHealthCheckClient(chain.Network, healthcheckId, prefixedLogger)
healthClients = append(healthClients, healthClient)

restakeManager, err := restake.NewRestakeManager(rpcClient, cdc, config.Mnemonic, config.Memo, gasMultiplier, *chain, prefixedLogger)
if err != nil {
panic(err)
}
restakeManagers = append(restakeManagers, restakeManager)
}

runInterval := time.Duration(config.RunIntervalHours) * time.Hour
for {
var wg sync.WaitGroup
var results RestakeResults = []*RestakeResult{}

for idx, restakeManager := range restakeManagers {
wg.Add(1)

go func(ctx context.Context, restakeManager *restake.RestakeManager, healthClient *health.HealthCheckClient) {
defer wg.Done()

timeoutContext, cancelFunc := context.WithTimeout(ctx, runInterval)
defer cancelFunc()

_ = healthClient.Start()
txHash, err := restakeManager.Restake(timeoutContext)
if err != nil {
_ = healthClient.Failed(err)
} else {
_ = healthClient.Success("Hooray!")
}

result := &RestakeResult{
network: restakeManager.Network(),
txHash: txHash,
err: err,
}
results = append(results, result)
}(ctx, restakeManager, healthClients[idx])
}

// Print results whenever they all finish
go func() {
wg.Wait()
printResults(results, log)
}()

log.Info().Dur("next run in hours", runInterval).Msg("Finished restaking. Sleeping until next round")
time.Sleep(runInterval)
}
},
}

func init() {
rootCmd.AddCommand(startCmd)

startCmd.Flags().StringVarP(&configFile, "config-file", "c", "~/.restake/config.yml", "A path to the configuration file")
startCmd.Flags().Float64VarP(&gasMultiplier, "gas-multiplier", "g", 1.2, "The multiplier to use for gas")
}

// TODO: Move to pickaxe here
func expandHomeDir(path string) string {
if !strings.HasPrefix(path, "~") {
return path
}

usr, err := user.Current()
if err != nil {
panic(fmt.Errorf("failed to get user's home directory: %v", err))
}
return strings.Replace(path, "~", usr.HomeDir, 1)
}

func printResults(results RestakeResults, log *log.Logger) {
sort.Sort(results)

log.Info().Msg("Restake Results:")
for _, result := range results {
if result.err == nil {
log.Info().Str("tx_hash", result.txHash).Msg(fmt.Sprintf("✅ %s: Success", result.network))
} else {
log.Error().Err(result.err).Msg(fmt.Sprintf("❌ %s: Failure", result.network))
}
}
}
Loading

0 comments on commit a1c72dd

Please sign in to comment.