Skip to content

Commit e089273

Browse files
authored
Merge pull request #176 from hack-a-chain-software/improvements-2
General improvements
2 parents 8a94811 + 89f93b1 commit e089273

File tree

86 files changed

+10249
-724
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+10249
-724
lines changed

backfill/Dockerfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM golang:1.23.3 AS builder
2+
WORKDIR /app
3+
COPY . .
4+
RUN go mod download
5+
RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o main .
6+
7+
FROM scratch
8+
WORKDIR /app
9+
COPY ./global-bundle.pem ./global-bundle.pem
10+
COPY --from=builder /app/main .
11+
CMD ["./main"]

backfill/Dockerfile.indexes

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM golang:1.23.3 AS builder
2+
WORKDIR /app
3+
COPY . .
4+
RUN go mod download
5+
RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o recreate-indexes ./recreate-indexes/recreate-indexes.go
6+
7+
FROM scratch
8+
WORKDIR /app
9+
COPY ./global-bundle.pem ./global-bundle.pem
10+
COPY --from=builder /app/recreate-indexes .
11+
CMD ["./recreate-indexes"]
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM golang:1.23.3 AS builder
2+
WORKDIR /app
3+
COPY . .
4+
RUN go mod download
5+
RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o middle-backfill ./middle-backfill/middle-backfill.go
6+
7+
FROM scratch
8+
WORKDIR /app
9+
COPY ./global-bundle.pem ./global-bundle.pem
10+
COPY --from=builder /app/middle-backfill .
11+
CMD ["./middle-backfill"]

backfill/config/db.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package config
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"time"
8+
9+
"github.com/jackc/pgx/v5/pgxpool"
10+
)
11+
12+
func InitDatabase() *pgxpool.Pool {
13+
env := GetConfig()
14+
connStr := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
15+
env.DbHost, env.DbPort, env.DbUser, env.DbPassword, env.DbName)
16+
17+
config, err := pgxpool.ParseConfig(connStr)
18+
if err != nil {
19+
log.Fatalf("Unable to parse connection string: %v\n", err)
20+
}
21+
22+
config.MaxConns = 2 // Maximum number of connections
23+
config.MinConns = 1 // Minimum number of connections to keep alive
24+
config.MaxConnLifetime = 1 * time.Hour // Close and refresh connections after 1 hour
25+
config.HealthCheckPeriod = 1 * time.Minute // Check connection health every minute
26+
27+
// Create the pool
28+
pool, err := pgxpool.NewWithConfig(context.Background(), config)
29+
if err != nil {
30+
log.Fatalf("Unable to connect to database: %v\n", err)
31+
}
32+
33+
return pool
34+
}

backfill/config/env.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package config
2+
3+
import (
4+
"log"
5+
"os"
6+
"strconv"
7+
8+
"github.com/joho/godotenv"
9+
)
10+
11+
type Config struct {
12+
DbUser string
13+
DbPassword string
14+
DbName string
15+
DbHost string
16+
DbPort string
17+
CertPath string
18+
Network string
19+
ChainId int
20+
SyncBaseUrl string
21+
SyncMinHeight int
22+
SyncFetchIntervalInBlocks int
23+
SyncAttemptsMaxRetry int
24+
SyncAttemptsIntervalInMs int
25+
IsDevelopment bool
26+
}
27+
28+
var config *Config
29+
30+
func InitEnv(envFilePath string) {
31+
IsDevelopment := true
32+
if err := godotenv.Load(envFilePath); err != nil {
33+
IsDevelopment = false
34+
log.Printf("No .env file found at %s, falling back to system environment variables", envFilePath)
35+
}
36+
37+
config = &Config{
38+
DbUser: getEnv("DB_USER"),
39+
DbPassword: getEnv("DB_PASSWORD"),
40+
DbName: getEnv("DB_NAME"),
41+
DbHost: getEnv("DB_HOST"),
42+
DbPort: getEnv("DB_PORT"),
43+
CertPath: getEnv("CERT_PATH"),
44+
Network: getEnv("NETWORK"),
45+
ChainId: getEnvAsInt("CHAIN_ID"),
46+
SyncBaseUrl: getEnv("SYNC_BASE_URL"),
47+
SyncMinHeight: getEnvAsInt("SYNC_MIN_HEIGHT"),
48+
SyncFetchIntervalInBlocks: getEnvAsInt("SYNC_FETCH_INTERVAL_IN_BLOCKS"),
49+
SyncAttemptsMaxRetry: getEnvAsInt("SYNC_ATTEMPTS_MAX_RETRY"),
50+
SyncAttemptsIntervalInMs: getEnvAsInt("SYNC_ATTEMPTS_INTERVAL_IN_MS"),
51+
IsDevelopment: IsDevelopment,
52+
}
53+
}
54+
55+
func GetConfig() *Config {
56+
if config == nil {
57+
log.Fatal("Config not initialized. Call InitEnv first.")
58+
}
59+
return config
60+
}
61+
62+
func getEnv(key string) string {
63+
value := os.Getenv(key)
64+
if value == "" {
65+
log.Fatalf("Environment variable %s is required but not set", key)
66+
}
67+
return value
68+
}
69+
70+
func getEnvAsInt(key string) int {
71+
valueStr := getEnv(key)
72+
value, err := strconv.Atoi(valueStr)
73+
if err != nil {
74+
log.Fatalf("Environment variable %s must be an integer, but got: %s", key, valueStr)
75+
}
76+
return value
77+
}

