Skip to content

Commit eecf724

Browse files
authored
services/horizon: Fix operations participants logic to include InvokeHostFunction operation (stellar#5574)
1 parent 07d197a commit eecf724

7 files changed

Lines changed: 216 additions & 7 deletions

File tree

services/horizon/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ file. This project adheres to [Semantic Versioning](http://semver.org/).
77

88
- Update default pubnet captive core configuration to replace Whalestack with Creit Technologies in the quorum set ([5564](https://github.com/stellar/go/pull/5564)).
99

10+
### Fixed
11+
- Fix the account operations endpoint to include InvokeHostFunction operations. The fix ensures that all account operations will be listed going forward. However, it will not retroactively include these operations for previously ingested ledgers; reingesting the historical data is required to address that. ([5574](https://github.com/stellar/go/pull/5574)).
12+
1013
## 22.0.2
1114

1215
### Fixed

services/horizon/internal/ingest/processor_runner.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ func (s *ProcessorRunner) buildTransactionProcessor(ledgersProcessor *processors
151151
processors.NewOperationProcessor(s.historyQ.NewOperationBatchInsertBuilder(), s.config.NetworkPassphrase),
152152
tradeProcessor,
153153
processors.NewParticipantsProcessor(accountLoader,
154-
s.historyQ.NewTransactionParticipantsBatchInsertBuilder(), s.historyQ.NewOperationParticipantBatchInsertBuilder()),
154+
s.historyQ.NewTransactionParticipantsBatchInsertBuilder(), s.historyQ.NewOperationParticipantBatchInsertBuilder(), s.config.NetworkPassphrase),
155155
processors.NewTransactionProcessor(s.historyQ.NewTransactionBatchInsertBuilder(), s.config.SkipTxmeta),
156156
processors.NewClaimableBalancesTransactionProcessor(cbLoader,
157157
s.historyQ.NewTransactionClaimableBalanceBatchInsertBuilder(), s.historyQ.NewOperationClaimableBalanceBatchInsertBuilder()),

services/horizon/internal/ingest/processors/operations_processor.go

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,7 +1047,31 @@ func (operation *transactionOperationWrapper) Participants() ([]xdr.AccountId, e
10471047
case xdr.OperationTypeLiquidityPoolWithdraw:
10481048
// the only direct participant is the source_account
10491049
case xdr.OperationTypeInvokeHostFunction:
1050-
// the only direct participant is the source_account
1050+
if changes, err := operation.transaction.GetOperationChanges(operation.index); err != nil {
1051+
return participants, err
1052+
} else {
1053+
for _, change := range changes {
1054+
var data xdr.LedgerEntryData
1055+
switch {
1056+
case change.Post != nil:
1057+
data = change.Post.Data
1058+
case change.Pre != nil:
1059+
data = change.Pre.Data
1060+
default:
1061+
log.Errorf("Change Type %s with no pre or post", change.Type.String())
1062+
continue
1063+
}
1064+
if ledgerKey, err := data.LedgerKey(); err == nil {
1065+
participants = append(participants, getLedgerKeyParticipants(ledgerKey)...)
1066+
}
1067+
}
1068+
}
1069+
if diagnosticEvents, err := operation.transaction.GetDiagnosticEvents(); err != nil {
1070+
return participants, err
1071+
} else {
1072+
participants = append(participants, getParticipantsFromSACEvents(filterEvents(diagnosticEvents), operation.network)...)
1073+
}
1074+
10511075
case xdr.OperationTypeExtendFootprintTtl:
10521076
// the only direct participant is the source_account
10531077
case xdr.OperationTypeRestoreFootprint:
@@ -1067,6 +1091,48 @@ func (operation *transactionOperationWrapper) Participants() ([]xdr.AccountId, e
10671091
return dedupeParticipants(participants), nil
10681092
}
10691093

1094+
func getParticipantsFromSACEvents(contractEvents []xdr.ContractEvent, network string) []xdr.AccountId {
1095+
var participants []xdr.AccountId
1096+
1097+
for _, contractEvent := range contractEvents {
1098+
if sacEvent, err := contractevents.NewStellarAssetContractEvent(&contractEvent, network); err == nil {
1099+
// 'to' and 'from' fields in the events can be either a Contract address or an Account address. We're
1100+
// only interested in account addresses and will skip Contract addresses.
1101+
switch sacEvent.GetType() {
1102+
case contractevents.EventTypeTransfer:
1103+
var transferEvt *contractevents.TransferEvent
1104+
transferEvt = sacEvent.(*contractevents.TransferEvent)
1105+
var from, to xdr.AccountId
1106+
if from, err = xdr.AddressToAccountId(transferEvt.From); err == nil {
1107+
participants = append(participants, from)
1108+
}
1109+
if to, err = xdr.AddressToAccountId(transferEvt.To); err == nil {
1110+
participants = append(participants, to)
1111+
}
1112+
case contractevents.EventTypeMint:
1113+
mintEvt := sacEvent.(*contractevents.MintEvent)
1114+
var to xdr.AccountId
1115+
if to, err = xdr.AddressToAccountId(mintEvt.To); err == nil {
1116+
participants = append(participants, to)
1117+
}
1118+
case contractevents.EventTypeClawback:
1119+
clawbackEvt := sacEvent.(*contractevents.ClawbackEvent)
1120+
var from xdr.AccountId
1121+
if from, err = xdr.AddressToAccountId(clawbackEvt.From); err == nil {
1122+
participants = append(participants, from)
1123+
}
1124+
case contractevents.EventTypeBurn:
1125+
burnEvt := sacEvent.(*contractevents.BurnEvent)
1126+
var from xdr.AccountId
1127+
if from, err = xdr.AddressToAccountId(burnEvt.From); err == nil {
1128+
participants = append(participants, from)
1129+
}
1130+
}
1131+
}
1132+
}
1133+
return participants
1134+
}
1135+
10701136
// dedupeParticipants remove any duplicate ids from `in`
10711137
func dedupeParticipants(in []xdr.AccountId) []xdr.AccountId {
10721138
if len(in) <= 1 {
@@ -1090,7 +1156,7 @@ func dedupeParticipants(in []xdr.AccountId) []xdr.AccountId {
10901156
}
10911157

10921158
// OperationsParticipants returns a map with all participants per operation
1093-
func operationsParticipants(transaction ingest.LedgerTransaction, sequence uint32) (map[int64][]xdr.AccountId, error) {
1159+
func operationsParticipants(transaction ingest.LedgerTransaction, sequence uint32, network string) (map[int64][]xdr.AccountId, error) {
10941160
participants := map[int64][]xdr.AccountId{}
10951161

10961162
for opi, op := range transaction.Envelope.Operations() {
@@ -1099,6 +1165,7 @@ func operationsParticipants(transaction ingest.LedgerTransaction, sequence uint3
10991165
transaction: transaction,
11001166
operation: op,
11011167
ledgerSequence: sequence,
1168+
network: network,
11021169
}
11031170

11041171
p, err := operation.Participants()

services/horizon/internal/ingest/processors/participants_processor.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,21 @@ type ParticipantsProcessor struct {
1919
accountLoader *history.AccountLoader
2020
txBatch history.TransactionParticipantsBatchInsertBuilder
2121
opBatch history.OperationParticipantBatchInsertBuilder
22+
network string
2223
}
2324

2425
func NewParticipantsProcessor(
2526
accountLoader *history.AccountLoader,
2627
txBatch history.TransactionParticipantsBatchInsertBuilder,
2728
opBatch history.OperationParticipantBatchInsertBuilder,
29+
network string,
30+
2831
) *ParticipantsProcessor {
2932
return &ParticipantsProcessor{
3033
accountLoader: accountLoader,
3134
txBatch: txBatch,
3235
opBatch: opBatch,
36+
network: network,
3337
}
3438
}
3539

@@ -129,7 +133,7 @@ func (p *ParticipantsProcessor) addOperationsParticipants(
129133
sequence uint32,
130134
transaction ingest.LedgerTransaction,
131135
) error {
132-
participants, err := operationsParticipants(transaction, sequence)
136+
participants, err := operationsParticipants(transaction, sequence, p.network)
133137
if err != nil {
134138
return errors.Wrap(err, "could not determine operation participants")
135139
}

services/horizon/internal/ingest/processors/participants_processor_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ func (s *ParticipantsProcessorTestSuiteLedger) SetupTest() {
9696
s.accountLoader,
9797
s.mockBatchInsertBuilder,
9898
s.mockOperationsBatchInsertBuilder,
99+
networkPassphrase,
99100
)
100101

101102
s.txs = []ingest.LedgerTransaction{

services/horizon/internal/ingest/processors/transaction_operation_wrapper_test.go

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@
33
package processors
44

55
import (
6+
"math/big"
67
"testing"
78

89
"github.com/stretchr/testify/assert"
910
"github.com/stretchr/testify/suite"
1011

12+
"github.com/stellar/go/keypair"
1113
"github.com/stellar/go/protocols/horizon/base"
14+
"github.com/stellar/go/strkey"
15+
"github.com/stellar/go/support/contractevents"
1216

1317
"github.com/stellar/go/ingest"
1418
"github.com/stellar/go/services/horizon/internal/db2/history"
@@ -1104,7 +1108,7 @@ func TestOperationParticipants(t *testing.T) {
11041108
xdr.MustAddress("GDRW375MAYR46ODGF2WGANQC2RRZL7O246DYHHCGWTV2RE7IHE2QUQLD"),
11051109
xdr.MustAddress("GACAR2AEYEKITE2LKI5RMXF5MIVZ6Q7XILROGDT22O7JX4DSWFS7FDDP"),
11061110
}
1107-
participantsMap, err := operationsParticipants(transaction, sequence)
1111+
participantsMap, err := operationsParticipants(transaction, sequence, networkPassphrase)
11081112
tt.NoError(err)
11091113
tt.Len(participantsMap, 1)
11101114
for k, v := range participantsMap {
@@ -2285,3 +2289,110 @@ func TestDetailsCoversAllOperationTypes(t *testing.T) {
22852289
_, err := operation.Details()
22862290
assert.ErrorContains(t, err, "unknown operation type: ")
22872291
}
2292+
2293+
func TestTestInvokeHostFnOperationParticipants(t *testing.T) {
2294+
sourceAddress := "GAUJETIZVEP2NRYLUESJ3LS66NVCEGMON4UDCBCSBEVPIID773P2W6AY"
2295+
source := xdr.MustMuxedAddress(sourceAddress)
2296+
2297+
randomIssuer := keypair.MustRandom()
2298+
randomAsset := xdr.MustNewCreditAsset("TESTING", randomIssuer.Address())
2299+
passphrase := "passphrase"
2300+
randomAccount := keypair.MustRandom().Address()
2301+
2302+
burnEvtAcc := keypair.MustRandom().Address()
2303+
mintEvtAcc := keypair.MustRandom().Address()
2304+
clawbkEvtAcc := keypair.MustRandom().Address()
2305+
transferEvtFromAcc := keypair.MustRandom().Address()
2306+
transferEvtToAcc := keypair.MustRandom().Address()
2307+
2308+
transferContractEvent := contractevents.GenerateEvent(contractevents.EventTypeTransfer, transferEvtFromAcc, transferEvtToAcc, "", randomAsset, big.NewInt(10000000), passphrase)
2309+
mintContractEvent := contractevents.GenerateEvent(contractevents.EventTypeMint, "", mintEvtAcc, randomAccount, randomAsset, big.NewInt(10000000), passphrase)
2310+
burnContractEvent := contractevents.GenerateEvent(contractevents.EventTypeBurn, burnEvtAcc, "", randomAccount, randomAsset, big.NewInt(10000000), passphrase)
2311+
clawbackContractEvent := contractevents.GenerateEvent(contractevents.EventTypeClawback, clawbkEvtAcc, "", randomAccount, randomAsset, big.NewInt(10000000), passphrase)
2312+
2313+
tx1 := ingest.LedgerTransaction{
2314+
UnsafeMeta: xdr.TransactionMeta{
2315+
V: 3,
2316+
V3: &xdr.TransactionMetaV3{
2317+
SorobanMeta: &xdr.SorobanTransactionMeta{
2318+
Events: []xdr.ContractEvent{
2319+
transferContractEvent,
2320+
burnContractEvent,
2321+
mintContractEvent,
2322+
clawbackContractEvent,
2323+
},
2324+
},
2325+
},
2326+
},
2327+
}
2328+
2329+
wrapper1 := transactionOperationWrapper{
2330+
transaction: tx1,
2331+
operation: xdr.Operation{
2332+
SourceAccount: &source,
2333+
Body: xdr.OperationBody{
2334+
Type: xdr.OperationTypeInvokeHostFunction,
2335+
},
2336+
},
2337+
network: passphrase,
2338+
}
2339+
2340+
participants, err := wrapper1.Participants()
2341+
assert.NoError(t, err)
2342+
assert.ElementsMatch(t,
2343+
[]xdr.AccountId{
2344+
xdr.MustAddress(source.Address()),
2345+
xdr.MustAddress(mintEvtAcc),
2346+
xdr.MustAddress(burnEvtAcc),
2347+
xdr.MustAddress(clawbkEvtAcc),
2348+
xdr.MustAddress(transferEvtFromAcc),
2349+
xdr.MustAddress(transferEvtToAcc),
2350+
},
2351+
participants,
2352+
)
2353+
2354+
contractId := [32]byte{}
2355+
zeroContractStrKey, err := strkey.Encode(strkey.VersionByteContract, contractId[:])
2356+
assert.NoError(t, err)
2357+
2358+
transferContractEvent = contractevents.GenerateEvent(contractevents.EventTypeTransfer, zeroContractStrKey, zeroContractStrKey, "", randomAsset, big.NewInt(10000000), passphrase)
2359+
mintContractEvent = contractevents.GenerateEvent(contractevents.EventTypeMint, "", zeroContractStrKey, randomAccount, randomAsset, big.NewInt(10000000), passphrase)
2360+
burnContractEvent = contractevents.GenerateEvent(contractevents.EventTypeBurn, zeroContractStrKey, "", randomAccount, randomAsset, big.NewInt(10000000), passphrase)
2361+
clawbackContractEvent = contractevents.GenerateEvent(contractevents.EventTypeClawback, zeroContractStrKey, "", randomAccount, randomAsset, big.NewInt(10000000), passphrase)
2362+
2363+
tx2 := ingest.LedgerTransaction{
2364+
UnsafeMeta: xdr.TransactionMeta{
2365+
V: 3,
2366+
V3: &xdr.TransactionMetaV3{
2367+
SorobanMeta: &xdr.SorobanTransactionMeta{
2368+
Events: []xdr.ContractEvent{
2369+
transferContractEvent,
2370+
burnContractEvent,
2371+
mintContractEvent,
2372+
clawbackContractEvent,
2373+
},
2374+
},
2375+
},
2376+
},
2377+
}
2378+
2379+
wrapper2 := transactionOperationWrapper{
2380+
transaction: tx2,
2381+
operation: xdr.Operation{
2382+
SourceAccount: &source,
2383+
Body: xdr.OperationBody{
2384+
Type: xdr.OperationTypeInvokeHostFunction,
2385+
},
2386+
},
2387+
network: passphrase,
2388+
}
2389+
2390+
participants, err = wrapper2.Participants()
2391+
assert.NoError(t, err)
2392+
assert.ElementsMatch(t,
2393+
[]xdr.AccountId{
2394+
xdr.MustAddress(source.Address()),
2395+
},
2396+
participants,
2397+
)
2398+
}

services/horizon/internal/integration/sac_test.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func TestContractMintToAccount(t *testing.T) {
5959
itest.Master(),
6060
mint(itest, issuer, asset, "20", accountAddressParam(recipient.GetAccountID())),
6161
)
62-
62+
assertAccountInvokeHostFunctionOperation(itest, recipientKp.Address(), "", recipientKp.Address(), "20.0000000")
6363
assertContainsBalance(itest, recipientKp, issuer, code, amount.MustParse("20"))
6464
assertAssetStats(itest, assetStats{
6565
code: code,
@@ -92,6 +92,7 @@ func TestContractMintToAccount(t *testing.T) {
9292
itest.Master(),
9393
transfer(itest, issuer, asset, "30", accountAddressParam(otherRecipient.GetAccountID())),
9494
)
95+
assertAccountInvokeHostFunctionOperation(itest, otherRecipientKp.Address(), issuer, otherRecipientKp.Address(), "30.0000000")
9596
assertContainsBalance(itest, recipientKp, issuer, code, amount.MustParse("20"))
9697
assertContainsBalance(itest, otherRecipientKp, issuer, code, amount.MustParse("30"))
9798

@@ -552,7 +553,8 @@ func TestContractTransferBetweenAccounts(t *testing.T) {
552553
recipientKp,
553554
transfer(itest, recipientKp.Address(), asset, "30", accountAddressParam(otherRecipient.GetAccountID())),
554555
)
555-
556+
assertAccountInvokeHostFunctionOperation(itest, recipientKp.Address(), recipientKp.Address(), otherRecipientKp.Address(), "30.0000000")
557+
assertAccountInvokeHostFunctionOperation(itest, otherRecipientKp.Address(), recipientKp.Address(), otherRecipientKp.Address(), "30.0000000")
556558
assertContainsBalance(itest, recipientKp, issuer, code, amount.MustParse("970"))
557559
assertContainsBalance(itest, otherRecipientKp, issuer, code, amount.MustParse("30"))
558560

@@ -646,6 +648,7 @@ func TestContractTransferBetweenAccountAndContract(t *testing.T) {
646648
recipientKp,
647649
transfer(itest, recipientKp.Address(), asset, "30", contractAddressParam(recipientContractID)),
648650
)
651+
assertAccountInvokeHostFunctionOperation(itest, recipientKp.Address(), recipientKp.Address(), strkeyRecipientContractID, "30.0000000")
649652
assertContainsBalance(itest, recipientKp, issuer, code, amount.MustParse("970"))
650653
assertContainsEffect(t, getTxEffects(itest, transferTx, asset),
651654
effects.EffectAccountDebited, effects.EffectContractCredited)
@@ -668,6 +671,7 @@ func TestContractTransferBetweenAccountAndContract(t *testing.T) {
668671
recipientKp,
669672
transferFromContract(itest, recipientKp.Address(), asset, recipientContractID, recipientContractHash, "500", accountAddressParam(recipient.GetAccountID())),
670673
)
674+
assertAccountInvokeHostFunctionOperation(itest, recipientKp.Address(), strkeyRecipientContractID, recipientKp.Address(), "500.0000000")
671675
assertContainsEffect(t, getTxEffects(itest, transferTx, asset),
672676
effects.EffectContractDebited, effects.EffectAccountCredited)
673677
assertContainsBalance(itest, recipientKp, issuer, code, amount.MustParse("1470"))
@@ -827,6 +831,7 @@ func TestContractBurnFromAccount(t *testing.T) {
827831
recipientKp,
828832
burn(itest, recipientKp.Address(), asset, "500"),
829833
)
834+
assertAccountInvokeHostFunctionOperation(itest, recipientKp.Address(), recipientKp.Address(), "", "500.0000000")
830835

831836
fx := getTxEffects(itest, burnTx, asset)
832837
require.Len(t, fx, 1)
@@ -981,6 +986,7 @@ func TestContractClawbackFromAccount(t *testing.T) {
981986
itest.Master(),
982987
clawback(itest, issuer, asset, "1000", accountAddressParam(recipientKp.Address())),
983988
)
989+
assertAccountInvokeHostFunctionOperation(itest, recipientKp.Address(), recipientKp.Address(), "", "1000.0000000")
984990

985991
assertContainsEffect(t, getTxEffects(itest, clawTx, asset), effects.EffectAccountDebited)
986992
assertContainsBalance(itest, recipientKp, issuer, code, 0)
@@ -1164,6 +1170,23 @@ func getTxEffects(itest *integration.Test, txHash string, asset xdr.Asset) []eff
11641170
return result
11651171
}
11661172

1173+
func assertAccountInvokeHostFunctionOperation(itest *integration.Test, account string, from string, to string, amount string) {
1174+
ops, err := itest.Client().Operations(horizonclient.OperationRequest{
1175+
ForAccount: account,
1176+
Limit: 1,
1177+
Order: "desc",
1178+
})
1179+
1180+
assert.NoError(itest.CurrentTest(), err)
1181+
result := ops.Embedded.Records[0]
1182+
assert.Equal(itest.CurrentTest(), result.GetType(), operations.TypeNames[xdr.OperationTypeInvokeHostFunction])
1183+
invokeHostFn := result.(operations.InvokeHostFunction)
1184+
assert.Equal(itest.CurrentTest(), invokeHostFn.Function, "HostFunctionTypeHostFunctionTypeInvokeContract")
1185+
assert.Equal(itest.CurrentTest(), to, invokeHostFn.AssetBalanceChanges[0].To)
1186+
assert.Equal(itest.CurrentTest(), from, invokeHostFn.AssetBalanceChanges[0].From)
1187+
assert.Equal(itest.CurrentTest(), amount, invokeHostFn.AssetBalanceChanges[0].Amount)
1188+
}
1189+
11671190
func assertEventPayments(itest *integration.Test, txHash string, asset xdr.Asset, from string, to string, evtType string, amount string) {
11681191
ops, err := itest.Client().Operations(horizonclient.OperationRequest{
11691192
ForTransaction: txHash,

0 commit comments

Comments
 (0)