Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Build Arbiter Signer
Copy link
Contributor

@mollkeith mollkeith Jan 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job, but there are a few points that need adjustment:

  1. There will be multiple apps in the future, not just Arbiter. You can refer to the feature_web branch, which is still in development. So it's better to write a generic build check method instead of just building Arbiter.
  2. The go.yaml file under workflows path needs to be deleted to avoid duplication.
  3. Additionally, the workflow here will affect the build check in the README.md, so it needs to be modified and tested accordingly in the README


on:
push:
branches: [ main ]
tags:
- 'v*' # Trigger on version tags
pull_request:
branches: [ main ]
workflow_dispatch:

jobs:
build:
name: Build for ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: ubuntu-latest
output_name: arbiter-linux
goarch: amd64
- os: windows-latest
output_name: arbiter-windows.exe
goarch: amd64
- os: macos-latest
output_name: arbiter-macos-amd64
goarch: amd64
- os: macos-latest
output_name: arbiter-macos-arm64
goarch: arm64

steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'

- name: Go Mod Tidy
run: go mod tidy

- name: Build
env:
GOOS: ${{ matrix.os == 'windows-latest' && 'windows' || matrix.os == 'ubuntu-latest' && 'linux' || 'darwin' }}
GOARCH: ${{ matrix.goarch }}
run: go build -o ${{ matrix.output_name }} ./app/arbiter

- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.output_name }}
path: ${{ matrix.output_name }}

release:
needs: build
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4

- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: |
*/arbiter-linux
*/arbiter-windows.exe
*/arbiter-macos-amd64
*/arbiter-macos-arm64
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
231 changes: 229 additions & 2 deletions app/arbiter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,241 @@
package main

import (
"bufio"
"context"
"encoding/hex"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"sync"

"gopkg.in/yaml.v2"

_ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
"github.com/gogf/gf/os/gfile"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcfg"
"github.com/gogf/gf/v2/os/gctx"

"github.com/BeL2Labs/Arbiter_Signer/app/arbiter/config"
"github.com/BeL2Labs/Arbiter_Signer/app/arbiter/arbiter"
"github.com/BeL2Labs/Arbiter_Signer/app/arbiter/config"
"golang.org/x/term"
)

