forked from gnolang/gno
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: gfanton <[email protected]>
- Loading branch information
Showing
10 changed files
with
23,936 additions
and
212 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package integration | ||
|
||
import ( | ||
"bufio" | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
|
||
"github.com/gnolang/gno/tm2/pkg/amino" | ||
tmcfg "github.com/gnolang/gno/tm2/pkg/bft/config" | ||
bft "github.com/gnolang/gno/tm2/pkg/bft/types" | ||
) | ||
|
||
type MarshalableGenesisDoc bft.GenesisDoc | ||
|
||
func NewMarshalableGenesisDoc(doc *bft.GenesisDoc) *MarshalableGenesisDoc { | ||
m := MarshalableGenesisDoc(*doc) | ||
return &m | ||
} | ||
|
||
func (m *MarshalableGenesisDoc) MarshalJSON() ([]byte, error) { | ||
doc := (*bft.GenesisDoc)(m) | ||
return amino.MarshalJSON(doc) | ||
} | ||
|
||
func (m *MarshalableGenesisDoc) UnmarshalJSON(data []byte) (err error) { | ||
doc, err := bft.GenesisDocFromJSON(data) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
*m = MarshalableGenesisDoc(*doc) | ||
return | ||
} | ||
|
||
// Function to cast back to the original bft.GenesisDoc | ||
func (m *MarshalableGenesisDoc) ToGenesisDoc() *bft.GenesisDoc { | ||
return (*bft.GenesisDoc)(m) | ||
} | ||
|
||
type ForkConfig struct { | ||
RootDir string `json:"rootdir"` | ||
Genesis *MarshalableGenesisDoc `json:"genesis"` | ||
TMConfig *tmcfg.Config `json:"tm"` | ||
} | ||
|
||
// ExecuteForkBinary runs the binary at the given path with the provided configuration. | ||
// It marshals the configuration to JSON and passes it to the binary via stdin. | ||
// The function waits for "READY:<address>" on stdout and returns the address if successful, | ||
// or kills the process and returns an error if "READY" is not received within 10 seconds. | ||
func ExecuteForkBinary(ctx context.Context, binaryPath string, cfg *ForkConfig) (string, *exec.Cmd, error) { | ||
// Marshal the configuration to JSON | ||
configData, err := json.Marshal(cfg) | ||
if err != nil { | ||
return "", nil, fmt.Errorf("failed to marshal config to JSON: %w", err) | ||
} | ||
|
||
// Create the command to execute the binary | ||
cmd := exec.Command(binaryPath) | ||
cmd.Env = os.Environ() | ||
|
||
// Set the standard input to the JSON data | ||
cmd.Stdin = bytes.NewReader(configData) | ||
|
||
// Create pipes for stdout and stderr | ||
stdoutPipe, err := cmd.StdoutPipe() | ||
if err != nil { | ||
return "", nil, fmt.Errorf("failed to create stdout pipe: %w", err) | ||
} | ||
|
||
cmd.Stderr = os.Stderr | ||
|
||
// Start the command | ||
if err := cmd.Start(); err != nil { | ||
return "", nil, fmt.Errorf("failed to start command: %w", err) | ||
} | ||
|
||
readyChan := make(chan error, 1) | ||
var address string | ||
|
||
// Goroutine to read stdout and check for "READY" | ||
go func() { | ||
var scanned bool | ||
scanner := bufio.NewScanner(stdoutPipe) | ||
for scanner.Scan() { | ||
line := scanner.Text() | ||
fmt.Println(line) // Print each line to stdout for logging | ||
if scanned { | ||
continue | ||
} | ||
if _, err := fmt.Sscanf(line, "READY:%s", &address); err == nil { | ||
readyChan <- nil | ||
scanned = true | ||
} | ||
} | ||
if err := scanner.Err(); err != nil { | ||
readyChan <- fmt.Errorf("error reading stdout: %w", err) | ||
} else { | ||
readyChan <- fmt.Errorf("process exited without 'READY'") | ||
} | ||
}() | ||
|
||
// Wait for either the "READY" signal or a timeout | ||
select { | ||
case err := <-readyChan: | ||
if err != nil { | ||
cmd.Process.Kill() | ||
return "", cmd, err | ||
} | ||
case <-ctx.Done(): | ||
cmd.Process.Kill() | ||
return "", cmd, ctx.Err() | ||
} | ||
|
||
return address, cmd, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package integration | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"path/filepath" | ||
"testing" | ||
"time" | ||
|
||
"github.com/gnolang/gno/gnovm/pkg/gnoenv" | ||
"github.com/gnolang/gno/tm2/pkg/bft/rpc/client" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestForkGnoland(t *testing.T) { | ||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) | ||
defer cancel() | ||
|
||
tmpdir := t.TempDir() | ||
|
||
gnoRootDir := gnoenv.RootDir() | ||
|
||
gnolandBuildDir := filepath.Join(tmpdir, "build") | ||
gnolandBin := filepath.Join(gnolandBuildDir, "gnoland") | ||
err := buildGnoland(t, gnoRootDir, gnolandBin) | ||
require.NoError(t, err) | ||
|
||
cfg := TestingMinimalNodeConfig(gnoRootDir) | ||
|
||
gnoenv.RootDir() | ||
remoteAddr, cmd, err := ExecuteForkBinary(ctx, gnolandBin, &ForkConfig{ | ||
RootDir: gnoRootDir, | ||
TMConfig: cfg.TMConfig, | ||
Genesis: NewMarshalableGenesisDoc(cfg.Genesis), | ||
}) | ||
require.NoError(t, err) | ||
|
||
defer cmd.Process.Kill() | ||
|
||
cli, err := client.NewHTTPClient(remoteAddr) | ||
require.NoError(t, err) | ||
|
||
info, err := cli.ABCIInfo() | ||
require.NoError(t, err) | ||
|
||
fmt.Println(info) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"log/slog" | ||
"os" | ||
"slices" | ||
"time" | ||
|
||
"github.com/gnolang/gno/gno.land/pkg/gnoland" | ||
"github.com/gnolang/gno/gno.land/pkg/integration" | ||
bft "github.com/gnolang/gno/tm2/pkg/bft/types" | ||
) | ||
|
||
func ForkableNode(cfg *integration.ForkConfig) error { | ||
logger := slog.New(slog.NewTextHandler(io.Discard, nil)) | ||
|
||
nodecfg := integration.TestingMinimalNodeConfig(cfg.RootDir) | ||
pv := nodecfg.PrivValidator.GetPubKey() | ||
nodecfg.TMConfig = cfg.TMConfig | ||
nodecfg.Genesis = cfg.Genesis.ToGenesisDoc() | ||
nodecfg.Genesis.Validators = []bft.GenesisValidator{ | ||
{ | ||
Address: pv.Address(), | ||
PubKey: pv, | ||
Power: 10, | ||
Name: "self", | ||
}, | ||
} | ||
|
||
node, err := gnoland.NewInMemoryNode(logger, nodecfg) | ||
if err != nil { | ||
return fmt.Errorf("failed to create new in-memory node: %w", err) | ||
} | ||
|
||
err = node.Start() | ||
if err != nil { | ||
return fmt.Errorf("failed to start node: %w", err) | ||
} | ||
|
||
ourAddress := nodecfg.PrivValidator.GetPubKey().Address() | ||
isValidator := slices.ContainsFunc(nodecfg.Genesis.Validators, func(val bft.GenesisValidator) bool { | ||
return val.Address == ourAddress | ||
}) | ||
|
||
// Wait for first block if we are a validator. | ||
// If we are not a validator, we don't produce blocks, so node.Ready() hangs. | ||
if isValidator { | ||
select { | ||
case <-node.Ready(): | ||
fmt.Printf("READY:%s\n", node.Config().RPC.ListenAddress) | ||
case <-time.After(time.Second * 10): | ||
return fmt.Errorf("timeout while waiting for the node to start") | ||
} | ||
} else { | ||
fmt.Printf("READY:%s\n", node.Config().RPC.ListenAddress) | ||
} | ||
|
||
// Keep the function running indefinitely if no errors occur | ||
select {} | ||
} | ||
|
||
func main() { | ||
// Read the configuration from standard input | ||
configData, err := io.ReadAll(os.Stdin) | ||
if err != nil { | ||
fmt.Fprintf(os.Stdout, "Error reading stdin: %v\n", err) | ||
os.Exit(1) | ||
} | ||
|
||
// Unmarshal the JSON configuration | ||
var cfg integration.ForkConfig | ||
err = json.Unmarshal(configData, &cfg) | ||
if err != nil { | ||
fmt.Fprintf(os.Stdout, "Error unmarshaling JSON: %v\n", err) | ||
os.Exit(1) | ||
} | ||
|
||
// Call the ForkableNode function with the parsed configuration | ||
if err := ForkableNode(&cfg); err != nil { | ||
fmt.Fprintf(os.Stdout, "Error running ForkableNode: %v\n", err) | ||
os.Exit(1) | ||
} | ||
} |
Oops, something went wrong.