From 07d197a68f32af17cb14dac3670e4ea89de66e2e Mon Sep 17 00:00:00 2001 From: chowbao Date: Tue, 21 Jan 2025 16:00:29 -0500 Subject: [PATCH 1/2] xdrill for ledgerCloseMeta (#5568) --- ingest/ledger/ledger.go | 143 +++++++++++++++++++++++++ ingest/ledger/ledger_test.go | 199 +++++++++++++++++++++++++++++++++++ xdr/ledger_close_meta.go | 12 +++ xdr/node_id.go | 26 +++++ xdr/transaction_envelope.go | 4 + 5 files changed, 384 insertions(+) create mode 100644 ingest/ledger/ledger.go create mode 100644 ingest/ledger/ledger_test.go create mode 100644 xdr/node_id.go diff --git a/ingest/ledger/ledger.go b/ingest/ledger/ledger.go new file mode 100644 index 0000000000..3a6e0e966e --- /dev/null +++ b/ingest/ledger/ledger.go @@ -0,0 +1,143 @@ +package ledger + +import ( + "encoding/base64" + "fmt" + "time" + + "github.com/stellar/go/xdr" +) + +func Sequence(l xdr.LedgerCloseMeta) uint32 { + return uint32(l.LedgerHeaderHistoryEntry().Header.LedgerSeq) +} + +func Hash(l xdr.LedgerCloseMeta) string { + return l.LedgerHeaderHistoryEntry().Hash.HexString() +} + +func PreviousHash(l xdr.LedgerCloseMeta) string { + return l.PreviousLedgerHash().HexString() +} + +func CloseTime(l xdr.LedgerCloseMeta) int64 { + return l.LedgerCloseTime() +} + +func ClosedAt(l xdr.LedgerCloseMeta) time.Time { + return time.Unix(l.LedgerCloseTime(), 0).UTC() +} + +func TotalCoins(l xdr.LedgerCloseMeta) int64 { + return int64(l.LedgerHeaderHistoryEntry().Header.TotalCoins) +} + +func FeePool(l xdr.LedgerCloseMeta) int64 { + return int64(l.LedgerHeaderHistoryEntry().Header.FeePool) +} + +func BaseFee(l xdr.LedgerCloseMeta) uint32 { + return uint32(l.LedgerHeaderHistoryEntry().Header.BaseFee) +} + +func BaseReserve(l xdr.LedgerCloseMeta) uint32 { + return uint32(l.LedgerHeaderHistoryEntry().Header.BaseReserve) +} + +func MaxTxSetSize(l xdr.LedgerCloseMeta) uint32 { + return uint32(l.LedgerHeaderHistoryEntry().Header.MaxTxSetSize) +} + +func LedgerVersion(l xdr.LedgerCloseMeta) uint32 { + return uint32(l.LedgerHeaderHistoryEntry().Header.LedgerVersion) +} + +func SorobanFeeWrite1Kb(l xdr.LedgerCloseMeta) (int64, bool) { + lcmV1, ok := l.GetV1() + if !ok { + return 0, false + } + + extV1, ok := lcmV1.Ext.GetV1() + if !ok { + return 0, false + } + + return int64(extV1.SorobanFeeWrite1Kb), true +} + +func TotalByteSizeOfBucketList(l xdr.LedgerCloseMeta) (uint64, bool) { + lcmV1, ok := l.GetV1() + if !ok { + return 0, false + } + + return uint64(lcmV1.TotalByteSizeOfBucketList), true +} + +func NodeID(l xdr.LedgerCloseMeta) (string, error) { + LedgerCloseValueSignature, ok := l.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() + if !ok { + return "", fmt.Errorf("could not get LedgerCloseValueSignature") + + } + return LedgerCloseValueSignature.NodeId.GetAddress() +} + +func Signature(l xdr.LedgerCloseMeta) (string, bool) { + LedgerCloseValueSignature, ok := l.LedgerHeaderHistoryEntry().Header.ScpValue.Ext.GetLcValueSignature() + if !ok { + return "", false + } + + return base64.StdEncoding.EncodeToString(LedgerCloseValueSignature.Signature), true +} + +// TransactionCounts calculates and returns the number of successful and total transactions +func TransactionCounts(l xdr.LedgerCloseMeta) (successTxCount, totalTxCount uint32) { + transactions := l.TransactionEnvelopes() + results, err := l.TxProcessing() + if err != nil { + panic(err) + } + + txCount := len(transactions) + if txCount != len(results) { + panic("transaction count and number of TransactionResultMeta not equal") + } + + for i := 0; i < txCount; i++ { + if results[i].Result.Successful() { + successTxCount++ + } + } + + return successTxCount, uint32(txCount) +} + +// OperationCounts calculates and returns the number of successful operations and the total operations within +// a LedgerCloseMeta +func OperationCounts(l xdr.LedgerCloseMeta) (successfulOperationCount, totalOperationCount uint32) { + transactions := l.TransactionEnvelopes() + results, err := l.TxProcessing() + if err != nil { + panic(err) + } + + for i, result := range results { + operations := transactions[i].OperationsCount() + totalOperationCount += operations + + // for successful transactions, the operation count is based on the operations results slice + if result.Result.Successful() { + operationResults, ok := result.Result.OperationResults() + if !ok { + panic("could not get OperationResults") + } + + successfulOperationCount += uint32(len(operationResults)) + } + } + + return successfulOperationCount, totalOperationCount +} diff --git a/ingest/ledger/ledger_test.go b/ingest/ledger/ledger_test.go new file mode 100644 index 0000000000..70c2c75d01 --- /dev/null +++ b/ingest/ledger/ledger_test.go @@ -0,0 +1,199 @@ +package ledger + +import ( + "testing" + "time" + + "github.com/stellar/go/keypair" + "github.com/stellar/go/txnbuild" + "github.com/stellar/go/xdr" + "github.com/stretchr/testify/assert" +) + +func TestLedger(t *testing.T) { + ledger := ledgerTestInput() + + assert.Equal(t, uint32(30578981), Sequence(ledger)) + assert.Equal(t, "26932dc4d84b5fabe9ae744cb43ce4c6daccf98c86a991b2a14945b1adac4d59", Hash(ledger)) + assert.Equal(t, "f63c15d0eaf48afbd751a4c4dfade54a3448053c47c5a71d622668ae0cc2a208", PreviousHash(ledger)) + assert.Equal(t, int64(1594584547), CloseTime(ledger)) + assert.Equal(t, time.Time(time.Date(2020, time.July, 12, 20, 9, 7, 0, time.UTC)), ClosedAt(ledger)) + assert.Equal(t, int64(1054439020873472865), TotalCoins(ledger)) + assert.Equal(t, int64(18153766209161), FeePool(ledger)) + assert.Equal(t, uint32(100), BaseFee(ledger)) + assert.Equal(t, uint32(5000000), BaseReserve(ledger)) + assert.Equal(t, uint32(1000), MaxTxSetSize(ledger)) + assert.Equal(t, uint32(13), LedgerVersion(ledger)) + + var ok bool + var freeWrite int64 + freeWrite, ok = SorobanFeeWrite1Kb(ledger) + assert.Equal(t, true, ok) + assert.Equal(t, int64(12), freeWrite) + + var bucketSize uint64 + bucketSize, ok = TotalByteSizeOfBucketList(ledger) + assert.Equal(t, true, ok) + assert.Equal(t, uint64(56), bucketSize) + + var nodeID string + var err error + nodeID, err = NodeID(ledger) + assert.Equal(t, nil, err) + assert.Equal(t, "GARAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA76O", nodeID) + + var signature string + signature, ok = Signature(ledger) + assert.Equal(t, true, ok) + assert.Equal(t, "9g==", signature) + + var success uint32 + var total uint32 + success, total = TransactionCounts(ledger) + assert.Equal(t, uint32(1), success) + assert.Equal(t, uint32(2), total) + + success, total = OperationCounts(ledger) + assert.Equal(t, uint32(1), success) + assert.Equal(t, uint32(13), total) +} + +func ledgerTestInput() (lcm xdr.LedgerCloseMeta) { + lcm = xdr.LedgerCloseMeta{ + V: 1, + V1: &xdr.LedgerCloseMetaV1{ + Ext: xdr.LedgerCloseMetaExt{ + V: 1, + V1: &xdr.LedgerCloseMetaExtV1{ + SorobanFeeWrite1Kb: xdr.Int64(12), + }, + }, + LedgerHeader: xdr.LedgerHeaderHistoryEntry{ + Hash: xdr.Hash{0x26, 0x93, 0x2d, 0xc4, 0xd8, 0x4b, 0x5f, 0xab, 0xe9, 0xae, 0x74, 0x4c, 0xb4, 0x3c, 0xe4, 0xc6, 0xda, 0xcc, 0xf9, 0x8c, 0x86, 0xa9, 0x91, 0xb2, 0xa1, 0x49, 0x45, 0xb1, 0xad, 0xac, 0x4d, 0x59}, + Header: xdr.LedgerHeader{ + LedgerSeq: 30578981, + TotalCoins: 1054439020873472865, + FeePool: 18153766209161, + BaseFee: 100, + BaseReserve: 5000000, + MaxTxSetSize: 1000, + LedgerVersion: 13, + PreviousLedgerHash: xdr.Hash{0xf6, 0x3c, 0x15, 0xd0, 0xea, 0xf4, 0x8a, 0xfb, 0xd7, 0x51, 0xa4, 0xc4, 0xdf, 0xad, 0xe5, 0x4a, 0x34, 0x48, 0x5, 0x3c, 0x47, 0xc5, 0xa7, 0x1d, 0x62, 0x26, 0x68, 0xae, 0xc, 0xc2, 0xa2, 0x8}, + ScpValue: xdr.StellarValue{ + Ext: xdr.StellarValueExt{ + V: 1, + LcValueSignature: &xdr.LedgerCloseValueSignature{ + NodeId: xdr.NodeId{ + Type: 0, + Ed25519: &xdr.Uint256{34}, + }, + Signature: []byte{0xf6}, + }, + }, + CloseTime: 1594584547, + }, + }, + }, + TotalByteSizeOfBucketList: xdr.Uint64(56), + TxSet: xdr.GeneralizedTransactionSet{ + V: 0, + V1TxSet: &xdr.TransactionSetV1{ + Phases: []xdr.TransactionPhase{ + { + V: 0, + V0Components: &[]xdr.TxSetComponent{ + { + Type: 0, + TxsMaybeDiscountedFee: &xdr.TxSetComponentTxsMaybeDiscountedFee{ + Txs: []xdr.TransactionEnvelope{ + createSampleTx(3), + createSampleTx(10), + }, + }, + }, + }, + }, + }, + }, + }, + TxProcessing: []xdr.TransactionResultMeta{ + { + Result: xdr.TransactionResultPair{ + Result: xdr.TransactionResult{ + Result: xdr.TransactionResultResult{ + Code: xdr.TransactionResultCodeTxSuccess, + Results: &[]xdr.OperationResult{ + { + Code: xdr.OperationResultCodeOpInner, + Tr: &xdr.OperationResultTr{ + Type: xdr.OperationTypeCreateAccount, + CreateAccountResult: &xdr.CreateAccountResult{ + Code: 0, + }, + }, + }, + }, + }, + }, + }, + }, + { + Result: xdr.TransactionResultPair{ + Result: xdr.TransactionResult{ + Result: xdr.TransactionResultResult{ + Code: xdr.TransactionResultCodeTxFailed, + Results: &[]xdr.OperationResult{ + { + Code: xdr.OperationResultCodeOpInner, + Tr: &xdr.OperationResultTr{ + Type: xdr.OperationTypeCreateAccount, + CreateAccountResult: &xdr.CreateAccountResult{ + Code: 0, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + return lcm +} + +func createSampleTx(operationCount int) xdr.TransactionEnvelope { + kp, err := keypair.Random() + panicOnError(err) + + operations := []txnbuild.Operation{} + operationType := &txnbuild.BumpSequence{ + BumpTo: 0, + } + for i := 0; i < operationCount; i++ { + operations = append(operations, operationType) + } + + sourceAccount := txnbuild.NewSimpleAccount(kp.Address(), int64(0)) + tx, err := txnbuild.NewTransaction( + txnbuild.TransactionParams{ + SourceAccount: &sourceAccount, + Operations: operations, + BaseFee: txnbuild.MinBaseFee, + Preconditions: txnbuild.Preconditions{TimeBounds: txnbuild.NewInfiniteTimeout()}, + }, + ) + panicOnError(err) + + env := tx.ToXDR() + return env +} + +// PanicOnError is a function that panics if the provided error is not nil +func panicOnError(err error) { + if err != nil { + panic(err) + } +} diff --git a/xdr/ledger_close_meta.go b/xdr/ledger_close_meta.go index 30e80b2e38..ace0a0f378 100644 --- a/xdr/ledger_close_meta.go +++ b/xdr/ledger_close_meta.go @@ -156,3 +156,15 @@ func (l LedgerCloseMeta) EvictedPersistentLedgerEntries() ([]LedgerEntry, error) panic(fmt.Sprintf("Unsupported LedgerCloseMeta.V: %d", l.V)) } } + +// TxProcessing returns the TransactionResultMeta in this ledger +func (l LedgerCloseMeta) TxProcessing() ([]TransactionResultMeta, error) { + switch l.V { + case 0: + return l.MustV0().TxProcessing, nil + case 1: + return l.MustV1().TxProcessing, nil + default: + return []TransactionResultMeta{}, fmt.Errorf("Unsupported LedgerCloseMeta.V: %d", l.V) + } +} diff --git a/xdr/node_id.go b/xdr/node_id.go new file mode 100644 index 0000000000..7f7420fb76 --- /dev/null +++ b/xdr/node_id.go @@ -0,0 +1,26 @@ +package xdr + +import ( + "fmt" + + "github.com/stellar/go/strkey" +) + +func (n NodeId) GetAddress() (string, error) { + switch n.Type { + case PublicKeyTypePublicKeyTypeEd25519: + ed, ok := n.GetEd25519() + if !ok { + return "", fmt.Errorf("could not get NodeID.Ed25519") + } + raw := make([]byte, 32) + copy(raw, ed[:]) + encodedAddress, err := strkey.Encode(strkey.VersionByteAccountID, raw) + if err != nil { + return "", err + } + return encodedAddress, nil + default: + return "", fmt.Errorf("unknown NodeId.PublicKeyType") + } +} diff --git a/xdr/transaction_envelope.go b/xdr/transaction_envelope.go index 6d513ff342..2cde059e5a 100644 --- a/xdr/transaction_envelope.go +++ b/xdr/transaction_envelope.go @@ -242,3 +242,7 @@ func (e TransactionEnvelope) Memo() Memo { panic("unsupported transaction type: " + e.Type.String()) } } + +func (e TransactionEnvelope) OperationsCount() uint32 { + return uint32(len(e.Operations())) +} From eecf724b0831e3a57d6e2129672fe2a26abd18ba Mon Sep 17 00:00:00 2001 From: urvisavla Date: Wed, 22 Jan 2025 16:47:10 -0800 Subject: [PATCH 2/2] services/horizon: Fix operations participants logic to include InvokeHostFunction operation (#5574) --- services/horizon/CHANGELOG.md | 3 + .../internal/ingest/processor_runner.go | 2 +- .../ingest/processors/operations_processor.go | 71 ++++++++++- .../processors/participants_processor.go | 6 +- .../processors/participants_processor_test.go | 1 + .../transaction_operation_wrapper_test.go | 113 +++++++++++++++++- .../horizon/internal/integration/sac_test.go | 27 ++++- 7 files changed, 216 insertions(+), 7 deletions(-) diff --git a/services/horizon/CHANGELOG.md b/services/horizon/CHANGELOG.md index 6875534c48..9fb5a0e905 100644 --- a/services/horizon/CHANGELOG.md +++ b/services/horizon/CHANGELOG.md @@ -7,6 +7,9 @@ file. This project adheres to [Semantic Versioning](http://semver.org/). - Update default pubnet captive core configuration to replace Whalestack with Creit Technologies in the quorum set ([5564](https://github.com/stellar/go/pull/5564)). +### Fixed +- 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)). + ## 22.0.2 ### Fixed diff --git a/services/horizon/internal/ingest/processor_runner.go b/services/horizon/internal/ingest/processor_runner.go index a4dbfdc656..0170e904f5 100644 --- a/services/horizon/internal/ingest/processor_runner.go +++ b/services/horizon/internal/ingest/processor_runner.go @@ -151,7 +151,7 @@ func (s *ProcessorRunner) buildTransactionProcessor(ledgersProcessor *processors processors.NewOperationProcessor(s.historyQ.NewOperationBatchInsertBuilder(), s.config.NetworkPassphrase), tradeProcessor, processors.NewParticipantsProcessor(accountLoader, - s.historyQ.NewTransactionParticipantsBatchInsertBuilder(), s.historyQ.NewOperationParticipantBatchInsertBuilder()), + s.historyQ.NewTransactionParticipantsBatchInsertBuilder(), s.historyQ.NewOperationParticipantBatchInsertBuilder(), s.config.NetworkPassphrase), processors.NewTransactionProcessor(s.historyQ.NewTransactionBatchInsertBuilder(), s.config.SkipTxmeta), processors.NewClaimableBalancesTransactionProcessor(cbLoader, s.historyQ.NewTransactionClaimableBalanceBatchInsertBuilder(), s.historyQ.NewOperationClaimableBalanceBatchInsertBuilder()), diff --git a/services/horizon/internal/ingest/processors/operations_processor.go b/services/horizon/internal/ingest/processors/operations_processor.go index 39d09b1608..301ec7165a 100644 --- a/services/horizon/internal/ingest/processors/operations_processor.go +++ b/services/horizon/internal/ingest/processors/operations_processor.go @@ -1047,7 +1047,31 @@ func (operation *transactionOperationWrapper) Participants() ([]xdr.AccountId, e case xdr.OperationTypeLiquidityPoolWithdraw: // the only direct participant is the source_account case xdr.OperationTypeInvokeHostFunction: - // the only direct participant is the source_account + if changes, err := operation.transaction.GetOperationChanges(operation.index); err != nil { + return participants, err + } else { + for _, change := range changes { + var data xdr.LedgerEntryData + switch { + case change.Post != nil: + data = change.Post.Data + case change.Pre != nil: + data = change.Pre.Data + default: + log.Errorf("Change Type %s with no pre or post", change.Type.String()) + continue + } + if ledgerKey, err := data.LedgerKey(); err == nil { + participants = append(participants, getLedgerKeyParticipants(ledgerKey)...) + } + } + } + if diagnosticEvents, err := operation.transaction.GetDiagnosticEvents(); err != nil { + return participants, err + } else { + participants = append(participants, getParticipantsFromSACEvents(filterEvents(diagnosticEvents), operation.network)...) + } + case xdr.OperationTypeExtendFootprintTtl: // the only direct participant is the source_account case xdr.OperationTypeRestoreFootprint: @@ -1067,6 +1091,48 @@ func (operation *transactionOperationWrapper) Participants() ([]xdr.AccountId, e return dedupeParticipants(participants), nil } +func getParticipantsFromSACEvents(contractEvents []xdr.ContractEvent, network string) []xdr.AccountId { + var participants []xdr.AccountId + + for _, contractEvent := range contractEvents { + if sacEvent, err := contractevents.NewStellarAssetContractEvent(&contractEvent, network); err == nil { + // 'to' and 'from' fields in the events can be either a Contract address or an Account address. We're + // only interested in account addresses and will skip Contract addresses. + switch sacEvent.GetType() { + case contractevents.EventTypeTransfer: + var transferEvt *contractevents.TransferEvent + transferEvt = sacEvent.(*contractevents.TransferEvent) + var from, to xdr.AccountId + if from, err = xdr.AddressToAccountId(transferEvt.From); err == nil { + participants = append(participants, from) + } + if to, err = xdr.AddressToAccountId(transferEvt.To); err == nil { + participants = append(participants, to) + } + case contractevents.EventTypeMint: + mintEvt := sacEvent.(*contractevents.MintEvent) + var to xdr.AccountId + if to, err = xdr.AddressToAccountId(mintEvt.To); err == nil { + participants = append(participants, to) + } + case contractevents.EventTypeClawback: + clawbackEvt := sacEvent.(*contractevents.ClawbackEvent) + var from xdr.AccountId + if from, err = xdr.AddressToAccountId(clawbackEvt.From); err == nil { + participants = append(participants, from) + } + case contractevents.EventTypeBurn: + burnEvt := sacEvent.(*contractevents.BurnEvent) + var from xdr.AccountId + if from, err = xdr.AddressToAccountId(burnEvt.From); err == nil { + participants = append(participants, from) + } + } + } + } + return participants +} + // dedupeParticipants remove any duplicate ids from `in` func dedupeParticipants(in []xdr.AccountId) []xdr.AccountId { if len(in) <= 1 { @@ -1090,7 +1156,7 @@ func dedupeParticipants(in []xdr.AccountId) []xdr.AccountId { } // OperationsParticipants returns a map with all participants per operation -func operationsParticipants(transaction ingest.LedgerTransaction, sequence uint32) (map[int64][]xdr.AccountId, error) { +func operationsParticipants(transaction ingest.LedgerTransaction, sequence uint32, network string) (map[int64][]xdr.AccountId, error) { participants := map[int64][]xdr.AccountId{} for opi, op := range transaction.Envelope.Operations() { @@ -1099,6 +1165,7 @@ func operationsParticipants(transaction ingest.LedgerTransaction, sequence uint3 transaction: transaction, operation: op, ledgerSequence: sequence, + network: network, } p, err := operation.Participants() diff --git a/services/horizon/internal/ingest/processors/participants_processor.go b/services/horizon/internal/ingest/processors/participants_processor.go index e889e747c0..e97fe91600 100644 --- a/services/horizon/internal/ingest/processors/participants_processor.go +++ b/services/horizon/internal/ingest/processors/participants_processor.go @@ -19,17 +19,21 @@ type ParticipantsProcessor struct { accountLoader *history.AccountLoader txBatch history.TransactionParticipantsBatchInsertBuilder opBatch history.OperationParticipantBatchInsertBuilder + network string } func NewParticipantsProcessor( accountLoader *history.AccountLoader, txBatch history.TransactionParticipantsBatchInsertBuilder, opBatch history.OperationParticipantBatchInsertBuilder, + network string, + ) *ParticipantsProcessor { return &ParticipantsProcessor{ accountLoader: accountLoader, txBatch: txBatch, opBatch: opBatch, + network: network, } } @@ -129,7 +133,7 @@ func (p *ParticipantsProcessor) addOperationsParticipants( sequence uint32, transaction ingest.LedgerTransaction, ) error { - participants, err := operationsParticipants(transaction, sequence) + participants, err := operationsParticipants(transaction, sequence, p.network) if err != nil { return errors.Wrap(err, "could not determine operation participants") } diff --git a/services/horizon/internal/ingest/processors/participants_processor_test.go b/services/horizon/internal/ingest/processors/participants_processor_test.go index f6154b2b39..53c211b7a7 100644 --- a/services/horizon/internal/ingest/processors/participants_processor_test.go +++ b/services/horizon/internal/ingest/processors/participants_processor_test.go @@ -96,6 +96,7 @@ func (s *ParticipantsProcessorTestSuiteLedger) SetupTest() { s.accountLoader, s.mockBatchInsertBuilder, s.mockOperationsBatchInsertBuilder, + networkPassphrase, ) s.txs = []ingest.LedgerTransaction{ diff --git a/services/horizon/internal/ingest/processors/transaction_operation_wrapper_test.go b/services/horizon/internal/ingest/processors/transaction_operation_wrapper_test.go index 78afaa6e71..bfd0e65e02 100644 --- a/services/horizon/internal/ingest/processors/transaction_operation_wrapper_test.go +++ b/services/horizon/internal/ingest/processors/transaction_operation_wrapper_test.go @@ -3,12 +3,16 @@ package processors import ( + "math/big" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" + "github.com/stellar/go/keypair" "github.com/stellar/go/protocols/horizon/base" + "github.com/stellar/go/strkey" + "github.com/stellar/go/support/contractevents" "github.com/stellar/go/ingest" "github.com/stellar/go/services/horizon/internal/db2/history" @@ -1104,7 +1108,7 @@ func TestOperationParticipants(t *testing.T) { xdr.MustAddress("GDRW375MAYR46ODGF2WGANQC2RRZL7O246DYHHCGWTV2RE7IHE2QUQLD"), xdr.MustAddress("GACAR2AEYEKITE2LKI5RMXF5MIVZ6Q7XILROGDT22O7JX4DSWFS7FDDP"), } - participantsMap, err := operationsParticipants(transaction, sequence) + participantsMap, err := operationsParticipants(transaction, sequence, networkPassphrase) tt.NoError(err) tt.Len(participantsMap, 1) for k, v := range participantsMap { @@ -2285,3 +2289,110 @@ func TestDetailsCoversAllOperationTypes(t *testing.T) { _, err := operation.Details() assert.ErrorContains(t, err, "unknown operation type: ") } + +func TestTestInvokeHostFnOperationParticipants(t *testing.T) { + sourceAddress := "GAUJETIZVEP2NRYLUESJ3LS66NVCEGMON4UDCBCSBEVPIID773P2W6AY" + source := xdr.MustMuxedAddress(sourceAddress) + + randomIssuer := keypair.MustRandom() + randomAsset := xdr.MustNewCreditAsset("TESTING", randomIssuer.Address()) + passphrase := "passphrase" + randomAccount := keypair.MustRandom().Address() + + burnEvtAcc := keypair.MustRandom().Address() + mintEvtAcc := keypair.MustRandom().Address() + clawbkEvtAcc := keypair.MustRandom().Address() + transferEvtFromAcc := keypair.MustRandom().Address() + transferEvtToAcc := keypair.MustRandom().Address() + + transferContractEvent := contractevents.GenerateEvent(contractevents.EventTypeTransfer, transferEvtFromAcc, transferEvtToAcc, "", randomAsset, big.NewInt(10000000), passphrase) + mintContractEvent := contractevents.GenerateEvent(contractevents.EventTypeMint, "", mintEvtAcc, randomAccount, randomAsset, big.NewInt(10000000), passphrase) + burnContractEvent := contractevents.GenerateEvent(contractevents.EventTypeBurn, burnEvtAcc, "", randomAccount, randomAsset, big.NewInt(10000000), passphrase) + clawbackContractEvent := contractevents.GenerateEvent(contractevents.EventTypeClawback, clawbkEvtAcc, "", randomAccount, randomAsset, big.NewInt(10000000), passphrase) + + tx1 := ingest.LedgerTransaction{ + UnsafeMeta: xdr.TransactionMeta{ + V: 3, + V3: &xdr.TransactionMetaV3{ + SorobanMeta: &xdr.SorobanTransactionMeta{ + Events: []xdr.ContractEvent{ + transferContractEvent, + burnContractEvent, + mintContractEvent, + clawbackContractEvent, + }, + }, + }, + }, + } + + wrapper1 := transactionOperationWrapper{ + transaction: tx1, + operation: xdr.Operation{ + SourceAccount: &source, + Body: xdr.OperationBody{ + Type: xdr.OperationTypeInvokeHostFunction, + }, + }, + network: passphrase, + } + + participants, err := wrapper1.Participants() + assert.NoError(t, err) + assert.ElementsMatch(t, + []xdr.AccountId{ + xdr.MustAddress(source.Address()), + xdr.MustAddress(mintEvtAcc), + xdr.MustAddress(burnEvtAcc), + xdr.MustAddress(clawbkEvtAcc), + xdr.MustAddress(transferEvtFromAcc), + xdr.MustAddress(transferEvtToAcc), + }, + participants, + ) + + contractId := [32]byte{} + zeroContractStrKey, err := strkey.Encode(strkey.VersionByteContract, contractId[:]) + assert.NoError(t, err) + + transferContractEvent = contractevents.GenerateEvent(contractevents.EventTypeTransfer, zeroContractStrKey, zeroContractStrKey, "", randomAsset, big.NewInt(10000000), passphrase) + mintContractEvent = contractevents.GenerateEvent(contractevents.EventTypeMint, "", zeroContractStrKey, randomAccount, randomAsset, big.NewInt(10000000), passphrase) + burnContractEvent = contractevents.GenerateEvent(contractevents.EventTypeBurn, zeroContractStrKey, "", randomAccount, randomAsset, big.NewInt(10000000), passphrase) + clawbackContractEvent = contractevents.GenerateEvent(contractevents.EventTypeClawback, zeroContractStrKey, "", randomAccount, randomAsset, big.NewInt(10000000), passphrase) + + tx2 := ingest.LedgerTransaction{ + UnsafeMeta: xdr.TransactionMeta{ + V: 3, + V3: &xdr.TransactionMetaV3{ + SorobanMeta: &xdr.SorobanTransactionMeta{ + Events: []xdr.ContractEvent{ + transferContractEvent, + burnContractEvent, + mintContractEvent, + clawbackContractEvent, + }, + }, + }, + }, + } + + wrapper2 := transactionOperationWrapper{ + transaction: tx2, + operation: xdr.Operation{ + SourceAccount: &source, + Body: xdr.OperationBody{ + Type: xdr.OperationTypeInvokeHostFunction, + }, + }, + network: passphrase, + } + + participants, err = wrapper2.Participants() + assert.NoError(t, err) + assert.ElementsMatch(t, + []xdr.AccountId{ + xdr.MustAddress(source.Address()), + }, + participants, + ) +} diff --git a/services/horizon/internal/integration/sac_test.go b/services/horizon/internal/integration/sac_test.go index be06fe32f3..19f27d47ac 100644 --- a/services/horizon/internal/integration/sac_test.go +++ b/services/horizon/internal/integration/sac_test.go @@ -59,7 +59,7 @@ func TestContractMintToAccount(t *testing.T) { itest.Master(), mint(itest, issuer, asset, "20", accountAddressParam(recipient.GetAccountID())), ) - + assertAccountInvokeHostFunctionOperation(itest, recipientKp.Address(), "", recipientKp.Address(), "20.0000000") assertContainsBalance(itest, recipientKp, issuer, code, amount.MustParse("20")) assertAssetStats(itest, assetStats{ code: code, @@ -92,6 +92,7 @@ func TestContractMintToAccount(t *testing.T) { itest.Master(), transfer(itest, issuer, asset, "30", accountAddressParam(otherRecipient.GetAccountID())), ) + assertAccountInvokeHostFunctionOperation(itest, otherRecipientKp.Address(), issuer, otherRecipientKp.Address(), "30.0000000") assertContainsBalance(itest, recipientKp, issuer, code, amount.MustParse("20")) assertContainsBalance(itest, otherRecipientKp, issuer, code, amount.MustParse("30")) @@ -552,7 +553,8 @@ func TestContractTransferBetweenAccounts(t *testing.T) { recipientKp, transfer(itest, recipientKp.Address(), asset, "30", accountAddressParam(otherRecipient.GetAccountID())), ) - + assertAccountInvokeHostFunctionOperation(itest, recipientKp.Address(), recipientKp.Address(), otherRecipientKp.Address(), "30.0000000") + assertAccountInvokeHostFunctionOperation(itest, otherRecipientKp.Address(), recipientKp.Address(), otherRecipientKp.Address(), "30.0000000") assertContainsBalance(itest, recipientKp, issuer, code, amount.MustParse("970")) assertContainsBalance(itest, otherRecipientKp, issuer, code, amount.MustParse("30")) @@ -646,6 +648,7 @@ func TestContractTransferBetweenAccountAndContract(t *testing.T) { recipientKp, transfer(itest, recipientKp.Address(), asset, "30", contractAddressParam(recipientContractID)), ) + assertAccountInvokeHostFunctionOperation(itest, recipientKp.Address(), recipientKp.Address(), strkeyRecipientContractID, "30.0000000") assertContainsBalance(itest, recipientKp, issuer, code, amount.MustParse("970")) assertContainsEffect(t, getTxEffects(itest, transferTx, asset), effects.EffectAccountDebited, effects.EffectContractCredited) @@ -668,6 +671,7 @@ func TestContractTransferBetweenAccountAndContract(t *testing.T) { recipientKp, transferFromContract(itest, recipientKp.Address(), asset, recipientContractID, recipientContractHash, "500", accountAddressParam(recipient.GetAccountID())), ) + assertAccountInvokeHostFunctionOperation(itest, recipientKp.Address(), strkeyRecipientContractID, recipientKp.Address(), "500.0000000") assertContainsEffect(t, getTxEffects(itest, transferTx, asset), effects.EffectContractDebited, effects.EffectAccountCredited) assertContainsBalance(itest, recipientKp, issuer, code, amount.MustParse("1470")) @@ -827,6 +831,7 @@ func TestContractBurnFromAccount(t *testing.T) { recipientKp, burn(itest, recipientKp.Address(), asset, "500"), ) + assertAccountInvokeHostFunctionOperation(itest, recipientKp.Address(), recipientKp.Address(), "", "500.0000000") fx := getTxEffects(itest, burnTx, asset) require.Len(t, fx, 1) @@ -981,6 +986,7 @@ func TestContractClawbackFromAccount(t *testing.T) { itest.Master(), clawback(itest, issuer, asset, "1000", accountAddressParam(recipientKp.Address())), ) + assertAccountInvokeHostFunctionOperation(itest, recipientKp.Address(), recipientKp.Address(), "", "1000.0000000") assertContainsEffect(t, getTxEffects(itest, clawTx, asset), effects.EffectAccountDebited) assertContainsBalance(itest, recipientKp, issuer, code, 0) @@ -1164,6 +1170,23 @@ func getTxEffects(itest *integration.Test, txHash string, asset xdr.Asset) []eff return result } +func assertAccountInvokeHostFunctionOperation(itest *integration.Test, account string, from string, to string, amount string) { + ops, err := itest.Client().Operations(horizonclient.OperationRequest{ + ForAccount: account, + Limit: 1, + Order: "desc", + }) + + assert.NoError(itest.CurrentTest(), err) + result := ops.Embedded.Records[0] + assert.Equal(itest.CurrentTest(), result.GetType(), operations.TypeNames[xdr.OperationTypeInvokeHostFunction]) + invokeHostFn := result.(operations.InvokeHostFunction) + assert.Equal(itest.CurrentTest(), invokeHostFn.Function, "HostFunctionTypeHostFunctionTypeInvokeContract") + assert.Equal(itest.CurrentTest(), to, invokeHostFn.AssetBalanceChanges[0].To) + assert.Equal(itest.CurrentTest(), from, invokeHostFn.AssetBalanceChanges[0].From) + assert.Equal(itest.CurrentTest(), amount, invokeHostFn.AssetBalanceChanges[0].Amount) +} + func assertEventPayments(itest *integration.Test, txHash string, asset xdr.Asset, from string, to string, evtType string, amount string) { ops, err := itest.Client().Operations(horizonclient.OperationRequest{ ForTransaction: txHash,