func main() {
type ConfigFile struct {
Chain struct {
Esc string `yaml:"esc"`
} `yaml:"chain"`
Arbiter struct {
Listener bool `yaml:"listener"`
Signer bool `yaml:"signer"`
Network string `yaml:"network"`
EscStartHeight uint64 `yaml:"escStartHeight"`
EscArbiterContractAddress string `yaml:"escArbiterContractAddress"`
EscArbiterManagerContractAddress string `yaml:"escArbiterManagerContractAddress"`
DataPath string `yaml:"dataPath"`
KeyFilePath string `yaml:"keyFilePath"`
EscArbiterAddress string `yaml:"escArbiterAddress"`
EscPrivateKey string `yaml:"escPrivateKey"`
BtcPrivateKey string `yaml:"btcPrivateKey"`
} `yaml:"arbiter"`
}

func getDefaultConfig() ConfigFile {
Copy link
Contributor

@mollkeith mollkeith Jan 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not possible to enforce that config.yaml be placed in the current location:
GoFrame is designed to look for configuration files sequentially from the current directory and then from the manifest/config directory. Since a single repository can have multiple different config.yaml files, they need to be placed in their respective manifest directories. The official recommended location is always under the manifest/config directory. the config retrieval order of arbiter:

1. Arbiter_Signer/
2. Arbiter_Signer/config
3. Arbiter_Signer/manifest/config
4. Arbiter_Signer/app/arbiter/
5. Arbiter_Signer/app/arbiter/config
6. Arbiter_Signer/app/arbiter/manifest/config 

If you place a config.yaml file in the '.' directory, it will override the config.yaml under the manifest directory.

var cfg ConfigFile
cfg.Chain.Esc = "https://api.elastos.io/esc"
cfg.Arbiter.Listener = true
cfg.Arbiter.Signer = true
cfg.Arbiter.Network = "mainnet"
cfg.Arbiter.EscStartHeight = 28437808
cfg.Arbiter.EscArbiterContractAddress = "0xA10b92006743Ef3B12077da67e465963743b03D3"
cfg.Arbiter.EscArbiterManagerContractAddress = "0x9963b5214434776D043A4e98Bc7f33321F6aaCfc"

// Get executable directory for portable paths
execPath, err := os.Executable()
if err != nil {
execPath = "."
}
execDir := filepath.Dir(execPath)

cfg.Arbiter.DataPath = filepath.Join(execDir, "data")
cfg.Arbiter.KeyFilePath = filepath.Join(execDir, "key")
cfg.Arbiter.EscArbiterAddress = ""
cfg.Arbiter.EscPrivateKey = ""
cfg.Arbiter.BtcPrivateKey = ""

return cfg
}

func setupConfig() error {
configPath := "config.yaml"

// Check if config already exists and warn user
if _, err := os.Stat(configPath); err == nil {
fmt.Println("Warning: Config file already exists and will be overwritten.")
fmt.Println("Press Enter to continue or Ctrl+C to abort...")
bufio.NewReader(os.Stdin).ReadString('\n')
}

cfg := getDefaultConfig()
reader := bufio.NewReader(os.Stdin)

fmt.Println("\nPress Enter to keep the default value, or type a new value:")

// Chain configuration
fmt.Printf("ESC Chain URL [%s]: ", cfg.Chain.Esc)
if input, _ := reader.ReadString('\n'); strings.TrimSpace(input) != "" {
cfg.Chain.Esc = strings.TrimSpace(input)
}

// Arbiter configuration
fmt.Printf("Listener enabled [%v]: ", cfg.Arbiter.Listener)
if input, _ := reader.ReadString('\n'); strings.TrimSpace(input) != "" {
cfg.Arbiter.Listener = strings.ToLower(strings.TrimSpace(input)) == "true"
}

fmt.Printf("Signer enabled [%v]: ", cfg.Arbiter.Signer)
if input, _ := reader.ReadString('\n'); strings.TrimSpace(input) != "" {
cfg.Arbiter.Signer = strings.ToLower(strings.TrimSpace(input)) == "true"
}

fmt.Printf("Network [%s]: ", cfg.Arbiter.Network)
if input, _ := reader.ReadString('\n'); strings.TrimSpace(input) != "" {
cfg.Arbiter.Network = strings.TrimSpace(input)
}

fmt.Printf("ESC Start Height [%d]: ", cfg.Arbiter.EscStartHeight)
if input, _ := reader.ReadString('\n'); strings.TrimSpace(input) != "" {
if height, err := strconv.ParseUint(strings.TrimSpace(input), 10, 64); err == nil {
cfg.Arbiter.EscStartHeight = height
}
}

fmt.Printf("ESC Arbiter Contract Address [%s]: ", cfg.Arbiter.EscArbiterContractAddress)
if input, _ := reader.ReadString('\n'); strings.TrimSpace(input) != "" {
cfg.Arbiter.EscArbiterContractAddress = strings.TrimSpace(input)
}

fmt.Printf("ESC Arbiter Manager Contract Address [%s]: ", cfg.Arbiter.EscArbiterManagerContractAddress)
if input, _ := reader.ReadString('\n'); strings.TrimSpace(input) != "" {
cfg.Arbiter.EscArbiterManagerContractAddress = strings.TrimSpace(input)
}

fmt.Printf("Data Path [%s]: ", cfg.Arbiter.DataPath)
if input, _ := reader.ReadString('\n'); strings.TrimSpace(input) != "" {
cfg.Arbiter.DataPath = strings.TrimSpace(input)
}

fmt.Printf("Key File Path [%s]: ", cfg.Arbiter.KeyFilePath)
if input, _ := reader.ReadString('\n'); strings.TrimSpace(input) != "" {
cfg.Arbiter.KeyFilePath = strings.TrimSpace(input)
}

fmt.Printf("ESC Arbiter Address [%s]: ", cfg.Arbiter.EscArbiterAddress)
if input, _ := reader.ReadString('\n'); strings.TrimSpace(input) != "" {
cfg.Arbiter.EscArbiterAddress = strings.TrimSpace(input)
}

// Create directories
os.MkdirAll(cfg.Arbiter.DataPath, 0755)
os.MkdirAll(cfg.Arbiter.KeyFilePath, 0755)

// Write config file
yamlData, err := yaml.Marshal(cfg)
if err != nil {
return fmt.Errorf("error marshaling config: %v", err)
}

err = os.WriteFile(configPath, yamlData, 0644)
if err != nil {
return fmt.Errorf("error writing config file: %v", err)
}

// Always recreate key files
return createKeyFiles(cfg)
}

func createKeyFiles(cfg ConfigFile) error {
Copy link
Contributor

@mollkeith mollkeith Jan 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Users should not be prompted to input during the running process, as the current deployment is automated through scripts or Docker. Any process that requires user input will disrupt the deployment. If it is indeed necessary, it should be changed to: 'If the key file exists, use the key file.'
Maybe this is not really necessary, as creating a default config.yaml will affect GoFrame's configuration retrieval method.

// Create ESC key file
escKeyFile := filepath.Join(cfg.Arbiter.KeyFilePath, "escKey.json")
if _, err := os.Stat(escKeyFile); err == nil {
fmt.Println("\nWarning: ESC key file already exists and will be overwritten.")
}

// Loop until valid ESC key is provided
var escKey string
for {
fmt.Print("\nEnter ESC private key (64 hex characters): ")
// Read password without echo
keyBytes, err := term.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return fmt.Errorf("error reading private key: %v", err)
}
fmt.Println() // Add newline after hidden input

escKey = strings.TrimSpace(string(keyBytes))

// Validate hex format and length
if len(escKey) != 64 {
fmt.Println("Error: Private key must be exactly 64 hex characters")
continue
}
if _, err := hex.DecodeString(escKey); err != nil {
fmt.Println("Error: Private key must be in hex format")
continue
}
break
}

// Correct key file format
keyData := fmt.Sprintf(`{"privKey":"%s"}`, escKey)
if err := os.WriteFile(escKeyFile, []byte(keyData), 0600); err != nil {
return fmt.Errorf("failed to create ESC key file: %v", err)
}

// Create BTC key file
btcKeyFile := filepath.Join(cfg.Arbiter.KeyFilePath, "btcKey.json")
if _, err := os.Stat(btcKeyFile); err == nil {
fmt.Println("\nWarning: BTC key file already exists and will be overwritten.")
}

// Loop until valid BTC key is provided
var btcKey string
for {
fmt.Print("\nEnter BTC private key (64 hex characters): ")
// Read password without echo
keyBytes, err := term.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return fmt.Errorf("error reading private key: %v", err)
}
fmt.Println() // Add newline after hidden input

btcKey = strings.TrimSpace(string(keyBytes))

// Validate hex format and length
if len(btcKey) != 64 {
fmt.Println("Error: Private key must be exactly 64 hex characters")
continue
}
if _, err := hex.DecodeString(btcKey); err != nil {
fmt.Println("Error: Private key must be in hex format")
continue
}
break
}

