Skip to content

Commit

Permalink
Add shared functions to get the stellar-core build version and protoc…
Browse files Browse the repository at this point in the history
…ol version and update ledgerexporter and captivecorebackend to use these shared functions. Implement a version check for protocol 21 when creating a new captive-core instance.
  • Loading branch information
urvisavla committed Jun 18, 2024
1 parent 5ff51c7 commit ffc70d0
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 117 deletions.
21 changes: 2 additions & 19 deletions exp/services/ledgerexporter/internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import (
_ "embed"
"fmt"
"os"
"os/exec"
"strings"

"github.com/stellar/go/historyarchive"
"github.com/stellar/go/ingest/ledgerbackend"
Expand Down Expand Up @@ -194,26 +192,11 @@ func (config *Config) GenerateCaptiveCoreConfig(coreBinFromPath string) (ledgerb
}, nil
}

// By default, it points to exec.Command, overridden for testing purpose
var execCommand = exec.Command

// Executes the "stellar-core version" command and parses its output to extract
// the core version
// The output of the "version" command is expected to be a multi-line string where the
// first line is the core version in format "vX.Y.Z-*".
func (c *Config) setCoreVersionInfo() (err error) {
versionCmd := execCommand(c.StellarCoreConfig.StellarCoreBinaryPath, "version")
versionOutput, err := versionCmd.Output()
c.CoreVersion, err = ledgerbackend.GetCoreBuildVersionFunc(c.StellarCoreConfig.StellarCoreBinaryPath)
if err != nil {
return fmt.Errorf("failed to execute stellar-core version command: %w", err)
}

// Split the output into lines
rows := strings.Split(string(versionOutput), "\n")
if len(rows) == 0 || len(rows[0]) == 0 {
return fmt.Errorf("stellar-core version not found")
return fmt.Errorf("failed to set stellar-core version: %w", err)
}
c.CoreVersion = rows[0]
logger.Infof("stellar-core version: %s", c.CoreVersion)
return nil
}
Expand Down
83 changes: 6 additions & 77 deletions exp/services/ledgerexporter/internal/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,15 @@ package ledgerexporter
import (
"context"
"fmt"
"os"
"os/exec"
"testing"

"github.com/stellar/go/ingest/ledgerbackend"
"github.com/stellar/go/network"
"github.com/stellar/go/support/datastore"

"github.com/stretchr/testify/require"

"github.com/stellar/go/historyarchive"
"github.com/stellar/go/support/errors"
)

