diff --git a/exp/lighthorizon/archive/ingest_archive.go b/exp/lighthorizon/archive/ingest_archive.go index ec3561ea10..b5de038686 100644 --- a/exp/lighthorizon/archive/ingest_archive.go +++ b/exp/lighthorizon/archive/ingest_archive.go @@ -9,6 +9,7 @@ import ( "github.com/stellar/go/ingest" "github.com/stellar/go/ingest/ledgerbackend" "github.com/stellar/go/metaarchive" + "github.com/stellar/go/support/collections/set" "github.com/stellar/go/support/errors" "github.com/stellar/go/support/log" "github.com/stellar/go/support/storage" @@ -95,30 +96,26 @@ func (a ingestArchive) NewLedgerTransactionReaderFromLedgerCloseMeta(networkPass return &ingestTransactionReaderAdaption{ingestReader}, nil } -func (a ingestArchive) GetTransactionParticipants(transaction LedgerTransaction) (map[string]struct{}, error) { - participants, err := index.GetTransactionParticipants(a.ingestTx(transaction)) +func (a ingestArchive) GetTransactionParticipants(tx LedgerTransaction) (set.Set[string], error) { + participants, err := index.GetTransactionParticipants(a.ingestTx(tx)) if err != nil { return nil, err } - set := make(map[string]struct{}) - exists := struct{}{} - for _, participant := range participants { - set[participant] = exists - } - return set, nil + + s := set.NewSet[string](len(participants)) + s.AddSlice(participants) + return s, nil } -func (a ingestArchive) GetOperationParticipants(transaction LedgerTransaction, operation xdr.Operation, opIndex int) (map[string]struct{}, error) { - participants, err := index.GetOperationParticipants(a.ingestTx(transaction), operation, opIndex) +func (a ingestArchive) GetOperationParticipants(tx LedgerTransaction, op xdr.Operation, opIndex int) (set.Set[string], error) { + participants, err := index.GetOperationParticipants(a.ingestTx(tx), op, opIndex) if err != nil { return nil, err } - set := make(map[string]struct{}) - exists := struct{}{} - for _, participant := range participants { - set[participant] = exists - } - return set, nil + + s := set.NewSet[string](len(participants)) + s.AddSlice(participants) + return s, nil } func (ingestArchive) ingestTx(transaction LedgerTransaction) ingest.LedgerTransaction { diff --git a/exp/lighthorizon/archive/main.go b/exp/lighthorizon/archive/main.go index 378b627c11..882cd3397b 100644 --- a/exp/lighthorizon/archive/main.go +++ b/exp/lighthorizon/archive/main.go @@ -3,6 +3,7 @@ package archive import ( "context" + "github.com/stellar/go/support/collections/set" "github.com/stellar/go/xdr" ) @@ -49,10 +50,10 @@ type Archive interface { // GetTransactionParticipants - takes a LedgerTransaction and returns a set of all // participants(accounts) in the transaction. If there is any error, it will return nil and the error. - GetTransactionParticipants(transaction LedgerTransaction) (map[string]struct{}, error) + GetTransactionParticipants(tx LedgerTransaction) (set.Set[string], error) // GetOperationParticipants - takes a LedgerTransaction, the Operation within the transaction, and // the 0 based index of the operation within the transaction. It will return a set of all participants(accounts) // in the operation. If there is any error, it will return nil and the error. - GetOperationParticipants(transaction LedgerTransaction, operation xdr.Operation, opIndex int) (map[string]struct{}, error) + GetOperationParticipants(tx LedgerTransaction, op xdr.Operation, opIndex int) (set.Set[string], error) } diff --git a/exp/lighthorizon/archive/mock_archive.go b/exp/lighthorizon/archive/mock_archive.go index b40076bc91..29c928314f 100644 --- a/exp/lighthorizon/archive/mock_archive.go +++ b/exp/lighthorizon/archive/mock_archive.go @@ -3,6 +3,7 @@ package archive import ( "context" + "github.com/stellar/go/support/collections/set" "github.com/stellar/go/xdr" "github.com/stretchr/testify/mock" ) @@ -35,12 +36,12 @@ func (m *MockArchive) NewLedgerTransactionReaderFromLedgerCloseMeta(networkPassp return args.Get(0).(LedgerTransactionReader), args.Error(1) } -func (m *MockArchive) GetTransactionParticipants(transaction LedgerTransaction) (map[string]struct{}, error) { - args := m.Called(transaction) - return args.Get(0).(map[string]struct{}), args.Error(1) +func (m *MockArchive) GetTransactionParticipants(tx LedgerTransaction) (set.Set[string], error) { + args := m.Called(tx) + return args.Get(0).(set.Set[string]), args.Error(1) } -func (m *MockArchive) GetOperationParticipants(transaction LedgerTransaction, operation xdr.Operation, opIndex int) (map[string]struct{}, error) { - args := m.Called(transaction, operation, opIndex) - return args.Get(0).(map[string]struct{}), args.Error(1) +func (m *MockArchive) GetOperationParticipants(tx LedgerTransaction, op xdr.Operation, opIndex int) (set.Set[string], error) { + args := m.Called(tx, op, opIndex) + return args.Get(0).(set.Set[string]), args.Error(1) } diff --git a/exp/lighthorizon/index/backend/file.go b/exp/lighthorizon/index/backend/file.go index 2e25e1c1bc..e414730308 100644 --- a/exp/lighthorizon/index/backend/file.go +++ b/exp/lighthorizon/index/backend/file.go @@ -10,6 +10,7 @@ import ( types "github.com/stellar/go/exp/lighthorizon/index/types" + "github.com/stellar/go/support/collections/set" "github.com/stellar/go/support/errors" "github.com/stellar/go/support/log" ) @@ -157,7 +158,7 @@ func (s *FileBackend) ReadAccounts() ([]string, error) { // Note that this will never be too large, but may be too small. preallocationSize = int(info.Size()) / (gAddressSize + 1) // +1 for \n } - accountMap := make(map[string]struct{}, preallocationSize) + accountMap := set.NewSet[string](preallocationSize) accounts := make([]string, 0, preallocationSize) reader := bufio.NewReaderSize(f, 100*gAddressSize) // reasonable buffer size @@ -173,8 +174,8 @@ func (s *FileBackend) ReadAccounts() ([]string, error) { // The account list is very unlikely to be unique (especially if it was made // w/ parallel flushes), so let's ensure that that's the case. - if _, ok := accountMap[account]; !ok { - accountMap[account] = struct{}{} + if !accountMap.Contains(account) { + accountMap.Add(account) accounts = append(accounts, account) } } diff --git a/exp/lighthorizon/index/cmd/mapreduce_test.go b/exp/lighthorizon/index/cmd/mapreduce_test.go index ab37771b70..db529fd8bc 100644 --- a/exp/lighthorizon/index/cmd/mapreduce_test.go +++ b/exp/lighthorizon/index/cmd/mapreduce_test.go @@ -15,6 +15,8 @@ import ( "github.com/stellar/go/exp/lighthorizon/index" "github.com/stellar/go/historyarchive" "github.com/stellar/go/network" + "github.com/stellar/go/support/collections/maps" + "github.com/stellar/go/support/collections/set" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -53,7 +55,7 @@ func TestReduce(t *testing.T) { require.NoError(t, err) stores := []index.Store{indexStore} // to reuse code: same as array of 1 store - assertParticipantsEqual(t, keysU32(participants), stores) + assertParticipantsEqual(t, maps.Keys(participants), stores) for account, checkpoints := range participants { assertParticipantCheckpointsEqual(t, account, checkpoints, stores) } @@ -138,7 +140,7 @@ func RunMapTest(t *testing.T) (uint32, uint32, string) { t.Logf("Connected to index #%d at %s", i+1, indexUrl) } - assertParticipantsEqual(t, keysU32(participants), stores) + assertParticipantsEqual(t, maps.Keys(participants), stores) for account, checkpoints := range participants { assertParticipantCheckpointsEqual(t, account, checkpoints, stores) } @@ -152,20 +154,17 @@ func assertParticipantsEqual(t *testing.T, expectedAccountSet []string, indexGroup []index.Store, ) { - indexGroupAccountSet := make(map[string]struct{}, len(expectedAccountSet)) + indexGroupAccountSet := set.NewSet[string](len(expectedAccountSet)) for _, store := range indexGroup { accounts, err := store.ReadAccounts() require.NoError(t, err) - - for _, account := range accounts { - indexGroupAccountSet[account] = struct{}{} - } + indexGroupAccountSet.AddSlice(accounts) } assert.Lenf(t, indexGroupAccountSet, len(expectedAccountSet), "quantity of accounts across indices doesn't match") - mappedAccountSet := keysSet(indexGroupAccountSet) + mappedAccountSet := maps.Keys(indexGroupAccountSet) require.ElementsMatch(t, expectedAccountSet, mappedAccountSet) } @@ -177,7 +176,7 @@ func assertParticipantCheckpointsEqual(t *testing.T, // Ensure that all of the active checkpoints reported by the index match // the ones we tracked while ingesting the range ourselves. - foundCheckpoints := make(map[uint32]struct{}, len(expected)) + foundCheckpoints := set.NewSet[uint32](len(expected)) for _, store := range indexGroup { var err error var lastActiveCheckpoint uint32 = 0 @@ -188,7 +187,7 @@ func assertParticipantCheckpointsEqual(t *testing.T, } require.NoError(t, err) // still an error since it shouldn't happen - foundCheckpoints[lastActiveCheckpoint] = struct{}{} + foundCheckpoints.Add(lastActiveCheckpoint) lastActiveCheckpoint += 1 // hit next active one } } @@ -231,19 +230,3 @@ func assertTOIDsEqual(t *testing.T, toids map[string]int64, stores []index.Store require.Truef(t, found, "TOID for tx 0x%s not found in stores", hash) } } - -func keysU32(dict map[string][]uint32) []string { - result := make([]string, 0, len(dict)) - for key := range dict { - result = append(result, key) - } - return result -} - -func keysSet(dict map[string]struct{}) []string { - result := make([]string, 0, len(dict)) - for key := range dict { - result = append(result, key) - } - return result -} diff --git a/exp/lighthorizon/services/main_test.go b/exp/lighthorizon/services/main_test.go index 8e6d955b2b..3b73292f71 100644 --- a/exp/lighthorizon/services/main_test.go +++ b/exp/lighthorizon/services/main_test.go @@ -9,6 +9,7 @@ import ( "github.com/stellar/go/exp/lighthorizon/archive" "github.com/stellar/go/exp/lighthorizon/index" + "github.com/stellar/go/support/collections/set" "github.com/stellar/go/toid" "github.com/stellar/go/xdr" "github.com/stretchr/testify/mock" @@ -128,12 +129,12 @@ func mockArchiveAndIndex(ctx context.Context, passphrase string) (archive.Archiv On("NewLedgerTransactionReaderFromLedgerCloseMeta", passphrase, expectedLedger3).Return(mockReaderLedger3, nil). On("NewLedgerTransactionReaderFromLedgerCloseMeta", passphrase, mock.Anything).Return(mockReaderLedgerTheRest, nil) - partialParticipants := make(map[string]struct{}) - partialParticipants[source.Address()] = struct{}{} + partialParticipants := set.Set[string]{} + partialParticipants.Add(source.Address()) - allParticipants := make(map[string]struct{}) - allParticipants[source.Address()] = struct{}{} - allParticipants[source2.Address()] = struct{}{} + allParticipants := set.Set[string]{} + allParticipants.Add(source.Address()) + allParticipants.Add(source2.Address()) mockArchive. On("GetTransactionParticipants", expectedLedger1Tx1).Return(partialParticipants, nil). diff --git a/support/collections/maps/map.go b/support/collections/maps/map.go new file mode 100644 index 0000000000..49417dfbfb --- /dev/null +++ b/support/collections/maps/map.go @@ -0,0 +1,17 @@ +package maps + +func Keys[T comparable, U any](m map[T]U) []T { + keys := make([]T, 0, len(m)) + for key := range m { + keys = append(keys, key) + } + return keys +} + +func Values[T comparable, U any](m map[T]U) []U { + values := make([]U, 0, len(m)) + for _, value := range m { + values = append(values, value) + } + return values +} diff --git a/support/collections/maps/map_test.go b/support/collections/maps/map_test.go new file mode 100644 index 0000000000..89b8d84df8 --- /dev/null +++ b/support/collections/maps/map_test.go @@ -0,0 +1,26 @@ +package maps + +import ( + "testing" + + "github.com/stellar/go/support/collections/set" + "github.com/stretchr/testify/require" +) + +func TestSanity(t *testing.T) { + m := map[int]float32{1: 10, 2: 20, 3: 30} + for k, v := range m { + require.Contains(t, Keys(m), k) + require.Contains(t, Values(m), v) + } + + // compatibility with collections/set.Set + s := set.Set[float32]{} + s.Add(1) + s.Add(2) + s.Add(3) + + for item := range s { + require.Contains(t, Keys(s), item) + } +} diff --git a/support/collections/set/set.go b/support/collections/set/set.go index 0cad14dcd4..f7a2d3adea 100644 --- a/support/collections/set/set.go +++ b/support/collections/set/set.go @@ -10,6 +10,12 @@ func (set Set[T]) Add(item T) { set[item] = struct{}{} } +func (set Set[T]) AddSlice(items []T) { + for _, item := range items { + set[item] = struct{}{} + } +} + func (set Set[T]) Remove(item T) { delete(set, item) }