// Correct key file format
keyData = fmt.Sprintf(`{"privKey":"%s"}`, btcKey)
if err := os.WriteFile(btcKeyFile, []byte(keyData), 0600); err != nil {
return fmt.Errorf("failed to create BTC key file: %v", err)
}

return nil
}

func main() {
if len(os.Args) > 1 {
operation := os.Args[1]
switch strings.ToLower(operation) {
Expand All @@ -36,9 +253,19 @@ func main() {
}
}

// Run setup first
if err := setupConfig(); err != nil {
fmt.Printf("Setup failed: %v\n", err)
os.Exit(1)
}

ctx := gctx.New()
var wg sync.WaitGroup
wg.Add(1)

// Initialize config file path for gf framework
g.Cfg().GetAdapter().(*gcfg.AdapterFile).SetPath(".")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove it, useless code does not affect GoFrame's own configuration retrieval


// start arbiter
g.Log().Info(ctx, "Starting arbiter...")
arb := arbiter.NewArbiter(ctx, initConfig(ctx))
Expand Down
12 changes: 7 additions & 5 deletions app/arbiter/manifest/config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ chain:

# Arbiter
arbiter:
listener: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good!

signer: true,
listener: true
signer: true
network: "mainnet"
escStartHeight: 28437808
escArbiterContractAddress: "0xA10b92006743Ef3B12077da67e465963743b03D3"
escArbiterManagerContractAddress: "0x9963b5214434776D043A4e98Bc7f33321F6aaCfc"
escArbiterAddress: "0x0262aB0ED65373cC855C34529fDdeAa0e686D913"
dataPath: "./app/arbiter/data"
keyFilePath: "./app/arbiter/data/keys/"
dataPath: "./data"
Copy link
Contributor

@mollkeith mollkeith Jan 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to modify the default values here

  1. This config.yaml is just for convenience during local testing. You can modify the config.yaml example in README.md and adjust it as needed.
  2. Additionally, do not place the data directory under the '.' directory directly, as there may be multiple apps, and each app's data should be placed in a separate directory. If consolidation is necessary, you might consider renaming it to something like './data/arbiter' or similar

keyFilePath: "./key"
escArbiterAddress: ""
escPrivateKey: ""
btcPrivateKey: ""
7 changes: 3 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ go 1.20
require (
github.com/btcsuite/btcd v0.24.0
github.com/btcsuite/btcd/btcec/v2 v2.3.2
github.com/btcsuite/btcd/btcutil v1.1.5
github.com/ethereum/go-ethereum v1.13.8
github.com/gogf/gf v1.16.9
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.6.1
github.com/gogf/gf/v2 v2.6.1
gopkg.in/yaml.v2 v2.4.0
)

require (
Expand All @@ -18,13 +18,11 @@ require (
github.com/StackExchange/wmi v1.2.1 // indirect
github.com/bits-and-blooms/bitset v1.10.0 // indirect
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/consensys/bavard v0.1.13 // indirect
github.com/consensys/gnark-crypto v0.12.1 // indirect
github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect
github.com/deckarep/golang-set/v2 v2.1.0 // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/ethereum/c-kzg-4844 v0.4.0 // indirect
github.com/fatih/color v1.15.0 // indirect
Expand Down Expand Up @@ -56,7 +54,8 @@ require (
golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.18.0 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/term v0.18.0
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.15.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
Loading