func TestNewConfig(t *testing.T) {
Expand Down Expand Up @@ -95,7 +93,7 @@ func TestDefaultCaptiveCoreBin(t *testing.T) {
RuntimeSettings{ConfigFilePath: "test/no_core_bin.toml"})
require.NoError(t, err)

cmdOut = "v20.2.0-2-g6e73c0a88\n"
ledgerbackend.GetCoreBuildVersionFunc = func(string) (string, error) { return "v20.2.0-2-g6e73c0a88", nil }
ccConfig, err := cfg.GenerateCaptiveCoreConfig("/test/default/stellar-core")
require.NoError(t, err)
require.Equal(t, ccConfig.BinaryPath, "/test/default/stellar-core")
Expand All @@ -116,7 +114,7 @@ func TestValidCaptiveCorePreconfiguredNetwork(t *testing.T) {
require.Equal(t, cfg.StellarCoreConfig.NetworkPassphrase, network.PublicNetworkPassphrase)
require.Equal(t, cfg.StellarCoreConfig.HistoryArchiveUrls, network.PublicNetworkhistoryArchiveURLs)

cmdOut = "v20.2.0-2-g6e73c0a88\n"
ledgerbackend.GetCoreBuildVersionFunc = func(string) (string, error) { return "v20.2.0-2-g6e73c0a88", nil }
ccConfig, err := cfg.GenerateCaptiveCoreConfig("")
require.NoError(t, err)

Expand All @@ -137,7 +135,7 @@ func TestValidCaptiveCoreManualNetwork(t *testing.T) {
require.Equal(t, cfg.StellarCoreConfig.NetworkPassphrase, "test")
require.Equal(t, cfg.StellarCoreConfig.HistoryArchiveUrls, []string{"http://testarchive"})

cmdOut = "v20.2.0-2-g6e73c0a88\n"
ledgerbackend.GetCoreBuildVersionFunc = func(string) (string, error) { return "v20.2.0-2-g6e73c0a88", nil }
ccConfig, err := cfg.GenerateCaptiveCoreConfig("")
require.NoError(t, err)

Expand All @@ -157,7 +155,7 @@ func TestValidCaptiveCoreOverridenToml(t *testing.T) {
require.Equal(t, cfg.StellarCoreConfig.NetworkPassphrase, network.PublicNetworkPassphrase)
require.Equal(t, cfg.StellarCoreConfig.HistoryArchiveUrls, network.PublicNetworkhistoryArchiveURLs)

cmdOut = "v20.2.0-2-g6e73c0a88\n"
ledgerbackend.GetCoreBuildVersionFunc = func(string) (string, error) { return "v20.2.0-2-g6e73c0a88", nil }
ccConfig, err := cfg.GenerateCaptiveCoreConfig("")
require.NoError(t, err)

Expand All @@ -179,7 +177,7 @@ func TestValidCaptiveCoreOverridenArchiveUrls(t *testing.T) {
require.Equal(t, cfg.StellarCoreConfig.NetworkPassphrase, network.PublicNetworkPassphrase)
require.Equal(t, cfg.StellarCoreConfig.HistoryArchiveUrls, []string{"http://testarchive"})

cmdOut = "v20.2.0-2-g6e73c0a88\n"
ledgerbackend.GetCoreBuildVersionFunc = func(string) (string, error) { return "v20.2.0-2-g6e73c0a88\n", nil }
ccConfig, err := cfg.GenerateCaptiveCoreConfig("")
require.NoError(t, err)

Expand Down Expand Up @@ -438,72 +436,3 @@ func TestAdjustedLedgerRangeUnBoundedMode(t *testing.T) {
}
mockArchive.AssertExpectations(t)
}

var cmdOut = ""

func fakeExecCommand(command string, args ...string) *exec.Cmd {
cs := append([]string{"-test.run=TestExecCmdHelperProcess", "--", command}, args...)
cmd := exec.Command(os.Args[0], cs...)
cmd.Env = append(os.Environ(), "GO_EXEC_CMD_HELPER_PROCESS=1", "CMD_OUT="+cmdOut)
return cmd
}

func init() {
execCommand = fakeExecCommand
}

func TestExecCmdHelperProcess(t *testing.T) {
if os.Getenv("GO_EXEC_CMD_HELPER_PROCESS") != "1" {
return
}
fmt.Fprint(os.Stdout, os.Getenv("CMD_OUT"))
os.Exit(0)
}

func TestSetCoreVersionInfo(t *testing.T) {
execCommand = fakeExecCommand
tests := []struct {
name string
commandOutput string
expectedError error
expectedCoreVer string
}{
{
name: "version found",
commandOutput: "v20.2.0-2-g6e73c0a88\n" +
"rust version: rustc 1.74.1 (a28077b28 2023-12-04)\n" +
"soroban-env-host: \n" +
" curr:\n" +
" package version: 20.2.0\n" +
" git version: 1bfc0f2a2ee134efc1e1b0d5270281d0cba61c2e\n" +
" ledger protocol version: 20\n" +
" pre-release version: 0\n" +
" rs-stellar-xdr:\n" +
" package version: 20.1.0\n" +
" git version: 8b9d623ef40423a8462442b86997155f2c04d3a1\n" +
" base XDR git version: b96148cd4acc372cc9af17b909ffe4b12c43ecb6\n",
expectedError: nil,
expectedCoreVer: "v20.2.0-2-g6e73c0a88",
},
{
name: "core version not found",
commandOutput: "",
expectedError: errors.New("stellar-core version not found"),
expectedCoreVer: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
config := Config{}
cmdOut = tt.commandOutput
err := config.setCoreVersionInfo()

if tt.expectedError != nil {
require.EqualError(t, err, tt.expectedError.Error())
} else {
require.NoError(t, err)
require.Equal(t, tt.expectedCoreVer, config.CoreVersion)
}
})
}
}
37 changes: 16 additions & 21 deletions ingest/ledgerbackend/captive_core_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import (
"fmt"
"net/http"
"os"
"os/exec"
"strings"
"sync"
"time"

Expand All @@ -22,6 +20,8 @@ import (
"github.com/stellar/go/xdr"
)

var minProtocolVersionSupported uint = 21

// Ensure CaptiveStellarCore implements LedgerBackend
var _ LedgerBackend = (*CaptiveStellarCore)(nil)

Expand Down Expand Up @@ -168,6 +168,17 @@ func NewCaptive(config CaptiveCoreConfig) (*CaptiveStellarCore, error) {
config.Log.SetLevel(logrus.InfoLevel)
}

protocolVersion, err := GetCoreProtocolVersionFunc(config.BinaryPath)
if err != nil {
return nil, fmt.Errorf("error determining stellar-core protocol version: %w", err)
}

if protocolVersion < minProtocolVersionSupported {
return nil, fmt.Errorf("stellar-core version not supported. Installed stellar-core version is at protocol %d, but minimum "+
"required version is %d. Please upgrade stellar-core to a version that supports protocol version %d or higher",
protocolVersion, minProtocolVersionSupported, minProtocolVersionSupported)
}

parentCtx := config.Context
if parentCtx == nil {
parentCtx = context.Background()
Expand Down Expand Up @@ -250,28 +261,12 @@ func (c *CaptiveStellarCore) coreVersionMetric() float64 {
return float64(info.Info.ProtocolVersion)
}

// By default, it points to exec.Command, overridden for testing purpose
var execCommand = exec.Command

// Executes the "stellar-core version" command and parses its output to extract
// the core version
// The output of the "version" command is expected to be a multi-line string where the
// first line is the core version in format "vX.Y.Z-*".
func (c *CaptiveStellarCore) setCoreVersion() {
versionCmd := execCommand(c.config.BinaryPath, "version")
versionOutput, err := versionCmd.Output()
var err error
c.captiveCoreVersion, err = GetCoreBuildVersionFunc(c.config.BinaryPath)
if err != nil {
c.config.Log.Errorf("failed to execute stellar-core version command: %s", err)
c.config.Log.Errorf("Failed to set stellar-core version: %s", err)
}

// Split the output into lines
rows := strings.Split(string(versionOutput), "\n")
if len(rows) == 0 || len(rows[0]) == 0 {
c.config.Log.Error("stellar-core version not found")
return
}

c.captiveCoreVersion = rows[0]
c.config.Log.Infof("stellar-core version: %s", c.captiveCoreVersion)
}

Expand Down
33 changes: 33 additions & 0 deletions ingest/ledgerbackend/captive_core_backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ func TestCaptiveNew(t *testing.T) {
networkPassphrase := network.PublicNetworkPassphrase
historyURLs := []string{server.URL}

GetCoreProtocolVersionFunc = func(string) (uint, error) { return 21, nil }

captiveStellarCore, err := NewCaptive(
CaptiveCoreConfig{
BinaryPath: executablePath,
Expand All @@ -169,6 +171,35 @@ func TestCaptiveNew(t *testing.T) {
assert.Equal(t, "uatest", userAgent)
}

func TestCaptiveNewUnsupportedProtocolVersion(t *testing.T) {
storagePath, err := os.MkdirTemp("", "captive-core-*")
require.NoError(t, err)
defer os.RemoveAll(storagePath)

server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
defer server.Close()

executablePath := "/etc/stellar-core"
networkPassphrase := network.PublicNetworkPassphrase
historyURLs := []string{server.URL}

GetCoreProtocolVersionFunc = func(string) (uint, error) { return 20, nil }

_, err = NewCaptive(
CaptiveCoreConfig{
BinaryPath: executablePath,
NetworkPassphrase: networkPassphrase,
HistoryArchiveURLs: historyURLs,
StoragePath: storagePath,
UserAgent: "uatest",
},
)

assert.EqualError(t, err, "stellar-core version not supported. Installed stellar-core version is at protocol 20, but minimum required version is 21. Please upgrade stellar-core to a version that supports protocol version 21 or higher")
}

func TestCaptivePrepareRange(t *testing.T) {
metaChan := make(chan metaResult, 100)

Expand Down Expand Up @@ -984,6 +1015,8 @@ func TestCaptiveStellarCore_PrepareRangeAfterClose(t *testing.T) {
captiveCoreToml, err := NewCaptiveCoreToml(CaptiveCoreTomlParams{})
assert.NoError(t, err)

GetCoreProtocolVersionFunc = func(string) (uint, error) { return 21, nil }

captiveStellarCore, err := NewCaptive(
CaptiveCoreConfig{
BinaryPath: executablePath,
Expand Down
63 changes: 63 additions & 0 deletions ingest/ledgerbackend/stellar_core_version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package ledgerbackend

import (
"fmt"
"os/exec"
"regexp"
"strconv"
"strings"
)

// By default, it points to exec.Command, overridden for testing purpose
var execCommand = exec.Command
var GetCoreProtocolVersionFunc = GetCoreProtocolVersion
var GetCoreBuildVersionFunc = GetCoreBuildVersion

// GetCoreBuildVersion executes the "stellar-core version" command and parses its output to extract
// the core version
// The output of the "version" command is expected to be a multi-line string where the
// first line is the core version in format "vX.Y.Z-*".
func GetCoreBuildVersion(coreBinaryPath string) (string, error) {
versionCmd := execCommand(coreBinaryPath, "version")
versionOutput, err := versionCmd.Output()
if err != nil {
return "", fmt.Errorf("failed to execute stellar-core version command: %w", err)
}

// Split the output into lines
rows := strings.Split(string(versionOutput), "\n")
if len(rows) == 0 || len(rows[0]) == 0 {
return "", fmt.Errorf("stellar-core version not found")
}

return rows[0], nil
}

// GetCoreProtocolVersion retrieves the ledger protocol version from the specified stellar-core binary.
// It executes the "stellar-core version" command and parses the output to extract the protocol version.
func GetCoreProtocolVersion(coreBinaryPath string) (uint, error) {
if coreBinaryPath == "" {
return 0, fmt.Errorf("stellar-core binary path is empty")
}

versionBytes, err := execCommand(coreBinaryPath, "version").Output()
if err != nil {
return 0, fmt.Errorf("error executing stellar-core version command (%s): %w", coreBinaryPath, err)
}

versionRows := strings.Split(string(versionBytes), "\n")
re := regexp.MustCompile(`^\s*ledger protocol version: (\d*)`)
var ledgerProtocolStrings []string
for _, line := range versionRows {
ledgerProtocolStrings = re.FindStringSubmatch(line)
if len(ledgerProtocolStrings) == 2 {
val, err := strconv.Atoi(ledgerProtocolStrings[1])
if err != nil {
return 0, fmt.Errorf("error parsing protocol version from stellar-core output: %w", err)
}
return uint(val), nil
}
}

return 0, fmt.Errorf("error parsing protocol version from stellar-core output")
}
Loading

0 comments on commit ffc70d0

Please sign in to comment.