backfill/config/memory-monitor.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package config
2+
3+
import (
4+
"log"
5+
"runtime"
6+
"time"
7+
)
8+
9+
func StartMemoryMonitoring() {
10+
var memStats runtime.MemStats
11+
for {
12+
runtime.ReadMemStats(&memStats)
13+
14+
log.Printf(
15+
"Alloc: %v KB, Sys: %v KB, HeapIdle: %v KB, HeapInuse: %v KB, NumGC: %v\n",
16+
memStats.Alloc/1024, // Total allocated memory
17+
memStats.Sys/1024, // Total system memory requested
18+
memStats.HeapIdle/1024, // Idle heap memory
19+
memStats.HeapInuse/1024, // In-use heap memory
20+
memStats.NumGC, // Number of garbage collections
21+
)
22+
23+
time.Sleep(10 * time.Second)
24+
}
25+
}

backfill/fetch/fetch_cut.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package fetch
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"go-backfill/config"
7+
"io"
8+
"log"
9+
"net/http"
10+
"strconv"
11+
)
12+
13+
type FetchCutResult struct {
14+
Hashes map[string]struct {
15+
Height int `json:"height"`
16+
Hash string `json:"hash"`
17+
} `json:"hashes"`
18+
}
19+
20+
type CutResult struct {
21+
Hash string
22+
Height int
23+
}
24+
25+
func FetchCut() CutResult {
26+
env := config.GetConfig()
27+
28+
endpoint := fmt.Sprintf("%s/%s/cut", env.SyncBaseUrl, env.Network)
29+
30+
resp, err := http.Get(endpoint)
31+
if err != nil {
32+
log.Fatalf("error making GET request: %v", err)
33+
}
34+
defer resp.Body.Close()
35+
36+
if resp.StatusCode != http.StatusOK {
37+
body, _ := io.ReadAll(resp.Body)
38+
log.Fatalf("Unexpected status code: %d, body: %s", resp.StatusCode, string(body))
39+
}
40+
41+
body, err := io.ReadAll(resp.Body)
42+
if err != nil {
43+
log.Fatalf("error reading response body: %v", err)
44+
}
45+
46+
var result FetchCutResult
47+
err = json.Unmarshal(body, &result)
48+
if err != nil {
49+
log.Fatalf("Error parsing JSON response: %v", err)
50+
}
51+
52+
ChainId := strconv.Itoa(env.ChainId)
53+
lastHeight := result.Hashes[ChainId].Height
54+
// if lastHeight == nil {
55+
// return 0, fmt.Errorf("no height found: %w", err)
56+
// }
57+
res := CutResult{
58+
Hash: result.Hashes[ChainId].Hash,
59+
Height: lastHeight,
60+
}
61+
62+
return res
63+
}
64+
65+
func FetchCuts() FetchCutResult {
66+
env := config.GetConfig()
67+
68+
endpoint := fmt.Sprintf("%s/%s/cut", env.SyncBaseUrl, env.Network)
69+
70+
resp, err := http.Get(endpoint)
71+
if err != nil {
72+
log.Fatalf("error making GET request: %v", err)
73+
}
74+
defer resp.Body.Close()
75+
76+
if resp.StatusCode != http.StatusOK {
77+
body, _ := io.ReadAll(resp.Body)
78+
log.Fatalf("Unexpected status code: %d, body: %s", resp.StatusCode, string(body))
79+
}
80+
81+
body, err := io.ReadAll(resp.Body)
82+
if err != nil {
83+
log.Fatalf("error reading response body: %v", err)
84+
}
85+
86+
var result FetchCutResult
87+
err = json.Unmarshal(body, &result)
88+
if err != nil {
89+
log.Fatalf("Error parsing JSON response: %v", err)
90+
}
91+
92+
return result
93+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package fetch
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"go-backfill/config"
8+
"io"
9+
"log"
10+
"net/http"
11+
"time"
12+
)
13+
14+
type BlockInfo struct {
15+
Header Header `json:"header"`
16+
Payload Payload `json:"payloadWithOutputs"`
17+
}
18+
19+
type Adjacents map[string]string
20+
21+
type Header struct {
22+
Nonce string `json:"nonce"`
23+
CreationTime int64 `json:"creationTime"`
24+
Parent string `json:"parent"`
25+
Adjacents Adjacents `json:"adjacents"`
26+
Target string `json:"target"`
27+
PayloadHash string `json:"payloadHash"`
28+
ChainId int `json:"chainId"`
29+
Weight string `json:"weight"`
30+
Height int `json:"height"`
31+
ChainwebVersion string `json:"chainwebVersion"`
32+
EpochStart int64 `json:"epochStart"`
33+
FeatureFlags uint64 `json:"featureFlags"`
34+
Hash string `json:"hash"`
35+
}
36+
37+
type Payload struct {
38+
Transactions [][2]string `json:"transactions"`
39+
MinerData string `json:"minerData"`
40+
TransactionsHash string `json:"transactionsHash"`
41+
OutputsHash string `json:"outputsHash"`
42+
PayloadHash string `json:"payloadHash"`
43+
Coinbase string `json:"coinbase"`
44+
}
45+
46+
func FetchPayloadsWithHeaders(network string, chainId int, Hash string, minHeight int, maxHeight int) ([]BlockInfo, error) {
47+
type FetchResponse struct {
48+
Items []BlockInfo `json:"items"`
49+
}
50+
51+
startTime := time.Now()
52+
env := config.GetConfig()
53+
endpoint := fmt.Sprintf("%s/%s/chain/%d/block/branch?minheight=%d&maxheight=%d", env.SyncBaseUrl, network, chainId, minHeight, maxHeight)
54+
55+
param := map[string]interface{}{
56+
"upper": []string{Hash},
57+
}
58+
59+
paramJSON, err := json.Marshal(param)
60+
if err != nil {
61+
return nil, fmt.Errorf("failed to marshal payload hashes to JSON: %v", err)
62+
}
63+
64+
attempt := 1
65+
for attempt <= env.SyncAttemptsMaxRetry {
66+
req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(paramJSON))
67+
if err != nil {
68+
return nil, fmt.Errorf("failed to create request: %v", err)
69+
}
70+
71+
req.Header.Set("Content-Type", "application/json")
72+
req.Header.Set("Accept", "application/json")
73+
74+
client := &http.Client{}
75+
resp, err := client.Do(req)
76+
if err != nil {
77+
log.Printf("Attempt %d: Error making POST request for payloads: %v\n", attempt, err)
78+
if attempt == env.SyncAttemptsMaxRetry {
79+
return nil, err
80+
}
81+
82+
attempt++
83+
time.Sleep(time.Duration(env.SyncAttemptsIntervalInMs) * time.Millisecond)
84+
continue
85+
}
86+
defer resp.Body.Close()
87+
88+
if resp.StatusCode != http.StatusOK {
89+
log.Printf("Attempt %d: Received non-OK HTTP status %d\n", attempt, resp.StatusCode)
90+
if attempt == env.SyncAttemptsMaxRetry {
91+
return nil, fmt.Errorf("received non-OK HTTP status: %d", resp.StatusCode)
92+
}
93+
94+
attempt++
95+
time.Sleep(time.Duration(env.SyncAttemptsIntervalInMs) * time.Millisecond)
96+
continue
97+
}
98+
99+
body, err := io.ReadAll(resp.Body)
100+
if err != nil {
101+
return nil, fmt.Errorf("failed to read response body: %v", err)
102+
}
103+
104+
var payload FetchResponse
105+
err = json.Unmarshal(body, &payload)
106+
if err != nil {
107+
return nil, fmt.Errorf("failed to unmarshal JSON response: %v", err)
108+
}
109+
110+
if len(payload.Items) == 0 {
111+
log.Printf("Attempt %d: No payloads found, retrying...\n", attempt)
112+
if attempt == env.SyncAttemptsMaxRetry {
113+
return nil, fmt.Errorf("no payloads found after maximum attempts: %v", err)
114+
}
115+
116+
attempt++
117+
time.Sleep(time.Duration(env.SyncAttemptsIntervalInMs) * time.Millisecond)
118+
continue
119+
}
120+
121+
log.Printf("Fetched payloads in %fs\n", time.Since(startTime).Seconds())
122+
return payload.Items, nil
123+
}
124+
125+
return nil, fmt.Errorf("failed to fetch payloads after maximum retry attempts: %v", err)
126+
}

0 commit comments

Comments
 (0)