diff --git a/idb/postgres/internal/writer/test_resources/heartbeat.txn b/idb/postgres/internal/writer/test_resources/heartbeat.txn new file mode 100644 index 00000000..c2070003 Binary files /dev/null and b/idb/postgres/internal/writer/test_resources/heartbeat.txn differ diff --git a/idb/postgres/internal/writer/write_txn_participation.go b/idb/postgres/internal/writer/write_txn_participation.go index d458d854..e1066cd7 100644 --- a/idb/postgres/internal/writer/write_txn_participation.go +++ b/idb/postgres/internal/writer/write_txn_participation.go @@ -40,6 +40,8 @@ func addTransactionParticipants(stxnad *types.SignedTxnWithAD, includeInner bool for _, address := range txn.ApplicationCallTxnFields.Accounts { add(address) } + case types.HeartbeatTx: + add(txn.HbAddress) } if includeInner { diff --git a/idb/postgres/internal/writer/writer_test.go b/idb/postgres/internal/writer/writer_test.go index e0edcbfb..61a20667 100644 --- a/idb/postgres/internal/writer/writer_test.go +++ b/idb/postgres/internal/writer/writer_test.go @@ -481,9 +481,9 @@ func TestWriterTxnParticipationTableNoPayout(t *testing.T) { tests = append(tests, testcase) } { - stxnad := test.MakeCreateAppTxn(sdk.Address(test.AccountA)) + stxnad := test.MakeCreateAppTxn(test.AccountA) stxnad.Txn.ApplicationCallTxnFields.Accounts = - []sdk.Address{sdk.Address(test.AccountB), sdk.Address(test.AccountC)} + []sdk.Address{test.AccountB, test.AccountC} stib, err := util.EncodeSignedTxn(makeBlockFunc().BlockHeader, stxnad.SignedTxn, stxnad.ApplyData) require.NoError(t, err) @@ -492,17 +492,40 @@ func TestWriterTxnParticipationTableNoPayout(t *testing.T) { payset: []sdk.SignedTxnInBlock{stib}, expected: []txnParticipationRow{ { - addr: sdk.Address(test.AccountA), + addr: test.AccountA, round: 2, intra: 0, }, { - addr: sdk.Address(test.AccountB), + addr: test.AccountB, round: 2, intra: 0, }, { - addr: sdk.Address(test.AccountC), + addr: test.AccountC, + round: 2, + intra: 0, + }, + }, + } + tests = append(tests, testcase) + } + { + stxnad0 := test.MakeHeartbeatTxn(test.AccountA, test.AccountD) + stib0, err := util.EncodeSignedTxn(makeBlockFunc().BlockHeader, stxnad0.SignedTxn, stxnad0.ApplyData) + require.NoError(t, err) + + testcase := testtype{ + name: "heartbeat", + payset: []sdk.SignedTxnInBlock{stib0}, + expected: []txnParticipationRow{ + { + addr: test.AccountA, + round: 2, + intra: 0, + }, + { + addr: test.AccountD, round: 2, intra: 0, }, @@ -648,6 +671,40 @@ func TestWriterTxnParticipationTableWithPayout(t *testing.T) { } tests = append(tests, testcase) } + { + stxnad0 := test.MakeHeartbeatTxn(test.AccountA, test.AccountD) + stib0, err := util.EncodeSignedTxn(makeBlockFunc().BlockHeader, stxnad0.SignedTxn, stxnad0.ApplyData) + require.NoError(t, err) + + testcase := testtype{ + name: "heartbeat", + payset: []sdk.SignedTxnInBlock{stib0}, + expected: []txnParticipationRow{ + { + addr: test.AccountA, + round: 2, + intra: 0, + }, + { + addr: test.AccountD, + round: 2, + intra: 0, + }, + // Payout involved accounts + { + addr: test.AccountE, + round: 2, + intra: 1, + }, + { + addr: test.FeeAddr, + round: 2, + intra: 1, + }, + }, + } + tests = append(tests, testcase) + } for _, testcase := range tests { t.Run(testcase.name, func(t *testing.T) { diff --git a/util/test/account_testutil.go b/util/test/account_testutil.go index a5e475e6..566599c4 100644 --- a/util/test/account_testutil.go +++ b/util/test/account_testutil.go @@ -4,7 +4,9 @@ import ( "crypto/rand" "crypto/sha512" "fmt" + "os" + "github.com/algorand/indexer/v3/idb" "github.com/algorand/indexer/v3/util" "github.com/algorand/go-algorand-sdk/v2/encoding/msgpack" @@ -352,6 +354,31 @@ func MakeAppCallWithInnerTxn(appSender, paymentSender, paymentReceiver, assetSen return createApp } +// MakeHeartbeatTxn creates a heartbeat transaction, overriding fields with the provided values. +func MakeHeartbeatTxn(sender, hbAddress sdk.Address) sdk.SignedTxnWithAD { + var hbTxn sdk.SignedTxn + _ = msgpack.Decode(loadResourceFileOrPanic("test_resources/heartbeat.txn"), &hbTxn) + + hbTxn.Txn.Sender = sender + hbTxn.Txn.GenesisHash = GenesisHash + var fields = hbTxn.Txn.HeartbeatTxnFields + fields.HbAddress = hbAddress + return sdk.SignedTxnWithAD{ + SignedTxn: hbTxn, + } +} + +func loadResourceFileOrPanic(path string) []byte { + data, err := os.ReadFile(path) + + if err != nil { + panic(fmt.Sprintf("Failed to load resource file: '%s'", path)) + } + var ret idb.TxnRow + _ = msgpack.Decode(data, &ret) + return data +} + // MakeBlockForTxns takes some transactions and constructs a block compatible with the indexer import function. func MakeBlockForTxns(prevHeader sdk.BlockHeader, inputs ...*sdk.SignedTxnWithAD) (sdk.Block, error) { res := sdk.Block{BlockHeader: prevHeader}