From 151fd85f34c7f14bf20da04d4a6eef97c2e04e4c Mon Sep 17 00:00:00 2001 From: Reece Williams Date: Fri, 13 Sep 2024 20:39:54 -0500 Subject: [PATCH 1/2] remove base blocksdb --- blockdb/chain.go | 87 ----- blockdb/chain_test.go | 185 ----------- blockdb/collect.go | 115 ------- blockdb/collect_test.go | 175 ---------- blockdb/doc.go | 2 - blockdb/messages_view_test.go | 317 ------------------- blockdb/migrate.go | 282 ----------------- blockdb/migrate_test.go | 45 --- blockdb/query.go | 200 ------------ blockdb/query_test.go | 218 ------------- blockdb/sql.go | 41 --- blockdb/sql_test.go | 148 --------- blockdb/test_case.go | 46 --- blockdb/test_case_test.go | 95 ------ blockdb/testdata/README.md | 9 - blockdb/testdata/sample_txs.json | 13 - blockdb/tui/help.go | 91 ------ blockdb/tui/maincontent_string.go | 26 -- blockdb/tui/model.go | 90 ------ blockdb/tui/model_test.go | 16 - blockdb/tui/presenter/cosmos_message.go | 54 ---- blockdb/tui/presenter/cosmos_message_test.go | 83 ----- blockdb/tui/presenter/highlight.go | 43 --- blockdb/tui/presenter/highlight_test.go | 51 --- blockdb/tui/presenter/test_case.go | 32 -- blockdb/tui/presenter/test_case_test.go | 44 --- blockdb/tui/presenter/time.go | 8 - blockdb/tui/presenter/tx.go | 65 ---- blockdb/tui/presenter/tx_test.go | 69 ---- blockdb/tui/style.go | 13 - blockdb/tui/update.go | 138 -------- blockdb/tui/update_test.go | 217 ------------- blockdb/tui/views.go | 282 ----------------- blockdb/views_test.go | 141 --------- chainset.go | 76 +---- cmd/interchaintest/interchaintest_test.go | 51 --- interchain.go | 4 - 37 files changed, 2 insertions(+), 3570 deletions(-) delete mode 100644 blockdb/chain.go delete mode 100644 blockdb/chain_test.go delete mode 100644 blockdb/collect.go delete mode 100644 blockdb/collect_test.go delete mode 100644 blockdb/doc.go delete mode 100644 blockdb/messages_view_test.go delete mode 100644 blockdb/migrate.go delete mode 100644 blockdb/migrate_test.go delete mode 100644 blockdb/query.go delete mode 100644 blockdb/query_test.go delete mode 100644 blockdb/sql.go delete mode 100644 blockdb/sql_test.go delete mode 100644 blockdb/test_case.go delete mode 100644 blockdb/test_case_test.go delete mode 100644 blockdb/testdata/README.md delete mode 100644 blockdb/testdata/sample_txs.json delete mode 100644 blockdb/tui/help.go delete mode 100644 blockdb/tui/maincontent_string.go delete mode 100644 blockdb/tui/model.go delete mode 100644 blockdb/tui/model_test.go delete mode 100644 blockdb/tui/presenter/cosmos_message.go delete mode 100644 blockdb/tui/presenter/cosmos_message_test.go delete mode 100644 blockdb/tui/presenter/highlight.go delete mode 100644 blockdb/tui/presenter/highlight_test.go delete mode 100644 blockdb/tui/presenter/test_case.go delete mode 100644 blockdb/tui/presenter/test_case_test.go delete mode 100644 blockdb/tui/presenter/time.go delete mode 100644 blockdb/tui/presenter/tx.go delete mode 100644 blockdb/tui/presenter/tx_test.go delete mode 100644 blockdb/tui/style.go delete mode 100644 blockdb/tui/update.go delete mode 100644 blockdb/tui/update_test.go delete mode 100644 blockdb/tui/views.go delete mode 100644 blockdb/views_test.go diff --git a/blockdb/chain.go b/blockdb/chain.go deleted file mode 100644 index 2694d2b2d..000000000 --- a/blockdb/chain.go +++ /dev/null @@ -1,87 +0,0 @@ -package blockdb - -import ( - "context" - "database/sql" - "fmt" - "hash/fnv" - - "golang.org/x/sync/singleflight" -) - -// Chain tracks its blocks and a block's transactions. -type Chain struct { - db *sql.DB - id int64 - single singleflight.Group -} - -type transactions []Tx - -func (txs transactions) Hash() []byte { - h := fnv.New32() - for _, tx := range txs { - h.Write(tx.Data) - } - return h.Sum(nil) -} - -// SaveBlock tracks a block at height with its transactions. -// This method is idempotent and can be safely called multiple times with the same arguments. -// The txs should be human-readable. -func (chain *Chain) SaveBlock(ctx context.Context, height int64, txs []Tx) error { - k := fmt.Sprintf("%d-%x", height, transactions(txs).Hash()) - _, err, _ := chain.single.Do(k, func() (any, error) { - return nil, chain.saveBlock(ctx, height, txs) - }) - return err -} - -func (chain *Chain) saveBlock(ctx context.Context, height int64, txs transactions) error { - dbTx, err := chain.db.BeginTx(ctx, nil) - if err != nil { - return err - } - defer func() { _ = dbTx.Rollback() }() - - res, err := dbTx.ExecContext(ctx, `INSERT OR REPLACE INTO block(height, fk_chain_id, created_at) VALUES (?, ?, ?)`, height, chain.id, nowRFC3339()) - if err != nil { - return fmt.Errorf("insert into block: %w", err) - } - - blockID, err := res.LastInsertId() - if err != nil { - return err - } - for _, tx := range txs { - txRes, err := dbTx.ExecContext(ctx, `INSERT INTO tx(data, fk_block_id) VALUES (?, ?)`, string(tx.Data), blockID) - if err != nil { - return fmt.Errorf("insert into tx: %w", err) - } - txID, err := txRes.LastInsertId() - if err != nil { - return err - } - - for _, e := range tx.Events { - eventRes, err := dbTx.ExecContext(ctx, `INSERT INTO tendermint_event(type, fk_tx_id) VALUES (?, ?)`, e.Type, txID) - if err != nil { - return fmt.Errorf("insert into tendermint_event: %w", err) - } - - eventID, err := eventRes.LastInsertId() - if err != nil { - return err - } - - for _, attr := range e.Attributes { - _, err := dbTx.ExecContext(ctx, `INSERT INTO tendermint_event_attr(key, value, fk_event_id) VALUES (?, ?, ?)`, attr.Key, attr.Value, eventID) - if err != nil { - return fmt.Errorf("insert into tendermint_event_attr: %w", err) - } - } - } - } - - return dbTx.Commit() -} diff --git a/blockdb/chain_test.go b/blockdb/chain_test.go deleted file mode 100644 index 7180f8dbd..000000000 --- a/blockdb/chain_test.go +++ /dev/null @@ -1,185 +0,0 @@ -package blockdb - -import ( - "context" - "database/sql" - "fmt" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -func validChain(t *testing.T, db *sql.DB) *Chain { - t.Helper() - - tc, err := CreateTestCase(context.Background(), db, "TestCase", "112233") - require.NoError(t, err) - c, err := tc.AddChain(context.Background(), "chain1", "cosmos") - require.NoError(t, err) - return c -} - -func TestChain_SaveBlock(t *testing.T) { - t.Parallel() - - var ( - ctx = context.Background() - tx1 = Tx{Data: []byte(`{"test":0}`)} - tx2 = Tx{ - Data: []byte(`{"test":1}`), - Events: []Event{ - { - Type: "e1", - Attributes: []EventAttribute{ - {Key: "k1", Value: "v1"}, - }, - }, - { - Type: "e2", - Attributes: []EventAttribute{ - {Key: "k2", Value: "v2"}, - {Key: "k3", Value: "v3"}, - }, - }, - }, - } - ) - - t.Run("happy path", func(t *testing.T) { - db := migratedDB() - defer db.Close() - - chain := validChain(t, db) - - err := chain.SaveBlock(ctx, 5, []Tx{tx1, tx2}) - require.NoError(t, err) - - row := db.QueryRow(`SELECT height, fk_chain_id, created_at FROM block LIMIT 1`) - var ( - gotHeight int - gotChainID int - gotCreatedAt string - ) - err = row.Scan(&gotHeight, &gotChainID, &gotCreatedAt) - require.NoError(t, err) - - require.Equal(t, 5, gotHeight) - require.Equal(t, 1, gotChainID) - - ts, err := time.Parse(time.RFC3339, gotCreatedAt) - require.NoError(t, err) - require.WithinDuration(t, ts, time.Now(), 10*time.Second) - - rows, err := db.Query(`SELECT data, fk_block_id FROM tx`) - require.NoError(t, err) - defer rows.Close() - var i int - for rows.Next() { - var ( - gotData string - gotBlockID int - ) - require.NoError(t, rows.Scan(&gotData, &gotBlockID)) - require.Equal(t, 1, gotBlockID) - require.JSONEq(t, fmt.Sprintf(`{"test":%d}`, i), gotData) - i++ - } - require.Equal(t, 2, i) - - rows, err = db.Query(`SELECT -tx.data, tendermint_event.type, key, value -FROM tendermint_event_attr -LEFT JOIN tendermint_event ON tendermint_event.id = fk_event_id -LEFT JOIN tx ON tx.id = tendermint_event.fk_tx_id -ORDER BY tendermint_event_attr.id`) - require.NoError(t, err) - defer rows.Close() - for i = 0; rows.Next(); i++ { - var gotData, gotType, gotKey, gotValue string - require.NoError(t, rows.Scan(&gotData, &gotType, &gotKey, &gotValue)) - require.Equal(t, gotData, `{"test":1}`) - switch i { - case 0: - require.Equal(t, gotType, "e1") - require.Equal(t, gotKey, "k1") - require.Equal(t, gotValue, "v1") - case 1: - require.Equal(t, gotType, "e2") - require.Equal(t, gotKey, "k2") - require.Equal(t, gotValue, "v2") - case 2: - require.Equal(t, gotType, "e2") - require.Equal(t, gotKey, "k3") - require.Equal(t, gotValue, "v3") - default: - t.Fatalf("expected 3 results, got i=%d", i) - } - } - }) - - t.Run("idempotent", func(t *testing.T) { - db := migratedDB() - defer db.Close() - - chain := validChain(t, db) - - err := chain.SaveBlock(ctx, 1, []Tx{tx2}) - require.NoError(t, err) - err = chain.SaveBlock(ctx, 1, []Tx{tx2}) - require.NoError(t, err) - - row := db.QueryRow(`SELECT count(*) FROM block`) - var count int - err = row.Scan(&count) - require.NoError(t, err) - require.Equal(t, 1, count) - - row = db.QueryRow(`SELECT count(*) FROM tx`) - err = row.Scan(&count) - require.NoError(t, err) - require.Equal(t, 1, count) - - row = db.QueryRow(`SELECT count(*) FROM tendermint_event`) - err = row.Scan(&count) - require.NoError(t, err) - require.Equal(t, 2, count) - - row = db.QueryRow(`SELECT count(*) FROM tendermint_event_attr`) - err = row.Scan(&count) - require.NoError(t, err) - require.Equal(t, 3, count) - }) - - t.Run("zero state", func(t *testing.T) { - db := migratedDB() - defer db.Close() - - chain := validChain(t, db) - - err := chain.SaveBlock(ctx, 5, nil) - require.NoError(t, err) - - row := db.QueryRow(`SELECT height FROM block LIMIT 1`) - var gotHeight int - err = row.Scan(&gotHeight) - require.NoError(t, err) - require.Equal(t, 5, gotHeight) - - var count int - row = db.QueryRow(`SELECT count(*) FROM tx`) - err = row.Scan(&count) - require.NoError(t, err) - require.Zero(t, count) - - row = db.QueryRow(`SELECT count(*) FROM tendermint_event`) - err = row.Scan(&count) - require.NoError(t, err) - require.Zero(t, count) - - row = db.QueryRow(`SELECT count(*) FROM tendermint_event_attr`) - err = row.Scan(&count) - require.NoError(t, err) - require.Zero(t, count) - }) -} diff --git a/blockdb/collect.go b/blockdb/collect.go deleted file mode 100644 index adf5f2607..000000000 --- a/blockdb/collect.go +++ /dev/null @@ -1,115 +0,0 @@ -package blockdb - -import ( - "context" - "fmt" - "strings" - "time" - - "go.uber.org/zap" -) - -type Tx struct { - // For Tendermint transactions, this should be encoded as JSON. - // Otherwise, this should be a human-readable format if possible. - Data []byte - - // Events associated with the transaction, if applicable. - Events []Event -} - -// Event is an alternative representation of tendermint/abci/types.Event, -// so that the blockdb package does not depend directly on tendermint. -type Event struct { - Type string - Attributes []EventAttribute - - // Notably, not including the Index field from the tendermint event. - // The ABCI docs state: - // - // "The index flag notifies the Tendermint indexer to index the attribute. The value of the index flag is non-deterministic and may vary across different nodes in the network." -} - -type EventAttribute struct { - Key, Value string -} - -// TxFinder finds transactions given block at height. -type TxFinder interface { - FindTxs(ctx context.Context, height int64) ([]Tx, error) -} - -// BlockSaver saves transactions for block at height. -type BlockSaver interface { - SaveBlock(ctx context.Context, height int64, txs []Tx) error -} - -// Collector saves block transactions at regular intervals. -type Collector struct { - finder TxFinder - log *zap.Logger - rate time.Duration - saver BlockSaver - cancel context.CancelFunc -} - -// NewCollector creates a valid Collector that polls every duration at rate. -// The rate should be less than the time it takes to produce a block. -// Typically, a rate that will collect a few times a second is sufficient such as 100-200ms. -func NewCollector(log *zap.Logger, finder TxFinder, saver BlockSaver, rate time.Duration) *Collector { - return &Collector{ - finder: finder, - log: log, - rate: rate, - saver: saver, - } -} - -// Collect saves block transactions starting at height 1 and advancing by 1 height as long as there are -// no errors with finding or saving the transactions. -func (p *Collector) Collect(ctx context.Context) { - ctx, p.cancel = context.WithCancel(ctx) - defer p.cancel() - - tick := time.NewTicker(p.rate) - defer tick.Stop() - var height int64 = 1 - for { - select { - case <-ctx.Done(): - return - case <-tick.C: - if err := p.saveTxsForHeight(ctx, height); err != nil { - if strings.Contains(err.Error(), "must be less than or equal to the current blockchain height") { - // (I could not find a more precise way to match this error.) - // Don't log because it happens frequently and is expected. - continue - } - - p.log.Info("Failed to save transactions", zap.Error(err), zap.Int64("height", height)) - continue - } - height++ - } - } -} - -// Stop terminates the Collect loop. -// Stop is safe to be called concurrently and is safe to be called multiple times. -// -// If Collect has not been called, Stop panics. -func (p *Collector) Stop() { - p.cancel() -} - -func (p *Collector) saveTxsForHeight(ctx context.Context, height int64) error { - txs, err := p.finder.FindTxs(ctx, height) - if err != nil { - return fmt.Errorf("find txs: %w", err) - } - err = p.saver.SaveBlock(ctx, height, txs) - if err != nil { - return fmt.Errorf("save block: %w", err) - } - return nil -} diff --git a/blockdb/collect_test.go b/blockdb/collect_test.go deleted file mode 100644 index 2a6159aae..000000000 --- a/blockdb/collect_test.go +++ /dev/null @@ -1,175 +0,0 @@ -package blockdb - -import ( - "context" - "errors" - "runtime" - "strconv" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/stretchr/testify/require" - "go.uber.org/zap" - "golang.org/x/sync/errgroup" -) - -type mockTxFinder func(ctx context.Context, height int64) ([]Tx, error) - -func (f mockTxFinder) FindTxs(ctx context.Context, height int64) ([]Tx, error) { - return f(ctx, height) -} - -type mockBlockSaver func(ctx context.Context, height int64, txs []Tx) error - -func (f mockBlockSaver) SaveBlock(ctx context.Context, height int64, txs []Tx) error { - return f(ctx, height, txs) -} - -func TestCollector_Collect(t *testing.T) { - nopLog := zap.NewNop() - - t.Run("happy path", func(t *testing.T) { - finder := mockTxFinder(func(ctx context.Context, height int64) ([]Tx, error) { - if height == 0 { - panic("zero height") - } - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - if height > 3 { - return nil, nil - } - return []Tx{{Data: []byte(strconv.FormatInt(height, 10))}}, nil - } - }) - - var ( - currentHeight int64 - savedHeights []int - savedTxs [][]Tx - ) - saver := mockBlockSaver(func(ctx context.Context, height int64, txs []Tx) error { - select { - case <-ctx.Done(): - return ctx.Err() - default: - savedHeights = append(savedHeights, int(height)) - savedTxs = append(savedTxs, txs) - } - atomic.SwapInt64(¤tHeight, int64(height)) - return nil - }) - - collector := NewCollector(nopLog, finder, saver, time.Nanosecond) - defer collector.Stop() - - ctx, cancel := context.WithCancel(context.Background()) - var eg errgroup.Group - - eg.Go(func() error { - collector.Collect(ctx) - return nil - }) - eg.Go(func() error { - for atomic.LoadInt64(¤tHeight) <= 3 { - } - cancel() - return nil - }) - - require.NoError(t, eg.Wait()) - require.Equal(t, []int{1, 2, 3}, savedHeights[:3]) - require.Equal(t, "1", string(savedTxs[0][0].Data)) - require.Equal(t, "2", string(savedTxs[1][0].Data)) - require.Equal(t, "3", string(savedTxs[2][0].Data)) - }) - - t.Run("find error", func(t *testing.T) { - ch := make(chan int) - finder := mockTxFinder(func(ctx context.Context, height int64) ([]Tx, error) { - defer func() { ch <- int(height) }() - if height == 1 { - return nil, nil - } - return nil, errors.New("boom") - }) - saver := mockBlockSaver(func(ctx context.Context, height int64, txs []Tx) error { return nil }) - - collector := NewCollector(nopLog, finder, saver, time.Nanosecond) - defer collector.Stop() - go collector.Collect(context.Background()) - - require.Equal(t, 1, <-ch) - require.Equal(t, 2, <-ch) - require.Equal(t, 2, <-ch) // assert height stops advancing - }) - - t.Run("save error", func(t *testing.T) { - ch := make(chan int) - finder := mockTxFinder(func(ctx context.Context, height int64) ([]Tx, error) { - defer func() { ch <- int(height) }() - return nil, nil - }) - saver := mockBlockSaver(func(ctx context.Context, height int64, txs []Tx) error { - if height == 1 { - return nil - } - return errors.New("boom") - }) - - collector := NewCollector(nopLog, finder, saver, time.Nanosecond) - defer collector.Stop() - go collector.Collect(context.Background()) - - require.Equal(t, 1, <-ch) - require.Equal(t, 2, <-ch) - require.Equal(t, 2, <-ch) // assert height stops advancing - }) -} - -func TestCollector_Stop(t *testing.T) { - // Synchronization control to allow test to progress without a data race. - // Begins locked, unlocks from the finder, and the test blocks trying to re-lock it. - var foundMu sync.Mutex - foundMu.Lock() - - // Ensures the finder only unlocks the mutex once. - var foundOnce sync.Once - - finder := mockTxFinder(func(ctx context.Context, height int64) ([]Tx, error) { - foundOnce.Do(func() { - foundMu.Unlock() - }) - return nil, nil - }) - saver := mockBlockSaver(func(ctx context.Context, height int64, txs []Tx) error { return nil }) - - c := NewCollector(zap.NewNop(), finder, saver, time.Millisecond) - defer c.Stop() // Will be stopped explicitly in a few lines, but defer anyway for cleanup just in case. - - n := runtime.NumGoroutine() - go c.Collect(context.Background()) - - // Block until the finder was called at least once. - foundMu.Lock() - - // At least one goroutine was created. - require.Greater(t, runtime.NumGoroutine(), n) - - c.Stop() - - // require.Eventually would be nice here, but that starts its own goroutine, - // which defeats the purpose of this test. - deadline := time.Now().Add(2 * time.Second) - for time.Now().Before(deadline) { - if runtime.NumGoroutine() == n { - return - } - time.Sleep(5 * time.Millisecond) - } - - require.Failf(t, "goroutine count did not drop after stopping collector", "want %d, got %d", n, runtime.NumGoroutine()) -} diff --git a/blockdb/doc.go b/blockdb/doc.go deleted file mode 100644 index 190e3881d..000000000 --- a/blockdb/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package blockdb saves chain and block data for inspection later to aid in debugging interchaintest failures. -package blockdb diff --git a/blockdb/messages_view_test.go b/blockdb/messages_view_test.go deleted file mode 100644 index 442c563a7..000000000 --- a/blockdb/messages_view_test.go +++ /dev/null @@ -1,317 +0,0 @@ -package blockdb_test - -// This test is in a separate file, so it can be in the blockdb_test package, -// so it can import interchaintest without creating an import cycle. - -import ( - "context" - "database/sql" - "path/filepath" - "testing" - - "cosmossdk.io/math" - "github.com/cosmos/cosmos-sdk/types" - "github.com/strangelove-ventures/interchaintest/v8" - "github.com/strangelove-ventures/interchaintest/v8/ibc" - "github.com/strangelove-ventures/interchaintest/v8/relayer" - "github.com/strangelove-ventures/interchaintest/v8/testreporter" - "github.com/strangelove-ventures/interchaintest/v8/testutil" - "github.com/stretchr/testify/require" - "go.uber.org/zap/zaptest" -) - -func TestMessagesView(t *testing.T) { - if testing.Short() { - t.Skip("skipping in short mode") - } - - t.Parallel() - - client, network := interchaintest.DockerSetup(t) - - const gaia0ChainID = "g0" - const gaia1ChainID = "g1" - cf := interchaintest.NewBuiltinChainFactory(zaptest.NewLogger(t), []*interchaintest.ChainSpec{ - {Name: "gaia", Version: "v7.0.1", ChainConfig: ibc.ChainConfig{ChainID: gaia0ChainID}}, - {Name: "gaia", Version: "v7.0.1", ChainConfig: ibc.ChainConfig{ChainID: gaia1ChainID}}, - }) - - chains, err := cf.Chains(t.Name()) - require.NoError(t, err) - - gaia0, gaia1 := chains[0], chains[1] - - rf := interchaintest.NewBuiltinRelayerFactory(ibc.CosmosRly, zaptest.NewLogger(t)) - r := rf.Build(t, client, network) - - ic := interchaintest.NewInterchain(). - AddChain(gaia0). - AddChain(gaia1). - AddRelayer(r, "r"). - AddLink(interchaintest.InterchainLink{ - Chain1: gaia0, - Chain2: gaia1, - Relayer: r, - }) - - dbDir := interchaintest.TempDir(t) - dbPath := filepath.Join(dbDir, "blocks.db") - - rep := testreporter.NewNopReporter() - eRep := rep.RelayerExecReporter(t) - - ctx := context.Background() - require.NoError(t, ic.Build(ctx, eRep, interchaintest.InterchainBuildOptions{ - TestName: t.Name(), - Client: client, - NetworkID: network, - - SkipPathCreation: true, - - BlockDatabaseFile: dbPath, - })) - - // The database should exist on disk, - // but no transactions should have happened yet. - db, err := sql.Open("sqlite", dbPath) - require.NoError(t, err) - defer db.Close() - - // Copy the busy timeout from the migration. - // The journal_mode pragma should be persisted on disk, so we should not need to set that here. - _, err = db.Exec(`PRAGMA busy_timeout = 3000`) - require.NoError(t, err) - - var count int - row := db.QueryRow(`SELECT COUNT(*) FROM v_cosmos_messages`) - require.NoError(t, row.Scan(&count)) - require.Equal(t, count, 0) - - // Generate the path. - // No transactions happen here. - const pathName = "p" - require.NoError(t, r.GeneratePath(ctx, eRep, gaia0ChainID, gaia1ChainID, pathName)) - - t.Run("create clients", func(t *testing.T) { - // Creating the clients will cause transactions. - require.NoError(t, r.CreateClients(ctx, eRep, pathName, ibc.DefaultClientOpts())) - - // MsgCreateClient should match the opposite chain IDs. - const qCreateClient = `SELECT -client_chain_id -FROM v_cosmos_messages -WHERE type = "/ibc.core.client.v1.MsgCreateClient" AND chain_id = ?;` - var clientChainID string - require.NoError(t, db.QueryRow(qCreateClient, gaia0ChainID).Scan(&clientChainID)) - require.Equal(t, clientChainID, gaia1ChainID) - - require.NoError(t, db.QueryRow(qCreateClient, gaia1ChainID).Scan(&clientChainID)) - require.Equal(t, clientChainID, gaia0ChainID) - }) - if t.Failed() { - return - } - - var gaia0ClientID, gaia0ConnID, gaia1ClientID, gaia1ConnID string - t.Run("create connections", func(t *testing.T) { - // The client isn't created immediately -- wait for two blocks to ensure the clients are ready. - require.NoError(t, testutil.WaitForBlocks(ctx, 2, gaia0, gaia1)) - - // Next, create the connections. - require.NoError(t, r.CreateConnections(ctx, eRep, pathName)) - - // Wait for another block before retrieving the connections and querying for them. - require.NoError(t, testutil.WaitForBlocks(ctx, 1, gaia0, gaia1)) - - conns, err := r.GetConnections(ctx, eRep, gaia0ChainID) - require.NoError(t, err) - - // Collect the reported client IDs. - gaia0ConnID = conns[0].ID - gaia0ClientID = conns[0].ClientID - gaia1ConnID = conns[0].Counterparty.ConnectionId - gaia1ClientID = conns[0].Counterparty.ClientId - - // OpenInit happens on first chain. - const qConnectionOpenInit = `SELECT -client_id, counterparty_client_id -FROM v_cosmos_messages -WHERE type = "/ibc.core.connection.v1.MsgConnectionOpenInit" AND chain_id = ? -` - var clientID, counterpartyClientID string - require.NoError(t, db.QueryRow(qConnectionOpenInit, gaia0ChainID).Scan(&clientID, &counterpartyClientID)) - require.Equal(t, clientID, gaia0ClientID) - require.Equal(t, counterpartyClientID, gaia1ClientID) - - // OpenTry happens on second chain. - const qConnectionOpenTry = `SELECT -counterparty_client_id, counterparty_conn_id -FROM v_cosmos_messages -WHERE type = "/ibc.core.connection.v1.MsgConnectionOpenTry" AND chain_id = ? -` - var counterpartyConnID string - require.NoError(t, db.QueryRow(qConnectionOpenTry, gaia1ChainID).Scan(&counterpartyClientID, &counterpartyConnID)) - require.Equal(t, counterpartyClientID, gaia0ClientID) - require.Equal(t, counterpartyConnID, gaia0ConnID) - - // OpenAck happens on first chain again. - const qConnectionOpenAck = `SELECT -conn_id, counterparty_conn_id -FROM v_cosmos_messages -WHERE type = "/ibc.core.connection.v1.MsgConnectionOpenAck" AND chain_id = ? -` - var connID string - require.NoError(t, db.QueryRow(qConnectionOpenAck, gaia0ChainID).Scan(&connID, &counterpartyConnID)) - require.Equal(t, connID, gaia0ConnID) - require.Equal(t, counterpartyConnID, gaia1ConnID) - - // OpenConfirm happens on second chain again. - const qConnectionOpenConfirm = `SELECT -conn_id -FROM v_cosmos_messages -WHERE type = "/ibc.core.connection.v1.MsgConnectionOpenConfirm" AND chain_id = ? -` - require.NoError(t, db.QueryRow(qConnectionOpenConfirm, gaia1ChainID).Scan(&connID)) - require.Equal(t, connID, gaia0ConnID) // Not sure if this should be connection 0 or 1, as they are typically equal during this test. - }) - if t.Failed() { - return - } - - const gaia0Port, gaia1Port = "transfer", "transfer" // Would be nice if these could differ. - var gaia0ChannelID, gaia1ChannelID string - t.Run("create channel", func(t *testing.T) { - require.NoError(t, r.CreateChannel(ctx, eRep, pathName, ibc.CreateChannelOptions{ - SourcePortName: gaia0Port, - DestPortName: gaia1Port, - Order: ibc.Unordered, - Version: "ics20-1", - })) - - // Wait for another block before retrieving the channels and querying for them. - require.NoError(t, testutil.WaitForBlocks(ctx, 1, gaia0, gaia1)) - - channels, err := r.GetChannels(ctx, eRep, gaia0ChainID) - require.NoError(t, err) - require.Len(t, channels, 1) - - gaia0ChannelID = channels[0].ChannelID - gaia1ChannelID = channels[0].Counterparty.ChannelID - - // OpenInit happens on first chain. - const qChannelOpenInit = `SELECT -port_id, counterparty_port_id -FROM v_cosmos_messages -WHERE type = "/ibc.core.channel.v1.MsgChannelOpenInit" AND chain_id = ? -` - var portID, counterpartyPortID string - require.NoError(t, db.QueryRow(qChannelOpenInit, gaia0ChainID).Scan(&portID, &counterpartyPortID)) - require.Equal(t, portID, gaia0Port) - require.Equal(t, counterpartyPortID, gaia1Port) - - // OpenTry happens on second chain. - const qChannelOpenTry = `SELECT -port_id, counterparty_port_id, counterparty_channel_id -FROM v_cosmos_messages -WHERE type = "/ibc.core.channel.v1.MsgChannelOpenTry" AND chain_id = ? -` - var counterpartyChannelID string - require.NoError(t, db.QueryRow(qChannelOpenTry, gaia1ChainID).Scan(&portID, &counterpartyPortID, &counterpartyChannelID)) - require.Equal(t, portID, gaia1Port) - require.Equal(t, counterpartyPortID, gaia0Port) - require.Equal(t, counterpartyChannelID, gaia0ChannelID) - - // OpenAck happens on first chain again. - const qChannelOpenAck = `SELECT -port_id, channel_id, counterparty_channel_id -FROM v_cosmos_messages -WHERE type = "/ibc.core.channel.v1.MsgChannelOpenAck" AND chain_id = ? -` - var channelID string - require.NoError(t, db.QueryRow(qChannelOpenAck, gaia0ChainID).Scan(&portID, &channelID, &counterpartyChannelID)) - require.Equal(t, portID, gaia0Port) - require.Equal(t, channelID, gaia0ChannelID) - require.Equal(t, counterpartyChannelID, gaia1ChannelID) - - // OpenConfirm happens on second chain again. - const qChannelOpenConfirm = `SELECT -port_id, channel_id -FROM v_cosmos_messages -WHERE type = "/ibc.core.channel.v1.MsgChannelOpenConfirm" AND chain_id = ? -` - require.NoError(t, db.QueryRow(qChannelOpenConfirm, gaia1ChainID).Scan(&portID, &channelID)) - require.Equal(t, portID, gaia1Port) - require.Equal(t, channelID, gaia1ChannelID) - }) - if t.Failed() { - return - } - - t.Run("initiate transfer", func(t *testing.T) { - // Build the faucet address for gaia1, so that gaia0 can send it a transfer. - g1FaucetAddrBytes, err := gaia1.GetAddress(ctx, interchaintest.FaucetAccountKeyName) - require.NoError(t, err) - gaia1FaucetAddr, err := types.Bech32ifyAddressBytes(gaia1.Config().Bech32Prefix, g1FaucetAddrBytes) - require.NoError(t, err) - - // Send the IBC transfer. Relayer isn't running, so this will just create a MsgTransfer. - const txAmount = 13579 // Arbitrary amount that is easy to find in logs. - transfer := ibc.WalletAmount{ - Address: gaia1FaucetAddr, - Denom: gaia0.Config().Denom, - Amount: math.NewInt(txAmount), - } - tx, err := gaia0.SendIBCTransfer(ctx, gaia0ChannelID, interchaintest.FaucetAccountKeyName, transfer, ibc.TransferOptions{}) - require.NoError(t, err) - require.NoError(t, tx.Validate()) - - const qMsgTransfer = `SELECT -port_id, channel_id -FROM v_cosmos_messages -WHERE type = "/ibc.applications.transfer.v1.MsgTransfer" AND chain_id = ? -` - var portID, channelID string - require.NoError(t, db.QueryRow(qMsgTransfer, gaia0ChainID).Scan(&portID, &channelID)) - require.Equal(t, portID, gaia0Port) - require.Equal(t, channelID, gaia0ChannelID) - }) - if t.Failed() { - return - } - - if !rf.Capabilities()[relayer.Flush] { - t.Skip("cannot continue due to missing capability Flush") - } - - t.Run("relay", func(t *testing.T) { - require.NoError(t, r.Flush(ctx, eRep, pathName, gaia0ChannelID)) - require.NoError(t, testutil.WaitForBlocks(ctx, 5, gaia0)) - - const qMsgRecvPacket = `SELECT -port_id, channel_id, counterparty_port_id, counterparty_channel_id -FROM v_cosmos_messages -WHERE type = "/ibc.core.channel.v1.MsgRecvPacket" AND chain_id = ? -` - - var portID, channelID, counterpartyPortID, counterpartyChannelID string - - require.NoError(t, db.QueryRow(qMsgRecvPacket, gaia1ChainID).Scan(&portID, &channelID, &counterpartyPortID, &counterpartyChannelID)) - - require.Equal(t, portID, gaia0Port) - require.Equal(t, channelID, gaia0ChannelID) - require.Equal(t, counterpartyPortID, gaia1Port) - require.Equal(t, counterpartyChannelID, gaia1ChannelID) - - const qMsgAck = `SELECT -port_id, channel_id, counterparty_port_id, counterparty_channel_id -FROM v_cosmos_messages -WHERE type = "/ibc.core.channel.v1.MsgAcknowledgement" AND chain_id = ? -` - require.NoError(t, db.QueryRow(qMsgAck, gaia0ChainID).Scan(&portID, &channelID, &counterpartyPortID, &counterpartyChannelID)) - - require.Equal(t, portID, gaia0Port) - require.Equal(t, channelID, gaia0ChannelID) - require.Equal(t, counterpartyPortID, gaia1Port) - require.Equal(t, counterpartyChannelID, gaia1ChannelID) - }) -} diff --git a/blockdb/migrate.go b/blockdb/migrate.go deleted file mode 100644 index 2ad1c027d..000000000 --- a/blockdb/migrate.go +++ /dev/null @@ -1,282 +0,0 @@ -package blockdb - -import ( - "database/sql" - "errors" - "fmt" - "strings" - - "modernc.org/sqlite" -) - -// Migrate migrates db in an idempotent manner. -// If an error is returned, it's acceptable to delete the database and start over. -// The basic ERD is as follows: -// -// ┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐ -// │ │ │ │ │ │ │ │ -// │ │ ╱│ │ ╱│ │ ╱│ │ -// │ Test Case │───────┼──│ Chain │───────○─│ Block │────────○─│ Tx │ -// │ │ ╲│ │ ╲│ │ ╲│ │ -// │ │ │ │ │ │ │ │ -// └────────────────────┘ └────────────────────┘ └────────────────────┘ └────────────────────┘ -// -// The gitSha ensures we can trace back to the version of the codebase that produced the schema. -// Warning: Typical best practice wraps each migration step into its own transaction. For simplicity given -// this is an embedded database, we omit transactions. -func Migrate(db *sql.DB, gitSha string) error { - // If a timeout is encountered, sleep and try again, - // up until the provided number of milliseconds. - // A 3000ms timeout worked fine on my workstation but failed the concurrency test on CI. - // - // Setting this makes it practical to have multiple test instances - // writing to the same database file. - // - // https://www.sqlite.org/pragma.html#pragma_busy_timeout - _, err := db.Exec(`PRAGMA busy_timeout = 4000`) - if err != nil { - return fmt.Errorf("pragma busy_timeout: %w", err) - } - - // Only setting the busy_timeout pragma is insufficient to get the concurrency test to pass. - // The WAL journal mode, supported by SQLite version 3.7.0 (2010-07-21) or later, - // is more forgiving about concurrent reads and writes: - // "WAL provides more concurrency as readers do not block writers and a writer does not block readers." - // - // https://www.sqlite.org/pragma.html#pragma_journal_mode - _, err = db.Exec(`PRAGMA journal_mode = WAL`) - if err != nil { - return fmt.Errorf("pragma journal_mode: %w", err) - } - - // TODO(nix 05-27-2022): Appropriate indexes? - _, err = db.Exec(`PRAGMA foreign_keys = ON`) - if err != nil { - return fmt.Errorf("pragma foreign_keys: %w", err) - } - - tx, err := db.Begin() - if err != nil { - return fmt.Errorf("begin tx: %w", err) - } - defer func() { - // If commit succeeded, rollback will return nil; - // if a step failed, the returned error is more meaningful. - _ = tx.Rollback() - }() - - _, err = tx.Exec(`CREATE TABLE IF NOT EXISTS schema_version( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - created_at TEXT NOT NULL CHECK (length(created_at) > 0), - git_sha TEXT NOT NULL CHECK (length(git_sha) > 0), - UNIQUE(git_sha) -)`) - if err != nil { - return fmt.Errorf("create table schema_version: %w", err) - } - - _, err = tx.Exec(`INSERT INTO schema_version(created_at, git_sha) VALUES (?, ?) -ON CONFLICT(git_sha) DO UPDATE SET git_sha=git_sha`, nowRFC3339(), gitSha) - if err != nil { - return fmt.Errorf("upsert schema_version with git sha %s: %w", gitSha, err) - } - - _, err = tx.Exec(`CREATE TABLE IF NOT EXISTS test_case ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL CHECK ( length(name) > 0 ), - git_sha TEXT NOT NULL CHECK ( length(git_sha) > 0 ), - created_at TEXT NOT NULL CHECK (length(created_at) > 0), - UNIQUE(name,created_at) -)`) - if err != nil { - return fmt.Errorf("create table test_case: %w", err) - } - _, err = tx.Exec(`CREATE TABLE IF NOT EXISTS chain ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - chain_id TEXT NOT NULL CHECK ( length(chain_id) > 0 ), - fk_test_id INTEGER, - FOREIGN KEY(fk_test_id) REFERENCES test_case(id) ON DELETE CASCADE, - UNIQUE(chain_id,fk_test_id) -)`) - if err != nil { - return fmt.Errorf("create table chain: %w", err) - } - _, err = tx.Exec(`CREATE TABLE IF NOT EXISTS block ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - height INTEGER NOT NULL CHECK (length(height > 0)), - fk_chain_id INTEGER, - created_at TEXT NOT NULL CHECK (length(created_at) > 0), - FOREIGN KEY(fk_chain_id) REFERENCES chain(id) ON DELETE CASCADE, - UNIQUE(height,fk_chain_id) -)`) - if err != nil { - return fmt.Errorf("create table block: %w", err) - } - _, err = tx.Exec(`CREATE TABLE IF NOT EXISTS tx ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - data TEXT NOT NULL CHECK (length(data > 0)), - fk_block_id INTEGER, - FOREIGN KEY(fk_block_id) REFERENCES block(id) ON DELETE CASCADE -)`) - if err != nil { - return fmt.Errorf("create table tx: %w", err) - } - - _, err = tx.Exec(`ALTER TABLE chain ADD COLUMN chain_type TEXT NOT NULL check(length(chain_type) > 0) DEFAULT "unknown"`) - if errIgnoreDuplicateColumn(err, "chain_type") != nil { - return fmt.Errorf("alter table chain add chain_type: %w", err) - } - - _, err = tx.Exec(`CREATE TABLE IF NOT EXISTS tendermint_event ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - type TEXT NOT NULL CHECK (length(type) > 0), - fk_tx_id INTEGER, - FOREIGN KEY(fk_tx_id) REFERENCES tx(id) ON DELETE CASCADE -)`) - if err != nil { - return fmt.Errorf("create table tendermint_event: %w", err) - } - - _, err = tx.Exec(`CREATE TABLE IF NOT EXISTS tendermint_event_attr ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - key TEXT NOT NULL CHECK (length(key) > 0), - value TEXT NOT NULL, - fk_event_id INTEGER, - FOREIGN KEY(fk_event_id) REFERENCES tendermint_event(id) ON DELETE CASCADE -)`) - if err != nil { - return fmt.Errorf("create table tendermint_event: %w", err) - } - - // Creating views should be last migration step. - if err := upsertViews(tx); err != nil { - // Error already wrapped. - return err - } - - if err := tx.Commit(); err != nil { - return fmt.Errorf("committing migrations: %w", err) - } - - return nil -} - -// upsertViews should be idempotent by dropping/re-creating the view. The drop/re-create makes view authoring simpler -// in case table columns are altered, added, or dropped. -// Performance impact is negligible since views are essentially stored queries. -func upsertViews(tx *sql.Tx) error { - // Drop and recreate views because it's performant and allows changing columns in earlier migration steps. - - _, err := tx.Exec(`DROP VIEW IF EXISTS v_tx_flattened`) - if err != nil { - return fmt.Errorf("drop old v_tx_flattened view: %w", err) - } - - _, err = tx.Exec(`CREATE VIEW v_tx_flattened AS -SELECT - test_case.id as test_case_id - , test_case.created_at as test_case_created_at - , test_case.name as test_case_name - , chain.id as chain_kid - , chain.chain_id as chain_id - , chain.chain_type as chain_type - , block.id as block_id - , block.created_at as block_created_at - , block.height as block_height - , tx.id as tx_id - , tx.data as tx -FROM tx -LEFT JOIN block ON tx.fk_block_id = block.id -LEFT JOIN chain ON block.fk_chain_id = chain.id -LEFT JOIN test_case ON chain.fk_test_id = test_case.id -`) - if err != nil { - return fmt.Errorf("create v_tx_flattened view: %w", err) - } - - _, err = tx.Exec(`DROP VIEW IF EXISTS v_cosmos_messages`) - if err != nil { - return fmt.Errorf("drop old v_cosmos_messages view: %w", err) - } - _, err = tx.Exec(`CREATE VIEW v_cosmos_messages AS -SELECT - test_case_id - , test_case_name - , chain_kid - , chain_id - , block_id - , block_height - , tx_id - , key as msg_n -- message position within the tx - , json_extract(value, "$.@type") as type - , json_extract(value, "$.client_state.chain_id") as client_chain_id - , json_extract(value, "$.client_id") as client_id - , json_extract(value, "$.counterparty.client_id") as counterparty_client_id - , json_extract(value, "$.connection_id") as conn_id - , COALESCE( - json_extract(value, "$.counterparty_connection_id"), -- ConnectionOpenAck - json_extract(value, "$.counterparty.connection_id") -- ConnectionOpenTry - ) as counterparty_conn_id - , COALESCE( - json_extract(value, "$.port_id"), -- ChannelOpen* - json_extract(value, "$.source_port"), -- MsgTransfer - json_extract(value, "$.packet.source_port") -- MsgRecvPacket and MsgAcknowledgement (might be backwards) - ) as port_id - , COALESCE( - json_extract(value, "$.channel.counterparty.port_id"), -- ChannelOpenTry - json_extract(value, "$.packet.destination_port") -- MsgRecvPacket and MsgAcknowledgement (might be backwards) - ) as counterparty_port_id - , COALESCE( - json_extract(value, "$.channel_id"), -- ChannelOpen* - json_extract(value, "$.source_channel"), -- MsgTransfer - json_extract(value, "$.packet.source_channel") -- MsgRecvPacket and MsgAcknowledgement (might be backwards) - ) as channel_id - , COALESCE( - json_extract(value, "$.counterparty_channel_id"), -- ChannelOpenAck - json_extract(value, "$.channel.counterparty.channel_id"), -- ChannelOpenTry - json_extract(value, "$.packet.destination_channel") -- MsgRecvPacket and MsgAcknowledgement (might be backwards) - ) as counterparty_channel_id - , value as raw -FROM v_tx_flattened, json_each(v_tx_flattened.tx, "$.body.messages") -`) - if err != nil { - return fmt.Errorf("create v_cosmos_messages view: %w", err) - } - - _, err = tx.Exec(`DROP VIEW IF EXISTS v_tx_agg`) - if err != nil { - return fmt.Errorf("drop old v_tx_agg view: %w", err) - } - - _, err = tx.Exec(`CREATE VIEW v_tx_agg AS - SELECT - test_case.id AS test_case_id - , test_case.created_at AS test_case_created_at - , test_case.name AS test_case_name - , test_case.git_sha AS test_case_git_sha - , chain.id AS chain_kid - , chain.chain_id AS chain_id - , chain.chain_type AS chain_type - , MAX(COALESCE(block.height, 0)) AS chain_height - , COUNT(tx.data) AS tx_total - FROM test_case - LEFT JOIN chain ON chain.fk_test_id = test_case.id - LEFT JOIN block ON block.fk_chain_id = chain.id - LEFT JOIN tx ON tx.fk_block_id = block.id - GROUP BY test_case.id, chain.id -`) - if err != nil { - return fmt.Errorf("create v_tx_agg view: %w", err) - } - - return nil -} - -func errIgnoreDuplicateColumn(err error, col string) error { - var serr *sqlite.Error - if errors.As(err, &serr) && - strings.Contains(serr.Error(), fmt.Sprintf("duplicate column name: %s", col)) { - return nil - } - return err -} diff --git a/blockdb/migrate_test.go b/blockdb/migrate_test.go deleted file mode 100644 index 67ee2876b..000000000 --- a/blockdb/migrate_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package blockdb - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestMigrate(t *testing.T) { - t.Parallel() - - db := emptyDB() - defer db.Close() - - const gitSha = "abc123" - err := Migrate(db, gitSha) - require.NoError(t, err) - - // Tests idempotency. - err = Migrate(db, gitSha) - require.NoError(t, err) - - row := db.QueryRow(`select count(*) from schema_version`) - var count int - err = row.Scan(&count) - - require.NoError(t, err) - require.Equal(t, 1, count) - - err = Migrate(db, "new-sha") - require.NoError(t, err) - - row = db.QueryRow(`select count(*) from schema_version`) - err = row.Scan(&count) - - require.NoError(t, err) - require.Equal(t, 2, count) - - row = db.QueryRow(`select git_sha from schema_version order by id desc limit 1`) - var gotSha string - err = row.Scan(&gotSha) - - require.NoError(t, err) - require.Equal(t, "new-sha", gotSha) -} diff --git a/blockdb/query.go b/blockdb/query.go deleted file mode 100644 index 30284e1a6..000000000 --- a/blockdb/query.go +++ /dev/null @@ -1,200 +0,0 @@ -package blockdb - -import ( - "context" - "database/sql" - "fmt" - "time" -) - -// Query is a service that queries the database. -type Query struct { - db *sql.DB -} - -func NewQuery(db *sql.DB) *Query { - return &Query{db: db} -} - -func timeToLocal(timeStr string) (time.Time, error) { - t, err := time.Parse(time.RFC3339, timeStr) - if err != nil { - return time.Time{}, fmt.Errorf("time.Parse RFC3339: %w", err) - } - return t.In(time.Local), nil -} - -type SchemaVersionResult struct { - GitSha string - // Always set to user's local time zone. - CreatedAt time.Time -} - -// CurrentSchemaVersion returns the latest git sha and time that produced the sqlite schema. -func (q *Query) CurrentSchemaVersion(ctx context.Context) (SchemaVersionResult, error) { - row := q.db.QueryRowContext(ctx, `SELECT git_sha, created_at FROM schema_version ORDER BY id DESC limit 1`) - var ( - res SchemaVersionResult - createAt string - ) - if err := row.Scan(&res.GitSha, &createAt); err != nil { - return res, err - } - t, err := timeToLocal(createAt) - if err != nil { - return res, fmt.Errorf("parse createdAt: %w", err) - } - res.CreatedAt = t - return res, nil -} - -// TestCaseResult is a combination of a single test case and single chain associated with the test case. -type TestCaseResult struct { - ID int64 - Name string - GitSha string // Git commit that ran the test. - CreatedAt time.Time - ChainPKey int64 // chain primary key - ChainID string // E.g. osmosis-1001 - ChainType string // E.g. cosmos, penumbra - ChainHeight sql.NullInt64 - TxTotal sql.NullInt64 -} - -// RecentTestCases returns aggregated data for each test case and chain combination. -func (q *Query) RecentTestCases(ctx context.Context, limit int) ([]TestCaseResult, error) { - rows, err := q.db.QueryContext(ctx, `SELECT - test_case_id, test_case_created_at, test_case_name, test_case_git_sha, chain_kid, chain_id, chain_type, chain_height, tx_total - FROM v_tx_agg - WHERE chain_kid IS NOT NULL - ORDER BY test_case_id DESC, chain_id ASC LIMIT ?`, limit) - if err != nil { - return nil, err - } - defer rows.Close() - var results []TestCaseResult - for rows.Next() { - var ( - res TestCaseResult - createdAt string - ) - if err = rows.Scan( - &res.ID, - &createdAt, - &res.Name, - &res.GitSha, - &res.ChainPKey, - &res.ChainID, - &res.ChainType, - &res.ChainHeight, - &res.TxTotal, - ); err != nil { - return nil, err - } - t, err := timeToLocal(createdAt) - if err != nil { - return nil, fmt.Errorf("parse createdAt: %w", err) - } - res.CreatedAt = t - results = append(results, res) - } - return results, nil -} - -type CosmosMessageResult struct { - Height int64 - Index int - Type string // URI for proto definition, e.g. /ibc.core.client.v1.MsgCreateClient - - ClientChainID sql.NullString - - ClientID sql.NullString - CounterpartyClientID sql.NullString - - ConnID sql.NullString - CounterpartyConnID sql.NullString - - PortID sql.NullString - CounterpartyPortID sql.NullString - - ChannelID sql.NullString - CounterpartyChannelID sql.NullString -} - -// CosmosMessages returns a summary of Cosmos messages for the chainID. In Cosmos, a transaction may have 1 or more -// associated messages. -// chainPkey is the chain primary key "chain.id", not to be confused with the column "chain_id". -func (q *Query) CosmosMessages(ctx context.Context, chainPkey int64) ([]CosmosMessageResult, error) { - rows, err := q.db.QueryContext(ctx, `SELECT - block_height - , msg_n -- message index or position within the tx - , type - , client_chain_id - , client_id - , counterparty_client_id - , conn_id - , counterparty_conn_id - , port_id - , counterparty_port_id - , channel_id - , counterparty_channel_id - FROM v_cosmos_messages - WHERE chain_kid = ? - ORDER BY block_height ASC , msg_n ASC`, chainPkey) - if err != nil { - return nil, err - } - defer rows.Close() - var results []CosmosMessageResult - for rows.Next() { - var res CosmosMessageResult - if err = rows.Scan( - &res.Height, - &res.Index, - &res.Type, - &res.ClientChainID, - &res.ClientID, - &res.CounterpartyClientID, - &res.ConnID, - &res.CounterpartyConnID, - &res.PortID, - &res.CounterpartyPortID, - &res.ChannelID, - &res.CounterpartyChannelID, - ); err != nil { - return nil, err - } - results = append(results, res) - } - return results, nil -} - -type TxResult struct { - Height int64 - Tx []byte -} - -// Transactions returns TxResults only for blocks with transactions present. -// chainPkey is the chain primary key "chain.id", not to be confused with the column "chain_id". -func (q *Query) Transactions(ctx context.Context, chainPkey int64) ([]TxResult, error) { - rows, err := q.db.QueryContext(ctx, `SELECT block.height, tx.data FROM tx - INNER JOIN block on tx.fk_block_id = block.id - INNER JOIN chain on block.fk_chain_id = chain.id - WHERE chain.id = ? - ORDER BY block.height ASC, tx.id ASC`, chainPkey) - if err != nil { - return nil, err - } - defer rows.Close() - - var results []TxResult - for rows.Next() { - var res TxResult - if err := rows.Scan(&res.Height, &res.Tx); err != nil { - return nil, err - } - results = append(results, res) - } - - return results, nil -} diff --git a/blockdb/query_test.go b/blockdb/query_test.go deleted file mode 100644 index d4c5d705c..000000000 --- a/blockdb/query_test.go +++ /dev/null @@ -1,218 +0,0 @@ -package blockdb - -import ( - "context" - _ "embed" - "encoding/json" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -var ( - //go:embed testdata/sample_txs.json - txsFixture []byte -) - -func TestQuery_CurrentSchemaVersion(t *testing.T) { - t.Parallel() - - db := emptyDB() - defer db.Close() - - require.NoError(t, Migrate(db, "first-sha")) - require.NoError(t, Migrate(db, "second-sha")) - - res, err := NewQuery(db).CurrentSchemaVersion(context.Background()) - - require.NoError(t, err) - require.Equal(t, "second-sha", res.GitSha) - require.WithinDuration(t, res.CreatedAt, time.Now(), 10*time.Second) -} - -func TestQuery_RecentTestCases(t *testing.T) { - t.Parallel() - - ctx := context.Background() - - t.Run("happy path", func(t *testing.T) { - db := migratedDB() - defer db.Close() - - tc, err := CreateTestCase(ctx, db, "test1", "sha1") - require.NoError(t, err) - c, err := tc.AddChain(ctx, "chain-b", "cosmos") - require.NoError(t, err) - require.NoError(t, c.SaveBlock(ctx, 10, []Tx{{Data: []byte("tx1")}, {Data: []byte("tx2")}})) - require.NoError(t, c.SaveBlock(ctx, 11, []Tx{{Data: []byte("tx3")}})) - - _, err = tc.AddChain(ctx, "chain-a", "cosmos") - require.NoError(t, err) - - _, err = CreateTestCase(ctx, db, "empty", "empty-test") - require.NoError(t, err) - - results, err := NewQuery(db).RecentTestCases(ctx, 10) - require.NoError(t, err) - - require.Len(t, results, 2) - - // No blocks or txs. - got := results[0] - require.EqualValues(t, 1, got.ID) - require.Equal(t, "test1", got.Name) - require.Equal(t, "sha1", got.GitSha) - require.WithinDuration(t, time.Now(), got.CreatedAt, 10*time.Second) - require.Equal(t, "chain-a", got.ChainID) - require.Equal(t, "cosmos", got.ChainType) - require.EqualValues(t, 2, got.ChainPKey) - require.Zero(t, got.ChainHeight.Int64) - require.Zero(t, got.TxTotal.Int64) - - // With blocks and txs. - got = results[1] - require.EqualValues(t, 1, got.ID) - require.Equal(t, "test1", got.Name) - require.WithinDuration(t, time.Now(), got.CreatedAt, 10*time.Second) - require.Equal(t, "chain-b", got.ChainID) - require.Equal(t, "cosmos", got.ChainType) - require.EqualValues(t, 1, got.ChainPKey) - require.EqualValues(t, 11, got.ChainHeight.Int64) - require.EqualValues(t, 3, got.TxTotal.Int64) - }) - - t.Run("limit", func(t *testing.T) { - db := migratedDB() - defer db.Close() - - tc, err := CreateTestCase(ctx, db, "1", "1") - require.NoError(t, err) - _, err = tc.AddChain(ctx, "chain1", "cosmos") - require.NoError(t, err) - _, err = tc.AddChain(ctx, "chain2", "cosmos") - require.NoError(t, err) - - got, err := NewQuery(db).RecentTestCases(ctx, 1) - require.NoError(t, err) - require.Len(t, got, 1) - }) - - t.Run("no test cases", func(t *testing.T) { - db := migratedDB() - defer db.Close() - - got, err := NewQuery(db).RecentTestCases(ctx, 1) - - require.NoError(t, err) - require.Empty(t, got) - }) -} - -func TestQuery_CosmosMessages(t *testing.T) { - t.Parallel() - - ctx := context.Background() - - db := migratedDB() - defer db.Close() - - tc, err := CreateTestCase(ctx, db, "test", "sha") - require.NoError(t, err) - chain, err := tc.AddChain(ctx, "chain1", "cosmos") - require.NoError(t, err) - - var txs []struct { - Raw string `json:"tx"` - } - - err = json.Unmarshal(txsFixture, &txs) - require.NoError(t, err) - require.NotEmpty(t, txs) - - for i, tx := range txs { - require.NotEmpty(t, tx.Raw) - err = chain.SaveBlock(ctx, int64(i+1), []Tx{{Data: []byte(tx.Raw)}}) - require.NoError(t, err) - } - - results, err := NewQuery(db).CosmosMessages(ctx, chain.id) - require.NoError(t, err) - require.NotEmpty(t, results) - - first := results[0] - require.EqualValues(t, 1, first.Height) - require.EqualValues(t, 0, first.Index) - require.Equal(t, "/ibc.core.client.v1.MsgCreateClient", first.Type) - - second := results[1] - require.EqualValues(t, 2, second.Height) - require.EqualValues(t, 0, second.Index) - require.Equal(t, "/ibc.core.client.v1.MsgUpdateClient", second.Type) - - third := results[2] - require.EqualValues(t, 2, third.Height) - require.EqualValues(t, 1, third.Index) - require.Equal(t, "/ibc.core.connection.v1.MsgConnectionOpenInit", third.Type) - - for _, res := range results { - if !strings.HasPrefix(res.Type, "/ibc") { - continue - } - atLeastOnePresent := res.ClientChainID.Valid || - res.ClientID.Valid || res.CounterpartyClientID.Valid || - res.ConnID.Valid || res.CounterpartyConnID.Valid || - res.PortID.Valid || res.CounterpartyPortID.Valid || - res.ChannelID.Valid || res.CounterpartyChannelID.Valid - require.Truef(t, atLeastOnePresent, "IBC messages must contain valid IBC info for %+v", res) - } -} - -func TestQuery_Transactions(t *testing.T) { - t.Parallel() - - ctx := context.Background() - - t.Run("happy path", func(t *testing.T) { - db := migratedDB() - defer db.Close() - - tc, err := CreateTestCase(ctx, db, "test", "abc123") - require.NoError(t, err) - chain, err := tc.AddChain(ctx, "chain-a", "cosmos") - require.NoError(t, err) - - require.NoError(t, chain.SaveBlock(ctx, 12, []Tx{{Data: []byte(`1`)}})) - require.NoError(t, chain.SaveBlock(ctx, 14, []Tx{{Data: []byte(`2`)}, {Data: []byte(`3`)}})) - - results, err := NewQuery(db).Transactions(ctx, chain.id) - require.NoError(t, err) - - require.Len(t, results, 3) - - require.EqualValues(t, 12, results[0].Height) - require.Equal(t, "1", string(results[0].Tx)) - - require.EqualValues(t, 14, results[1].Height) - require.Equal(t, "2", string(results[1].Tx)) - - require.EqualValues(t, 14, results[2].Height) - require.Equal(t, "3", string(results[2].Tx)) - }) - - t.Run("no txs", func(t *testing.T) { - db := migratedDB() - defer db.Close() - - tc, err := CreateTestCase(ctx, db, "test", "abc123") - require.NoError(t, err) - chain, err := tc.AddChain(ctx, "chain-a", "cosmos") - require.NoError(t, err) - - results, err := NewQuery(db).Transactions(ctx, chain.id) - require.NoError(t, err) - - require.Len(t, results, 0) - }) -} diff --git a/blockdb/sql.go b/blockdb/sql.go deleted file mode 100644 index 75f4288c4..000000000 --- a/blockdb/sql.go +++ /dev/null @@ -1,41 +0,0 @@ -package blockdb - -import ( - "context" - "database/sql" - "fmt" - "os" - "path/filepath" - "time" - - _ "modernc.org/sqlite" -) - -// ConnectDB connects to the sqlite database at databasePath. -// Auto-creates directory path via MkdirAll. -// Pings database once to ensure connection. -// Pass :memory: as databasePath for in-memory database. -func ConnectDB(ctx context.Context, databasePath string) (*sql.DB, error) { - if databasePath != ":memory:" { - if err := os.MkdirAll(filepath.Dir(databasePath), 0755); err != nil { - return nil, err - } - } - db, err := sql.Open("sqlite", databasePath) - if err != nil { - return nil, fmt.Errorf("open db %s: %w", databasePath, err) - } - // Sqlite does not handle >1 open connections per process well, - // otherwise "database is locked" errors frequently occur. - db.SetMaxOpenConns(1) - err = db.PingContext(ctx) - if err != nil { - _ = db.Close() - return nil, fmt.Errorf("ping db %s: %w", databasePath, err) - } - return db, err -} - -func nowRFC3339() string { - return time.Now().UTC().Format(time.RFC3339) -} diff --git a/blockdb/sql_test.go b/blockdb/sql_test.go deleted file mode 100644 index 59cb1bab4..000000000 --- a/blockdb/sql_test.go +++ /dev/null @@ -1,148 +0,0 @@ -package blockdb - -import ( - "context" - "database/sql" - "fmt" - "path/filepath" - "strconv" - "testing" - "time" - - "github.com/stretchr/testify/require" - "golang.org/x/sync/errgroup" -) - -func emptyDB() *sql.DB { - db, err := ConnectDB(context.Background(), ":memory:") - if err != nil { - panic(err) - } - return db -} - -func migratedDB() *sql.DB { - db := emptyDB() - if err := Migrate(db, "test"); err != nil { - panic(err) - } - return db -} - -func TestConnectDB(t *testing.T) { - file := filepath.Join(t.TempDir(), strconv.FormatInt(time.Now().UnixMilli(), 10), "test", t.Name()+".db") - db, err := ConnectDB(context.Background(), file) - - require.NoError(t, err) - require.NoError(t, db.Close()) -} - -// Test that multiple writers and readers against the same underlying file -// do not fail due to a "database is locked" error. -func TestDB_Concurrency(t *testing.T) { - if testing.Short() { - t.Skip("skipping due to short mode") - } - - t.Parallel() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - dbPath := filepath.Join(t.TempDir(), "concurrent.db") - - // Block all writers until this channel is closed. - beginWrites := make(chan struct{}) - - const nWriters = 4 - const nTestCases = 500 - const nQueriers = 2 - const sha = "abc123" - - // Dedicated errgroup for the writers, - // and a shared context so all fail if one fails. - egWrites, egCtx := errgroup.WithContext(ctx) - for i := 0; i < nWriters; i++ { - i := i - - // Connecting to the database in the main goroutine - // because concurrently connecting to the same database - // causes a data race inside sqlite. - db, err := ConnectDB(ctx, dbPath) - require.NoErrorf(t, err, "failed to connect to db for writer %d: %v", i, err) - defer db.Close() - require.NoError(t, Migrate(db, sha)) - - egWrites.Go(func() error { - // Block until this channel is closed. - <-beginWrites - - for j := 0; j < nTestCases; j++ { - tc, err := CreateTestCase(egCtx, db, fmt.Sprintf("test-%d-%d", i, j), sha) - if err != nil { - return fmt.Errorf("writer %d failed to create test case %d/%d: %w", i, j+1, nTestCases, err) - } - time.Sleep(time.Millisecond) - - _, err = tc.AddChain(egCtx, fmt.Sprintf("chain-%d-%d", i, j), "cosmos") - if err != nil { - return fmt.Errorf("writer %d failed to add chain to test case %d/%d: %w", i, j+1, nTestCases, err) - } - time.Sleep(time.Millisecond) - } - - return nil - }) - } - - // Separate errgroup for the queriers. - var egQueries errgroup.Group - for i := 0; i < nQueriers; i++ { - i := i - - db, err := ConnectDB(ctx, dbPath) - require.NoErrorf(t, err, "failed to connect to db for querier %d: %v", i, err) - defer db.Close() - require.NoError(t, Migrate(db, sha)) - - egQueries.Go(func() error { - // No need to synchronize here; just begin querying. - q := NewQuery(db) - - for { - if ctx.Err() != nil { - // Context was canceled; querying is finished. - return nil - } - - // Deliberately using context.Background() here so that - // canceling the writers does not cause an "interrupted" error - // when querying the recent test cases. - // (This must be an sqlite implementation detail, as that error - // is distinct from context.Canceled.) - _, err := q.RecentTestCases(context.Background(), nTestCases*nWriters) - if err != nil { - return fmt.Errorf("error in querier %d retrieving test cases: %w", i, err) - } - } - }) - } - - // Signal that writes can begin, then wait for them to finish. - close(beginWrites) - require.NoError(t, egWrites.Wait()) - - // Signal that queries should stop, then wait for them to finish. - cancel() - require.NoError(t, egQueries.Wait()) - - // Final assertions against written number of test cases. - db, err := ConnectDB(context.Background(), dbPath) - require.NoErrorf(t, err, "failed to connect to db for final assertion") - defer db.Close() - - tcs, err := NewQuery(db).RecentTestCases(context.Background(), nTestCases*nWriters) - require.NoError(t, err, "failed to collect recent test cases") - - require.Len(t, tcs, nTestCases*nWriters, "incorrect count on final written test cases") -} diff --git a/blockdb/test_case.go b/blockdb/test_case.go deleted file mode 100644 index 03d2dcc53..000000000 --- a/blockdb/test_case.go +++ /dev/null @@ -1,46 +0,0 @@ -package blockdb - -import ( - "context" - "database/sql" -) - -// TestCase is a single test invocation. -type TestCase struct { - db *sql.DB - id int64 -} - -// CreateTestCase starts tracking new test case with testName. -func CreateTestCase(ctx context.Context, db *sql.DB, testName, gitSha string) (*TestCase, error) { - res, err := db.ExecContext(ctx, `INSERT INTO test_case(name, created_at, git_sha) VALUES(?, ?, ?)`, testName, nowRFC3339(), gitSha) - if err != nil { - return nil, err - } - id, err := res.LastInsertId() - if err != nil { - return nil, err - } - return &TestCase{ - db: db, - id: id, - }, nil -} - -// AddChain tracks and attaches a chain to the test case. -// The chainID must be unique per test case. E.g. osmosis-1001, cosmos-1004 -// The chainType denotes which ecosystem the chain belongs to. E.g. cosmos, penumbra, composable, etc. -func (tc *TestCase) AddChain(ctx context.Context, chainID, chainType string) (*Chain, error) { - res, err := tc.db.ExecContext(ctx, `INSERT INTO chain(chain_id, chain_type, fk_test_id) VALUES(?, ?, ?)`, chainID, chainType, tc.id) - if err != nil { - return nil, err - } - id, err := res.LastInsertId() - if err != nil { - return nil, err - } - return &Chain{ - id: id, - db: tc.db, - }, nil -} diff --git a/blockdb/test_case_test.go b/blockdb/test_case_test.go deleted file mode 100644 index 408cbb520..000000000 --- a/blockdb/test_case_test.go +++ /dev/null @@ -1,95 +0,0 @@ -package blockdb - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -func TestCreateTestCase(t *testing.T) { - t.Parallel() - - ctx := context.Background() - - t.Run("happy path", func(t *testing.T) { - db := migratedDB() - defer db.Close() - - tc, err := CreateTestCase(ctx, db, "SomeTest", "abc123") - require.NoError(t, err) - require.NotNil(t, tc) - - row := db.QueryRow(`SELECT name, created_at, git_sha FROM test_case LIMIT 1`) - var ( - gotName string - gotTime string - gotSha string - ) - err = row.Scan(&gotName, &gotTime, &gotSha) - require.NoError(t, err) - - require.Equal(t, "SomeTest", gotName) - require.Equal(t, "abc123", gotSha) - - ts, err := time.Parse(time.RFC3339, gotTime) - require.NoError(t, err) - require.WithinDuration(t, time.Now(), ts, 10*time.Second) - }) - - t.Run("errors", func(t *testing.T) { - db := emptyDB() - _, err := CreateTestCase(ctx, db, "fail", "") - require.Error(t, err) - }) -} - -func TestTestCase_AddChain(t *testing.T) { - t.Parallel() - - ctx := context.Background() - - t.Run("happy path", func(t *testing.T) { - db := migratedDB() - defer db.Close() - - tc, err := CreateTestCase(ctx, db, "SomeTest", "abc") - require.NoError(t, err) - - chain, err := tc.AddChain(ctx, "my-chain1", "penumbra") - require.NoError(t, err) - require.NotNil(t, chain) - - row := db.QueryRow(`SELECT chain_id, chain_type, fk_test_id, id FROM chain`) - var ( - gotChainID string - gotChainType string - gotTestID int - gotPrimaryKey int64 - ) - err = row.Scan(&gotChainID, &gotChainType, &gotTestID, &gotPrimaryKey) - require.NoError(t, err) - require.Equal(t, "my-chain1", gotChainID) - require.Equal(t, "penumbra", gotChainType) - require.Equal(t, 1, gotTestID) - require.EqualValues(t, 1, gotPrimaryKey) - - _, err = tc.AddChain(ctx, "my-chain2", "test") - require.NoError(t, err) - }) - - t.Run("errors", func(t *testing.T) { - db := migratedDB() - defer db.Close() - - tc, err := CreateTestCase(ctx, db, "SomeTest", "abc") - require.NoError(t, err) - - _, err = tc.AddChain(ctx, "my-chain", "cosmos") - require.NoError(t, err) - - _, err = tc.AddChain(ctx, "my-chain", "cosmos") - require.Error(t, err) - }) -} diff --git a/blockdb/testdata/README.md b/blockdb/testdata/README.md deleted file mode 100644 index 5c97bd455..000000000 --- a/blockdb/testdata/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# How to dump JSON from sqlite3. - -For sample_txs.json - -Produces an array of JSON objects: - -```shell -sqlite3 ~/.interchaintest/databases/block.db 'select tx_id, tx from v_tx_flattened where chain_kid = 1 order by tx_id asc' -json > /some/file/path.json -``` \ No newline at end of file diff --git a/blockdb/testdata/sample_txs.json b/blockdb/testdata/sample_txs.json deleted file mode 100644 index f05eb8a6d..000000000 --- a/blockdb/testdata/sample_txs.json +++ /dev/null @@ -1,13 +0,0 @@ -[{"tx_id":1,"tx":"{\n \"body\": {\n \"messages\": [\n {\n \"@type\": \"/ibc.core.client.v1.MsgCreateClient\",\n \"client_state\": {\n \"@type\": \"/ibc.lightclients.tendermint.v1.ClientState\",\n \"chain_id\": \"osmosis-1001\",\n \"trust_level\": {\n \"numerator\": \"1\",\n \"denominator\": \"3\"\n },\n \"trusting_period\": \"1540800s\",\n \"unbonding_period\": \"1814400s\",\n \"max_clock_drift\": \"600s\",\n \"frozen_height\": {\n \"revision_number\": \"0\",\n \"revision_height\": \"0\"\n },\n \"latest_height\": {\n \"revision_number\": \"1001\",\n \"revision_height\": \"14\"\n },\n \"proof_specs\": [\n {\n \"leaf_spec\": {\n \"hash\": \"SHA256\",\n \"prehash_key\": \"NO_HASH\",\n \"prehash_value\": \"SHA256\",\n \"length\": \"VAR_PROTO\",\n \"prefix\": \"AA==\"\n },\n \"inner_spec\": {\n \"child_order\": [\n 0,\n 1\n ],\n \"child_size\": 33,\n \"min_prefix_length\": 4,\n \"max_prefix_length\": 12,\n \"empty_child\": null,\n \"hash\": \"SHA256\"\n },\n \"max_depth\": 0,\n \"min_depth\": 0\n },\n {\n \"leaf_spec\": {\n \"hash\": \"SHA256\",\n \"prehash_key\": \"NO_HASH\",\n \"prehash_value\": \"SHA256\",\n \"length\": \"VAR_PROTO\",\n \"prefix\": \"AA==\"\n },\n \"inner_spec\": {\n \"child_order\": [\n 0,\n 1\n ],\n \"child_size\": 32,\n \"min_prefix_length\": 1,\n \"max_prefix_length\": 1,\n \"empty_child\": null,\n \"hash\": \"SHA256\"\n },\n \"max_depth\": 0,\n \"min_depth\": 0\n }\n ],\n \"upgrade_path\": [\n \"upgrade\",\n \"upgradedIBCState\"\n ],\n \"allow_update_after_expiry\": true,\n \"allow_update_after_misbehaviour\": true\n },\n \"consensus_state\": {\n \"@type\": \"/ibc.lightclients.tendermint.v1.ConsensusState\",\n \"timestamp\": \"2022-06-02T21:00:46.116616094Z\",\n \"root\": {\n \"hash\": \"DyvhXUVq7TQlyyNjASuYPGFWYMfSZJiIQytFgYnwOeE=\"\n },\n \"next_validators_hash\": \"EAB1566C915C19F51B006FE81FB7CF3C7AC2FA4E7C0BAA2239BE38A64B2BE4D2\"\n },\n \"signer\": \"cosmos1kczfgaqd74px9cgkfl4v2tnknnsh3lkuhulvtz\"\n }\n ],\n \"memo\": \"\",\n \"timeout_height\": \"0\",\n \"extension_options\": [],\n \"non_critical_extension_options\": []\n },\n \"auth_info\": {\n \"signer_infos\": [\n {\n \"public_key\": {\n \"@type\": \"/cosmos.crypto.secp256k1.PubKey\",\n \"key\": \"AvGJBwmMw1+SzbAvvLfraQaudHOoPkNOWsW6JM/J9QSt\"\n },\n \"mode_info\": {\n \"single\": {\n \"mode\": \"SIGN_MODE_DIRECT\"\n }\n },\n \"sequence\": \"0\"\n }\n ],\n \"fee\": {\n \"amount\": [\n {\n \"denom\": \"uatom\",\n \"amount\": \"1076\"\n }\n ],\n \"gas_limit\": \"107568\",\n \"payer\": \"\",\n \"granter\": \"\"\n }\n },\n \"signatures\": [\n \"BrbMDy6bjLCqlftJIE5SXt352bJ4DQeP1iczeorLaINg9B3ZHGlYjfa8R3UyZCuSdyrO/LcwQMRogpxwN8DqbQ==\"\n ]\n}"}, -{"tx_id":3,"tx":"{\n \"body\": {\n \"messages\": [\n {\n \"@type\": \"/ibc.core.client.v1.MsgUpdateClient\",\n \"client_id\": \"07-tendermint-0\",\n \"header\": {\n \"@type\": \"/ibc.lightclients.tendermint.v1.Header\",\n \"signed_header\": {\n \"header\": {\n \"version\": {\n \"block\": \"11\",\n \"app\": \"0\"\n },\n \"chain_id\": \"osmosis-1001\",\n \"height\": \"16\",\n \"time\": \"2022-06-02T21:00:50.774262221Z\",\n \"last_block_id\": {\n \"hash\": \"KxyXn6AdSv85+PYwuB3Tn3tc7HGan6Hb8WR0LAoIRL4=\",\n \"part_set_header\": {\n \"total\": 1,\n \"hash\": \"wDZIyta8wtkEcl+fvMwKAA9/7Vnvdhf/wTUa/5xA7B4=\"\n }\n },\n \"last_commit_hash\": \"Ghq4gJ8l0P90AjKwSh4dCAlDolGQ6s0Y6/gCCwK8n10=\",\n \"data_hash\": \"bJ/5dPgQm/imm0F6e+vpVi2rIYI0OzoOmq8PJyC8PL4=\",\n \"validators_hash\": \"6rFWbJFcGfUbAG/oH7fPPHrC+k58C6oiOb44pksr5NI=\",\n \"next_validators_hash\": \"6rFWbJFcGfUbAG/oH7fPPHrC+k58C6oiOb44pksr5NI=\",\n \"consensus_hash\": \"BICRvH3cKD93v7+R1zxE2ljD34qcvIZ0Bdi389qtoi8=\",\n \"app_hash\": \"azs7zp3NMRxhrwGcarAlz+sbLcJ5FLezSv0eh0Jw2mU=\",\n \"last_results_hash\": \"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=\",\n \"evidence_hash\": \"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=\",\n \"proposer_address\": \"6KMcLEZQl4IYnvRQqoIDDzDvnYM=\"\n },\n \"commit\": {\n \"height\": \"16\",\n \"round\": 0,\n \"block_id\": {\n \"hash\": \"NZj0fwkKJojvveqaPQ5k/Cv5lGM+PrYA3KmJyXSfrk8=\",\n \"part_set_header\": {\n \"total\": 1,\n \"hash\": \"JWrQcVPeadME7/qbV8ILIv2+rgfaCZ7vgRII79bGpoc=\"\n }\n },\n \"signatures\": [\n {\n \"block_id_flag\": \"BLOCK_ID_FLAG_COMMIT\",\n \"validator_address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"timestamp\": \"2022-06-02T21:00:53.072075514Z\",\n \"signature\": \"j2FWTxJDc8yBSI0hBzeDKSgDr5iDAQp6griUBRFHhGCuLA8aDsJ7p75ziWCmPeTZCLIX1Q6YEgklNFVr4TRJCw==\"\n },\n {\n \"block_id_flag\": \"BLOCK_ID_FLAG_COMMIT\",\n \"validator_address\": \"6KMcLEZQl4IYnvRQqoIDDzDvnYM=\",\n \"timestamp\": \"2022-06-02T21:00:53.172459847Z\",\n \"signature\": \"ChfQ9qVw/B0exQ1seLWzwH27Ffsq61BRYeOX8GA+IFpYOXE6aEzF3V2j4kppFmuzn//vdvZn6lL5bpR6ieojAg==\"\n }\n ]\n }\n },\n \"validator_set\": {\n \"validators\": [\n {\n \"address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"pub_key\": {\n \"ed25519\": \"hhrauMYxUfpQMIgIA5TE242yIYZtqjyNXLQhztAkScM=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"-100000\"\n },\n {\n \"address\": \"6KMcLEZQl4IYnvRQqoIDDzDvnYM=\",\n \"pub_key\": {\n \"ed25519\": \"avI/FwInJgqfue0GaYUO5pxYxCqpAwr3mHcCIask4yw=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"100000\"\n }\n ],\n \"proposer\": {\n \"address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"pub_key\": {\n \"ed25519\": \"hhrauMYxUfpQMIgIA5TE242yIYZtqjyNXLQhztAkScM=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"-100000\"\n },\n \"total_voting_power\": \"0\"\n },\n \"trusted_height\": {\n \"revision_number\": \"1001\",\n \"revision_height\": \"14\"\n },\n \"trusted_validators\": {\n \"validators\": [\n {\n \"address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"pub_key\": {\n \"ed25519\": \"hhrauMYxUfpQMIgIA5TE242yIYZtqjyNXLQhztAkScM=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"-100000\"\n },\n {\n \"address\": \"6KMcLEZQl4IYnvRQqoIDDzDvnYM=\",\n \"pub_key\": {\n \"ed25519\": \"avI/FwInJgqfue0GaYUO5pxYxCqpAwr3mHcCIask4yw=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"100000\"\n }\n ],\n \"proposer\": {\n \"address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"pub_key\": {\n \"ed25519\": \"hhrauMYxUfpQMIgIA5TE242yIYZtqjyNXLQhztAkScM=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"-100000\"\n },\n \"total_voting_power\": \"0\"\n }\n },\n \"signer\": \"cosmos1kczfgaqd74px9cgkfl4v2tnknnsh3lkuhulvtz\"\n },\n {\n \"@type\": \"/ibc.core.connection.v1.MsgConnectionOpenInit\",\n \"client_id\": \"07-tendermint-0\",\n \"counterparty\": {\n \"client_id\": \"07-tendermint-0\",\n \"connection_id\": \"\",\n \"prefix\": {\n \"key_prefix\": \"aWJj\"\n }\n },\n \"version\": null,\n \"delay_period\": \"0\",\n \"signer\": \"cosmos1kczfgaqd74px9cgkfl4v2tnknnsh3lkuhulvtz\"\n }\n ],\n \"memo\": \"\",\n \"timeout_height\": \"0\",\n \"extension_options\": [],\n \"non_critical_extension_options\": []\n },\n \"auth_info\": {\n \"signer_infos\": [\n {\n \"public_key\": {\n \"@type\": \"/cosmos.crypto.secp256k1.PubKey\",\n \"key\": \"AvGJBwmMw1+SzbAvvLfraQaudHOoPkNOWsW6JM/J9QSt\"\n },\n \"mode_info\": {\n \"single\": {\n \"mode\": \"SIGN_MODE_DIRECT\"\n }\n },\n \"sequence\": \"1\"\n }\n ],\n \"fee\": {\n \"amount\": [\n {\n \"denom\": \"uatom\",\n \"amount\": \"1432\"\n }\n ],\n \"gas_limit\": \"143102\",\n \"payer\": \"\",\n \"granter\": \"\"\n }\n },\n \"signatures\": [\n \"/k72tTGh04z73toPHaWpYRsllUQG4QRuVTbRK7E23Wc14UbMyWzKkaEenTHwA4DdWsMFiRWcC7NnwtHocOMxPA==\"\n ]\n}"}, -{"tx_id":5,"tx":"{\n \"body\": {\n \"messages\": [\n {\n \"@type\": \"/ibc.core.client.v1.MsgUpdateClient\",\n \"client_id\": \"07-tendermint-0\",\n \"header\": {\n \"@type\": \"/ibc.lightclients.tendermint.v1.Header\",\n \"signed_header\": {\n \"header\": {\n \"version\": {\n \"block\": \"11\",\n \"app\": \"0\"\n },\n \"chain_id\": \"osmosis-1001\",\n \"height\": \"24\",\n \"time\": \"2022-06-02T21:01:09.565709840Z\",\n \"last_block_id\": {\n \"hash\": \"OszOYL0tMZ2JKNoXm0ZqQut/UcbjQXZPyb9DF6lV6Ic=\",\n \"part_set_header\": {\n \"total\": 1,\n \"hash\": \"MZKBmBoZ0O6mJjD3wZieN0S8ZpyicFOX3yXUzyV3vQM=\"\n }\n },\n \"last_commit_hash\": \"Y93G2XU6zzOXtO9hPoFItLxo3zoWPi5fnqP4IYWcCOA=\",\n \"data_hash\": \"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=\",\n \"validators_hash\": \"6rFWbJFcGfUbAG/oH7fPPHrC+k58C6oiOb44pksr5NI=\",\n \"next_validators_hash\": \"6rFWbJFcGfUbAG/oH7fPPHrC+k58C6oiOb44pksr5NI=\",\n \"consensus_hash\": \"BICRvH3cKD93v7+R1zxE2ljD34qcvIZ0Bdi389qtoi8=\",\n \"app_hash\": \"QDTArrBa/jPeadcu4GyWTYRS89iSpN/9lKEA0cwkUIo=\",\n \"last_results_hash\": \"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=\",\n \"evidence_hash\": \"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=\",\n \"proposer_address\": \"6KMcLEZQl4IYnvRQqoIDDzDvnYM=\"\n },\n \"commit\": {\n \"height\": \"24\",\n \"round\": 0,\n \"block_id\": {\n \"hash\": \"zRgHu5HcWXqqM9V0qjP4FFS+8XfCeBjddiSI/LT2Ghs=\",\n \"part_set_header\": {\n \"total\": 1,\n \"hash\": \"2iLIf39c+Sa38tbHzM0hWzvrexHrhTi6RIOQXL39rJo=\"\n }\n },\n \"signatures\": [\n {\n \"block_id_flag\": \"BLOCK_ID_FLAG_COMMIT\",\n \"validator_address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"timestamp\": \"2022-06-02T21:01:11.882249842Z\",\n \"signature\": \"WVNDMtRRc0xedpFaSgnAfPJ+T9jUhvwVGRceJ+rnarSRIkwcut/0+lRJkVz/CpLksZV7en92C5pT+WIO/zcSDQ==\"\n },\n {\n \"block_id_flag\": \"BLOCK_ID_FLAG_COMMIT\",\n \"validator_address\": \"6KMcLEZQl4IYnvRQqoIDDzDvnYM=\",\n \"timestamp\": \"2022-06-02T21:01:11.969452633Z\",\n \"signature\": \"uGQ8BrtE4xHHPXGF5Q7GMF4zGXPOPbIjWRdAHtUpNo9sKCSOJKVEthjleOQJ5HdgGEUFm4wv9TZJFWF9+KopDA==\"\n }\n ]\n }\n },\n \"validator_set\": {\n \"validators\": [\n {\n \"address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"pub_key\": {\n \"ed25519\": \"hhrauMYxUfpQMIgIA5TE242yIYZtqjyNXLQhztAkScM=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"-100000\"\n },\n {\n \"address\": \"6KMcLEZQl4IYnvRQqoIDDzDvnYM=\",\n \"pub_key\": {\n \"ed25519\": \"avI/FwInJgqfue0GaYUO5pxYxCqpAwr3mHcCIask4yw=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"100000\"\n }\n ],\n \"proposer\": {\n \"address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"pub_key\": {\n \"ed25519\": \"hhrauMYxUfpQMIgIA5TE242yIYZtqjyNXLQhztAkScM=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"-100000\"\n },\n \"total_voting_power\": \"0\"\n },\n \"trusted_height\": {\n \"revision_number\": \"1001\",\n \"revision_height\": \"16\"\n },\n \"trusted_validators\": {\n \"validators\": [\n {\n \"address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"pub_key\": {\n \"ed25519\": \"hhrauMYxUfpQMIgIA5TE242yIYZtqjyNXLQhztAkScM=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"-100000\"\n },\n {\n \"address\": \"6KMcLEZQl4IYnvRQqoIDDzDvnYM=\",\n \"pub_key\": {\n \"ed25519\": \"avI/FwInJgqfue0GaYUO5pxYxCqpAwr3mHcCIask4yw=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"100000\"\n }\n ],\n \"proposer\": {\n \"address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"pub_key\": {\n \"ed25519\": \"hhrauMYxUfpQMIgIA5TE242yIYZtqjyNXLQhztAkScM=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"-100000\"\n },\n \"total_voting_power\": \"0\"\n }\n },\n \"signer\": \"cosmos1kczfgaqd74px9cgkfl4v2tnknnsh3lkuhulvtz\"\n },\n {\n \"@type\": \"/ibc.core.connection.v1.MsgConnectionOpenAck\",\n \"connection_id\": \"connection-0\",\n \"counterparty_connection_id\": \"connection-0\",\n \"version\": {\n \"identifier\": \"1\",\n \"features\": [\n \"ORDER_ORDERED\",\n \"ORDER_UNORDERED\"\n ]\n },\n \"client_state\": {\n \"@type\": \"/ibc.lightclients.tendermint.v1.ClientState\",\n \"chain_id\": \"cosmoshub-1004\",\n \"trust_level\": {\n \"numerator\": \"1\",\n \"denominator\": \"3\"\n },\n \"trusting_period\": \"1540800s\",\n \"unbonding_period\": \"1814400s\",\n \"max_clock_drift\": \"600s\",\n \"frozen_height\": {\n \"revision_number\": \"0\",\n \"revision_height\": \"0\"\n },\n \"latest_height\": {\n \"revision_number\": \"1004\",\n \"revision_height\": \"20\"\n },\n \"proof_specs\": [\n {\n \"leaf_spec\": {\n \"hash\": \"SHA256\",\n \"prehash_key\": \"NO_HASH\",\n \"prehash_value\": \"SHA256\",\n \"length\": \"VAR_PROTO\",\n \"prefix\": \"AA==\"\n },\n \"inner_spec\": {\n \"child_order\": [\n 0,\n 1\n ],\n \"child_size\": 33,\n \"min_prefix_length\": 4,\n \"max_prefix_length\": 12,\n \"empty_child\": null,\n \"hash\": \"SHA256\"\n },\n \"max_depth\": 0,\n \"min_depth\": 0\n },\n {\n \"leaf_spec\": {\n \"hash\": \"SHA256\",\n \"prehash_key\": \"NO_HASH\",\n \"prehash_value\": \"SHA256\",\n \"length\": \"VAR_PROTO\",\n \"prefix\": \"AA==\"\n },\n \"inner_spec\": {\n \"child_order\": [\n 0,\n 1\n ],\n \"child_size\": 32,\n \"min_prefix_length\": 1,\n \"max_prefix_length\": 1,\n \"empty_child\": null,\n \"hash\": \"SHA256\"\n },\n \"max_depth\": 0,\n \"min_depth\": 0\n }\n ],\n \"upgrade_path\": [\n \"upgrade\",\n \"upgradedIBCState\"\n ],\n \"allow_update_after_expiry\": true,\n \"allow_update_after_misbehaviour\": true\n },\n \"proof_height\": {\n \"revision_number\": \"1001\",\n \"revision_height\": \"24\"\n },\n \"proof_try\": \"CuUCCuICChhjb25uZWN0aW9ucy9jb25uZWN0aW9uLTASYAoPMDctdGVuZGVybWludC0wEiMKATESDU9SREVSX09SREVSRUQSD09SREVSX1VOT1JERVJFRBgCIiYKDzA3LXRlbmRlcm1pbnQtMBIMY29ubmVjdGlvbi0wGgUKA2liYxoLCAEYASABKgMAAioiKQgBEiUCBCogdB9GOSfHxKdsfheCX6GeDut+hHldKJQNx+3nl/dODaogIikIARIlBAgqIE9yHIdataPA1Vr7zPAhN77b1nJy0Nl86oshGUyo02zvICIpCAESJQYMKiDUtJFjqdDlob7sF2K8zglzLRudR+ky26a5wVQuPeXa9yAiKwgBEgQIEiogGiEgB4wH9xPVSHSi3aVv0dCvojWBqP8Ki//raIiHATiS93siKQgBEiUKHCog76flN3ju+zp5Vultn7oS6OuOu3pT4S1vWZTVVQGWZlogCvwBCvkBCgNpYmMSIJZKHtgB5iP+hQJLZqfXG89R6YlE8iI93h3TTnAZHMtNGgkIARgBIAEqAQAiJQgBEiEBRrIPWyoPk3eSgkugPkJ6eniFTnDkezXSqYrL5TiW+RMiJQgBEiEBIyFJmJHCdHkEIm1a/e+f3urEr0QxcYMlBa+UCIZa/X8iJwgBEgEBGiCy0rNn332H8b5iORsbUJ9v2RsoP2uLFblNUb3ArZl07SIlCAESIQHAR6usnnPobIK0rAthdvH9UPnm0+ob9nVnBwm6oNPUnSInCAESAQEaIBFOu9PEZ0iUlN/352OG91do+dHL1Kd7WYx2Z+nSVjpY\",\n \"proof_client\": \"CqEDCp4DCiNjbGllbnRzLzA3LXRlbmRlcm1pbnQtMC9jbGllbnRTdGF0ZRK1AQorL2liYy5saWdodGNsaWVudHMudGVuZGVybWludC52MS5DbGllbnRTdGF0ZRKFAQoOY29zbW9zaHViLTEwMDQSBAgBEAMaBAjAhV4iBAiA324qAwjYBDIAOgUI7AcQFEIZCgkIARgBIAEqAQASDAoCAAEQIRgEIAwwAUIZCgkIARgBIAEqAQASDAoCAAEQIBgBIAEwAUoHdXBncmFkZUoQdXBncmFkZWRJQkNTdGF0ZVABWAEaCwgBGAEgASoDAAIqIisIARIEAgQqIBohIMb8Wbs9v5pilWGIsyHsGEHF46cQKUz/XT7Lj84OKoXSIisIARIEBAYqIBohIMbwRuvin2daJ7Olsr79RH2qAQGVfiBMMPOnqPoJXmy2IisIARIEBgoqIBohIHnap5W2+AmDH5sxVOb457BUUhIWPDtelsI4epbC6oxsIisIARIEChwqIBohIMF/m/biJU436p+SCQA7o0PQtUZXUKv/DeJB0ZW5YvTzCvwBCvkBCgNpYmMSIJZKHtgB5iP+hQJLZqfXG89R6YlE8iI93h3TTnAZHMtNGgkIARgBIAEqAQAiJQgBEiEBRrIPWyoPk3eSgkugPkJ6eniFTnDkezXSqYrL5TiW+RMiJQgBEiEBIyFJmJHCdHkEIm1a/e+f3urEr0QxcYMlBa+UCIZa/X8iJwgBEgEBGiCy0rNn332H8b5iORsbUJ9v2RsoP2uLFblNUb3ArZl07SIlCAESIQHAR6usnnPobIK0rAthdvH9UPnm0+ob9nVnBwm6oNPUnSInCAESAQEaIBFOu9PEZ0iUlN/352OG91do+dHL1Kd7WYx2Z+nSVjpY\",\n \"proof_consensus\": \"CvwCCvkCCi9jbGllbnRzLzA3LXRlbmRlcm1pbnQtMC9jb25zZW5zdXNTdGF0ZXMvMTAwNC0yMBKGAQouL2liYy5saWdodGNsaWVudHMudGVuZGVybWludC52MS5Db25zZW5zdXNTdGF0ZRJUCgwIi8rklAYQzqaL0gESIgogLIfrCq8Iz8dMP5x+o7+9nC813X0Djoa/iXqp6HLc0OcaICHaoeBk1xKUB5tJf4hI35obCqjBbajYjeyyTG2TgBt1GgsIARgBIAEqAwACKiIrCAESBAIEKiAaISCuBKbUlyNImeZynWGfdk8kER0fDUfqWhVVNnA+rxuQ6iIrCAESBAYMKiAaISD5+w5bj/eMHvzPvu97VIpjqi7zzFmZTdqZ7ADlCobWniIrCAESBAgSKiAaISAHjAf3E9VIdKLdpW/R0K+iNYGo/wqL/+toiIcBOJL3eyIpCAESJQocKiDvp+U3eO77OnlW6W2fuhLo6467elPhLW9ZlNVVAZZmWiAK/AEK+QEKA2liYxIglkoe2AHmI/6FAktmp9cbz1HpiUTyIj3eHdNOcBkcy00aCQgBGAEgASoBACIlCAESIQFGsg9bKg+Td5KCS6A+Qnp6eIVOcOR7NdKpisvlOJb5EyIlCAESIQEjIUmYkcJ0eQQibVr975/e6sSvRDFxgyUFr5QIhlr9fyInCAESAQEaILLSs2fffYfxvmI5GxtQn2/ZGyg/a4sVuU1RvcCtmXTtIiUIARIhAcBHq6yec+hsgrSsC2F28f1Q+ebT6hv2dWcHCbqg09SdIicIARIBARogEU6708RnSJSU3/fnY4b3V2j50cvUp3tZjHZn6dJWOlg=\",\n \"consensus_height\": {\n \"revision_number\": \"1004\",\n \"revision_height\": \"20\"\n },\n \"signer\": \"cosmos1kczfgaqd74px9cgkfl4v2tnknnsh3lkuhulvtz\"\n }\n ],\n \"memo\": \"\",\n \"timeout_height\": \"0\",\n \"extension_options\": [],\n \"non_critical_extension_options\": []\n },\n \"auth_info\": {\n \"signer_infos\": [\n {\n \"public_key\": {\n \"@type\": \"/cosmos.crypto.secp256k1.PubKey\",\n \"key\": \"AvGJBwmMw1+SzbAvvLfraQaudHOoPkNOWsW6JM/J9QSt\"\n },\n \"mode_info\": {\n \"single\": {\n \"mode\": \"SIGN_MODE_DIRECT\"\n }\n },\n \"sequence\": \"2\"\n }\n ],\n \"fee\": {\n \"amount\": [\n {\n \"denom\": \"uatom\",\n \"amount\": \"1856\"\n }\n ],\n \"gas_limit\": \"185520\",\n \"payer\": \"\",\n \"granter\": \"\"\n }\n },\n \"signatures\": [\n \"aeLtmSnoXOZzk/9Kt/v/0XEtKkUGDEJke5fjSYEicPgPYxxJk9qNoJj85sotGxgwSC0e2HAEWRbgEcCe0P6Dlg==\"\n ]\n}"}, -{"tx_id":7,"tx":"{\n \"body\": {\n \"messages\": [\n {\n \"@type\": \"/ibc.core.client.v1.MsgUpdateClient\",\n \"client_id\": \"07-tendermint-0\",\n \"header\": {\n \"@type\": \"/ibc.lightclients.tendermint.v1.Header\",\n \"signed_header\": {\n \"header\": {\n \"version\": {\n \"block\": \"11\",\n \"app\": \"0\"\n },\n \"chain_id\": \"osmosis-1001\",\n \"height\": \"30\",\n \"time\": \"2022-06-02T21:01:23.730205125Z\",\n \"last_block_id\": {\n \"hash\": \"/R0Y8fqciyDWRJyL0qapIiNWGKEABxE2RT9UowCkP+M=\",\n \"part_set_header\": {\n \"total\": 1,\n \"hash\": \"Byh5DTurula9F9syfrHr/pvp9a+yDeU6yEvnND2UmkE=\"\n }\n },\n \"last_commit_hash\": \"qbFMFp3hkLbqMen5obCAMisW/WNzH3P0PJTu4EgeI9Y=\",\n \"data_hash\": \"rrQes3YhPxjvPTOHLAk/PrKhMioBOASLA+wqmIhyHkQ=\",\n \"validators_hash\": \"6rFWbJFcGfUbAG/oH7fPPHrC+k58C6oiOb44pksr5NI=\",\n \"next_validators_hash\": \"6rFWbJFcGfUbAG/oH7fPPHrC+k58C6oiOb44pksr5NI=\",\n \"consensus_hash\": \"BICRvH3cKD93v7+R1zxE2ljD34qcvIZ0Bdi389qtoi8=\",\n \"app_hash\": \"FNJ8gHyfTR/DdBPAYMZnmHW19+LRLL+8sZOCW1QL8XU=\",\n \"last_results_hash\": \"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=\",\n \"evidence_hash\": \"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=\",\n \"proposer_address\": \"6KMcLEZQl4IYnvRQqoIDDzDvnYM=\"\n },\n \"commit\": {\n \"height\": \"30\",\n \"round\": 0,\n \"block_id\": {\n \"hash\": \"W+jen8Unk8D+v2ivyBsiI420AT15OqUVmm3Ulm2vjGs=\",\n \"part_set_header\": {\n \"total\": 1,\n \"hash\": \"TLChUgEG5LO24du0he0eT3p1AbY2ZSK9RMWXTeD8Wo8=\"\n }\n },\n \"signatures\": [\n {\n \"block_id_flag\": \"BLOCK_ID_FLAG_COMMIT\",\n \"validator_address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"timestamp\": \"2022-06-02T21:01:26.059278959Z\",\n \"signature\": \"pc7rpNXWzDxT2nGx+bTN3mEgxi2fndSn4Vhde//aUpiCIhf7XZGlhWW8ql28m9Q/idPALsgAY1QYKfhgrWc8BA==\"\n },\n {\n \"block_id_flag\": \"BLOCK_ID_FLAG_COMMIT\",\n \"validator_address\": \"6KMcLEZQl4IYnvRQqoIDDzDvnYM=\",\n \"timestamp\": \"2022-06-02T21:01:26.162092626Z\",\n \"signature\": \"SmDnuOhHCAC4AjXT9rBzu/2hl9XnrBHi4xbPgkr+gFwt+iel1pnrlG5qkUcA6i/K8+4c+feqogo4Z8AbdEumDA==\"\n }\n ]\n }\n },\n \"validator_set\": {\n \"validators\": [\n {\n \"address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"pub_key\": {\n \"ed25519\": \"hhrauMYxUfpQMIgIA5TE242yIYZtqjyNXLQhztAkScM=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"-100000\"\n },\n {\n \"address\": \"6KMcLEZQl4IYnvRQqoIDDzDvnYM=\",\n \"pub_key\": {\n \"ed25519\": \"avI/FwInJgqfue0GaYUO5pxYxCqpAwr3mHcCIask4yw=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"100000\"\n }\n ],\n \"proposer\": {\n \"address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"pub_key\": {\n \"ed25519\": \"hhrauMYxUfpQMIgIA5TE242yIYZtqjyNXLQhztAkScM=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"-100000\"\n },\n \"total_voting_power\": \"0\"\n },\n \"trusted_height\": {\n \"revision_number\": \"1001\",\n \"revision_height\": \"24\"\n },\n \"trusted_validators\": {\n \"validators\": [\n {\n \"address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"pub_key\": {\n \"ed25519\": \"hhrauMYxUfpQMIgIA5TE242yIYZtqjyNXLQhztAkScM=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"-100000\"\n },\n {\n \"address\": \"6KMcLEZQl4IYnvRQqoIDDzDvnYM=\",\n \"pub_key\": {\n \"ed25519\": \"avI/FwInJgqfue0GaYUO5pxYxCqpAwr3mHcCIask4yw=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"100000\"\n }\n ],\n \"proposer\": {\n \"address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"pub_key\": {\n \"ed25519\": \"hhrauMYxUfpQMIgIA5TE242yIYZtqjyNXLQhztAkScM=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"-100000\"\n },\n \"total_voting_power\": \"0\"\n }\n },\n \"signer\": \"cosmos1kczfgaqd74px9cgkfl4v2tnknnsh3lkuhulvtz\"\n },\n {\n \"@type\": \"/ibc.core.channel.v1.MsgChannelOpenInit\",\n \"port_id\": \"transfer\",\n \"channel\": {\n \"state\": \"STATE_INIT\",\n \"ordering\": \"ORDER_UNORDERED\",\n \"counterparty\": {\n \"port_id\": \"transfer\",\n \"channel_id\": \"\"\n },\n \"connection_hops\": [\n \"connection-0\"\n ],\n \"version\": \"ics20-1\"\n },\n \"signer\": \"cosmos1kczfgaqd74px9cgkfl4v2tnknnsh3lkuhulvtz\"\n }\n ],\n \"memo\": \"\",\n \"timeout_height\": \"0\",\n \"extension_options\": [],\n \"non_critical_extension_options\": []\n },\n \"auth_info\": {\n \"signer_infos\": [\n {\n \"public_key\": {\n \"@type\": \"/cosmos.crypto.secp256k1.PubKey\",\n \"key\": \"AvGJBwmMw1+SzbAvvLfraQaudHOoPkNOWsW6JM/J9QSt\"\n },\n \"mode_info\": {\n \"single\": {\n \"mode\": \"SIGN_MODE_DIRECT\"\n }\n },\n \"sequence\": \"3\"\n }\n ],\n \"fee\": {\n \"amount\": [\n {\n \"denom\": \"uatom\",\n \"amount\": \"2011\"\n }\n ],\n \"gas_limit\": \"201026\",\n \"payer\": \"\",\n \"granter\": \"\"\n }\n },\n \"signatures\": [\n \"u/fXhYIW4ZbKaiDqHIdc5usjprmie4+6K1nEe/BWhEp9WO+xIEmculVM13W2HCekKaf415LE5wzTYDzW9OShHQ==\"\n ]\n}"}, -{"tx_id":9,"tx":"{\n \"body\": {\n \"messages\": [\n {\n \"@type\": \"/ibc.core.client.v1.MsgUpdateClient\",\n \"client_id\": \"07-tendermint-0\",\n \"header\": {\n \"@type\": \"/ibc.lightclients.tendermint.v1.Header\",\n \"signed_header\": {\n \"header\": {\n \"version\": {\n \"block\": \"11\",\n \"app\": \"0\"\n },\n \"chain_id\": \"osmosis-1001\",\n \"height\": \"38\",\n \"time\": \"2022-06-02T21:01:42.403218967Z\",\n \"last_block_id\": {\n \"hash\": \"2mGKSkvA+alvPviPWGHbqpUO1VuuU1qG6QQFuW2EfS4=\",\n \"part_set_header\": {\n \"total\": 1,\n \"hash\": \"hjhDM4G+vY0pLABQAwe7tICJ3Bb3GcdhrQmzFrKSW8I=\"\n }\n },\n \"last_commit_hash\": \"qf63kEhR7BYdDOIs3CVae0IySqtZNVDRYU+ZIyJjdNU=\",\n \"data_hash\": \"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=\",\n \"validators_hash\": \"6rFWbJFcGfUbAG/oH7fPPHrC+k58C6oiOb44pksr5NI=\",\n \"next_validators_hash\": \"6rFWbJFcGfUbAG/oH7fPPHrC+k58C6oiOb44pksr5NI=\",\n \"consensus_hash\": \"BICRvH3cKD93v7+R1zxE2ljD34qcvIZ0Bdi389qtoi8=\",\n \"app_hash\": \"MKRYXXQRdDyESbqsKGbmYyAd5ZG0rBV0okLIDxO7CMw=\",\n \"last_results_hash\": \"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=\",\n \"evidence_hash\": \"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=\",\n \"proposer_address\": \"6KMcLEZQl4IYnvRQqoIDDzDvnYM=\"\n },\n \"commit\": {\n \"height\": \"38\",\n \"round\": 0,\n \"block_id\": {\n \"hash\": \"oKEdaE5Tlu1ZoxY24VaQhnfg5cKhgdPW8jqmq5PtsfU=\",\n \"part_set_header\": {\n \"total\": 1,\n \"hash\": \"gj7f6tbhx5+fY1+kCPQrNwAtIUNCuNnfmeN3DagvbBk=\"\n }\n },\n \"signatures\": [\n {\n \"block_id_flag\": \"BLOCK_ID_FLAG_COMMIT\",\n \"validator_address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"timestamp\": \"2022-06-02T21:01:44.807735260Z\",\n \"signature\": \"D1O+VPU4rb2NeFTNYVrQjPYJ8RZ8b8iuMOD/IsSjWrZUn0EGTZuu2m9GHq+9/0JqPQpwtZ2ZV0fJgV1+a4wtDA==\"\n },\n {\n \"block_id_flag\": \"BLOCK_ID_FLAG_COMMIT\",\n \"validator_address\": \"6KMcLEZQl4IYnvRQqoIDDzDvnYM=\",\n \"timestamp\": \"2022-06-02T21:01:44.842610051Z\",\n \"signature\": \"4vTlsQnN4VYc9gEa+B9liicgOXsonaqGXSaeU+lvjnZd1qhjeJPCYPiILC5dvV6lWCsdqWwDmb0kci/Mh7TzBg==\"\n }\n ]\n }\n },\n \"validator_set\": {\n \"validators\": [\n {\n \"address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"pub_key\": {\n \"ed25519\": \"hhrauMYxUfpQMIgIA5TE242yIYZtqjyNXLQhztAkScM=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"-100000\"\n },\n {\n \"address\": \"6KMcLEZQl4IYnvRQqoIDDzDvnYM=\",\n \"pub_key\": {\n \"ed25519\": \"avI/FwInJgqfue0GaYUO5pxYxCqpAwr3mHcCIask4yw=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"100000\"\n }\n ],\n \"proposer\": {\n \"address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"pub_key\": {\n \"ed25519\": \"hhrauMYxUfpQMIgIA5TE242yIYZtqjyNXLQhztAkScM=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"-100000\"\n },\n \"total_voting_power\": \"0\"\n },\n \"trusted_height\": {\n \"revision_number\": \"1001\",\n \"revision_height\": \"30\"\n },\n \"trusted_validators\": {\n \"validators\": [\n {\n \"address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"pub_key\": {\n \"ed25519\": \"hhrauMYxUfpQMIgIA5TE242yIYZtqjyNXLQhztAkScM=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"-100000\"\n },\n {\n \"address\": \"6KMcLEZQl4IYnvRQqoIDDzDvnYM=\",\n \"pub_key\": {\n \"ed25519\": \"avI/FwInJgqfue0GaYUO5pxYxCqpAwr3mHcCIask4yw=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"100000\"\n }\n ],\n \"proposer\": {\n \"address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"pub_key\": {\n \"ed25519\": \"hhrauMYxUfpQMIgIA5TE242yIYZtqjyNXLQhztAkScM=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"-100000\"\n },\n \"total_voting_power\": \"0\"\n }\n },\n \"signer\": \"cosmos1kczfgaqd74px9cgkfl4v2tnknnsh3lkuhulvtz\"\n },\n {\n \"@type\": \"/ibc.core.channel.v1.MsgChannelOpenAck\",\n \"port_id\": \"transfer\",\n \"channel_id\": \"channel-0\",\n \"counterparty_channel_id\": \"channel-0\",\n \"counterparty_version\": \"ics20-1\",\n \"proof_try\": \"CtQCCtECCi1jaGFubmVsRW5kcy9wb3J0cy90cmFuc2Zlci9jaGFubmVscy9jaGFubmVsLTASMggCEAEaFQoIdHJhbnNmZXISCWNoYW5uZWwtMCIMY29ubmVjdGlvbi0wKgdpY3MyMC0xGgsIARgBIAEqAwACRiIrCAESBAIERiAaISAIDCZmmihzAxTbuEdf5Jon+Qe5B89gDGQofrf5KuwD8CIrCAESBAQIRiAaISCIQea9JbmhMeVAaNbmmW6KsmsrEG2TL7BDgsaXbhWiSSIrCAESBAYMRiAaISB52qeVtvgJgx+bMVTm+OewVFISFjw7XpbCOHqWwuqMbCIrCAESBAgURiAaISCmHYH5A4Tziv3ga8M5V1TvtY9N8J/fh8PeqjocMN5aXCIrCAESBAw0RiAaISDEPoQzK4EJl7aXQL1Etsm5AIMG+1gWNADdLO8rCBAPMAr8AQr5AQoDaWJjEiAGUxIYzW7B0bbsfkwcs9G0ryJVTsL3DUHU0ywnjYDQVxoJCAEYASABKgEAIiUIARIhAUayD1sqD5N3koJLoD5Cenp4hU5w5Hs10qmKy+U4lvkTIiUIARIhASMhSZiRwnR5BCJtWv3vn97qxK9EMXGDJQWvlAiGWv1/IicIARIBARogstKzZ999h/G+YjkbG1Cfb9kbKD9rixW5TVG9wK2ZdO0iJQgBEiEBanjmWrheD7bfwhwmOIgbmhAvshreswDR0FbWtK0cE1MiJwgBEgEBGiB5Tht3Ha3ESR2kT8fIB9fpbK736tY7UxxhQyVUmDrZ4g==\",\n \"proof_height\": {\n \"revision_number\": \"1001\",\n \"revision_height\": \"38\"\n },\n \"signer\": \"cosmos1kczfgaqd74px9cgkfl4v2tnknnsh3lkuhulvtz\"\n }\n ],\n \"memo\": \"\",\n \"timeout_height\": \"0\",\n \"extension_options\": [],\n \"non_critical_extension_options\": []\n },\n \"auth_info\": {\n \"signer_infos\": [\n {\n \"public_key\": {\n \"@type\": \"/cosmos.crypto.secp256k1.PubKey\",\n \"key\": \"AvGJBwmMw1+SzbAvvLfraQaudHOoPkNOWsW6JM/J9QSt\"\n },\n \"mode_info\": {\n \"single\": {\n \"mode\": \"SIGN_MODE_DIRECT\"\n }\n },\n \"sequence\": \"4\"\n }\n ],\n \"fee\": {\n \"amount\": [\n {\n \"denom\": \"uatom\",\n \"amount\": \"1549\"\n }\n ],\n \"gas_limit\": \"154895\",\n \"payer\": \"\",\n \"granter\": \"\"\n }\n },\n \"signatures\": [\n \"p1YRDZ0z8Wvi5HrNyzTHOyiBKqC4t0FWTPk41Y5YqjQjDVPoGCmH4RpzSeNRblmFPkc+nQzfKHY+MAU/8VrHNQ==\"\n ]\n}"}, -{"tx_id":11,"tx":"{\n \"body\": {\n \"messages\": [\n {\n \"@type\": \"/cosmos.bank.v1beta1.MsgSend\",\n \"from_address\": \"cosmos1wq7sy0qyz64sgqv2gqlda6lsakcuv0l9dutqzw\",\n \"to_address\": \"cosmos1gts7s7rxldg7ggtylxggnzaxyrez5le2872zge\",\n \"amount\": [\n {\n \"denom\": \"uatom\",\n \"amount\": \"10000000000\"\n }\n ]\n }\n ],\n \"memo\": \"\",\n \"timeout_height\": \"0\",\n \"extension_options\": [],\n \"non_critical_extension_options\": []\n },\n \"auth_info\": {\n \"signer_infos\": [\n {\n \"public_key\": {\n \"@type\": \"/cosmos.crypto.secp256k1.PubKey\",\n \"key\": \"AnUx9tLaoezfknkdyLDC0CvrOlDH8yd7RWPL5ZBHFwXc\"\n },\n \"mode_info\": {\n \"single\": {\n \"mode\": \"SIGN_MODE_DIRECT\"\n }\n },\n \"sequence\": \"0\"\n }\n ],\n \"fee\": {\n \"amount\": [],\n \"gas_limit\": \"200000\",\n \"payer\": \"\",\n \"granter\": \"\"\n }\n },\n \"signatures\": [\n \"VvpiKa/UP6kUMElpaBFxSr75KpQ9GvSNS4LgvQkGFV4gFcMz7Jp0XfYV/QVNgaTwTf3cKmRo+a1FlQxDq33Pkw==\"\n ]\n}"}, -{"tx_id":12,"tx":"{\n \"body\": {\n \"messages\": [\n {\n \"@type\": \"/cosmos.bank.v1beta1.MsgSend\",\n \"from_address\": \"cosmos1wq7sy0qyz64sgqv2gqlda6lsakcuv0l9dutqzw\",\n \"to_address\": \"cosmos1kguxk3t2254kfqjwvg2gwva8kt6f0muyqykzdh\",\n \"amount\": [\n {\n \"denom\": \"uatom\",\n \"amount\": \"10000000000\"\n }\n ]\n }\n ],\n \"memo\": \"\",\n \"timeout_height\": \"0\",\n \"extension_options\": [],\n \"non_critical_extension_options\": []\n },\n \"auth_info\": {\n \"signer_infos\": [\n {\n \"public_key\": {\n \"@type\": \"/cosmos.crypto.secp256k1.PubKey\",\n \"key\": \"AnUx9tLaoezfknkdyLDC0CvrOlDH8yd7RWPL5ZBHFwXc\"\n },\n \"mode_info\": {\n \"single\": {\n \"mode\": \"SIGN_MODE_DIRECT\"\n }\n },\n \"sequence\": \"1\"\n }\n ],\n \"fee\": {\n \"amount\": [],\n \"gas_limit\": \"200000\",\n \"payer\": \"\",\n \"granter\": \"\"\n }\n },\n \"signatures\": [\n \"gXWcwXNOgdJFT3OrR6GDghY5Kof9yj8WNw0bqya1uaEegCqOQjPsdfGL6FOCQWgp9aBXAjBGEHO4m2L+8ktSvA==\"\n ]\n}"}, -{"tx_id":14,"tx":"{\n \"body\": {\n \"messages\": [\n {\n \"@type\": \"/cosmos.bank.v1beta1.MsgSend\",\n \"from_address\": \"cosmos1wq7sy0qyz64sgqv2gqlda6lsakcuv0l9dutqzw\",\n \"to_address\": \"cosmos19t00vprf88mdlw4sxjhf2mr8uhd9zpdjvgnzn3\",\n \"amount\": [\n {\n \"denom\": \"uatom\",\n \"amount\": \"10000000000\"\n }\n ]\n }\n ],\n \"memo\": \"\",\n \"timeout_height\": \"0\",\n \"extension_options\": [],\n \"non_critical_extension_options\": []\n },\n \"auth_info\": {\n \"signer_infos\": [\n {\n \"public_key\": {\n \"@type\": \"/cosmos.crypto.secp256k1.PubKey\",\n \"key\": \"AnUx9tLaoezfknkdyLDC0CvrOlDH8yd7RWPL5ZBHFwXc\"\n },\n \"mode_info\": {\n \"single\": {\n \"mode\": \"SIGN_MODE_DIRECT\"\n }\n },\n \"sequence\": \"2\"\n }\n ],\n \"fee\": {\n \"amount\": [],\n \"gas_limit\": \"200000\",\n \"payer\": \"\",\n \"granter\": \"\"\n }\n },\n \"signatures\": [\n \"8OluO5Ve/0ICY6TuBTkYuvYTRFnOsS0lmzUtj+jXnWBNOdOJT6g9qlPgCbsiTC1W3abhYMbxKv5XHDtqm3O9IA==\"\n ]\n}"}, -{"tx_id":17,"tx":"{\n \"body\": {\n \"messages\": [\n {\n \"@type\": \"/ibc.applications.transfer.v1.MsgTransfer\",\n \"source_port\": \"transfer\",\n \"source_channel\": \"channel-0\",\n \"token\": {\n \"denom\": \"uatom\",\n \"amount\": \"1000000\"\n },\n \"sender\": \"cosmos1gts7s7rxldg7ggtylxggnzaxyrez5le2872zge\",\n \"receiver\": \"osmo1gts7s7rxldg7ggtylxggnzaxyrez5le209ej7t\",\n \"timeout_height\": {\n \"revision_number\": \"1001\",\n \"revision_height\": \"48\"\n },\n \"timeout_timestamp\": \"1654204348373032043\"\n }\n ],\n \"memo\": \"\",\n \"timeout_height\": \"0\",\n \"extension_options\": [],\n \"non_critical_extension_options\": []\n },\n \"auth_info\": {\n \"signer_infos\": [\n {\n \"public_key\": {\n \"@type\": \"/cosmos.crypto.secp256k1.PubKey\",\n \"key\": \"A1Q5QX0UEM6sclMspF6NYcvOnfUv3TbcMjZMpqKzfj08\"\n },\n \"mode_info\": {\n \"single\": {\n \"mode\": \"SIGN_MODE_DIRECT\"\n }\n },\n \"sequence\": \"0\"\n }\n ],\n \"fee\": {\n \"amount\": [\n {\n \"denom\": \"uatom\",\n \"amount\": \"2000\"\n }\n ],\n \"gas_limit\": \"200000\",\n \"payer\": \"\",\n \"granter\": \"\"\n }\n },\n \"signatures\": [\n \"vEwlno69ACfKZyKXw+315xX6fsWGPP3gRG41tntmMO9JkFr8sJ8XWCLbWUV/qZ4aluR4h6AGIwpu0fBQY0GLfQ==\"\n ]\n}"}, -{"tx_id":19,"tx":"{\n \"body\": {\n \"messages\": [\n {\n \"@type\": \"/ibc.applications.transfer.v1.MsgTransfer\",\n \"source_port\": \"transfer\",\n \"source_channel\": \"channel-0\",\n \"token\": {\n \"denom\": \"uatom\",\n \"amount\": \"1000000\"\n },\n \"sender\": \"cosmos1kguxk3t2254kfqjwvg2gwva8kt6f0muyqykzdh\",\n \"receiver\": \"osmo1kguxk3t2254kfqjwvg2gwva8kt6f0muygl9jm9\",\n \"timeout_height\": {\n \"revision_number\": \"1001\",\n \"revision_height\": \"1038\"\n },\n \"timeout_timestamp\": \"1654204353107943629\"\n }\n ],\n \"memo\": \"\",\n \"timeout_height\": \"0\",\n \"extension_options\": [],\n \"non_critical_extension_options\": []\n },\n \"auth_info\": {\n \"signer_infos\": [\n {\n \"public_key\": {\n \"@type\": \"/cosmos.crypto.secp256k1.PubKey\",\n \"key\": \"A41JuTVD7+/oNToQmZ8aMFYF3KAYg5wnDaxkPS6atJLU\"\n },\n \"mode_info\": {\n \"single\": {\n \"mode\": \"SIGN_MODE_DIRECT\"\n }\n },\n \"sequence\": \"0\"\n }\n ],\n \"fee\": {\n \"amount\": [\n {\n \"denom\": \"uatom\",\n \"amount\": \"2000\"\n }\n ],\n \"gas_limit\": \"200000\",\n \"payer\": \"\",\n \"granter\": \"\"\n }\n },\n \"signatures\": [\n \"Bf+5P+rhgxJHX/NHCbI+/WGojQovBKCXZuYUr1Vna1NK5e+Bs+utZWEX+LFwuCFQyssDubkGq64Q9ZmgaD8dJw==\"\n ]\n}"}, -{"tx_id":21,"tx":"{\n \"body\": {\n \"messages\": [\n {\n \"@type\": \"/ibc.applications.transfer.v1.MsgTransfer\",\n \"source_port\": \"transfer\",\n \"source_channel\": \"channel-0\",\n \"token\": {\n \"denom\": \"uatom\",\n \"amount\": \"1000000\"\n },\n \"sender\": \"cosmos19t00vprf88mdlw4sxjhf2mr8uhd9zpdjvgnzn3\",\n \"receiver\": \"osmo19t00vprf88mdlw4sxjhf2mr8uhd9zpdjynqj9r\",\n \"timeout_height\": {\n \"revision_number\": \"1001\",\n \"revision_height\": \"1038\"\n },\n \"timeout_timestamp\": \"1654204357766832506\"\n }\n ],\n \"memo\": \"\",\n \"timeout_height\": \"0\",\n \"extension_options\": [],\n \"non_critical_extension_options\": []\n },\n \"auth_info\": {\n \"signer_infos\": [\n {\n \"public_key\": {\n \"@type\": \"/cosmos.crypto.secp256k1.PubKey\",\n \"key\": \"A+2TnoZ+vjacMNN7sFxxlA4wRQ8lC6aEHfkS5pObrD1o\"\n },\n \"mode_info\": {\n \"single\": {\n \"mode\": \"SIGN_MODE_DIRECT\"\n }\n },\n \"sequence\": \"0\"\n }\n ],\n \"fee\": {\n \"amount\": [\n {\n \"denom\": \"uatom\",\n \"amount\": \"2000\"\n }\n ],\n \"gas_limit\": \"200000\",\n \"payer\": \"\",\n \"granter\": \"\"\n }\n },\n \"signatures\": [\n \"j0uOPTd34ZPGJntPUV6lfFF5YMmrnlguFNRxemHtX38103haDcXrWt8ce1nFSG3IFJAubWtuGi0Fw+dxz4A+BA==\"\n ]\n}"}, -{"tx_id":23,"tx":"{\n \"body\": {\n \"messages\": [\n {\n \"@type\": \"/ibc.core.client.v1.MsgUpdateClient\",\n \"client_id\": \"07-tendermint-0\",\n \"header\": {\n \"@type\": \"/ibc.lightclients.tendermint.v1.Header\",\n \"signed_header\": {\n \"header\": {\n \"version\": {\n \"block\": \"11\",\n \"app\": \"0\"\n },\n \"chain_id\": \"osmosis-1001\",\n \"height\": \"74\",\n \"time\": \"2022-06-02T21:03:07.489186798Z\",\n \"last_block_id\": {\n \"hash\": \"Ph634nxIqKCLm1RuIViZKnjfziBpd6iMPjcFs+N6RD4=\",\n \"part_set_header\": {\n \"total\": 1,\n \"hash\": \"tjQBwGlrzKeh8amRaUQBcnfFLtpMAr2tNGWHqx7hWMA=\"\n }\n },\n \"last_commit_hash\": \"Pq8JpBl3jrEtUSONY7KQlRswYkhuEGUs02LrKonwGWY=\",\n \"data_hash\": \"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=\",\n \"validators_hash\": \"6rFWbJFcGfUbAG/oH7fPPHrC+k58C6oiOb44pksr5NI=\",\n \"next_validators_hash\": \"6rFWbJFcGfUbAG/oH7fPPHrC+k58C6oiOb44pksr5NI=\",\n \"consensus_hash\": \"BICRvH3cKD93v7+R1zxE2ljD34qcvIZ0Bdi389qtoi8=\",\n \"app_hash\": \"HvHWPpnRc2uzUgrVBqGEi/FJGxmI2Jyt8UD8FeLKEUo=\",\n \"last_results_hash\": \"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=\",\n \"evidence_hash\": \"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=\",\n \"proposer_address\": \"6KMcLEZQl4IYnvRQqoIDDzDvnYM=\"\n },\n \"commit\": {\n \"height\": \"74\",\n \"round\": 0,\n \"block_id\": {\n \"hash\": \"Nywe/HaI99B5ph0+pcgvLMvOx9Ofp/5XZylXuwMypkU=\",\n \"part_set_header\": {\n \"total\": 1,\n \"hash\": \"tUWbth8A91O/ByP2jmqiVbqq8KEmG4v3RpnQNkeb57c=\"\n }\n },\n \"signatures\": [\n {\n \"block_id_flag\": \"BLOCK_ID_FLAG_COMMIT\",\n \"validator_address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"timestamp\": \"2022-06-02T21:03:09.824380132Z\",\n \"signature\": \"O4bSNVekdQoMZ1v09xmXVGWrMQG+7YmJJjN1+D1cFTkDa0z8qJXuYcnpiUEn5Q5he7zmtoT4+TMMA7B/hWnhBw==\"\n },\n {\n \"block_id_flag\": \"BLOCK_ID_FLAG_COMMIT\",\n \"validator_address\": \"6KMcLEZQl4IYnvRQqoIDDzDvnYM=\",\n \"timestamp\": \"2022-06-02T21:03:09.923017799Z\",\n \"signature\": \"dGjRKY/NPWJ1vHPHuW0RRRNkwaKXmSwMWGYtu995lgOshv8um6PBT6NrarlsT2DhCa8FFp+4IOq9RhBWsuLeCQ==\"\n }\n ]\n }\n },\n \"validator_set\": {\n \"validators\": [\n {\n \"address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"pub_key\": {\n \"ed25519\": \"hhrauMYxUfpQMIgIA5TE242yIYZtqjyNXLQhztAkScM=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"-100000\"\n },\n {\n \"address\": \"6KMcLEZQl4IYnvRQqoIDDzDvnYM=\",\n \"pub_key\": {\n \"ed25519\": \"avI/FwInJgqfue0GaYUO5pxYxCqpAwr3mHcCIask4yw=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"100000\"\n }\n ],\n \"proposer\": {\n \"address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"pub_key\": {\n \"ed25519\": \"hhrauMYxUfpQMIgIA5TE242yIYZtqjyNXLQhztAkScM=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"-100000\"\n },\n \"total_voting_power\": \"0\"\n },\n \"trusted_height\": {\n \"revision_number\": \"1001\",\n \"revision_height\": \"38\"\n },\n \"trusted_validators\": {\n \"validators\": [\n {\n \"address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"pub_key\": {\n \"ed25519\": \"hhrauMYxUfpQMIgIA5TE242yIYZtqjyNXLQhztAkScM=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"-100000\"\n },\n {\n \"address\": \"6KMcLEZQl4IYnvRQqoIDDzDvnYM=\",\n \"pub_key\": {\n \"ed25519\": \"avI/FwInJgqfue0GaYUO5pxYxCqpAwr3mHcCIask4yw=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"100000\"\n }\n ],\n \"proposer\": {\n \"address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"pub_key\": {\n \"ed25519\": \"hhrauMYxUfpQMIgIA5TE242yIYZtqjyNXLQhztAkScM=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"-100000\"\n },\n \"total_voting_power\": \"0\"\n }\n },\n \"signer\": \"cosmos1kczfgaqd74px9cgkfl4v2tnknnsh3lkuhulvtz\"\n },\n {\n \"@type\": \"/ibc.core.channel.v1.MsgTimeout\",\n \"packet\": {\n \"sequence\": \"1\",\n \"source_port\": \"transfer\",\n \"source_channel\": \"channel-0\",\n \"destination_port\": \"transfer\",\n \"destination_channel\": \"channel-0\",\n \"data\": \"eyJhbW91bnQiOiIxMDAwMDAwIiwiZGVub20iOiJ1YXRvbSIsInJlY2VpdmVyIjoib3NtbzFndHM3czdyeGxkZzdnZ3R5bHhnZ256YXh5cmV6NWxlMjA5ZWo3dCIsInNlbmRlciI6ImNvc21vczFndHM3czdyeGxkZzdnZ3R5bHhnZ256YXh5cmV6NWxlMjg3MnpnZSJ9\",\n \"timeout_height\": {\n \"revision_number\": \"1001\",\n \"revision_height\": \"48\"\n },\n \"timeout_timestamp\": \"1654204348373032043\"\n },\n \"proof_unreceived\": \"CosDEogDCjZyZWNlaXB0cy9wb3J0cy90cmFuc2Zlci9jaGFubmVscy9jaGFubmVsLTAvc2VxdWVuY2VzLzESzQIKMm5leHRTZXF1ZW5jZVNlbmQvcG9ydHMvdHJhbnNmZXIvY2hhbm5lbHMvY2hhbm5lbC0wEggAAAAAAAAABBoLCAEYASABKgMAAnoiKQgBEiUCBHogP7DWlVVngbHt4Ojh7a90xn9VJGm6WJ0mwFZhw37cjfcgIikIARIlBAh6IJ851RvCL7IxQfZyxjqJRTPAaPxXYQbZC6HzhSvusmCmICIpCAESJQYMeiAtCNKb9fDU/oxD5n5Ckyw5ZHtbuLipbINMCHr3+evajiAiKQgBEiUIFnogCQzrIDuokWJ4h2Tk0g7aaodi04o1piRLYdaaS4d4EosgIikIARIlCi56ID39uV/DJMtN1wEQusnYyoC8A7Xiw3MgFN6Irh4Efyc9ICIpCAESJQxCeiCZUJGoSckexY26KBUTyO+Z3whfvUyzMf4JSPTLo3OuKCAK/AEK+QEKA2liYxIg38GwOBJ9/6fO/jiw2Of0NK2WalHMjOe5qvWiQPqUQ0saCQgBGAEgASoBACIlCAESIQFGsg9bKg+Td5KCS6A+Qnp6eIVOcOR7NdKpisvlOJb5EyIlCAESIQEjIUmYkcJ0eQQibVr975/e6sSvRDFxgyUFr5QIhlr9fyInCAESAQEaILLSs2fffYfxvmI5GxtQn2/ZGyg/a4sVuU1RvcCtmXTtIiUIARIhAQDS9NmSmNwk7enFDCCJzXNFy7RlA3gHQLN5Ws/lwD75IicIARIBARogzqkEbaN4vx2zUqTxqfZtmNpEBrcuBl4+ZW+KBgm7E6A=\",\n \"proof_height\": {\n \"revision_number\": \"1001\",\n \"revision_height\": \"74\"\n },\n \"next_sequence_recv\": \"1\",\n \"signer\": \"cosmos1kczfgaqd74px9cgkfl4v2tnknnsh3lkuhulvtz\"\n },\n {\n \"@type\": \"/ibc.core.channel.v1.MsgRecvPacket\",\n \"packet\": {\n \"sequence\": \"2\",\n \"source_port\": \"transfer\",\n \"source_channel\": \"channel-0\",\n \"destination_port\": \"transfer\",\n \"destination_channel\": \"channel-0\",\n \"data\": \"eyJhbW91bnQiOiIxMDAwMDAwIiwiZGVub20iOiJ1b3NtbyIsInJlY2VpdmVyIjoiY29zbW9zMXl6ODV2ajcwZDhsNG10emRxY2xhZWFkejB0NjBkcWh6cnh5Y25xIiwic2VuZGVyIjoib3NtbzF5ejg1dmo3MGQ4bDRtdHpkcWNsYWVhZHowdDYwZHFoenRhaGc5aiJ9\",\n \"timeout_height\": {\n \"revision_number\": \"1004\",\n \"revision_height\": \"1043\"\n },\n \"timeout_timestamp\": \"1654204353050089129\"\n },\n \"proof_commitment\": \"CvUCCvICCjljb21taXRtZW50cy9wb3J0cy90cmFuc2Zlci9jaGFubmVscy9jaGFubmVsLTAvc2VxdWVuY2VzLzISILbJzV8M8eUOQIc/q874jJqR6OZhKD5T2bN4omSXrP/EGgsIARgBIAEqAwACdiIrCAESBAIEeiAaISAUoinHhTyEpkLP77G7LtalYNljvHKfUH+h8QbZQniTGyIrCAESBAQGeiAaISD2gmBaFsRctVdXLMncfyzYPrGuidcWPmjB8wG4e8XCyyIpCAESJQYKeiCdYN57xWQMfmSaIpfPBZYsBNlZTC7JfxzaKaSneXaSGSAiKwgBEgQIFnogGiEgVTwOHGqdfvScN6d3Ri0erpDv1JIcha0gDMocA25PaLciKQgBEiUKLnogPf25X8Mky03XARC6ydjKgLwDteLDcyAU3oiuHgR/Jz0gIikIARIlDEJ6IJlQkahJyR7FjbooFRPI75nfCF+9TLMx/glI9Mujc64oIAr8AQr5AQoDaWJjEiDfwbA4En3/p87+OLDY5/Q0rZZqUcyM57mq9aJA+pRDSxoJCAEYASABKgEAIiUIARIhAUayD1sqD5N3koJLoD5Cenp4hU5w5Hs10qmKy+U4lvkTIiUIARIhASMhSZiRwnR5BCJtWv3vn97qxK9EMXGDJQWvlAiGWv1/IicIARIBARogstKzZ999h/G+YjkbG1Cfb9kbKD9rixW5TVG9wK2ZdO0iJQgBEiEBANL02ZKY3CTt6cUMIInNc0XLtGUDeAdAs3laz+XAPvkiJwgBEgEBGiDOqQRto3i/HbNSpPGp9m2Y2kQGty4GXj5lb4oGCbsToA==\",\n \"proof_height\": {\n \"revision_number\": \"1001\",\n \"revision_height\": \"74\"\n },\n \"signer\": \"cosmos1kczfgaqd74px9cgkfl4v2tnknnsh3lkuhulvtz\"\n },\n {\n \"@type\": \"/ibc.core.channel.v1.MsgRecvPacket\",\n \"packet\": {\n \"sequence\": \"3\",\n \"source_port\": \"transfer\",\n \"source_channel\": \"channel-0\",\n \"destination_port\": \"transfer\",\n \"destination_channel\": \"channel-0\",\n \"data\": \"eyJhbW91bnQiOiIxMDAwMDAwIiwiZGVub20iOiJ1b3NtbyIsInJlY2VpdmVyIjoiY29zbW9zMXlyYXB0eHNkeHJ6OXc3ems4cmRqM3BzNjd2bDgweGwwYXo3cGtmIiwic2VuZGVyIjoib3NtbzF5cmFwdHhzZHhyejl3N3prOHJkajNwczY3dmw4MHhsMDRlZDNxbSJ9\",\n \"timeout_height\": {\n \"revision_number\": \"1004\",\n \"revision_height\": \"1043\"\n },\n \"timeout_timestamp\": \"1654204357646307839\"\n },\n \"proof_commitment\": \"CvMCCvACCjljb21taXRtZW50cy9wb3J0cy90cmFuc2Zlci9jaGFubmVscy9jaGFubmVsLTAvc2VxdWVuY2VzLzMSIIYooG+1hrEZnjCSSkFadtHfjRb5U/QW4Pj2iN+PrEHVGgsIARgBIAEqAwACeiIpCAESJQIEeiDTuDfCrjb88EE8v4Iz862oDrobDdNUM/AmV2nb378CaSAiKwgBEgQEBnogGiEg9oJgWhbEXLVXVyzJ3H8s2D6xronXFj5owfMBuHvFwssiKQgBEiUGCnognWDee8VkDH5kmiKXzwWWLATZWUwuyX8c2imkp3l2khkgIisIARIECBZ6IBohIFU8DhxqnX70nDend0YtHq6Q79SSHIWtIAzKHANuT2i3IikIARIlCi56ID39uV/DJMtN1wEQusnYyoC8A7Xiw3MgFN6Irh4Efyc9ICIpCAESJQxCeiCZUJGoSckexY26KBUTyO+Z3whfvUyzMf4JSPTLo3OuKCAK/AEK+QEKA2liYxIg38GwOBJ9/6fO/jiw2Of0NK2WalHMjOe5qvWiQPqUQ0saCQgBGAEgASoBACIlCAESIQFGsg9bKg+Td5KCS6A+Qnp6eIVOcOR7NdKpisvlOJb5EyIlCAESIQEjIUmYkcJ0eQQibVr975/e6sSvRDFxgyUFr5QIhlr9fyInCAESAQEaILLSs2fffYfxvmI5GxtQn2/ZGyg/a4sVuU1RvcCtmXTtIiUIARIhAQDS9NmSmNwk7enFDCCJzXNFy7RlA3gHQLN5Ws/lwD75IicIARIBARogzqkEbaN4vx2zUqTxqfZtmNpEBrcuBl4+ZW+KBgm7E6A=\",\n \"proof_height\": {\n \"revision_number\": \"1001\",\n \"revision_height\": \"74\"\n },\n \"signer\": \"cosmos1kczfgaqd74px9cgkfl4v2tnknnsh3lkuhulvtz\"\n }\n ],\n \"memo\": \"\",\n \"timeout_height\": \"0\",\n \"extension_options\": [],\n \"non_critical_extension_options\": []\n },\n \"auth_info\": {\n \"signer_infos\": [\n {\n \"public_key\": {\n \"@type\": \"/cosmos.crypto.secp256k1.PubKey\",\n \"key\": \"AvGJBwmMw1+SzbAvvLfraQaudHOoPkNOWsW6JM/J9QSt\"\n },\n \"mode_info\": {\n \"single\": {\n \"mode\": \"SIGN_MODE_DIRECT\"\n }\n },\n \"sequence\": \"5\"\n }\n ],\n \"fee\": {\n \"amount\": [\n {\n \"denom\": \"uatom\",\n \"amount\": \"3825\"\n }\n ],\n \"gas_limit\": \"382435\",\n \"payer\": \"\",\n \"granter\": \"\"\n }\n },\n \"signatures\": [\n \"6/7SR8LaW310LoxmttIkXC9rLGmBwvLvDAY2StWKXmMqlJB37AWn6sABQm9AlzrPf+uiEgv9VDjjqOCNLv5DIg==\"\n ]\n}"}, -{"tx_id":25,"tx":"{\n \"body\": {\n \"messages\": [\n {\n \"@type\": \"/ibc.core.client.v1.MsgUpdateClient\",\n \"client_id\": \"07-tendermint-0\",\n \"header\": {\n \"@type\": \"/ibc.lightclients.tendermint.v1.Header\",\n \"signed_header\": {\n \"header\": {\n \"version\": {\n \"block\": \"11\",\n \"app\": \"0\"\n },\n \"chain_id\": \"osmosis-1001\",\n \"height\": \"77\",\n \"time\": \"2022-06-02T21:03:14.572369468Z\",\n \"last_block_id\": {\n \"hash\": \"XkHc1acGfdHStLg3637WNE6EfVRKM7QWt37HvM02K0M=\",\n \"part_set_header\": {\n \"total\": 1,\n \"hash\": \"tG6aSeXRDYyhLRonNnJ2Ktg3+v1hedxRpfduhsVXDxU=\"\n }\n },\n \"last_commit_hash\": \"UGRWBL0HvTENEYYvduZkSaCbQ7DGH05HLBOpM0yF7H0=\",\n \"data_hash\": \"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=\",\n \"validators_hash\": \"6rFWbJFcGfUbAG/oH7fPPHrC+k58C6oiOb44pksr5NI=\",\n \"next_validators_hash\": \"6rFWbJFcGfUbAG/oH7fPPHrC+k58C6oiOb44pksr5NI=\",\n \"consensus_hash\": \"BICRvH3cKD93v7+R1zxE2ljD34qcvIZ0Bdi389qtoi8=\",\n \"app_hash\": \"nu/D9bl3Sn7JoTVPRafZHJJppJmHJQOhh1nMXgfP9V4=\",\n \"last_results_hash\": \"7+vmojjCJZg9TZ069IQaldyG0DQWSyoCpRGnZlzQQTo=\",\n \"evidence_hash\": \"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=\",\n \"proposer_address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\"\n },\n \"commit\": {\n \"height\": \"77\",\n \"round\": 0,\n \"block_id\": {\n \"hash\": \"yM7UKInY2Ik73guuftAE8+bvnkDmS7M475/zwMZkv0U=\",\n \"part_set_header\": {\n \"total\": 1,\n \"hash\": \"CZs4RTnBkfxPNEt/puiJhRggMXXAZXrT0m68e49MVBo=\"\n }\n },\n \"signatures\": [\n {\n \"block_id_flag\": \"BLOCK_ID_FLAG_COMMIT\",\n \"validator_address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"timestamp\": \"2022-06-02T21:03:17.024390094Z\",\n \"signature\": \"wL8fcpHzQ0rhHhg0Ev1GoI2q1PbE4BBpML0N1BcxmPKXBvlI9rMEXUDw9FgbtygrJwqjRQY34MIO8FKyAFbGBA==\"\n },\n {\n \"block_id_flag\": \"BLOCK_ID_FLAG_COMMIT\",\n \"validator_address\": \"6KMcLEZQl4IYnvRQqoIDDzDvnYM=\",\n \"timestamp\": \"2022-06-02T21:03:16.922733094Z\",\n \"signature\": \"6SD6npqaWHq1We5SvxezOtapktkBsMJNXtVyxK4aKztR4YLaxE3YYcFsIps64P4Kzd5tzn/wIhzOwe5uy2ZrAA==\"\n }\n ]\n }\n },\n \"validator_set\": {\n \"validators\": [\n {\n \"address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"pub_key\": {\n \"ed25519\": \"hhrauMYxUfpQMIgIA5TE242yIYZtqjyNXLQhztAkScM=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"-100000\"\n },\n {\n \"address\": \"6KMcLEZQl4IYnvRQqoIDDzDvnYM=\",\n \"pub_key\": {\n \"ed25519\": \"avI/FwInJgqfue0GaYUO5pxYxCqpAwr3mHcCIask4yw=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"100000\"\n }\n ],\n \"proposer\": {\n \"address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"pub_key\": {\n \"ed25519\": \"hhrauMYxUfpQMIgIA5TE242yIYZtqjyNXLQhztAkScM=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"-100000\"\n },\n \"total_voting_power\": \"0\"\n },\n \"trusted_height\": {\n \"revision_number\": \"1001\",\n \"revision_height\": \"74\"\n },\n \"trusted_validators\": {\n \"validators\": [\n {\n \"address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"pub_key\": {\n \"ed25519\": \"hhrauMYxUfpQMIgIA5TE242yIYZtqjyNXLQhztAkScM=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"-100000\"\n },\n {\n \"address\": \"6KMcLEZQl4IYnvRQqoIDDzDvnYM=\",\n \"pub_key\": {\n \"ed25519\": \"avI/FwInJgqfue0GaYUO5pxYxCqpAwr3mHcCIask4yw=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"100000\"\n }\n ],\n \"proposer\": {\n \"address\": \"LeI1w0dQ22+9unQdXTpo3NShaLg=\",\n \"pub_key\": {\n \"ed25519\": \"hhrauMYxUfpQMIgIA5TE242yIYZtqjyNXLQhztAkScM=\"\n },\n \"voting_power\": \"100000\",\n \"proposer_priority\": \"-100000\"\n },\n \"total_voting_power\": \"0\"\n }\n },\n \"signer\": \"cosmos1kczfgaqd74px9cgkfl4v2tnknnsh3lkuhulvtz\"\n },\n {\n \"@type\": \"/ibc.core.channel.v1.MsgAcknowledgement\",\n \"packet\": {\n \"sequence\": \"2\",\n \"source_port\": \"transfer\",\n \"source_channel\": \"channel-0\",\n \"destination_port\": \"transfer\",\n \"destination_channel\": \"channel-0\",\n \"data\": \"eyJhbW91bnQiOiIxMDAwMDAwIiwiZGVub20iOiJ1YXRvbSIsInJlY2VpdmVyIjoib3NtbzFrZ3V4azN0MjI1NGtmcWp3dmcyZ3d2YThrdDZmMG11eWdsOWptOSIsInNlbmRlciI6ImNvc21vczFrZ3V4azN0MjI1NGtmcWp3dmcyZ3d2YThrdDZmMG11eXF5a3pkaCJ9\",\n \"timeout_height\": {\n \"revision_number\": \"1001\",\n \"revision_height\": \"1038\"\n },\n \"timeout_timestamp\": \"1654204353107943629\"\n },\n \"acknowledgement\": \"eyJyZXN1bHQiOiJBUT09In0=\",\n \"proof_acked\": \"CvsCCvgCCjJhY2tzL3BvcnRzL3RyYW5zZmVyL2NoYW5uZWxzL2NoYW5uZWwtMC9zZXF1ZW5jZXMvMhIgCPdVftUYJv4Y2EUSvyTsdQAe268hI6R333KgqfNkCnwaDAgBGAEgASoEAAKYASIsCAESBQIEmAEgGiEgwZO9xVL8EiN0UJzWsNHbPhv3vV12NAP7p3ZZaZUdu8YiLAgBEgUECJgBIBohIDfhcrwHl3Pr/NYQDZcQqzFM0884eQP6XXB2kmLYebqVIiwIARIFBhCYASAaISC/he0hK1r8eeFHmNMcJO1YarnZAF0XePYyR4pyvx5CByIsCAESBQgYmAEgGiEgph2B+QOE84r94GvDOVdU77WPTfCf34fD3qo6HDDeWlwiLAgBEgUKJJgBIBohICPCmiEe0ud034qS+9r7k31CDNYheUPZYFhaGQvThBCRIiwIARIFDFCYASAaISAw9iJSNKtWTVBCegWeRXp0XvKs3fmuKwp5/2H1DZxvYwr8AQr5AQoDaWJjEiBjnJOfFg/UuBggr8Rhwe+d/HJ4rykLJemyujjBF8AOnRoJCAEYASABKgEAIiUIARIhAUayD1sqD5N3koJLoD5Cenp4hU5w5Hs10qmKy+U4lvkTIiUIARIhASMhSZiRwnR5BCJtWv3vn97qxK9EMXGDJQWvlAiGWv1/IicIARIBARogstKzZ999h/G+YjkbG1Cfb9kbKD9rixW5TVG9wK2ZdO0iJQgBEiEBIFAyp6DrV9jVQF4040p1EyEJBAOQSUULNQr3BC+lpn4iJwgBEgEBGiCi2TleSd+J2q6hAeVJ4ws0derm1q37h1tQXLesmIbwYA==\",\n \"proof_height\": {\n \"revision_number\": \"1001\",\n \"revision_height\": \"77\"\n },\n \"signer\": \"cosmos1kczfgaqd74px9cgkfl4v2tnknnsh3lkuhulvtz\"\n },\n {\n \"@type\": \"/ibc.core.channel.v1.MsgAcknowledgement\",\n \"packet\": {\n \"sequence\": \"3\",\n \"source_port\": \"transfer\",\n \"source_channel\": \"channel-0\",\n \"destination_port\": \"transfer\",\n \"destination_channel\": \"channel-0\",\n \"data\": \"eyJhbW91bnQiOiIxMDAwMDAwIiwiZGVub20iOiJ1YXRvbSIsInJlY2VpdmVyIjoib3NtbzE5dDAwdnByZjg4bWRsdzRzeGpoZjJtcjh1aGQ5enBkanlucWo5ciIsInNlbmRlciI6ImNvc21vczE5dDAwdnByZjg4bWRsdzRzeGpoZjJtcjh1aGQ5enBkanZnbnpuMyJ9\",\n \"timeout_height\": {\n \"revision_number\": \"1001\",\n \"revision_height\": \"1038\"\n },\n \"timeout_timestamp\": \"1654204357766832506\"\n },\n \"acknowledgement\": \"eyJyZXN1bHQiOiJBUT09In0=\",\n \"proof_acked\": \"CvkCCvYCCjJhY2tzL3BvcnRzL3RyYW5zZmVyL2NoYW5uZWxzL2NoYW5uZWwtMC9zZXF1ZW5jZXMvMxIgCPdVftUYJv4Y2EUSvyTsdQAe268hI6R333KgqfNkCnwaDAgBGAEgASoEAAKYASIqCAESJgIEmAEgrSt6oXhfs/Z0NgDezj7PLQWC0myZOXu0Z3phNJUxJjMgIiwIARIFBAiYASAaISA34XK8B5dz6/zWEA2XEKsxTNPPOHkD+l1wdpJi2Hm6lSIsCAESBQYQmAEgGiEgv4XtISta/HnhR5jTHCTtWGq52QBdF3j2MkeKcr8eQgciLAgBEgUIGJgBIBohIKYdgfkDhPOK/eBrwzlXVO+1j03wn9+Hw96qOhww3lpcIiwIARIFCiSYASAaISAjwpohHtLndN+Kkvva+5N9QgzWIXlD2WBYWhkL04QQkSIsCAESBQxQmAEgGiEgMPYiUjSrVk1QQnoFnkV6dF7yrN35risKef9h9Q2cb2MK/AEK+QEKA2liYxIgY5yTnxYP1LgYIK/EYcHvnfxyeK8pCyXpsro4wRfADp0aCQgBGAEgASoBACIlCAESIQFGsg9bKg+Td5KCS6A+Qnp6eIVOcOR7NdKpisvlOJb5EyIlCAESIQEjIUmYkcJ0eQQibVr975/e6sSvRDFxgyUFr5QIhlr9fyInCAESAQEaILLSs2fffYfxvmI5GxtQn2/ZGyg/a4sVuU1RvcCtmXTtIiUIARIhASBQMqeg61fY1UBeNONKdRMhCQQDkElFCzUK9wQvpaZ+IicIARIBARogotk5XknfidquoQHlSeMLNHXq5tat+4dbUFy3rJiG8GA=\",\n \"proof_height\": {\n \"revision_number\": \"1001\",\n \"revision_height\": \"77\"\n },\n \"signer\": \"cosmos1kczfgaqd74px9cgkfl4v2tnknnsh3lkuhulvtz\"\n }\n ],\n \"memo\": \"\",\n \"timeout_height\": \"0\",\n \"extension_options\": [],\n \"non_critical_extension_options\": []\n },\n \"auth_info\": {\n \"signer_infos\": [\n {\n \"public_key\": {\n \"@type\": \"/cosmos.crypto.secp256k1.PubKey\",\n \"key\": \"AvGJBwmMw1+SzbAvvLfraQaudHOoPkNOWsW6JM/J9QSt\"\n },\n \"mode_info\": {\n \"single\": {\n \"mode\": \"SIGN_MODE_DIRECT\"\n }\n },\n \"sequence\": \"6\"\n }\n ],\n \"fee\": {\n \"amount\": [\n {\n \"denom\": \"uatom\",\n \"amount\": \"1940\"\n }\n ],\n \"gas_limit\": \"193906\",\n \"payer\": \"\",\n \"granter\": \"\"\n }\n },\n \"signatures\": [\n \"k+Xb5NyBveTZ+2gQHDpWO8mqjOGPSN/RQdzFfH02QAso2J5yxbteWikM+2WlzK/M2J27BbtQDC+t+RUnMbIpEw==\"\n ]\n}"}] diff --git a/blockdb/tui/help.go b/blockdb/tui/help.go deleted file mode 100644 index 25e073036..000000000 --- a/blockdb/tui/help.go +++ /dev/null @@ -1,91 +0,0 @@ -package tui - -import ( - "fmt" - - "github.com/gdamore/tcell/v2" - "github.com/rivo/tview" -) - -type keyBinding struct { - Key string // Single key or combination of keys. - Help string // Very short help text describing the key's action. -} - -var baseHelpKeys = []keyBinding{ - {"esc", "go back"}, - {"ctl+c", "exit"}, -} - -func bindingsWithBase(bindings ...[]keyBinding) []keyBinding { - var all []keyBinding - for i := range bindings { - all = append(all, bindings[i]...) - } - return append(all, baseHelpKeys...) -} - -var ( - tableNavKeys = []keyBinding{ - {fmt.Sprintf("%c/k", tcell.RuneUArrow), "move up"}, - {fmt.Sprintf("%c/j", tcell.RuneDArrow), "move down"}, - } - textNavKeys = []keyBinding{ - {fmt.Sprintf("%c/k", tcell.RuneUArrow), "scroll up"}, - {fmt.Sprintf("%c/j", tcell.RuneDArrow), "scroll down"}, - {"g", "go to top"}, - {"shift+g", "go to bottom"}, - {"ctrl+b", "page up"}, - {"ctrl+f", "page down"}, - } - - keyMap = map[mainContent][]keyBinding{ - testCasesMain: bindingsWithBase([]keyBinding{{"m", "cosmos messages"}, {"enter", "view txs"}}, tableNavKeys), - cosmosMessagesMain: bindingsWithBase(tableNavKeys), - txDetailMain: bindingsWithBase([]keyBinding{ - {"[", "previous tx"}, - {"]", "next tx"}, - {"/", "toggle search"}, - {"c", "copy all txs"}, - }, textNavKeys), - errorModalMain: bindingsWithBase(nil), - } -) - -type helpView struct { - *tview.Table -} - -func newHelpView() *helpView { - tbl := tview.NewTable().SetBorders(false) - tbl.SetBorder(false) - return &helpView{tbl} -} - -// Replace serves as a hook to clear all keys and update the help table view with new keys. -func (view *helpView) Replace(keys []keyBinding) *helpView { - view.Table.Clear() - keyCell := func(s string) *tview.TableCell { - return tview.NewTableCell("<" + s + ">"). - SetTextColor(tcell.ColorBlue) - } - textCell := func(s string) *tview.TableCell { - return tview.NewTableCell(s). - SetStyle(textStyle.Attributes(tcell.AttrDim)) - } - var ( - row int - col int - ) - for _, binding := range keys { - // Only allow 6 help items per row or else help items will not be visible. - if row > 0 && row%6 == 0 { - row = 0 - col += 2 - } - view.Table.SetCell(row, col, keyCell(binding.Key)) - view.Table.SetCell(row, col+1, textCell(binding.Help)) - row++ - } - return view -} diff --git a/blockdb/tui/maincontent_string.go b/blockdb/tui/maincontent_string.go deleted file mode 100644 index 91d0a24fa..000000000 --- a/blockdb/tui/maincontent_string.go +++ /dev/null @@ -1,26 +0,0 @@ -// Code generated by "stringer -type=mainContent"; DO NOT EDIT. - -package tui - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[testCasesMain-0] - _ = x[cosmosMessagesMain-1] - _ = x[txDetailMain-2] - _ = x[errorModalMain-3] -} - -const _mainContent_name = "testCasesMaincosmosMessagesMaintxDetailMainerrorModalMain" - -var _mainContent_index = [...]uint8{0, 13, 31, 43, 57} - -func (i mainContent) String() string { - if i < 0 || i >= mainContent(len(_mainContent_index)-1) { - return "mainContent(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _mainContent_name[_mainContent_index[i]:_mainContent_index[i+1]] -} diff --git a/blockdb/tui/model.go b/blockdb/tui/model.go deleted file mode 100644 index cdee852ef..000000000 --- a/blockdb/tui/model.go +++ /dev/null @@ -1,90 +0,0 @@ -package tui - -import ( - "context" - "time" - - "github.com/atotto/clipboard" - "github.com/rivo/tview" - "github.com/strangelove-ventures/interchaintest/v8/blockdb" -) - -//go:generate go run golang.org/x/tools/cmd/stringer -type=mainContent - -// mainContent is the primary content for user interaction in the UI akin to html
. -type mainContent int - -const ( - testCasesMain mainContent = iota - cosmosMessagesMain - txDetailMain - errorModalMain -) - -type mainStack []mainContent - -func (stack mainStack) Push(s mainContent) []mainContent { return append(stack, s) } -func (stack mainStack) Current() mainContent { return stack[len(stack)-1] } -func (stack mainStack) Pop() []mainContent { return stack[:len(stack)-1] } - -// QueryService fetches data from a database. -type QueryService interface { - CosmosMessages(ctx context.Context, chainPkey int64) ([]blockdb.CosmosMessageResult, error) - Transactions(ctx context.Context, chainPkey int64) ([]blockdb.TxResult, error) -} - -// Model encapsulates state that updates a view. -type Model struct { - querySvc QueryService - databasePath string - schemaVersion string - schemaDate time.Time - testCases []blockdb.TestCaseResult - - layout *tview.Flex - - // stack keeps tracks of primary content pushed and popped - stack mainStack - - // write to the system clipboard - clipboard func(text string) error -} - -// NewModel returns a valid *Model. -func NewModel( - querySvc QueryService, - databasePath string, - schemaVersion string, - schemaDate time.Time, - testCases []blockdb.TestCaseResult, -) *Model { - m := &Model{ - querySvc: querySvc, - databasePath: databasePath, - schemaVersion: schemaVersion, - schemaDate: schemaDate, - testCases: testCases, - stack: mainStack{testCasesMain}, - clipboard: clipboard.WriteAll, - } - - flex := tview.NewFlex().SetDirection(tview.FlexRow) - flex.SetBackgroundColor(backgroundColor).SetBorder(false) - // Setting fixed size keeps the header height stable, so it will show all help keys. - flex.AddItem(headerView(m), 6, 1, false) - - // The primary view is a page view to act like a stack where we can push and pop views. - // Flex and grid views do not allow a "stack-like" behavior. - pages := tview.NewPages() - pages.AddAndSwitchToPage(m.stack[0].String(), testCasesView(m), true) - flex.AddItem(pages, 0, 10, true) - - m.layout = flex - - return m -} - -// RootView is a root view for a tview.Application. -func (m *Model) RootView() *tview.Flex { - return m.layout -} diff --git a/blockdb/tui/model_test.go b/blockdb/tui/model_test.go deleted file mode 100644 index ec3e832ca..000000000 --- a/blockdb/tui/model_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package tui - -import ( - "testing" - "time" - - "github.com/strangelove-ventures/interchaintest/v8/blockdb" - "github.com/stretchr/testify/require" -) - -func TestModel_RootView(t *testing.T) { - m := NewModel(&mockQueryService{}, "test.db", "abc123", time.Now(), make([]blockdb.TestCaseResult, 1)) - view := m.RootView() - require.NotNil(t, view) - require.Greater(t, view.GetItemCount(), 0) -} diff --git a/blockdb/tui/presenter/cosmos_message.go b/blockdb/tui/presenter/cosmos_message.go deleted file mode 100644 index 1f29a9a21..000000000 --- a/blockdb/tui/presenter/cosmos_message.go +++ /dev/null @@ -1,54 +0,0 @@ -package presenter - -import ( - "strconv" - "strings" - - "github.com/strangelove-ventures/interchaintest/v8/blockdb" -) - -// CosmosMessage presents a blockdb.CosmosMessageResult. -type CosmosMessage struct { - Result blockdb.CosmosMessageResult -} - -func (msg CosmosMessage) Height() string { return strconv.FormatInt(msg.Result.Height, 10) } - -// Index is the message's ordered position within the tx. -func (msg CosmosMessage) Index() string { return strconv.Itoa(msg.Result.Index) } - -// Type is a URI for the proto definition, e.g. /ibc.core.client.v1.MsgCreateClient -func (msg CosmosMessage) Type() string { return msg.Result.Type } - -func (msg CosmosMessage) ClientChain() string { return msg.Result.ClientChainID.String } - -func (msg CosmosMessage) Clients() string { - return msg.srcCounterpartyPair(msg.Result.ClientID.String, msg.Result.CounterpartyClientID.String) -} - -func (msg CosmosMessage) Connections() string { - return msg.srcCounterpartyPair(msg.Result.ConnID.String, msg.Result.CounterpartyConnID.String) -} - -func (msg CosmosMessage) Channels() string { - join := func(channel, port string) string { - if channel+port == "" { - return "" - } - return channel + ":" + port - } - return msg.srcCounterpartyPair( - join(msg.Result.ChannelID.String, msg.Result.PortID.String), - join(msg.Result.CounterpartyChannelID.String, msg.Result.CounterpartyPortID.String), - ) -} - -func (msg CosmosMessage) srcCounterpartyPair(source, counterparty string) string { - if source != "" { - source += " (source)" - } - if counterparty != "" { - counterparty += " (counterparty)" - } - return strings.TrimSpace(source + " " + counterparty) -} diff --git a/blockdb/tui/presenter/cosmos_message_test.go b/blockdb/tui/presenter/cosmos_message_test.go deleted file mode 100644 index ea30330ad..000000000 --- a/blockdb/tui/presenter/cosmos_message_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package presenter - -import ( - "database/sql" - "testing" - - "github.com/strangelove-ventures/interchaintest/v8/blockdb" - "github.com/stretchr/testify/require" -) - -func TestCosmosMessage(t *testing.T) { - t.Parallel() - - t.Run("non-variable fields", func(t *testing.T) { - res := blockdb.CosmosMessageResult{ - Height: 55, - Index: 13, - Type: "/ibc.MsgFoo", - ClientChainID: sql.NullString{String: "chain1"}, - } - pres := CosmosMessage{res} - - require.Equal(t, "55", pres.Height()) - require.Equal(t, "13", pres.Index()) - require.Equal(t, "/ibc.MsgFoo", pres.Type()) - require.Equal(t, "chain1", pres.ClientChain()) - require.Empty(t, pres.Clients()) - require.Empty(t, pres.Connections()) - require.Empty(t, pres.Channels()) - }) - - t.Run("ibc details", func(t *testing.T) { - for _, tt := range []struct { - Result blockdb.CosmosMessageResult - WantClients string - WantConnections string - WantChannels string - }{ - { - blockdb.CosmosMessageResult{ - ClientID: sql.NullString{String: "tendermint-1", Valid: true}, - CounterpartyClientID: sql.NullString{String: "tendermint-2", Valid: true}, - ConnID: sql.NullString{String: "conn-1", Valid: true}, - CounterpartyConnID: sql.NullString{String: "conn-2", Valid: true}, - PortID: sql.NullString{String: "port-1", Valid: true}, - ChannelID: sql.NullString{String: "chan-1", Valid: true}, - CounterpartyPortID: sql.NullString{String: "port-2", Valid: true}, - CounterpartyChannelID: sql.NullString{String: "chan-2", Valid: true}, - }, - "tendermint-1 (source) tendermint-2 (counterparty)", - "conn-1 (source) conn-2 (counterparty)", - "chan-1:port-1 (source) chan-2:port-2 (counterparty)", - }, - { - blockdb.CosmosMessageResult{ - ClientID: sql.NullString{String: "tendermint-1", Valid: true}, - ConnID: sql.NullString{String: "conn-1", Valid: true}, - PortID: sql.NullString{String: "port-1", Valid: true}, - ChannelID: sql.NullString{String: "chan-1", Valid: true}, - }, - "tendermint-1 (source)", - "conn-1 (source)", - "chan-1:port-1 (source)", - }, - { - blockdb.CosmosMessageResult{ - CounterpartyClientID: sql.NullString{String: "tendermint-2", Valid: true}, - CounterpartyConnID: sql.NullString{String: "conn-2", Valid: true}, - CounterpartyPortID: sql.NullString{String: "port-2", Valid: true}, - CounterpartyChannelID: sql.NullString{String: "chan-2", Valid: true}, - }, - "tendermint-2 (counterparty)", - "conn-2 (counterparty)", - "chan-2:port-2 (counterparty)", - }, - } { - pres := CosmosMessage{tt.Result} - require.Equal(t, tt.WantClients, pres.Clients(), tt) - require.Equal(t, tt.WantConnections, pres.Connections(), tt) - require.Equal(t, tt.WantChannels, pres.Channels(), tt) - } - }) -} diff --git a/blockdb/tui/presenter/highlight.go b/blockdb/tui/presenter/highlight.go deleted file mode 100644 index e2c421181..000000000 --- a/blockdb/tui/presenter/highlight.go +++ /dev/null @@ -1,43 +0,0 @@ -package presenter - -import ( - "fmt" - "regexp" - "strconv" - "strings" -) - -type Highlight struct { - rx *regexp.Regexp -} - -// NewHighlight returns a presenter that adds regions around text that matches searchTerm. -func NewHighlight(searchTerm string) *Highlight { - searchTerm = strings.TrimSpace(searchTerm) - if searchTerm == "" { - return &Highlight{} - } - // Should always be valid with regexp.QuoteMeta. - return &Highlight{rx: regexp.MustCompile(fmt.Sprintf(`(?i)(%s)`, regexp.QuoteMeta(searchTerm)))} -} - -// Text returns the text decorated with tview.TextView regions given the "searchTerm" from NewHighlight. -// The second return value is the highlighted region ids for use with *(tview.TextView).Highlight. -// See https://github.com/rivo/tview/wiki/TextView for more info about regions. -func (h *Highlight) Text(text string) (string, []string) { - if h.rx == nil { - return text, nil - } - var ( - region int - regionIDs []string - ) - final := h.rx.ReplaceAllStringFunc(text, func(s string) string { - id := strconv.Itoa(region) - regionIDs = append(regionIDs, id) - s = fmt.Sprintf(`["%s"]%s[""]`, id, s) - region++ - return s - }) - return final, regionIDs -} diff --git a/blockdb/tui/presenter/highlight_test.go b/blockdb/tui/presenter/highlight_test.go deleted file mode 100644 index bbf0bfb76..000000000 --- a/blockdb/tui/presenter/highlight_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package presenter - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestHighlighter_Text(t *testing.T) { - t.Run("happy path", func(t *testing.T) { - h := NewHighlight("\tDay ") - - got, regions := h.Text(highlighterFixture) - - const want = `Tomorrow, and tomorrow, and tomorrow, -Creeps in this petty pace from ["0"]day[""] to ["1"]day[""], -To the last syllable of recorded time; -And all our yester["2"]day[""]s have lighted fools -The way to dusty death. Out, out, brief candle!` - require.Equal(t, want, got) - require.Equal(t, []string{"0", "1", "2"}, regions) - }) - - t.Run("ignores regex meta characters", func(t *testing.T) { - h := NewHighlight("(one paren") - got, regions := h.Text("(one paren") - - require.Equal(t, `["0"](one paren[""]`, got) - require.Equal(t, []string{"0"}, regions) - }) - - t.Run("missing search term", func(t *testing.T) { - h := NewHighlight("") - got, regions := h.Text(highlighterFixture) - - require.Empty(t, regions) - require.Equal(t, highlighterFixture, got) - - h = NewHighlight(" \t") - got, regions = h.Text(highlighterFixture) - - require.Empty(t, regions) - require.Equal(t, highlighterFixture, got) - }) -} - -const highlighterFixture = `Tomorrow, and tomorrow, and tomorrow, -Creeps in this petty pace from day to day, -To the last syllable of recorded time; -And all our yesterdays have lighted fools -The way to dusty death. Out, out, brief candle!` diff --git a/blockdb/tui/presenter/test_case.go b/blockdb/tui/presenter/test_case.go deleted file mode 100644 index c5e5f1118..000000000 --- a/blockdb/tui/presenter/test_case.go +++ /dev/null @@ -1,32 +0,0 @@ -package presenter - -import ( - "strconv" - - "github.com/strangelove-ventures/interchaintest/v8/blockdb" -) - -// TestCase presents a blockdb.TestCaseResult. -type TestCase struct { - Result blockdb.TestCaseResult -} - -func (p TestCase) ID() string { return strconv.FormatInt(p.Result.ID, 10) } -func (p TestCase) Date() string { return FormatTime(p.Result.CreatedAt) } -func (p TestCase) Name() string { return p.Result.Name } -func (p TestCase) GitSha() string { return p.Result.GitSha } -func (p TestCase) ChainID() string { return p.Result.ChainID } - -func (p TestCase) Height() string { - if !p.Result.ChainHeight.Valid { - return "" - } - return strconv.FormatInt(p.Result.ChainHeight.Int64, 10) -} - -func (p TestCase) TxTotal() string { - if !p.Result.TxTotal.Valid { - return "" - } - return strconv.FormatInt(p.Result.TxTotal.Int64, 10) -} diff --git a/blockdb/tui/presenter/test_case_test.go b/blockdb/tui/presenter/test_case_test.go deleted file mode 100644 index 525a0b4b2..000000000 --- a/blockdb/tui/presenter/test_case_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package presenter - -import ( - "database/sql" - "testing" - "time" - - "github.com/strangelove-ventures/interchaintest/v8/blockdb" - "github.com/stretchr/testify/require" -) - -func TestTestCase(t *testing.T) { - t.Parallel() - - t.Run("happy path", func(t *testing.T) { - result := blockdb.TestCaseResult{ - ID: 321, - Name: "My Test", - GitSha: "sha1", - CreatedAt: time.Now(), - ChainPKey: 789, - ChainID: "chain1", - ChainType: "cosmos", - ChainHeight: sql.NullInt64{Int64: 77, Valid: true}, - TxTotal: sql.NullInt64{Int64: 88, Valid: true}, - } - - pres := TestCase{result} - require.Equal(t, "321", pres.ID()) - require.Equal(t, "My Test", pres.Name()) - require.Equal(t, "sha1", pres.GitSha()) - require.NotEmpty(t, pres.Date()) - require.Equal(t, "chain1", pres.ChainID()) - require.Equal(t, "77", pres.Height()) - require.Equal(t, "88", pres.TxTotal()) - }) - - t.Run("zero state", func(t *testing.T) { - var pres TestCase - - require.Empty(t, pres.Height()) - require.Empty(t, pres.TxTotal()) - }) -} diff --git a/blockdb/tui/presenter/time.go b/blockdb/tui/presenter/time.go deleted file mode 100644 index 96d9c6371..000000000 --- a/blockdb/tui/presenter/time.go +++ /dev/null @@ -1,8 +0,0 @@ -package presenter - -import "time" - -// FormatTime returns a shortened local time. -func FormatTime(t time.Time) string { - return t.Format("01-02 03:04PM MST") -} diff --git a/blockdb/tui/presenter/tx.go b/blockdb/tui/presenter/tx.go deleted file mode 100644 index d823ba893..000000000 --- a/blockdb/tui/presenter/tx.go +++ /dev/null @@ -1,65 +0,0 @@ -package presenter - -import ( - "bytes" - "encoding/json" - "strconv" - "sync" - - "github.com/strangelove-ventures/interchaintest/v8/blockdb" -) - -var bufPool = sync.Pool{New: func() any { return new(bytes.Buffer) }} - -type Tx struct { - Result blockdb.TxResult -} - -func (tx Tx) Height() string { return strconv.FormatInt(tx.Result.Height, 10) } - -// Data attempts to pretty print JSON. If not valid JSON, returns tx data as-is which may not be human-readable. -func (tx Tx) Data() string { - buf := bufPool.Get().(*bytes.Buffer) - defer bufPool.Put(buf) - defer buf.Reset() - - if err := json.Indent(buf, tx.Result.Tx, "", " "); err != nil { - return string(tx.Result.Tx) - } - return buf.String() -} - -type Txs []blockdb.TxResult - -// ToJSON always renders valid JSON given the blockdb.TxResult. -// If the tx data is not valid JSON, the tx data is represented as a base64 encoded string. -func (txs Txs) ToJSON() []byte { - type jsonObj struct { - Height int64 - Tx json.RawMessage - } - type jsonBytes struct { - Height int64 - Tx []byte - } - objs := make([]any, len(txs)) - for i, tx := range txs { - if !json.Valid(tx.Tx) { - objs[i] = jsonBytes{ - Height: tx.Height, - Tx: tx.Tx, - } - continue - } - objs[i] = jsonObj{ - Height: tx.Height, - Tx: tx.Tx, - } - } - b, err := json.Marshal(objs) - if err != nil { - // json.Valid check above should prevent an error here. - panic(err) - } - return b -} diff --git a/blockdb/tui/presenter/tx_test.go b/blockdb/tui/presenter/tx_test.go deleted file mode 100644 index 50a91b5c2..000000000 --- a/blockdb/tui/presenter/tx_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package presenter - -import ( - "encoding/json" - "testing" - - "github.com/strangelove-ventures/interchaintest/v8/blockdb" - "github.com/stretchr/testify/require" -) - -func TestTx(t *testing.T) { - t.Parallel() - - t.Run("json", func(t *testing.T) { - tx := blockdb.TxResult{ - Height: 13, - Tx: []byte(`{"json":{"foo":true}}`), - } - require.True(t, json.Valid(tx.Tx)) // sanity check - - pres := Tx{tx} - require.Equal(t, "13", pres.Height()) - - const want = `{ - "json": { - "foo": true - } -}` - require.Equal(t, want, pres.Data()) - }) - - t.Run("non-json", func(t *testing.T) { - tx := blockdb.TxResult{ - Tx: []byte(`some data`), - } - pres := Tx{tx} - require.Equal(t, "some data", pres.Data()) - }) -} - -func TestTxs_ToJSON(t *testing.T) { - t.Run("happy path", func(t *testing.T) { - txs := Txs{ - {Height: 1, Tx: []byte(`{"num":1}`)}, - {Height: 3, Tx: []byte(`{"num":3}`)}, - {Height: 5, Tx: []byte(`{"num":5}`)}, - } - - const want = `[ -{ "Height": 1, "Tx": { "num": 1 } }, -{ "Height": 3, "Tx": { "num": 3 } }, -{ "Height": 5, "Tx": { "num": 5 } } -]` - require.JSONEq(t, want, string(txs.ToJSON())) - }) - - t.Run("invalid json", func(t *testing.T) { - txs := Txs{ - {Height: 1, Tx: []byte(`{"num":1}`)}, - {Height: 2, Tx: []byte(`not valid`)}, - } - - const want = `[ -{ "Height": 1, "Tx": { "num": 1 } }, -{ "Height": 2, "Tx": "bm90IHZhbGlk" } -]` - require.JSONEq(t, want, string(txs.ToJSON())) - }) -} diff --git a/blockdb/tui/style.go b/blockdb/tui/style.go deleted file mode 100644 index 9a8a4eee1..000000000 --- a/blockdb/tui/style.go +++ /dev/null @@ -1,13 +0,0 @@ -package tui - -import "github.com/gdamore/tcell/v2" - -const ( - backgroundColor = tcell.ColorBlack - textColor = tcell.ColorWhite - errorTextColor = tcell.ColorRed -) - -var ( - textStyle = tcell.Style{}.Foreground(textColor) -) diff --git a/blockdb/tui/update.go b/blockdb/tui/update.go deleted file mode 100644 index 2287afaed..000000000 --- a/blockdb/tui/update.go +++ /dev/null @@ -1,138 +0,0 @@ -package tui - -import ( - "context" - "fmt" - "strconv" - - "github.com/gdamore/tcell/v2" - "github.com/rivo/tview" - "github.com/strangelove-ventures/interchaintest/v8/blockdb/tui/presenter" -) - -// Update should be the argument for *(tview.Application).SetInputCapture. -// The Model potentially updates view state based on the event. -// Update must be called from the main goroutine. Otherwise, view updates will not render or cause data races. -// Per tview documentation, return nil to stop event propagation. -func (m *Model) Update(ctx context.Context) func(event *tcell.EventKey) *tcell.EventKey { - return func(event *tcell.EventKey) *tcell.EventKey { - oldMain := m.stack.Current() - defer m.updateHelp(oldMain) - - switch { - case event.Key() == tcell.KeyESC: - if len(m.stack) > 1 { // Stack must be at least 1, so we don't remove all main content views. - m.mainContentView().RemovePage(m.stack.Current().String()) - m.stack = m.stack.Pop() - return nil - } - - case event.Key() == tcell.KeyEnter && m.stack.Current() == testCasesMain: - // Show tx detail. - tc := m.testCases[m.selectedRow()] - results, err := m.querySvc.Transactions(ctx, tc.ChainPKey) - if err != nil { - m.pushErrorModal(fmt.Errorf("query transactions: %w", err)) - return nil - } - m.pushMainView(txDetailMain, newTxDetailView(tc.ChainID, results)) - return nil - - case event.Rune() == 'm' && m.stack.Current() == testCasesMain: - // Show cosmos messages. - tc := m.testCases[m.selectedRow()] - results, err := m.querySvc.CosmosMessages(ctx, tc.ChainPKey) - if err != nil { - m.pushErrorModal(fmt.Errorf("query cosmos messages: %w", err)) - return nil - } - m.pushMainView(cosmosMessagesMain, cosmosMessagesView(tc, results)) - return nil - - case event.Rune() == '[' && m.stack.Current() == txDetailMain: - goToPrevPage(m.txDetailView().Pages) - return nil - - case event.Rune() == ']' && m.stack.Current() == txDetailMain: - gotToNextPage(m.txDetailView().Pages) - return nil - - case event.Rune() == '/' && m.stack.Current() == txDetailMain: - m.txDetailView().ToggleSearch() - return nil - - case event.Rune() == 'c' && m.stack.Current() == txDetailMain: - if err := m.clipboard(string(presenter.Txs(m.txDetailView().Txs).ToJSON())); err != nil { - m.pushErrorModal(fmt.Errorf("copy to clipboard: %w", err)) - } - return nil - - case event.Key() == tcell.KeyEnter && m.stack.Current() == txDetailMain: - // Search tx detail. - m.txDetailView().DoSearch() - return nil - } - - return event - } -} - -func (m *Model) updateHelp(oldMainContent mainContent) { - // Prevent redrawing if nothing has changed. - if oldMainContent == m.stack.Current() { - return - } - help := m.layout.GetItem(0).(*tview.Flex).GetItem(0).(*helpView) - help.Replace(keyMap[m.stack.Current()]) -} - -func (m *Model) mainContentView() *tview.Pages { - return m.layout.GetItem(1).(*tview.Pages) -} - -func (m *Model) pushMainView(main mainContent, view tview.Primitive) { - m.stack = m.stack.Push(main) - m.mainContentView().AddAndSwitchToPage(main.String(), view, true) -} - -func (m *Model) pushErrorModal(err error) { - m.pushMainView(errorModalMain, errorModalView(err)) -} - -func (m *Model) selectedRow() int { - _, view := m.mainContentView().GetFrontPage() - row, _ := view.(*tview.Table).GetSelection() - // Offset by 1 to account for header row. - return row - 1 -} - -func (m *Model) txDetailView() *txDetailView { - _, primitive := m.mainContentView().GetFrontPage() - return primitive.(*txDetailView) -} - -// gotToNextPage assumes a convention where the page name is equal to its index. e.g. "0", "1", "2", etc. -func gotToNextPage(pages *tview.Pages) { - idxStr, _ := pages.GetFrontPage() - idx, err := strconv.Atoi(idxStr) - if err != nil { - panic(err) - } - if idx == pages.GetPageCount()-1 { - return - } - pages.SwitchToPage(strconv.Itoa(idx + 1)) -} - -// goToPrevPage assumes a convention where the page name is equal to its index. e.g. "0", "1", "2", etc. -func goToPrevPage(pages *tview.Pages) { - idxStr, _ := pages.GetFrontPage() - idx, err := strconv.Atoi(idxStr) - if err != nil { - panic(err) - } - if idx == 0 { - return - } - pages.SwitchToPage(strconv.Itoa(idx - 1)) -} diff --git a/blockdb/tui/update_test.go b/blockdb/tui/update_test.go deleted file mode 100644 index 623f563fc..000000000 --- a/blockdb/tui/update_test.go +++ /dev/null @@ -1,217 +0,0 @@ -package tui - -import ( - "context" - "encoding/json" - "errors" - "testing" - "time" - - "github.com/gdamore/tcell/v2" - "github.com/rivo/tview" - "github.com/strangelove-ventures/interchaintest/v8/blockdb" - "github.com/stretchr/testify/require" -) - -var ( - escKey = tcell.NewEventKey(tcell.KeyESC, ' ', 0) - enterKey = tcell.NewEventKey(tcell.KeyEnter, ' ', 0) -) - -func runeKey(c rune) *tcell.EventKey { - return tcell.NewEventKey(tcell.KeyRune, c, 0) -} - -// draw is necessary for some of the below tests to get default behavior such as selecting the first available -// row in a *tview.Table. -func draw(view tview.Primitive) { - view.Draw(tcell.NewSimulationScreen("")) -} - -type mockQueryService struct { - GotChainPkey int64 - Messages []blockdb.CosmosMessageResult - Txs []blockdb.TxResult - Err error -} - -func (m *mockQueryService) Transactions(ctx context.Context, chainPkey int64) ([]blockdb.TxResult, error) { - if ctx == nil { - panic("nil context") - } - m.GotChainPkey = chainPkey - return m.Txs, m.Err -} - -func (m *mockQueryService) CosmosMessages(ctx context.Context, chainPkey int64) ([]blockdb.CosmosMessageResult, error) { - if ctx == nil { - panic("nil context") - } - m.GotChainPkey = chainPkey - return m.Messages, m.Err -} - -func TestModel_Update(t *testing.T) { - ctx := context.Background() - - t.Run("go back", func(t *testing.T) { - model := NewModel(&mockQueryService{}, "", "", time.Now(), nil) - require.Equal(t, 1, model.mainContentView().GetPageCount()) - - draw(model.RootView()) - - update := model.Update(ctx) - update(escKey) - - require.Equal(t, 1, model.mainContentView().GetPageCount()) - }) - - t.Run("cosmos summary view", func(t *testing.T) { - querySvc := &mockQueryService{ - Messages: []blockdb.CosmosMessageResult{ - {Height: 10}, - {Height: 11}, - {Height: 12}, - }, - } - model := NewModel(querySvc, "", "", time.Now(), []blockdb.TestCaseResult{ - {ChainPKey: 5, ChainID: "my-chain1"}, - {ChainPKey: 6}, - }) - - draw(model.RootView()) - - update := model.Update(ctx) - update(runeKey('m')) - - // By default, first row is selected in a rendered table. - require.EqualValues(t, 5, querySvc.GotChainPkey) - - require.Equal(t, 2, model.mainContentView().GetPageCount()) - _, table := model.mainContentView().GetFrontPage() - - // 4 rows: 1 header + 3 blockdb.CosmosMessageResult - require.Equal(t, 4, table.(*tview.Table).GetRowCount()) - require.Contains(t, table.(*tview.Table).GetTitle(), "my-chain1") - }) - - t.Run("tx detail", func(t *testing.T) { - querySvc := &mockQueryService{ - Txs: []blockdb.TxResult{ - {Height: 12, Tx: []byte(`{"tx":1}`)}, - {Height: 13, Tx: []byte(`{"tx":2}`)}, - {Height: 14, Tx: []byte(`{"tx":3}`)}, - }, - } - model := NewModel(querySvc, "", "", time.Now(), []blockdb.TestCaseResult{ - {ChainPKey: 5, ChainID: "my-chain1"}, - {ChainPKey: 6}, - }) - - draw(model.RootView()) - - update := model.Update(ctx) - - update(enterKey) - - // By default, first row is selected in a rendered table. - require.EqualValues(t, 5, querySvc.GotChainPkey) - - require.Equal(t, 2, model.mainContentView().GetPageCount()) - txDetail := model.txDetailView() - - // Search and text view - require.Equal(t, 2, txDetail.Flex.GetItemCount()) - - require.Equal(t, 3, txDetail.Pages.GetPageCount()) - - _, primitive := txDetail.Pages.GetFrontPage() - textView := primitive.(*tview.TextView) - - require.Contains(t, textView.GetTitle(), "Tx 1 of 3") - require.Contains(t, textView.GetTitle(), "my-chain1 @ Height 12") - const wantFirstPage = `{ - "tx": 1 -}` - require.Equal(t, wantFirstPage, textView.GetText(true)) - - // Move to the next page. - update(runeKey(']')) - - _, primitive = txDetail.Pages.GetFrontPage() - textView = primitive.(*tview.TextView) - - require.Contains(t, textView.GetTitle(), "Tx 2 of 3") - require.Contains(t, textView.GetTitle(), "my-chain1 @ Height 13") - const wantSecondPage = `{ - "tx": 2 -}` - require.Equal(t, wantSecondPage, textView.GetText(true)) - - // Assert does not advance past last page. - update(runeKey(']')) - update(runeKey(']')) - update(runeKey(']')) - - _, primitive = txDetail.Pages.GetFrontPage() - textView = primitive.(*tview.TextView) - - require.Contains(t, textView.GetTitle(), "Tx 3 of 3") - - // Move back to the previous page. Assert does not retreat past first page. - update(runeKey('[')) - update(runeKey('[')) - update(runeKey('[')) - update(runeKey('[')) - - _, primitive = txDetail.Pages.GetFrontPage() - textView = primitive.(*tview.TextView) - - require.Contains(t, textView.GetTitle(), "Tx 1 of 3") - }) - - t.Run("tx detail copy", func(t *testing.T) { - querySvc := &mockQueryService{ - Txs: []blockdb.TxResult{ - {Height: 12, Tx: []byte(`{"tx":1}`)}, - {Height: 13, Tx: []byte(`{"tx":2}`)}, - }, - } - model := NewModel(querySvc, "", "", time.Now(), []blockdb.TestCaseResult{ - {ChainPKey: 5, ChainID: "my-chain1"}, - }) - - draw(model.RootView()) - - var gotText string - model.clipboard = func(text string) error { - gotText = text - return nil - } - - update := model.Update(ctx) - - // Show tx detail. - update(enterKey) - // Now copy. - update(runeKey('c')) - - require.NotEmpty(t, gotText) - - var gotTxs []any - require.NoError(t, json.Unmarshal([]byte(gotText), &gotTxs)) - require.Len(t, gotTxs, 2) - - // Simulate clipboard failure. - model.clipboard = func(string) error { - return errors.New("boom") - } - - update(runeKey('c')) - - _, primitive := model.mainContentView().GetFrontPage() - // TODO (nix - 6/22/22) Can't get text from a tview.Modal. We could use a tview.TextView but it does not render - // properly with the nested flex views. - require.IsType(t, &tview.Modal{}, primitive.(*tview.Flex).GetItem(1).(*tview.Flex).GetItem(1)) - }) -} diff --git a/blockdb/tui/views.go b/blockdb/tui/views.go deleted file mode 100644 index 3a50633e2..000000000 --- a/blockdb/tui/views.go +++ /dev/null @@ -1,282 +0,0 @@ -package tui - -import ( - "errors" - "fmt" - "strconv" - "strings" - - "github.com/gdamore/tcell/v2" - "github.com/rivo/tview" - "github.com/strangelove-ventures/interchaintest/v8/blockdb" - "github.com/strangelove-ventures/interchaintest/v8/blockdb/tui/presenter" -) - -func headerView(m *Model) *tview.Flex { - flex := tview.NewFlex().SetDirection(tview.FlexColumn) - flex.SetBorder(false) - flex.SetBorderPadding(0, 0, 1, 1) - - help := newHelpView().Replace(keyMap[testCasesMain]) - flex.AddItem(help, 0, 2, false) - flex.AddItem(schemaVersionView(m), 0, 1, false) - - return flex -} - -func schemaVersionView(m *Model) *tview.Table { - tbl := tview.NewTable().SetBorders(false) - tbl.SetBorder(false) - - titleCell := func(s string) *tview.TableCell { - return tview.NewTableCell(s). - SetStyle(textStyle.Bold(true).Foreground(tcell.ColorDarkOrange)) - } - tbl.SetCell(0, 0, titleCell("Database:")) - tbl.SetCell(1, 0, titleCell("Schema Version:")) - tbl.SetCell(2, 0, titleCell("Schema Date:")) - - valCell := func(s string) *tview.TableCell { - return tview.NewTableCell(s).SetStyle(textStyle) - } - tbl.SetCell(0, 1, valCell(m.databasePath)) - tbl.SetCell(1, 1, valCell(m.schemaVersion)) - tbl.SetCell(2, 1, valCell(presenter.FormatTime(m.schemaDate))) - - return tbl -} - -func detailTableView(title string, headers []string, rows [][]string) *tview.Table { - if len(headers) == 0 { - panic(errors.New("detailTableView headers are required")) - } - tbl := tview.NewTable(). - SetBorders(false). - SetSelectable(true, false). - SetSelectedStyle(tcell.Style{}.Foreground(backgroundColor).Background(textColor)). - SetFixed(1, 0) - tbl. - SetBorder(true). - SetBorderPadding(0, 0, 1, 1). - SetBorderAttributes(tcell.AttrDim) - - tbl.SetTitle(title) - - headerCell := func(s string) *tview.TableCell { - s = strings.ToUpper(s) - return tview.NewTableCell(s). - SetStyle(textStyle.Bold(true)). - SetExpansion(1). - SetSelectable(false) - } - - for col, header := range headers { - tbl.SetCell(0, col, headerCell(header)) - } - - contentCell := func(s string) *tview.TableCell { - return tview.NewTableCell(s).SetStyle(textStyle).SetExpansion(1) - } - - for i, row := range rows { - rowPos := i + 1 // 1 offsets header row - - if len(row) != len(headers) { - panic(fmt.Errorf("row %v column count %d must equal header count %d", row, len(row), len(headers))) - } - - for col, content := range row { - tbl.SetCell(rowPos, col, contentCell(content)) - } - } - return tbl -} - -// testCasesView is the initial main content. -func testCasesView(m *Model) *tview.Table { - headers := []string{ - "ID", - "Date", - "Name", - "Git Sha", - "Chain", - "Height", - "Tx Total", - } - - rows := make([][]string, len(m.testCases)) - for i, tc := range m.testCases { - pres := presenter.TestCase{Result: tc} - rows[i] = []string{ - pres.ID(), - pres.Date(), - pres.Name(), - pres.GitSha(), - pres.ChainID(), - pres.Height(), - pres.TxTotal(), - } - } - - return detailTableView("Test Cases", headers, rows) -} - -func cosmosMessagesView(tc blockdb.TestCaseResult, msgs []blockdb.CosmosMessageResult) *tview.Table { - headers := []string{ - "Height", - "Index", - "Type", - "Client Chain", - "Client", - "Connection", - "Channel:Port", - } - - rows := make([][]string, len(msgs)) - for i, msg := range msgs { - pres := presenter.CosmosMessage{Result: msg} - rows[i] = []string{ - pres.Height(), - pres.Index(), - pres.Type(), - pres.ClientChain(), - pres.Clients(), - pres.Connections(), - pres.Channels(), - } - } - - title := fmt.Sprintf("%s [%s]", tc.ChainID, presenter.FormatTime(tc.CreatedAt)) - return detailTableView(title, headers, rows) -} - -func errorModalView(err error) *tview.Flex { - modal := tview.NewModal(). - SetText(fmt.Sprintf("Error: %v", err)). - SetTextColor(errorTextColor). - SetBackgroundColor(backgroundColor) - - // Flex centers the modal. See: https://github.com/rivo/tview/wiki/Modal - return tview.NewFlex(). - AddItem(nil, 0, 1, false). - AddItem(tview.NewFlex().SetDirection(tview.FlexRow). - AddItem(nil, 0, 1, false). - AddItem(modal, 0, 1, true). - AddItem(nil, 0, 1, false), 0, 1, true). - AddItem(nil, 0, 1, false) -} - -const ( - searchActiveColor = tcell.ColorPaleGreen - searchInactiveColor = tcell.ColorBlue -) - -// txDetailView is a very stateful view that could be refactored into a model. -// It allows a variety of interactions when viewing individual transactions. -type txDetailView struct { - *tview.Flex - - chainID string - - Txs []blockdb.TxResult - Pages *tview.Pages - Search *tview.InputField -} - -func newTxDetailView(chainID string, txs []blockdb.TxResult) *txDetailView { - detail := &txDetailView{ - chainID: chainID, - Txs: txs, - } - - detail.Pages = tview.NewPages() - detail.replacePages("", "0") - detail.Search = detail.buildSearchInput() - - flex := tview.NewFlex().SetDirection(tview.FlexRow) - flex.SetBorder(false) - flex.AddItem(detail.Search, 3, 1, false) - flex.AddItem(detail.Pages, 0, 9, true) - - detail.Flex = flex - return detail -} - -func (detail *txDetailView) ToggleSearch() { - if detail.Search.HasFocus() { - detail.deactivateSearch() - return - } - detail.activateSearch() -} - -func (detail *txDetailView) deactivateSearch() { - detail.Search.SetBorderColor(searchInactiveColor) - detail.Search.SetFieldTextColor(searchInactiveColor) - detail.Search.SetTitleColor(searchInactiveColor) - detail.Search.Blur() - detail.Pages.Focus(nil) -} - -func (detail *txDetailView) activateSearch() { - detail.Search.SetBorderColor(searchActiveColor) - detail.Search.SetFieldTextColor(searchActiveColor) - detail.Search.SetTitleColor(searchActiveColor) - detail.Search.Focus(nil) - detail.Pages.Blur() -} - -// DoSearch re-renders the text views with highlighted text. -func (detail *txDetailView) DoSearch() { - detail.deactivateSearch() - term := detail.Search.GetText() - idx, _ := detail.Pages.GetFrontPage() - detail.replacePages(term, idx) -} - -// "pageIdx" is an integer string, e.g. "0", "1". -func (detail *txDetailView) replacePages(searchTerm, pageIdx string) { - highlight := presenter.NewHighlight(searchTerm) - for i, tx := range detail.Txs { - idx := strconv.Itoa(i) - detail.Pages.RemovePage(idx) - - pres := presenter.Tx{Result: tx} - text, regions := highlight.Text(pres.Data()) - textView := tview.NewTextView(). - SetText(text). - SetTextColor(textColor). - SetWrap(true). - SetWordWrap(true). - SetTextAlign(tview.AlignLeft). - SetScrollable(true) - - // Support highlighting text. - textView.SetDynamicColors(true).SetRegions(true) - textView.Highlight(regions...).ScrollToHighlight() - - textView.SetBorder(true). - SetBorderPadding(0, 0, 1, 1). - SetBorderAttributes(tcell.AttrDim) - - textView.SetTitle(fmt.Sprintf("%s @ Height %d [Tx %d of %d]", detail.chainID, tx.Height, i+1, len(detail.Txs))) - - detail.Pages.AddPage(idx, textView, true, false) - } - - detail.Pages.SwitchToPage(pageIdx) -} - -func (*txDetailView) buildSearchInput() *tview.InputField { - input := tview.NewInputField(). - SetFieldTextColor(searchInactiveColor). - SetFieldBackgroundColor(backgroundColor) - - input.SetTitle("Search"). - SetTitleColor(searchInactiveColor). - SetTitleAlign(tview.AlignLeft). - SetBorder(true). - SetBorderAttributes(tcell.AttrDim). - SetBorderColor(searchInactiveColor) - return input -} diff --git a/blockdb/views_test.go b/blockdb/views_test.go deleted file mode 100644 index 79a353546..000000000 --- a/blockdb/views_test.go +++ /dev/null @@ -1,141 +0,0 @@ -package blockdb - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -func TestTxFlattenedView(t *testing.T) { - t.Parallel() - - db := migratedDB() - defer db.Close() - - ctx := context.Background() - - beforeTestCaseCreate := time.Now().UTC().Format(time.RFC3339) - tc, err := CreateTestCase(ctx, db, "mytest", "abc123") - require.NoError(t, err) - - chain, err := tc.AddChain(ctx, "chain1", "cosmos") - require.NoError(t, err) - - beforeBlocksCreated := time.Now().UTC().Format(time.RFC3339) - require.NoError(t, chain.SaveBlock(ctx, 1, []Tx{ - {Data: []byte("tx1.0")}, - })) - require.NoError(t, chain.SaveBlock(ctx, 2, []Tx{ - {Data: []byte("tx2.0")}, - {Data: []byte("tx2.1")}, - })) - afterBlocksCreated := time.Now().UTC().Format(time.RFC3339) - - var ( - tcID int64 - tcCreatedAt string - tcName string - - chainKeyID int64 - chainID string - chainType string - - blockID int - blockCreatedAt string - blockHeight int - - txID int - tx string - ) - rows, err := db.Query(`SELECT - test_case_id, test_case_created_at, test_case_name, - chain_kid, chain_id, chain_type, - block_id, block_created_at, block_height, - tx_id, tx -FROM v_tx_flattened -ORDER BY test_case_id, chain_kid, block_id, tx_id -`) - require.NoError(t, err) - defer rows.Close() - - // Collect the first row and make assertions. - require.True(t, rows.Next()) - require.NoError(t, rows.Scan( - &tcID, &tcCreatedAt, &tcName, - &chainKeyID, &chainID, &chainType, - &blockID, &blockCreatedAt, &blockHeight, - &txID, &tx, - )) - - require.Equal(t, tcID, tc.id) - require.GreaterOrEqual(t, tcCreatedAt, beforeTestCaseCreate) - require.LessOrEqual(t, tcCreatedAt, beforeBlocksCreated) - require.Equal(t, tcName, "mytest") - - require.Equal(t, chainKeyID, chain.id) - require.Equal(t, chainID, "chain1") - require.Equal(t, chainType, "cosmos") - - require.GreaterOrEqual(t, blockCreatedAt, beforeBlocksCreated) - require.LessOrEqual(t, blockCreatedAt, afterBlocksCreated) - require.Equal(t, blockHeight, 1) - - require.Equal(t, tx, "tx1.0") - - // Save some state gathered from the first row. - firstBlockCreatedAt := blockCreatedAt - firstTxID := txID - - // Collect the second row and make assertions. - require.True(t, rows.Next()) - require.NoError(t, rows.Scan( - &tcID, &tcCreatedAt, &tcName, - &chainKeyID, &chainID, &chainType, - &blockID, &blockCreatedAt, &blockHeight, - &txID, &tx, - )) - - // Same test case and chain. - require.Equal(t, tcID, tc.id) - require.Equal(t, chainKeyID, chain.id) - - // New block height. - require.GreaterOrEqual(t, blockCreatedAt, firstBlockCreatedAt) - require.LessOrEqual(t, blockCreatedAt, afterBlocksCreated) - require.Equal(t, blockHeight, 2) - - // Next transaction. - require.Greater(t, txID, firstTxID) - require.Equal(t, tx, "tx2.0") - - secondTxID := txID - - // Third and final row. - require.True(t, rows.Next()) - require.NoError(t, rows.Scan( - &tcID, &tcCreatedAt, &tcName, - &chainKeyID, &chainID, &chainType, - &blockID, &blockCreatedAt, &blockHeight, - &txID, &tx, - )) - - // Same test case and chain. - require.Equal(t, tcID, tc.id) - require.Equal(t, chainKeyID, chain.id) - - // Same block height. - require.Equal(t, blockHeight, 2) - - // Next transaction. - require.Greater(t, txID, secondTxID) - require.Equal(t, tx, "tx2.1") - - // No more rows. - require.False(t, rows.Next()) -} - -func TestTxAggView(t *testing.T) { - // Nop. Tested as part of QueryService. -} diff --git a/chainset.go b/chainset.go index 5bd172693..e8eefe513 100644 --- a/chainset.go +++ b/chainset.go @@ -4,12 +4,9 @@ import ( "context" "database/sql" "fmt" - "os" "sync" - "time" "github.com/docker/docker/client" - "github.com/strangelove-ventures/interchaintest/v8/blockdb" "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" "github.com/strangelove-ventures/interchaintest/v8/ibc" "go.uber.org/multierr" @@ -27,9 +24,8 @@ type chainSet struct { chains map[ibc.Chain]struct{} // The following fields are set during TrackBlocks, and used in Close. - trackerEg *errgroup.Group - db *sql.DB - collectors []*blockdb.Collector + trackerEg *errgroup.Group + db *sql.DB } func newChainSet(log *zap.Logger, chains []ibc.Chain) *chainSet { @@ -158,79 +154,11 @@ func (cs *chainSet) Start(ctx context.Context, testName string, additionalGenesi return eg.Wait() } -// TrackBlocks initializes database tables and polls for transactions to be saved in the database. -// This method is a nop if dbPath is blank. -// The gitSha is used to pin a git commit to a test invocation. Thus, when a user is looking at historical -// data they are able to determine which version of the code produced the results. -// Expected to be called after Start. -func (cs chainSet) TrackBlocks(ctx context.Context, testName, dbPath, gitSha string) error { - if len(dbPath) == 0 { - // nop - return nil - } - - db, err := blockdb.ConnectDB(ctx, dbPath) - if err != nil { - return fmt.Errorf("connect to sqlite database %s: %w", dbPath, err) - } - cs.db = db - - if len(gitSha) == 0 { - gitSha = "unknown" - } - - if err := blockdb.Migrate(db, gitSha); err != nil { - return fmt.Errorf("migrate sqlite database %s; deleting file recommended: %w", dbPath, err) - } - - testCase, err := blockdb.CreateTestCase(ctx, db, testName, gitSha) - if err != nil { - _ = db.Close() - return fmt.Errorf("create test case in sqlite database: %w", err) - } - - // TODO (nix - 6/1/22) Need logger instead of fmt.Fprint - cs.trackerEg = new(errgroup.Group) - cs.collectors = make([]*blockdb.Collector, len(cs.chains)) - i := 0 - for c := range cs.chains { - c := c - id := c.Config().ChainID - finder, ok := c.(blockdb.TxFinder) - if !ok { - fmt.Fprintf(os.Stderr, `Chain %s is not configured to save blocks; must implement "FindTxs(ctx context.Context, height int64) ([][]byte, error)"`+"\n", id) - return nil - } - j := i // Avoid closure on loop variable. - cs.trackerEg.Go(func() error { - chaindb, err := testCase.AddChain(ctx, id, c.Config().Type) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to add chain %s to database: %v", id, err) - return nil - } - log := cs.log.With(zap.String("chain_id", id)) - collector := blockdb.NewCollector(log, finder, chaindb, 100*time.Millisecond) - cs.collectors[j] = collector - collector.Collect(ctx) - return nil - }) - i++ - } - - return nil -} - // Close frees any resources associated with the chainSet. // // Currently, it only frees resources from TrackBlocks. // Close is safe to call even if TrackBlocks was not called. func (cs *chainSet) Close() error { - for _, c := range cs.collectors { - if c != nil { - c.Stop() - } - } - var err error if cs.trackerEg != nil { multierr.AppendInto(&err, cs.trackerEg.Wait()) diff --git a/cmd/interchaintest/interchaintest_test.go b/cmd/interchaintest/interchaintest_test.go index c46718578..cf2fa53b7 100644 --- a/cmd/interchaintest/interchaintest_test.go +++ b/cmd/interchaintest/interchaintest_test.go @@ -11,10 +11,7 @@ import ( "testing" "time" - "github.com/rivo/tview" interchaintest "github.com/strangelove-ventures/interchaintest/v8" - "github.com/strangelove-ventures/interchaintest/v8/blockdb" - blockdbtui "github.com/strangelove-ventures/interchaintest/v8/blockdb/tui" "github.com/strangelove-ventures/interchaintest/v8/conformance" "github.com/strangelove-ventures/interchaintest/v8/ibc" "github.com/strangelove-ventures/interchaintest/v8/relayer" @@ -52,15 +49,8 @@ func TestMain(m *testing.M) { addFlags() parseFlags() - ctx := context.Background() - switch subcommand() { case "debug": - if err := runDebugTerminalUI(ctx); err != nil { - fmt.Fprintf(os.Stderr, "Failed to run debug: %v\n", err) - os.Exit(1) - } - os.Exit(0) case "version": fmt.Fprintln(os.Stderr, interchaintest.GitSha) os.Exit(0) @@ -260,44 +250,3 @@ func parseFlags() { func subcommand() string { return flag.Arg(0) } - -func runDebugTerminalUI(ctx context.Context) error { - dbPath := extraFlags.BlockDatabaseFile - - // Explicitly check for file existence otherwise blockdb.ConnectDB implicitly creates and migrates a sqlite file. - if _, err := os.Stat(dbPath); err != nil { - return err - } - - db, err := blockdb.ConnectDB(ctx, dbPath) - if err != nil { - return fmt.Errorf("connect to database %s: %w", dbPath, err) - } - defer db.Close() - - if err = blockdb.Migrate(db, interchaintest.GitSha); err != nil { - return fmt.Errorf("migrate database %s: %w", dbPath, err) - } - - querySvc := blockdb.NewQuery(db) - - schemaInfo, err := querySvc.CurrentSchemaVersion(ctx) - if err != nil { - return fmt.Errorf("query schema version: %w", err) - } - - testCases, err := querySvc.RecentTestCases(ctx, 100) - if err != nil { - return fmt.Errorf("query recent test cases: %w", err) - } - if len(testCases) == 0 { - return fmt.Errorf("no test cases found in database %s", dbPath) - } - - app := tview.NewApplication() - model := blockdbtui.NewModel(blockdb.NewQuery(db), dbPath, schemaInfo.GitSha, schemaInfo.CreatedAt, testCases) - return app. - SetInputCapture(model.Update(ctx)). - SetRoot(model.RootView(), true). - Run() -} diff --git a/interchain.go b/interchain.go index 95d09108a..4c4fc39fa 100644 --- a/interchain.go +++ b/interchain.go @@ -339,10 +339,6 @@ func (ic *Interchain) Build(ctx context.Context, rep *testreporter.RelayerExecRe return fmt.Errorf("failed to start chains: %w", err) } - if err := ic.cs.TrackBlocks(ctx, opts.TestName, opts.BlockDatabaseFile, opts.GitSha); err != nil { - return fmt.Errorf("failed to track blocks: %w", err) - } - // If any configured chain is an instance of Penumbra we need to initialize new pclientd instances for the // newly created faucet account. for c := range ic.chains { From 8aa0eb02dacadbf0c2d91defe2bcc760f07faab1 Mon Sep 17 00:00:00 2001 From: Reece Williams Date: Fri, 13 Sep 2024 20:41:42 -0500 Subject: [PATCH 2/2] remove FindTxs() commands --- chain/cosmos/chain_node.go | 80 --------------------- chain/cosmos/cosmos_chain.go | 9 --- chain/polkadot/polkadot_chain.go | 6 -- chain/thorchain/thorchain.go | 9 --- chain/thorchain/thornode.go | 80 --------------------- examples/cosmos/chain_miscellaneous_test.go | 13 ---- 6 files changed, 197 deletions(-) diff --git a/chain/cosmos/chain_node.go b/chain/cosmos/chain_node.go index 912c2a7fe..21a9001a1 100644 --- a/chain/cosmos/chain_node.go +++ b/chain/cosmos/chain_node.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "crypto/sha256" - "encoding/hex" "encoding/json" "fmt" "hash/fnv" @@ -22,7 +21,6 @@ import ( "github.com/cometbft/cometbft/p2p" rpcclient "github.com/cometbft/cometbft/rpc/client" rpchttp "github.com/cometbft/cometbft/rpc/client/http" - coretypes "github.com/cometbft/cometbft/rpc/core/types" libclient "github.com/cometbft/cometbft/rpc/jsonrpc/client" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" @@ -37,13 +35,11 @@ import ( "github.com/docker/go-connections/nat" "go.uber.org/zap" "golang.org/x/mod/semver" - "golang.org/x/sync/errgroup" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" icatypes "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/types" ccvclient "github.com/cosmos/interchain-security/v5/x/ccv/provider/client" - "github.com/strangelove-ventures/interchaintest/v8/blockdb" "github.com/strangelove-ventures/interchaintest/v8/dockerutil" "github.com/strangelove-ventures/interchaintest/v8/ibc" "github.com/strangelove-ventures/interchaintest/v8/testutil" @@ -427,82 +423,6 @@ func (tn *ChainNode) Height(ctx context.Context) (int64, error) { return height, nil } -// FindTxs implements blockdb.BlockSaver. -func (tn *ChainNode) FindTxs(ctx context.Context, height int64) ([]blockdb.Tx, error) { - h := int64(height) - var eg errgroup.Group - var blockRes *coretypes.ResultBlockResults - var block *coretypes.ResultBlock - eg.Go(func() (err error) { - blockRes, err = tn.Client.BlockResults(ctx, &h) - return err - }) - eg.Go(func() (err error) { - block, err = tn.Client.Block(ctx, &h) - return err - }) - if err := eg.Wait(); err != nil { - return nil, err - } - interfaceRegistry := tn.Chain.Config().EncodingConfig.InterfaceRegistry - txs := make([]blockdb.Tx, 0, len(block.Block.Txs)+2) - for i, tx := range block.Block.Txs { - var newTx blockdb.Tx - newTx.Data = []byte(fmt.Sprintf(`{"data":"%s"}`, hex.EncodeToString(tx))) - - sdkTx, err := decodeTX(interfaceRegistry, tx) - if err != nil { - tn.logger().Info("Failed to decode tx", zap.Int64("height", height), zap.Error(err)) - continue - } - b, err := encodeTxToJSON(interfaceRegistry, sdkTx) - if err != nil { - tn.logger().Info("Failed to marshal tx to json", zap.Int64("height", height), zap.Error(err)) - continue - } - newTx.Data = b - - rTx := blockRes.TxsResults[i] - - newTx.Events = make([]blockdb.Event, len(rTx.Events)) - for j, e := range rTx.Events { - attrs := make([]blockdb.EventAttribute, len(e.Attributes)) - for k, attr := range e.Attributes { - attrs[k] = blockdb.EventAttribute{ - Key: string(attr.Key), - Value: string(attr.Value), - } - } - newTx.Events[j] = blockdb.Event{ - Type: e.Type, - Attributes: attrs, - } - } - txs = append(txs, newTx) - } - if len(blockRes.FinalizeBlockEvents) > 0 { - finalizeBlockTx := blockdb.Tx{ - Data: []byte(`{"data":"finalize_block","note":"this is a transaction artificially created for debugging purposes"}`), - } - finalizeBlockTx.Events = make([]blockdb.Event, len(blockRes.FinalizeBlockEvents)) - for i, e := range blockRes.FinalizeBlockEvents { - attrs := make([]blockdb.EventAttribute, len(e.Attributes)) - for j, attr := range e.Attributes { - attrs[j] = blockdb.EventAttribute{ - Key: string(attr.Key), - Value: string(attr.Value), - } - } - finalizeBlockTx.Events[i] = blockdb.Event{ - Type: e.Type, - Attributes: attrs, - } - } - txs = append(txs, finalizeBlockTx) - } - return txs, nil -} - // TxCommand is a helper to retrieve a full command for broadcasting a tx // with the chain node binary. func (tn *ChainNode) TxCommand(keyName string, command ...string) []string { diff --git a/chain/cosmos/cosmos_chain.go b/chain/cosmos/cosmos_chain.go index c493806ce..5938d7df2 100644 --- a/chain/cosmos/cosmos_chain.go +++ b/chain/cosmos/cosmos_chain.go @@ -30,7 +30,6 @@ import ( dockertypes "github.com/docker/docker/api/types" volumetypes "github.com/docker/docker/api/types/volume" "github.com/docker/docker/client" - "github.com/strangelove-ventures/interchaintest/v8/blockdb" wasmtypes "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos/08-wasm-types" "github.com/strangelove-ventures/interchaintest/v8/chain/internal/tendermint" "github.com/strangelove-ventures/interchaintest/v8/dockerutil" @@ -1139,14 +1138,6 @@ func (c *CosmosChain) Timeouts(ctx context.Context, height int64) ([]ibc.PacketT return ibcTimeouts, nil } -// FindTxs implements blockdb.BlockSaver. -func (c *CosmosChain) FindTxs(ctx context.Context, height int64) ([]blockdb.Tx, error) { - fn := c.getFullNode() - c.findTxMu.Lock() - defer c.findTxMu.Unlock() - return fn.FindTxs(ctx, height) -} - // StopAllNodes stops and removes all long running containers (validators and full nodes) func (c *CosmosChain) StopAllNodes(ctx context.Context) error { var eg errgroup.Group diff --git a/chain/polkadot/polkadot_chain.go b/chain/polkadot/polkadot_chain.go index 6c71c3b39..df4d1c316 100644 --- a/chain/polkadot/polkadot_chain.go +++ b/chain/polkadot/polkadot_chain.go @@ -22,7 +22,6 @@ import ( p2pcrypto "github.com/libp2p/go-libp2p/core/crypto" "github.com/misko9/go-substrate-rpc-client/v4/signature" gstypes "github.com/misko9/go-substrate-rpc-client/v4/types" - "github.com/strangelove-ventures/interchaintest/v8/blockdb" "github.com/strangelove-ventures/interchaintest/v8/dockerutil" "github.com/strangelove-ventures/interchaintest/v8/ibc" "go.uber.org/zap" @@ -847,11 +846,6 @@ func (c *PolkadotChain) GetKeyringPair(keyName string) (signature.KeyringPair, e return kp, nil } -// FindTxs implements blockdb.BlockSaver (Not implemented yet for polkadot, but we don't want to exit) -func (c *PolkadotChain) FindTxs(ctx context.Context, height int64) ([]blockdb.Tx, error) { - return []blockdb.Tx{}, nil -} - // GetIbcBalance returns the Coins type of ibc coins in account func (c *PolkadotChain) GetIbcBalance(ctx context.Context, address string, denom uint64) (sdktypes.Coin, error) { return c.ParachainNodes[0][0].GetIbcBalance(ctx, address, denom) diff --git a/chain/thorchain/thorchain.go b/chain/thorchain/thorchain.go index 078b6a4a3..c92ec5436 100644 --- a/chain/thorchain/thorchain.go +++ b/chain/thorchain/thorchain.go @@ -26,7 +26,6 @@ import ( dockertypes "github.com/docker/docker/api/types" volumetypes "github.com/docker/docker/api/types/volume" "github.com/docker/docker/client" - "github.com/strangelove-ventures/interchaintest/v8/blockdb" "github.com/strangelove-ventures/interchaintest/v8/dockerutil" "github.com/strangelove-ventures/interchaintest/v8/ibc" "github.com/strangelove-ventures/interchaintest/v8/testutil" @@ -1120,14 +1119,6 @@ func (c *Thorchain) Timeouts(ctx context.Context, height int64) ([]ibc.PacketTim return ibcTimeouts, nil } -// FindTxs implements blockdb.BlockSaver. -func (c *Thorchain) FindTxs(ctx context.Context, height int64) ([]blockdb.Tx, error) { - fn := c.getFullNode() - c.findTxMu.Lock() - defer c.findTxMu.Unlock() - return fn.FindTxs(ctx, height) -} - // StopAllNodes stops and removes all long running containers (validators and full nodes) func (c *Thorchain) StopAllNodes(ctx context.Context) error { var eg errgroup.Group diff --git a/chain/thorchain/thornode.go b/chain/thorchain/thornode.go index b7c8d9309..2eb1482c4 100644 --- a/chain/thorchain/thornode.go +++ b/chain/thorchain/thornode.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "crypto/sha256" - "encoding/hex" "encoding/json" "fmt" "hash/fnv" @@ -21,7 +20,6 @@ import ( "github.com/cometbft/cometbft/p2p" rpcclient "github.com/cometbft/cometbft/rpc/client" rpchttp "github.com/cometbft/cometbft/rpc/client/http" - coretypes "github.com/cometbft/cometbft/rpc/core/types" libclient "github.com/cometbft/cometbft/rpc/jsonrpc/client" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/crypto/keyring" @@ -34,11 +32,9 @@ import ( "github.com/icza/dyno" "go.uber.org/zap" "golang.org/x/mod/semver" - "golang.org/x/sync/errgroup" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" - "github.com/strangelove-ventures/interchaintest/v8/blockdb" "github.com/strangelove-ventures/interchaintest/v8/dockerutil" "github.com/strangelove-ventures/interchaintest/v8/ibc" "github.com/strangelove-ventures/interchaintest/v8/testutil" @@ -404,82 +400,6 @@ func (tn *ChainNode) Height(ctx context.Context) (int64, error) { return height, nil } -// FindTxs implements blockdb.BlockSaver. -func (tn *ChainNode) FindTxs(ctx context.Context, height int64) ([]blockdb.Tx, error) { - h := int64(height) - var eg errgroup.Group - var blockRes *coretypes.ResultBlockResults - var block *coretypes.ResultBlock - eg.Go(func() (err error) { - blockRes, err = tn.Client.BlockResults(ctx, &h) - return err - }) - eg.Go(func() (err error) { - block, err = tn.Client.Block(ctx, &h) - return err - }) - if err := eg.Wait(); err != nil { - return nil, err - } - interfaceRegistry := tn.Chain.Config().EncodingConfig.InterfaceRegistry - txs := make([]blockdb.Tx, 0, len(block.Block.Txs)+2) - for i, tx := range block.Block.Txs { - var newTx blockdb.Tx - newTx.Data = []byte(fmt.Sprintf(`{"data":"%s"}`, hex.EncodeToString(tx))) - - sdkTx, err := decodeTX(interfaceRegistry, tx) - if err != nil { - tn.logger().Info("Failed to decode tx", zap.Int64("height", height), zap.Error(err)) - continue - } - b, err := encodeTxToJSON(interfaceRegistry, sdkTx) - if err != nil { - tn.logger().Info("Failed to marshal tx to json", zap.Int64("height", height), zap.Error(err)) - continue - } - newTx.Data = b - - rTx := blockRes.TxsResults[i] - - newTx.Events = make([]blockdb.Event, len(rTx.Events)) - for j, e := range rTx.Events { - attrs := make([]blockdb.EventAttribute, len(e.Attributes)) - for k, attr := range e.Attributes { - attrs[k] = blockdb.EventAttribute{ - Key: string(attr.Key), - Value: string(attr.Value), - } - } - newTx.Events[j] = blockdb.Event{ - Type: e.Type, - Attributes: attrs, - } - } - txs = append(txs, newTx) - } - if len(blockRes.FinalizeBlockEvents) > 0 { - finalizeBlockTx := blockdb.Tx{ - Data: []byte(`{"data":"finalize_block","note":"this is a transaction artificially created for debugging purposes"}`), - } - finalizeBlockTx.Events = make([]blockdb.Event, len(blockRes.FinalizeBlockEvents)) - for i, e := range blockRes.FinalizeBlockEvents { - attrs := make([]blockdb.EventAttribute, len(e.Attributes)) - for j, attr := range e.Attributes { - attrs[j] = blockdb.EventAttribute{ - Key: string(attr.Key), - Value: string(attr.Value), - } - } - finalizeBlockTx.Events[i] = blockdb.Event{ - Type: e.Type, - Attributes: attrs, - } - } - txs = append(txs, finalizeBlockTx) - } - return txs, nil -} - // TxCommand is a helper to retrieve a full command for broadcasting a tx // with the chain node binary. func (tn *ChainNode) TxCommand(keyName string, command ...string) []string { diff --git a/examples/cosmos/chain_miscellaneous_test.go b/examples/cosmos/chain_miscellaneous_test.go index 6203fb901..2589dc7e1 100644 --- a/examples/cosmos/chain_miscellaneous_test.go +++ b/examples/cosmos/chain_miscellaneous_test.go @@ -82,7 +82,6 @@ func TestICTestMiscellaneous(t *testing.T) { testBuildDependencies(ctx, t, chain) testWalletKeys(ctx, t, chain) testSendingTokens(ctx, t, chain, users) - testFindTxs(ctx, t, chain, users) // not supported with CometMock testPollForBalance(ctx, t, chain, users) testRangeBlockMessages(ctx, t, chain, users) testBroadcaster(ctx, t, chain, users) @@ -208,18 +207,6 @@ func testSendingTokens(ctx context.Context, t *testing.T, chain *cosmos.CosmosCh require.Equal(t, b2.Add(math.NewInt(sendAmt)), b2New) } -func testFindTxs(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { - height, _ := chain.Height(ctx) - - _, err := sendTokens(ctx, chain, users[0], users[1], "", 1) - require.NoError(t, err) - - txs, err := chain.FindTxs(ctx, height+1) - require.NoError(t, err) - require.NotEmpty(t, txs) - require.Equal(t, txs[0].Events[0].Type, "coin_spent") -} - func testPollForBalance(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { bal2, err := chain.GetBalance(ctx, users[1].FormattedAddress(), chain.Config().Denom) require.NoError(t, err)