Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Rollkit x CelstiaDA #952

Closed
wants to merge 5 commits into from
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
53 changes: 42 additions & 11 deletions chain/cosmos/chain_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,17 @@ import (

// ChainNode represents a node in the test network that is being created
type ChainNode struct {
VolumeName string
Index int
Chain ibc.Chain
Validator bool
NetworkID string
DockerClient *dockerclient.Client
Client rpcclient.Client
GrpcConn *grpc.ClientConn
TestName string
Image ibc.DockerImage
VolumeName string
Index int
Chain ibc.Chain
Validator bool
ValidatorMnemonic string // if this is a validator, this will be set
NetworkID string
DockerClient *dockerclient.Client
Client rpcclient.Client
GrpcConn *grpc.ClientConn
TestName string
Image ibc.DockerImage

// Additional processes that need to be run on a per-validator basis.
Sidecars SidecarProcesses
Expand Down Expand Up @@ -137,6 +138,22 @@ func (tn *ChainNode) NewClient(addr string) error {
return nil
}

func (tn *ChainNode) NewSidecarProcessFromConfig(ctx context.Context, sidecar ibc.SidecarConfig) error {
return tn.NewSidecarProcess(
ctx,
sidecar.PreStart,
sidecar.ProcessName,
tn.DockerClient,
tn.NetworkID,
tn.Image,
tn.HomeDir(),
sidecar.Ports,
sidecar.StartCmd,
sidecar.Env,
)

}

func (tn *ChainNode) NewSidecarProcess(
ctx context.Context,
preStart bool,
Expand Down Expand Up @@ -644,11 +661,25 @@ func (tn *ChainNode) CreateKey(ctx context.Context, name string) error {
tn.lock.Lock()
defer tn.lock.Unlock()

_, _, err := tn.ExecBin(ctx,
_, stderr, err := tn.ExecBin(ctx,
"keys", "add", name,
"--coin-type", tn.Chain.Config().CoinType,
"--keyring-backend", keyring.BackendTest,
)

if tn.Validator && tn.ValidatorMnemonic == "" {
lines := strings.Split(string(stderr), "\n")

updated := []string{}
for _, line := range lines {
if line != "" {
updated = append(updated, line)
}
}

tn.ValidatorMnemonic = updated[len(updated)-1]
}

return err
}

Expand Down
5 changes: 5 additions & 0 deletions chain/cosmos/sidecar.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ func (s *SidecarProcess) logger() *zap.Logger {
}

func (s *SidecarProcess) CreateContainer(ctx context.Context) error {
// TODO: move into containerLifecycle.CreateContainer
if err := s.Image.PullImage(ctx, *s.DockerClient); err != nil {
return fmt.Errorf("sidecar createcontainer failed to pull image: %w", err)
}

return s.containerLifecycle.CreateContainer(ctx, s.TestName, s.NetworkID, s.Image, s.ports, s.Bind(), nil, s.HostName(), s.startCmd, s.env)
}

Expand Down
21 changes: 21 additions & 0 deletions examples/cosmos/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

# docker build . -t strangelove/full-celestia:v0.0.1
#
# Run both a celestia-da and celestia-app node from the same image
# Ideally this is temperary so we can ignore docker host mounting.

FROM ghcr.io/strangelove-ventures/heighliner/celestia:v1.6.0 AS celestia-app

FROM ghcr.io/rollkit/celestia-da:v0.12.6

USER root

COPY --from=celestia-app /bin/celestia-appd /bin/

RUN chmod +x /bin/celestia-appd
RUN chmod +x /bin/celestia-da

EXPOSE 26650 26657 26658 26659 9090 26657 26656 1317


# docker run reece/full-celestia:v0.0.1 celestia-da
269 changes: 269 additions & 0 deletions examples/cosmos/rollkit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
package cosmos_test

import (
"context"
"encoding/json"
"fmt"
"path"
"testing"
"time"

"cosmossdk.io/math"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/strangelove-ventures/interchaintest/v8"
"github.com/strangelove-ventures/interchaintest/v8/chain/cosmos"
"github.com/strangelove-ventures/interchaintest/v8/ibc"
"github.com/strangelove-ventures/interchaintest/v8/testutil"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
)

// https://rollkit.dev/tutorials/gm-world
var (
// TODO: get https://github.com/rollkit/local-celestia-devnet/blob/main/entrypoint.sh into interchaintest
// 26650:26650 -p 26657:26657 -p 26658:26658 -p 26659:26659 -p 9090:9090 // 26650 runs th celestia-da server
// DockerImage = "ghcr.io/strangelove-ventures/heighliner/celestia"
// DockerVersion = "v1.6.0"
// DADockerImage = "ghcr.io/rollkit/celestia-da"
// DADockerVersion = "v0.12.6"

// holds both the da and app binaries from heighliner so we can ignore docker mounting magic
DockerImage = "reece/full-celestia"
DockerVersion = "v0.0.1"

// App & node has a celestia user with home dir /home/celestia
hardcodedPath = "/var/cosmos-chain/celestia"
APP_PATH = ".celestia-app"
NODE_PATH = path.Join(hardcodedPath, "bridge")

// TODO: Set namespace and CELESTIA_CUSTOM env variable on bridge start
// echo 0000$(openssl rand -hex 8)
CELESTIA_NAMESPACE = "00007e5327f23637ed07"
)

func TestRollkitCelestia(t *testing.T) {
if testing.Short() {
t.Skip("skipping in short mode.")
}

t.Parallel()

numVals := 1
numFullNodes := 0
coinDecimals := int64(6)

cfg := ibc.ChainConfig{
Name: "celestia", // Type: Rollkit / Rollup / Celestia?
Denom: "utia",
Type: "cosmos",
GasPrices: "0utia",
TrustingPeriod: "500h",
HostPortOverride: map[int]int{26650: 26650, 1317: 1317, 26656: 26656, 26657: 26657, 26658: 26658, 26659: 26659, 9090: 9090},
EncodingConfig: nil,
SkipGenTx: false,
CoinDecimals: &coinDecimals,
AdditionalStartArgs: []string{"--grpc.enable"},
ChainID: "test",
Bin: "celestia-appd",
Images: []ibc.DockerImage{
{
Repository: DockerImage,
Version: DockerVersion,
UidGid: "1025:1025",
},
},
Bech32Prefix: "celestia",
CoinType: "118",
// ModifyGenesis: cosmos.ModifyGenesis([]cosmos.GenesisKV{}),
GasAdjustment: 1.5,
ConfigFileOverrides: testutil.Toml{"config/config.toml": testutil.Toml{
"index_all_keys": true,
"mode": "validator", // be sure to only use 1 validator here
"tx_index": testutil.Toml{
"indexer": "kv", // TODO: since we execute queries against the validator (unsure if this works on a celestia validator node)
},
}},
}

cf := interchaintest.NewBuiltinChainFactory(zaptest.NewLogger(t), []*interchaintest.ChainSpec{
{
Name: cfg.Name,
ChainName: cfg.Name,
Version: DockerVersion,
ChainConfig: cfg,
NumValidators: &numVals,
NumFullNodes: &numFullNodes,
},
})

chains, err := cf.Chains(t.Name())
require.NoError(t, err)

chain := chains[0].(*cosmos.CosmosChain)

ic := interchaintest.NewInterchain().
AddChain(chain)

ctx := context.Background()
client, network := interchaintest.DockerSetup(t)

require.NoError(t, ic.Build(ctx, nil, interchaintest.InterchainBuildOptions{
TestName: t.Name(),
Client: client,
NetworkID: network,
SkipPathCreation: true,
}))
t.Cleanup(func() {
_ = ic.Close()
})

var userFunds = math.NewInt(10_000_000_000)
users := interchaintest.GetAndFundTestUsers(t, ctx, t.Name(), userFunds, chain)
chainUser := users[0]
fmt.Println("chainUser", chainUser)

n := chain.GetNode()

// valCommonAddr, err := chain.GetAddress(ctx, "validator")
// require.NoError(t, err)

// // print valCommonAddr
// fmt.Println("valCommonAddr", string(valCommonAddr))

// // celestiavaloper (could also just query from staking?)
// valAddr, err := sdk.GetFromBech32(string(valCommonAddr), cfg.Bech32Prefix+"valoper")
// require.NoError(t, err)

// fmt.Println("valAddr", string(valAddr))

fmt.Println("n.ValidatorMnemonic", n.ValidatorMnemonic)

// qgb register validator
vals, err := chain.StakingQueryValidators(ctx, stakingtypes.BondStatusBonded)
require.NoError(t, err)

// fmt.Println("n.Sidecars", n.Sidecars[0])

// recover the validator key (from mnemonic) to the da layer home ( https://github.com/rollkit/local-celestia-devnet/blob/main/entrypoint.sh#L57 )
if err := RecoverKey(ctx, n, "validator", n.ValidatorMnemonic, path.Join(NODE_PATH, "keys")); err != nil {
t.Fatal(err)
}

// wait for 100 blocks
// testutil.WaitForBlocks(ctx, 100, chain)

genesisHash := getGenesisBlockHash(ctx, n, 0)
fmt.Println("genesisHash", genesisHash)

err = n.NewSidecarProcessFromConfig(ctx, ibc.SidecarConfig{
ValidatorProcess: true,
ProcessName: "celestia-da",
Image: ibc.DockerImage{
Repository: DockerImage,
Version: DockerVersion,
UidGid: "1025:1025",
},
HomeDir: hardcodedPath,
Ports: []string{"26650"},
StartCmd: []string{
"celestia-da", "bridge", "init", "--node.store", NODE_PATH,
},
Env: []string{
"CELESTIA_CUSTOM=test:" + genesisHash, // TODO: does this fix bridge start?
},
PreStart: false,
})
if err != nil {
t.Fatal(fmt.Errorf("failed to start celestia-da: %w", err))
}

sc := n.Sidecars[0]
if err := sc.CreateContainer(ctx); err != nil {
t.Fatal(err)
}
if err := sc.StartContainer(ctx); err != nil {
t.Fatal(err)
}

// waits for 20 seconds (2s blocks * 10)
testutil.WaitForBlocks(ctx, 5, chain)

stdout, stderr, err := sc.Exec(ctx, []string{
"celestia-da", "bridge", "start",
"--node.store", NODE_PATH,
"--gateway",
"--core.ip", n.HostName(), // "test-val-0-TestRollkitCelestia" = n.HostName()
"--keyring.accname", "validator",
"--gateway.addr", "0.0.0.0",
"--rpc.addr", "0.0.0.0",
"--da.grpc.namespace", CELESTIA_NAMESPACE,
"--da.grpc.listen", "0.0.0.0:26650",
}, nil)
fmt.Println("stdout", stdout)
fmt.Println("stderr", stderr)
fmt.Println("err", err)

testutil.WaitForBlocks(ctx, 5, chain)

// register teh EVM address
// # private key: da6ed55cb2894ac2c9c10209c09de8e8b9d109b910338d5bf3d747a7e1fc9eb9
txHash, err := n.ExecTx(ctx, "validator", "qgb", "register", vals[0].OperatorAddress, "0x966e6f22781EF6a6A82BBB4DB3df8E225DfD9488", "--fees", "30000utia", "-b", "block", "-y")
require.NoError(t, err)

// convert txHash to response (this does not print, but works upon manually querying against the local node)
res, err := n.TxHashToResponse(ctx, txHash)
require.NoError(t, err)
fmt.Printf("res: %+v\n", res)

// wait for 100 blocks
testutil.WaitForBlocks(ctx, 100, chain)

}

func RecoverKey(ctx context.Context, tn *cosmos.ChainNode, keyName, mnemonic, homeDir string) error {
command := []string{
"sh",
"-c",
fmt.Sprintf(`echo %q | %s keys add %s --recover --keyring-backend %s --coin-type %s --home %s --output json`, mnemonic, tn.Chain.Config().Bin, keyName, keyring.BackendTest, tn.Chain.Config().CoinType, homeDir),
}

_, _, err := tn.Exec(ctx, command, nil)
return err
}

func getGenesisBlockHash(ctx context.Context, node *cosmos.ChainNode, attempt int) string {
// GENESIS=$(curl -s | jq '.result.block_id.hash' | tr -d '"')

type CelestiaBlock struct {
BlockID struct {
Hash string `json:"hash"`
} `json:"block_id"`
}

// stdout, stderr, err := node.ExecQuery(ctx, "block", "1", "--log_format=json") // --output breaks celestia ofc....

// docker run reece/full-celestia:v0.0.1 celestia-appd q block 1 --node=http://172.17.0.1:26657

cmd := []string{"query", "block", "1", "--log_format=json", "--node=http://" + node.HostName() + ":26657"}
stdout, _, err := node.ExecBin(ctx, cmd...)
// if err != nil {
// log.Fatal(err)
// }
fmt.Println("stdout", string(stdout))
fmt.Println("err", err)

// put stdout into genesis struct
var g CelestiaBlock
json.Unmarshal(stdout, &g)

if g.BlockID.Hash == "" {
if attempt > 15 {
panic("could not get genesis block hash")
}
time.Sleep(1 * time.Second)
return getGenesisBlockHash(ctx, node, attempt+1)
}

return g.BlockID.Hash
}
Loading
Loading