diff --git a/src/shidai/internal/api/api.go b/src/shidai/internal/api/api.go index 0cac745..567fa48 100644 --- a/src/shidai/internal/api/api.go +++ b/src/shidai/internal/api/api.go @@ -5,6 +5,7 @@ import ( "github.com/gin-gonic/gin" "github.com/kiracore/sekin/src/shidai/internal/commands" + interxhandler "github.com/kiracore/sekin/src/shidai/internal/interx_handler" "github.com/kiracore/sekin/src/shidai/internal/logger" "github.com/kiracore/sekin/src/shidai/internal/types" "github.com/kiracore/sekin/src/shidai/internal/update" @@ -28,12 +29,12 @@ func Serve() { router.GET("/dashboard", getDashboardHandler()) router.POST("/config", getCurrentConfigs()) router.PUT("/config", setConfig()) - updateContext := context.Background() go backgroundUpdate() go update.UpdateRunner(updateContext) + go interxhandler.AddrbookManager(context.Background()) if err := router.Run(":8282"); err != nil { log.Error("Failed to start the server", zap.Error(err)) } diff --git a/src/shidai/internal/commands/commands.go b/src/shidai/internal/commands/commands.go index 4fdff81..a20a752 100644 --- a/src/shidai/internal/commands/commands.go +++ b/src/shidai/internal/commands/commands.go @@ -226,7 +226,7 @@ func handleJoinCommand(args map[string]interface{}) (string, error) { if err != nil { return "", fmt.Errorf("unable to init interx: %w", err) } - err = interxhandler.StartInterx() + err = interxhandler.StartInterx(ctx) if err != nil { return "", fmt.Errorf("unable to start interx: %w", err) } @@ -262,7 +262,7 @@ func handleStartComamnd(args map[string]interface{}) (string, error) { return "", err } - err = interxhandler.StartInterx() + err = interxhandler.StartInterx(ctx) if err != nil { return "", fmt.Errorf("unable to start interx: %w", err) } diff --git a/src/shidai/internal/interx_handler/interx_handler.go b/src/shidai/internal/interx_handler/interx_handler.go index d36cf64..6b22b00 100644 --- a/src/shidai/internal/interx_handler/interx_handler.go +++ b/src/shidai/internal/interx_handler/interx_handler.go @@ -5,12 +5,15 @@ import ( "errors" "fmt" "io" + "time" mnemonicsgenerator "github.com/KiraCore/tools/validator-key-gen/MnemonicsGenerator" + "go.uber.org/zap" httpexecutor "github.com/kiracore/sekin/src/shidai/internal/http_executor" "github.com/kiracore/sekin/src/shidai/internal/logger" "github.com/kiracore/sekin/src/shidai/internal/types" + "github.com/kiracore/sekin/src/shidai/internal/utils" ) var log = logger.GetLogger() @@ -32,6 +35,7 @@ func InitInterx(ctx context.Context, masterMnemonicSet *mnemonicsgenerator.Maste "signing_mnemonic": signerMnemonic, "port": fmt.Sprintf("%v", types.DEFAULT_INTERX_PORT), "validator_node_id": string(masterMnemonicSet.ValidatorNodeId), + "addrbook": types.INTERX_ADDRBOOK_PATH, }, } out, err := httpexecutor.ExecuteCallerCommand(types.INTERX_CONTAINER_ADDRESS, "8081", "POST", cmd) @@ -43,7 +47,7 @@ func InitInterx(ctx context.Context, masterMnemonicSet *mnemonicsgenerator.Maste return nil } -func StartInterx() error { +func StartInterx(ctx context.Context) error { cmd := httpexecutor.CommandRequest{ Command: "start", Args: map[string]interface{}{ @@ -61,3 +65,54 @@ func StartInterx() error { return nil } + +// run this in goroutine +func AddrbookManager(ctx context.Context) { + ticker := time.NewTicker(30 * time.Minute) + defer ticker.Stop() + errorCooldown := time.Second * 1 + + err := addrbookCopy() + if err != nil { + log.Debug("Error when replacing interx addrbook with sekai addrbook, sleeping", zap.Duration("errorCooldown", errorCooldown)) + time.Sleep(errorCooldown) + } + for { + select { + case <-ctx.Done(): + fmt.Println("Stopping the addrbookManager loop") + return + case t := <-ticker.C: + for { + err := addrbookCopy() + if err != nil { + log.Debug("Error when replacing interx addrbook with sekai addrbook, sleeping", zap.Duration("errorCooldown", errorCooldown)) + time.Sleep(errorCooldown) + continue + } + log.Debug("Address book copying was executed", zap.Time("ticker", t)) + break + } + } + } +} + +func addrbookCopy() error { + var equal, interxAddrbookExist bool + var err error + interxAddrbookExist = utils.FileExists(types.INTERX_ADDRBOOK_PATH) + if interxAddrbookExist { + equal, err = utils.FilesAreEqualMD5(types.SEKAI_ADDRBOOK_PATH, types.INTERX_ADDRBOOK_PATH) + if err != nil { + return fmt.Errorf("error when comparing sekai and interx address books: %w", err) + } + } + if !equal || !interxAddrbookExist { + err := utils.SafeCopy(types.SEKAI_ADDRBOOK_PATH, types.INTERX_ADDRBOOK_PATH) + if err != nil { + return fmt.Errorf("error when replacing interx addrbook with sekai addrbook: %w", err) + } + } + + return nil +} diff --git a/src/shidai/internal/types/types.go b/src/shidai/internal/types/types.go index c80160c..39d6c1a 100644 --- a/src/shidai/internal/types/types.go +++ b/src/shidai/internal/types/types.go @@ -243,6 +243,10 @@ const ( DEFAULT_RPC_PORT int = 26657 DEFAULT_GRPC_PORT int = 9090 + SEKAI_CONFIG_FOLDER string = SEKAI_HOME + "/config" + INTERX_ADDRBOOK_PATH string = INTERX_HOME + "/addrbook.json" + SEKAI_ADDRBOOK_PATH string = SEKAI_CONFIG_FOLDER + "/addrbook.json" + SEKAI_CONTAINER_ADDRESS string = "sekai.local" INTERX_CONTAINER_ADDRESS string = "interx.local" diff --git a/src/shidai/internal/utils/utils.go b/src/shidai/internal/utils/utils.go index aef37aa..b6b9b6f 100644 --- a/src/shidai/internal/utils/utils.go +++ b/src/shidai/internal/utils/utils.go @@ -2,7 +2,9 @@ package utils import ( "bytes" + "crypto/md5" "crypto/rand" + "encoding/hex" "encoding/json" "fmt" "io" @@ -11,6 +13,7 @@ import ( "os" "reflect" "regexp" + "syscall" "github.com/BurntSushi/toml" "github.com/kiracore/sekin/src/shidai/internal/logger" @@ -336,3 +339,125 @@ func CheckForExtraFields(structure interface{}, rawMap map[string]interface{}) e return nil } +func SafeCopy(src, dst string) error { + log.Debug("Trying to copy <%v> to <%v>", zap.String("source:", src), zap.String("destination:", dst)) + check := FileExists(src) + if !check { + return fmt.Errorf("source file does not exist") + } + + info, err := os.Stat(dst) + if err == nil { + if info.IsDir() { + return fmt.Errorf("destination path <%v> is a folder, cannot overwrite", dst) + } + } else if !os.IsNotExist(err) { + return fmt.Errorf("failed to access destination path <%v>: %w", dst, err) + } + + srcFile, err := os.Open(src) + if err != nil { + return fmt.Errorf("failed to open source file: %w", err) + } + defer srcFile.Close() + + if err := syscall.Flock(int(srcFile.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil { + if err == syscall.EWOULDBLOCK { + return fmt.Errorf("file is locked: %w", err) + } else { + return fmt.Errorf("failed to lock source file: %w", err) + } + } + + defer syscall.Flock(int(srcFile.Fd()), syscall.LOCK_UN) + + dstFile, err := os.Create(dst) + if err != nil { + return fmt.Errorf("failed to create destination file: %w", err) + } + defer dstFile.Close() + + if err := syscall.Flock(int(dstFile.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil { + if err == syscall.EWOULDBLOCK { + return fmt.Errorf("destination file is locked: %w", err) + } else { + return fmt.Errorf("failed to lock destination file: %w", err) + } + } + defer syscall.Flock(int(dstFile.Fd()), syscall.LOCK_UN) + + if _, err := io.Copy(dstFile, srcFile); err != nil { + return fmt.Errorf("failed to copy file: %w", err) + } + + log.Debug("File copied", zap.String("source:", src), zap.String("destination:", dst)) + return nil +} + +func FilesAreEqual(file1, file2 string) (bool, error) { + f1, err := os.Open(file1) + if err != nil { + return false, fmt.Errorf("failed to open file1: %w", err) + } + defer f1.Close() + + f2, err := os.Open(file2) + if err != nil { + return false, fmt.Errorf("failed to open file2: %w", err) + } + defer f2.Close() + + buf1 := make([]byte, 1024) + buf2 := make([]byte, 1024) + + for { + n1, err1 := f1.Read(buf1) + n2, err2 := f2.Read(buf2) + + if err1 != nil && err1 != io.EOF { + return false, fmt.Errorf("failed to read from file1: %w", err1) + } + if err2 != nil && err2 != io.EOF { + return false, fmt.Errorf("failed to read from file2: %w", err2) + } + + if n1 != n2 || !bytes.Equal(buf1[:n1], buf2[:n2]) { + return false, nil + } + + if err1 == io.EOF && err2 == io.EOF { + break + } + } + + return true, nil +} + +func FilesAreEqualMD5(file1, file2 string) (bool, error) { + hash1, err := HashFileMD5(file1) + if err != nil { + return false, fmt.Errorf("failed to hash file1: %w", err) + } + + hash2, err := HashFileMD5(file2) + if err != nil { + return false, fmt.Errorf("failed to hash file2: %w", err) + } + + return hash1 == hash2, nil +} + +func HashFileMD5(filename string) (string, error) { + f, err := os.Open(filename) + if err != nil { + return "", fmt.Errorf("failed to open file: %w", err) + } + defer f.Close() + + hasher := md5.New() + if _, err := io.Copy(hasher, f); err != nil { + return "", fmt.Errorf("failed to hash file: %w", err) + } + + return hex.EncodeToString(hasher.Sum(nil)), nil +}