diff --git a/idb/dummy/dummy.go b/idb/dummy/dummy.go index 2926e01f8..29ca96e61 100644 --- a/idb/dummy/dummy.go +++ b/idb/dummy/dummy.go @@ -87,3 +87,8 @@ func (db *dummyIndexerDb) Health(ctx context.Context) (state idb.Health, err err func (db *dummyIndexerDb) GetNetworkState() (state idb.NetworkState, err error) { return idb.NetworkState{}, nil } + +// SetNetworkState is part of idb.IndexerDB +func (db *dummyIndexerDb) SetNetworkState(genesis bookkeeping.Genesis) error { + return nil +} diff --git a/idb/idb.go b/idb/idb.go index e225c594a..f9f5341fa 100644 --- a/idb/idb.go +++ b/idb/idb.go @@ -169,6 +169,7 @@ type IndexerDb interface { GetNextRoundToAccount() (uint64, error) GetSpecialAccounts(ctx context.Context) (transactions.SpecialAddresses, error) GetNetworkState() (NetworkState, error) + SetNetworkState(genesis bookkeeping.Genesis) error GetBlock(ctx context.Context, round uint64, options GetBlockOptions) (blockHeader bookkeeping.BlockHeader, transactions []TxnRow, err error) diff --git a/idb/mocks/IndexerDb.go b/idb/mocks/IndexerDb.go index b13a3ab70..1a110084a 100644 --- a/idb/mocks/IndexerDb.go +++ b/idb/mocks/IndexerDb.go @@ -260,6 +260,20 @@ func (_m *IndexerDb) LoadGenesis(genesis bookkeeping.Genesis) error { return r0 } +// SetNetworkState provides a mock function with given fields: genesis +func (_m *IndexerDb) SetNetworkState(genesis bookkeeping.Genesis) error { + ret := _m.Called(genesis) + + var r0 error + if rf, ok := ret.Get(0).(func(bookkeeping.Genesis) error); ok { + r0 = rf(genesis) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // Transactions provides a mock function with given fields: ctx, tf func (_m *IndexerDb) Transactions(ctx context.Context, tf idb.TransactionFilter) (<-chan idb.TxnRow, uint64) { ret := _m.Called(ctx, tf) diff --git a/idb/postgres/postgres.go b/idb/postgres/postgres.go index da9c7a180..b59c6b8a4 100644 --- a/idb/postgres/postgres.go +++ b/idb/postgres/postgres.go @@ -2312,3 +2312,11 @@ func (db *IndexerDb) GetNetworkState() (idb.NetworkState, error) { } return networkState, nil } + +// SetNetworkState is part of idb.IndexerDB +func (db *IndexerDb) SetNetworkState(genesis bookkeeping.Genesis) error { + networkState := types.NetworkState{ + GenesisHash: crypto.HashObj(genesis), + } + return db.setNetworkState(nil, &networkState) +} diff --git a/idb/postgres/postgres_integration_test.go b/idb/postgres/postgres_integration_test.go index 6698a83fa..8ff834bcc 100644 --- a/idb/postgres/postgres_integration_test.go +++ b/idb/postgres/postgres_integration_test.go @@ -12,6 +12,7 @@ import ( "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-codec/codec" "github.com/algorand/indexer/importer" "github.com/jackc/pgx/v4" "github.com/jackc/pgx/v4/pgxpool" @@ -1942,6 +1943,10 @@ func TestGenesisHashCheckAtDBSetup(t *testing.T) { assert.Contains(t, err.Error(), "genesis hash not matching") } +type ImportState struct { + NextRoundToAccount uint64 `codec:"next_account_round"` +} + // Test that if genesis hash at initial import is different from what is in db metastate // indexer does not start. func TestGenesisHashCheckAtInitialImport(t *testing.T) { @@ -1951,11 +1956,27 @@ func TestGenesisHashCheckAtInitialImport(t *testing.T) { db, _, err := OpenPostgres(connStr, idb.IndexerDbOptions{}, nil) require.NoError(t, err) defer db.Close() + // test db upgrade + // set next round to account + state := ImportState{NextRoundToAccount: 1} + var buf []byte + jsonCodecHandle := new(codec.JsonHandle) + enc := codec.NewEncoderBytes(&buf, jsonCodecHandle) + enc.MustEncode(state) + db.setMetastate(nil, schema.StateMetastateKey, string(buf)) + // network state not initialized + networkState, err := db.getNetworkState(context.Background(), nil) + require.ErrorIs(t, err, idb.ErrorNotInitialized) logger := logrus.New() genesisReader := bytes.NewReader(protocol.EncodeJSON(genesis)) imported, err := importer.EnsureInitialImport(db, genesisReader, logger) require.NoError(t, err) require.True(t, true, imported) + // network state should be set + networkState, err = db.getNetworkState(context.Background(), nil) + require.NoError(t, err) + require.Equal(t, networkState.GenesisHash, crypto.HashObj(genesis)) + // change genesis value genesis.Network = "testnest" genesisReader = bytes.NewReader(protocol.EncodeJSON(genesis)) diff --git a/importer/helper.go b/importer/helper.go index b092617ee..7f1001373 100644 --- a/importer/helper.go +++ b/importer/helper.go @@ -4,6 +4,7 @@ import ( "archive/tar" "compress/bzip2" "context" + "errors" "fmt" "io" "io/ioutil" @@ -267,10 +268,6 @@ func GetGenesisFile(genesisJSONPath string, client *algod.Client, l *log.Logger) } func checkGenesisHash(db idb.IndexerDb, genesisReader io.Reader) error { - network, err := db.GetNetworkState() - if err != nil { - return fmt.Errorf("unable to fetch network state from db %w", err) - } var genesis bookkeeping.Genesis gbytes, err := ioutil.ReadAll(genesisReader) if err != nil { @@ -280,6 +277,16 @@ func checkGenesisHash(db idb.IndexerDb, genesisReader io.Reader) error { if err != nil { return fmt.Errorf("error decoding genesis, %w", err) } + network, err := db.GetNetworkState() + if errors.Is(err, idb.ErrorNotInitialized) { + err = db.SetNetworkState(genesis) + if err != nil { + return fmt.Errorf("error setting network state %w", err) + } + return nil + } else if err != nil { + return fmt.Errorf("unable to fetch network state from db %w", err) + } if network.GenesisHash != crypto.HashObj(genesis) { return fmt.Errorf("genesis hash not matching") }